How Can I Get Controller Parameters In Authorization Filter
Filters in .NET offering a great mode to hook into the MVC action invocation pipeline. Therefore, we can use filters to excerpt code that tin exist reused and make our deportment cleaner and maintainable. At that place are some filters that are already provided by ASP.NET Core like the potency filter, and there are the custom ones that we can create ourselves.
There are different filter types:
- Authorisation filters – They run showtime to determine whether a user is authorized for the current request
- Resources filters – They run right after the authorization filters and are very useful for caching and operation
- Activity filters – They run right before and afterwards the action method execution
- Exception filters – They are used to handle exceptions before the response body is populated
- Result filters – They run before and after the execution of the activity methods result.
In this article, we are going to talk about Action filters and how to use them to create cleaner and reusable code in our Web API.
VIDEO: Implementing Action Filters in ASP.NET Core Video.
We have divided this article into the following sections:
- Action Filters Implementation
- Activity Filters Scope
- Order of Invocation
- Writing Improve Code with Action Filters
- Validation with Activeness Filters
- Dependency Injection in Activeness Filters
- Decision
Activeness Filters Implementation
To create an Acton filter, we demand to create a class that inherits either from the IActionFilter
interface or IAsyncActionFilter
interface or from the ActionFilterAttribute
form which is the implementation of the IActionFilter
, IAsyncActionFilter
, and a few different interfaces as well:
public abstract class ActionFilterAttribute : Attribute, IActionFilter, IFilterMetadata, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IOrderedFilter
In our examples, nosotros are going to inherit from the IActionFIlter
interface because it has all the method definitions we require.
To implement the synchronous Action filter that runs before and after action method execution, we need to implementOnActionExecuting
and OnActionExecuted
methods:
namespace ActionFilters.Filters { public course ActionFilterExample : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // our code earlier action executes } public void OnActionExecuted(ActionExecutedContext context) { // our code after action executes } } }
Nosotros can do the same thing with an asynchronous filter by inheriting from IAsyncActionFilter
, but nosotros only have one method to implement the OnActionExecutionAsync
:
namespace ActionFilters.Filters { public grade AsyncActionFilterExample : IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // execute any lawmaking before the action executes var result = await next(); // execute any lawmaking afterward the action executes } } }
The Scope of Action Filters
Like the other types of filters, the action filter tin can be added to dissimilar scope levels: Global, Action, Controller.
If we want to utilise our filter globally, we demand to register it inside the AddControllers()
method in the ConfigureServices
method:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(config => { config.Filters.Add together(new GlobalFilterExample()); }); }
But if we desire to use our filter equally a service type on the Action or Controller level, we need to register it in the same ConfigureServices
method but as a service in the IoC container:
services.AddScoped<ActionFilterExample>(); services.AddScoped<ControllerFilterExample>();
Finally, to use a filter registered on the Action or Controller level, we demand to place it on top of the Controller or Activity as a ServiceType:
namespace AspNetCore.Controllers { [ServiceFilter(typeof(ControllerFilterExample))] [Route("api/[controller]")] [ApiController] public course TestController : ControllerBase { [HttpGet] [ServiceFilter(typeof(ActionFilterExample))] public IEnumerable<cord> Get() { return new string[] { "example", "data" }; } } }
Guild of Invocation
The order in which our filters are executed is equally follows:
Of grade, nosotros can change the club of invocation by adding an additional belongings Order
to the invocation statement:
namespace AspNetCore.Controllers { [ServiceFilter(typeof(ControllerFilterExample), Guild=2)] [Road("api/[controller]")] [ApiController] public class TestController : ControllerBase { [HttpGet] [ServiceFilter(typeof(ActionFilterExample), Social club=ane)] public IEnumerable<cord> Get() { render new cord[] { "example", "data" }; } } }
Or something similar this on top of the same action:
[HttpGet] [ServiceFilter(typeof(ActionFilterExample), Order=2)] [ServiceFilter(typeof(ActionFilterExample2), Order=1)] public IEnumerable<string> Get() { return new string[] { "example", "data" }; }
Improving the Code with Action Filters
If nosotros open the starting project from the AppStart binder from our repository, we tin can find the MoveController
class in the Controllers
folder. This controller has an implementation for all the CRUD operations. For the sake of simplicity, we haven't used whatever additional layers for our API. This project also implements global fault handling so if you are not familiar with that topic, we advise you read Global Exception Handling in .Net Core Spider web API.
Our actions are quite clean and readable without try-catch
blocks due to global exception handling, merely we can meliorate them even further.
The of import thing to detect is that our Film
model inherits from the IEntity
interface:
[Table("Film")] public class Picture: IEntity { [Key] public Guid Id { get; ready; } [Required(ErrorMessage = "Name is required")] public string Proper noun { become; ready; } [Required(ErrorMessage = "Genre is required")] public string Genre { go; set; } [Required(ErrorMessage = "Manager is required")] public string Managing director { get; set; } }
So allow'southward outset with the validation lawmaking from the Postal service and PUT deportment.
Validation with Activity Filters
If we look at our Mail and PUT actions, we tin find the repeated code in which we validate our Movie
model:
if (pic == null) { render BadRequest("Movie object is zero"); } if (!ModelState.IsValid) { render BadRequest(ModelState); }
We can extract that code into a custom Action Filter form, thus making this code reusable and the action cleaner.
So permit's do that.
Let's create a new folder in our solution explorer, and proper name it ActionFilters
. So inside that folder, we are going to create a new class ValidationFilterAttribute
:
using Microsoft.AspNetCore.Mvc.Filters; namespace ActionFilters.ActionFilters { public grade ValidationFilterAttribute : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { } public void OnActionExecuted(ActionExecutedContext context) { } } }
Now nosotros are going to modify the OnActionExecuting
method to validate our model:
using ActionFilters.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Arrangement.Linq; namespace ActionFilters.ActionFilters { public class ValidationFilterAttribute : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { var param = context.ActionArguments.SingleOrDefault(p => p.Value is IEntity); if(param.Value == zero) { context.Outcome = new BadRequestObjectResult("Object is zip"); return; } if(!context.ModelState.IsValid) { context.Result = new UnprocessableEntityObjectResult(context.ModelState); } } public void OnActionExecuted(ActionExecutedContext context) { } } }
Next, let'southward register this action filter in the ConfigureServices
method:
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<MovieContext>(options => options.UseSqlServer(Configuration.GetConnectionString("sqlConString"))); services.AddScoped<ValidationFilterAttribute>(); services.AddControllers(); }
Finally, let's remove that validation code from our actions and telephone call this activity filter as a service:
[HttpPost] [ServiceFilter(typeof(ValidationFilterAttribute))] public IActionResult Post([FromBody] Movie motion picture) { _context.Movies.Add together(movie); _context.SaveChanges(); return CreatedAtRoute("MovieById", new { id = movie.Id }, pic); } [HttpPut("{id}")] [ServiceFilter(typeof(ValidationFilterAttribute))] public IActionResult Put(Guid id, [FromBody]Movie movie) { var dbMovie = _context.Movies.SingleOrDefault(10 => x.Id.Equals(id)); if (dbMovie == nada) { return NotFound(); } dbMovie.Map(movie); _context.Movies.Update(dbMovie); _context.SaveChanges(); render NoContent(); }
Excellent.
This code is much cleaner and more than readable at present without the validation part. And furthermore, the validation part is now reusable every bit long equally our model classes inherit from the IEntity interface, which is quite common behavior.
Before nosotros test this validation filter, we have to suppress validation from the [ApiController]
attribute. If we don't practise it, it will overtake the validation from our action filter and e'er render 400 (BadRequest) for all validation errors. But as yous've seen, if our model is invalid, we want to return the UnprocessableEntity result (422).
To suppress the default validation, we take to alter the Startup
class:
services.Configure<ApiBehaviorOptions>(options => { options.SuppressModelStateInvalidFilter = truthful; });
Now, if we send a PUT request for instance with the invalid model we will get the Unprocessable Entity
response:
Dependency Injection in Action Filters
If we take a await at our GetById
, DELETE and PUT deportment, we are going to encounter the code where we fetch the move past id from the database and check if it exists:
var dbMovie = _context.Movies.SingleOrDefault(x => x.Id.Equals(id)); if (dbMovie == null) { return NotFound(); }
That'due south something we tin can excerpt to the Activity Filter class every bit well, thus making information technology reusable in all the actions.
Of course, nosotros need to inject our context
in a new ActionFilter class by using dependency injection.
And so, allow's create another Action Filter grade ValidateEntityExistsAttribute
in the ActionFilters
folder and modify information technology:
using System.Linq; namespace ActionFilters.ActionFilters { public form ValidateEntityExistsAttribute<T> : IActionFilter where T: class, IEntity { private readonly MovieContext _context; public ValidateEntityExistsAttribute(MovieContext context) { _context = context; } public void OnActionExecuting(ActionExecutingContext context) { Guid id = Guid.Empty; if (context.ActionArguments.ContainsKey("id")) { id = (Guid)context.ActionArguments["id"]; } else { context.Result = new BadRequestObjectResult("Bad id parameter"); return; } var entity = _context.Set<T>().SingleOrDefault(ten => x.Id.Equals(id)); if(entity == null) { context.Result = new NotFoundResult(); } else { context.HttpContext.Items.Add("entity", entity); } } public void OnActionExecuted(ActionExecutedContext context) { } } }
Nosotros've created this Action Filter class to be generic and so that we could reuse information technology for any model in our project. Furthermore, if we find the entity in the database, nosotros store it in HttpContext
because we demand that entity in our activeness methods and we don't want to query the database two times (we would lose more than nosotros gain if we double that action).
At present let's register information technology:
services.AddScoped<ValidateEntityExistsAttribute<Motion picture>>();
And let's modify our actions:
[HttpGet("{id}", Name = "MovieById")] [ServiceFilter(typeof(ValidateEntityExistsAttribute<Movie>))] public IActionResult Become(Guid id) { var dbMovie = HttpContext.Items["entity"] equally Flick; return Ok(dbMovie); } [HttpPut("{id}")] [ServiceFilter(typeof(ValidationFilterAttribute))] [ServiceFilter(typeof(ValidateEntityExistsAttribute<Movie>))] public IActionResult Put(Guid id, [FromBody]Motion-picture show movie) { var dbMovie = HttpContext.Items["entity"] as Movie; dbMovie.Map(movie); _context.Movies.Update(dbMovie); _context.SaveChanges(); return NoContent(); } [HttpDelete("{id}")] [ServiceFilter(typeof(ValidateEntityExistsAttribute<Movie>))] public IActionResult Delete(Guid id) { var dbMovie = HttpContext.Items["entity"] every bit Movie; _context.Movies.Remove(dbMovie); _context.SaveChanges(); return NoContent(); }
Awesome.
Now our deportment look great without code repetition, endeavour-catch blocks, or additional fetch requests towards the database.
Conclusion
Thank you for reading this article. We hope you have learned new useful things.
Every bit nosotros already said, we always recommend using Action Filters because they give u.s.a. reusability in our code and cleaner lawmaking in our deportment as well.
How Can I Get Controller Parameters In Authorization Filter,
Source: https://code-maze.com/action-filters-aspnetcore/
Posted by: galvanlaideard.blogspot.com
0 Response to "How Can I Get Controller Parameters In Authorization Filter"
Post a Comment