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 :: Minimal APIs or Controllers in ASP.NET Core

clock October 15, 2024 07:17 by author Peter

In the previous five to six years,.NET has seen substantial evolution. It's now simpler than ever to create applications using MVC or APIs (Application Programming Interfaces). The Minimal APIs feature, new in ASP.NET Core 6, streamlines API development by doing away with the requirement to create controllers, which are often placed at the front of APIs. Currently,.NET supports two methods: Minimal APIs (the new method) and Controllers (the old method). Which one, though, ought to you use?

What are Minimal APIs?
Minimal APIs define endpoints as logical handlers using lambdas or methods. They utilize method injection for services, while controllers use constructor or property injection. In Minimal APIs, each endpoint only requires the specific services it needs. This is in contrast to controllers, where all endpoints within the controller use the same class constructor, which can make the controller "fat" as it grows. Minimal APIs are designed to hide the host class by default and emphasize configuration and extensibility via extension methods that take lambda expressions.

Here is an example of minimal API.

app.MapGet("/weatherforecast", (HttpContext httpContext) =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        })
        .ToArray();

    return forecast;
});

Here are the important points that we should know before jumping into the minimal API. These points are from the Microsoft docs.

Minimal APIs lack built-in support for,

  • Model binding (IModelBinderProvider, IModelBinder). However, support can be added via custom binding shims.
  • Validation (IModelValidator).
  • Application parts or the application model. There is no way to apply or build your conventions.
  • View rendering. For this, it is recommended to use Razor Pages.
  • JsonPatch.
  • OData.

You can work around these limitations by implementing custom solutions for each of these missing features.

Conclusion
Minimal APIs are an excellent way to start building APIs. One practical use case for Minimal APIs is in Vertical Slice Architecture (Vertical Slice Architecture is a design approach where features are implemented end-to-end, encapsulating all layers (UI, business logic, and data access) within self-contained slices.). In this approach, you can define endpoints for each module separately, making management easier. Since each endpoint in Minimal APIs declares the specific services it needs, this approach helps avoid the issue of controller classes becoming "fat" as they grow and more endpoints are added.

For me, minimal APIs are good to go for small-scale projects and they are easy to handle when endpoints grow.



European ASP.NET Core 9.0 Hosting - HostForLIFE :: Expression Trees in Real Life: C# Dynamic Filtering with Minimal API

clock October 7, 2024 08:39 by author Peter

We covered the essentials of expression trees, use cases, and restrictions in our prior tutorial. Any subject that lacks a real-world example is particularly nonsensical when it comes to programming. The second section of expression trees in C# will be covered in this article, along with some practical examples of how they may be used effectively.

What are we going to construct?
Our primary goal is to create an Asp.NET Core web API with dynamic filtering capabilities using Expression Trees, EF core, and a simple API.
In order to demonstrate the true potential of expression trees for creating intricate and dynamic searches, we intend to implement filtering across the product database. The last example with several dynamic filtering arguments is as follows:

Getting started
First, open Visual Studio and select the Asp.NET Core Web API template with the following configuration:


We use .NET 8.0, but the topic itself doesn’t depend on any .NET version. You can even use classical .NET Framework to use Expression Trees. The project name is “ExpressionTreesInPractice”. Here is the generated template from the Visual Studio:

To have simple storage, we will use InMemory Ef Core. You can use any other EF Core sub-storage.

Now go to Tool->Nuget Package Manager->Package Manager Console and type the following command:
install-package microsoft.entityframeworkcore.inmemory

Now, let’s create our DbContext implementation. Create a folder called ‘Database’ and add a class called ProductDbContext to it with the following implementation:

using ExpressionTreesInPractice.Models;
using Microsoft.EntityFrameworkCore;

namespace ExpressionTreesInPractice.Database
{
    public class ProductDbContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
        public ProductDbContext(DbContextOptions<ProductDbContext> options) : base(options) { }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Product>().HasData(new List<Product>
            {
                new Product(){ Id = 1, Category = "TV", IsActive = true, Name = "LG", Price = 500},
                new Product(){ Id = 2, Category = "Mobile", IsActive = false, Name = "Iphone", Price = 4500},
                new Product(){ Id = 3, Category = "TV", IsActive = true, Name = "Samsung", Price = 2500}
            });
            base.OnModelCreating(modelBuilder);
        }
    }
}

We just need some basic initialized data when we run our application, and that is why we need to override OnModelCreating from DbContext. A great example of a template method pattern, isn’t it?
We need our Entity model called Product, and you can create a folder called ‘Models’ and add the Product class to it with the following content:
namespace ExpressionTreesInPractice.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
        public bool IsActive { get; set; }
        public string Name { get; set; }
    }
}

It is time to register our DbContext implementation in the Program.cs file:
builder.Services.AddDbContext<ProductDbContext>(x => x.UseInMemoryDatabase("ProductDb"));

By the way, Program.cs has tons of unnecessary code snippets that we need to remove. After the cleaning process, our should look like this:
using ExpressionTreesInPractice.Database;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddDbContext<ProductDbContext>(x => x.UseInMemoryDatabase("ProductDb"));

var app = builder.Build();

// Configure the HTTP request pipeline.
app.UseHttpsRedirection();

app.Run();


We don’t want to use controllers because they are heavy and cause additional problems. That is why we choose to use minimal API. If you don’t know what minimal API is, please refer to our video tutorial to learn more.

After understanding it, open Program.cs and add the following code snippet:
app.MapGet("/products", async ([FromBody] ProductSearchCriteria productSearch, ProductDbContext dbContext) =>

{ }


The above code defines a route in a minimal ASP.NET Core API and creates an endpoint for an HTTP GET request to the /products path. The method uses asynchronous programming to handle potentially long-running operations without blocking the main application flow.

ProductSearchCriteria is a parameter passed to the method, which contains the criteria used to filter the products. It's marked with [FromBody], meaning the request body will be bound to this parameter. Usually, GET requests don't use request bodies, but this setup is allowed if you need to pass a complex object.

ProductDbContext is the database context, which represents the session with the database. It's injected into the method, allowing the application to perform operations like querying the database for products based on the search criteria.

The reason for using `ProductSearchCriteria` instead of `Product` is that the query needs to be dynamic. In this case, the user may provide some of the attributes of the `Product`, but not all of them. Since the properties of `Product` are not nullable, the user would be required to provide every property, even if they don't want to filter by all of them.

By using `ProductSearchCriteria`, we allow for more flexibility. It acts as a container for optional and dynamic parameters. The user can choose to provide only the attributes they want to search by, making it a better fit for scenarios where not all product properties are needed in the query.

Here is what our ProductSearchCriteria class looks like in the ‘Models’ folder.
namespace ExpressionTreesInPractice.Models
{
    public record PriceRange(decimal? Min, decimal? Max);
    public record Category(string Name);
    public record ProductName(string Name);
    public class ProductSearchCriteria
    {
        public bool? IsActive { get; set; }
        public PriceRange? Price { get; set; }
        public Category[]? Categories { get; set; }
        public ProductName[]? Names { get; set; }
    }
}

Now, let's focus on our minimal API implementation. Please take into account that the purpose of the current tutorial is not to show the best practices or write clean code. The purpose is to demonstrate Expression trees in practice and after learning the point you can easily refactor the code.

Here is our first code snippet inside the MapGet function:
await dbContext.Database.EnsureCreatedAsync();

 ParameterExpression parameterExp = Expression.Parameter(typeof(Product), "x");

 Expression predicate = Expression.Constant(true);//x=>True && x.IsActive=true/false


 if (productSearch.IsActive.HasValue)

 {

     MemberExpression memberExp = Expression.Property(parameterExp, nameof(Product.IsActive));

     ConstantExpression constantExp = Expression.Constant(productSearch.IsActive.Value);

     BinaryExpression binaryExp = Expression.Equal(memberExp, constantExp);

     predicate = Expression.AndAlso(predicate, binaryExp);

 }

var lambdaExp = Expression.Lambda<Func<Product, bool>>(predicate, parameterExp);

var data = await dbContext.Products.Where(lambdaExp).ToListAsync();

 return Results.Ok(data);

This code is using C#'s Expression classes to dynamically build a predicate for querying a database. Let's break it down step by step.

  • await dbContext.Database.EnsureCreatedAsync();
  • This line asynchronously ensures that the database is created. If it doesn’t exist, it will be created. This is typically used in development or testing environments to ensure the database schema is in place.
  • ParameterExpression parameterExp = Expression.Parameter(typeof(Product), "x");
  • Here, a parameter expression is created to represent an instance of the Product class. This will act as the input parameter (x) in the expression tree, similar to how you define a lambda expression like x => ....
  • Expression predicate = Expression.Constant(true);
  • An initial predicate is created as a constant boolean expression with the value true. This is useful for building the dynamic predicate incrementally, as you can use it as a base to add more conditions (e.g., true AND other conditions). It serves as a starting point for combining additional expressions.
  • if (productSearch.IsActive.HasValue)
  • This block checks if the IsActive property in productSearch is not null, meaning the user has provided a filter for whether the product is active or not.
  • Inside the if block:
  • MemberExpression memberExp = Expression.Property(parameterExp, nameof(Product.IsActive));
  • This creates a MemberExpression that accesses the IsActive property of the Product instance represented by parameterExp (x.IsActive). Essentially, it represents the expression x => x.IsActive.
  • ConstantExpression constantExp = Expression.Constant(productSearch.IsActive.Value);
  • A ConstantExpression is created with the value of productSearch.IsActive. This represents the value to compare against (true or false).
  • BinaryExpression binaryExp = Expression.Equal(memberExp, constantExp);
  • A BinaryExpression is created to compare the IsActive property with the provided value. This represents x.IsActive == productSearch.IsActive.
  • predicate = Expression.AndAlso(predicate, binaryExp);
  • The current predicate (which started as true) is combined with the new condition (x.IsActive == productSearch.IsActive) using a logical AND. This results in an expression that can be used to filter products based on their active status.

Overall, the above code is dynamically building an expression tree that will eventually be used to filter products based on whether they are active. The initial predicate (true) allows for additional conditions to be added easily without special handling for the first condition. If productSearch.IsActive is provided. It adds a condition that checks if the product’s IsActive property matches the given value (true or false).

Then, the lambdaExp variable is assigned a lambda expression that represents a filtering function for the Product entities. This lambda expression is created from the predicate built earlier, which may contain conditions like checking whether the product is active (IsActive). The Expression.Lambda<Func<Product, bool>> call generates a Func<Product, bool>, meaning a function that takes a Product as input and returns a boolean value, determining whether the product satisfies the filtering criteria.

Next, this lambda expression is passed to the Where method of the Products DbSet in dbContext. The Where method applies this filter to the product records in the database. It creates a query that retrieves only the products matching the conditions defined in the lambda expression.

Finally, the ToListAsync() method asynchronously executes the query and retrieves the matching products as a list. This list is then returned as part of an HTTP 200 OK response using Results.Ok(data). The result is the filtered list of products, which is sent back as the API's response.

In order to test it, just run the application and send the following GET request with Body via Postman:

This approach is useful for us when building queries dynamically, as it allows the flexibility to add conditions based on which filters are provided.

Here is how your LINQ expression should look after compiling your expression tree:
{x => (True AndAlso (x.IsActive == True))}

So far, we have implemented the easiest property, which has two values: true or false. But how about other properties like categories, names, prices, etc.? Users are also able to not pick a product based on whether it is active or not but pick, for example, based on its category field. We allow users to provide multiple categories at the same time. That is why we implemented it as an array in our ProductSearchCategory class.

if (productSearch.Categories is not null && productSearch.Categories.Any())
{
    //x.Category
    MemberExpression memberExp = Expression.Property(parameterExp, nameof(Product.Category));
    Expression orExpression = Expression.Constant(false);
    foreach (var category in productSearch.Categories)
    {
        var constExp = Expression.Constant(category.Name);
        BinaryExpression binaryExp = Expression.Equal(memberExp, constExp);
        orExpression = Expression.OrElse(orExpression, binaryExp);
    }
    predicate = Expression.AndAlso(predicate, orExpression);
}


The code is adding dynamic filtering for product categories. It first checks if the `Categories` in the `productSearch` object is not null and contains any items. If so, it proceeds to build a dynamic expression to filter products by category.

It starts by accessing the `Category` property of the `Product` class through an expression. This member expression represents `x => x.Category`, where `x` is an instance of `Product`.

An initial `orExpression` is set to `false`. This will serve as the base for the dynamic category comparison. It uses a loop to iterate over each category in `productSearch.Categories`. For each category, a constant expression with the category name is created, and a binary expression checks if the product's `Category` equals this name.

The binary expressions are then combined using `OrElse`, meaning that if the product matches any of the given categories, the condition becomes true. After processing all categories, the combined `orExpression` is appended to the main `predicate` with `AndAlso`. This means the overall predicate will now check both the previous conditions and whether the product's category matches any of the categories in the search criteria.

This approach allows for dynamically filtering products by multiple categories, and it integrates the category filtering into the existing predicate.

At the end of the last code, you would get a LINQ expression that represents a lambda function used to filter products based on dynamic conditions. This expression can be translated into a predicate for use in a LINQ query, which can be applied to your ProductDbContext or any IQueryable<Product>.

The LINQ expression, in this case, would be a combination of logical operations (AND and OR) that filter products. Specifically, it looks like this in pseudocode:
products.Where(x => (x.Category == "Category1" || x.Category == "Category2" || ...) && other conditions)

If a  user provides both (isActive and categories) then we should get the following lambda expression:
{x => ((True AndAlso (x.IsActive == True)) AndAlso (((False OrElse (x.Category == "TV")) OrElse (x.Category == "Some Other")) OrElse (x.Category == "Mobile")))}

We follow the same approach for the Names field. Here is our code snippet:
if (productSearch.Names is not null && productSearch.Names.Any())
{
    //x.Name
    MemberExpression memberExp = Expression.Property(parameterExp, nameof(Product.Name));
    Expression orExpression = Expression.Constant(false);
    foreach (var productName in productSearch.Names)
    {
        var constExp = Expression.Constant(productName.Name);
        BinaryExpression binaryExp = Expression.Equal(memberExp, constExp);
        orExpression = Expression.OrElse(orExpression, binaryExp);
    }
    predicate = Expression.AndAlso(predicate, orExpression);
}


This code snippet dynamically builds a filtering condition for product names using expression trees. It first checks if the `productSearch.Names` property is not null and contains any items. If there are product names to filter by, it proceeds to build an expression for comparing the `Name` property of the `Product` entity.

The `memberExp` expression refers to the `Name` property of the `Product` (`x.Name` in a lambda expression). An initial expression, `orExpression`, is created, starting as `false`. This `orExpression` will be updated in a loop to accumulate comparisons for each name in `productSearch.Names`.

Within the loop, for each name in the `productSearch.Names` collection, a constant expression is created from the product name. A binary expression is then formed to check if the product's `Name` equals the current name from the search. The loop builds up a series of `OR` conditions using `Expression.OrElse`, which creates a logical OR operation between the current `orExpression` and the new comparison.

After the loop, the final `orExpression` represents a chain of OR conditions where the product's `Name` must match one of the names in `productSearch.Names`. This expression is combined with the existing `predicate` using `Expression.AndAlso`, ensuring that the name filter is applied along with any other conditions previously defined in the `predicate`.

Long story short, our block of code dynamically constructs a query filter that matches products based on their `Name`, allowing for multiple possible names from the `productSearch.Names` collection.

If the User provides only Names from the Body of the query, we will get approximately the following lambda expression at the end:
{x => (True AndAlso (((False OrElse (x.Name == "LG")) OrElse (x.Name == "LG2")) OrElse (x.Name == "Samsung")))}

If we get all filter parameters like isActive, categories, and names from the request body, we will get the following lambda expression at the end:
{x => (((True AndAlso (x.IsActive == True)) AndAlso (((False OrElse (x.Category == "TV")) OrElse (x.Category == "Some Other")) OrElse (x.Category == "Mobile"))) AndAlso (((False OrElse (x.Name == "LG")) OrElse (x.Name == "LG2")) OrElse (x.Name == "Samsung")))}

Here is what it looks like when running the application and sending the query:

The final argument for our dynamic filtering is Price. It is a complex object which consists of min and max values. The user should be able to provide any of them, both or none of them. That is why we designed it with nullable parameters.

Here is what our code implementation looks like:
if (productSearch.Price is not null)
{
    //x.Price 400
    MemberExpression memberExp = Expression.Property(parameterExp, nameof(Product.Price));
    //x.Price>=min
    if (productSearch.Price.Min is not null)
    {
        var constExp = Expression.Constant(productSearch.Price.Min);
        var binaryExp = Expression.GreaterThanOrEqual(memberExp, constExp);
        predicate = Expression.AndAlso(predicate, binaryExp);
    }
    //(x.Price>=min && x.Price.Max<=max)
    if (productSearch.Price.Max is not null)
    {
        var constExp = Expression.Constant(productSearch.Price.Max);
        var binaryExp = Expression.LessThanOrEqual(memberExp, constExp);
        predicate = Expression.AndAlso(predicate, binaryExp);
    }
}

This code dynamically constructs a predicate for filtering products based on their `Price` range using expression trees. It starts by checking if the `productSearch.Price` object is not null, which indicates that a price filter is applied.

The `memberExp` expression is created to represent the `Price` property of the `Product` (`x.Price`). This expression is used to compare the product's price against the minimum and maximum values in the `productSearch.Price` object.

If the minimum price (`productSearch.Price.Min`) is provided (not null), an expression is built to check if the product's `Price` is greater than or equal to this minimum value. This condition is added to the overall `predicate` using `Expression.AndAlso`, meaning the product must satisfy this condition to be included in the results.

Similarly, if the maximum price (`productSearch.Price.Max`) is provided, another expression is constructed to check if the product's `Price` is less than or equal to the maximum value. This condition is also combined with the existing `predicate` using `Expression.AndAlso`, ensuring that both the minimum and maximum price conditions are applied.

Long story short, the code builds a predicate that filters products by a specified price range, ensuring that products have a price greater than or equal to the minimum (if provided) and less than or equal to the maximum (if provided).

If the User provides only Price from the Body of the query, we will get approximately the following lambda expression at the end:
{x => ((True AndAlso (x.Price >= 400)) AndAlso (x.Price <= 5000))}

If we get all filter parameters like IsActive, Categories, Names, and Price from the request body, we will get the following lambda expression at the end:
{x => (((((True AndAlso (x.IsActive == True)) AndAlso (((False OrElse (x.Category == "TV")) OrElse (x.Category == "Some Other")) OrElse (x.Category == "Mobile"))) AndAlso (((False OrElse (x.Name == "LG")) OrElse (x.Name == "LG2")) OrElse (x.Name == "Samsung"))) AndAlso (x.Price >= 400)) AndAlso (x.Price <= 5000))}

Here is what it looks like when running the application and sending the query:

The elegant ending

This article serves as a practical continuation of the previous tutorial on C# expression trees, focusing on their real-world usage within an ASP.NET Core web API. It explores the creation of dynamic filtering functionality using minimal API, Entity Framework Core (EF Core), and expression trees.

The project involves building a product database with dynamic filtering capabilities, such as filtering by product attributes like `IsActive`, `Category`, `Name`, and `Price`. The use of expression trees is highlighted to construct flexible, dynamic queries without hardcoding-specific filters.

The setup begins with an ASP.NET Core Web API using an in-memory database for storage, although other EF Core-supported databases could be used. The article emphasizes using minimal API over traditional controllers for simplicity and performance and guides the user through the necessary steps, including setting up the database context (`DbContext`) and initializing data.

One of the core features demonstrated is how expression trees are used to build predicates dynamically. For example, when filtering by the `IsActive` property, the system checks whether the user provided this filter and then dynamically constructs a condition that compares the product's `IsActive` status with the provided value. The process is extended to handle dynamic filtering of other properties such as `Category`, `Name`, and `Price`, each of which allows flexible criteria for querying.

By using expression trees, the article illustrates how complex and flexible queries can be constructed without writing multiple hardcoded query methods. The example of filtering products by `Name` and `Category` demonstrates how logical `OR` conditions can be combined dynamically, depending on user input, resulting in concise and reusable query logic.

Additionally, the price filtering is handled by checking both minimum and maximum values and dynamically adjusting the predicate to include only those products within the specified price range.

In conclusion, this article demonstrates the power of expression trees in building dynamic, flexible queries in C# applications. It provides hands-on code examples of using expression trees to construct queries for an ASP.NET Core web API, offering a practical way to manage complex, real-world scenarios like filtering product databases based on varying user input.



European ASP.NET Core 9.0 Hosting - HostForLIFE :: Differences in Span<T> and List<T>

clock October 1, 2024 07:13 by author Peter

The two data structures in.NET, Span<T> and List<T>, have different functions and features, particularly with regard to memory management and performance. Depending on the use case, I'll discuss the differences and offer insights into which is better for performance below. Note: If you are a Java fan, you should think of Span as an array and List as an ArrayList. In Java, Span<T> and List<T> are comparable to Arrays and ArrayList.

Memory Management

  • Span<T>
    • Span<T> is a stack-only structure that provides a view over a contiguous block of memory (such as an array, memory from the stack, or a portion of an existing array).
    • It does not own the memory but rather operates over existing memory, meaning it does not allocate memory on the heap.
    • Span<T> Is lightweight and efficient because it doesn't involve allocations or resizing, and it is used for scenarios where you need to work with slices of arrays or memory buffers without making copies.
  • List<T>
    • List<T> is a heap-allocated collection that dynamically manages a resizable array under the hood.
    • It manages its memory by growing the internal array as needed when new items are added, which incurs allocation and copy costs.
    • List<T> Has a lot of flexibility in terms of adding, removing, and accessing elements, but this comes with overhead due to heap allocations and resizing.

Mutability and Resizing

  • Span<T>
    • Span<T> cannot be resized. It represents a fixed-size view of existing memory. You cannot add or remove elements from a, only modify the elements within the given range.
    • If you need to add or remove elements dynamically, Span<T> is not suitable, but it excels in scenarios where the size is fixed and known in advance.
  • List<T>
    • List<T> is dynamically resizable, making it convenient for scenarios where the number of elements is unknown or changes frequently.
    • However, resizing comes with performance costs, as it requires allocating a new array and copying over the elements whenever the capacity of the list is exceeded.

Performance

  • Span<T>
    • Faster for fixed-size data manipulation: Since Span<T> avoids heap allocations and runs directly on existing memory, it can be faster than List<T> for operations like slicing arrays or working with buffers.
    • Minimal overhead: Because Span<T> is designed to work with stack-allocated data or fixed-length buffers, there is virtually no memory overhead, making it more efficient for memory-constrained operations.
    • Ideal for scenarios where the performance of accessing and manipulating in-memory data is critical (e.g., high-performance applications like games, parsers, or real-time systems).

List<T>

  • More overhead due to dynamic resizing: Each time List<T> Grows beyond its current capacity, it has to allocate a larger array and copy elements, which impacts performance, especially in scenarios with frequent additions.
  • Despite these costs, List<T> is still performant for general use cases where dynamic size changes are necessary, and the slight performance overhead is acceptable.
  • Access to elements in a List<T> (indexer-based access) is very fast (O(1) time complexity), but modifications that require resizing (like Add, Remove) can incur extra costs.

Use Cases

  • Span<T>
    • High-performance scenarios: Span<T> Is designed for performance-critical code, such as working with buffers, memory manipulation, or slices of arrays where dynamic allocation and copying should be avoided.
    • Memory-efficient processing: If you're working with large datasets (e.g., image processing, networking buffers) where you just need to process data and not store it permanently, Span<T> is a good choice.
    • Fixed-size operations: If you have data that won’t change in size (e.g., you’re reading data into an array and just want to operate on parts of it), Span<T> is perfect.
  • List<T>
    • Dynamic collection handling: List<T> Is great when you need to manage a collection whose size changes over time. It’s ideal for situations where elements are frequently added or removed.
    • General-purpose collection: List<T> Is a high-level data structure that offers a lot of functionality out of the box, such as sorting, searching, and collection-wide operations like ForEach.
    • Higher-level use cases: If performance isn’t the absolute top priority and you need a flexible collection that grows and shrinks, List<T> is the better choice.

Memory Safety and Stack Limitations

  • Span<T>
    • Span<T> operates on stack memory, so it's constrained by the stack size of the thread. Stack sizes are typically much smaller than heap sizes, so you can't store large data in Span<T>.
    • However, Span<T> Can also reference heap-allocated arrays without copying them. But for stack-allocated spans (like stackalloc), large allocations can lead to stack overflow exceptions.
  • List<T>
    • Since List<T> is heap-allocated, it is not constrained by the stack size. You can store significantly larger amounts of data in a List<T>, although at the cost of dynamic memory management.

Safety and Lifetime Constraints

  • Span<T>
    • Lifespan constraints: Span<T> Is meant to be short-lived and cannot be stored on the heap, which limits its use outside of local scopes.
    • Stack Safety: You can't return a Span<T> from a method if it's referencing stack-allocated memory, as that memory would no longer be valid once the method returns.
  • List<T>
    • No such lifespan restrictions exist in List<T>, as it’s stored on the heap. This makes it easier to pass between methods and store in-class fields.

Span<T> vs List<T>

Aspect Span<T> List<T>
Memory Allocation Stack-allocated or a slice of existing memory Heap-allocated, dynamically resizable array
Resizing Fixed-size (non-resizable) Dynamically resizable
Performance Faster for fixed-size, in-memory operations Slower due to resizing and heap allocations
Use Cases High-performance scenarios, low-level memory access General-purpose dynamic collections
Context Short-lived, stack-constrained Long-lived, heap-allocated
Memory Safety Stack-safe, cannot be heap-allocated No stack constraints, heap-based
Thread Safety It can be used safely for memory slices Not inherently thread-safe without synchronization

When to Choose Span<T> or List<T>?

  • Choose Span<T>
    • If you are working with slices of memory or arrays and need maximum performance with minimal memory overhead.
    • If you know the size of the data and don’t need the collection to grow or shrink dynamically.
    • For high-performance applications (e.g., parsers, network buffers, game engines).
  • Choose List<T>
    • If you need a dynamic, resizable collection where elements will be added and removed frequently.
    • If you need the convenience of built-in operations like searching, sorting, and enumerating.
    • This is for general-purpose applications where performance is important but not critical.

Conclusion
For performance-critical operations involving fixed-size memory manipulation, Span<T> offers significant advantages because it avoids the overhead of heap allocations and resizes. However, if you need flexibility in a dynamic collection, List<T> is more appropriate, even though it has additional overhead.



European ASP.NET Core 9.0 Hosting - HostForLIFE :: Versioning the API and Turning on Authorization in the Swagger UI for.NET Core

clock September 26, 2024 07:18 by author Peter

API Versioning
The process of creating different versions of an API to coexist at the same time is called API versioning. By employing this method, we may allow new features and enhancements for clients that are more recent while preserving backward compatibility with older clients. Your application may require breaking updates to your API as it develops. Managing these changes in a way that doesn't affect current users who are dependent on previous API versions is helpful.

There are various ways to implement API versioning in .NET Core web API. The most common ways to implement API versioning are:

  • URL versioning: This is the most common approach to implementing API versioning. In this technique, the version is part of the endpoint itself.
  • For Example: /api/v1/purchaseOrders
  • Query String Versioning: In this technique, the version is passed as a query parameter. This approach maintains the same URL path, with the version indicated by an additional query parameter.
  • For Example: /api/purchaseOrders?api-version=1.0
  • Header Versioning: In this technique, the version is passed in a custom request header. The version is included in the request headers rather than the URL.
  • For Example

GET /api/purchaseOrders

  • Headers: x-api-version: 1.0Media Type Versioning (Accept Header Versioning): In this technique, the version is passed via content negotiation. The version is included in the Accept header with a custom media type. The client requests a specific version of the API by setting the Accept header to a custom MIME type.
  • For Example
    GET /api/purchaseOrder
  • Headers: Accept: application/vnd.companyname.v1+json
How to implement API versioning?
How to implement API versioning using URL versioning and enable the token authorization option in Swagger step by step. For this, I am using .NET Core 8.

Create a web API: Create a .NET Core web API and name it "APIVersioingPOC".
Add required packages: Add the below-required packages for API versioning using the Nuget package manager.

Install-Package Asp.Versioning.Mvc
Install-Package Asp.Versioning.Mvc.ApiExplorer


Create Entity Class: Create a folder with the name "Entity" and add an entity class named "PurchaseDetails"
namespace APIVersioingPOC.Entity
{
    public class PurchaseDetails
    {
        public string ProductName { get; set; }
        public int Rate { get; set; }
        public int Qty { get; set; }
        public int Amount { get; set; }
    }
}


Create Service: Create a folder with the name "Service" and add an interface with the name "IPurchaseOrderService" and a service class with the name "PurchaseOrderService" as below.

using APIVersioingPOC.Entity;

namespace APIVersioingPOC.Service
{
    public interface IPurchaseOrderService
    {
        List<PurchaseDetails> GetPurchaseOrders();
    }
}


using APIVersioingPOC.Entity;

namespace APIVersioingPOC.Service
{
    public class PurchaseOrderService: IPurchaseOrderService
    {
        public List<PurchaseDetails> GetPurchaseOrders()
        {
            return new List<PurchaseDetails>
            {
               new PurchaseDetails { ProductName="Laptop", Rate=80000, Qty=2, Amount=160000},
               new PurchaseDetails { ProductName="Dekstop", Rate=40000, Qty=1, Amount=40000},
               new PurchaseDetails { ProductName="Hard Disk", Rate=4000, Qty=10, Amount=40000},
               new PurchaseDetails { ProductName="Pen Drive", Rate=600, Qty=10, Amount=6000},
            };
        }
    }
}

To resolve the dependency, register the service in Program.cs as below.
// Add custom services
builder.Services.AddScoped<IPurchaseOrderService, PurchaseOrderService>();

Configure the versioning: In Program.cs file, add the below code.
var builder = WebApplication.CreateBuilder(args);

// Add API Explorer that provides information about the versions available
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0); // Default API version (v1.0)
    options.AssumeDefaultVersionWhenUnspecified = true; // Assume the default version if not specified
    options.ReportApiVersions = true; // Report API versions in response headers
    options.ApiVersionReader = new UrlSegmentApiVersionReader(); // Use URL segment versioning (e.g., /api/v1/resource)

}).AddApiExplorer(options =>
{
    options.GroupNameFormat = "'v'VVV";
    options.SubstituteApiVersionInUrl = true;
});

builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI(options =>
{
    var provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();

    foreach (var description in provider.ApiVersionDescriptions)
    {
        options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
    }
});

ConfigureSwaggerOptions: Create a folder with the name "OpenApi" and add the ConfigureSwaggerOptions class to configure swagger options.
using Asp.Versioning.ApiExplorer;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace APIVersioingPOC.OpenApi
{
    public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
    {
        private readonly IApiVersionDescriptionProvider _provider;

        public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider)
        {
            _provider = provider;
        }

        public void Configure(SwaggerGenOptions options)
        {
            foreach (var description in _provider.ApiVersionDescriptions)
            {
                options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
            }

            // Add token authentication option to pass bearer token
            options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                In = ParameterLocation.Header,
                Description = "Please enter token",
                Name = "Authorization",
                Type = SecuritySchemeType.Http,
                BearerFormat = "JWT",
                Scheme = "bearer"
            });

            // Add security scheme
            options.AddSecurityRequirement(new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference
                        {
                            Type = ReferenceType.SecurityScheme,
                            Id = "Bearer"
                        }
                    },
                    new string[] { }
                }
            });
        }

        private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription apiVersionDescription)
        {
            var info = new OpenApiInfo
            {
                Title = "API Versioning",
                Version = apiVersionDescription.ApiVersion.ToString(),
                Description = "Swagger document for API Versioning.",
            };

            // Add deprecated API description
            if (apiVersionDescription.IsDeprecated)
            {
                info.Description += " This API version has been deprecated.";
            }

            return info;
        }
    }
}


Add the code below to the Program.cs

// Add custom services
builder.Services.AddSingleton<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();


Create Controller: Create a controller with the name "PurchaseOrderController". For demo purposes, I have created two versions of the same API endpoint.
using APIVersioingPOC.Service;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace APIVersioingPOC.Controllers
{
    [ApiController]
    [Route("api/v{version:apiVersion}/[controller]")]
    [ApiVersion("1.0")]
    [ApiVersion("2.0")]
    [Authorize]
    public class PurchaseOrderController : ControllerBase
    {
        private readonly IPurchaseOrderService _purchaseOrderService;

        public PurchaseOrderController(IPurchaseOrderService purchaseOrderService)
        {
            _purchaseOrderService = purchaseOrderService;
        }

        [HttpGet("GetPurchaseOrders")]
        [MapToApiVersion("1.0")]
        public IActionResult GetPurchaseOrders()
        {
            var users = _purchaseOrderService.GetPurchaseOrders();
            return Ok(users);
        }

        [HttpGet("GetPurchaseOrders")]
        [MapToApiVersion("2.0")]
        public IActionResult GetPurchaseOrdersV2()
        {
            var purchaseDetails = _purchaseOrderService.GetPurchaseOrders();
            return Ok(purchaseDetails);
        }
    }
}


Let's run the project

For default version V1 you will get the swagger document as below.

When you use the V2 option in the "Select a Definition" dropdown box, the swagger document will appear as seen below.


You can pass the authentication token as below and click on the Authorize button.

In this way, we learned how to implement API versioning and enable authorization in Swagger UI.

Happy Learning!



European ASP.NET Core 9.0 Hosting - HostForLIFE :: Cache Profiles for Response Caching in.NET Core 8

clock September 23, 2024 08:10 by author Peter

Response caching is the technique by which a browser or other client stores a server's response in memory. As a result, requests for the same resources will be processed more quickly in the future. Additionally, this will spare the server from processing and producing the same response over and over again.

ASP.NET Core uses the ResponseCache property to set the response caching headers. Moreover, we can use the Response Caching Middleware to control the caching behavior from the server side. Once we've configured clients and other proxies to determine how to cache the server response, they can read the response caching headers. The HTTP 1.1 Response Cache Specification stipulates that browsers, clients, and proxies need to have caching headers.

Profiles in Cache
Instead of repeating the response cache settings on various controller action methods, we can create cache profiles and use them across the application. After it is configured, the application can utilize the cache profile's values as defaults for the ResponseCache attribute. Obviously, we can override the defaults by declaring the characteristics on the attribute.

A cache profile can be defined in the Program class.

builder.Services.AddControllers(options =>
{
    options.CacheProfiles.Add("Default", new CacheProfile
    {
        Duration = 60,
        Location = ResponseCacheLocation.Any
    });
});


Here, we define a new cache profile named Default, whose location is set to public and whose duration is set to two minutes.

This cache profile can now be applied to any controller or endpoint.
[HttpGet("default")]
[ResponseCache(CacheProfileName = "Default")]
public IActionResult Default()
{
    return Ok($"Default response was generated, {DateTime.Now}");
}


This will cause the response's defined cache-control settings to be applied.
cache-control: public,max-age=60

We can define multiple cache profiles in the app settings file and make the response cache settings configurable, avoiding hard-coding the cache settings in the Program class.

"CacheProfiles": {
  "Cache5Mins": {
    "Duration": 600,
    "Location": "Any"
  },
  "CacheVaryByHeader": {
    "Duration": 60,
    "Location": "Any",
    "VaryByHeader": "User-Agent"
  }
}


The ConfigurationManager class can then be used to read the cache profiles in the Program class.

builder.Services.AddControllers(options =>
{
    var cacheProfiles = builder.Configuration
            .GetSection("CacheProfiles")
            .GetChildren();

    foreach (var cacheProfile in cacheProfiles)
    {
        options.CacheProfiles
        .Add(cacheProfile.Key, cacheProfile.Get<CacheProfile>());
    }
});


This approach to defining cache profiles is far superior, particularly if our application requires the definition of multiple cache profiles. Let’s run the application and validate the response cache.

 

The above screenshot is for the default response cache profile; the default value is 60 (1 min) until that response is the same; after that duration, the response is changed.

The cache vary by header response cache profile shown in the above example has a default value of 60 (1 minute), and the vary by header property value is User-Agent up until the point at which the answer changes.

Remember that while Response Caching can significantly improve your application's speed, it may not be suitable in all circumstances. Caching can cause unwanted or inappropriate behavior when user-specific data is served that shouldn't be cached, or when information is changing quickly.

Together, we developed and mastered the new method.

Have fun with coding!



European ASP.NET Core 9.0 Hosting - HostForLIFE :: In-Memory Databases- Unit Testing With C#, EFCore and XUnit

clock September 19, 2024 08:31 by author Peter

We occasionally find it difficult to unit test our repositories because of the process of establishing a record in the database. However, with the EF-Core In-Memory Database, testing your repository is now simple. To show you the same, we have made a little dummy project.

In-Memory Database

A lightweight, quick database that runs exclusively in memory is called an in-memory database. Because it lets you imitate database operations without the overhead of an actual database, it's especially helpful for unit testing. You can test your data access code using the In-Memory Database service offered by EF Core.

Use these instructions to build up in-memory databases and unit testing using xUnit, EF Core, and C#.

Set Up Your Project

  • Ensure you have the necessary NuGet packages installed.
    • `Microsoft.EntityFrameworkCore.InMemory`
    • `xUnit`
    • `xUnit.runner.visualstudio`
    • `Microsoft.NET.Test.Sdk`
  • Configure In-Memory Database: In your test project, configure the in-memory database in your DbContext options. This allows you to use an in-memory database for testing purposes.
  • Create Your DbContext: Define your DbContext class as you would for any EF Core application.
  • Write Unit Tests: Use xUnit to write your unit tests. You can create a test class and use the [Fact] attribute to define individual test methods.
  • Initialize the In-Memory Database in Tests: In your test methods, initialize the in-memory database and seed it with test data if necessary.
  • Run Your Tests: Use the xUnit test runner to execute your tests and verify the behavior of your code.

Here’s a high-level overview of the process.

Install Packages
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package xunit
dotnet add package xunit.runner.visualstudio
dotnet add package Microsoft.NET.Test.Sdk


Configure DbContext
public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
    public DbSet<User> Users { get; set; }
}


Write Unit Tests
public class UserRepositoryTests
{
    [Fact]
    public void Create_ShouldReturnSuccess_WhenDataGiven()
    {
        int expectedUserId = 1;
        string expectedUserName = “Test”;

        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase(databaseName: new Guid.NewGuid().ToString())
            .Options;

        using (var context = new ApplicationDbContext(options))
        {
            // Arrange: Seed data into the context
            context.Users.Add(new User { Id = expectedUserId, Name = expectedUserName });
            context.SaveChanges();

            // Act: Perform the action to be tested
            var actualUser  = context.Users.FirstOrDefault(e => e.Id == expectedUserId);

            // Assert: Verify the result
            Assert.NotNull(result);
            Assert.Equal(expectedUserName, actualUser.Name);
        }
    }
}


This setup allows you to test your EF Core code using an in-memory database, ensuring your tests are fast and isolated from your production database.

Output

Benefits and Limitations
Benefits

  • Speed: In-memory databases are faster than real databases.
  • Isolation: Tests are isolated from each other, ensuring no side effects.
  • Simplicity: Easy to set up and use.

Limitations
Not a Real Database: It doesn’t enforce all constraints (e.g., foreign keys) like a real relational database.
Limited Features: Some database-specific features may not be supported.



European ASP.NET Core 9.0 Hosting - HostForLIFE :: Protecting ASP.NET Core Web API from XSS Attacks

clock September 10, 2024 07:50 by author Peter

One of the most frequent vulnerabilities discovered in web applications is cross-site scripting (XSS). It happens when a victim's browser executes malicious scripts that have been injected into online sites or APIs, possibly compromising sensitive data. This post will describe how to use security best practices, sanitize inputs, and encode outputs to prevent XSS attacks in an ASP.NET Core Web API.

XSS: What is it?
When harmful scripts are injected into content that is given to users without the necessary validation or encoding, it is known as an XSS attack. When it comes to Web APIs, the fact that the API can process user input and then provide it to clients (browsers or other consumers) raises the possibility of criminal activity and script execution.

Example of an XSS Attack
Imagine a Web API that accepts user input, such as a name, and returns it back to the client.

{
    "name": "<script>alert('XSS Attack!');</script>"
}


If the API does not sanitize this input, the malicious JavaScript (<script>alert('XSS Attack!');</script>) will be executed in the client’s browser.

Types of XSS Attacks

  • Stored XSS: Malicious scripts are stored in the database or file system and executed when the victim visits the page that retrieves and displays the data.
  • Reflected XSS: Malicious scripts are embedded in the URL and executed when the victim clicks the link.
  • DOM-Based XSS: The vulnerability is within the client-side JavaScript itself.

Preventing XSS in ASP.NET Core Web API
Here’s a step-by-step guide to protect your API from XSS attacks.

1. Input Validation and Data Annotations

The first line of defense against XSS is to validate user inputs using model validation and constraints.
In ASP.NET Core, you can use Data Annotations to specify validation rules for models. For example, a user registration API might look like this:
public class UserInput
{
    [Required]
    [MaxLength(50)]
    [RegularExpression(@"^[a-zA-Z0-9]*$", ErrorMessage = "Invalid characters in name")]
    public string Name { get; set; }

    [Required]
    [EmailAddress]
    public string Email { get; set; }
}


By applying these annotations, the API ensures that only valid data is accepted and limits the possibility of malicious scripts getting through.

Example API Controller
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    [HttpPost]
    [Route("create")]
    public IActionResult CreateUser([FromBody] UserInput userInput)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        return Ok(new { Message = "User created successfully!" });
    }
}


In the above example, the input Name is restricted to alphanumeric characters, which limits the possibility of script injection.

2. Sanitizing User Input

Even with validation in place, you should sanitize inputs that could potentially be harmful. ASP.NET Core provides various ways to sanitize and encode user inputs.
Using the HtmlEncoder Class: You can use the HtmlEncoder class to encode dangerous characters before processing the input.
    using System.Text.Encodings.Web;
    public string SanitizeInput(string input)
    {
        return HtmlEncoder.Default.Encode(input);
    }


This encodes any special characters like <, >, or & that could be used for XSS attacks.

Example Usage in API
[HttpPost]
[Route("sanitize")]
public IActionResult SanitizeUserInput([FromBody] string userInput)
{
    var sanitizedInput = HtmlEncoder.Default.Encode(userInput);
    return Ok(new { SanitizedInput = sanitizedInput });
}


3. Using a Third-Party Library for Sanitization
For more complex scenarios, you can use a third-party library like Ganss.XSS, which allows for advanced HTML sanitization.
Install the NuGet Package
Install-Package Ganss.XSS


Example Code
using Ganss.XSS;
public string SanitizeHtml(string input)
{
    var sanitizer = new HtmlSanitizer();
    return sanitizer.Sanitize(input);
}


4. Content Security Policy (CSP)
A Content Security Policy (CSP) is a security header that helps prevent XSS by controlling which resources (scripts, images, styles) can be loaded by the browser.
You can add CSP headers in ASP.NET Core like this.

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self'");
        await next();
    });

}


This policy restricts the loading of scripts to only those from the same domain, making it much harder for attackers to load malicious external scripts.

5. HTTP-Only and Secure Cookies

If your Web API works with cookies (e.g., for authentication), always mark them as HttpOnly and Secure to prevent client-side scripts from accessing them.
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;


6. Sanitize Data from Third-Party APIs
If your API consumes data from third-party services, it's important to sanitize that data before returning it to your clients. Even trusted APIs could be compromised, so always validate and sanitize the data.

Example
public IActionResult FetchDataFromExternalApi()
{
    var externalApiData = GetExternalApiData();
    var sanitizedData = HtmlEncoder.Default.Encode(externalApiData);
    return Ok(new { Data = sanitizedData });
}


7. Ensure Proper Response Headers
Return the proper Content-Type headers in your API responses. If you’re returning JSON, ensure the Content-Type is set to application/json. This prevents browsers from interpreting JSON responses as HTML or scripts.
context.Response.ContentType = "application/json";

8. Real-World Example of XSS Prevention
Let’s implement an API endpoint that accepts a user's comment and sanitizes the input before storing it in the database.
    Comment Model
    public class Comment
    {
        public int Id { get; set; }
        [Required]
        [MaxLength(500)]
        public string Content { get; set; }
        public DateTime CreatedAt { get; set; }
    }


CommentController
[ApiController]
[Route("api/[controller]")]
public class CommentController : ControllerBase
{
    private readonly HtmlSanitizer _sanitizer;
    public CommentController()
    {
        _sanitizer = new HtmlSanitizer();
    }
    [HttpPost]
    [Route("create")]
    public IActionResult CreateComment([FromBody] Comment comment)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        comment.Content = _sanitizer.Sanitize(comment.Content);
        comment.CreatedAt = DateTime.UtcNow;
        _dbContext.Comments.Add(comment);
        _dbContext.SaveChanges();
        return Ok(new { Message = "Comment created successfully!" });
    }
}


Complete End to end Example In Asp.net Core Web API
Step 1. Create a New ASP.NET Core Web API Project

  • Open Visual Studio Code.
  • Create a new folder for your project.
  • Open the folder in VS Code.
  • Open a terminal in VS Code and run the following command to create an ASP.NET Core Web API project.

dotnet new webapi -n XSSAttacksinASP.NETCoreWebAPI

Step 2. Add Ganss.XSS NuGet Package
Navigate to the project folder and install the Ganss.XSS NuGet package, which will help with input sanitization.
dotnet add package Ganss.XSS

Step 3. Modify the Project Structure
Our project structure should look something like this.

XSSAttacksinASP.NETCoreWebAPI/
│
├── Controllers/
│   └── CommentController.cs
│
├── Models/
│   └── Comment.cs
│
├── Program.cs
├── Startup.cs
├── XssPreventionApi.csproj
└── appsettings.json


Step 4. Define the Model (Comment.cs)
In the Models folder, create a Comment.cs file to define the model for user input.

using System.ComponentModel.DataAnnotations;
namespace XSSAttacksinASP.NETCoreWebAPI.Models
{
    public class Comment
    {
        public int Id { get; set; }
        [Required]
        [MaxLength(500, ErrorMessage = "Comment cannot be longer than 500 characters.")]
        public string Content { get; set; }
        public DateTime CreatedAt { get; set; }
    }
}


Step 5. Create the Comment Controller (CommentController.cs)
In the Controllers folder, create a CommentController.cs file to handle incoming HTTP requests for the comments. We'll use the Ganss.XSS library to sanitize the comment content before saving it to a data store (in this example, we'll just simulate saving it).
using Ganss.Xss;
using Microsoft.AspNetCore.Mvc;
using XSSAttacksinASP.NETCoreWebAPI.Models;
namespace XSSAttacksinASP.NETCoreWebAPI.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class CommentController : ControllerBase
    {
        private readonly HtmlSanitizer _sanitizer;
        public CommentController()
        {
            _sanitizer = new HtmlSanitizer();
        }
        [HttpPost]
        [Route("create")]
        public IActionResult CreateComment([FromBody] Comment comment)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            comment.Content = _sanitizer.Sanitize(comment.Content);
            comment.CreatedAt = DateTime.UtcNow;
            return Ok(new { Message = "Comment created successfully!", SanitizedContent = comment.Content });
        }
    }
}


Step 6. Configure Program. cs and Startup. cs (ASP.NET Core 6+)
Since ASP.NET Core 6+ has consolidated the Startup and Program files, you may just need to ensure the basic structure is in place to handle API routing and services.
namespace XSSAttacksinASP.NETCoreWebAPI
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            // Add services to the container.
            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();
        }
    }
}


Step 7. Add Launch Settings (Optional)
If you want to configure launch settings (for example, to run the project on a specific port), modify the Properties/launchSettings.json file.
{
  "profiles": {
    "XssPreventionApi": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Step 8. Run the Project
Open the terminal and navigate to the project folder (XssPreventionApi).

Run the project using the following command.
dotnet run

Step 9. Test the API Using Postman or curl
You can use Postman or Curl to send POST requests to the API.

    Example Request with curl
    curl -X POST https://localhost:5001/api/comment/create \
    -H "Content-Type: application/json" \
    -d "{\"Content\": \"<script>alert('XSS Attack!');</script>\"}"


Example Response
{
  "message": "Comment created successfully!",
  "sanitizedContent": "\"}"
}


In the response, you'll see that the <script> tags have been sanitized, preventing the malicious code from executing.

Step 10. Testing XSS Prevention
You can test by sending different kinds of potentially harmful input, such as.
    <script>alert('XSS')</script>
    <img src="x" onerror="alert('XSS')"/>
    onmouseover="alert('XSS')"

All these will be sanitized to their encoded form to ensure no harmful JavaScript gets executed.



European ASP.NET Core 9.0 Hosting - HostForLIFE :: ASP.NET Core 8 Logging Made Simpler with Serilog and AppInsight

clock September 3, 2024 07:06 by author Peter

Logs are a crucial component of application development since they let you keep an eye on and diagnose issues and defects in your program. Developers love Serilog because it offers organized logging, which makes it easy to find and fix problems. A diagnostic logging library called Serilog was created especially for.NET applications. This method of logging application events, failures, and other pertinent data is straightforward, adaptable, and effective. Support for structured logging, which enables developers to log rich, structured data rather than just plain text messages, is one of Serilog's main advantages.

This post will teach us how to use Serilog with sink AppInsights for application logging in an ASP.NET Core Web API project. Let's now proceed to using Verilog to develop application logging using stages.

Step 1: If your project requires the use of AppInsights for Serilog logging, create a new ASP.NET Core Web API application (.NET8) or utilize an already-existing one.

Give the project name, select the location of the project, and Click on Next.

Select project framework: .NET 8, as depicted below.

Step 2. Install the following packages in your project from the NuGet package manager.

  • Serilog
  • Serilog.AspNetCore
  • Serilog.Sinks.File
  • Serilog.Sinks.ApplicationInsights

Step 3. After the installation of the necessary packages, we will need to configure Serilog in the appsetting.json file. Implementing logging with Verilog in local files and Appinsights is almost similar. However, there are some changes, such as in Using, Write To, and so on, which you can see in the below appsetting.json file.

Note. You need to have an Azure subscription and have already created application insights for this configuration to work.

"Serilog": {
    "Using": [
        "Serilog.Sinks.ApplicationInsights"
    ],
    "MinimumLevel": {
        "Default": "Information",
        "Override": {
            "Microsoft": "Warning",
            "System": "Warning"
        }
    },
    "WriteTo": [
        {
            "Name": "ApplicationInsights",
            "Args": {
                "connectionString": "",
                "telemetryConverter": "Serilog.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights"
            }
        }
    ],
    "Enrich": [ "FromLogContext" ],
    "Properties": {
        "Application": "Connect.IMS"
    }
},


If you want to log in to the local file, you can refer to the previous article here.

Step 4. In the Program.cs, we will add the Serilog configuration
builder.Host.UseSerilog((context, configuration) =>
    configuration.ReadFrom.Configuration(context.Configuration));


Step 5. Then, above the App.Run() write Serilog middleware.
app.UseSerilogRequestLogging();

Step 6. Then, we can now log in to any C# class.
Below is an example of logging in TestController.
using Microsoft.AspNetCore.Mvc;
namespace SampleLogger.Controllers;
[ApiController]
[Route("[controller]")]
public class TestController :  ControllerBase
{
    private readonly ILogger<TestController> _logger;
    public TestController(ILogger<TestController> logger)
    {
        _logger = logger;
    }
    [HttpGet]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public IActionResult Get()
    {
        _logger.LogInformation("Test Controller called!");
        return Ok();
    }
}


Now, we can run the application and check the logs in application insights.

Conclusion

By integrating Serilog to produce more thorough and useful log data, developers can enhance debugging, monitoring, and system maintenance. Because of its structured logging features, Serilog is crucial for contemporary applications as they facilitate log analysis and visualization. The traceability, dependability, and general performance of your application can all be greatly enhanced by using Serilog. This tutorial taught us how to use Application Insights and Serilog to log apps. I hope it's useful to you.



European ASP.NET Core 9.0 Hosting - HostForLIFE :: Caching Singleton Patterns in ASP.NET C#

clock August 27, 2024 07:25 by author Peter

Let's look at an example of implementing the Singleton Design Pattern Real-Time Example Caching in C#. Stated otherwise, the class will be designed as a Singleton and will include the basic Caching capabilities. As a result, by using the Singleton instance, we may add elements to the cache, update the cache, retrieve elements from the cache, delete a specific element from the cache, and remove all elements from the cache.

In our post on Singleton vs. Static Class, we discussed how a Singleton class can inherit from another class, whereas a Static class can never inherit from another class. Let's now examine the inheritance mechanism used in this Caching Example's Singleton class.

Make the Cache Interface
The first step is to copy and paste the code below into the ICacheService.cs class file. This interface describes the operations required for caching. As you can see, we are using the object as the argument for both keys and values in order to store any data in the Cache.

public interface ICacheService
{
    bool Add(object key, object value);

    bool AddOrUpdate(object key, object value);

    object Get(object key);

    bool Remove(object key);

    void Clear();
}


Creating the Singleton Class
The Singleton Class must now be created by deriving from the ICacheService interface mentioned above and providing an implementation for each of the five interface methods. In order to use the following code, create a class file called CacheService.cs and copy and paste it.
public sealed class CacheService : ICacheService
{
    // Concurrent Dictionary Collection is Thread-Safe;
    // yet, it is a shared resource that requires protection in a multithreaded environment.
    private ConcurrentDictionary<object, object> _concurrentDictionary = new();

    // Initializing the variable during class startup and preparing it for use later on,
    // this variable will hold the Singleton Instance.
    private static readonly CacheService _cacheService = new();

    // The Singleton Instance will be returned by the subsequent Static Method.
    // A thread-safe method that makes use of eager loading
    public static CacheService GetInstance()
    {
        return _cacheService;
    }

    // To prevent class instantiation from outside of this class, the constructor must be private.
    private CacheService()
    {
        Console.WriteLine("Singleton cache instance created.");
        Console.WriteLine();
    }

    // The Singleton Instance can be used to access the following methods from outside of the class.

    // A Key-Value Pair can be added to the Cache using this function.
    public bool Add(object key, object value)
    {
        return _concurrentDictionary.TryAdd(key, value);
    }

    // A Key-Value Pair can be added or updated into the Cache using this function.
    // Add the key-value pair if the key is unavailable.
    // Update the key's value if it has already been added.
    public bool AddOrUpdate(object key, object value)
    {
        if (_concurrentDictionary.ContainsKey(key))
        {
            _concurrentDictionary.TryRemove(key, out _);
        }

        return _concurrentDictionary.TryAdd(key, value);
    }

    // If the specified key is in the cache, this function is used to return its value.
    // If not, return null.
    public object Get(object key)
    {
        if (_concurrentDictionary.ContainsKey(key))
        {
            return _concurrentDictionary[key];
        }

        return null!;
    }

    // Using this technique, the specified key is deleted from the cache.
    // Return true if removed; return false otherwise.
    public bool Remove(object key)
    {
        return _concurrentDictionary.TryRemove(key, out _);
    }

    // Using this technique, the specified key is deleted from the cache.
    // Return true if removed; return false otherwise.
    public void Clear()
    {
        // Eliminates every key and value from the Cache, or the ConcurrentDictionary.
        _concurrentDictionary.Clear();
    }
}


Code Explanations

  • To prevent Thread-Safety Problems when executing the program in a Multithreaded Environment, we have implemented the Singleton Design Pattern using Eager Loading in the CacheService class mentioned above.
  • Here, we store the data using key-value pairs in the ConcurrentDictionary collection. This will function similarly to a cache. We utilize the ConcurrentDictionary collection rather than the Generic Dictionary because it is by default thread-safe, meaning that we won't have any thread-safety problems while utilizing a multithread environment.
  • The Key and Value are expected parameters for the Add method. If the key does not already exist in the collection, it then adds the key and values to the ConcurrentDictionary collection. It won't add if the key already exists; in that case, it will return False.
  • The Key and Value are additional parameters that the AddOrUpdate function requires. If the key does not already exist in the collection, it then adds the key and values to the ConcurrentDictionary collection. If the key already exists, it will update the value with the new value that was received.
  • Based on the supplied key, the Get method will return the value. It returns null if the key is not present in the collection.
  • The Remove function returns true after removing the key from the Collection or Cache. It will return false if the key is not present.
  • Every key and value in the collection will be eliminated using the Clear technique.

Utilizing Caching and Singleton Instance in Client Code
The Client Code will be the Program class in our case. To learn how to do caching using the singleton class, let's change the Program class's Main method to use the singleton instance.
const string ID = "Id";
const string NAME = "Name";

// Bring the singleton cache instance
CacheService cache = CacheService.GetInstance();

// Using the Add and AddOrUpdate Method to Add Keys and Values to the Cache
Console.WriteLine("Putting Values and Keys in the Cache");
Console.WriteLine($" Putting Id in Cache: {cache.Add(ID, 1)}");
Console.WriteLine($" Putting Name in Cache: {cache.Add(NAME, "Peter")}");

Console.WriteLine($" Putting Same Id Key in Cache using Add: {cache.Add(ID, 163)}");
Console.WriteLine($" Putting Same Id Key in Cache using AddOrUpdate: {cache.AddOrUpdate(ID, 163)}");

// Using the Get Method and the Keys to access values from the Cache
Console.WriteLine("\nBring Values from Cache");
Console.WriteLine($" Bring Id From Cache: {cache.Get(ID)}");
Console.WriteLine($" Bring Name From Cache: {cache.Get(NAME)}");

// Using the Remove Method to remove elements from the cache by giving the specified keys
Console.WriteLine("\nRemoving Values from Cache");
Console.WriteLine($" Remove Id: {cache.Remove(ID)}");
Console.WriteLine($" Accessing Id From Cache: {cache.Get(ID)}");

// Using the Clear Method to Remove Every Element from the Cache
cache.Clear();
Console.WriteLine("\nClearing All Keys and Values");
Console.WriteLine($" Bring Name From Cache: {cache.Get(NAME)}");


We learned the new technique and evolved together.

Happy coding!



European ASP.NET Core 9.0 Hosting - HostForLIFE :: Real-Time Monitoring of Pageviews Using.NET Core

clock August 22, 2024 06:50 by author Peter

In order to improve and customize user experiences, digital content authors and website managers must have a thorough understanding of how consumers engage with published information. Acknowledging the significance of engagement metrics, it was imperative to put in place a system that tracks and shows the number of page views for every post in real time.

The requirement for this functionality stemmed from several key objectives.

  • Immediate Feedback: Authors and administrators wanted immediate feedback on the performance of newly published articles.
  • User Engagement: Displaying pageview counts can increase transparency and user engagement, as readers can see how popular an article is, potentially influencing their interactions (like comments and shares).
  • Content Strategy Optimization: Real-time data helps in quickly identifying trends and reader preferences, enabling quicker adjustments to content strategy.
  • Enhanced User Experience: Real-time updates contribute to a dynamic and interactive user experience, making the website feel more alive and responsive.

Putting such a feature into practice requires a number of contemporary tools and techniques. We'll use Entity Framework Core for database operations,.NET Core for server-side logic, and SignalR for real-time web functionality in this tutorial. Their strong performance, scalability, and broad community and Microsoft support are the main factors influencing these decisions.

Tools and Technologies

  • .NET Core: A versatile platform for building internet-connected applications, such as web apps and services.
  • Entity Framework Core: An object-database mapper that enables .NET developers to work with a database using .NET objects, eliminating the need for most data-access code.
  • SignalR: A library for ASP.NET developers that simplifies the process of adding real-time web functionality to applications.
  • SQL Database: Utilized to store and retrieve pageview data and article content efficiently.

Implementation Overview
To achieve real-time pageview tracking, we will implement the step-by-step example.
Model Setup: Define a data model for articles that includes a pageview count.
Database Configuration: Set up Entity Framework Core with SQL Server to manage data persistence.
SignalR Integration: Implement a SignalR hub to facilitate the real-time broadcasting of pageview counts to all connected clients.
Incrementing Views: Modify the server-side logic to increment pageview counts upon article access.
Client-Side Updates: Use SignalR on the client side to update the pageview count in real time as users visit the page.

Phase-by-Step Execution
This is how we can put real-time pageview tracking into practice.

Step 1. Define the Article Model
First, add a property to track page visits in the updated model.

public class Article
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int PageViews { get; set; } // Tracks the number of views
}

Step 2. Configure Entity Framework Core
Configure Entity Framework Core in the Startup.cs to include our database context with SQL Server.
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    services.AddControllersWithViews();
    services.AddSignalR();
}

Step 3. Implement SignalR Hub
Create a SignalR hub that manages the broadcasting of pageview updates to connected clients.
public class PageViewHub : Hub
{
    public async Task UpdatePageViewCount(int articleId, int pageViews)
    {
        await Clients.All.SendAsync("ReceivePageViewUpdate", articleId, pageViews);
    }
}

Step 4. Increment Pageviews in Controller
Modify the controller to increment the pageview count each time an article is viewed and notify clients using SignalR.
public async Task<IActionResult> ViewArticle(int id)
{
    var article = await _context.Articles.FindAsync(id);
    if (article == null)
    {
        return NotFound();
    }
    article.PageViews++;
    _context.Update(article);
    await _context.SaveChangesAsync();
    await _hubContext.Clients.All.SendAsync("ReceivePageViewUpdate", article.Id, article.PageViews);
    return View(article);
}

Step 5. Update Client-Side Code
On the client side, use SignalR JavaScript client to update the pageview count in real time.
@section Scripts {
    <script src="~/lib/signalr/signalr.min.js"></script>
    <script>
        var connection = new signalR.HubConnectionBuilder().withUrl("/pageViewHub").build();
        connection.on("ReceivePageViewUpdate", function (articleId, pageViews) {
            var pageViewElement = document.getElementById("page-views-" + articleId);
            if (pageViewElement) {
                pageViewElement.innerText = pageViews + " Views";
            }
        });
        connection.start().catch(function (err) {
            return console.error(err.toString());
        });
    </script>
}


Conclusion
Now that real-time pageview monitoring is included, the platform can provide a more responsive and dynamic user experience. This feature improves user engagement while also offering insightful data that aids in the development of successful content initiatives. By utilizing contemporary technologies such as SignalR and.NET Core, we can maintain our status as a community-driven website that values and reacts to user interactions.



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