In the project, I use action filters to test the availability of a method in gui, an example of such a filter is below:

public class DisallowChangingTheApprovedOperationsAttribute : ActionFilterAttribute { public IOperationService _operationService { get; set; } public override void OnActionExecuting(ActionExecutingContext filterContext) { var valueResult = filterContext.Controller.ValueProvider.GetValue("operationId"); if (valueResult == null || string.IsNullOrWhiteSpace(valueResult.AttemptedValue)) { throw new InvalidOperationException("OperationId is not be null"); } int operationId = int.Parse(valueResult.AttemptedValue); var result = _operationService.IsPublished(operationId); if (result) { if (string.Equals(filterContext.HttpContext.Request.RequestType, "Get", System.StringComparison.InvariantCultureIgnoreCase)) { filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.UrlReferrer.PathAndQuery); } else { filterContext.Result = new RedirectToRouteResult(filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, new RouteValueDictionary { { "operationId", operationId } }); } } } } 

its purpose is to prevent a change in the approved Operation ; the method for the passed parameter operationId checks the state var result = _operationService.IsPublished(operationId); and if the result is true then the action is canceled.

I apply this filter to the methods I need: EditOperation , DeleteOperation , etc., an example of a method for editing below:

 [DisallowChangingTheApprovedOperations] public ActionResult EditOperation(int operationId) { ViewBag.Products = new SelectList(_productService.GetProductList(), "Id", "Name"); ViewBag.Equipments = new SelectList(_equipmentService.GetEquipmentList(), "Id", "SelectListName"); ViewBag.Units = new SelectList(_unitService.GetAll(), "Id", "FullName"); var model = _operationService.GetOperation(operationId); return View("EditOperation", model); } [HttpPost] [DisallowChangingTheApprovedOperations] public Actionresult EditOperation(int operationId, EditedOperation model) { } 

In order for this method to work, I have to drag the operationId into each of the methods, which seems to me to be not convenient / not correct, but this is not all.

I also have a Program entity that relates to Operation in the ratio 1 operation, many programs, for working with Program I have methods: AddProgram , EditProgram , etc. I need to prohibit changes to an operation based on its state ( _operationService.isPublished(int operationId) ) - I cannot add, modify existing programs, for these purposes I pass the operationId parameter to the editing, modification, removal of the program and apply the same DisallowChangingTheApprovedOperations filter to the method

An example of a method for editing a program:

 [DisallowChangingTheApprovedOperations] //здесь прочие методы public ActionResult EditProgram(int operationId, programId) {} 

those. here, in addition to the program id , I have to pass on the operation id as well, as an option you can implement a method from the ActionFilterAttribute inherited from which, based on the programId will receive the status of the operation and, according to some logic, cancel or allow the action, i.e. something like

 public class DisallowChangingTheApprovedOperationsForTheProgrammAttribute : ActionFilterAttribute { //получаем programId из контекста var result = _operationService.GetOperationStateForTheProgramm(programId); //отменяем действия в соответствии с логикой } 

Tell me how to do better / correctly implement the possibility of restricting some user actions in accordance with certain logic?

  • How does the client know what opertionId to send? If this is a service parameter, you don’t need to add it as a route parameter, you can simply take it from the request to the actionfilter - Grundy
  • @Grundy in the view, I form a helper link to the method with the necessary parameters - Bald
  • Is this operationId used somehow inside the methods? or only in the filter? - Grundy
  • @Grundy added the EditOperation method's implementation of the get EditOperation , and I pass it to the program change methods only for the filter - Bald

1 answer 1

You have approached the task from the wrong side. First of all, your approach (to enter an additional parameter just to validate it) is simply dangerous - what if the hacker gives the wrong transaction number?

Think about this. Noting the action attribute (Action), in which the published operations cannot be changed, you architecturally enter the existence of actions, in which you can change the published operations (for example, if you forgot to specify your attribute).

It is better to hide the resolution of the action in a lower layer than the layer of the web application controllers. If you have a layer of business logic - then that is where the most logical place for such checks. If there is no business logic layer or it is there but is poorly designed and expanded, then it makes sense to include such rules in the DAL layer.

So, if you use EF as a DAL, it will look something like this:

 class Context : DbContext { protected override bool ShouldValidateEntity(DbEntityEntry entityEntry) { // По умолчанию удаленные сущности не валидируются - но нам-то надо! return entityEntry.State != EntityState.Unchanged; } protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) { if (entityEntry.Entity is Operation && entityEntry.State != EntityState.Added && IsPublishedOperation(entityEntry.Cast<Operation>())) { return new DbEntityValidationResult(entityEntry, new[] { new DbValidationError(null, "Cannot change published operation") }); } if (entityEntry.State == EntityState.Deleted) { // Нам все еще не надо применять обычные правила валидации к удаленным записям return new DbEntityValidationResult(entityEntry, new DbValidationError[0]); } return base.ValidateEntity(entityEntry, items); } } 

Now at the level of the web application, it remains to correctly handle the validation error. This can already be done in the filter.

PS be more careful with the implementation of the IsPublishedOperation check - it is not the current state of the operation that is to be checked, but the initial one - otherwise the operation will not be published.

And in order to “recognize” the custom cause of the error in the filter, you can create a heir to DbValidationError , since it is not sealed.