【ASP.NET Core】MVC控制器的各种自定义:特性化的路由规则

asp,net,core,mvc,控制器,各种,自定义,特性,路由,规则 · 浏览次数 : 2113

小编点评

**代码示例** ```csharp // 自定义控制器类 public class CustController : ControllerBase { public IActionResult Greeting() => Content("来自螃蟹精的问候") } // 自定义属性路由类 public class CustAttributeRouteModel : AttributeRouteModel { public Template Template { get; set; } } // 测试控制器类 public class HomeController : Controller { public IActionResult Index() { return Content("来自高达的问候"); } public IActionResult Hello() { return Content("来自西海龙王的问候"); } } // 测试属性路由类 public class PigController:ControllerBase { [Route("/haha/hehe")] public IActionResult Greeting() { return Content("来自猪的问候"); } } ``` **代码解析** 1. **`CustController`** 中没有使用 `[Route]` 特性,因此其程序集名称将以控制器名称为前缀,即 **`FlyApp/Cust`**。 2. **`CustAttributeRouteModel`** 继承了 `AttributeRouteModel`,并在其模板属性中设置了 **`Template`** 属性。 3. **`HomeController`** 中有一个带有 `[Route]` 特性的 **`Index`** 方法,其程序集名称为 **`FlyApp/Home/Index`**。 4. **`PigController`** 中有一个带有 `/haha/hehe` 的 **`Route`** 注解,其程序集名称为 **`FlyApp/Pig/haha/hehe`**。 5. 在代码中,如果发现控制器或操作方法上设置了多个 **`AttributeRouteModel`**,则使用 **`AttributeRouteModel.CombineTemplates()`** 方法自动拼接 URL。 6. **`CustAttributeRouteModel`** 的模板属性 **`Template`** 设置了 **`Template`** 属性的值,该属性将用于构建最终的 URL。 7. **`Configure`** 方法中注册了 **`CustController`** 和 **`PigController`**,并设置了程序集名称。 **注意** * 此代码示例假设程序集的名称为 **`FlyApp`**。 * 代码中没有使用任何特定命名空间,所以所有控制器和属性都以控制器名称为前缀。 * 属性路由的命名格式为 **`Assembly Name + Controller Name + Action Name`**。

正文

MVC的路由规则配置方式比较多,咱们用得最多的是两种:

A、全局规则。就是我们熟悉的”{controller}/{action}“。

app.MapControllerRoute(
        name: "bug",
        pattern: "{controller}/{action}"
    );
app.MapControllerRoute(
        name: "八阿哥",
        pattern: "app/{action}",
        defaults: new
        {
            controller = "Home"
        }
    );

其中,controller、action、area、page 这些字段名用于专属匹配。比如 controller 匹配控制器名称等。这个老周不必多说了,大伙伴们都知道。大括号({ })括起来的字段是全局路由。这些路由可以用于当前应用中所有未指定特性化路由的控制器。上面代码中第二条路由,由于URL模板缺少了 controller 字段,所以 defaults 参数要设定它调用的控制器是 Home。

B、特性化路由(局部路由)。此规则通过 [Route]、[HttpGet]、[HttpPost] 等特性类,在控制器类或方法上配置的路由规则。

[Route("abc")]
public class PigController:ControllerBase
{
    [Route("xyz")]
    public IActionResult Greeting()
    {
        return Content("来自猪的问候");
    }
}

这样的规则会进行合并。即控制器上的是”abc“,方法上是”xyz“,所以你要调用Greeting方法就要访问URL:

http://www.xxx.com/abc/xyz

如果控制器上没有 [Route],只有方法上有。

 public class PigController:ControllerBase
 {
     [Route("haha/hehe")]
     public IActionResult Greeting()
     {
         return Content("来自猪的问候");
     }
 }

这时候,要想访问 Greeting 方法,其URL变为:http://www.aaa.cc/haha/hehe

【总结】其实这个基于特性的路由规则是有规律的——合并模板原则。具体说就是:

1、如果控制器上有指定,就将控制器上的路由与各个方法上的路由合并;

2、如果控制器上未指定路由,那就用方法上的路由。

说白了,就是从外向内,层层合并

 

以上所说的都是大家熟悉的路由玩法,下面老周要说的这种玩法比较复杂,一般不用。

那什么情况下用?

1、你觉得个个控制器去加 [Route]、[HttpPost] 等太麻烦,想来个痛快的;

2、你想弄个前缀,但这个前缀可能不是固定的。比如,加个命名空间做前缀,像 http://www.yyy.cn/MyNamespace/MyController/MyAction/Other。这个命名空间的名称要通过编程,在程序运行的时候获取,而不是硬编码。

这样的话,就可以用到应用程序模型——其实我们这一系列文章都离不开应用程序模型,因为整个MVC应用程序的自定义方式都与其有关。

 所以这种方案也是通过实现自定义的约定接口来完成的,其中主要是用到 AttributeRouteModel 类。它的功能与直接用在控制器或方法上的 [Route] 特性差不多,只不过这个类能让我们通过编程的方式设置路由URL。也就是 Template 属性,它是一个字符串,跟 [Route] 中设置的URL一样的用途,比如

[Route("blogs/[controller]/[action]")]
public class KillerController : Controller ...

就相当于 AttributeRouteModel.Template = "blogs/[controller]/[action]"。在特性化的路由规则上,controller、action 这些字段都写在中括号里面。

下面老周就给大伙演示一下,主要实现:

1、以当前程序集的名称为URL前缀;

2、前缀后接控制器名称;

3、控制器名后面接操作方法名称。

假设当前程序集名为 MyHub,控制器名为 Home,操作方法为 Goodbye,那么,调用 Goodbye 方法的URL是:https://mycool.net/myhub/home/goodbye

这个都是应用程序在运行后自动设置的,要是程序集改名为 MyGooood,那么URL前缀就自动变为 /mygooood。

从以上分析看,此约定要改控制器的路由,也要改操作方法的路由,所以,实现的约定接口应为 IControllerModelConvention。下面是代码:

public class CustControllerConvension : IControllerModelConvention
{
    public void Apply(ControllerModel controller)
    {
        // 如果已存在可用的 Attribute Route,就跳过
        if (controller.Selectors.Any(s => s.AttributeRouteModel != null))
        {
            return;
        }
        // 程序集名称
        string assName = controller.ControllerType.Assembly.GetName().Name ?? "";
        // 除掉名称中的“.”
        assName = assName.Replace(".", "/");
        // 控制器名称
        string ctrlName = controller.ControllerName;

        // 至少要有一个Selector
        if (controller.Selectors.Count == 0)
        {
            controller.Selectors.Add(new());
        }
        // 先设置Controller上的路由
        foreach (var selector in controller.Selectors)
        {
            // Assembly name + controller name
            selector.AttributeRouteModel = new()
            {
                Template = AttributeRouteModel.CombineTemplates(assName, ctrlName)
            };
        }
        // 再设置Action上的路由
        foreach (var action in controller.Actions)
        {
            if (action.Selectors.Any(s => s.AttributeRouteModel != null))
            {
                // 如果已有Attribute route,就跳过
                continue;
            }
            // 至少得有一个Selector
            if (action.Selectors.Count == 0)
            {
                action.Selectors.Add(new SelectorModel());
            }
            foreach (var selector in action.Selectors)
            {
                // Action的名字作为URL的一部分
                selector.AttributeRouteModel = new()
                {
                    Template = action.ActionName
                };
            }
        }
    }
}

不管是控制器的还是操作方法的,都允许设置多个SelectorModel对象。这就类似我们在控制器上可以设置多个 [Route]。代码在处理之前都先判断一下是不是有任何 Selector 的 AttributeRouteModel 属性不为 null,这是为了让自定义的约定与 [Route]、[HttpGet] 等特性类不冲突。我的意思是如果你在控制器或操作方法上用了 [Route] 特性,那么这里就跳过,不要再修改它。

if (controller.Selectors.Any(s => s.AttributeRouteModel != null))
{
    return;
}

if (action.Selectors.Any(s => s.AttributeRouteModel != null))
{
    continue;
}

 

CombineTemplates 是静态方法,它可以帮我们自动拼接URL,只要你把两段URL传递给它就行了。

所以,上述约定类的规则就是:Assembly Name + Controller Name + Action Name。

约定完了后,还要在初始化MVC功能(注册服务)时设置一下。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers().AddMvcOptions(opt=>
{
    opt.Conventions.Add(new CustControllerConvension());
});
var app = builder.Build();

注意啊,这样设置后,约定是作用于全局的,应用程序内的控制器都会应用。你如果只想局部用,那就定义了特性类(从Attribute类派生),实现原理一样的。你可以参考老周在上上篇中举到的自定义控制器名称的例子。

应用程序在映射终结点时就不用设置路由了。

app.MapControllers();
app.Run();

现在,我们定义些控制器类测试一下。

 public class 大螃蟹Controller : ControllerBase 
 {
     public IActionResult Greeting() => Content("来自螃蟹精的问候");
 }

这里假设程序集的名称是 FlyApp。你应该知道怎么访问了。看图。

 

不过瘾的话,可以再写一个控制器类。

 public class HomeController : Controller
 {
     public IActionResult Index()
     {
         return Content("来自高达的问候");
     }

     public IActionResult Hello()
     {
         return Content("来自西海龙王的问候");
     }
 }

继续测试,看图。

这里补充一下,前面我们不是定义了这么个控制器吗?

 public class PigController:ControllerBase
 {
     [Route("haha/hehe")]
     public IActionResult Greeting()
     {
         return Content("来自猪的问候");
     }
 }

现在,如果套用了我们刚写的 CustControllerConvension 约定后,两个功能合在一块儿了,那这个控制器该怎么访问呢。咱们的约定在实现时是如果已设置了特性路由就跳过,只有没设置过的才会处理。来,我们分析一下。在这个 Pig 控制器中,控制器上没有应用 [Route] 特性,所以 Selector 里面的 AttributeRouteModel 是 null。所以,会为控制器设置程序集名称前缀 + 控制器名,即 FlyApp/Pig。

接着,它的 Greeting 方法是有 [Route] 特性的,根据咱们的代码逻辑,是保留已有的路由的,所以,”haha/hehe“被保留。

然后 Pig 控制器上的和 Greeting 方法上的路由一合并,就是 /flyapp/pig/haha/hehe。看图。

 

现在,你明白是咋回事了吧。

------------------------------------------------------------------------------

可能有大伙伴会说:老周,你这样弄有意思吗?

老周答曰:没意思,图增意趣耳!

老周再曰:其实啊,这个也不是完全没用的。老周前文说过的,如果你的URL中有某部分是要通过代码来获取,而不是硬编码的话,那这种折腾就有用了。总之,一句话:技巧老周都告诉你了,至于怎么去运用,看实际需要呗。

 

与【ASP.NET Core】MVC控制器的各种自定义:特性化的路由规则相似的内容:

【ASP.NET Core】MVC控制器的各种自定义:特性化的路由规则

MVC的路由规则配置方式比较多,咱们用得最多的是两种: A、全局规则。就是我们熟悉的”{controller}/{action}“。 app.MapControllerRoute( name: "bug", pattern: "{controller}/{action}" ); app.MapCon

【ASP.NET Core】MVC控制器的各种自定义:修改参数的名称

在上一篇中,老周演示了通过实现约定接口的方式自定义控制器的名称。 至于说自定义操作方法的名称,就很简单了,因为有内置的特性类可以用。看看下面的例子。 [Route("[controller]/[action]")] public class StockController : Controller

【ASP.NET Core】MVC控制器的各种自定义:应用程序约定的接口与模型

从本篇起,老周会连发N篇水文,总结一下在 MVC 项目中控制器的各种自定义配置。 本文内容相对轻松,重点讨论一下 MVC 项目中的各种约定接口。毕竟你要对控制器做各种自定义时,多数情况会涉及到约定接口。约定接口的结构都差不多,均包含一个 Apply 方法,实现类需要通过这个方法修改关联的模型设置。

【ASP.NET Core】MVC控制器的各种自定义:IActionHttpMethodProvider 接口

IActionHttpMethodProvider 接口的结构很简单,实现该接口只要实现一个属性即可——HttpMethods。该属性是一个字符串序列。 这啥意思呢?这个字符串序列代表的就是受支持的 HTTP 请求方式。比如,如果此属性返回 GET POST,那么被修饰的对象既支持 HTTP-GET

【ASP.NET Core】MVC操作方法如何绑定Stream类型的参数

咱们都知道,MVC在输入/输出中都需要模型绑定。因为HTTP请求发送的都是文本,为了使其能变成各种.NET 类型,于是在填充参数值之前需 ModelBinder 的参与,以将文本转换为 .NET 类型。 尽管 ASP.NET Core 已内置基础类型和复杂类型的各种 Binder,但有些数据还是不能

如何将 Autofac 整合进 Net6.0 Core MVC 项目中

一、前言 1、简而言之 Asp.Net Core Mvc,我也用了很长一段时间了,它现在的编程模型和方式还是特别棒的,都是组件开发,什么都可以替换,当然了,您别抬杠,有些还是不能替换的。自从我们进入了跨平台开发的时代,IOC容器也成了一个不可或缺的东西了。微软为我们提供了一个默认实现,那就是 ISe

【ASP.NET Core】动态映射MVC路由

ASP.NET Core 中的几大功能模块(Razor Pages、MVC、SignalR/Blazor、Mini-API 等等)都以终结点(End Point)的方式公开。在HTTP管道上调用时,其扩展方法基本是以 Map 开头,如 MapControllers、MapBlazorHub。 对于

Asp.net MVC 跨域设置

.Net Core 跨域

异常过滤器—MVC中异常过滤器使用

## 一、什么是异常过滤器? 异常过滤器(**Exception Filters**)是 ASP.NET Core 中用于处理全局异常的机制。它们允许你在发生异常时捕获、处理和记录异常,并提供自定义的异常处理逻辑。异常过滤器在整个应用程序范围内生效,可以用于处理各种异常情况。用于实现常见的错误处理策

做一个windos服务和api搭配,获取电脑的mac地址

创建webapi项目,只是搭配服务用,什么三层mvc都不弄了,默认的模板直接用就好。 简单分析下,采用signalr通信来传递mac地址,所以先安装个signalr的包(如果简单操作的话可以不装最新的,微软自带一个,不过好像是弃用的) using Microsoft.AspNetCore.Signa