ASP.NET Core model binder result logging - c#

I would like to increase / improve my logging.
So far I had in each controller action code like
public asyc Task<IActionResult> Action1(int id, [FromBody] RequestModel request) {
string log = $"{nameof(Action1)}(id: {id}, request: {request?.ToJson()})";
_logger.LogInformation(log);
The main purpose was to see whats actually reaching the controller action.
I removed it since it cluttered the code heavily (e.g. for methods with a lot of paramters). But now I am unhappy with the result that the logs do not show the information any more (and I needed them to investigate some unexplainable bugs).
Is there a way to hook up into the model binder result (e.g. via a service filter) to log the model binder result?
Works like charm: thanks to Shahzad Hassan
public class MethodCallParameterLogger : IAsyncActionFilter
{
public ILoggerFactory LoggerFactory { get; set; }
public MethodCallParameterLogger(ILoggerFactory loggerFactory)
{
LoggerFactory = loggerFactory;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
LoggerFactory
// the display name contains the controller name space, controller name, method name and the postfix " (ActionLogger)"
.CreateLogger(context.ActionDescriptor.DisplayName.Split(" ")[0])
// MIND THAT THIS LOGS EVEN SENSITIVE DATA (e.g. credentials) !!!
.LogInformation(JsonConvert.SerializeObject(context.ActionArguments));
var resultContext = await next();
}
}

I think you can use an ActionFilter instead. ActionFilters are executed after the model binding, so you can retrieve the parameters from the ActionExecutingContext. You can override the OnActionExecuting method and log whatever is required:
public class LogParamsFilter : ActionFilterAttribute
{
private readonly ILogger<LogsParamsFilter> _logger;
public LogParamsFilter (ILogger<LogsParamsFilter> logger)
{
_logger = logger;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var id = (int)context.ActionArguments["id"];
var request = context.ActionArguments["request"] as RequestModel;
var action = context.ActionDescriptor.DisplayName;
string log = $"{action)}(id: {id}, request: {request?.ToJson()})";
_logger.LogInformation(log);
base.OnActionExecuting(context);
}
}
You would need to use it as TypeFilter on the controller action so that its dependencies i.e. ILogger is resolved via DI.
[TypeFilter(typeof(LogParamsFilter))]
public asyc Task<IActionResult> Action1(int id, [FromBody] RequestModel request)
{
...
}
Or you can register it globally in the startup for all controllers:
services.AddMvc(options => options
.Filters.Add(new TypeFilterAttribute(typeof(LogParamsFilter))));
In order to use it as a generic filter for all controller actions, iterate through the context.ActionArguments.Keys property and log the value for each key. You would need to do some type checkings and call .ToJson() if the type of the ActionArgument is RequestModel.
I hope that helps.

Related

C# - Custom ActionFilter pass in configuration variables

I have a custom action filter that takes in a property but I need the property to come from my appsettings.json file. I pass my configuration into the controller, but when I try to pass in the "_config.GetValue< string >("myString")" the "_config" is red underlined with the message:
An object reference is required for the non-static field, method, or property 'MyController._config'
Action Filter
public class MyActionFilter : ActionFilterAttribute
{
public string Property1 { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
...
}
}
Controller
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
private readonly IConfiguration _config;
public MyController(IConfiguration config) {
_config = config;
}
[Authorize]
[HttpPost(Constants.ActionName.MyMethod, Name = nameof(MyMethod))]
[MyActionFilter(Property1 = _config.GetValue<string>("myString"))] // Breaks here!
public ActionResult<string> MyMethod()
{
...
}
}
How can I do this? Or at least, how can I avoid hardcoding a value for my action filter properties?
Your current approach does not work because constructor parameters and properties of attributes are evaluated at compile time.
You could use the service locator pattern in your filter like so:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var config = filterContext.HttpContext.RequestServices.GetService<IConfiguration>();
string Property1 = config.GetValue<string>("myString");
}
However, this approach is debatable because the service locator pattern is considered an anti-pattern.
Another approach is to use a ServiceFilter. First, create a constructor for the action filter:
public MyActionFilter(string property1)
{
Property1 = property1;
}
Second, change the action filter to a service filter in the controller:
[ServiceFilter(typeof(MyActionFilter))]
public ActionResult<string> MyMethod()
{
...
}
Third, register the action filter:
builder.Services.AddScoped(p => new MyActionFilter(p.GetService<IConfiguration>().GetValue<string>("myString")));
Here is a great blogpost about the topic: Dependency Injection in action filters in ASP.NET Core.

How to check ASP.NET auth policy in code rather than using AuthorizeAttribute?

The normal way to check that a policy is being fulfilled in ASP.NET Core's authorization system is to setup a policy in ConfigureServices like so:
services.AddAuthorization(conf => {
conf.AddPolicy("UserHasRecentPassport", policy => policy.RequireAssertion(ctx => { return ctx.User.HasRecentPassport(); }));
}
... and then specify it for a controller or action using AuthorizeAttribute, like so:
[Authorize("UserHasRecentPassport")]
public class HomeController : Controller {
public IActionResult Index() {
return View();
}
}
However, I'm writing a tag helper which needs to check whether a particular policy is being met. I therefore need to just check this in code rather than using the AuthorizeAttribute approach, ie. something like:
public override void Process(TagHelperContext context, TagHelperOutput output) {
output.TagName = null;
if (!policyRequirementIsMet("UserHasRecentPassport")) {
output.SuppressOutput();
}
}
Is there any way for me to implement policyRequirementIsMet so it goes to ASP.NET Core and says "tell me whether the policy with name X is met"?
Use IAuthorizationService to perform imperative authorization. It is a little more involved than shown in the docs when used inside of a TagHelper class since they don't have direct access to HttpContext and User.
Here's one approach that uses the [ViewContext] attribute as a means to get hold of HttpContext and User, and uses DI to get hold of IAuthorizationService:
public class PassportTagHelper : TagHelper
{
private readonly IAuthorizationService authorizationService;
public PassportTagHelper(IAuthorizationService authorizationService)
{
this.authorizationService = authorizationService;
}
[ViewContext]
public ViewContext ViewContext { get; set; }
public override async Task ProcessAsync(TagHelperContext ctx,
TagHelperOutput output)
{
var httpContext = ViewContext.HttpContext;
var authorizationResult = await authorizationService
.AuthorizeAsync(httpContext.User, "UserHasRecentPassport");
if (!authorizationResult.Succeeded)
output.SuppressOutput();
}
}
Things to note:
HttpContext is accessed via the ViewContext property, which is set courtesy of decorating it with the [ViewContext] attribute.
Process changes to ProcessAsync, so that we can use await.
The value returned from AuthorizeAsync is an AuthorizationResult, which indicates success via its Succeeded property and a reason for failure in its Failure property.

how can i get model object in exceptionFilter Asp.NetCore?

I am working with a WEB application in ASP .NET Core 2.0 where I have a custom ExceptionAttribute filter that inherits from ExceptionFilterAttribute.
How can I access the Model object passed to a action in POST call.
The mentioned method is passed a ExceptionContext but I could not find an easy and reliable way to get Model object from it and pass to ViewResult.
The filter I have, looks like the following:
public class ApiCallExceptionAttribute: ExceptionFilterAttribute
{
private readonly IModelMetadataProvider _modelMetadataProvider;
public ApiCallExceptionAttribute(
IModelMetadataProvider modelMetadataProvider)
{
_modelMetadataProvider = modelMetadataProvider;
}
public override void OnException(ExceptionContext context)
{
//how can i accesss model object here and pass to ViewResult
result.ViewData = new ViewDataDictionary(_modelMetadataProvider,context.ModelState);
context.Result = result;
}
}
and the controller looks like the following:
[HttpPost]
[ServiceFilter(typeof(ApiCallExceptionAttribute))]
public async Task<IActionResult> Activation(ActivationViewModel model)
{
//throw exception in controller content
}
Can't find any easy way to do that.
The approach I would take is to implement IActionFilter also on your class.
Read the model's info in onActionExecuting() and store it in HTTPContext.Items.
Then read it from HttpContext.Items when there is an exception.
Resist the urge to store this information as a private field in your class because MVC do not create a new instance of it's filters for every request!
public class LogExceptionFilterAttribute : Attribute, IExceptionFilter, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Items["ActionArguments"] = context.ActionArguments;
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnException(ExceptionContext context)
{
var model = context.HttpContext.Items["ActionArguments"];
}
}

WebAPI: Retrieve GET parameter from Controller Constructor

Every call to my WebAPI may (or may not) contain the GET parameter
/api/SomeControllerFunction?loglevel=(someint)
From the function inside the controller I can initialize a LogCollector:
[HttpGet]
SomeControllerFunction(int loglevel = 0)
{
LogCollector logger = new LogCollector(loglevel)
}
To not repeat myself too often, I want to hide this in the class hierarchy by adding it into the constructor of a BaseController, from which all my controllers shall inherit:
public class BaseController: ApiController
{
internal LogCollector Logger
BaseController()
{
Logger = new LogCollector(loglevel);
}
But how can I access a GET parameter from the constructor?
Instead of using the constructor you could inject the LogCollector directly into the method. If you did want to use the constructor you should use a Di / IoC framework as that would be more appropriate.
In the example below you can use a custom ActionFilterAttribute instance which injects the Logger based the incoming (optional) log level. The log level is then defined in the route using a RouteAttribute on the action. The RouteAttribute also defines a default value for the log level so it is not required when calling that action.
LogInjectorFilterAttribute.cs
public class LogInjectorFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
const string key = "loglevel";
if(actionContext.ControllerContext.RouteData.Values.ContainsKey(key))
{
var loglevel = int.Parse(actionContext.ControllerContext.RouteData.Values[key].ToString());
LogCollector logger = new LogCollector(loglevel);
actionContext.ActionArguments["logger"] = logger;
}
base.OnActionExecuting(actionContext);
}
}
HomeController.cs
[HttpGet]
[Route("api/Home/Get/{loglevel:int=1}")]
[LogInjectorFilter]
public IHttpActionResult Get(LogCollector logger)
{
}
The constructor is invoked too early, you can't access the parameters from there. However, you can override the Initialize method and retrieve the GET parameters from the context:
protected override void Initialize(HttpControllerContext controllerContext)
{
foreach (var parameter in controllerContext.Request.GetQueryNameValuePairs())
{
Debug.WriteLine(string.Format("{0} = {1}", parameter.Key, parameter.Value));
}
base.Initialize(controllerContext);
}

How to read action method's attributes in ASP.NET Core MVC?

Based on this article I'm trying to create an IActionFilter implementation for ASP.NET Core that can process attributes that are marked on the controller and the controller's action. Although reading the controller's attributes is easy, I'm unable to find a way to read the attributes defined on the action method.
Here's the code I have right now:
public sealed class ActionFilterDispatcher : IActionFilter
{
private readonly Func<Type, IEnumerable> container;
public ActionFilterDispatcher(Func<Type, IEnumerable> container)
{
this.container = container;
}
public void OnActionExecuting(ActionExecutingContext context)
{
var attributes = context.Controller.GetType().GetCustomAttributes(true);
attributes = attributes.Append(/* how to read attributes from action method? */);
foreach (var attribute in attributes)
{
Type filterType = typeof(IActionFilter<>).MakeGenericType(attribute.GetType());
IEnumerable filters = this.container.Invoke(filterType);
foreach (dynamic actionFilter in filters)
{
actionFilter.OnActionExecuting((dynamic)attribute, context);
}
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
throw new NotImplementedException();
}
}
My question is: how do I read the action method's attributes in ASP.NET Core MVC?
You can access the MethodInfo of the action through the ControllerActionDescriptor class:
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
var actionAttributes = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true);
}
}
The MVC 5 ActionDescriptor class used to implement the ICustomAttributeProvider interface which gave access to the attributes. For some reason this was removed in the ASP.NET Core MVC ActionDescriptor class.
Invoking GetCustomAttributes on a method and/or class is slow(er). You should not invoke GetCustomAttributes every request since .net core 2.2, which #Henk Mollema is suggesting. (There is one exception which I will explain later)
Instead, on application startup time, the asp.net core framework will invoke GetCustomAttributes on the action method and controller for you and store the result in the EndPoint metadata.
You can then access this metadata in your asp.net core filters via the EndpointMetadata property of the ActionDescriptor class.
public class CustomFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Get attributes on the executing action method and it's defining controller class
var attributes = context.ActionDescriptor.EndpointMetadata.OfType<MyCustomAttribute>();
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
If you do not have access to the ActionDescriptor (for example: because you are operating from a Middleware instead of an filter) from asp.net core 3.0 you can use the GetEndpoint extension method to access it's Metadata.
For more info see this github issue.
public class CustomMiddleware
{
private readonly RequestDelegate next;
public CustomMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
// Get the enpoint which is executing (asp.net core 3.0 only)
var executingEnpoint = context.GetEndpoint();
// Get attributes on the executing action method and it's defining controller class
var attributes = executingEnpoint.Metadata.OfType<MyCustomAttribute>();
await next(context);
// Get the enpoint which was executed (asp.net core 2.2 possible after call to await next(context))
var executingEnpoint2 = context.GetEndpoint();
// Get attributes on the executing action method and it's defining controller class
var attributes2 = executingEnpoint.Metadata.OfType<MyCustomAttribute>();
}
}
Like stated above, Endpoint Metadata contains the attributes for the action method and its defining controller class. This means that if you would want to explicitly IGNORE the attributes applied on either the controller class or the action method, you have to use GetCustomAttributes. This is almost never the case in asp.net core.
My custom attribute is inherit from ActionFilterAttribute. I put it on my controller but there is one action do not need it. I want to use AllowAnonymous attribute to ignore that but it not work. So I add this snippet in my custom attribute to find the AllowAnonymous and skip it. You can get other in the for loop.
public class PermissionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
foreach (var filterDescriptors in context.ActionDescriptor.FilterDescriptors)
{
if (filterDescriptors.Filter.GetType() == typeof(AllowAnonymousFilter))
{
return;
}
}
}
}
I created an extension method that mimics the original GetCustomAttributes based in Henk Mollema's solution.
public static IEnumerable<T> GetCustomAttributes<T>(this Microsoft.AspNet.Mvc.Abstractions.ActionDescriptor actionDescriptor) where T : Attribute
{
var controllerActionDescriptor = actionDescriptor as ControllerActionDescriptor;
if (controllerActionDescriptor != null)
{
return controllerActionDescriptor.MethodInfo.GetCustomAttributes<T>();
}
return Enumerable.Empty<T>();
}
Hope it helps.
As answered by Henk Mollena
public void OnActionExecuting(ActionExecutingContext context)
{
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (controllerActionDescriptor != null)
{
var controllerAttributes = controllerActionDescriptor
.MethodInfo
.GetCustomAttributes(inherit: true);
}
}
is the correct way if you want to check the presence of an attribute applied to an action.
I just want to add to his answer in case if you want to check the presence of an attribute applied to the controller
public void OnActionExecuting(ActionExecutingContext context)
{
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (controllerActionDescriptor != null)
{
var actionAttributes = controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes(inherit: true);
}
}
Also you can use the overloaded function of the GetCustomAttributes functions to get your specific attribute(s)
var specificAttribute = GetCustomAttributes(typeof(YourSpecificAttribute), true).FirstOrDefault()

Categories

Resources