Using Attributes and Middleware in .net Core
There are times you need to intercept every request being made to your application for some reason or another. This is where custom middleware comes in handy. You can create a class to handle your needs and then inject it into the pipeline and have it do what it handle your requirements.
I find that on larger projects, middleware comes in very handy for creating request-wide variables, handling specific logging, or adding metrics.
Sometimes, while you're using your middleware, it's nice to send in variables or flags to the middleware in order to help with your business requirements. Attributes come in very handy for these scenarios.
A Simple Scenario
You have some middleware that needs to run but only on certain endpoints. In this case, we will create an attribute class that derives from the System's Attribute base class:
using System;
namespace MySystem.Attributes
{
public class AllowMiddleware : Attribute
{
public AllowMiddleware()
{
}
}
}
This is a simple attribute that does nothing other than exist.
Now let's create our simple middleware:
namespace MySystem.Middleware
{
internal class SpecialMiddleware
{
private readonly RequestDelegate _next;
public SpecialMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// check for the attribute and skip everything else if it exists
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
var attribute = endpoint?.Metadata.GetMetadata<AllowMiddleware>();
if(attribute == null)
{
await _next(context);
return;
}
// Do everything that your middleware needs to do
// When finished, call the next delegate/middleware in the pipeline.
// await _next(context);
}
}
}
And for completeness, we must register our middleware in our Startup/Program:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//... other registrations
app.UseMiddleware<SpecialMiddleware>();
}
This entire scenario should only take you a few minutes to complete.
A More Dynamic Scenario
Let's say that you need to send in data with the Attribute. You can create a slightly more interesting attribute such as I have below:
using System;
namespace MySystem.Attributes
{
public class LogCustomType : Attribute
{
public CustomType CType { get; set; }
public LogCustomType(CustomType ct)
{
CType = ct;
}
}
}
public enum CustomType
{
Login,
Register,
Logout
}
With this attribute, you can then pass in the type of action the endpoint is handling to tell the middleware how to log something potentially. You would implement it like this:
[LogCustomType(CustomType.Login)]
public async Task<IActionResult> Login([FromBody]LoginViewModel vm)
{
//...
}
Then, when you're in the middleware, you can read that CustomType:
namespace MySystem.Middleware
{
internal class SpecialMiddleware
{
private readonly RequestDelegate _next;
public SpecialMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// check for the attribute and skip everything else if it exists
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
var attribute = endpoint?.Metadata.GetMetadata<LogCustomType>();
if(attribute != null)
{
var data = attribute.CType;
// Do everything that your middleware needs to do...
}
// When finished, call the next delegate/middleware in the pipeline.
// await _next(context);
}
}
}
Summary
You have learned how to use Middleware and Attributes together to create clean code that can be used to create logic on all of some of your endpoints to accomplish your business needs. Happy Coding.