问题
We have a bunch of endpoints where we'd like to do the exact same thing for each of them: Register them as a route and verify that the user has access to them. Very condensed our issue can be condensed to us having something like this:
[HttpGet, Route(EntityId.First)]
[HttpGet, Route(EntityId.Second)]
[VerifyAccessFilter(EntityId.First, EntityId.Second)]
public async Task<IActionResult> Endpoint()
{
return Ok();
}
But would much rather like something like:
[RouteAndVerify(EntityId.First, EntityId.Second)]
public async Task<IActionResult> Endpoint()
{
return Ok();
}
As you can tell this is very simplified, but I hope the intent gets across. The hard part seems to be registering the route without using the default Route-attribute.
回答1:
You can achieve this with a custom IActionModelConvention implementation. The official documentation explains the concept of an action model convention: Work with the application model in ASP.NET Core - Conventions. In a nutshell, by implementing IActionModelConvention
, you can make changes to the application model and add filters, routes, etc to an action at runtime.
This is best explained with a sample implementation, which follows below. As you want to combine your existing MVC filter with the ability to configure routes for an action, the implementation below implements both IResourceFilter
(this can be whatever filter type you're using) and IActionModelConvention
:
public class VerifyAccessFilterAttribute : Attribute, IActionModelConvention, IResourceFilter
{
public VerifyAccessFilterAttribute(params string[] routeTemplates)
{
RouteTemplates = routeTemplates;
}
public string[] RouteTemplates { get; set; }
public void Apply(ActionModel actionModel)
{
actionModel.Selectors.Clear();
foreach (var routeTemplate in RouteTemplates)
{
actionModel.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel { Template = routeTemplate },
ActionConstraints = { new HttpMethodActionConstraint(new[] { "GET" }) }
});
}
}
public void OnResourceExecuting(ResourceExecutingContext ctx) { ... }
public void OnResourceExecuted(ResourceExecutedContext ctx) { ... }
}
In this example, it's all about the Apply
method, which simply adds a new SelectorModel for each routeTemplate
(as I've named it), each of which is constrained to HTTP GET
requests.
回答2:
Generally speaking, you cannot "merge" attributes, because attributes do not execute code. Attributes are only markers. Like "this method is marked red and blue". Then other code will come along, one looking for all red marks and doing something and another looking for all blue marks and doing something else. Building a purple mark by merging red and blue is just going to confuse the code looking for the markup, because purple is neither red nor blue.
However, AOP (aspect oriented programming) is available from third parties for C# and means attributes (called aspects because they do more than the normal marker attributes) can execute code.
You could write an aspect that decorates the method it's sitting on with the attributes you need, so you can write it once (and test it) and then you can set it on every method without worrying about forgetting an attribute or setting it wrong.
There are multiple AOP providers for C#, the most popular one seems to be PostSharp. You can see how write an aspect that adds attributes to a class or method at compile time with PostSharp here.
来源:https://stackoverflow.com/questions/53225901/create-custom-route-attribute-in-asp-net-core-2