Net Core Custom Response Cache - asp.net-core

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

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.

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

How can I see which AuthorizeAttribute Failed ASP.NET Core

I am trying to work out if there is an easy way to get ASP.NET Core to log which [Authorize] attribute is failing. I have a mixture of "Role" and "Policy" authorize attributes but whenever a single one fails the logs just show:
Obviously this is the correct behaviour and it doesn't let someone in with incorrect permissions however if you have multiple attributes it's a bit of a pain to have to go and work out which one failed. If the log simply showed Authorization failed for Policy X then that would be really easy to then find what's failing.
Does anyone know if it's currently possible to make this happen through some option I'm unaware of?
EDIT: For example: If I had [Authorize(Policy = "Policy 1")] and [Authorize(Policy = "Policy 2")] and only "Policy 2" failed. I would like to see something that tells me that it was "Policy 2" that failed.
EDIT: For anyone still coming across this question this has now been implemented by Microsoft and is part of .NET 5.0, see issue https://github.com/aspnet/AspNetCore/issues/7789
For Roles and Policy, they are translated to requirements like RolesAuthorizationRequirement or your custom requirement like MinimumAgeRequirement.
For Authorization failed., this is logged by DefaultAuthorizationService in AuthorizeAsync, you may not able to get the exact name like Policy 1 and Policy 2. You could get the requirements for Policy.
Try to check whether workaround below meets your requirement.
Implement custom DefaultAuthorizationService
public class CustomAuthorizationService : DefaultAuthorizationService, IAuthorizationService
{
private readonly AuthorizationOptions _options;
private readonly IAuthorizationHandlerContextFactory _contextFactory;
private readonly IAuthorizationHandlerProvider _handlers;
private readonly IAuthorizationEvaluator _evaluator;
private readonly IAuthorizationPolicyProvider _policyProvider;
private readonly ILogger _logger;
public CustomAuthorizationService(IAuthorizationPolicyProvider policyProvider
, IAuthorizationHandlerProvider handlers
, ILogger<DefaultAuthorizationService> logger
, IAuthorizationHandlerContextFactory contextFactory
, IAuthorizationEvaluator evaluator
, IOptions<AuthorizationOptions> options)
: base(policyProvider, handlers, logger, contextFactory, evaluator, options)
{
_options = options.Value;
_handlers = handlers;
_policyProvider = policyProvider;
_logger = logger;
_evaluator = evaluator;
_contextFactory = contextFactory;
}
public new async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
if (requirements == null)
{
throw new ArgumentNullException(nameof(requirements));
}
var authContext = _contextFactory.CreateContext(requirements, user, resource);
var handlers = await _handlers.GetHandlersAsync(authContext);
foreach (var handler in handlers)
{
await handler.HandleAsync(authContext);
if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
{
break;
}
}
var result = _evaluator.Evaluate(authContext);
if (result.Succeeded)
{
_logger.LogInformation($"Authorization is succeeded for { JsonConvert.SerializeObject(requirements) }" );
//_logger.UserAuthorizationSucceeded();
}
else
{
//var r = result.Failure.FailedRequirements.Select(requirement => new { Requirement = requirement.GetType() });
var json = JsonConvert.SerializeObject(result.Failure.FailedRequirements);
_logger.LogInformation($"Authorization is failed for { json }");
//_logger.UserAuthorizationFailed();
}
return result;
}
}
Replace built-in DefaultAuthorizationService
services.AddAuthorization(config =>
{
config.AddPolicy("T1", policy => policy.AddRequirements(new MinimumAgeRequirement(21)));
});
services.Replace(ServiceDescriptor.Transient<IAuthorizationService, CustomAuthorizationService>());
Microsoft are implementing this by default in .NET 5.0, see the related GitHub issue for details and links to PR.
https://github.com/aspnet/AspNetCore/issues/7789
you can handle and log this inside Middlewares
public class AuthHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ErrorHandlingMiddleware> _logger;
public AuthHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IHostingEnvironment env /* other scoped dependencies */)
{
await _next(context);
if (context.Response.StatusCode == 401)
_logger.LogInformation($"'{context.User.Identity.Name}' is unauthorized");
}
}
In your starup config,
public void Configure(IApplicationBuilder app, ... )
{
....
app.UseMiddleware<AuthHandlerMiddleware>();
}

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