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.

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.

Add an authorization header to your swagger-ui with Swashbuckle

Edit July 2018: I’ve blogged a better way to do this. Add an authorization header to your swagger-ui with Swashbuckle (revisited).

Out of the box there’s no way to add an Authorization header to your API requests from swagger-ui. Fortunately (if you’re using ASP.NET), Swashbuckle 5.0 is extendable, so it’s very easy to add a new IOperationFilter to do it for us:

public class AddAuthorizationHeaderParameterOperationFilter : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        if (operation.parameters != null)
        {
            operation.parameters.Add(new Parameter
            {
                name = "Authorization",
                @in = "header",
                description = "access token",
                required = false,
                type = "string"
            });
        }
    }
}

Now all you need to do is register it in your EnableSwagger call:

configuration
    .EnableSwagger(c =>
    {
        c.SingleApiVersion("v1", "Commerce Services - Discounts");

        foreach (var commentFile in xmlCommentFiles)
        {
            c.IncludeXmlComments(commentFile);
        }

        c.OperationFilter<ExamplesOperationFilter>();
        c.OperationFilter<AddAuthorizationHeaderParameterOperationFilter>();
    })
    .EnableSwaggerUi(config => config.DocExpansion(DocExpansion.List));

Once that’s done it’ll give you an input field where you can paste your Authorization header. Don’t forget to add the word “bearer” if you’re using a JWT token:

Edit July 2018: I’ve blogged a better way to do this. Add an authorization header to your swagger-ui with Swashbuckle (revisited).

Generating Swagger example requests with Swashbuckle

This is a follow on from my post from last year about Generating example Swagger responses.

Update April 2020: You probably don’t need to do it this way any more. Swashbuckle.AspNetCore supports request examples via XML comments. See my blog post.

Update May 4th 2017: I have created a new NuGet package called Swashbuckle.Examples which contains the functionality I previously described in this blog post. The code lives on GitHub.

I have also created a .NET Standard version of the NuGet package at Swashbuckle.AspNetCore.Filters, which is also on GitHub.

It can also be useful to generate example requests, and in this post I will show you how.

First, install my Swashbuckle.Examples NuGet package, or the .NET Core version Swashbuckle.AspNetCore.Filters

Now decorate your controller methods with the included SwaggerRequestExample attribute:

[Route(RouteTemplates.DeliveryOptionsSearchByAddress)]
[SwaggerRequestExample(typeof(DeliveryOptionsSearchModel), typeof(DeliveryOptionsSearchModelExample))]
[SwaggerResponse(HttpStatusCode.OK, Type = typeof(DeliveryOptionsModel), Description = "Delivery options for the country found and returned successfully")]
[SwaggerResponseExample(tHttpStatusCode.OK, typeof(DeliveryOptionsModelExample))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(ErrorsModel), Description = "An invalid or missing input parameter will result in a bad request")]
[SwaggerResponse(HttpStatusCode.InternalServerError, Type = typeof(ErrorsModel), Description = "An unexpected error occurred, should not return sensitive information")]
public async Task<IHttpActionResult> DeliveryOptionsForAddress(DeliveryOptionsSearchModel search)
{

Now implement it, in this case via a DeliveryOptionsSearchModelExample (which should implement IExamplesProvider), which will generate the example data. It should return the type you specified when you specified the [SwaggerRequestExample].

public class DeliveryOptionsSearchModelExample : IExamplesProvider
{
    public object GetExamples()
    {
        return new DeliveryOptionsSearchModel
        {
            Lang = "en-GB",
            Currency = "GBP",
            Address = new AddressModel
            {
                Address1 = "1 Gwalior Road",
                Locality = "London",
                Country = "GB",
                PostalCode = "SW15 1NP"
            },
            Items = new[]
            {
                new ItemModel
                {
                    ItemId = "ABCD",
                    ItemType = ItemType.Product,
                    Price = 20,
                    Quantity = 1,
                    RestrictedCountries = new[] { "US" }
                }
            }
        };
    }

Don’t forget to enable the ExamplesOperationFilter when you enable Swagger, as before:

configuration
    .EnableSwagger(c =>
    {
        c.OperationFilter<ExamplesOperationFilter>();
    })
    .EnableSwaggerUi();

Or if you’re using .NET Core

services.AddSwaggerGen(c =>
   {
        c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
        c.OperationFilter<ExamplesOperationFilter>();

Now that we’ve done all that, we should see the examples output in our swagger.json file, which you can get to by starting your solution and navigating to /swagger/docs/v1.

Capture

And the best part is, when you’re using swagger-ui (at /swagger/ui/index), now when you click the example request in order to populate the form, instead of getting an autogenerated request like this:

Untitled

You’ll get your desired example, like this:

Capture2

I find that having a valid request on hand is useful for smoke testing your API endpoints are working correctly.

Azure Emulator not working with SQL server alias

I just spent a few hours trying to figure out something that had me stumped.

In my local dev environment I’m building a web api which has a SQL database. The connection string for the database is an alias with a named pipe. If I set the web api as StartUp project in Visual Studio it works fine. (See this post for some tips on how to do that). But when I’d instead start the Azure emulator as the StartUp project it wouldn’t connect to the sql server, with the good ol’:

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 – Error Locating Server/Instance Specified)

Eventually I figured it out. The alias was the problem, because if I changed the connectionstring to .\sqlexpress it worked fine.

Digging deeper, the problem was I had set up the alias on the “SQL Native Client 11.0 Configuration (32bit)” node, but I hadn’t added an alias on the “SQL Native Client 11.0 Configuration” node. So the fix was to create an additional Alias on the “SQL Native Client 11.0 Configuration” node.

sql

So it seems that debugging a web api locally may be a 32 bit process but debugging with the Azure emulator is a 64 bit process. Maybe?

Anyway, hope this helps someone.

Generating Swagger example responses with Swashbuckle

Update April 2020: You probably don’t need to do it this way any more. Swashbuckle.AspNetCore supports request examples via XML comments. See my blog post.

Update May 4th 2017: I have created a new NuGet package called Swashbuckle.Examples which contains the functionality I previously described in this blog post. The code lives on GitHub.

I have also created a .NET Standard version of the NuGet package at Swashbuckle.AspNetCore.Filters, which is also on GitHub.

Swashbuckle is a tool for generating Swagger, the API description language, from your ASP.NET Web Api solution.
Using Swashbuckle, which provides Swagger-UI, you can create pretty living documentation of your web api, like this:
swagger

Documenting the Response

In this post I am going to show you how to document the Response, and a new way to generate some response examples.

You can specify the type of response for Swashbuckle a number of ways. Consider a simple API endpoint which returns a list of Countries:

public class CountriesController : DefaultController
{
    [HttpGet]
    public async Task<HttpResponseMessage> Get()
    {
        var resource = new List<Country>
        {
            new Country {Code = "AR", Name = "Argentina"},
            new Country {Code = "BR", Name = "Brazil"},
            // etc etc omitted for brevity
        };

        return Request.CreateResponse(HttpStatusCode.OK, resource);
    }
}

One way of describing the response code and content for Swashbuckle is using a combination of XML comments, and the ResponseType attribute, like so:

/// <response code="200">Countries returned OK</response>
[HttpGet]
[ResponseType(typeof(IEnumerable<Country>))]
public async Task<HttpResponseMessage> Get()
{

However, this only allows for one type of response.

If your API method can return multiple types, i.e. in the case of an error, then you can use the new SwaggerResponse attribute:

[HttpGet]
[SwaggerResponse(HttpStatusCode.OK, Type=typeof(IEnumerable<Country>))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(IEnumerable<ErrorResource>))]
public async Task<HttpResponseMessage> Get(string lang)
{

The Swagger 2.0 spec allows for examples to be added to the Response. However, at time of writing Swashbuckle doesn’t support this. Fortunately Swashbuckle is extendible so here is a way of doing it.

Install my Swashbuckle.Examples NuGet package.

Decorate your methods with the new SwaggerResponseExample attribute:

[SwaggerResponse(HttpStatusCode.OK, Type=typeof(IEnumerable<Country>))]
[SwaggerResponseExample(HttpStatusCode.OK, typeof(CountryExamples))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(IEnumerable<ErrorResource>))]
public async Task<HttpResponseMessage> Get(string lang)

Now you’ll need to add an Examples class, which will implement IExamplesProvider to generate the example data

public class CountryExamples : IExamplesProvider
{
    public object GetExamples()
    {
        return new List<Country>
        {
            new Country { Code = "AA", Name = "Test Country" },
            new Country { Code = "BB", Name = "And another" }
        };
    }
}

And finally enable the ExamplesOperationFilter when you configure Swashbuckle’s startup.

configuration
    .EnableSwagger(c =>
    {
        c.OperationFilter<ExamplesOperationFilter>();
    })
    .EnableSwaggerUi();

Now that we’ve done all that, we should see the examples output in our swagger.json file, which you can get to by starting your solution and navigating to /swagger/docs/v1.

response

And then, when you browse the swagger-ui at /swagger/ui/index, instead of an autogenerated example like this:
response old

You’ll see your desired example, like this:
response new

Be sure to check out Part 2, where we again use Swashbuckle to generate example requests.