Using .NET Core 2.1.
I am trying to access the attributes on the action parameter inside of an IAsyncActionFilter.
public IActionResult DoSomething([MyAttribute] MyParameter p) { ... }
In my IAsyncActionFilter, I would like to access the MyAttribute on the parameter p, but GetCustomAttributes does not exist.
public class MyActionFilter : IAsyncActionFilter
{
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// GetCustomAttributes does not exist here...
var attributes = context.ActionDescriptor.Parameters[0].GetCustomAttributes<MyAttribute>();
return next();
}
}
In ASP.NET MVC 5.2, you can use GetCustomAttributes:
https://learn.microsoft.com/en-us/dotnet/api/system.web.mvc.parameterdescriptor.getcustomattributes?view=aspnet-mvc-5.2#System_Web_Mvc_ParameterDescriptor_GetCustomAttributes_System_Boolean_
What is the way to achieve the same in .NET Core?
UPDATE 1
It seems we can cast the ActionDescriptor to ControllerActionDescriptor to access the underlying MethodInfo and then the parameters and their attributes.
public class TempDataActionFilter : IAsyncActionFilter
{
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var actionDescriptor = (ControllerActionDescriptor)context.ActionDescriptor;
var parameters =
from p in actionDescriptor.MethodInfo.GetParameters()
where p.GetCustomAttributes(typeof(MyAttribute), true) != null
select p;
var controller = context.Controller as Controller;
foreach (var p in parameters)
{
// Do something with the parameters that have an attribute
}
return next();
}
}
This feels wrong. I am always dismayed to see solutions of this type being proposed in Microsoft's own documentation. This is a runtime error waiting to happen. Is there a better way?
Seems that you're trying to bind action arguments. If that's the case, ModelBinding is preferred over filter, thus you don't have to cast the ActionDescriptor to ControllerActionDescriptor to inspect whether a parameter has a specified attribute,
In your scenario, a much easier & safer way is to make your FromTempDataAttribute implement the IBindingSourceMetadata to indicate you want to bind data from TempData:
internal class FromTempDataAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource(
"id-FromTempData",
"TempData Binding Source",
true,
true
);
public BindingSource BindingSource {get{
return FromTempDataAttribute.Instance;
}}
}
And then create a ModelBinder and a related Provider:
public class MyFromTempDataModelBinder : IModelBinder
{
private readonly IServiceProvider sp;
public MyFromTempDataModelBinder(IServiceProvider sp)
{
this.sp = sp;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var factory = this.sp.GetRequiredService<ITempDataDictionaryFactory>();
var tempData = factory.GetTempData(bindingContext.HttpContext);
var name = bindingContext.FieldName;
var o = tempData.Peek(name);
if (o == null) {
bindingContext.ModelState.AddModelError(name, $"cannot get {name} from TempData");
} else {
var result = Convert.ChangeType(o,bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
return Task.CompletedTask;
}
}
public class FromTempDataBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var has = context.BindingInfo?.BindingSource == FromTempDataAttribute.Instance;
if(has){
return new BinderTypeModelBinder(typeof(MyFromTempDataModelBinder));
}
return null;
}
}
The Provider returns an instance of MyFromTempDataModelBinder if the context.BindingInfo.BindingSource equals the required attribute.
Also don't forget to register this provider in your startup:
services.AddMvc(opts => {
opts.ModelBinderProviders.Insert(0, new FromTempDataBinderProvider());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Finally, you can get the data automatically:
public IActionResult Test([FromTempDataAttribute] string a, string b )
{
return Json(new {A = a, B = b,});
}
In case you insist on Filter, you can also make the FromTempDataAttribute implement the IBindingSourceMetadata interface as we do above, and then you can get those parameters as below:
public class TempDataActionFilter : IAsyncActionFilter
{
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var parameteres = context.ActionDescriptor.Parameters.Where(p => p.BindingInfo?.BindingSource == FromTempDataAttribute.Instance);
foreach(var p in parameteres){
// ...
}
return next();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With