ASP.NET Core MVC基础知识

本章将和大家分享ASP.NET Core MVC的一些基础知识,包括Action接收参数、Action向视图传值、常用过滤器的使用、布局页、分部视图、视图组件ViewComponent的使用以及在视图中如何导入公共命名空间等,希望通过本文的分享能够对初学者有所帮助。下面我们直接进入主题。

首先来看一下我们的解决方案:

本Demo的Web项目为ASP.NET Core Web 应用程序(目标框架为.NET Core 3.1) MVC项目。

1、Action接收参数及Action向视图传值

下面直接通过代码加注释的方式来讲解:

Home控制器:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;

using MvcDemo.Models;
using MvcDemo.Attributes;

namespace MvcDemo.Controllers
{
    public class HomeController : BaseController
    {
        private readonly ILogger<HomeController> _logger;
        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        //Action接收参数方式1
        //id 指的是路由中的占位符{id},如果有值则id就是那个值
        //myId是URL中的值或者表单中name=myId的那个值
        public async Task<IActionResult> Index(int? id, int? myId)
        {
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}开始执行 => This is Home/Index Action");
            await Task.Delay(1000);
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}执行结束 => This is Home/Index Action");

            //Action接收参数方式2
            //var value1 = Request.Query["myId"];
            //var value2 = Request.Form["myId"];
            //var value3 = Request.Form["name"].ToArray(); //接收多个值
            //var value4 = RouteData.Values["id"]; //获取路由占位符的值

            var listStu = new List<Student>(){
                new Student(){
                    Name = "张三",
                    Sex = "",
                    Age = 16
                },
                new Student(){
                    Name = "李四",
                    Sex = "",
                    Age = 17
                },
                new Student(){
                    Name = "小美",
                    Sex = "",
                    Age = 18
                }
            };

            //Action向View传值的4种方式
            //ViewData["键"] = 值; 此方式View在接收Action传过去的值时
            //1、如果是字符串或者是数值型则可以直接使用,否则需要转换成对应的类型再进行处理
            ViewData["Stu"] = listStu[0];

            //2、ViewBag 动态类型,ViewBag底层用的是ViewData,所以这2个是相通的,设置其中一个值后另外一个也就有值了
            ViewBag.Title = "Home Index Page";
            ViewBag.ListStu = listStu;

            //3、TempData["键"] = 值;
            TempData["Love"] = 520;

            //4、View(obj); View接收的就是Model,可以指定Model的数据类型,这样子就可以直接使用
            return View(listStu[2]);
        }

        //Action接收参数方式3
        //使用实体接收post提交的参数
        public IActionResult MyLayoutDemo(Student stu)
        {
            return View();
        }

        /// <summary>
        /// 分部视图Demo
        /// 分部视图微软官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/partial?view=aspnetcore-5.0
        /// </summary>
        /// <returns></returns>
        public IActionResult MyPartialDemo()
        {
            ViewBag.Stu = new Student()
            {
                Name = "王五",
                Sex = "",
                Age = 22
            };
            ViewBag.FatherViewVal = "我是来自父视图ViewData里面的值";
            return View();
        }

        #region Ajax交互

        //Action接收参数方式4
        //参数列表IFormCollection对象,这种方式只能接收到表单提交的参数,该方式不常用
        public async Task<JsonResult> GetListDataAsync(IFormCollection form)
        {
            var myId = form["myId"].ToString();
            await Task.Delay(500);
            var result = new
            {
                code = 1,
                total = 10,
                data = new Student()
                {
                    Id = myId,
                    Name = "钱七",
                    Sex = "",
                    Age = 22
                },
                msg = "获取成功"
            };
            return Json(result);
        }

        #endregion Ajax交互
    }
}

对应的Home/Index视图:

@{
    Layout = null;
    var value = "15345678910";
    if (value.IsMobile())
    {
        Console.WriteLine("This is Home/Index View");
    }
}

@* 给Model指定数据类型 *@
@model Student

@{
    Student stu = ViewData["Stu"] as Student;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewData["Title"]</title>
</head>
<body>
    <div>
        @foreach (var item in ViewBag.ListStu)
        {
            @:我是foreach <br />
            @Html.Raw("我是Html.Raw<br />")
            <text>
                我是foreach内部的text
            </text>
            <p>
                姓名:@(item.Name),性别:@(item.Sex),年龄:@(item.Age)
            </p>
            <p>
                我是foreach内部p标签内的字符串
            </p>
            <hr />
        }
        <p>
            我是TempData["Love"]:@TempData["Love"]
        </p>
        <p>
            我是ViewData["Stu"]:@((ViewData["Stu"] as Student).Name)
        </p>
        <p>
            我是Model.Name:@Model.Name
        </p>
        <p>
            我是ViewBag.ListStu[0].Name:@ViewBag.ListStu[0].Name
        </p>
        <p>
            我是stu.Name:@(stu.Name)
        </p>
    </div>
</body>
</html>

访问 /Home/Index 运行结果如下:

此外Action向视图传值还有一种特殊的情况就是匿名类型,具体的传值方式可参考博文:https://www.cnblogs.com/xyh9039/p/11348684.html

2、 在视图中如何导入公共命名空间

MVC框架给我们提供了一个特殊的视图,叫 _ViewImports.cshtml,可以在该视图当中导入公共命名空间。

它的作用域是_ViewImports.cshtml视图所在的Views文件夹下的所有视图。

另外我们还可以看到一个特殊的视图,叫_ViewStart.cshtml,如下图:

其作用就是在所有的View在呈现之前都会先执行_ViewStart.cshtml里面的代码,但是有一种特殊情况就是如果一个View是按照分部视图方式输出的,则不会触发_ViewStart.cshtml里面的代码。

3、 分部视图

首先来看下目录结构:

其中Action如下所示:

/// <summary>
/// 分部视图Demo
/// 分部视图微软官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/partial?view=aspnetcore-5.0
/// </summary>
/// <returns></returns>
public IActionResult MyPartialDemo()
{
    ViewBag.Stu = new Student()
    {
        Name = "王五",
        Sex = "",
        Age = 22
    };
    ViewBag.FatherViewVal = "我是来自父视图ViewData里面的值";
    return View();
}

对应的视图如下:

其中_MyPartial.cshtml视图:

@model Student

<h2>我是_MyPartial.cshtml</h2>
<p style="color:red;">
    @(Model.Name),@(Model.Sex),@(Model.Age) <br />
    @ViewBag.Name <br />
    @ViewData["Name"] <br />
    @ViewBag.FatherViewVal
</p>

其中MyPartialDemo.cshtml视图:

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>MyPartialDemo</title>
</head>
<body>
    <div>
        <p>
            @* 分部视图 *@
            我是Html.PartialAsync:
            @await Html.PartialAsync("_MyPartial", ViewBag.Stu as Student, new ViewDataDictionary(this.ViewData) { { "Name", "老李" } })
        </p>
        <hr />
        <p>
            @* 分部视图 *@
            @* 调用无返回值的方法时必须放在花括号内部 *@
            我是Html.RenderPartialAsync:
            @{await Html.RenderPartialAsync("_MyPartial", ViewBag.Stu as Student, new ViewDataDictionary(this.ViewData) { { "Name", "老王" } });}
        </p>
        <hr />
        <p>
            @* 视图组件 *@
            我是Component.InvokeAsync:
            @await Component.InvokeAsync("StuInfo", new { id = "10086" })
        </p>
    </div>
</body>
</html>

访问 /Home/MyPartialDemo 运行结果如下:

分部视图微软官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/partial?view=aspnetcore-5.0

分部视图的视图搜索路径

//按名称(无文件扩展名)引用分部视图,则按所述顺序搜索以下位置
//1、/Areas/<Area-Name>/Views/<Controller-Name>
//2、/Areas/<Area-Name>/Views/Shared
//3、/Views/Shared
//4、/Pages/Shared
@await Html.PartialAsync("_PartialName")


//存在文件扩展名时
//该视图必须与调用分部视图的文件位于同一文件夹中
@await Html.PartialAsync("_PartialName.cshtml")


//从应用程序根目录引用分部视图
//以波形符斜杠 (~/) 或斜杠 (/) 开头的路径指代应用程序根目录
@await Html.PartialAsync("~/Views/Folder/_PartialName.cshtml")
@await Html.PartialAsync("/Views/Folder/_PartialName.cshtml")


//引用使用相对路径的分部视图
@await Html.PartialAsync("../Account/_LoginPartial.cshtml")

4、视图组件

可以看到上一步在讲解分部视图的时候里面就有调用了一个视图组件

//调用视图组件 
@await Component.InvokeAsync("StuInfo", new { id = "10086" })

接下来我们就来看下如何实现,同样我们先来看下目录结构:

下面我们直接来看代码:

视图组件类StuInfoViewComponent.cs,名称以ViewComponent后缀结尾

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

using MvcDemo.Models;

namespace MvcDemo.ViewComponents
{
    /// <summary>
    /// 学生信息视图组件
    /// 视图组件微软官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/view-components?view=aspnetcore-5.0
    /// </summary>
    [ViewComponent(Name = "StuInfo")] //[ViewComponent] 属性可以更改用于引用视图组件的名称。
    public class StuInfoViewComponent : ViewComponent
    {
        private readonly ILogger<StuInfoViewComponent> _logger;
        public StuInfoViewComponent(ILogger<StuInfoViewComponent> logger)
        {
            _logger = logger;
        }

        public async Task<IViewComponentResult> InvokeAsync(string id)
        {
            var stu = new Student
            {
                Id = id,
                Name = "隔壁老王",
                Sex = "",
                Age = 32
            };

            #region 摘自官网

            /*
                视图搜索路径
                运行时在以下路径中搜索视图:
                /Views/{Controller Name}/Components/{View Component Name}/{View Name}
                /Views/Shared/Components/{View Component Name}/{View Name}
                /Pages/Shared/Components/{View Component Name}/{View Name}
            */

            #endregion 摘自官网

            return View(stu);
        }
    }
}

Default.cshtml视图:

@* 视图组件 *@
@model Student

<h2>我是StuInfoViewComponent</h2>
<div>
    StuId=@(Model.Id),StuName=@(Model.Name),StuAge=@(Model.Age),StuSex=@(Model.Sex)
</div>

调用视图组件:

//调用视图组件
@await Component.InvokeAsync("StuInfo", new { id = "10086" })

视图组件微软官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/view-components?view=aspnetcore-5.0

其中值得注意的是:

1、[ViewComponent] 属性可以更改用于引用视图组件的名称。

2、视图组件的默认视图名称为“Default”,这意味着视图文件通常命名为“Default.cshtml”。 可以在创建视图组件结果或调用 View 方法时指定不同的视图名称。

3、官方建议将视图文件命名为 Default.cshtml 并使用 Views/Shared/Components/{View Component Name}/{View Name} 路径。

4、视图搜索路径

/*
    视图搜索路径
    运行时在以下路径中搜索视图:
    /Views/{Controller Name}/Components/{View Component Name}/{View Name}
    /Views/Shared/Components/{View Component Name}/{View Name}
    /Pages/Shared/Components/{View Component Name}/{View Name}
*/

5、布局页Layout

先来看下目录结构:

下面我们直接上代码:

 _Header.cshtml视图:

<script src="~/js/jquery-3.6.0.min.js"></script>

_MyLayout.cshtml自定义布局页:

@{
    //指定布局页
    //默认使用 /Views/Shared/ 目录下的_Layout布局页
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@(ViewBag.Title)</title>

    @* 加载分部视图 *@
    @await Html.PartialAsync("_Header")

    @* 占位符写法1 *@
    @RenderSection("header", required: false)
    @* 占位符写法2 *@
    @await RenderSectionAsync("header2", required: false)
</head>
<body>
    <h1>我是_MyLayout.cshtml</h1>
    <div>
        @* 将来内容将填充到这里 *@
        @RenderBody()
    </div>

    @RenderSection("footer", required: false)
    @await RenderSectionAsync("footer2", required: false)
</body>
</html>

为视图MyLayoutDemo.cshtml指定布局页:

@{
    ViewBag.Title = "MyLayoutDemo";

    //指定布局页
    //此发现过程与用于发现分部视图的过程相同
    //Layout = "~/Views/Shared/_MyLayout.cshtml"; //指定布局页
    Layout = "_MyLayout"; //指定布局页
}

@* 填充布局页里面对应的占位符 *@
@section header{
    <script type="text/javascript">

    </script>
}

<h2>MyLayoutDemo</h2>

@* 填充布局页里面对应的占位符 *@
@section footer2{
    <script type="text/javascript">

    </script>
}

访问 /Home/MyLayoutDemo 运行结果如下:

布局页微软官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/layout?view=aspnetcore-5.0

其中值得注意的是:

指定的布局可以使用完整路径(例如 /Pages/Shared/_Layout.cshtml 或 /Views/Shared/_Layout.cshtml)或部分名称(示例:_Layout)。

如果提供了部分名称, Razor 视图引擎将使用其标准发现进程搜索布局文件。 首先搜索处理程序方法(或控制器)所在的文件夹,然后搜索 Shared 文件夹。

此发现过程与用于发现分部视图的过程相同。 

6、常用过滤器的使用

老样子,我们首先先来看下相应的目录机构:

下面我们重点来看下基类和过滤器:

基类BaseController如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

using MvcDemo.Attributes;

namespace MvcDemo.Controllers
{
    /// <summary>
    /// 自定义控制器的基类
    /// </summary>
    public class BaseController : Controller
    {
        /// <summary>
        /// 是否需要登入验证
        /// </summary>
        protected bool IsNeedLogin = true;

        /// <summary>
        /// 在Action执行前触发(用于让子类重写)
        /// </summary>
        /// <param name="context">Action执行前上下文对象</param>
        protected virtual void OnSubActionExecuting(ActionExecutingContext context)
        {

        }

        /// <summary>
        /// 在Action执行前触发(如果继承该类的子类也重写了该方法,则先执行子类的方法,再执行父类的方法)
        /// </summary>
        /// <param name="context">Action执行前上下文对象</param>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            base.OnActionExecuting(context);
            OnSubActionExecuting(context); //先执行子类的

            //获取请求进来的控制器与Action
            var controllerActionDescriptor =
                context.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor;

            #region 【权限验证】【登入验证】

            //获取区域名称
            var areaName = context.ActionDescriptor.RouteValues["area"] ?? "";
            areaName = (string)context.RouteData.Values["area"];

            //获取控制器名称
            var controllerName = context.ActionDescriptor.RouteValues["controller"];
            controllerName = controllerActionDescriptor.ControllerName;
            controllerName = context.RouteData.Values["controller"].ToString();

            //获取action名称
            var actionName = context.ActionDescriptor.RouteValues["action"];
            actionName = controllerActionDescriptor.ActionName;
            actionName = context.RouteData.Values["action"].ToString();

            //获取当前的请求方式:Get或者Post
            string requestMethod = context.HttpContext.Request.Method.ToLower();

            //获取路由占位符对应的值
            object currId = context.RouteData.Values["id"];

            //获取当前请求URL参数
            var value =
                context.HttpContext.Request.Query.ContainsKey("key")
                ? context.HttpContext.Request.Query["key"].ToString()
                : "";

            //获取Action参数值
            IDictionary<string, object> actionArguments = context.ActionArguments;
            if (actionArguments != null)
            {
                foreach (KeyValuePair<string, object> item in actionArguments)
                {
                    var key = item.Key; //参数名
                    var val = item.Value; //参数值
                }
            }

            ParamsFilter(context); //参数过滤

            //判断当前所请求的控制器上是否有打上指定的特性标签
            if (controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(SkipLoginValidateAttribute), false))
            {
                //有的话就不进行相关操作【例如:不进行登入验证】
                //return;
            }

            //获取当前所请求的Action上指定的特性标签
            object[] arrObj1 = controllerActionDescriptor.MethodInfo.GetCustomAttributes(typeof(SkipLoginValidateAttribute), false);
            //获取当前所请求的Action上所有的特性标签
            object[] arrObj2 = controllerActionDescriptor.MethodInfo.GetCustomAttributes(false);

            //判断当前所请求的Action上是否有打上指定的特性标签
            if (controllerActionDescriptor.MethodInfo.IsDefined(typeof(SkipLoginValidateAttribute), false) || !IsNeedLogin)
            {
                //有的话就不进行相关操作【例如:不进行登入验证】
                return;
            }

            #endregion 【权限验证】【登入验证】

            //【权限验证】【登入验证】逻辑
            //To Do Something
        }

        #region 参数过滤

        /// <summary>
        /// 参数过滤
        /// </summary>
        void ParamsFilter(ActionExecutingContext context)
        {
            //获取参数集合
            var parameters = context.ActionDescriptor.Parameters;
            //遍历参数集合
            foreach (var param in parameters)
            {
                if (!context.ActionArguments.ContainsKey(param.Name))
                {
                    continue;
                }

                if (context.ActionArguments[param.Name] == null)
                {
                    continue;
                }

                //当参数是字符串
                if (param.ParameterType.Equals(typeof(string)))
                {
                    //业务处理
                    var value = context.ActionArguments[param.Name].ToString();
                    context.ActionArguments[param.Name] = value.Replace(" ", "");
                }
                else if (param.ParameterType.Equals(typeof(string[]))) //如果是字符串数组则遍历每一个成员
                {
                    var arrStrs = context.ActionArguments[param.Name] as string[];
                    if (arrStrs != null && arrStrs.Length > 0)
                    {
                        for (int i = 0; i < arrStrs.Length; i++)
                        {
                            arrStrs[i] = arrStrs[i].Replace(" ", ""); //业务处理
                        }
                        context.ActionArguments[param.Name] = arrStrs;
                    }
                }
                else if (param.ParameterType.IsClass) //当参数是一个实体
                {
                    ModelParamsFilter(param.ParameterType, context.ActionArguments[param.Name]);
                }
            }
        }

        /// <summary>
        /// 实体参数过滤
        /// </summary>
        object ModelParamsFilter(Type type, object obj)
        {
            if (obj == null)
            {
                return obj;
            }

            var properties = type.GetProperties();
            foreach (var item in properties)
            {
                if (item.GetValue(obj) == null)
                {
                    continue;
                }

                var attribute = typeof(SkipLoginValidateAttribute);
                if (item.IsDefined(attribute, false)) //特殊参数处理
                {
                    continue;
                }

                //当参数是字符串
                if (item.PropertyType.Equals(typeof(string)))
                {
                    //业务处理
                    string value = item.GetValue(obj).ToString();
                    item.SetValue(obj, value.Replace(" ", ""));
                }
                else if (item.PropertyType.Equals(typeof(string[]))) //如果是字符串数组则遍历每一个成员
                {
                    var arrStrs = item.GetValue(obj) as string[];
                    if (arrStrs != null && arrStrs.Length > 0)
                    {
                        for (int i = 0; i < arrStrs.Length; i++)
                        {
                            arrStrs[i] = arrStrs[i].Replace(" ", ""); //业务处理
                        }
                        item.SetValue(obj, arrStrs);
                    }
                }
                else if (item.PropertyType.IsClass) //当参数是一个实体
                {
                    item.SetValue(obj, ModelParamsFilter(item.PropertyType, item.GetValue(obj)));
                }
            }

            return obj;
        }

        #endregion 参数过滤

        /// <summary>
        /// 在Action执行后触发(如果继承该类的子类也重写了该方法,则先执行子类的方法,再执行父类的方法)
        /// </summary>
        /// <param name="context">Action执行后上下文对象</param>
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            base.OnActionExecuted(context);
        }

        /// <summary>
        /// 在Action执行前触发
        /// 注意:OnActionExecuting和OnActionExecuted是同步方法,而OnActionExecutionAsync是异步方法,同步和异步是不能同时都执行的,如果都写上了那只会执行异步的方法。
        /// 官网相关资料:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0
        /// </summary>
        /// <param name="context"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            //await base.OnActionExecutionAsync(context, next);

            #region 摘自官网

            /*
                // Do something before the action executes.
                // next() calls the action method.
                var resultContext = await next();
                // Do something after the action executes. 
            */

            #endregion 摘自官网

            //Action执行之前的业务处理
            Console.WriteLine("======================================================================================");
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}开始执行 => This is BaseController/OnActionExecutionAsync");

            Stopwatch sw = new Stopwatch();
            sw.Start();
            ActionExecutedContext resultContext = await next(); //执行Action
            sw.Stop();

            //Action执行之后的业务处理
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}执行结束 => This is BaseController/OnActionExecutionAsync  处理花费时间:{sw.ElapsedMilliseconds}ms");
        }
    }
}

自定义Action过滤器如下所示:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc.Filters;
using MvcDemo.Attributes;

namespace MvcDemo.Filters
{
    /// <summary>
    /// 自定义Action过滤器(也可通过BaseController实现过滤器功能)
    /// </summary>
    public class MyActionFilterAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// 在Action执行前触发(如果在控制器中也重写了该方法,则先执行控制器中的方法,再执行过滤器中的方法)
        /// </summary>
        /// <param name="filterContext">Action执行前上下文对象</param>
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);

            //获取请求进来的控制器与Action
            var controllerActionDescriptor =
                filterContext.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor;

            #region 【权限验证】【登入验证】

            //获取区域名称
            var areaName = filterContext.ActionDescriptor.RouteValues["area"] ?? "";
            areaName = (string)filterContext.RouteData.Values["area"];

            //获取控制器名称
            var controllerName = filterContext.ActionDescriptor.RouteValues["controller"];
            controllerName = controllerActionDescriptor.ControllerName;
            controllerName = filterContext.RouteData.Values["controller"].ToString();

            //获取action名称
            var actionName = filterContext.ActionDescriptor.RouteValues["action"];
            actionName = controllerActionDescriptor.ActionName;
            actionName = filterContext.RouteData.Values["action"].ToString();

            //获取当前的请求方式:Get或者Post
            string requestMethod = filterContext.HttpContext.Request.Method.ToLower();

            //获取路由占位符对应的值
            object currId = filterContext.RouteData.Values["id"];

            //获取当前请求URL参数
            var value =
                filterContext.HttpContext.Request.Query.ContainsKey("key")
                ? filterContext.HttpContext.Request.Query["key"].ToString()
                : "";

            //获取Action参数值
            IDictionary<string, object> actionArguments = filterContext.ActionArguments;
            if (actionArguments != null)
            {
                foreach (KeyValuePair<string, object> item in actionArguments)
                {
                    var key = item.Key; //参数名
                    var val = item.Value; //参数值
                }
            }

            ParamsFilter(filterContext); //参数过滤

            //判断当前所请求的控制器上是否有打上指定的特性标签
            if (controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(SkipLoginValidateAttribute), false))
            {
                //有的话就不进行相关操作【例如:不进行登入验证】
                //return;
            }

            //获取当前所请求的Action上指定的特性标签
            object[] arrObj1 = controllerActionDescriptor.MethodInfo.GetCustomAttributes(typeof(SkipLoginValidateAttribute), false);
            //获取当前所请求的Action上所有的特性标签
            object[] arrObj2 = controllerActionDescriptor.MethodInfo.GetCustomAttributes(false);

            //判断当前所请求的Action上是否有打上指定的特性标签
            if (controllerActionDescriptor.MethodInfo.IsDefined(typeof(SkipLoginValidateAttribute), false))
            {
                //有的话就不进行相关操作【例如:不进行登入验证】
                return;
            }

            #endregion 【权限验证】【登入验证】

            //【权限验证】【登入验证】逻辑
            //To Do Something
        }

        #region 参数过滤

        /// <summary>
        /// 参数过滤
        /// </summary>
        void ParamsFilter(ActionExecutingContext filterContext)
        {
            //获取参数集合
            var parameters = filterContext.ActionDescriptor.Parameters;
            //遍历参数集合
            foreach (var param in parameters)
            {
                if (!filterContext.ActionArguments.ContainsKey(param.Name))
                {
                    continue;
                }

                if (filterContext.ActionArguments[param.Name] == null)
                {
                    continue;
                }

                //当参数是字符串
                if (param.ParameterType.Equals(typeof(string)))
                {
                    //业务处理
                    var value = filterContext.ActionArguments[param.Name].ToString();
                    filterContext.ActionArguments[param.Name] = value.Replace(" ", "");
                }
                else if (param.ParameterType.Equals(typeof(string[]))) //如果是字符串数组则遍历每一个成员
                {
                    var arrStrs = filterContext.ActionArguments[param.Name] as string[];
                    if (arrStrs != null && arrStrs.Length > 0)
                    {
                        for (int i = 0; i < arrStrs.Length; i++)
                        {
                            arrStrs[i] = arrStrs[i].Replace(" ", ""); //业务处理
                        }
                        filterContext.ActionArguments[param.Name] = arrStrs;
                    }
                }
                else if (param.ParameterType.IsClass) //当参数是一个实体
                {
                    ModelParamsFilter(param.ParameterType, filterContext.ActionArguments[param.Name]);
                }
            }
        }

        /// <summary>
        /// 实体参数过滤
        /// </summary>
        object ModelParamsFilter(Type type, object obj)
        {
            if (obj == null)
            {
                return obj;
            }

            var properties = type.GetProperties();
            foreach (var item in properties)
            {
                if (item.GetValue(obj) == null)
                {
                    continue;
                }

                var attribute = typeof(SkipLoginValidateAttribute);
                if (item.IsDefined(attribute, false)) //特殊参数处理
                {
                    continue;
                }

                //当参数是字符串
                if (item.PropertyType.Equals(typeof(string)))
                {
                    //业务处理
                    string value = item.GetValue(obj).ToString();
                    item.SetValue(obj, value.Replace(" ", ""));
                }
                else if (item.PropertyType.Equals(typeof(string[]))) //如果是字符串数组则遍历每一个成员
                {
                    var arrStrs = item.GetValue(obj) as string[];
                    if (arrStrs != null && arrStrs.Length > 0)
                    {
                        for (int i = 0; i < arrStrs.Length; i++)
                        {
                            arrStrs[i] = arrStrs[i].Replace(" ", ""); //业务处理
                        }
                        item.SetValue(obj, arrStrs);
                    }
                }
                else if (item.PropertyType.IsClass) //当参数是一个实体
                {
                    item.SetValue(obj, ModelParamsFilter(item.PropertyType, item.GetValue(obj)));
                }
            }

            return obj;
        }

        #endregion 参数过滤

        /// <summary>
        /// 在Action执行后触发(如果在控制器中也重写了该方法,则先执行过滤器中的方法,再执行控制器中的方法)
        /// </summary>
        /// <param name="filterContext">Action执行后上下文对象</param>
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            base.OnActionExecuted(filterContext);
        }

        /// <summary>
        /// 在Action返回前触发,执行View前
        /// </summary>
        /// <param name="filterContext">Action返回前上下文对象</param>
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            base.OnResultExecuting(filterContext);
        }

        /// <summary>
        /// 在Action返回后,View显示后触发
        /// </summary>
        /// <param name="filterContext">Action返回后上下文对象</param>
        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            base.OnResultExecuted(filterContext);
        }

        /// <summary>
        /// 在Action执行前触发
        /// 注意:OnActionExecuting和OnActionExecuted是同步方法,而OnActionExecutionAsync是异步方法,同步和异步是不能同时都执行的,如果都写上了那只会执行异步的方法。
        /// 官网相关资料:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0
        /// </summary>
        /// <param name="context"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            //await base.OnActionExecutionAsync(context, next);

            #region 摘自官网

            /*
                // Do something before the action executes.
                // next() calls the action method.
                var resultContext = await next();
                // Do something after the action executes. 
            */

            #endregion 摘自官网

            //Action执行之前的业务处理
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}开始执行 => This is MyActionFilterAttribute/OnActionExecutionAsync");

            Stopwatch sw = new Stopwatch();
            sw.Start();
            ActionExecutedContext resultContext = await next(); //执行Action
            sw.Stop();

            //Action执行之后的业务处理
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}执行结束 => This is MyActionFilterAttribute/OnActionExecutionAsync  处理花费时间:{sw.ElapsedMilliseconds}ms");
        }

        /// <summary>
        /// 在Result执行前触发
        /// 注意:OnResultExecuting和OnResultExecuted是同步方法,而OnResultExecutionAsync是异步方法,同步和异步是不能同时都执行的,如果都写上了那只会执行异步的方法。
        /// </summary>
        /// <param name="context"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
        {
            //await base.OnResultExecutionAsync(context, next);

            //Result执行之前的业务处理(View执行前)
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}开始执行 => This is MyActionFilterAttribute/OnResultExecutionAsync");

            Stopwatch sw = new Stopwatch();
            sw.Start();
            ResultExecutedContext resultContext = await next(); //执行Result(执行View)
            sw.Stop();

            //Result执行之后的业务处理(View执行后)
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}执行结束 => This is MyActionFilterAttribute/OnResultExecutionAsync  处理花费时间:{sw.ElapsedMilliseconds}ms");
        }
    }
}

在Startup.cs里面注册全局过滤器:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using MvcDemo.Filters;

namespace MvcDemo
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews(options =>
            {
                options.Filters.Add<MyActionFilterAttribute>(); //注册全局过滤器 任意控制器-Action
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "areas",
                    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

最后我们使用cmd命令行方式启动我们的Demo,并且访问 /Home/Index,看下最终控制台的输出结果:

关于过滤器的更多内容,有兴趣的可参考我之前写的另外一篇博文:https://www.cnblogs.com/xyh9039/p/14359665.html

至此本文就全部介绍完了,如果觉得对您有所启发请记得点个赞哦!!! 

 

Demo源码:

链接:https://pan.baidu.com/s/1EuNkf23sdDHhtFxERZxAvA 
提取码:2cyo

此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/15174104.html

版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改,谢谢!!!  

posted @ 2021-08-24 22:03  谢友海  阅读(2032)  评论(1编辑  收藏  举报