Using the .NET Core Middleware for request processing is, imho, not very well documented. There are a couple of things that are not obvious: retrieving query parameters and binding a request body to an object. To top things off, accessing the request Body has a few issues. Here’s how to deal with these scenarios.
Every Middleware handler expects to operate on a RequestDelegate. The Middleware is usually attached in the “application build” phase as an IApplicationBuilder extension. If we use MapGet or MapPost from a RouteBuilder, the RequestDelegate acts upon a Context. The Context has the Request. In .NET Core, the Request has no Content object like what was available in previous .NET OWIN Middleware. We do have “Context.Request.Body,” though. The older OWIN pipeline provided a Content object with a ReadAsAsync
The one caveat/limitaiton is that only JSON bodies are parsed and you’ll notice that the Request.Body has to be read and then replaced as a workaround to .NET Core disposing the Body, otherwise, after reading it. I denote this as a “workaround” in the code below.
Here are the extensions for reading the Request.Body. Note that there is a dependence on the Newtonsoft JSON deserializer.
public static class MapperExtentions { public async static Task<T> ReadAsAsync<T>(this HttpContext context) { var initialBody = context.Request.Body; // Workaround context.Request.EnableRewind(); var buffer = new byte[Convert.ToInt32(context.Request.ContentLength)]; await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); var json = Encoding.UTF8.GetString(buffer); context.Request.Body = initialBody; // Workaround T retValue = JsonConvert.DeserializeObject<T>(json); return retValue; } public async static Task<string> ReadAsString(this HttpRequest request) { var initialBody = request.Body; // Workaround request.EnableRewind(); var buffer = new byte[Convert.ToInt32(request.ContentLength)]; await request.Body.ReadAsync(buffer, 0, buffer.Length); var body = Encoding.UTF8.GetString(buffer); request.Body = initialBody; // Workaround return body; } public async static Task<T> ReadAsAsync<T>(this HttpRequest request) { var json = await request.ReadAsString(); T retValue = JsonConvert.DeserializeObject<T>(json); return retValue; }
Back to the RequestDelegate, we can utilize the above extension methods in our Middleware handler. Let’s say I want to map both a GET and POST request in a custom Middleware handler. I may call it “UseMyCustomMiddleware.” This extension method will be executed in the StartUp.cs’s “Configure” method when we have a handle to IApplicaitonBuilder.
I’ll define two routes.
public static class AppBuilderExtensions { public static void UseMyCustomMiddleware(this IApplicationBuilder app) { // Our path for both GET and POST is the same var url = $"api/mypath/{{id}}"; // We need a RouteBuilder to use MapGet and MapPost var routeBuilder = new RouteBuilder(app); // Define the handlers routeBuilder.MapGet(url, GetDelegate); routeBuilder.MapPost(url, PostDelegate); // Add our routes to the pipeline var routes = routeBuilder.Build(); app.UseRouter(routes); }
The delegates can easily get query parameters from the route values as you see. To parse the Request.Body to type T, the PostDelegate will use the extension methods to read the body to a custom class/object. Obviously, this is more useful in a non-static instance class if we need to perform specific actions with the parsed request body.
#region Delegates public static RequestDelegate GetDelegate { get { RequestDelegate requestDelegate = context => { var ip = (string)context.GetRouteValue("id"); return context.Response.WriteAsync($"OK, {ip}!"); }; return requestDelegate; } } public static RequestDelegate PostDelegate { get { RequestDelegate requestDelegate = context => { var id = (string)context.GetRouteValue("id"); var obj = context.Request.ReadAsAsync<MyClass>().Result; return context.Response.WriteAsync($"OK, {id}!"); }; return requestDelegate; } } }
On a similar note, if we didn’t want to deal with all of this directly, and our Middleware is in a namespace/assembly other than our primary web applcation, we can piggy-back controllers from our custom assembly to .NET Core’s MVC “AddMvc” IServiceCollection extension. This is done in the StartUp.cs’s ConfigureServices method where we have a handle to the IServiceCollection. The specific method is “AddApplicationPart.”
public void ConfigureServices(IServiceCollection services) { services.AddRouting(); // Add framework services. services .AddMvc() .AddApplicationPart(typeof(MyCustomerConller).Assembly); }
I haven’t actually tried this, but if it works as expected, it can be a nice alternative to a lot of boiler-plate (raw) handling of Request data.
Thanks for a good post. I’ve been trying to implement a similar middleware, but I’m having problem that the HttpRequest.Body gets randomly disposed somewhere. Might be some race condition, but cannot figure it out where. I tested with exactly your code (ReadAsString) and still have the same problem. If you’ve had a chance to try your solution, have you seen the Body stream getting disposed?
I did see this issue prior to adding the lines commented with “workaround.” After adding the get/set (replace) of the request body, I don’t think I had the issue any more. I use this same code in another project that sends/receives requests constantly (https://long2know.com/2016/08/creating-a-distributed-computing-engine-with-the-actor-model-and-net-core/). I once let it run overnight and did not have any issues with the middleware endpoints.