代码改变世界

动作过滤器

2012-09-29 11:41  yezhi  阅读(513)  评论(0编辑  收藏  举报

动作过滤器是可用于修饰动作方法和控制器的自定义特性。框架将在执行动作之前或者之后执行动作过滤器中的逻辑。

在典型的asp.net mvc应用程序中,动作负责处理用户发起的请求。用户单击按钮或提交表单,请求即被发送并通过路由逻辑路由至特定的控制器,最后执行特定的动作。但有时可能希望在动作执行之前执行某些代码,例如验证是否允许当前用户执行某个操作,或者添加日志或错误处理。另一种通过使用过滤器可以变得更容易的情况是使用用于显示数据的页面时。

以下摘自msdn:http://msdn.microsoft.com/zh-cn/library/gg416513(v=vs.98).aspx

ASP.NET MVC 筛选器类型

ASP.NET MVC 支持以下类型的操作筛选器:

  • 授权筛选器。 这些筛选器用于实现 IAuthorizationFilter 和做出关于是否执行操作方法(如执行身份验证或验证请求的属性)的安全决策。 AuthorizeAttribute 类和 RequireHttpsAttribute类是授权筛选器的示例。 授权筛选器在任何其他筛选器之前运行。

  • 操作筛选器。 这些筛选器用于实现 IActionFilter 以及包装操作方法执行。 IActionFilter 接口声明两个方法:OnActionExecuting 和 OnActionExecuted OnActionExecuting 在操作方法之前运行。 OnActionExecuted 在操作方法之后运行,可以执行其他处理,如向操作方法提供额外数据、检查返回值或取消执行操作方法。

  • 结果筛选器。 这些筛选器用于实现 IResultFilter 以及包装 ActionResult 对象的执行。 IResultFilter 声明两个方法:OnResultExecuting 和 OnResultExecuted OnResultExecuting 在执行ActionResult 对象之前运行。 OnResultExecuted 在结果之后运行,可以对结果执行其他处理,如修改 HTTP 响应。 OutputCacheAttribute 类是结果筛选器的一个示例。

  • 异常筛选器。 这些筛选器用于实现 IExceptionFilter,并在 ASP.NET MVC 管道执行期间引发了未处理的异常时执行。 异常筛选器可用于执行诸如日志记录或显示错误页之类的任务。HandleErrorAttribute 类是异常筛选器的一个示例。

Controller 类用于实现每个筛选器接口。 您可以通过重写控制器的 On<Filter> 方法为特定控制器实现这些筛选器中的任何一个。 例如,您可以重写 OnAuthorization 方法。 可下载示例中包含的简单控制器将重写每个筛选器,并在每个筛选器运行时写出诊断信息。 您可以在控制器中实现以下 On<Filter> 方法:

  • OnAuthorization

  • OnException

  • OnActionExecuting

  • OnActionExecuted

  • OnResultExecuting

  • OnResultExecuted

如何创建筛选器

您可以通过下列方式创建筛选器:

  • 重写控制器的一个或多个 On<Filter> 方法。

  • 创建派生自 ActionFilterAttribute 的特性类,并将该特性应用于控制器或操作方法。

  • 在筛选器提供程序(FilterProviders 类)中注册筛选器。

  • 使用 GlobalFilterCollection 类注册全局筛选器。

筛选器可实现抽象 ActionFilterAttribute 类。 某些筛选器(如 AuthorizeAttribute)直接实现 FilterAttribute 类。 授权筛选器始终在操作方法运行之前和所有其他筛选器类型运行之前调用。 诸如OutputCacheAttribute 之类的其他操作筛选器将实现抽象 ActionFilterAttribute 类,这使操作筛选器可在操作方法运行之前或之后运行。

您可以以声明方式将筛选器特性与操作方法或控制器结合使用。 如果特性标记控制器,则操作筛选器将应用于该控制器中的所有操作方法。

筛选器提供程序

可注册多个筛选器提供程序。 使用静态 Providers 属性注册筛选器提供程序。 GetFilters(ControllerContext, ActionDescriptor) 方法会将所有提供程序中的筛选器聚合到单个列表中。 提供程序可按任意顺序注册;它们的注册顺序对筛选器的运行顺序没有影响。

默认情况下,ASP.NET MVC 将注册以下提供程序:

  • Filters ,用于全局筛选器。

  • FilterAttributeFilterProvider ,用于筛选器特性。

  • ControllerInstanceFilterProvider ,用于控制器实例。

GetFilters 方法将返回服务定位器中的所有 IFilterProvider 实例。

筛选器顺序

筛选器按下列顺序运行:

  1. 授权筛选器

  2. 操作筛选器

  3. 响应筛选器

  4. 异常筛选器

例如,授权筛选器最先运行,异常筛选器最后运行。 在每个筛选器类型中,Order 值将指定运行顺序。 在每个筛选器类型和顺序中,Scope 模拟值将指定筛选器的顺序。 此枚举将定义以下筛选器范围值(按它们运行的顺序):

  1. First

  2. Global

  3. Controller

  4. Action

  5. Last

例如,其 Order 属性设置为 0 且筛选器范围设置为 First 的操作筛选器将在其 Order 属性设置为 0 且筛选器范围设置为 Action 的操作筛选器之前运行。

未定义具有相同类型、顺序和范围的筛选器的执行顺序。

OnActionExecuting(ActionExecutingContext) OnResultExecuting(ResultExecutingContext) 和 OnAuthorization(AuthorizationContext) 筛选器以正向顺序运行。OnActionExecuted(ActionExecutedContext) OnResultExecuting(ResultExecutingContext) 和 OnException(ExceptionContext) 筛选器以反向顺序运行。

取消筛选器执行

您可以将 Result 属性设置为非 null 值,以在 OnActionExecuting 和 OnResultExecuting 方法中取消筛选器执行。 任何挂起的 OnActionExecuted 和 OnActionExecuting 筛选器都不会调用,且调用程序不会为取消的筛选器和挂起的筛选器调用 OnActionExecuted 方法。 上次运行的筛选器的 OnActionExecuted 筛选器将会运行。 所有 OnResultExecuting 和 OnResultExecuted 筛选器都会运行。

例如,假定有一个具有主控制器和简单控制器的 ASP.NET MVC 应用程序。 对该应用程序应用一个全局请求计时筛选器。 请求计时筛选器将实现四个操作和结果筛选器(OnActionExecutingOnActionExecutedOnResultExecuting 和 OnResultExecuted)。 该应用程序中的每个筛选器方法都会编写跟踪信息,其中包含筛选器名称、控制器名称、操作名称和筛选器类型。 在这种情况下,筛选器类型是请求计时筛选器。 对 Home 控制器的 Index 操作的请求将在跟踪侦听器(调试窗口)中显示以下输出。

构建自定义动作过滤器

public class LoggingFilterAttribute : ActionFilterAttribute
    {
        private string _logMessage = string.Empty;

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            string message = String.Format(
               "Executing {0}.{1} with {2} parameters<br>",
               filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
               filterContext.ActionDescriptor.ActionName,
               filterContext.ActionParameters.Count);

            _logMessage = message;
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            string message = String.Format(
                "Finished executing action {0}.{1} with result {2}<br>",
                filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
                filterContext.ActionDescriptor.ActionName,
                filterContext.Result.GetType().Name);

            _logMessage = _logMessage + message;
        }

        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            string message = String.Format(
                "Starting to execute result {0}<br>",
                filterContext.Result.GetType().Name);

            _logMessage = _logMessage + message;
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            string message = String.Format(
                "Finished executing {0}<br>",
                filterContext.Result.GetType().Name);

            filterContext.HttpContext.Response.Output.WriteLine(
                "<div class=\"trace page\">" + _logMessage + message + "</div>");
        }
    }

控制器范围的过滤器

protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            ViewResult result = filterContext.Result as ViewResult;
            if (result != null)
            {
                string title = (string)result.ViewData["Title"];
                if (!String.IsNullOrEmpty(title))
                    title = "Home - " + title;
                else
                    title = "Home";
                result.ViewData["Title"] = title;
            }
            base.OnActionExecuted(filterContext);
        }

使用动作过滤器

取消动作

filter:

public class RequiredStepAttribute : ActionFilterAttribute
    {
        public string PreviousStep { get; set; }
        public bool FlowStart { get; set; }

        public RequiredStepAttribute()
        {
            FlowStart = false;
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (FlowStart)
                return;
            string previousStep =
                filterContext.Controller.TempData["PreviousStep"] as string;

            if (!String.IsNullOrEmpty(previousStep))
            {
                if (!previousStep.Equals(PreviousStep))
                    filterContext.Result = new ViewResult()
                    {
                        ViewName = "FlowError"
                    };
            }
            else
            {
                filterContext.Result = new ViewResult()
                {
                    ViewName = "FlowError"
                };
            }
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            filterContext.Controller.TempData["PreviousStep"] =
                filterContext.ActionDescriptor.ActionName;
        }
    }

action:

public class CheckOutController : Controller
    {
        [RequiredStep(FlowStart = true)]
        public ActionResult Confirm()
        {
            return View();
        }

        [RequiredStep(PreviousStep = "Confirm")]
        public ActionResult ExecuteOrder()
        {
            return RedirectToAction("ThankYou");
        }

        [RequiredStep(PreviousStep = "ExecuteOrder")]
        public ActionResult ThankYou()
        {
            return View();
        }
    }

 

为视图添加更多的数据

为母版页提供数据

filter:

public class FooterFilterAttribute : ActionFilterAttribute
    {
        public String Footer { get; set; }

        public override void OnActionExecuted(
            ActionExecutedContext filterContext)
        {
            ViewResult result = filterContext.Result as ViewResult;
            if (result != null)
                result.ViewData["Footer"] = Footer;
        }
    }

action:

[FooterFilter(Footer = "About Page")]
        public ActionResult About()
        {
            return View();
        }

为局部视图生成数据

filter:

public class AddCategoriesFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (!filterContext.Canceled)
            {
                ViewResult result = filterContext.Result as ViewResult;
                if (result != null)
                {
                    BlogPage page = result.ViewData.Model as BlogPage;
                    if (page != null)
                    {
                        page.Categories = Category.Create10Categories(
                            page.Categories);
                    }
                }
            }
        }
    }

action:

[AddCategoriesFilter]
        public ActionResult Index()
        {
            BlogPage page = new BlogPage();
            page.Posts = new List<Post>();
            for (int i = 1; i <= 10; i++)
            {
                page.Posts.Add(new Post()
                {
                    Id = i,
                    Title = "Title " + i,
                    Body = GetBigBody(i)
                });
            }

            return View(page);
        }

        [AddCategoriesFilter]
        public ActionResult Post(int id)
        {
            ViewData["Title"] = "Post Title " + id;

            BlogPage page = new BlogPage();
            page.Posts = new List<Post>();
            page.Posts.Add(new Post()
            {
                Title = "Title " + id,
                Body = GetBigBody(id)
            });
            return View(page);
        }

 控制action参数:

filter:

 public class FormValueExistsAttribute : FilterAttribute, IActionFilter
    {
        private readonly string _name;
        private readonly string _value;
        private readonly string _actionParameterName;

        public FormValueExistsAttribute(string name, string value, string actionParameterName)
        {
            _name = name;
            _value = value;
            _actionParameterName = actionParameterName;
        }

        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
        }

        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var formValue = filterContext.RequestContext.HttpContext.Request.Form[_name];
            filterContext.ActionParameters[_actionParameterName] = !string.IsNullOrEmpty(formValue) &&
                                                                   formValue.ToLower().Equals(_value.ToLower());
        }
    }

action:

[HttpPost, FormValueExists("save", "save-continue", "continueEditing")]
        public ActionResult Create(CategoryModel model, bool continueEditing)
        {
  
}

验证action

filter:

public class FormValueRequiredAttribute : ActionMethodSelectorAttribute
    {
        private readonly string[] _submitButtonNames;
        private readonly FormValueRequirement _requirement;

        public FormValueRequiredAttribute(params string[] submitButtonNames):
            this(FormValueRequirement.Equal, submitButtonNames)
        {
        }

        public FormValueRequiredAttribute(FormValueRequirement requirement, params string[] submitButtonNames)
        {
            //at least one submit button should be found
            this._submitButtonNames = submitButtonNames;
            this._requirement = requirement;
        }

        public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
        {
            foreach (string buttonName in _submitButtonNames)
            {
                try
                {
                    string value = "";
                    switch (this._requirement)
                    {
                        case FormValueRequirement.Equal:
                            {
                                //do not iterate because "Invalid request" exception can be thrown
                                value = controllerContext.HttpContext.Request.Form[buttonName];
                            }
                            break;
                        case FormValueRequirement.StartsWith:
                            {
                                foreach (var formValue in controllerContext.HttpContext.Request.Form.AllKeys)
                                {
                                    if (formValue.StartsWith(buttonName, StringComparison.InvariantCultureIgnoreCase))
                                    {
                                        value = controllerContext.HttpContext.Request.Form[formValue];
                                        break;
                                    }
                                }
                            }
                            break;
                    }
                    if (!String.IsNullOrEmpty(value))
                        return true;
                }
                catch (Exception exc)
                {
                    //try-catch to ensure that 
                    Debug.WriteLine(exc.Message);
                }
            }
            return false;
        }
    }

    public enum FormValueRequirement
    {
        Equal,
        StartsWith
    }

action:

[HttpPost]
        [FormValueRequired("save")]
        public ActionResult ProductAddPopup(string btnId, string formId, CategoryModel.AddCategoryProductModel model)
        {}
 
 
以下片段是摘自:http://www.cnblogs.com/QLeelulu/archive/2008/10/13/1310419.html的防盗链filter应用
public class AntiOutSiteLinkAttribute : ActionFilterAttribute, IActionFilter
{
    public AntiOutSiteLinkAttribute(FileType fileType)
    {
        this.FileType = fileType;
    }

    /// <summary>
    /// 请求的文件类型.(文件或图片)
    /// </summary>
    public FileType FileType { get; set; }

    #region IActionFilter 成员

    void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpContextBase httpContext = filterContext.HttpContext;
        if (null != httpContext.Request.UrlReferrer)
        {
            string serverDomain = httpContext.Request.Url.Host;
            string refDomain = httpContext.Request.UrlReferrer.Host;
            if (GetRootDomain(refDomain).Equals(GetRootDomain(serverDomain), StringComparison.OrdinalIgnoreCase))
            {
                return;//如果根域名相同就返回
            }
        }

        ContentResult cr = new ContentResult();
        if (FileType == FileType.Image)
        {
            cr.ContentType = "image/jpeg";
            FileInfo fi = new FileInfo(httpContext.Server.MapPath("~/Content/images/outsitelink.jpg"));
            if (fi.Exists)
            {
                httpContext.Response.WriteFile(fi.FullName);
            }
            else
            {
                Bitmap bmp = new Bitmap(200, 50);
                Graphics g = Graphics.FromImage(bmp);

                g.FillRectangle(Brushes.White, 0, 0, 200, 50);
                g.DrawString("请不要盗链", new Font("Arial", 15), Brushes.Red, new PointF(0, 0));

                bmp.Save(httpContext.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Gif);
            }
        }
        else
        {
            cr.ContentType = "text/html";
            cr.Content = string.Format("请不要盗链。返回<a href='{0}'>{1}</a>", Utils.AbsoluteWebRoot, BlogSettings.Instance.Name);
        }
        //将当前的上下文的ActionResult设置为我们的cr(ContentResult)
        filterContext.Result = cr;
    }

    #endregion

    /// <summary>
    /// 获取网站的根域名
    /// </summary>
    /// <param name="domain">网站的域名,不带"Http://"</param>
    /// <returns></returns>
    private string GetRootDomain(string domain)
    {
        if (string.IsNullOrEmpty(domain))
        {
            throw new ArgumentNullException("参数'domain'不能为空");
        }
        string[] arr = domain.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
        if (arr.Length <= 2)
        {
            return domain;
        }
        else
        {
            return arr[arr.Length - 2] + "." + arr[arr.Length - 1];
        }
    }
}

public enum FileType
{
    File = 1,
    Image
}

 
然后我们建立一个用于处理文件请求的Controller,并应用上我们刚才建立的Filter:

public class FilesController : BaseController
{
    [AntiOutSiteLink(FileType.Image)]
    public ActionResult Image(string file)
    {
        return Content("Image From 4mvc");
    } 

    [AntiOutSiteLink(FileType.File)]
    public ActionResult File(string file)
    {
        return Content("File From 4mvc");
    }
} 

 AjaxAuthorizeAttribute

public class AjaxAuthorizeAttribute : AuthorizeAttribute {

        protected override void HandleUnauthorizedRequest(AuthorizationContext context) {

            if (context.HttpContext.Request.IsAjaxRequest()) {
                UrlHelper urlHelper = new UrlHelper(context.RequestContext);
                context.Result = new JsonResult {
                    Data = new {
                        Error = "NotAuthorized",
                        LogOnUrl = urlHelper.Action("LogOn", "Account")
                    }, JsonRequestBehavior = JsonRequestBehavior.AllowGet};
            } else {
                base.HandleUnauthorizedRequest(context);
            }
        }
    }

ExceptionAttribute

public class ExceptionAttribute: FilterAttribute, IExceptionFilter {

        public void OnException(ExceptionContext filterContext) {

            if (!filterContext.ExceptionHandled && 
                filterContext.Exception is NullReferenceException) {

                filterContext.Result = new RedirectResult("/ErrorPage.html");
                filterContext.ExceptionHandled = true;
            }
        }
    }