Dynamic Payloads in ASP.NET Core

It has always been possible (but a tad problematic) to submit dynamic contents to an ASP.NET Core controller action. In the dark, pre-Core days, we had to implement a custom model binder for this. Now it is no longer the case.

All we need is to declare an action method as having a dynamic parameter, obtained from the request body, and accepting the request through POST:

[HttpPost]
public IActionResult Post([FromBody] dynamic payload)
{
    return Json(new { Ok = true });
}

This will accept any content that we POST, if we use the Content Type application/json, and it will by default be turned into a System.Text.Json.JsonElement, a type of the new JSON API, which is the default serializer of ASP.NET Core 3.

If, for some reason, we want to go back to the previous serializer, JSON.NET (Newtonsoft.Json), we need to do a couple of things:

  1. Install the Microsoft.AspNetCore.Mvc.NewtonsoftJson Nuget package (it will bring all of the necessary dependencies)
  2. Configure it as the default serializer (AddMvc or AddControllers or AddRazorPages or AddControllersWithViews all allow this):

    services

    .AddMvc()

    .AddNewtonsoftJson();

If you do, the payload will instead by a Newtonsoft.Json.Linq.JObject instance. The main difference between the two serializer is outlined here. In the end, System.Text.Json supports most of the usual cases and is already included.

In general, I prefer using strongly-typed models, but there may be use cases for this, particularly when implementing routers, API gateways or similar functionality.

As always, hope this is useful! Smile

Unit Testing the HttpContext in Controllers

Introduction

When it comes to unit testing, it is common to use mocking to replace “external” services, that is, those that are not part of the subject under test. Not everything can be mocked, and, sometimes, when we instantiate non-POCO classes, such as controllers (ControllerBase), there are lots of things that are not instantiated by the framework and are left for us do to. Just think, for example, if you want to access request or quest string data or know if the user is logged in?

In this post I will talk about two things: the user identity and the request data.

The HttpContext

Inside of a controller action method, it is common to get information which actually comes from the the context (the HttpContext property):

All of these properties get routed to the HttpContext property, which is read-only. It turns out that this property is itself routed to the ControllerContext’s property’s own HttpContext, but the ControllerContext can be set:

var controller = new HomeController();
controller.ControllerContext = new ControllerContext();

So, if we want to set a controller’s HttpContext property, all we have to do is set the ControllerContext’s HttpContext:

controller.ControllerContext.HttpContext = new DefaultHttpContext();

The DefaultHttpContext class is the default implementation of the abstract HttpContext class that is included in ASP.NET Core.

Features

All of the .NET and ASP.NET Core is very modular and designed for extensibility, so it should come as no surprise that all of the HttpContext features are provided as features and it’s easy to provide implementations for them.

Features are accessible through the Features collection and can be set using as key one interface, which is generally a core interface that identifies that feature:

context.Request.Set<IFormFeature>(new FormCollection(request));

The DefaultHttpContext takes in one of its constructors a collection of features, from which it can extract its functionality.

Request Data

When we talk about the request we can be talking about:

  • Form data (including files)
  • The query string

Each is implemented by its own feature. So, if we want to mock form data, we would do something like this, leveraging the IFormFeature:

var request = new Dictionary { { “email”, “rjperes@hotmail.com” }, { “name”, “Ricardo Peres” } }; var formCollection = new FormCollection(request); var form = new FormFeature(formCollection);

var features = new FeatureCollection(); features.Set<IFormFeature>(form); var context = new DefaultHttpContext(features);

This allows us to write code like this, from inside the :

var email = HttpContext.Request["email"];

As for the query string, we would do it like this, using IQueryFeature:

var request = new Dictionary { { “id”, “12345” } }; var queryCollection = new QueryCollection(request); var query = new QueryFeature(queryCollection);

var features = new FeatureCollection(); features.Set<IQueryFeature>(form); var context = new DefaultHttpContext(features);

This code gives us the ability to do this:

var id = HttpContext.Request.Query["id"];

Response

What about sending responses, HTTP status codes and cookies? I’ve got you covered too! The feature contract for the response is IHttpResponseFeature and we add it like this:

var response = new HttpResponseFeature();

var features = new FeatureCollection(); features.Set<IHttpResponseFeature>(response); var context = new DefaultHttpContext(features);

var responseCookies = new ResponseCookiesFeature(context.Features);

Notice that the ResponseCookiesFeature is a little awkward, as it takes the feature collection in its constructor.

With this on, you can set the response status or some cookies on the response of your action:

Response.StatusCode = 400;
Response.Cookies.Append("foo", "bar");

User Identity

As for the user identity, it’s much simpler, we just need to create a ClaimsPrincipal with any claims that we want:

var context = new DefaultHttpContext(features)

{ User = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]

{

     new Claim(ClaimTypes.Name, “<username>”),

new Claim(ClaimTypes.Role, “<role>”)

    })) };

You can just pass any username and roles you want, and they will be made available to the controller’s User property.

Conclusion

As you can see, with a little bit of work, almost anything is possible in .NET Core, and, particularly, when it comes to unit tests! Stay tuned for more! Winking smile

Dynamic Routing in ASP.NET Core 3

ASP.NET Core 3 introduced a not so talked about feature which is dynamic routing. In a nutshell, it means that it is possible to decide at runtime the controller, action and route tokens that a request will be dispatched to. The idea is to map a route pattern to a dynamic route handler, like this:

app.UseEndpoints(endpoints =>
{
    endpoints.MapDynamicControllerRoute<SearchValueTransformer>("search/{**product}");
});

This maps a route of “/search/<anything>” to a class SearchValueTransformer, which is then responsible for supplying values for the controller and action.

This SeachValueTransformer class must inherit from DynamicRouteValueTransformer, this is the abstract base class for all dynamic route handling, and it only has one method to implement, TransformAsync. The example I am providing here is that of a service that receives some random query for a product on the URL and then decides to which controller it will be forwarded to. A sample implementation of this class would be:

class SearchValueTransformer : DynamicRouteValueTransformer { private readonly IProductLocator _productLocator; public SearchValueTransformer(IProductLocator productLocator) { this._productLocator = productLocator; } public override async ValueTask TransformAsync(

HttpContext httpContext, RouteValueDictionary values) { var productString = values[“product”] as string; var id = await this._productLocator.FindProduct(“product”, out var controller); values[“controller”] = controller; values[“action”] = “Get”; values[“id”] = id; return values; } }

As you can see, it takes an instance of an IProductLocator (I will get to that in a moment) in its constructor, which means that it supports dependency injection. The TransformAsync extracts a “product” parameter from the route – I am skipping validation because if we got here that’s because we matched the route – which it then uses to call the stored IProductLocator instance to retrieve the id and controller, which are then set as route parameters. The result route is then returned.

The SearchValueTransformer needs to be registered in the dependency injector too:

services.AddSingleton<SearchValueTransformer>();

The actual lifetime is irrelevant, here I am setting it as singleton because the SearchValueTransformer holds a single IProductLocator which is itself a singleton and nothing else.

As to the IProductLocator, here is it’s interface:

public interface IProductLocator
{
    Task<string> FindProduct(string product, out string controller);
}

The actual implementation, I leave it up to you, it must apply some logic to the product passed in the URL to decide which controller should be used for search it, and then return some reference and a controller responsible for returning information about it. This interface and its implementation must also be registered:

services.AddSingleton<IProductLocator, ProductLocator>();

And this is it. This may serve as a complement to static routing, other uses include translating route parts (controller, action) on the fly. As always, hope you find this useful!