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.

Decompile ASP.NET source from deployed dlls

We had an interesting scenario at a client’s a couple of months ago. A staff member had deleted the source code repository for an ASP.NET Web API from Azure DevOps – their justification was that the repository had the name “to be deleted”, so they went ahead and deleted it.

Months later, they realised that actually, the application is still in use in production, and we need the source code back. It could not be restored from Azure DevOps, as it had been deleted too long ago.

An old timer (Mark) had recently left the company and so handed in his laptop, which, fortunately, hadn’t been reformatted, and still had a copy of the source code of the deleted repository on it. So they took the code from his laptop and added it back to Azure DevOps.

But it was always thought that the code recovered from Mark’s laptop wasn’t the latest version of the code, and that what was deployed to production was newer than the recovered code. Therefore the recovered code is read-only and only for reference, and we couldn’t deploy any changes to the API.

About a year goes by, and we need an enhancement done to the production API, which I’m asked to have a look at.

So, I first needed to determine if the source code really was older than what was really deployed. We came up with a plan:

  • deploy the recovered source code for the API to our test server
  • download the compiled dlls from the test Azure app service
  • download the compiled dlls from the production Azure app service
  • decompile the compiled dlls from both to reverse-engineer the source code
  • compare the decompiled source code from both to determine what code changes there are

It could have been quite a laborious process, because the application in question had lots of projects (.csproj files) and thus lots of dlls (28) to compare. But I found a couple of tools which helped greatly.

  • ILSpy (free)
  • Beyond Compare (free trial)

Download the dlls from Azure

You can do this from the Azure Portal using the Kudu console, aka “Advanced tools”, and then the CMD Debug Console:

Decompile source code from the Azure app service website

ILSpy is a great tool for this, and it’s free.

  1. Open ILSpy, and clear out the Assemblies window (Ctrl-A, delete)
  2. Unzip the wwwroot folder you downloaded above
  3. Select the .dlls containing your source code
  4. Drag them into ILSpy’s Assemblies window
  5. Select all of the Assemblies in ILSpy (Ctrl-A)
  6. File -> Save Code. ILSpy will generate a Visual Studio .sln file containing all the code for you – too easy!

I did this for our test server (from the recovered source code), and then again for the code in production.

Compare content of two folders

Once I had my decompiled source from test and production, I needed to compare the 2 to check for differences.

I couldn’t find a decent free tool to easily compare the content of 2 folders. I ended up using Beyond Compare, which I first saw over ten years ago. It has a free trial. I tweaked its options to ignore timestamps.

Leave a comment if you know of a decent freeware tool for comparing folders.

Results

I did find a couple of minor changes between the decompiled code in test and production. I also needed to check other non-code files such as web.config for differences. In the end it looked to me that what was on our test server was actually a  newer version than what was in production. I figured that Mark probably had develop branch with some minor fixes (which never made it to prod) checked out on his laptop when it was recovered, and so what was deployed to prod was older.

I was able to add the requested minor enhancement, but also the team was stoked to know that they now have the source code for their API back. Although, it’s a legacy API which is on the roadmap to be replaced in the next few months anyway…

Return objects instead of enums in RESTful APIs

A question that came up at work recently was, “should we return enums as ints or strings”?

The client-side team shouted that they prefer strings please, rather than “magic” ints.

I’m not a fan of strings, because then the client code becomes tightly coupled to those string values and you’ll see code like this in the clients:

if (customer.type == "goldCustomer") {
  // do something
}

Or, the clients need to have silly display code to make the string enum pretty, like this:

switch (customer.type) {
  case 'goldCustomer': return 'Gold customer';
  case 'silverCustomer': return 'Silver customer';
}

Occasionally these string enums might need to be renamed, e.g.
– one of the values isn’t correct because the business actually calls it something different, so it is confusing.

– there is a typo in the enum string, e.g. “goldCustomre”

But if you’re returning enums as strings, you can’t change any of the values, because renaming the string is a breaking API change which will break all the callers.

A more robust and loosely-coupled approach is to return an object instead, e.g.

"customer": {
  "type": {
    "id": 1,
    "displayName": "Gold customer"
  }
}

The client code then becomes:

if (customer.type.id == 1) {
  // do something
}

Which… I must admit, is a bit sucky and a bit “magic”. Hmm. I need to think about this a bit more…

I suppose the clients could define the enums somewhere in their logic, i.e.

enum CustomerType {
  Gold = 1,
  Silver,
}

if (customer.type.id == CustomerType.Gold) {
  // do something
}

At least, the switch statement in the client code above can be replaced with:

return customer.type.displayName

If the client needs all the values of your enum, e.g. for a select list, it can also be helpful to define the enums in a resource, e.g.

GET /customerTypes, which returns:

{
  customerTypes: [
    {
      "id": 1,
      "displayName": "Gold customer"
    },
    {
      "id": 2,
      "displayName": "Silver customer"
    }
  ]
}

TestServer cannot resolve scoped service

I’m using ASP.NET Core’s WebApplicationFactory<T> to run outside-in BDD-style acceptance tests.

One of my tests needed to resolve a scoped service, and I was suprised to find it crashed with an exception.

var factory = new WebApplicationFactory<Startup>();
var service = factory.Server.Services.GetRequiredService<RefreshOrderService>();

The exception:

System.InvalidOperationException: Cannot resolve scoped service 'RefreshOrderService' from root provider.

According to this GitHub comment, this behaviour is by design, as scoped services are only supposed to be resolved within a scope.

The fix then, is to create a scope:


var factory = new WebApplicationFactory<Startup>();
using var scope = factory.Server.Services
    .GetService<IServiceScopeFactory>().CreateScope();
var service = scope.ServiceProvider.GetRequiredService<RefreshOrderService>();

I hope that helps someone.

Conditionally specify a property in an ARM template

At my current contract I’ve been doing a lot more DevOps than I have in the past, mainly because they don’t have enough specialist DevOps engineers. So us developers are encouraged to look after our own Build and Release pipelines and Azure resources. Which means over the past 18 months I’ve spent probably about a month writing ARM (Azure Resource Manager) templates.

Recently I was stuck trying to conditionally specify a property. ARM templates allow you to conditionally specify a resource, via the condition property. But I couldn’t find an easy way to conditionally specify a property. I eventually figured out a way to do it which I couldn’t find documented anywhere.

In my case, I was deploying an API Management ARM template which is shared across our organization, and I wanted to be able to optionally set the OAuth 2.0 server for an API, if the server name is passed in as a parameter. The block in the ARM template looks like this:

{
    "parameters": {
        "oAuthServerId": {
            "type": "string",
            "defaultValue": "",
            "metadata": {
                "description": "The ID of the OAuth 2.0 server."
            }
        }
    },
    "resources": [
        {
            "type": "Microsoft.ApiManagement/service/apis",
            "name": "[variables('apiName')]",
            "apiVersion": "2018-06-01-preview",
            "properties": {
                "authenticationSettings": {
                    "oAuth2": {
                        "authorizationServerId": "[parameters('oAuthServerId')]"
                    }
                }
            }
        }
    ]
}

Because this template is already shared across our organization, and most of the organization’s APIs are not using OAuth (yet), I added a new optional oAuthServerId parameter.
My initial naive implementation was just like the above. My hope was that for all the other APIs that aren’t using OAuth, the empty default value of the parameter would be used (“”), resulting in the following output:

"properties": {
    "authenticationSettings": {
        "oAuth2": {
            "authorizationServerId": ""
        }
    }
}

My hope was that if the authorizationServerId is blank, then Azure would default the User Authorization to “None” (so that I don’t break existing deployments which already use this ARM template):

However, that didn’t work: Authorization server was not found.

I won’t bore you with all the things I tried in the hours that followed as I tried to get this to work. Eventually I found a solution, which is to create a variable for the entire authenticationSettings object and conditionally specify that parent object, rather than trying to conditionally specify the child authentication.oAuth2.authorizationServerId.

{
    "parameters": {
        "oAuthServerId": {
            "type": "string",
            "defaultValue": "",
            "metadata": {
                "description": "The ID of the OAuth 2.0 server."
            }
        }
    },
    "variables": {
        "authenticationSettings": {
            "oAuth2": {
                "authorizationServerId": "[parameters('oAuthServerId')]"
            }
        }
    },
    "resources": [
        {
            "type": "Microsoft.ApiManagement/service/apis",
            "name": "[variables('apiName')]",
            "apiVersion": "2018-06-01-preview",
            "properties": {
                "authenticationSettings": "[if(equals(parameters('oAuthServerId'),''), json('null'), variables('authenticationSettings'))]"
            }
        }
    ]
}

A couple of other tips I’ve found useful for ARM template writing.

  • Always test the ARM templates from Powershell on your local machine, rather than commiting and pushing the changes to Azure DevOps and waiting for DevOps to run your pipeline and eventually fail. As with all development, you’ll be much more efficient if you can shorten your feedback loop.
    1. Install the latest Powershell
    2. Install the latest Azure Powershell
    3. Connect to Azure at the Powershell command line with Connect-AzAccount
    4. Connect to your “Development” subscription with something like Set-AzContext -SubscriptionId f2b1b88a-xxxx-xxxx-a9e1-99a96d8b95f4
    5. Create a parameters file for testing your ARM template locally
    6. Validate your ARM template with Test-AzResourceGroupDeployment
    7. Run your ARM template with New-AzResourceGroupDeployment
  • If you can’t find the name or the property of a resource, check out the (still in Preview after many years) site https://resources.azure.com/

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);
}