Saturday, 16 July 2022

Using ValueConverter in EF Core 6 to handle custom data types

Usually we stick to the familiar data types, but EF Core 6 (Entity Framework) can also both save a custom data type and also load it up again. Usually, you would save the data type in a familiar representation and then have some mapper method map into the desired type in a data transfer object - DTO. In this example we will anyways look at an alternative, a ValueConverter. This have been supported since EF Core 2.1. We first off define a ValueConverter by inhering from the ValueConverter class. This takes a 'from' and 'to' generic type argument. We convert from a type into another type and vice versa. We send in two expressions to do these mappings to the base constructor. I have pushed the code here if you want to test out ValueConverter in EF Core yourself.
git clone https://github.com/toreaurstadboss/EfCore6ValueConversionTemplate.git Let's see how we for example can save a Color (System.Drawing.Color) via its color name and load it up again.
 
 
 using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 using System.Drawing;
 using System.Linq.Expressions;

 namespace PublisherData
 {
 
    public class ColorValueConverter : ValueConverter<Color, string>
    {
        private static Expression<Func<Color, string>> ColorString =
            c => new string(c.Name);
        static Expression<Func<string, Color>> ColorStruct =
            s => Color.FromName(s);

        public ColorValueConverter() : base(ColorString, ColorStruct) { 
        }
    }

 } 
 
Next up we 'bulk-configure' a value converter for the Color type to have conversion set to this ValueConverter. Inside the db context class we do :
 
 
  protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
  {
  	configurationBuilder.Properties<Color>().HaveConversion(typeof(ColorValueConverter)); 
  }
 
 
We also define a migration for database and add a column in a table to save the color, which is defined as a string (varchar(100)) column, so we can test out the conversion.
 
  using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace PublisherData.Migrations
{
    public partial class favcolor : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AddColumn<string>(
                name: "FavoriteColor",
                table: "Authors",
                type: "nvarchar(100)",
                maxLength: 100,
                nullable: true);
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropColumn(
                name: "FavoriteColor",
                table: "Authors");
        }
    }
}

 
A small console app then tests out the Value conversion :
 
 
 // See https://aka.ms/new-console-template for more information
 using Microsoft.EntityFrameworkCore;
 using PublisherData;
 using System.Drawing;

 Console.WriteLine("Posting a new author - testing out ValueConverter feature of EF Core 6");


 var options = new DbContextOptionsBuilder<PubContext>()
    .UseSqlServer("Data Source = somedevmachine\\sqlexpress01; Initial Catalog = PubDatabase_M12_Api_EfCoreCourse; Trusted_Connection=true");


 using var pubContext = new PubContext(options.Options);

 var authorInsert = new PublisherDomain.Author
  {
    FirstName = "Claude",
    LastName = "Monet",
    FavoriteColor = Color.Aquamarine
  }; 

 pubContext.Authors.Add(authorInsert);
 await pubContext.SaveChangesAsync();
 var insertedAuthor = pubContext.Authors.Find(authorInsert.AuthorId);
 string convertColorToArgbName(Color someColor) => Color.FromArgb(someColor.ToArgb()).Name;
 string insertedAuthorFavoriteColorArgb = convertColorToArgbName(authorInsert.FavoriteColor.Value);
 string aquaMarineColorArgb = convertColorToArgbName(Color.Aquamarine);

 Console.WriteLine($"Favorite color is expected? {insertedAuthorFavoriteColorArgb.Equals(aquaMarineColorArgb)}");

 
We save the favorite color and when we load up the entity again we load the expected color by checking the Argb value 'Name' property (the hexadecimal color value acutally). The output then reads:
Posting a new author - testing out ValueConverter feature of EF Core 6 Favorite color is expected? True This is a rather trivial example. We could have a more complex object which we wanted to save in a particular way to the database and load it up again. For some reason or another, that object might not just be serialized, saved and restored and loaded via deserialization. Anyways, ValueConverter is a feature which I wanted to test out in EF Core and it is an alternative to be at least aware of.

Saturday, 9 July 2022

MediatR - Adding a pipeline behavior and logging to SeriLog and Seq

This article will present how you can add a pipeline behavior to MediatR and then log to SeriLog and Seq. This makes it possible to add for example logging and use the 'decorator pattern' with MediatR to add behavior to the pipeline of Asp.net core in general (.net 6 is used here). It is therefore sort of Middelware and tied together via MediatR. First off, lets define the IPipelineBehavior, which will do our logging. Note that you have to add the generic type parameter where condition here of TRequest. Note that we inject the ILogger<LoggingBehavior<TRequest, TResponse>> logger object here. In .net (core) we send in ILogger instance of the type of the class we are injecting into to log out to log sources and SeriLog will present this information. Inside Seq we can also browse this log information. We only log payloads if we are inside Development environment and the payload of request / response can be serialized and is below 50 kB.
 
 Program.cs
 
using MediatR;
using Newtonsoft.Json;
using System.Diagnostics;

namespace CqrsDemoWebApi.Features.Books.Pipeline
{

    public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
    {
        private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
        private readonly IWebHostEnvironment _environment;

        public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger, IWebHostEnvironment environment)
        {
            _logger = logger;
            _environment = environment;
        }

        public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
        {
            string? requestData = null;

            if (_environment.IsDevelopment())
            {
                //log serializable payload data in request (as json format) to SeriLog if we are inside Development environment and the payload is below 50 kB in serializable size
                try
                {
                    requestData = request != null ? JsonConvert.SerializeObject(request) : null;
                    if (requestData?.Length > 50 * 1028)
                        requestData = null; //avoid showing data larger than 100 kB in serialized form in the logs of Seq / SeriLog                    
                }
                catch (Exception)
                {
                    //ignore 
                }
            }
            _logger.LogInformation($"Handling request {typeof(TRequest).Name} {{@requestData}}", requestData);
        
            var response = await next();

            string? responseData = null;
            if (_environment.IsDevelopment())
            {              
                //log serializable payload data in response (as json format) to SeriLog if we are in Development environment and the payload is below 50 kB in serialized size
                try
                {
                    responseData = response != null ? JsonConvert.SerializeObject(response) : null;
                    if (responseData?.Length > 50 * 1028)
                        responseData = null; //avoid showing data larger than 100 kB in serialized form in the logs of Seq / SeriLog
                }
                catch (Exception)
                {
                    //ignore 
                }
            }
            _logger.LogInformation($"Handled request {typeof(TRequest).Name}. Returning result of type {typeof(TResponse).Name} {{@responseData}}.", responseData);
            return response;
        }

    }

}
 
 
Afterwards register the IPipelineBehavior. Note how we also add UseSerilog method here and add Seq too.
 
 Program.cs
 
using CqrsDemoWebApi.Database;
using CqrsDemoWebApi.Features.Books.Pipeline;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Serilog;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((ctx, lc) =>
 lc.WriteTo.Console()
 .WriteTo.Seq("http://localhost:5341")); //note - to use Seq with this url - install it from here (free individual license) : https://datalust.co/download

// Add services to the container.

builder.Services.AddMediatR(Assembly.GetExecutingAssembly()); //adding MediatR support here. 
//register some pipeline(s) defined for MediatR usage
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>)); 

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

var connectionString = new ConnectionString(builder.Configuration.GetConnectionString("CqrsDemoBooksDb")); 
builder.Services.AddSingleton(connectionString);

builder.Services.AddDbContext<BooksDbContext>(options =>
options.UseSqlServer(connectionString.Value)); 

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();
 
 
Here are the updated nuget packages inside the csproj file of the Asp.net core project (.net 6) :
 
 
  <ItemGroup>
    <PackageReference Include="Mediatr" Version="10.0.1" />
    <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
    <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
    <PackageReference Include="Serilog.AspNetCore" Version="6.0.0-dev-00265" />
    <PackageReference Include="Serilog.Sinks.Seq" Version="5.1.2-dev-00225" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
  </ItemGroup>
 
 
We also need to install Seq on our computer to see the messages sent to SeriLog inside Seq. Go to this url to download free individual license of Seq : https://datalust.co/download You can see the logs at Seq's default url and also set up in Program.cs file : http://localhost:5341

Friday, 8 July 2022

Using MediatR to implement CQRS pattern with EF Core

This article will present a sample solution I have pushed to GitHub. It uses MediatR library to showcase CQRS pattern together with .NET 6 and Entity Framework Core. MediatR takes care of decoupling the sending of messages to handling messages in-process. It can be used in the data layer together with EF Core for example to dispatch (send) messages which will either handle (execute) a 'command' (defined in CQRS as a 'write operation') or process and return a 'query' (defined in CQRS as a 'read' operation). What we gain from CQRS is the ability to offload the sending of these messages (commands and queries) to the correct handler for given message handler (executing either a 'command' or return the results of a 'query') and this makes it easier to create scalable code in the data layer. It gives thinner Asp.net core (web api) controller actions in the sample demo. First off, clone the solution :
git clone https://github.com/toreaurstadboss/CqrsDemoWebApi.git
The following Nuget packages are relevant for EF Core and MediatR.
 
 
    <PackageReference Include="Mediatr" Version="10.0.1" />
    <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1"> 
 
 
 
The Wiki of MediatR contains some example code here: https://github.com/jbogard/MediatR/wiki We will first look into the Request /Reponse messages, dispatched to a single handler. These are either comands or queries in this sample. Let us first consider a query that returns a list of books. Here is how it is implemented :
 
 
using CqrsDemoWebApi.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;

namespace CqrsDemoWebApi.Features.Books.Query
{
    public class GetBooks
    {

        public class Query : IRequest<IEnumerable<Book>>
        {

        }

        public class QueryHandler : IRequestHandler<Query, IEnumerable<Book>>
        {
            private readonly BooksDbContext _db;

            public QueryHandler(BooksDbContext db)
            {
                _db = db;
            }

            public async Task<IEnumerable<Book>> Handle(Query request, CancellationToken cancellationToken)
            {
                return await _db.Books.ToListAsync(cancellationToken); 
            }
        }
    }
}
 
 
We see that we are supposed to handle the query async since we must return a Task of TResponse (although we could use Task.FromResult here if we wanted to return it synchronously..) where the TRequest is of type IRequest. Note that Query only implements IRequest of the return type and has no properties (i.e. 'request parameters') since we return all books here and need no such parameters. We inject the DbContext here via dependency injection. In Program.cs of the .NET 6 solution we add MediatR using the builder.Services.AddMediatR() method:
 
  Program.cs (.NET 6 Web Api project)

using CqrsDemoWebApi.Database;
using MediatR;
using Microsoft.EntityFrameworkCore;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddMediatR(Assembly.GetExecutingAssembly()); //adding MediatR support here. 
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var connectionString = new ConnectionString(builder.Configuration.GetConnectionString("CqrsDemoBooksDb")); 
builder.Services.AddSingleton(connectionString);

builder.Services.AddDbContext<BooksDbContext>(options =>
options.UseSqlServer(connectionString.Value)); 

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();
 
Nothing special here, just basic setup of a standard web api in Net 6 here, using the AddMediatR method pointing to the assembly where our contracts reside (commands and queries and their respective handlers). The BookController web api controller looks like this :
 

using CqrsDemoWebApi.Database;
using CqrsDemoWebApi.Features.Books;
using CqrsDemoWebApi.Features.Books.Command;
using CqrsDemoWebApi.Features.Books.Query;
using MediatR;
using Microsoft.AspNetCore.Mvc;

namespace CqrsDemoWebApi.Controllers
{

    [Route("api/[controller]")]
    public class BookController : ControllerBase
    {
        private readonly IMediator _mediator;

        public BookController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpGet]
        public async Task<IEnumerable<Book>> GetBooks() 
        {
            var books = await _mediator.Send(new GetBooks.Query());
            return books; 
        }

        [HttpGet("{id}")]
        public async Task<Book> GetBook(int id)
        {
            var book = await _mediator.Send(new GetBooksById.Query { Id = id });
            return book; 
        }

        [HttpPost]
        public async Task<ActionResult> AddBook([FromBody] AddNewBook.Command command)
        {
            var createdBookId = await _mediator.Send(command);
            return CreatedAtAction(nameof(GetBook), new { id = createdBookId }, null); 
        }

        [HttpDelete("{id}")]
        public async Task<ActionResult> DeleteBook(int id)
        {
            await _mediator.Send(new DeleteCommand.Command { Id = id });
            return NoContent(); 
        }

    }
}

 
 
As we see, we inject the IMediator here and then use the Dependency Injected field to send the command of query instance. Note the use of [FromBody] in Post requests to send the POST body into the handler for the Command. Each query or command is implemented as nested classes where Query and QueryHandler is nested inside, implement IRequest of T and IRequestHandler of T (and return type U back again if we want a return result). If we do not
want a return result 'void' - we use the special type Unit. This signals that we do not return any results from the command or the query. We can return an int value for the Command after we have inserted a row. It is okay in CQRS to return a resource identifier. CQRS allows also return results from command as long as it is a resource identifier to use to look up the data via a query. (it could be Guid etc too if that is suitable). Here is a example of a command that creates a book:
 
 
 using CqrsDemoWebApi.Database;
using MediatR;

namespace CqrsDemoWebApi.Features.Books.Command
{
    public class AddNewBook
    {
        public class Command : IRequest<int>
        {
            public string? Title { get; set; }
            public string? Author { get; set; }
            public int Year { get; set; }
            public string? ImageLink { get; set; }
            public int Pages { get; set; }
            public string? Country { get; set; }
            public string? Language { get; set; }
            public string? Link { get; set; }
        }

        public class CommandHandler : IRequestHandler<Command, int>
        {
            private readonly BooksDbContext _booksDbContext;

            public CommandHandler(BooksDbContext booksDbContext)
            {
                _booksDbContext = booksDbContext;
            }

            public async Task<int> Handle(Command request, CancellationToken cancellationToken)
            {
                var book = new Book
                {
                    Title = request.Title,
                    Author = request.Author,    
                    Year = request.Year,
                    ImageLink = request.ImageLink,
                    Pages = request.Pages,
                    Country = request.Country,
                    Language = request.Language,
                    Link = request.Link
                };

                await _booksDbContext.Books.AddAsync(book, cancellationToken);
                _booksDbContext.SaveChanges();
                return book.BookId;                          
            }
        }

    }
}
 
 
A sample json body for the POST is then:
 
 
 {
   "title": "Mythical Man Month",
   "author": "Frederick Brooks",
   "year": 1975,
   "country": "USA",
   "imagelink": "",
   "pages": 222
  }
 
 

We return the BookId of the new book as we impement IRequestHandler<int> . It is the auto generated id after saving the new book. This database is seeded by the way via a json file like this :
 
 
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;

namespace CqrsDemoWebApi.Database.Seed
{
    public static class ModelBuilderExtensions
    {
        public static void SeedBooks(this ModelBuilder modelBuilder, string contentRootPath)
        {
            string jsonInput = File.ReadAllText(Path.Combine(contentRootPath, @"Database/Seed/books.json")); 
            var books = JsonConvert.DeserializeObject<Book[]>(jsonInput);

            int bookId = 0;
            foreach (var book in books!)
            {
                bookId++;
                book.BookId = bookId; 
            }
           
            modelBuilder.Entity<Book>().HasData(books);
            modelBuilder.Entity<Book>().HasKey(b => b.BookId);
            modelBuilder.Entity<Book>().Property(b => b.BookId).ValueGeneratedOnAdd();                 
        }
    }
}
 
 
And we have the DbContext like this:
 
 
 using CqrsDemoWebApi.Database.Seed;
using Microsoft.EntityFrameworkCore;

namespace CqrsDemoWebApi.Database
{
    public class BooksDbContext : DbContext
    {
        private readonly IWebHostEnvironment _hostingEnvironment;

        public BooksDbContext(DbContextOptions<BooksDbContext> options,
            IWebHostEnvironment hostingEnvironment) : base(options) 
        {
           
            _hostingEnvironment = hostingEnvironment;
        }

        public DbSet<Book> Books { get; set; } = null!;  

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            string contentRootPath = _hostingEnvironment.ContentRootPath;
            modelBuilder.SeedBooks(contentRootPath); 
        }
    }
}
 
 
Now, the naming of each nested command/command handler or query/queryhandler is given a generic name. It could be that you want to make this a more specific name, but the wrapping class is a good enough specifier for this. I.e. GetBooks.Query() or AddNewBook.Command (we use [FromBody] here and there do not new up an instance like the Query) are specified enough allowing us to use generic nested class names.. MediatR simplifies the goal of implementing CQRS and following the mediator pattern. There are more possibilities with MediatR. Look at videos on Youtube for this topic for example.