Modify response using middleware in ASP.NET Core 3 - asp.net-core

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

Related

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

How to a I redirect to Custom Error Handler Page

I want a generic error page for all application errors.
I have followed the guidelines to create a custom error handler in ASP.NET core and this catches the errors as expected. However, I cannot see how to redirect to a generic error handling the page. Examples seemed to be focused on Web API, not UI.
I have the following custom error handling code
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
int exceptionId = ExceptionManager.Publish(exception);
return context.Response.WriteAsync(new ErrorViewModel()
{
ExceptionId = exceptionId
}.ToString());
}
The exception details are logged to a database and return an Id. I have a controller action that displays the Id so the users can report it.
How do I redirect to my error view?
In Startup.cs method you need to call ExceptionHandlerMiddleware like below.
app.UseMiddleware(typeof(ExceptionHandlerMiddleware));
create a middleware class and write below code
public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate next;
public ExceptionHandlerMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
//Write you logic
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.StatusCode = 500;
if (IsRequestAPI(context))
{
//when request api
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(new
{
State = 500,
message = exception.Message
}));
}
else
{
//when request page
context.Response.Redirect("/Home/Errorpage");
}
}
}
Middleware is "waterfalled" down through until either all have been executed, or one stops execution (in the case of our exception handling, we'll be writing ours so it stops the execution. More on that later).
The first things passed to your middleware is a request delegate. This is a delegate that takes the current HttpContext object and executes it. Your middleware saves this off upon creation and uses it in the Invoke() step.
Invoke() is where the work is done. Whatever you want to do to the request/response as part of your middleware is done here. Some other usages for middleware might be to authorize a request based on a header or inject a header into the request or response. For more examples, check out the Middleware documentation.

How to Get Route Data from Middleware for ASPNETCORE OData request?

I have the following Middleware class which gets the route data for the current request.
If you perform a Get on the Values Controller you will see the context.GetRouteData() returns a value and is not null. I have wired Swagger\Swashbuckle into the attached project to make this easier to demo.
However, if you call the Blog OData controller (http://localhost:40946/odata/Blog), context.GetRouteData() returns null.
Does anyone know how to access the RouteData from middleware for an OData request?
public class TestMiddleware
{
protected readonly RequestDelegate _next;
public TestMiddleware(RequestDelegate next)
{
this._next = next;
}
public virtual async Task Invoke(HttpContext context)
{
var routeData = context.GetRouteData();
await this._next.Invoke(context);
}
}
Link to sample solution demonstrating the issue.
Looks like the OData Middleware isn't providing the routing info as a IRoutingFeature to the context. Link to Source Code
Not sure the root cause - but as a workaround you could use
var httpRequestFeature = context.Features[typeof(IHttpRequestFeature)] as IHttpRequestFeature;
var path = httpRequestFeature?.Path;

Unable to get Scoped Service in aspnetcore 1 - RC1 to work

My scoped service for some reason seems to be generating different instances of the same class when I try to access it in 2 middlewares within the same request.
Scenario: I am adding a scoped service as such:
public interface ISimplyRecorder
{
void AddInfo(string key, string value);
Dictionary<string, string> GetAllInfo();
}
public class SimplyCoreRecorderService : ISimplyRecorder
{
private Dictionary<string,string> data;
public SimplyCoreRecorderService()
{
data = new Dictionary<string, string>();
}
public void AddInfo(string key,string value)
{
data.Add("",value);
}
public Dictionary<string,string> GetAllInfo()
{
return data;
}
}
and then the following in startup.cs
services.AddScoped<ISimplyRecorder,SimplyRecorderService>();
now I am calling this service in the constructor of a sample Middleware. I am able to access the service with a new instance and add data into it and then I call await _next(context). However, when I am calling the service in my HomeController, MVC which follows the middleware above, I seem to be getting a new instance of the service even though it's the same request.
HomeController:
ISimplyRecorder _simply;
private IHostingEnvironment _env;
public HomeController(IHostingEnvironment env,ISimplyRecorder simply)
{
_simply = simply;
_env = env;
}
public IActionResult Index()
{
_simply.AddInfo("Home:Action","resulted in index action");
return View();
}
complete code available at: https://github.com/muqeet-khan/SimplyCore if someone wants to give it a go.
Middlewares are instantiated only once when it's first involved, then all the following requests are handled by that middleware instance. NOT a new middleware instance for each request.
You get your ISimplyRecorder in the constructor of the middleware and "cache" it as a private readonly variable. This means the middleware will get the ISimplyRecorder instance of the first request, then keep adding data to that instance for all the following requests rather than the new ISimplyRecorder instance for the following requests which you get in HomeController.
To solve it, you need to get ISimplyRecorder instance from the Invoke method of the middleware.
// using Microsoft.Extensions.DependencyInjection;
public async Task Invoke(HttpContext httpContext)
{
ISimplyRecorder recoder = httpContext.RequestServices.GetRequiredService<ISimplyRecorder>();
}
EDIT:
The comment of Juergen is correct, I tried it out. You may also just write like this:
public async Task Invoke(HttpContext httpContext, ISimplyRecorder recorder)
{
// recorder is from DI
}

How to intercept response in ASP.NET 5 after all other middlewares?

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