European ASP.NET 4.5 Hosting BLOG

BLOG about ASP.NET 4, ASP.NET 4.5 Hosting and Its Technology - Dedicated to European Windows Hosting Customer

ASP.NET Core 8 Hosting - HostForLIFE.eu :: Third-party API Integration in the ASP.NET Core Web API

clock February 28, 2024 07:16 by author Peter

ASP.NET Core is a sophisticated framework for developing online APIs that enables developers to build strong and scalable applications. One of the most important aspects of current web development is the use of third-party APIs, which give access to external services and data.

Create a Model

using System.Text.Json.Serialization;

namespace _1_3rdAPIIntegrationInAspNetCoreWebAPI.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public double DiscountPercentage { get; set; }
        public double Rating { get; set; }
        public int Stock { get; set; }
        public string Brand { get; set; }
        public string Category { get; set; }
        public string Thumbnail { get; set; }
        public List<string> Images { get; set; }
    }
}

Here's a breakdown of each property.

  • ID: An integer representing the unique identifier of the product.
  • Title: A string representing the title or name of the product.
  • Description: A string representing the description or details of the product.
  • Price: A decimal representing the price of the product.
  • DiscountPercentage: A double representing the discount percentage applied to the product.
  • Rating: A double representing the rating of the product (e.g., star rating).
  • Stock: An integer representing the available quantity of the product in stock.
  • Brand: A string representing the brand or manufacturer of the product.
  • Category: A string representing the category or type of the product.
  • Thumbnail: A string representing the URL or path to the thumbnail image of the product.
  • Images: A list of strings representing the URLs or paths to additional images of the product.


Create the Interface for the Product Service
using _1_3rdAPIIntegrationInAspNetCoreWebAPI.Models;

namespace _1_3rdAPIIntegrationInAspNetCoreWebAPI.Interfaces
{
public interface IProducts
{
    Task<List<ProductsResponse>> GetProductsAsync();
}
}


GetProductsAsync: This function returns a Task<List<ProductsResponse>>. It indicates that implementing classes will enable the asynchronous retrieval of a list of products. The List<ProductsResponse> contains product responses, including IDs, titles, descriptions, and pricing. The method is asynchronous, as shown by the Task return type, which means that it can be anticipated for asynchronous execution.

Create the service for the products
using _1_3rdAPIIntegrationInAspNetCoreWebAPI.Interfaces;
using _1_3rdAPIIntegrationInAspNetCoreWebAPI.Models;
using System.Net;
using System.Text.Json;

namespace _1_3rdAPIIntegrationInAspNetCoreWebAPI.Services
{
public class ProductService : IProducts
{
    private static readonly HttpClient httpClient;

    static ProductService()
    {
        httpClient = new HttpClient()
        {
            BaseAddress = new Uri("https://dummyjson.com/")
        };

    }

    public async Task<List<ProductsResponse>> GetProductsAsync()
    {
        try
        {
            var url = string.Format("products");
            var result = new List<ProductsResponse>();
            var response = await httpClient.GetAsync(url);

            if (response.IsSuccessStatusCode)
            {
                var stringResponse = await response.Content.ReadAsStringAsync();
                var productsResponse = JsonSerializer.Deserialize<ProductsResponse>(stringResponse, new JsonSerializerOptions()
                {
                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
                });

                // Add the deserialized ProductsResponse to the result list
                result.Add(productsResponse);
            }
            else
            {
                if (response.StatusCode == HttpStatusCode.NotFound)
                {
                    throw new Exception("Products not found.");
                }
                else
                {
                    throw new Exception("Failed to fetch data from the server. Status code: " + response.StatusCode);
                }
            }

            return result;

        }
        catch (HttpRequestException ex)
        {
            throw new Exception("HTTP request failed: " + ex.Message);
        }
        catch (JsonException ex)
        {
            throw new Exception("JSON deserialization failed: " + ex.Message);
        }
        catch (Exception ex)
        {
            throw new Exception("An unexpected error occurred: " + ex.Message);
        }
    }
}
}

Create the Controller for the Products
using _1_3rdAPIIntegrationInAspNetCoreWebAPI.Interfaces;
using _1_3rdAPIIntegrationInAspNetCoreWebAPI.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace _1_3rdAPIIntegrationInAspNetCoreWebAPI.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class ProductsController : ControllerBase
{
    private readonly IProducts _productService;

    public ProductsController(IProducts productService)
    {
        _productService = productService;
    }

    [HttpGet]
    public async Task<IEnumerable<ProductsResponse>> GetProducts()
    {
        return await _productService.GetProductsAsync();
    }
}
}


Register the Services in IOC Container

using _1_3rdAPIIntegrationInAspNetCoreWebAPI.Interfaces;
using _1_3rdAPIIntegrationInAspNetCoreWebAPI.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddSingleton<IProducts, ProductService>();

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Output



ASP.NET Core 8 Hosting - HostForLIFE.eu :: Hands-On Prompt Engineering with .NET Core

clock February 20, 2024 08:29 by author Peter

Prompt engineering

For conversational AI systems to be effective, prompt engineering is essential. We'll examine how to apply rapid engineering with.NET Core, a flexible framework for creating cross-platform apps, in this practical course. In order to demonstrate several prompt engineering strategies, such as contextual prompts, error management, and response variation, we'll develop a basic chatbot application.

Setting up your development environment
Before we begin, ensure you have the .NET Core SDK installed on your system. You can download it from the official .NET website

Once installed, open your terminal or command prompt and run the following command to verify the installation.
dotnet --version

If the installation was successful, you should see the version of .NET Core.

Creating a new .NET core console application

Let's start by creating a new .NET Core console application. Open your terminal or command prompt and navigate to the directory where you want to create the project. Then, run the following command.
dotnet new console -n ChatbotApp

This command creates a new console application named "ChatbotApp."
Building the Chatbot

Now that we have our project set up, let's build the chatbot functionality. Open the Program.cs file in your preferred code editor and replace the existing code with the following.
using System;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Welcome to the Chatbot!");
        Console.WriteLine("What's your name?");
        string userName = Console.ReadLine();
        Console.WriteLine($"Hello, {userName}! How can I assist you today?"); // Your prompt engineering logic goes here
    }
}


This code prompts the user for their name and greets them accordingly. Let's add some prompt engineering logic to provide a more interactive experience.

Implementing prompt engineering

  • Contextual Prompts: Based on the user's input, we can tailor the prompts to provide relevant responses. For example, if the user asks for help, we can provide assistance prompts.
  • Error Handling: Handle user input errors gracefully. If the user provides invalid input, prompt them to try again.
  • Response Variation: Introduce a variety of responses to make the conversation feel more natural and engaging.


Here's an example of how you can enhance the chatbot with these prompt engineering techniques.
using System;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Welcome to the Chatbot!");
        Console.WriteLine("What's your name?");
        string userName = Console.ReadLine();
        Console.WriteLine($"Hello, {userName}! How can I assist you today?");

        // Continuously prompt the user for input
        while (true)
        {
            string userInput = Console.ReadLine().ToLower();

            // Handle specific user inputs
            if (userInput.Contains("help"))
            {
                Console.WriteLine("Sure, I can help you with that!");
                continue;
            }
            else if (userInput.Contains("quit") || userInput.Contains("exit"))
            {
                Console.WriteLine("Goodbye! Have a great day!");
                break;
            }

            // Default response for other inputs
            Console.WriteLine("I'm sorry, I didn't understand. Can you please rephrase?");
        }
    }
}


Running the Chatbot


To run the chatbot, navigate to the project directory in your terminal or command prompt and execute the following command.
dotnet run

You'll see the chatbot welcome message and prompt for the user's name. Then, you can interact with the chatbot by entering different inputs and observing the prompt engineering logic in action.

In this hands-on tutorial, we explored prompt engineering using .NET Core by building a simple chatbot application. We implemented various prompt engineering techniques, including contextual prompts, error handling, and response variation, to create an engaging conversational experience. By mastering prompt engineering with .NET Core, you can develop conversational interfaces that provide seamless interactions and enhance user engagement.

Start experimenting with prompt engineering in your .NET Core projects today and unleash the full potential of conversational AI systems.



ASP.NET Core 8 Hosting - HostForLIFE.eu :: Describe the.NET 8 Keyed Services

clock February 13, 2024 07:19 by author Peter

Keyed services are a major improvement to the built-in dependency injection (DI) framework of.NET 8. This functionality, which has long been a part of third-party DI frameworks like Autofac and StructureMap, gives developers more freedom and control over how dependencies are delivered to their applications. Now let's explore keyed services in more detail: Understanding Keyed Services


In the past,.NET DI registered services exclusively on the basis of their kind. Although this worked well in most cases, it became less successful when there were several implementations of the same interface. This is mitigated by keyed services, which enable us to link a special "key" to every service registration. An enum, string, or any other item that specifically defines the intended implementation can be used as this key.

Advantages of Services with Keys

  • Flexibility: You can inject particular implementations according to runtime circumstances, configuration settings, or dynamic conditions.
  • Decoupling: Implement different versions of the same interface for distinct uses to keep concerns apart.
  • Maintainability: Clearly name and reference desired dependencies in your code to improve readability.
  • Configurability: Apply several implementations according to configuration files or environment variables.

Consider a scenario where an application requires data storage because of configuration settings or user preferences. We have two implementations of the IDataStore interface: LocalStorage and CloudStorage.

1. Explain interfaces
public interface IDataStore
{
    void SaveData(string data);
}

public class LocalStorage : IDataStore
{
    public void SaveData(string data)
    {
        // Implementation to save data locally
        Console.WriteLine($"Saving data locally: {data}");
    }
}

public class CloudStorage : IDataStore
{
    public void SaveData(string data)
    {
        // Implementation to save data in the cloud
        Console.WriteLine($"Saving data in the cloud: {data}");
    }
}

2. Register Services with Keys
// In Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDataStore, LocalStorage>(key: "local");
    services.AddSingleton<IDataStore, CloudStorage>(key: "cloud");
}


3. Inject and Use Keyed Service
public class MyService
{
    private readonly IKeyedServiceProvider _provider;
    private readonly IConfiguration _configuration;

    public MyService(IKeyedServiceProvider provider, IConfiguration configuration)
    {
        _provider = provider;
        _configuration = configuration;
    }

    public void DoSomething()
    {
        string storageType = _configuration["StorageType"]; // e.g., "local" or "cloud"

        IDataStore store = _provider.GetKeyedService<IDataStore>(storageType);
        store.SaveData("This data will be saved based on the configuration.");
    }
}


4. Run the Application
// Program.cs
public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);

    // Configure services and app...

    var app = builder.Build();

    // Run the app...
}

With their more sophisticated and adaptable approach to dependency injection, keyed services are a great addition to the.NET 8 DI architecture. You can gain more authority and clarity over the design of your program by comprehending their advantages, usage trends, and sophisticated applications. Learn about, play with, and take use of keyed services to create more flexible, dynamic, and organized.NET applications!



ASP.NET Core 8 Hosting - HostForLIFE.eu :: .NET MAUI Dev Express Charts

clock February 6, 2024 05:49 by author Peter

Use Devexpress Charts to give your.NET MAUI projects a boost! This blog post will walk you through the process of utilizing the free lifetime plugin Dev Express to implement the chart in.NET MAUI projects. With so many customization options, this plugin will jump straight to the implementation section.

Establishing the Project
To create a new project, launch Visual Studio 2022 and select Create a new project from the Start box.

Click the Next button after choosing the.NET MAUI App template and MAUI from the All project types drop-down menu in the Create a new project box.

Click the Next button after giving your project a name and selecting an appropriate location in the Configure your new project window.

Click the Create button located in the Additional Information window.

Once the project is created, we can able to see the Android, iOS, Windows, and other running options in the toolbar. Press the emulator or run button to build and run the app.

Install Plugin

  • Library Requirement: The Dev Express's Nuget link should be mapped as a package source and we need to install "DevExpress.Maui.Charts" into our project.
  • Installation via NuGet: Obtain the Charts library by searching for "DevExpress.Maui.Charts" in the NuGet Package Manager.
  • User Interface Guidance: Open the NuGet Package Manager interface to facilitate the installation process.
  • Visual Confirmation: The library, once searched, should appear as "DevExpress.Maui.Charts" in the NuGet interface.

Implementation
First, we need to open "MauiProgram.cs" and include the following namespace and line to allow the app to use the Chart Library.

using DevExpress.Maui;

.UseDevExpress()

Open the MainPage.xaml file and add the following namespace. (the page will be replaced according to you).
xmlns:dxc="clr-namespace:DevExpress.Maui.Charts;assembly=DevExpress.Maui.Charts"

Then, remove the default content and add an instance of the ChartView class to the page.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:dxc="clr-namespace:DevExpress.Maui.Charts;assembly=DevExpress.Maui.Charts"
             x:Class="ScatterChartGetStarted.MainPage">
    <dxc:ChartView/>
</ContentPage>


Consider removing the event handlers from the code behind the default content. It is advisable to eliminate default styles (such as fonts, colors, and other settings) in the App.xaml file as well.

In this instance, the chart features a line series displaying the annual GDP for three countries. Generate a ViewModel.cs file that includes the following classes.
public class ViewModel {
    public CountryGdp GdpValueForUSA { get; }
    public CountryGdp GdpValueForChina { get; }
    public CountryGdp GdpValueForJapan { get; }

    public ViewModel() {
        GdpValueForUSA = new CountryGdp(
            "USA",
            new GdpValue(new DateTime(2020, 1, 1), 20.93),
            new GdpValue(new DateTime(2019, 1, 1), 21.43),
            new GdpValue(new DateTime(2018, 1, 1), 20.58),
            new GdpValue(new DateTime(2017, 1, 1), 19.391),
            new GdpValue(new DateTime(2016, 1, 1), 18.624),
            new GdpValue(new DateTime(2015, 1, 1), 18.121),
            new GdpValue(new DateTime(2014, 1, 1), 17.428),
            new GdpValue(new DateTime(2013, 1, 1), 16.692),
            new GdpValue(new DateTime(2012, 1, 1), 16.155),
            new GdpValue(new DateTime(2011, 1, 1), 15.518),
            new GdpValue(new DateTime(2010, 1, 1), 14.964)
        );
        GdpValueForChina = new CountryGdp(
            "China",
            new GdpValue(new DateTime(2020, 1, 1), 14.72),
            new GdpValue(new DateTime(2019, 1, 1), 14.34),
            new GdpValue(new DateTime(2018, 1, 1), 13.89),
            new GdpValue(new DateTime(2017, 1, 1), 12.238),
            new GdpValue(new DateTime(2016, 1, 1), 11.191),
            new GdpValue(new DateTime(2015, 1, 1), 11.065),
            new GdpValue(new DateTime(2014, 1, 1), 10.482),
            new GdpValue(new DateTime(2013, 1, 1), 9.607),
            new GdpValue(new DateTime(2012, 1, 1), 8.561),
            new GdpValue(new DateTime(2011, 1, 1), 7.573),
            new GdpValue(new DateTime(2010, 1, 1), 6.101)
        );
        GdpValueForJapan = new CountryGdp(
            "Japan",
            new GdpValue(new DateTime(2020, 1, 1), 4.888),
            new GdpValue(new DateTime(2019, 1, 1), 5.082),
            new GdpValue(new DateTime(2018, 1, 1), 4.955),
            new GdpValue(new DateTime(2017, 1, 1), 4.872),
            new GdpValue(new DateTime(2016, 1, 1), 4.949),
            new GdpValue(new DateTime(2015, 1, 1), 4.395),
            new GdpValue(new DateTime(2014, 1, 1), 4.850),
            new GdpValue(new DateTime(2013, 1, 1), 5.156),
            new GdpValue(new DateTime(2012, 1, 1), 6.203),
            new GdpValue(new DateTime(2011, 1, 1), 6.156),
            new GdpValue(new DateTime(2010, 1, 1), 5.700)
        );
    }
}

public class CountryGdp {
    public string CountryName { get; }
    public IList<GdpValue> Values { get; }

    public CountryGdp(string country, params GdpValue[] values) {
        this.CountryName = country;
        this.Values = new List<GdpValue>(values);
    }
}

public class GdpValue {
    public DateTime Year { get; }
    public double Value { get; }

    public GdpValue(DateTime year, double value) {
        this.Year = year;
        this.Value = value;
    }
}

In the MainPage.xaml file, incorporate three LineSeries objects into the ChartView.Series collection. To establish a connection between the series and data, assign each LineSeries object's Data property to a SeriesDataAdapter object. Utilize the adapter's properties to indicate the data source and fields containing arguments and values for each series.

Additionally, define a local XAML namespace referring to a CLR namespace encompassing the view model. Subsequently, employ the page's BindingContext property to link the view model with the view.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:dxc="clr-namespace:DevExpress.Maui.Charts;assembly=DevExpress.Maui.Charts"
             xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls"
             ios:Page.UseSafeArea="True"
             xmlns:local="clr-namespace:MauiDevExpress"
             x:Class="MauiDevExpress.MainPage">
    <ContentPage.BindingContext>
        <local:ViewModel/>
    </ContentPage.BindingContext>
    <dxc:ChartView>
        <dxc:ChartView.Series>
            <dxc:LineSeries DisplayName="{Binding GdpValueForUSA.CountryName}">
                <dxc:LineSeries.Data>
                    <dxc:SeriesDataAdapter DataSource="{Binding GdpValueForUSA.Values}"
                                           ArgumentDataMember="Year">
                        <dxc:ValueDataMember Type="Value" Member="Value"/>
                    </dxc:SeriesDataAdapter>
                </dxc:LineSeries.Data>
            </dxc:LineSeries>

            <dxc:LineSeries DisplayName="{Binding GdpValueForChina.CountryName}">
                <dxc:LineSeries.Data>
                    <dxc:SeriesDataAdapter DataSource="{Binding GdpValueForChina.Values}"
                                           ArgumentDataMember="Year">
                        <dxc:ValueDataMember Type="Value" Member="Value"/>
                    </dxc:SeriesDataAdapter>
                </dxc:LineSeries.Data>
            </dxc:LineSeries>

            <dxc:LineSeries DisplayName="{Binding GdpValueForJapan.CountryName}">
                <dxc:LineSeries.Data>
                    <dxc:SeriesDataAdapter DataSource="{Binding GdpValueForJapan.Values}"
                                           ArgumentDataMember="Year">
                        <dxc:ValueDataMember Type="Value" Member="Value"/>
                    </dxc:SeriesDataAdapter>
                </dxc:LineSeries.Data>
            </dxc:LineSeries>
        </dxc:ChartView.Series>
    </dxc:ChartView>
</ContentPage>

Configure the X-axis to display labels for years by assigning a DateTimeAxisX object with the specified settings to the ChartView.AxisX property.
<dxc:ChartView> <dxc:ChartView.AxisX>
    <dxc:DateTimeAxisX MeasureUnit="Year" GridAlignment="Year"
    GridSpacing="2"/> </dxc:ChartView.AxisX> </dxc:ChartView>


Configure the title and labels on the Y-axis. Set the ChartView.AxisY property to a NumericAxisY object and specify this object’s Title and Label properties.
<dxc:ChartView>
<!-- The X-axis config is here. -->
    <dxc:ChartView.AxisY>
        <dxc:NumericAxisY>
            <dxc:NumericAxisY.Title>
                <dxc:AxisTitle Text="Trillions of US$">
                    <dxc:AxisTitle.Style>
                        <dxc:TitleStyle>
                            <dxc:TitleStyle.TextStyle>
                                <dxc:TextStyle Size="16"/>
                            </dxc:TitleStyle.TextStyle>
                        </dxc:TitleStyle>
                    </dxc:AxisTitle.Style>
                </dxc:AxisTitle>
            </dxc:NumericAxisY.Title>
            <dxc:NumericAxisY.Label>
                <dxc:AxisLabel TextFormat="#.#" Position="Inside"/>
            </dxc:NumericAxisY.Label>
        </dxc:NumericAxisY>
    </dxc:ChartView.AxisY>
</dxc:ChartView>

Configure the legend position and orientation. Set the ChartView.Legend property to a Legend object, and specify this object’s properties as follows.
<dxc:ChartView>
    <dxc:ChartView.Legend>
        <dxc:Legend VerticalPosition="TopOutside"
                    HorizontalPosition="Center"
                    Orientation="LeftToRight"/>
    </dxc:ChartView.Legend>
</dxc:ChartView>

Establish the chart to showcase a series point hint as a crosshair cursor by setting the ChartView.Hint property to a Hint object and assigning a CrosshairHintBehavior object to Hint.Behavior. Subsequently, define the hint's content, data format, and visibility options. Set the LineSeries.HintOptions property to a SeriesCrosshairOptions object with the specified settings.
<ContentPage.Resources>
    <dxc:SeriesCrosshairOptions x:Key="lineSeriesHintOptions"
                                PointTextPattern="{}{S}: {V}M"
                                ShowInLabel="True"
                                AxisLabelVisible="True"
                                AxisLineVisible="True"/>
</ContentPage.Resources>
<dxc:ChartView>
    <dxc:ChartView.Hint>
        <dxc:Hint>
            <dxc:Hint.Behavior>
                <dxc:CrosshairHintBehavior GroupHeaderTextPattern="{}{A$YYYY}"
                                           MaxSeriesCount="3"/>
            </dxc:Hint.Behavior>
        </dxc:Hint>
    </dxc:ChartView.Hint>

    <dxc:ChartView.Series>
        <dxc:LineSeries HintOptions="{StaticResource lineSeriesHintOptions}">
            <!--Series Data-->
        </dxc:LineSeries>
        <dxc:LineSeries HintOptions="{StaticResource lineSeriesHintOptions}">
            <!--Series Data-->
        </dxc:LineSeries>
        <dxc:LineSeries HintOptions="{StaticResource lineSeriesHintOptions}">
            <!--Series Data-->
        </dxc:LineSeries>
    </dxc:ChartView.Series>
</dxc:ChartView>


Set the LineSeries.MarkersVisible property to True to display point markers. To change the line series appearance, set the LineSeries.Style property to a LineSeriesStyle object. This object’s Stroke, StrokeThickness, MarkerSize, and MarkerStyle properties allow you to configure the appearance of the series line and point markers.
<dxc:LineSeries MarkersVisible="True">
    <!--Series Data-->
    <dxc:LineSeries.Style>
        <dxc:LineSeriesStyle Stroke="#7145a7" StrokeThickness="2" MarkerSize="8">
            <dxc:LineSeriesStyle.MarkerStyle>
                <dxc:MarkerStyle Fill="#7145a7"/>
            </dxc:LineSeriesStyle.MarkerStyle>
        </dxc:LineSeriesStyle>
    </dxc:LineSeries.Style>
</dxc:LineSeries>




About HostForLIFE.eu

HostForLIFE.eu is European Windows Hosting Provider which focuses on Windows Platform only. We deliver on-demand hosting solutions including Shared hosting, Reseller Hosting, Cloud Hosting, Dedicated Servers, and IT as a Service for companies of all sizes.

We have offered the latest Windows 2016 Hosting, ASP.NET Core 2.2.1 Hosting, ASP.NET MVC 6 Hosting and SQL 2017 Hosting.


Tag cloud

Sign in