.NET 7 Entity Framework Upgrade

We have just upgraded a project from .NET 6 to .NET 7 and updated the package dependencies to Entity Framework Core.

If you do this you might get this Certificate Chain error:

This is because the latest Entity Framework Core requires the server certificate to be trusted. Add the following to your SQL Server connection string:

Trust Server Certificate=true;

C# Nullable Context

We had an older project that we are updating to C#11 and .NET 7.0 and noticed that the nullable compiler warnings were not showing. These are turned on in new projects (C#8.0 and above in VS2022 etc) by default.

But if you load an older project and look in the project properties, you will see the dropdown empty:

Select ‘Enable’ in this setting and the compiler warning will show. For example:

Testing ASP.NET Core Web API with VSC.App.Core

This article is a guide to creating a (MSTest) test project for a JWT secured ASP.NET Core Web API. Where both the Web API under test and the tests themselves are using the VSC.App.Core.WebApi library.

For details about how to create a Web API using the VSC.App.Core.WebApi library, see here.

Start with a standard MSTest test project. Add the VSC.App.Core.WebApi project. This provides:

  • the web data model for JWT sign in as well;
  • some standard base classes for response web data models;
  • an AuthenticateController to add JWT sign in to your API;
  • a TestController that provides methods to test the authentication.

    Now add the VSC.App.Core.WebApi.Test project.

    You will need to add a config.json file to the core test project to match the Web API under test. This must include the base URL for the Web API under test, and sign-in details for an account that can be used to test authentication. For example:

    {
      "urlBase": "https://localhost:7120", 
      "userName": "test@test.com",
      "password": "Password99!"  
    }

    Make sure your config.json build action is set to ‘Copy always’.

    This gives you the ability to test the authentication part of the Web API under test.

    These tests are based on some standard Web API methods that are added to your Web API when it is based on the VSC.App.Core.WebApi project.

    Now add your own test classes and methods for the Web API under test.

    Base your test classes on the WebApiTestSecuredJwt class, and add a config.json file (as before). For example:

    [TestClass]
    public class UnitTest1 : WebApiTestSecuredJwt
    {
        [TestMethod]
        public async Task TestMethod1()
        {
            await SignIn();
        }
    }

    The test run will sign in using the credentials in the config.json file:

    The WebApiTestSecuredJwt base class provides the GetBasedUri helper which reads the base URL from the config.json file and adds a relative path. It also provides the HttpClient object, which after sign-in, will have its security headers populated with a valid JWT as a ‘Bearer Token’.

    So after sign-in, the test code can proceed with assembling the URL and creating the request, before sending the request and receiving the response. For example:

    
    ...
    
    var uri = GetBasedUri("/DoThis");
    var payload = new DoThisRequest {FirstName = "John", LastName = "Doe"};
    var request = new HttpRequestMessage(HttpMethod.Post, uri)
    {
        Content = new StringContent(JsonConvert.SerializeObject(payload), 
                      Encoding.UTF8, "application/json")
    };
    var response = await HttpClient.SendAsync(request);
    if (response.IsSuccessStatusCode)
    {
       var model = await response.Content.ReadAsAsync<MyWebDataModel>();
       ...

    Further, as we base our web data models on standard base classes for the response, more information will be available on the status of the model retrieved. See here for more information.

    Add Identity to ASP.NET Core

    We have factored out the code needed to implement Microsoft ASP.NET Core Identity security to Web APIs and applications (Blazor, Razor, MVC etc). This code implements a custom UserStore and custom RoleStore to manage access to a customised Identity data store (on SQL Server). Details of this customisation are here.

    builder.Services.AddIdentity<UserCredential, AssignedUserRole>(
        options =>
        {
            options.Password.RequireLowercase = true;
            options.Password.RequireUppercase = true;
            options.Password.RequireDigit = true;
            options.Password.RequireNonAlphanumeric = true;
            options.Password.RequiredLength = 8;
        })
        .AddDefaultTokenProviders();

    The classes UserCredential and AssignedUserRole are part of the Identity object model.

    Add JWT Security to Web API

    Add the package:

    Add authentication to the services; configured for JWT:

    builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(options =>
    {
        options.SaveToken = true;
        options.RequireHttpsMetadata = false;
        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidAudience = "https://visual-software.co.uk",
            ValidIssuer = "https://visual-software.co.uk",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("This is my shared not so secret key"))
        };
    });

    You will also need Identity configured (before) – see this article.

    Add the authorization attribute to your controller classes:

    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public class MyController: ControllerBase
    {
       ...

    Consumers of your Web API will now have to obtain a JWT and present it in the security header of their requests to access the methods in your controller. Failure to do this will return a ‘401 unauthorized’ response.