如何将方法或参数传递给ASP.Net MVC中的操作筛选器 [英] How to pass a method or parameter to an action filter in ASP.Net MVC

查看:65
本文介绍了如何将方法或参数传递给ASP.Net MVC中的操作筛选器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将在操作过滤器中处理身份验证和授权,并创建如下所示的操作过滤器:

 公共类Auth:ActionFilterAttribute{public int Access {获取;放;}公共字符串角色{get;放;} =默认";公共功能AuthFunc {get;放;}公共重写void OnActionExecuting(HttpActionContext actionContext){字符串UserId = HttpContext.Current.User.Identity.GetUserId();//验证if(角色!=默认"&& UserManager.IsInRole(UserId,Roles)){//授权如果(AuthFunc){base.OnActionExecuting(actionContext);}别的{var response = actionContext.Request.CreateResponse(HttpStatusCode.Redirect);Uri requestUrl = actionContext.Request.RequestUri;response.Headers.Location = new Uri($"{requestUrl.Scheme}://{requestUrl.Host}:{requestUrl.Port}");actionContext.Response =响应;}}别的{var response = actionContext.Request.CreateResponse(HttpStatusCode.Redirect);Uri requestUrl = actionContext.Request.RequestUri;response.Headers.Location = new Uri($"{requestUrl.Scheme}://{requestUrl.Host}:{requestUrl.Port}");actionContext.Response =响应;}}} 

在控制器中:

  [Auth(Roles ="Teacher",Access =(short)TableEnum.Course,AuthFunc = Courses.CheckCoursesOfTeacher(CourseId))]公共ActionResult ShowExerciseAnswers(int CourseId,int ExerciseId){返回视图(模型:ChapterExerciseAnswer.ExerciseAnswerList(CourseId,ExerciseId));} 

AuthFunc 方法可能具有多个输入,但只有一个 bool 返回值.

  1. 如何将 AuthFunc ( Courses.CheckCoursesOfTeacher(CourseId)方法)传递给动作过滤器?

  2. 如何在动作过滤器属性中获取 CourseId 动作参数(将 CourseId ExerciseId 作为属性值)?

    处理这些问题(函数和变量不能发送到动作过滤器)的最佳方法是什么?

解决方案

属性参数传递函数的问题

最近我发现自己正在寻找这样的解决方案.每个 MS文件:

属性构造函数的参数仅限于简单的类型/文字:bool,int,double,string,Type,enums等以及这些类型的数组.您不能使用表达式或变量.您可以自由使用位置或命名参数.

因此,我们无法通过属性参数将函数传递给过滤器.可能有很多替代方法,但这是我选择要做的:

解决方案

我使用依赖项注入向管理器注入了动作过滤器,然后使用 Reflection 告诉过滤器在管理器上执行哪种方法.看起来是这样的:

班级:

 公共类Phone:AuditBase{其他道具[AuditRuleset(AuditRule.PhoneNumber)]公共字符串Number {get;放;}} 

枚举:

 公共枚举AuditRule{电话号码//如果需要,可以添加[说明]} 

属性:

 公共类AuditRulesetAttribute:属性{私有只读AuditRule _rule;public AuditRulesetAttribute(AuditRule rule)=>_rule =规则;} 

过滤器:

 公共类FormAuditActionFilter:IActionFilter{私人ILog _log {get;放;}私人IFormAuditor _auditor {get;放;}公共FormAuditActionFilter(ILog日志,IFormAuditor审核员){_log =日志;_auditor =审核员;}大量的过滤器代码...以下来自OnActionExecuted,已将提交对象的道具存储在objectProperties中...foreach(objectProperties中的PropertyInfo propertyInfo){//首先检查是否应遵循任何特殊的审核比较规则var auditRuleAttributes = propertyInfo.CustomAttributes.Where(x => x.AttributeType.Name == typeof(AuditRulesetAttribute).Name).ToList();如果(auditRuleAttributes.Any()){IEnumerable< IList< CustomAttributeTypedArgument>>attrList = auditRuleAttributes.Select(x => x.ConstructorArguments);foreach(attrList中的IList< CustomAttributeTypedArgument> attr)foreach(attr中的CustomAttributeTypedArgument arg)如果(_auditRuleManager.IsChanged(oldValue,newValue,(AuditRule)arg.Value))result.Add(BuildValueHistory(propertyInfo.Name,oldValue,newValue));继续;}}...更多过滤器代码...} 

AuditRuleManager:

 公共类AuditRuleManager:IAuditRuleManager{公共布尔IsChanged(对象val1,对象val2,AuditRule规则){object [] objArray = {val1,val2};var comparisonResult = typeof(AuditRuleManager).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Single(m => m.Name == rule.GetDescription())//尝试获取描述,但默认情况下返回名称.invoke(this,objArray)as bool ?;返回(布尔)比较结果;//如果比较结果不是有效的布尔值,则引发异常}//将电话号码与特殊规则进行比较,并返回其相等性private bool PhoneNumber(object val1,object val2)//注意:方法名称与枚举值名称匹配=>GetNumbersFromString(以字符串形式的val1)!= GetNumbersFromString(以字符串形式的val2); 

我花了一段时间的最后一块是使用Ninject的过滤器的DI.这就是我的

的工作方式

Global.asax.cs:

  kernel.BindFilter< FormAuditActionFilter>(FilterScope.Action,0).WhenActionMethodHas< FormAuditAttribute>().WithConstructorArgument("log",log).WithConstructorArgument("auditor",auditManager); 

摘要

我没有使用函数作为属性参数,而是使用DI将管理器注入到过滤器中.这使您的过滤器可以访问所需的功能.其次,我使用一个枚举来保存应执行的函数的名称.因此,从本质上讲,创建新函数并使用参数执行该函数所要做的就是:

  1. 将其添加到枚举
  2. 向管理器添加同名功能

我希望这会有所帮助!

进一步阅读

https://blogs.cuttingedge.it/steven/posts/2014/dependency-injection-in-attributes-dont-do-it/

I'm going to handle authentication and authorization in an action filter and create an action filter like below:

public class Auth : ActionFilterAttribute
{
    public int Access { get; set; }
    public string Roles { get; set; } = "Default";
    public Func<bool> AuthFunc { get; set; }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        string UserId = HttpContext.Current.User.Identity.GetUserId();
        //Authentication 
        if (Roles != "Default" && UserManager.IsInRole(UserId, Roles))
        {
           //Authorization 
           if (AuthFunc) { base.OnActionExecuting(actionContext); }
           else
             {
                var response = actionContext.Request.CreateResponse(HttpStatusCode.Redirect);
                Uri requestUrl = actionContext.Request.RequestUri;
                response.Headers.Location = new Uri($"{requestUrl.Scheme}://{requestUrl.Host}:{requestUrl.Port}");
                actionContext.Response = response;
             }
        }
        else
        {
            var response = actionContext.Request.CreateResponse(HttpStatusCode.Redirect);
            Uri requestUrl = actionContext.Request.RequestUri;
            response.Headers.Location = new Uri($"{requestUrl.Scheme}://{requestUrl.Host}:{requestUrl.Port}");
            actionContext.Response = response;
        }
    }
}

And in the controller:

[Auth(Roles="Teacher" , Access = (short)TableEnum.Course , AuthFunc = Courses.CheckCoursesOfTeacher(CourseId))]
public ActionResult ShowExerciseAnswers(int CourseId,int ExerciseId)
{
    return View(model: ChapterExerciseAnswer.ExerciseAnswerList(CourseId,ExerciseId));
}

The AuthFunc method maybe has multiple inputs but just a bool return value.

  1. How to pass AuthFunc (the Courses.CheckCoursesOfTeacher(CourseId) method) to action filter?

  2. How to get CourseId action parameter in action filter attribute (pass CourseId or ExerciseId as an attribute value)?

    What is the best way of handling these issues(functions and variables can't be sent to an action filter)?

解决方案

The Problem with Passing Functions in Attribute Parameters

I found myself looking for a solution like this recently. Parameters for attributes have to follow the following rules, per MS Docs:

Parameters to an attribute constructor are limited to simple types/literals: bool, int, double, string, Type, enums, etc and arrays of those types. You can not use an expression or a variable. You are free to use positional or named parameters.

Because of this, passing a function to the filter via an attribute parameter is not something we can do. There are probably lots of alternatives, but here's what I chose to do:

A Solution

I used dependency injection to inject my action filter with a manager, and then used Reflection to tell the filter which method on the manager to execute. Here's what it looks like:

Class:

public class Phone : AuditBase 
{
    ...other props...

    [AuditRuleset(AuditRule.PhoneNumber)]
    public string Number { get; set; }
}

Enum:

public enum AuditRule
{
    PhoneNumber // You can add [Description] if you want
}

Attribute:

public class AuditRulesetAttribute : Attribute
{
    private readonly AuditRule _rule;

    public AuditRulesetAttribute(AuditRule rule) => _rule = rule;
}

Filter:

public class FormAuditActionFilter : IActionFilter
{
    private ILog _log { get; set; }
    private IFormAuditor _auditor { get; set; }

    public FormAuditActionFilter(ILog log, IFormAuditor auditor)
    {
        _log = log;
        _auditor = auditor;
    }
    ...lots of filter code...
    ... The following is from OnActionExecuted, having stored the props of the submitted object in objectProperties...

    foreach(PropertyInfo propertyInfo in objectProperties)
    {
        // Check first for any special audit comparison rules which should be followed
        var auditRuleAttributes = propertyInfo.CustomAttributes
            .Where(x => x.AttributeType.Name == typeof(AuditRulesetAttribute).Name)
            .ToList();

        if (auditRuleAttributes.Any())
        {
            IEnumerable<IList<CustomAttributeTypedArgument>> attrList = auditRuleAttributes
                .Select(x => x.ConstructorArguments);

            foreach(IList<CustomAttributeTypedArgument> attr in attrList)
                foreach(CustomAttributeTypedArgument arg in attr)
                    if (_auditRuleManager.IsChanged(oldValue, newValue, (AuditRule)arg.Value))
                        result.Add(BuildValueHistory(propertyInfo.Name, oldValue, newValue));
            continue;
        }
    }
    ...lots more filter code...
}

AuditRuleManager:

public class AuditRuleManager : IAuditRuleManager
{
public bool IsChanged(object val1, object val2, AuditRule rule)
{
    object[] objArray = {val1, val2};

    var comparisonResult = typeof(AuditRuleManager)
        .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
        .Single(m => m.Name == rule.GetDescription()) // Try to get description, but falls back to name by default
        .Invoke(this, objArray) as bool?;

    return (bool)comparisonResult; // Throw an exception if the comparison result was not a valid bool
}

// Compare phone numbers with special rules, and return their equality
private bool PhoneNumber(object val1, object val2) // NOTE: Name of method matches name of enum value
    => GetNumbersFromString(val1 as string) != GetNumbersFromString(val2 as string);

The last piece that took me a while was the DI for the filter using Ninject. Here's how it worked in my

Global.asax.cs:

kernel.BindFilter<FormAuditActionFilter>(FilterScope.Action, 0)
                  .WhenActionMethodHas<FormAuditAttribute>()
                  .WithConstructorArgument("log", log)
                  .WithConstructorArgument("auditor", auditManager);

Summary

Instead of passing a function as an attribute parameter, I used DI to inject a manager into my filter. This gives your filter access to the functions you want. Second, I used an enum to hold the name of the function that should be executed. So essentially, all you have to do to create a new function and execute it with a parameter is to:

  1. Add it to the enum
  2. Add a function of the same name to the manager

I hope this helps!

Further Reading

https://blogs.cuttingedge.it/steven/posts/2014/dependency-injection-in-attributes-dont-do-it/

这篇关于如何将方法或参数传递给ASP.Net MVC中的操作筛选器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆