How to intercept response in ASP.NET 5 after all other middlewares? - asp.net-core

In my application I need to add a header to almost all responses.
However, middleware won't solve this for me because some other middleware sets a completely fresh response, ends the pipeline and I don't get a look in:
app.Use((context, next) =>
{
context.Response.Headers.Add("MyHeader", "IsCool");
return next();
});
app.UseSomeOtherMiddleware(); // This ends the pipeline after removing my `MyHeader`
I can't add another middleware after the offending one, because the pipeline is finished.
I could add a web.config entry for it:
But as I said, this needs to be added to almost all responses. I need just a teeny bit of logic to determine if I add it, and the web.config solution doesn't afford me that.
So how can I do this in ASP.NET 5? How can I tap into the pipeline after everything is supposedly finished?

Correct implementation for RC2
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var sw = new Stopwatch();
sw.Start();
context.Response.OnStarting((state) =>
{
sw.Stop();
context.Response.Headers.Add("x-elapsed-time", sw.ElapsedTicks.ToString());
return Task.FromResult(0);
}, null);
await _next.Invoke(context);
}
}

You can register a callback with HttpContext.Response.OnStarting and modify the headers just before they are sent.

I think I solved this by creating a middleware as follows:
public class MyMiddleware
{
RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next(context);
context.Response.Headers.Add("MyHeader", "IsCool");
}
}
And using the following in Startup.cs:
app.UseMiddleware<MyMiddleware>();

Related

Register or remove middleware without deploying code

I have created a middleware which logs requests/response data in the database.
I want this middleware to work only when I want to troubleshoot defect or unwanted exception. The middleware should not log rest of the time.
I want a switch button which I can on or off on any controller without making any code changes and deployment.
Please suggests the ways to achieve the above.
In Program.cs, you can add conditionally a middleware like :
var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build();
if (app.Configuration.Get<bool>("MiddlewareLog.Enable"))
{
app.UseCustomeLoggerMiddleware();
}
...
To enable/disable the middleware, you only need to update the appsettings.json and restart the web api/app.
A solution is to enable/disable the middleware from a global setting. Then the controller's action can modify this global setting to enable/disable the middleware.
public class LoggerMiddleware
{
public static volatile bool Enable;
private readonly RequestDelegate _next;
public LoggerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
if(Enable)
{
// Log
}
await _next(context);
}
}
[Route("logger")]
public class LoggerController : ControllerBase
{
[HttpGet]
public void EnableOrDisable(bool enable)
{
LoggerMiddleware.Enable = enable;
}
}
In the example, I use a static field, but it's possible to inject a singleton service in the middleware and the controller to share the setting.

Net Core Custom Response Cache

I need to decide whether to cache the response according to the boolean value from the query string. Unfortunately, I couldn't find such an example. Can you help me?
You can create a custom middleware for that scenario, which reads the boolean value from the query and caches the response (whatever that may be) according to that value.
You can read about custom middlewares here.
Your middleware should look something like this:
public class OptionalCachingMiddleware
{
private readonly RequestDelegate _next;
private readonly IServiceProvider _services;
public OptionalCachingMiddleware(RequestDelegate next, IServiceProvider services)
{
_next = next;
_services= services;
}
public async Task InvokeAsync(HttpContext context)
{
var shouldCache = bool.Parse(context.Request.Query["your-query-parameter-name"]);
if (shouldCache)
{
var responseCache = _services.GetRequiredService<IResponseCache>();
// put your caching logic here
}
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}

Modify response using middleware in ASP.NET Core 3

My goal is to write a middleware that will take care of logging requests to my API and API's responses to those requests in a DB.
I already made a middleware that handles exceptions in a similar fashion, but I got stumped over this.
When you read MSDN about Middleware you can see this nice picture:
This makes you think that Middleware 2 receives the requests, does certain manipulations with it and passes it onto Middleware 3, then once all processing is done by middleware 3 it passes controls back to Middleware 2 for additional processing.
The only thing I do not understand is how to log the response if Middleware 2 Invoke() method is only called once during the request and not called during the response?
Startup.cs:
app.UseMiddleware<RequestLoggingMiddleware>();
Middleware:
public class RequestLoggingMiddleware
{
private readonly RequestDelegate nextMiddleware;
public RequestLoggingMiddleware(RequestDelegate nextMiddleware)
{
this.nextMiddleware = nextMiddleware;
this.options = options;
}
public async Task Invoke(HttpContext context)
{
System.Diagnostics.Debug.WriteLine("Middleware runs");
await nextMiddleware(context);
}
}
}
In the example above I only see "Middleware runs" once in a console, during the initial request but before the response is made. How do I get it to run during the response cycle?
To get the response, all you need to do is apply your same logic after the await nextMiddleware(context); line.
For example, to log the status code:
public class RequestLoggingMiddleware
{
private readonly RequestDelegate nextMiddleware;
public RequestLoggingMiddleware(RequestDelegate nextMiddleware)
{
this.nextMiddleware = nextMiddleware;
}
public async Task Invoke(HttpContext context)
{
System.Diagnostics.Debug.WriteLine("Middleware runs");
await nextMiddleware(context);
System.Diagnostics.Debug.WriteLine($"Response Code: {context.Response.StatusCode}");
}
}

Generic string router with DB in Asp.net Core

I am creating an internet store. And I want to add short URLs for products, categories and so on.
For example:
store.com/iphone-7-plus
This link should open the page with iPhone 7 plus product.
The logic is:
The server receives an URL
The server try it against existent routes
If there is no any route for this path - the server looks at a DB and try to find a product or category with such title.
Obvious solutions and why are they not applicable:
The first solution is a new route like that:
public class StringRouter : IRouter
{
private readonly IRouter _defaultRouter;
public StringRouter(IRouter defaultRouter)
{
_defaultRouter = defaultRouter;
}
public async Task RouteAsync(RouteContext context)
{
// special loggic
await _defaultRouter.RouteAsync(context);
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
return _defaultRouter.GetVirtualPath(context);
}
}
The problem is I can't provide any access to my DB from StringRouter.
The second solution is:
public class MasterController : Controller
{
[Route("{path}")]
public IActionResult Map(string path)
{
// some logic
}
}
The problem is the server receive literally all callings like store.com/robots.txt
So the question is still open - could you please advise me some applicable solution?
For accessing DbContext, you could try :
using Microsoft.Extensions.DependencyInjection;
public async Task RouteAsync(RouteContext context)
{
var dbContext = context.HttpContext.RequestServices.GetRequiredService<RouterProContext>();
var products = dbContext.Product.ToList();
await _defaultRouter.RouteAsync(context);
}
You also could try Middleware to check whether the reuqest is not exist, and then return the expected response.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
app.Use(async (context,next) => {
await next.Invoke();
// add your own business logic to check this if statement
if (context.Response.StatusCode == 404)
{
var db = context.RequestServices.GetRequiredService<RouterProContext>();
var users = db.Users.ToList();
await context.Response.WriteAsync("Request From Middleware");
}
});
//your rest code
}

asp.net core check route attribute in middleware

I'm trying to build some ASP.Net core middleware.
It needs see if the current route is marked as [Authorize].
eg:
public async Task Invoke(HttpContext context)
{
if(context.Request.Path.Value.StartsWith("/api"))
{
// check if route is marked as [Authorize]
// and then do some logic
}
await _next.Invoke(context);
}
Does anyone know how this could be achieved or if it's even possible?
If not, what would be a good alternative approach?
I believe it can be achieved in a middleware class via:
var hasAuthorizeAttribute = context.Features.Get<IEndpointFeature>().Endpoint.Metadata
.Any(m => m is AuthorizeAttribute);
Without knowing exactly what you want to achieve, it's a bit tricky to answer, but I suggest you have a look at the controller filters : https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters
I put together an example:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddMvcOptions(options => options.Filters.Insert(0, new CustomFilter()));
}
CustomFilter.cs
public class CustomFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if(context.HttpContext.Request.Path.Value.StartsWith("/api"))
{
var controllerInfo = context.ActionDescriptor as ControllerActionDescriptor;
var hasAuthorizeAttr = controllerInfo.ControllerTypeInfo.CustomAttributes.Any(_ => _.AttributeType == typeof(AuthorizeAttribute));
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// NOP
}
}
HansElsen's answer is correct, but I would like to add some things to note about it:
You can use the extension method context.GetEndpoint() which is exactly the same as context.Features.Get<IEndpointFeature>().Endpoint
The endpoint will always be null if you set up your middleware before calling app.UseRouting() in your Startup.cs:
Don't do:
app.UseMiddleware<MyMiddleware>();
app.UseRouting();
Do:
app.UseRouting();
app.UseMiddleware<MyMiddleware>();
Just self-answering to close the question.
Doesn't seem like it's possible to do this as it's too early in the pipeline.