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

European ASP.NET Core 9.0 Hosting - HostForLIFE :: Working with JSON in .NET Core: Newtonsoft.Json, NetJSON, and System.Text.Json

clock November 25, 2024 06:22 by author Peter

I'll go over three widely used approaches to handle JSON in.NET in this post. JavaScript Object Notation, or JSON, is a text-based, lightweight format that is frequently used for data interchange. There are various ways to work with JSON in.NET, each with advantages of its own and appropriate for particular situations. You can select from a variety of libraries, each designed to meet specific needs. Here are three well-liked choices: NetJSON, System.Text.Json, and Newtonsoft.Json, as well as code samples to get you started.

Working With Json in ASP.NET

  • Using Newtonsoft.Json (Json.NET)
  • Using System.Text.Json (Built-in .NET Core Library)
  • Using NetJSON (High-Performance JSON Library)

Let's create an employee class and prepare data to work with different JSON parsing libraries.
public class Employee
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public string? Department { get; set; }
}


Now, prepare the data with a list of employees.
static List<Employee> Getemployees()
{
    var employees = new List<Employee>
        {
            new Employee { Id = 1, Name = "John Doe", Department = "HR" },
            new Employee { Id = 2, Name = "Jane Smith", Department = "IT" },
            new Employee { Id = 3, Name = "Mike Johnson", Department = "Finance" }
        };
    return employees;
}

Using Newtonsoft.Json (Json.NET)
How do you install the Newtonsoft.Json package using the Manage NuGet Packages feature?

Install Newtonsoft.Json package
Search for the Newtonsoft.Json package in the NuGet package list as shown below.

Search package and install package
Let's look at an example where we serialize a list of employees into a JSON string and then deserialize the JSON string back into a list of employees.
static void JsonUsingNewtonsoft(List<Employee> employees)
{
    // Serialize to JSON
    string json = JsonConvert.SerializeObject(employees);
    Console.WriteLine("Serialized JSON (Newtonsoft.Json):");
    Console.WriteLine(json);

    // Deserialize back to Employee object
    List<Employee> employeeList = JsonConvert.DeserializeObject<List<Employee>>(json);
    Console.WriteLine("\nDeserialized Employee:");
    foreach (Employee employee in employeeList)
    {
        Console.WriteLine($"Id: {employee.Id}, Name: {employee.Name}, Department: {employee.Department}");
    }
    Console.ReadLine();
}

The snippet above shows the output of the code using the Newtonsoft.Json package.

Using System.Text.Json (Built-in .NET Core Library)

System.Text.Json, introduced in .NET Core 3.0, is the default and highly efficient library for processing JSON in .NET. It offers powerful features for serialization, deserialization, and parsing JSON documents.
static void JsonUsingSystemTextJson(List<Employee> employees)
{
    // Serialize to JSON using System.Text.Json
    string json = JsonSerializer.Serialize(employees);
    Console.WriteLine("Serialized JSON (System.Text.Json):");
    Console.WriteLine(Environment.NewLine);
    Console.WriteLine(json);

    // Deserialize back to Employee object using System.Text.Json
    List<Employee> employeeList = JsonSerializer.Deserialize<List<Employee>>(json);
    Console.WriteLine("\nDeserialized Employee (System.Text.Json):");

    foreach (Employee employee in employeeList)
    {
        Console.WriteLine($"Id: {employee.Id}, Name: {employee.Name}, Department: {employee.Department}");
    }
}

The snippet above shows the output of the code using the

Using NetJSON (High-Performance JSON Library)

NetJSON is a high-performance, lightweight JSON library for .NET, designed for speed and minimal memory usage. It is ideal for performance-critical scenarios, such as processing large datasets, high-throughput applications, or real-time systems. While it lacks the extensive customization options of libraries like Newtonsoft.Json, it stands out for its simplicity and exceptional performance.

Install NetJSON Package from NuGet Package Manager
Here’s an example demonstrating the use of the NetJSON package to serialize a list of employees into a JSON string and then deserialize the JSON string back into a list of employees.
static void JsonUsingNetJSON(List<Employee> employees)
{
    // Serialize to JSON using NetJSON
    string json = NetJSON.NetJSON.Serialize(employees);
    Console.WriteLine("Serialized JSON (NetJSON):");
    Console.WriteLine(Environment.NewLine);
    Console.WriteLine(json);

    // Deserialize back to Employee object using NetJSON
    List<Employee> employeeList = NetJSON.NetJSON.Deserialize<List<Employee>>(json);
    Console.WriteLine("\nDeserialized Employee (NetJSON):");

    foreach (Employee employee in employeeList)
    {
        Console.WriteLine($"Id: {employee.Id}, Name: {employee.Name}, Department: {employee.Department}");
    }
}

The snippet above shows the output of the code using the NetJSON package.

Let's put all methods together in a single class.
using JsonExample;
using Newtonsoft.Json;
using JsonSerializer = System.Text.Json.JsonSerializer;

var employeeList = Getemployees();

// Using Newtonsoft.Json
JsonUsingNewtonsoft(employeeList);

// Using System.Text.Json .Net Core built-in library
JsonUsingSystemTextJson(employeeList);

// Using NetJSON
JsonUsingNetJSON(employeeList);

Console.ReadLine();

static List<Employee> Getemployees()
{
    var employees = new List<Employee>
    {
        new Employee { Id = 1, Name = "John Doe", Department = "HR" },
        new Employee { Id = 2, Name = "Jane Smith", Department = "IT" },
        new Employee { Id = 3, Name = "Mike Johnson", Department = "Finance" }
    };
    return employees;
}

static void JsonUsingNewtonsoft(List<Employee> employees)
{
    // Serialize to JSON
    string json = JsonConvert.SerializeObject(employees);
    Console.WriteLine("Serialized JSON (Newtonsoft.Json):");
    Console.WriteLine(Environment.NewLine);
    Console.WriteLine(json);

    // Deserialize back to Employee object
    List<Employee> employeeList = JsonConvert.DeserializeObject<List<Employee>>(json);
    Console.WriteLine("\nDeserialized Employee:");
    foreach (Employee employee in employeeList)
    {
        Console.WriteLine($"Id: {employee.Id}, Name: {employee.Name}, Department: {employee.Department}");
    }
}

static void JsonUsingSystemTextJson(List<Employee> employees)
{
    // Serialize to JSON using System.Text.Json
    string json = JsonSerializer.Serialize(employees);
    Console.WriteLine("Serialized JSON (System.Text.Json):");
    Console.WriteLine(Environment.NewLine);
    Console.WriteLine(json);

    // Deserialize back to Employee object using System.Text.Json
    List<Employee> employeeList = JsonSerializer.Deserialize<List<Employee>>(json);
    Console.WriteLine("\nDeserialized Employee (System.Text.Json):");
    foreach (Employee employee in employeeList)
    {
        Console.WriteLine($"Id: {employee.Id}, Name: {employee.Name}, Department: {employee.Department}");
    }
}

static void JsonUsingNetJSON(List<Employee> employees)
{
    // Serialize to JSON using NetJSON
    string json = NetJSON.NetJSON.Serialize(employees);
    Console.WriteLine("Serialized JSON (NetJSON):");
    Console.WriteLine(Environment.NewLine);
    Console.WriteLine(json);

    // Deserialize back to Employee object using NetJSON
    List<Employee> employeeList = NetJSON.NetJSON.Deserialize<List<Employee>>(json);
    Console.WriteLine("\nDeserialized Employee (NetJSON):");
    foreach (Employee employee in employeeList)
    {
        Console.WriteLine($"Id: {employee.Id}, Name: {employee.Name}, Department: {employee.Department}");
    }
}


Recommendation. Choosing the Right JSON Library in the Real-time Project.

  • System.Text.Json: Best suited for modern .NET projects due to its performance and native integration.
  • Newtonsoft.Json: Ideal for projects requiring compatibility or advanced customization features.
  • NetJSON: Recommended for performance-critical scenarios such as real-time systems or large-scale data processing.

Note. Each approach serves different needs, and your choice depends on project requirements and performance considerations

Summary

This article covers three popular methods for working with JSON in .NET, highlighting their unique advantages and ideal use cases. JSON is a lightweight, text-based format commonly used for data exchange, and .NET provides versatile tools to handle it effectively. Note. I have provided a working solution compatible with Visual Studio 2022, which you can download from the top left corner of the article. Simply click the download icon to get the .zip file.



European ASP.NET Core 9.0 Hosting - HostForLIFE :: .NET 9 : Task.WhenEach

clock November 19, 2024 07:15 by author Peter

In .NET 9, a new method, Task.WhenEach, has been introduced to streamline asynchronous programming. This method allows you to process tasks as they complete, rather than waiting for all tasks to finish. This is particularly useful in scenarios where tasks have varying completion times and you want to act on each one as soon as it's done.


Step 1. Create a function named PrintWithDelay
async Task<int> PrintWithDelay(int delay)
{
await Task.Delay(delay);
return delay;
}


This code defines an asynchronous method named PrintWithDelay that takes an integer delay as input and returns an integer.

async Task<int>

  • async: This keyword indicates that the method is asynchronous, meaning it can yield execution to other tasks while waiting for I/O operations or other asynchronous operations to complete.
  • Task<int>: This specifies the return type of the method. It will return a Task object that, when awaited, will yield the integer result.

await Task.Delay(delay)

  • Task.Delay(delay): This creates a new task that will complete after the specified delay milliseconds.
  • await: This keyword pauses the execution of the current method until the Task.Delay task completes. While waiting, the thread can be used by other tasks.

return delay
Once the delay has elapsed, the method resumes execution and returns the original delay value.

Step 2. Create a list of tasks that will each execute the PrintWithDelay method with different delay values.
List<Task<int>> printTasks = [
PrintWithDelay(4000),
PrintWithDelay(6000),
PrintWithDelay(2000)
];

  • This declares a list named printTasks to store tasks. Each task in this list will return an integer.
  • There are three calls to the PrintWithDelay method, each with a different delay value (4000, 6000, and 2000 milliseconds, respectively).
  • Each call returns a Task<int>, representing an asynchronous operation that will eventually return an integer.
  • These tasks are added to the printTasks list.

Step 3. Utilize Task.WhenEach in .NET 9.

Task.WhenEach yields an IAsyncEnumerable, allowing asynchronous processing of tasks as they complete.
await foreach (var task in Task.WhenEach(printTasks))
{
Console.WriteLine(await task);
}

Task.WhenEach(printTasks)

  • This part takes a collection of Task<int> objects (stored in printTasks).
  • It returns an IAsyncEnumerable<Task<int>>. This enumerable represents a sequence of tasks that will complete over time.

await foreach (var task in Task.WhenEach(printTasks))

  • This is an asynchronous foreach loop that iterates over the IAsyncEnumerable returned by Task.WhenEach.
  • The await keyword signifies that the loop will pause execution until the next task in the sequence completes.

In short, the code does the following

  • Schedules Tasks: The printTasks are scheduled to run asynchronously.
  • Processes Completed Tasks: As each task completes, it is yielded from the Task.WhenEach enumerable.
  • Logs Task Completion: The await foreach loop iterates over these completed tasks, and for each one, it logs a message to the console, which includes the task's status and result.

Output


By leveraging Task.WhenEach, you can write more efficient and responsive asynchronous code in .NET 9. Happy Coding!



European ASP.NET Core 9.0 Hosting - HostForLIFE :: A Beginner's Guide to.NET Core 8 Web API CRUD Operations

clock November 13, 2024 07:59 by author Peter

Because it enables the development of applications that expand and adapt to the requirements of various services and platforms, web API development is a crucial part of the current development scenario. CRUD actions, which enable the fundamental interface with the application's data, are one of the cornerstone procedures in the development of APIs. Starting with CRUD operations makes perfect sense if you are "green" with.NET Core because it teaches you the basics of creating a Web API and handling data.

The creation of a.NET Core 8 Web API project and the specification of the endpoints for each CRUD operation will be covered in this tutorial. Effective data handling techniques are outlined by the Entity Framework Core, an ORM system made to communicate with.NET databases. By the end of this article, you will have mastered the fundamentals of RESTful design in.NET Core and developed a functional Web API that can manipulate data.

This tutorial is intended for both a novice front-end developer looking to hone their skills and an aspiring backend developer with experience in.NET Core 8 API development. Therefore, you will undoubtedly learn how to begin creating APIs in.NET Core 8 if you fit into either of the two categories. Let's go over this initial phase.

Open Visual Studio 2022 and Choose "Create a new project".

On the Create a new project page, search for "Web API" on the search bar, select the project template and press the "next" button.

On the configuration of the project, enter the Project Name and choose the check box to keep the solution file and project in the same directory.

To configure the Framework version, tick the boxes according to the screenshot, and click "create" on the project's additional details page. To get things started, it will generate the project using the default files.

Initially, the project folder structure looked like this.

First, we need to install the packages required for the ORM to interact with the Database. To install the packages, Right click on the solution and choose "Manage NuGet Packages... "

On the NuGet page, search for the below two packages and install the versions above 8.
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.SqlServer

Then, Right-click on the solution. Create a new Class file named Employee.cs and paste the code.
namespace EmployeePortal.Models.Entities
{
    public class Employee
    {
        public Guid Id { get; set; }
        public required string Name { get; set; }
        public required string Email { get; set; }
        public required string PhoneNumber { get; set; }
        public decimal Salary { get; set; }
    }
}


Then, we need to create a DB Context file for the application that holds the configuration for the ORM and its Entities. Create a file named ApplicationDbContext.cs and paste the below code.
using EmployeePortal.Models.Entities;
using Microsoft.EntityFrameworkCore;
namespace EmployeePortal
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
        public DbSet<Employee> Employees { get; set; }
    }
}

Then, we need to add a DB connection string to the appsettings.json file.
"ConnectionStrings": {
  "DefaultConnection": "Server=your_server_name;Database=your_database_name;User Id=your_username;Password=your_password;TrustServerCertificate=True;"
}


Then, we need to add the SQL Server services to our Program.cs file and add the below code to the program file under the services, which tells the application to use the SQL server from the connection string.
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(
    builder.Configuration.GetConnectionString("DefaultConnection")));


Now, we add a migration to create a snapshot of our entities from the application because we are using the Code First Approach in EF. So, we need to specify the entities and their relationships, and then we run the migration which will create a Database and tables based on the relationship of the entities.

Open the package manager console and run the following commands.

  • add-migration "initial one": generates a migration file based on the current state of your data models compared to the database schema.
  • update-database: applies the migration to your database, creating or altering tables, columns, or relationships as defined in the migration file.

Now, If you open the SQL server, you can see the database and the tables.


We need to create a Web API controller to fetch the data from the database through the endpoints. Right-click on the controller's folder and choose to add a new Web API Controller.


You can request the endpoints using the necessary information.

Endpoints and their use cases with return types

  • Get All Employees: it is GET api/employees. Method: GetEmployees(). Purpose: Fetches all the employees from the store. Response: Returns an Ok (HTTP 200) status with the body, which contains the list of employees.
  • Get Employee by ID: it is GET api/employees/{id}. Method: GetEmployeeById(Guid id). Purpose: Use unique IDs to retrieve an employee. Response: Returns an Ok (HTTP 200) status and data about an employee when such is available, however, there is a risk that if no such employees can be traced then one will be returned as NotFound (HTTP 404).
  • Add a New Employee: it is POST api/employees. Method: AddEmployee(EmployeeDto employees). Purpose: Whenever an employee Dto record is transmitted to the application, the application updates the defined employees in the store. Response: Returned 201 Created status whenever an employer is successfully added.
  • Update Employee: it is PUT api/employees/{id}. Method: UpdateEmployee(Guid id, UpdateEmployeeDto employeeDto). Purpose: One has to search the employee by the unique ID and change his information. Response: If the updates work effectively, they return an Ok (HTTP 200) status, while the NotFound (HTTP 404) will be resolved in situations where no suitable culprit persons are available.
  • Delete Employee: it is DELETE api/employees/{id}. Method: DeleteEmployee(Guid id). Purpose: When databases are directed to delete certain profiles, the corresponding unique ID numbers are used. Response: If it works, returns NoContent (HTTP 204) in situations where the deletion was successful, whereas NotFound (HTTP 404) if no matching employee is found.

Conclusion
In this article, we explored a basic implementation of CRUD operations for managing employee data in a .NET Core Web API. By following these steps, we created endpoints to add, retrieve, update, and delete employees, using DTOs to encapsulate and simplify data transfer. This approach helps establish a solid foundation for building RESTful APIs and managing data flow in a secure and organized manner.



European ASP.NET Core 9.0 Hosting - HostForLIFE :: Explaning IExceptionFilter in .NET Core

clock November 4, 2024 12:50 by author Peter

So, Let's get started.


Exception Filter in ASP.NET Core
Exception Filter allows us to handle unhandled exceptions that occur while processing an HTTP request within our application. Exception Filters in ASP.NET Core Applications are used for tasks such as Logging Exceptions, Returning Custom Error Responses, and Performing any other necessary action when an exception occurs.

Exception Filter also provides a way to centralize the exception-handling logic and keep it separate from our controller or from the business and data access logic, making our code more organized and maintainable.

IExceptionFilter

  • IExceptionFilter is an interface in ASP.NET Core that provides a mechanism for handling exceptions that occur during the processing of a request. By implementing IExceptionFilter, you can write custom logic to handle exceptions globally or per-controller.
  • Advantages of IExceptionFilter
  • Centralized Exception Handling: You can manage all your exceptions in a single place, making it easier to maintain and modify your exception-handling strategy.
  • Separation of Concerns: By handling exceptions separately, you keep the error-handling logic away from your business logic, improving code readability and maintainability.
  • Consistent Error Responses: It allows you to standardize the way errors are reported back to the client, which can improve the API's usability. You can return consistent model formats, error codes, and messages.
  • Access to HttpContext: Since filters have access to the `HttpContext`, you can easily log errors, modify responses, or perform any other operation based on the context of the request.
  • Interception of All Exceptions: It can catch exceptions that aren't handled anywhere else, ensuring that your application can respond gracefully to unexpected errors.
  • Custom Logic: You can implement any custom logic needed for exception handling, such as logging specific exceptions differently.

Disadvantages of IExceptionFilter

  • Global Scope: When implemented globally, all exceptions will be handled by the same filter. This might not be desirable if different controllers or actions require different handling strategies.
  • Complex Error Handling Logic: If you have complex error-handling needs, managing too many unique cases in a single filter could lead to convoluted code.
  • Performance Concerns: Introducing additional logic in exception handling can potentially add overhead, especially if the handling involves extensive processing or logging.
  • Limited to Web Context: Unlike middleware, exception filters are limited in scope to the MVC pipeline. They cannot handle exceptions that occur outside of the controller actions, such as in middleware.
  • Difficulty in Testing: Since exception filters are tied to the ASP.NET injection system, they can introduce complexity when writing unit tests, particularly if they depend on the HttpContext.

Implementing IExceptionFilter
Implementing IExceptionFilter can greatly benefit your ASP.NET Core applications by providing structured and centralized exception handling. However, balance must be struck in how it's used to avoid complexity, ensure performance, and maintain flexibility. Choosing the right approach to exception handling may also involve combining it with other options like middleware, custom error pages, or even using logged service responses as needed.
public class HandleExceptionFilter : IExceptionFilter
    {
      private readonly ILogger<HandleExceptionFilter> _logger;
      public HandleExceptionFilter(ILogger<HandleExceptionFilter> logger)
        {
            _logger = logger;
        }

      public override void OnException(ExceptionContext filterContext)
        {
            bool isAjaxCall = filterContext.HttpContext.Request.Headers["x-requested-with"] == "XMLHttpRequest";
            filterContext.HttpContext.Session.Clear();

            if (isAjaxCall)
            {
                filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                var data = new
                {
                    filterContext.Exception.Message,
                    filterContext.Exception.StackTrace
                };
                filterContext.Result = new JsonResult(data);
                filterContext.ExceptionHandled = true;
            }

            if (!isAjaxCall)
            {
                filterContext.Result = new RedirectResult("/Error/Error");
            }

            _logger.LogError(GetExceptionDetails(filterContext.Exception));

            filterContext.ExceptionHandled = true;
            base.OnException(filterContext);

        }

       private string GetExceptionDetails(Exception exception)
        {
            var properties = exception.GetType()
                .GetProperties();
            var fields = properties
                .Select(property => new
                {
                    Name = property.Name,
                    Value = property.GetValue(exception, null)
                })
                .Select(x => $"{x.Name} = {(x.Value != null ? x.Value.ToString() : String.Empty)}");
            return String.Join("\n", fields);
        }
    }


// Register the filter in Startup.cs

public void ConfigureServices(IServiceCollection services)
  {
    services.AddControllers(options =>
     {
         options.Filters.Add<HandleExceptionFilter>();
     });
  }

// Above .net 6

builder.Services.AddScoped<HandleExceptionFilter>();

Then, add the name as EmployeesController.cs and paste the code below.
using EmployeeAdminPortal.Data;
using EmployeeAdminPortal.DTO;
using EmployeeAdminPortal.Models.Entities;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace EmployeeAdminPortal.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeesController : ControllerBase
    {
        private readonly ApplicationDbContext _dbContext;

        public EmployeesController(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        [HttpGet]
        public IActionResult GetEmployees()
        {
            return Ok(_dbContext.Employees);
        }

        [HttpGet]
        [Route("{id:guid}")]
        public IActionResult GetEmployeeById(Guid id)
        {
            var employee = _dbContext.Employees.Find(id);
            if (employee == null)
            {
                return NotFound();
            }
            return Ok(employee);
        }

        [HttpPost]
        public IActionResult AddEmployee(EmployeeDto employeeDto)
        {
            var employee = new Employee
            {
                Name = employeeDto.Name,
                Email = employeeDto.Email,
                PhoneNumber = employeeDto.PhoneNumber,
                Salary = employeeDto.Salary
            };
            _dbContext.Employees.Add(employee);
            _dbContext.SaveChanges();
            return StatusCode(StatusCodes.Status201Created);
        }

        [HttpPut]
        [Route("{id:guid}")]
        public IActionResult UpdateEmployee(Guid id, UpdateEmployeeDto employeeDto)
        {
            var employee = _dbContext.Employees.Find(id);
            if (employee == null)
            {
                return NotFound();
            }
            employee.Name = employeeDto.Name;
            employee.Email = employeeDto.Email;
            employee.PhoneNumber = employeeDto.PhoneNumber;
            employee.Salary = employeeDto.Salary;
            _dbContext.SaveChanges();
            return Ok(employee);
        }

        [HttpDelete]
        [Route("{id:guid}")]
        public IActionResult DeleteEmployee(Guid id)
        {
            var employee = _dbContext.Employees.Find(id);
            if (employee == null)
            {
                return NotFound();
            }
            _dbContext.Employees.Remove(employee);
            _dbContext.SaveChanges();
            return NoContent();
        }
    }
}


Also, you need to create a new folder for DTO(Data Transfer Objects), which is used to transfer data between layers or services within an application.
Create two DTO files named EmployeeDto.cs and UpdateEmployeeDto.cs.
// Employee DTO
namespace EmployeePortal.DTO
{
    public class EmployeeDto
    {
        public required string Name { get; set; }
        public required string Email { get; set; }
        public required string PhoneNumber { get; set; }
        public decimal Salary { get; set; }
    }
}

UpdateEmployeeDto.cs
// Update Employee DTO
namespace EmployeePortal.DTO
{
    public class UpdateEmployeeDto
    {
        public string? Name { get; set; }
        public string? Email { get; set; }
        public string? PhoneNumber { get; set; }
        public decimal Salary { get; set; }
    }
}

Then, build and run your application. You can able to see the endpoints of our application on the chrome with the help of Swagger.



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