Tutorial: Implement Serilog in ASP.NET Core 3 Part I

To be clear, ASP.NET Core has a logging right under the hood. The framework has acces to structured logging API’s. Serilog is easy to set up, you can overwrite all levels of logging and create custom sinks to log to any DB or file.

Install Serilog

Install the Serilog.AspNetCore package in your solution or project, this package contains about everything needed to get started. You can use the Nuget Package Manager or use the following command in the terminal.

dotnet add package Serilog.AspNetCore

Create and configure Serilog in the Main() method of the Program.cs file. This is example contains the basics to activate Serilog in your ASP.NET Core application to write logs tot the console.

public static void Main(string[] args)
    {
         Log.Logger = new LoggerConfiguration()
            .WriteTo.Console()
            .CreateLogger();
        
          CreateHostBuilder(args).Build().Run();
      }

We need to let all logging events pass through Serilog, and just Serilog. For now, logging is still done by 2 providers. We need to let the application know that we will use Serilog only. To do that we update the CreateHostBuilder method, also located in the Program.cs file. Add .UseSerilog to the method as following:

 public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSerilog()
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            }

Now all logging happens though Serilog. We just need to clean up appsettings.json as Serilog does not use these settings by default.

// BEFORE
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

// AFTER
{
  "AllowedHosts": "*"
}

Use Serilog in your application

Just inject the ILogger where you need it, using Dependency Injection. ILogger is registered in the IServiceCollection this this can be resolved by just injecting it in the construtor.

public class ActionCommandHandler
{
    private readonly ILogger<ActionCommandHandler> _logger;

    public ActionCommandHandler(ILogger<ActionCommandHandler> logger)
    {
        _logger = logger;
    }

    public async Task<int> HandleCommand()
    {
        int result = 1 + 1;
        _logger.LogInformation("One plus one is {result}", result);

        return result;
    }
}

This is a quick walkthrough, showing you how to implement Serilog in a ASP.NET Core 3 application. In part 2 I will address how you can overwrite multiple levels of logging, and how to add custom Serilog Sinks.

.NET Core Tutorial: Using the ServiceCollection Extension Pattern

If you have worked on applications (in .NET Core) you should know how Dependency Injection (DI) is supported and is applied for your services. This is straight forward and not so hard at all. Just add them to the IServiceCollection in the ConfigureServices() pipeline in the startup.cs file. Considering a N-Tier architrecture, where our Startup.cs is in the Project.Host project and mall example:

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IService, Service>();
            services.AddScoped<IProcessor, Processor>();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        }

Now, in the code snippet above, we just add a service, a processor and the IHttpContextAccessor to the DI container. As the application grows and grows, more and more services will be added here and on a long term, this will just bloat up the startup.cs file.

For example, there is a new project added to the solution Project.Reporting which hold services like IPdfReportGeneratorService and a IPdfReportConverterService, and maybe a few more for demonstration purposes. What you would normally do is just add a reference to the reporting project and just add those services to the container. You can see already how this is adding up to the pipeline.

using Solution.Project.Reporting.Services;

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IPdfReportGeneratorService , PdfReportGeneratorService >();
            services.AddTransient<IPdfReportConverterService, PdfReportConverterService>();
            services.AddTransient<IServiceThis, ServiceThis>();
            services.AddTransient<IServiceThat, ServiceThat>();
            services.AddTransient<IServiceOther, ServiceOther>();

            services.AddTransient<IService, Service>();
            services.AddScoped<IProcessor, Processor>();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        }

We can clean this up a bit. The Service Collection Extension Pattern is exactly what it says. We add our services in a extension method in its own project. The goal is to keep this pipeline as clear as possible. Let’s do this for the Project.Reporting library in the aplication.

Inside the reporting project, we add a new static class called ReportingServiceCollectionExtensions at the root of the project. Inside we create a extension method where we extend the IServiceCollection and add the services. We also return the services – because the service registration implements the Chain Pattern.

using Microsoft.Extensions.DependencyInjection;

public static class ReportingServiceCollectionExtensions
{
    public static IServiceCollection AddReporting(this IServiceCollection services)
    {
        services.AddTransient<IPdfReportGeneratorService , PdfReportGeneratorService >();
            services.AddTransient<IPdfReportConverterService, PdfReportConverterService>();
            services.AddTransient<IServiceThis, ServiceThis>();
            services.AddTransient<IServiceThat, ServiceThat>();
            services.AddTransient<IServiceOther, ServiceOther>();

        return services;
    }
}

Now we can easily call this extension in our RegisterServices pipeline in the startup.cs file in the host project by calling services.AddReporting();. If you do this for each project/assembly/layer in your solution, this keeps the startup file nice and clean.

using Solution.Project.Reporting.Services;

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddReporting();

            services.AddTransient<IService, Service>();
            services.AddScoped<IProcessor, Processor>();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        }

I hope you understand what the Service Collection Extension Pattern is now and how to implement it. If you have any feedback or questions, do not hesitate to comment.

AutoTerminal – A VS2019 extension.

A friend of me had a little rant while developing an Angular app on a .NET Core backend. Whenever you had to run the npm run E2E command or any other npm or ng command, you had to right click on the folder node in solution explorer, open with file explorere, open cmd and maybe navigate to the right directory (cd ..).

This made me think of another fun little project to work on. Figuring out a way to open the terminal directly in the directory you want, straight from Visual Studio’s solution explorer. Just right click a folder, click ‘Open Terminal’ and there it is.

You can get it from the Marketplace & the source code is at GitHub.

Migrate your ASP.NET Core 2.2 application to ASP.NET Core 3.0 : Basic steps

The newest version of ASP.NET Core has released, and for for a lot of running applications this update will be somehow mandatory. This article will address important steps when upgrading your ASP.NET Core application from 2.2 to 2.3.

The project (.csproj) file

First thing first is to update the Target Framework to the netcoreapp3.0. There’s a long list of packages that are no longer being produced which can be removed from the csproj referenced packages, they are no longer available through the Microsoft.AspNetCore.App framework.

<TargetFramework>netcoreapp3.0</TargetFramework>

<!-- Microsoft.NET.Sdk.Web SDK implicitly references the Microsoft.AspNetCore.App -->

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>
    ...
</Project>

If your project targets the Razor SDK should add the framework reference to Microsoft.AspNetCore.App

<ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>
    ...

For more info about what package references are removed from the Microsoft.AspNetCore.App and what to do to continue using features provided by there packages, check out the official docs.

Kestrel

In Program.cs, migrate the Kestrel confguration to the WebHostBuilder.

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(serverOptions =>
            {
                // Set properties and call methods on options
            })
            .UseStartup<Startup>();
        });

JSON!

Json.NET has been removed from the ASP.NET Core shared framework. In 3.0 we get the System.Text.Json namespace build in ASP.NET Core 3.0 ! Note that if you target .NET Standard or NET Framework install the System.Text.Json NuGet package. Below is a simple and clear example.


using System;
using System.Text.Json;
using System.Text.Json.Serialization;
 
namespace Project.Component.Service.Utils
{
    class Thing
    {
        public string Name { get; set; }
        public int Count{ get; set; }
    }
     
    class Program
    {
        static void Main(string[] args)
        {
            var thingy = new Thing() 
                { 
                    Name = "Tim", 
                    Count = 30,
                };

            Console.WriteLine(JsonSerializer.Serialize<Thing>(thingy));
        }
    }
}

Startup.cs

Some changes must be or can be made to the Startup.cs configuration and services pipelines.

MVC Service Registration

In Startup.ConfigureServices there are some new options to register MVC scenarios. For example, there are new extensions on IServiceCollection available, these use new methods other than UseMvc(). However, you can still use UseMvc().

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(); // Controller & API features
    services.AddControllersWithViews();
    services.AddRazorPages(); // Razor pages and minimal controller support
}

Routing

Best practice in ASP.NET Core 3.0 when using SignalR is to migrate to Endpoint Routing. This -in most cases- only takes some adjustments to Startup.cs.

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();

    app.UseRouting();

    app.UseCors();

    app.UseAuthentication();
    app.UseAuthorization();
    

    // App.UseSignalR and App.UseMvc is now configured on app.UseEndPoints.
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<ChatHub>("/chat");
        endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });

For most apps, calls to UseAuthentication, UseAuthorization, and UseCors must appear between the calls to UseRouting and UseEndpoints to be effective. – Docs

Most of these changes do not create any breaking changes to business logic in your application, at least it didn’ when I tested. Let me know if this helped you or if you have any questions.