Here is a simple example:
Middlewares:
public class AuthorizationMiddleware
{
private readonly RequestDelegate _next;
public AuthorizationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains("X-Not-Authorized"))
{
context.Response.StatusCode = 401;
return;
}
await _next.Invoke(context);
}
}
public class RequestHeaderMiddleware
{
private readonly RequestDelegate _next;
public RequestHeaderMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains("X-Cancel-Request"))
{
context.Response.StatusCode = 500;
return;
}
await _next.Invoke(context);
// not calling
context.Response.Headers.Add("X-Transfer-Success", "true");
await context.Response.WriteAsync("test");
}
}
public class ProcessingTimeMiddleware
{
private readonly RequestDelegate _next;
public ProcessingTimeMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var watch = new Stopwatch();
watch.Start();
await _next(context);
context.Response.Headers.Add("X-Processing-Time-Milliseconds", new[] { watch.ElapsedMilliseconds.ToString() });
}
}
registration:
app.UseMiddleware<AuthorizationMiddleware>();
app.UseMiddleware<RequestHeaderMiddleware>();
app.UseMiddleware<ProcessingTimeMiddleware>();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Why does this code await context.Response.WriteAsync("test"); not calling in the class? And it doesn't work only if I register MVC. How does MVC prevent continue method execution? Am I missed something in c# ?
If some middleware has already begun writing to the response, then you can't do things like editing the response headers. This is why the docs recommend against manipulating the headers after other middleware has run (e.g. on the way "out" of the stack, after calling _next.Invoke().
You should find a way to log the information you want that doesn't involve directly writing to the response. One approach is to simply log and store it on the server (cache, database) and view it in a separate request.
When you add MVC, it is going to send headers and start sending the body of the response as well. This is why your code that runs after MVC can no longer write to the response - it's been sent.
Try the code below:
context.Response.OnStarting(() =>
{
context.Response.Headers.Add("X-Transfer-Success", "true");
});
// await context.Response.WriteAsync("test");
await _next(context);
Related
I´ve got a Middleware for my ASP.NET WebApi, which should Log the Request in case its something we cannot work with. There for I´ve invented the following Attribute to declare which Methods uses the Middleware:
public class UseMiddlewareAttribute : Attribute
{
public Type MiddlewareType { get; }
public UseMiddlewareAttribute(Type middlewareType)
{
MiddlewareType = middlewareType;
}
}
And a example controller:
[HttpPost]
[DisableRequestSizeLimit]
[UseMiddleware(typeof(PlausibilityMiddleware))]
public async Task<ActionResult> ImportPersonsAsync(List<Person> persons)
{
return Ok(persons);
}
And here the middleware:
internal class PlausibilityMiddleware
{
#region Fields
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private readonly RequestDelegate _next;
private readonly IServiceProvider _provider;
#endregion
#region Constructor
public PlausibilityMiddleware(RequestDelegate next, IServiceProvider provider)
{
_next = next;
_provider = provider;
}
#endregion
public async Task InvokeAsync(HttpContext context)
{
try
{
if (!CanProcess(context))
{
await _next(context);
return;
}
var requestBody = DoSomeMagic();
Log.Trace(requestBody);
}
catch (Exception ex)
{
// do not skip _next(context) ---> This method calls the Controller behind
Log.Error("Unknown error occured while validate pre import");
Log.Error(ex);
}
await _next(context);
}
private bool CanProcess(HttpContext context)
{
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
var attribute = endpoint?.Metadata.GetMetadata<UseMiddlewareAttribute>();
return attribute != null && attribute.MiddlewareType.Equals(GetType());
}
Firstly the context.Features.Get<IEndpointFeature>()?.Endpoint returns null, so I cannot access the attributes, also I don´t know how to read Request-Body.
To access the given Attribute I needed to configure the app, it´s neccessary to Add the Middleware between UseRouting and UseEndpoint:
app
.UseRouting()
.UseMiddleware<PlausibilityMiddleware>()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
})
To read the Request-Body I need to EnableBuffering and use a StreamReader. But then the StreamReader disposes the underlying Stream, so the Controller cannot access the Stream anymore. You can use CopyTo and use another MemoryStream but that is overhead. StreamReader has a Consturctor parameter leaveOpen finaly:
private static async Task<string> DoSomeMagic(HttpContext context)
{
context.Request.EnableBuffering();
using var reader = new StreamReader(context.Request.Body, leaveOpen: true);
var result = await reader.ReadToEndAsync();
context.Request.Body.Position = 0;
return result;
}
Also its nice to know you cannot access the stream as synchronously read ReadToEnd() if you want to, you need to set AllowSynchronousIO = true
var syncIOFeature = httpContext.Features.Get<IHttpBodyControlFeature>();
if (syncIOFeature is not null)
syncIOFeature.AllowSynchronousIO = true;
Then you can ReadToEnd as Synchronously method.
I have a custom authorization policy where I'd like to output the reason to the output to help the consumer understand why he or she gets a 403 forbidden result. I have the following code:
internal class MyRequirementHandler : AuthorizationHandler<MyRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
if (myCondition)
{
context.Succeed(requirement);
}
else
{
context.Fail(new AuthorizationFailureReason(this, "Reason why failing"));
}
return Task.CompletedTask;
}
}
I would expect this to output "Reason why failing" to the body of the response, but I don't. I would get it to work by doing this instead:
if (context.Resource is DefaultHttpContext mvcContext)
{
mvcContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await mvcContext.HttpContext.Response.WriteAsync("Reason why failing");
}
But that doesn't feel like the correct way.
Grateful for any help and or input!
You can use IAuthorizationMiddlewareResultHandler to get AuthorizationFailureReason information. see Customize the behavior of AuthorizationMiddleware.
In my case, I use global exception middleware to handle exception, so I need to rethrow exception. You can see below example.
// Implement IAuthorizationMiddlewareResultHandler
public class ForbiddenAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler defaultHandler = new();
public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
{
if (authorizeResult.Forbidden)
{
var failureReasons = authorizeResult.AuthorizationFailure?.FailureReasons.FirstOrDefault();
throw new UnauthorizedAccessException(failureReasons?.Message ?? "You do not have permission to access.");
}
await defaultHandler.HandleAsync(next, context, policy, authorizeResult);
}
}
// Handle Global Exception
public class ExceptionHandleMiddleware
{
private readonly ILogger<ExceptionHandleMiddleware> _logger;
private readonly RequestDelegate _next;
public ExceptionHandleMiddleware(ILogger<ExceptionHandleMiddleware> logger, RequestDelegate next)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (System.Exception e)
{
_logger.LogError(e, "Catch some exception.");
// Handle UnauthorizedAccessException and get your "Reason why failing" message
}
}
}
// Don't forget Register DI
services.AddSingleton<IAuthorizationMiddlewareResultHandler, ForbiddenAuthorizationMiddlewareResultHandler>();
So currently I need to implement try-catches, but everywhere I look the try-catch can be relevant. How do you determine where to use the try-catches?
Is there also a generalized way how to implement the try-catches? For example Is there some way how an empty class can be used in Faulthandling as indicated below?
public class FailedRoleManager : IRoleManager { }
Depending on the type of project you are doing, it is possible to make a middleware that handles all the exceptions that occurred in the application. In this link you can see an example of an implementation of global error capture in a .net core api:
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception error)
{
var response = context.Response;
response.ContentType = "application/json";
response.StatusCode = (int)HttpStatusCode.InternalServerError;
var result = JsonSerializer.Serialize(new { message = error?.Message });
await response.WriteAsync(result);
}
}
}
https://jasonwatmore.com/post/2020/10/02/aspnet-core-31-global-error-handler-tutorial#:~:text=The%20global%20error%20handler%20middleware%20is%20used%20catch%20all%20exceptions,Configure%20method%20of%20the%20Startup.
I am building an ASP.NET Core Api with a middleware for global exception handling. In Startup class, I configured some JSON options which are to be used for all controllers.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver()
{
NamingStrategy = new SnakeCaseNamingStrategy()
};
options.SerializerSettings.Converters.Add(new StringEnumConverter());
});
}
Then, I created a custom middleware for exceptions handling. The middleware is supposed to log the error and return error response as JSON. The problem is that global Newtonsoft's JsonConvert doesn't use the options which are specified during Mvc initialization (quite obvious).
Error handler middleware code:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, ILogger<ErrorHandlingMiddleware> logger)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, logger);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception ex, ILogger logger)
{
var errorResponse = new ErrorResponse { Message = ex.Message };
logger.LogError(ex, errorResponse.Message);
var result = JsonConvert.SerializeObject(errorResponse);
context.Response.ContentType = "application/json";
context.Response.StatusCode = 500;
return context.Response.WriteAsync(result);
}
}
The question is: Is there any way to use the same JSON serializer, which is used in controllers, in my custom middleware? I would like to avoid setting up the same configuration in two separate places.
JsonConvert.SerializeObject has an overload that also takes an instance of JsonSerializerSettings, which could be combined with the options pattern and dependency injection. All-in-all, that would look something like this:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly JsonSerializerSettings _jsonSerializerSettings;
public ErrorHandlingMiddleware(RequestDelegate next, IOptions<MvcJsonOptions> mvcJsonOptions)
{
_next = next;
_jsonSerializerSettings = mvcJsonOptions.Value.SerializerSettings;
}
...
private Task HandleExceptionAsync(HttpContext context, Exception ex, ILogger logger)
{
...
var result = JsonConvert.SerializeObject(errorResponse, _jsonSerializerSettings);
...
}
}
In this example, we're injecting the MvcJsonOptions instance that's been configured and then passing its SerializerSettings value through to JsonConvert.SerializeObject.
I am writing a middleware class within my project in order to log the request data into our database.
I do not see any easy way to get the controller name and action ?
Any chance to do this easily in core?
I have something like this:
public class RequestResponseLoggingMiddleware
{
private readonly RequestDelegate _next;
public RequestResponseLoggingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
//handle the request
//something like: context.GetRouteData();
await _next(context);
//handle the response
}
}
I had the same issue and this worked for me in .NetCore 3.1:
public async Task InvokeAsync(HttpContext httpContext)
{
var controllerActionDescriptor = httpContext
.GetEndpoint()
.Metadata
.GetMetadata<ControllerActionDescriptor>();
var controllerName = controllerActionDescriptor.ControllerName;
var actionName = controllerActionDescriptor.ActionName;
await _next(httpContext);
}
In order for GetEndpoint() to return the actual endpoint instad of null, the following conditions have to be met.
Enable endpoint routing (AddControllers() instead of AddMvc())
Call your middleware between UseRouting() and UseEndpoints().
Adding to Ganesh's answer I'd like to add a few conditions.
Enable endpoint routing (AddControllers() instead of AddMvc())
Call your middleware between UseRouting() and UseEndpoints().
Otherwise GetEndpoint() returns null.
This works but you need to make sure to call _next(context) before you query the controller and action names.
public async Task InvokeAsync(HttpContext context)
{
await _next(context);
var controllerName = context.GetRouteData().Values["controller"];
var actionName = context.GetRouteData().Values["action"];
}
In .NET Core 2.0 you can do this in Filter:
public class AuthorizationFilter : IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
string controllerName = controllerActionDescriptor?.ControllerName;
string actionName = controllerActionDescriptor?.ActionName;
}
}
Controller and action data are available through filters so I implemented it this way. The controller and action data are available in the ActionExecutingContext object.
public class AsyncActionFilter : IAsyncActionFilter
{
public AsyncActionFilter(IEntityContext entityContext)
{
_entityContext = entityContext;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
//Handle the request
await next();
}
}