Read Controller and Action name in middleware .Net Core - c#

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

Related

ASP.NET Core Own middleware component

How to get the second argument from HTTP Context? For example
localhost:45423/?login=login1/?password=password1
Here is what I coded but it doesn't work. It always says that I'm not logged even when I inputted the correct login and password
public class CheckData
{
private readonly RequestDelegate _next;
public CheckData(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var login = context.Request.Query["login"];
var password = context.Request.Query["password"];
if(login != "log1" && password != "pass1")
{
context.Response.StatusCode = 403;
await context.Response.WriteAsync("Sorry, you're not logged");
}
else
{
await _next.Invoke(context);
}
}
}
P.S: I've began learning ASP.NET Core today, only login works, but not login with password
You are passing the query parameters wrongly, For multiple query parameter you should pass like localhost:45423?login=login1&password=password1

Azure Function Middleware: How to return a custom HTTP response?

I am exploring Azure Function running on .net 5 and I found out about the new middleware capabilities.
I have built a dummy middleware like this one:
public sealed class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
private readonly ILogger<ExceptionLoggingMiddleware> m_logger;
public ExceptionLoggingMiddleware(ILogger<ExceptionLoggingMiddleware> logger)
{
m_logger = logger;
}
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception unhandledException)
{
m_logger.LogCritical(unhandledException, "Unhandled exception caught: {UnhandledException}", unhandledException.Message);
}
}
}
In my use case, the Azure Function is an HTTP triggered function:
public sealed class StorageAccountsFunction
{
private readonly ILogger<StorageAccountsFunction> m_logger;
public StorageAccountsFunction
(
ILogger<StorageAccountsFunction> logger
)
{
m_logger = logger;
}
[Function("v1-post-storage-account")]
public async Task<HttpResponseData> CreateAsync
(
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "v1/storage-accounts")]
HttpRequestData httpRequestData,
FunctionContext context
)
{
m_logger.LogInformation("Processing a request to create a new storage account");
throw new Exception("Oh no! Oh well..");
}
}
In my Function App running in-process on .net core 3.1, each Function had the responsibility of catching the unhandled exception (via a base class) and returned the appropriate HTTP status code.
I would like to have that logic sit in a middleware instead to have it centralized and avoid any future mistakes.
Question
The exception is caught by the middleware properly. However, I do not see how I can alter the response and return something more appropriate, instead of a 500 Internal Server Error that I get right now?
According to this issue, there is currently no official implementation regarding this, but they also mention a "hacky workaround" until the proper functionality is implemented directly into Azure functions
We created an extension method for FunctionContext:
internal static class FunctionUtilities
{
internal static HttpRequestData GetHttpRequestData(this FunctionContext context)
{
var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
var functionBindingsFeature = keyValuePair.Value;
var type = functionBindingsFeature.GetType();
var inputData = type.GetProperties().Single(p => p.Name == "InputData").GetValue(functionBindingsFeature) as IReadOnlyDictionary<string, object>;
return inputData?.Values.SingleOrDefault(o => o is HttpRequestData) as HttpRequestData;
}
internal static void InvokeResult(this FunctionContext context, HttpResponseData response)
{
var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
var functionBindingsFeature = keyValuePair.Value;
var type = functionBindingsFeature.GetType();
var result = type.GetProperties().Single(p => p.Name == "InvocationResult");
result.SetValue(functionBindingsFeature, response);
}
}
The usage in the middleware looks like this:
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
if (ex.InnerException is *NameOfExceptionYouNeed* e)
{
var req = context.GetHttpRequestData();
var res = await req.ErrorResponseAsync(e.Message);
context.InvokeResult(res);
return;
}
throw;
}
}
This is natively supported now as of version 1.8.0 of Microsoft.Azure.Functions.Worker.
The FunctionContextHttpRequestExtensions class was introduced so now you can just
using Microsoft.Azure.Functions.Worker;
public class MyMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// To access the RequestData
var req = await context.GetHttpRequestDataAsync();
// To set the ResponseData
var res = req!.CreateResponse();
await res.WriteStringAsync("Please login first", HttpStatusCode.Unauthorized);
context.GetInvocationResult().Value = res;
}
}
This code works for me. It is based on the example here: https://github.com/Azure/azure-functions-dotnet-worker/blob/main/samples/CustomMiddleware/ExceptionHandlingMiddleware.cs
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// Simple example which always fails. Use the following in an error condition
var httpReqData = await context.GetHttpRequestDataAsync();
if (httpReqData != null)
{
var newHttpResponse = httpReqData.CreateResponse(HttpStatusCode.InternalServerError);
await newHttpResponse.WriteAsJsonAsync(new { ResponseStatus = "Invocation failed!" }, newHttpResponse.StatusCode);
context.GetInvocationResult().Value = newHttpResponse;
}
}

IAsyncActionFilter return blank page

I'm trying to implement an IAsyncActionFilter in order to redirect to a Maintenance page if the application is set OnMaintenance in the appsettings.json.
I firstly implemented the ActionFilter as following :
public class MaintenanceFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
string controller = (string)context.RouteData.Values["Controller"];
string action = (string)context.RouteData.Values["action"];
if (controller != "States" && action != "Maintenance")
{
var cs = context.HttpContext.RequestServices.GetService<IClientService>();
if (await cs.GetMaintenanceState())
{
context.Result = new RedirectToActionResult("Maintenance", "States", null);
}
}
}
}
Basically, it just reads the controller and the action from the RouteData of the request. If the action is not "Maintenance", I ask the API if the application is on Maintenance. If yes, it returns to the "Maintenance" action.
Then, in the Startup.cs, I made the actionfilter global :
services.AddMvc(options =>
{
options.Filters.Add(new MaintenanceFilter());
});
The actionfilter is working as expected, it redirects to the Maintenance action if the api returns true, else it redirects to the initially requested page.
But the html code returned while always be blank, either if the application is on maintenance or not.
Even if I leave the ActionFilter empty like that :
public class MaintenanceFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
}
}
Why does my filter return a blank page ?
You should call await next() at the end of OnActionExecutionAsync, otherwise the actual action is not executed:
public class MaintenanceFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
string controller = (string)context.RouteData.Values["Controller"];
string action = (string)context.RouteData.Values["action"];
if (controller != "States" && action != "Maintenance")
{
var cs = context.HttpContext.RequestServices.GetService<IClientService>();
if (await cs.GetMaintenanceState())
{
context.Result = new RedirectToActionResult("Maintenance", "States", null);
}
}
}
await next(); // this executes the actual action
}

.Net Core: Return IActionResult from a custom Exception Middleware

I have created a new Exception middleware in my .Net Core application. All the exceptions throughout the application are captured and logged here. What I want is to return a IActionResult type like InternalServerError() or NotFound() from the Exception Middleware and not do response.WriteAsync as below.
Controller Method:
public async Task<IActionResult> Post()
{
//Do Something
return Ok();
}
Middleware:
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
public ExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next.Invoke(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var response = context.Response;
var statusCode = (int)HttpStatusCode.InternalServerError;
var message = exception.Message;
var description = exception.Message;
response.ContentType = "application/json";
response.StatusCode = statusCode;
await response.WriteAsync(JsonConvert.SerializeObject(new ErrorResponse
{
Message = message,
Description = description
}));
}
}
IActionResult is a thing from MVC, so it is only available within the MVC pipeline (including Razor Pages). Just before the MVC middleware terminates, it will execute those action results using ExecuteResultAsync. That method is then responsible of writing that response to HttpContext.Response.
So in custom middleware, you cannot just set an action result and have it executed, since you are not running within the MVC pipeline. However, with that knowledge, you can simply execute the result yourself.
Let’s say you want to execute a NotFoundResult which is what Controller.NotFound() creates. So you create that result and call ExecuteResultAsync with an . That executor will be able to execute that result object and write to the response:
var result = new NotFoundResult();
await result.ExecuteResultAsync(new ActionContext
{
HttpContext = context
});
That's not really possible due to where IActionResult and middleware sit in relation to one another in the architecture. Middleware sits much lower, and so it can't reach further up the stack to IActionResult. Here's an answer that talks more about it: https://stackoverflow.com/a/43111292/12431728
What you're trying to do can be done by simply adding this line:
app.UseStatusCodePagesWithReExecute("/Public/Error", "?statusCode={0}");
to the Configure method in the Startup.cs. Then you can create your Public Controller with Error method that does the following:
[AllowAnonymous]
public IActionResult Error(int? statusCode = null)
{
// Retrieve error information in case of internal errors.
var error = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error;
var path = HttpContext.Features.Get<IExceptionHandlerPathFeature>()?.Path;
// TODO: Redirect here based on your status code or perhaps just render different views for different status codes.
}
There is also another middleware that allows you to do a similar thing:
app.UseStatusCodePages(async context =>
{
if (context.HttpContext.Response.StatusCode == 401)
{
context.HttpContext.Response.Redirect("Errors/Unauthorized/");
}
else if (context.HttpContext.Response.StatusCode == 500)
{
// TODO: Redirect for 500 and so on...
}
});

Simple middleware example. Invoke method dosn't continue processing

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

Categories

Resources