ASP.NET 6 return a message in a ProblemDetails with a 404s and 400s

In ASP.NET 6, the standard way to return a 404 from a Controller action is with

return NotFound();

And if you want to return a message with it, you can do

return NotFound($"Couldn't find an account with id {accountId}.");

Which returns:

Couldn't find an account with id 123.

Which isn’t ideal. Ideally we want to return a ProblemDetails like a nice API should. I found this workaround:

return Problem(statusCode: StatusCodes.Status404NotFound, detail: $"Couldn't find an account with id {accountId}");

Which returns us a ProblemDetails:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
  "title": "Not Found",
  "status": 404,
  "detail": "Couldn't find an account with id 123.",
  "traceId": "00-8292718bbb9d727dd1108abe3165deac-82f256594498617d-00"
}

Similarly, for 400s, you might think the best thing to return is

return BadRequest("Invalid accountId.");

But sadly my friend, as with NotFound(“…”) above, you’ll just get a 400 with a string in the request body. To return a ProblemDetails with a 400, you can call

ModelState.AddModelError(nameof(accountId), "must be greater than zero");
return ValidationProblem(ModelState);

Which gives you:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-3ae564a748991dee1d1a269d73e73b3f-bcdb5d6686a1b66a-00",
  "errors": {
    "accountId": [
      "must be greater than zero"
    ]
  }
}

Or if you’re not happy with the shape of that response, you could use Problem() as above:

return Problem(statusCode: StatusCodes.Status400BadRequest, detail: $"{nameof(accountId)} must be greater than zero.");
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "Bad Request",
  "status": 400,
  "detail": "accountId must be greater than zero.",
  "traceId": "00-0fc60678b8c5dee4e6d22faa40d8f12a-4e1992ddf4c43cd0-00"
}

While we’re on the topic, don’t forget to decorate your controller actions with these responses:

[SwaggerResponse(StatusCodes.Status400BadRequest, null, typeof(ProblemDetails), "application/problem+json")]
[SwaggerResponse(StatusCodes.Status404NotFound, null, typeof(ProblemDetails), "application/problem+json")]
Advertisement

Stub typed HttpClients in ASP.NET 6 integration tests

For the last 8 years or so my work doesn’t have many unit tests in it, instead I favour integration tests which fire up and run the web API we are testing in memory, and then run tests against that. This way you test the entire stack as a black box, which has loads of benefits:

  • you’re testing the entire stack so all the Httphandlers etc
  • you can usually refactor the code freely, as your tests are not tied to the implementation

Years ago on .NET Framework we had to jump through all sorts of OWIN hurdles to get this to work, but fortunately ASP.NET Core has supported it since the beginning, and has decent documentation over at https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests – although, the documentation of late seems to be targeted more at ASP.NET Razor Pages rather than at APIs like it was a few years ago.

In the early days we went to the hassle of writing full on BDD tests using Specflow, but for most teams that’s too much effort and unless the BAs or Product Owners can actually access your source code and have an interest in reading your tests, then that’s usually wasted effort, so XUnit tests with Given When Then comments (or Arrange Act Assert) is usually what I roll with these days.

Integration tests shouldn’t make any external calls, so this means you need to come up with a strategy for faking the database and stubbing HTTP calls to 3rd parties. The latter is the topic of this post.

Lately I’ve been using typed Httpclients in my solutions. I couldn’t find it documented anywhere a way to stub typed HttpClients, so this is the solution I came up with.

I override the HttpClientFactory to always return the same mocked HttpClient.
Tests can call the StubHttpRequest method to stub any http request.

public class TestWebApplicationFactory<TStartup>
        : WebApplicationFactory<TStartup> where TStartup : class
{
    private readonly Mock<HttpMessageHandler> mockHttpMessageHandler;
    private Mock<IHttpClientFactory> mockFactory;

    public TestWebApplicationFactory()
    {
        mockHttpMessageHandler = new Mock<HttpMessageHandler>();
        var client = new HttpClient(mockHttpMessageHandler.Object);
        client.BaseAddress = new Uri("https://test.com/");
        mockFactory = new Mock<IHttpClientFactory>();
        mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(sp =>
        {
            sp.AddScoped(sp => mockFactory.Object);
        });
    }

    public void StubHttpRequest<T>(string requestUrl, HttpStatusCode statusCode, T content)
    {
        _mockHttpMessageHandler
            .Protected()
            .Setup<Task<HttpResponseMessage>>("SendAsync",
                ItExpr.Is<HttpRequestMessage>(msg => msg.RequestUri!.ToString().EndsWith(requestUrl, StringComparison.InvariantCultureIgnoreCase)),
                ItExpr.IsAny<CancellationToken>())
            .ReturnsAsync(new HttpResponseMessage
            {
                StatusCode = statusCode,
                Content = new StringContent(JsonConvert.SerializeObject(content)),
            });
    }
}

Then, the test code that uses it might look like:

public class GetAccountMembershipFeature : IClassFixture<TestWebApplicationFactory<Program>>
{
    private const string URL = "customers/{0}/accounts/{1}/membership";
    private readonly TestWebApplicationFactory<Program> _factory;
    private readonly HttpClient _sut;

    public GetAccountMembershipFeature(TestWebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _sut = factory.CreateClient();
    }

    [Fact]
    public async Task GetAccountMembership_IsRegistered()
    {
        // GIVEN this customer and account
        long customerId = 88888888;
        long accountId = 9828282828;

        // WHEN the downstream system contains this membership information
        var membership = new AccountMembershipByTypeResponse
        {
            AccountNo = accountId,
            Amount = null,
            EndDate = null,
            ExternalReference = "7",
            MembershipType = membershipType,
            StartDate = new DateTime(2021, 1, 1)
        };

        _factory.StubHttpRequest(ApiUrls.AccountMembershipByTypeUrl(accountId.ToString(), membershipType, true), 
            HttpStatusCode.OK, 
            new List<AccountMembershipByTypeResponse> { membership });

        // THEN our GET endpoint should return the correct Amount
        var response = await _sut.GetFromJsonAsync<MembershipDetails>(string.Format(URL, customerId, accountId));
        response.ShouldNotBeNull();
        response.Amount.ShouldBe("7");
        response.Registered.ShouldBe(true);
    }

There’s also the ApiUrls helper which is a public method in the production code, which the test code also calls:

public static class ApiUrls
{
    public static string RegisterBankTransferUrl => "api/api/payment/transaction/BankTransfer";
    public static string CreateDirectDebitUrl => "api/payment/directdebit/create";
    public static string CreateSmoothPayUrl => "api/payment/smoothpay/create";

    public static string AccountMembershipByTypeUrl(string accountNo, string membershipType, bool currentOnly) =>
        $"api/accounts/{accountNo}/membershipbytype/{membershipType}?currentOnly={currentOnly}";

Hopefully that helps someone.

Windows Authentication with a .NET 6 typed HttpClient

Recently at a client’s site I had to write a new API which calls a downstream web API which was secured with Kerberos authentication, or something, I think. Not sure.

I dug up the code for an existing .NET Framework solution which calls the legacy service. The old code looks like this:

WebRequest myWebRequest = WebRequest.Create(serverUrl);

// Set 'Preauthenticate' property to true. Credentials will be sent with the request.
myWebRequest.PreAuthenticate = true;
myWebRequest.Credentials = new NetworkCredential(user, password);

using (WebResponse myWebResponse = myWebRequest.GetResponse())
using (Stream receiveStream = myWebResponse.GetResponseStream())
{
    byte[] data = Helper.ReadFully(receiveStream);

I didn’t know what a WebRequest’s NetworkCredential is – but a bit of Fiddler investigation revealed the following header is being sent:

Authorization: Negotiate TlRMTVNTUAADAAAA...

I wasn’t sure how to call this with .NET 6, and it’s not documented in the HttpClient documentation. Fortunately, I eventually found my way to good ol’ Rick Strahl, who has a solution at https://weblog.west-wind.com/posts/2021/Nov/27/NTLM-Windows-Authentication-Authentication-with-HttpClient

In my solution I’m using typed HttpClients which is different to Rick’s custom HttpClient factory implementation. My working solution looks like this:

builder.Services.AddHttpClient<ILegacyClient, LegacyClient>(
    client =>
    {
        client.DefaultRequestHeaders.UserAgent.ParseAdd("My great Api");
        client.BaseAddress = new Uri(builder.Configuration["Clients:Legacy:BaseUri"]);
    }).ConfigurePrimaryHttpMessageHandler(() =>
    {
        var username = builder.Configuration["Clients:Legacy:Username"];
        var password = builder.Configuration["Clients:Legacy:Password"];

        var credentials = new NetworkCredential(username, password);
        return new HttpClientHandler { Credentials = credentials, PreAuthenticate = true };
    });

Easy once you know how.

Swagger or OpenApi 3.0 examples in Swashbuckle.AspNetCore

If you’d like to generate request and response examples for your APIs, you no longer need to use my Swashbuckle.AspNetCore.Filters package.

Since May 2018, Swashbuckle.AspNetCore supports adding examples via XML comments.

For installation instructions, see the instructions in Swashbuckle.AspNetCore’s readme.

Request examples – POST

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpPost]
    public void Submit(WeatherForecast forecast)
    {
        // blah
    }
}

public class WeatherForecast
{
    /// <summary>
    /// The date of the forecast in ISO-whatever format
    /// </summary>
    public DateTime Date { get; set; }

    /// <summary>
    /// Temperature in celcius
    /// </summary>
    /// <example>25</example>
    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    /// <summary>
    /// A textual summary
    /// </summary>
    /// <example>Cloudy with a chance of rain</example>
    public string Summary { get; set; }
}

Results in:

Request examples – GET

OpenApi 3.0 supports examples on querystring parameters, which is pretty handy. Just add example= to the param:

/// <summary>
/// Retrieves a specific product by unique id
/// </summary>
/// <param name="id" example="123">The product id</param>
[HttpGet("{id}")]
public Product GetById(int id)

Courtesy of my pull request :-)

Or if you’ve got a reference type in your request (who would do that?), it still works:

// e.g. https://localhost:5001/weatherforecast/AU/MEL/1/2/2020
[HttpGet]
[Route("{country}/{city}/{day}/{month}/{year}")]
public string Get([FromRoute]WeatherRequest wr)
{
    // blah
}

public class WeatherRequest {
    /// <summary>
    /// The 2 digit country code
    /// </summary>
    /// <example>New Zealand, bro</example>
    public string Country { get; set;}
    public string City { get; set; }
    public int Day { get; set; }
    public int Month { get; set; }
    public int Year { get; set; }
}

Response examples

Response examples, again just add XML comments to your response class, and [ProducesResponseType]

[HttpGet]
[ProducesResponseType(typeof(WeatherForecast), StatusCodes.Status200OK)]
public WeatherForecast Get()
{
    // blah
}

// see WeatherForecast at the top of this post

Again, for installation instructions, see the instructions in Swashbuckle.AspNetCore’s readme.

Add Swagger request and response examples in XML

A few years ago I blogged about how to add Swagger examples for requests and responses. But those examples are rendered in JSON. What if your application supports XML, wouldn’t it be nice to see the examples in XML too? Let me show you how to set that up (in .NET Core).

Enable XML requests and responses

Firstly, you need to enable XML in your requests and responses.

services
.AddMvc(options => {
    options.InputFormatters.Add(new XmlSerializerInputFormatter());
    options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
})

Swashbuckle will now show XML in the supported content types select list for the request:

and for the response:

Now you’ll need to consume version 5.0.0-beta or later of my Swashbuckle.AspNetCore.Filters NuGet package. Follow the instructions and implement IExamplesProvider<T>. Then when you choose application/xml in the request or response select list, you’ll see the example in XML format:

Or in JSON format (as before):

Verify data in tests with ASP.NET Core and EF Core in memory

Introduction

You’re writing a Web API with ASP.NET Core 2.1, and use EF Core as your ORM.
If you follow the official guidance on doing integration tests in ASP.NET Core 2.1, then you can use either an in-memory database provider, or SQLite in-memory. We are using an in-memory database provider, which is setup in our CustomWebApplicationFactory as per the current Microsoft guidelines which are these:

public class CustomWebApplicationFactory<TStartup> 
    : WebApplicationFactory<RazorPagesProject.Startup>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            // Create a new service provider.
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase()
                .BuildServiceProvider();

            // Add a database context (ApplicationDbContext) using an in-memory 
            // database for testing.
            services.AddDbContext<ApplicationDbContext>(options => 
            {
                options.UseInMemoryDatabase("InMemoryDbForTesting");
                options.UseInternalServiceProvider(serviceProvider);
            });

These work well when running integration tests, as it allows you to quickly setup a database for each test, independent of every other test.

The problem

I couldn’t find any guidance on how to verify the data gets written correctly to the database in an assertion. e.g. here is one of our tests. We are using the XBehave library:

[Scenario]
public void CreateApplication_ShouldReturn201()
{
    "Given a valid create application request"
      .x(() => _fixture.GivenAValidCreateApplicationRequest());
    "When an application is created"
      .x(() => _fixture.WhenAnApplicationIsCreated());
    "Then the response http status code is a 201"
      .x(() => _fixture.ThenTheResponseStatusCodeIs(HttpStatusCode.Created));
    "And the response should contain an id"
      .x(() => _fixture.ThenTheResponseShouldContainAnApplicationId());
    "And the database should contain the application"
      .x(() => _fixture.ThenTheDatabaseShouldContainTheApplication());
}

How to verify the final step – “ThenTheDatabaseShouldContainTheApplication”?

The solution

Firstly, you need to change the call to AddDbContext so that your DbContext is a Singleton (the default is Scoped).

services.AddDbContext<ApplicationDbContext>(options => 
{
    options.UseInMemoryDatabase("InMemoryDbForTesting");
    options.UseInternalServiceProvider(serviceProvider);
}, ServiceLifetime.Singleton);

Then, you can get a DbContext from the system under test by asking its service collection for one, with a helper property like so:

protected ApplicationDbContext DbContext
{
    get => TestServer.Host.Services.GetService(typeof(ApplicationDbContext)) as ApplicationDbContext;
}

Since the DbContext is a singleton it’ll be the same one the system under test used, so we can query the DbContext for it directly.

internal void ThenTheDatabaseShouldContainTheApplication()
{
    var application = DbContext.Applications.Find(ApplicationId);
    Assert.NotNull(application);
}

Azure Key Vault + MSI = failing Web API integration tests

Introduction

In a number of our ASP.NET Core Web APIs we’re using Azure Key Vault for keeping secrets such as connection strings and authentication credentials out of source control.

Initially we were using Azure Key Vault with a clientid and clientsecret, which are stored in appsettings.json like so:

  "KeyVault": {
    "Name": "MyApplicationDev",
    "ClientId": "abcdefg123-b292-4177-ba53-858227a9143c",
    "ClientSecret": "5m9g9cpuNc31abcJZcjkfP9/pDwJgQ+T82t/qCey7Nc="
  },

But this begs the question – what happens if the Key Vault ClientId and ClientSecret get compromised? To prevent this you can setup Key Vault to use Managed Service Identity (MSI). With that in place you don’t need to have the ClientId and ClientSecret in your appsettings.json, instead you only need the KeyVault url:

  "KeyVaultSettings": {
    "Url": "https://myapplicationdev.vault.azure.net"
  },

This is usually configured in the WebApi’s Program.cs, via something like:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, builder) =>
        {
            builder.SetBasePath(context.HostingEnvironment.ContentRootPath)
                    .AddEnvironmentVariables();

            var config = builder.Build();
            var tokenProvider = new AzureServiceTokenProvider();
            var keyvaultClient = new KeyVaultClient((authority, resource, scope)
               => tokenProvider.KeyVaultTokenCallback(authority, resource, scope));
                    
            builder.AddAzureKeyVault(config["KeyVaultSettings:Url"], keyvaultClient, new DefaultKeyVaultSecretManager());
        })
        .UseStartup<Startup>();

Under the covers, this will add an AzureKeyVaultConfigurationSource to the registered list of IConfigurationBuilders (as well as do other things).

The problem

In ASP.NET 2.1 we are using the new WebApplicationFactory<T> for running integration tests against an in-memory TestServer. When you run these tests locally it’ll use your (i.e. you, the developer’s) AD credentials to authenticate against the Key Vault, and so the tests should pass if you have access to the Key Vault. However, when the tests run on the Build server (as part of your CI pipeline) then they’ll probably fail because the Build agent does not have access to the Key Vault.

My solution

Firstly, I didn’t want to change the code of the system under test (i.e. the Web API), i.e. by adding configuration to determine whether to use Key Vault or not. Although thinking about it, that might have been easier! But it feels a bit dirty to change the application code to make integration tests easier.

The approach I took was to remove the AzureKeyVaultConfigurationSource from the list of IConfigurationBuilders from the system under test, in my custom WebApplicationFactory<T>, i.e:

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
    builder.ConfigureAppConfiguration((_, configurationBuilder) =>
    {
        var keyVaultSource = configurationBuilder.Sources.FirstOrDefault(cs => cs.GetType().Name == "AzureKeyVaultConfigurationSource");
        if (keyVaultSource != null)
        {
            configurationBuilder.Sources.Remove(keyVaultSource);
        }
    });
}

This way, the code in the system under test is unchanged, instead we are just changing the configuration of the TestServer prior to it starting. Hope that helps someone.

Where to download vstest.console.exe

Often when I’m debugging builds or releases on VSTS, I will see that it uses the vstest.console.exe command line for running tests. Sometimes I need to run vstest.console.exe locally so that I can debug test run failures.

FYI, it is installed as part of Visual Studio 2017, and if you run the Visual Studio Developer Command prompt you will be able to run it from there. The executable lives in C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow

If you don’t have VS2017 installed and you need to download it so that you can run it locally, you can find it in the Microsoft.TestPlatform NuGet package.

Once you’ve downloaded the NuGet package, rename it from microsoft.testplatform.15.8.0.nupkg to microsoft.testplatform.15.8.0.zip. Then open it, and you’ll find vstest.console.exe in the tools\net451\Common7\IDE\Extensions\TestPlatform folder.

Add an authorization header to your swagger-ui with Swashbuckle (revisited)

Just over a year ago I blogged a simple way to add an authorization header to your swagger-ui with Swashbuckle. Although that works, Swagger-UI and Swashbuckle support a better way, which I’ll describe below.

Before starting I assume you’ve already got OAuth2 setup correctly on your application (using bearer tokens), and you have decorated your controllers and actions with [Authorize] attributes. If you haven’t, that is beyond the scope of this blog post. Here all I’m doing is explaining how to configure Swashbuckle.

First, you need to tell Swashbuckle what security your API has:

services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("oauth2", new ApiKeyScheme
    {
        Description = "Standard Authorization header using the Bearer scheme. Example: \"bearer {token}\"",
        In = "header",
        Name = "Authorization",
        Type = "apiKey"
    });

This adds a securityDefinition to the bottom of the Swagger document, which Swagger-UI renders as an “Authorize” button:

Clicking that brings up a dialog box where you can put your bearer token:

The next thing we need to do is tell Swashbuckle which of our actions require Authorization. To do that you can use the SecurityRequirementsOperationFilter:

services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("oauth2", new ApiKeyScheme
    {
        Description = "Standard Authorization header using the Bearer scheme. Example: \"bearer {token}\"",
        In = "header",
        Name = "Authorization",
        Type = "apiKey"
    });

    options.OperationFilter<SecurityRequirementsOperationFilter>();

You can either download the SecurityRequirementsOperationFilter from here, or, if you’re using ASP.NET Core you can install my Swashbuckle.AspNetCore.Filters package from NuGet, which includes it (and other filters).

The SecurityRequirementsOperationFilter adds a security property to each operation in the Swagger document, which renders in Swagger-UI as a padlock next to the operation:

Once you’ve done that, when you “Try it out” using the Swagger-UI, the authorization header with your bearer token should be sent to your API.