ASP.NET Core MVC中的路由


ASP.NET Core MVC中的路由

​ ASP.NET Core中有两种路由技术,分别是常规路由属性路由

​ 我们先了解什么是路由,当来自浏览器的请求到达应用程序时,MVC中的控制器会处理传入的HTTP请求并响应用户操作,请求URL会被映射到控制器的操作方法上。此映射过程由应用程序中定义的路由规则完成。

​ 比如,当向/Home/Index发出请求时,此URL将映射到HomeController类中的Index()方法,如图所示:

在这里插入图片描述

​ 类似的,当向/Home/Details/1发出请求时,此URL将映射到HomeCaontroller类中的Details() 操作方法。URL中的值1自动映射到id,即Details(int id)的参数id。

在这里插入图片描述

​ 目前,我们还没有在ASP.NET Core MVC应用中明确定义任何路由规则。由此我们想到的问题是,这个URL(/Home/Index)如何映射到HomeController中的Index()方法。

ASP.NET Core MVC中的默认路由

​ 以下是Startup.cs文件中的Configure()方法,此方法中的代码设置HTTP请求处理管道

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseStaticFiles();

            app.UseMvcWithDefaultRoute();
        }

​ 在这个方法中,我们调用了UseMvcWithDefaultRoute()扩展方法。正是这种方法将MVC与默认路由添加到应用程序的请求处理管道中。

{Controller=Home}/{action=Index}/{id?}

​ 只需将鼠标悬停在VIsual Studio中的UseMvcWithDefaultRoute()上,就可以看到智能提示的默认路由了。
在这里插入图片描述

UseMvcWithDefaultRoute()方法中的代码

​ 为了方便读者快速参考,以下是来自Github的代码。注意,UseMvcWithDefaultRoute()方法内部调用了UseMvc()方法,通过它设置默认路由。

public static IApplicationBuilder UseMvcWithDefaultRoute(this IApplicationBuilder app)
{
    if (app == null)
    {
        throw new ArgumentNullException(nameof(app));
    }
    
    return app.UseMvc(route=>
                      {
                          routes.MapRoute(
                          	name:"default",
                              template:"{controller=Home}/{action=Index}/{id?}"
                          );
                      });
}

了解默认路由

​ 默认路由模板规则:{controller=Home}/{action=Index}/{id?},大多数的URL都会按照这个规则进行映射,具体如表所示:

路径段映射信息
/HomeHomeController类
/DetailsDetails(int id)方法
/1Details(int id)方法中的id参数

​ 请求流程说明如图所示:

在这里插入图片描述

  • 第一个URL路径段/Home映射到HomeController。在URL中,我们不用添加Controller这个后缀。当MVC在第一个URL路径段中找到单词Home时,它会附加单词Controller并查找名为HomeController的类。
  • 第二个URL路径段/Details映射到HomeControllerDetails(int id) 操作方法。
  • 第三个URL路径段1映射到Details(int id)方法的id参数。这个步骤被称为模型绑定

请注意,在以下默认路由模板中,我们在id参数后面加一个问号,默认路由模板为{controller=Home}/{action=Index}/{id?},问号表示URL中的id参数可选。这意味着通过以下URL,都可以通过路由映射到StudentController类的Details()方法中。

  • /Student/Details/1
  • /Student/Details

{controller=Home}中的值Home是Controller的默认值。类似地,{action=Index}中的值Index是操作方法的默认值。

​ 因此,程序导航到应用程序根目录http://localhost:2051。因为URL中没有指定控制器名称和操作方法名称,所以将使用路有模板的默认值,路由会将请求映射到HomeController中的Index() 操作方法上。

同理,以下请求URL也将映射到HomeController类的Index()操作方法上。

  • http://localhost:2051/Home
  • http://localhost:2051/Home/Index

对于大多数应用程序,默认路由即可满足日常的开发工作需求,代码如下:

public class DepartmentsController:Controller
    {
        public string List()
        {
            return "我是Departments控制器的List()方法";
        }
        
        public string Details()
        {
            return "我是Departments控制器的Details()方法";
        }
    }

/Departments/list映射到DepartmentsControllerList()方法,/Departments/Details映射到DepartmentsController的**Details()**方法。

在这里插入图片描述

​ 如果要自定义路径模板并希望更多地控制路径,请使用UseMvc()方法来配置路由,而不是UseMvcWithDefaultRoute()方法,请参考以下代码修改Startup类地路由规则。

app.UseMvc(routes=>
           {
               routes.MapRoute(
               	name:"default",
                 template:"{controller=Home}/{action=Index}/{id?}"
               );
           });

ASP.NET Core MVC中的属性路由

​ 请参考以下Startup.cs文件中的Configure()方法中的代码。请注意,我们使用的UseMvc()方法不包含默认路由模板,无法进行参数传递。

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseStaticFiles();

            app.UseMvc();

            //app.UseMvcWithDefaultRoute();
        }

​ 这意味着,目前应用程序没有配置任何的路由,当导航到以下任何URL时。我们会看到404错误❌。

属性路由示例

​ 使用Route()属性来定义路由,我们可以在Controller类或Controller()操作方法上应用Route()属性。

  public class HomeController : Controller
    {
      
      
        [Route("")]
        [Route("Home")]
        [Route("Home/Index")]
        public ViewResult Index()
        {
            return View();
        }

       //其他代码

    }

​ 在Index()操作方法上指定了3个不同的Route()属性。对于Route()属性的每个实例,我们指定了不同的路有模板。使用这3个路由规则,3个URL中都会访问HomeController的Index()操作方法。

​ 使用这3个路由模板,当遇到以下3个URL访问HomeController的Index()方法时,都会匹配成功并进入方法内。

  • /
  • /Home
  • /Home/Index

配置完成后运行项目,我们能正常访问项目首页。

属性路由参数

​ 使用传统路由,我们可以将路由参数指定为路由模板的一部分。当然属性路由也可以做同样的事,请看下面的例子。

public class HomeController : Controller
    {
        private readonly IStudentRepository _studentRepository;

        //使用构造函数注入的方式注入IStudentRepository
        public HomeController(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }

        [Route("")]
        [Route("Home")]
        [Route("Home/Index")]
        //返回学生的信息
        public ViewResult Index()
        {
            //查询所有的学生信息
            IEnumerable<Student> students = _studentRepository.GetAllStudents();
            //将学生列表传递到视图
            return View(students);
        }

        [Route("Home/Details/{id}")]
        public ViewResult Details(int id)
        {
            //实例化HomeDetailsViewModel并存储Student详细信息和PageTitle
            HomeDetailsViewModel homeDetailsViewModel = new HomeDetailsViewModel()
            {
                Student = _studentRepository.GetStudent(id),
                PageTitle = "学生详情"
            };
            //将ViewModel对象传递给View()方法
            return View(homeDetailsViewModel);
        }
	}

​ Details()方法具有id参数,此参数根据指定的id来查询学生的详细信息。

​ 请注意,在路由模板中我们指定了id参数。因此,URL(/Home/Details)将执行Details(int id) 方法,并将值1映射到Details(int id)的id参数。这是通过模型绑定来完成的。

属性路由可选参数

​ 当URL(/Home/Details/1)中具有id路由参数的值时,才执行HomeController的Details(int id)方法。如果URL中不存在id值,那么我们会得到404错误❌。

​ 比如,访问/Home/Details就不会执行Details(int id)方法,而是显示404错误。要使路由参数id可选,只需在末尾添加问号即可

public class HomeController : Controller
    {
        private readonly IStudentRepository _studentRepository;

        //使用构造函数注入的方式注入IStudentRepository
        public HomeController(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }

        [Route("")]
        [Route("Home")]
        [Route("Home/Index")]
        //返回学生的信息
        public ViewResult Index()
        {
            //查询所有的学生信息
            IEnumerable<Student> students = _studentRepository.GetAllStudents();
            //将学生列表传递到视图
            return View(students);
        }

        [Route("Home/Details/{id?}")]
        public ViewResult Details(int? id)
        {
            //实例化HomeDetailsViewModel并存储Student详细信息和PageTitle
            HomeDetailsViewModel homeDetailsViewModel = new HomeDetailsViewModel()
            {
            //如果id为null,则使用1,否则使用路由属性中传递的值
                Student = _studentRepository.GetStudent(id??1),
                PageTitle = "学生详情"
            };
            //将ViewModel对象传递给View()方法
            return View(homeDetailsViewModel);
        }
	}

控制器和操作方法名称

​ 在属性路由中,控制器和操作方法名称不会影响属性路由名称,它们没有强关联关系。请看下面的示例。

 public class WelcomeController:Controller
    {
        [Route("WC")]
        [Route("WC/Index")]
        public string Welcome()
        {
            return "我是Welcome控制器中的welcome()方法";
        }
    }

​ 由于我们直接在操作方法上指定了路由模板,因此WelcomeController的Welcome()方法对以下两个URL都会执行。

在这里插入图片描述
在这里插入图片描述

属性路由支持多层

​ Route()属性也可以应用于Controller类以及各个操作方法中。为了使属性路由代码重复性减弱并提升可维护性,我们可以将Controller上的属性路由与各个操作方法的属性路由相结合。考虑下面的例子。

public class HomeController : Controller
    {
        private readonly IStudentRepository _studentRepository;

        //使用构造函数注入的方式注入IStudentRepository
        public HomeController(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }

        [Route("")]
        [Route("Home")]
        [Route("Home/Index")]
        //返回学生的信息
        public ViewResult Index()
        {
            //查询所有的学生信息
            IEnumerable<Student> students = _studentRepository.GetAllStudents();
            //将学生列表传递到视图
            return View(students);
        }


        //?使路由模板中的id参数为可选,如果要让它为必选,删除?即可
        [Route("Home/Details/{id?}")]
        public ViewResult Details(int id)
        {
            //实例化HomeDetailsViewModel并存储Student详细信息和PageTitle
            HomeDetailsViewModel homeDetailsViewModel = new HomeDetailsViewModel()
            {
                Student = _studentRepository.GetStudent(id),
                PageTitle = "学生详情"
            };
            //将ViewModel对象传递给View()方法
            return View(homeDetailsViewModel);
        }
	}

HomeController的Index() 方法匹配以下3中URL。

  • /
  • /Home
  • /Home/Index

HomeController的Details(int id) 操作方法匹配以下两种URL路径。

  • /Home/Details
  • /Home/Details/1(id参数)

正如读者所看到的,有很多重读的路由名称。我们对代码进行修改并精简,在HomeController类应用Route()属性。如下所示:

 [Route("Home")]
    public class HomeController : Controller
    {
        private readonly IStudentRepository _studentRepository;

        //使用构造函数注入的方式注入IStudentRepository
        public HomeController(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }

        [Route("")]
        [Route("Index")]
        //返回学生的信息
        public ViewResult Index()
        {
            //查询所有的学生信息
            IEnumerable<Student> students = _studentRepository.GetAllStudents();
            //将学生列表传递到视图
            return View(students);
        }


        //?使路由模板中的id参数为可选,如果要让它为必选,删除?即可
        [Route("Details/{id?}")]
        public ViewResult Details(int id)
        {
            //实例化HomeDetailsViewModel并存储Student详细信息和PageTitle
            HomeDetailsViewModel homeDetailsViewModel = new HomeDetailsViewModel()
            {
                Student = _studentRepository.GetStudent(id),
                PageTitle = "学生详情"
            };
            //将ViewModel对象传递给View()方法
            return View(homeDetailsViewModel);
        }
    }

​ 我们将应用于控制器操作方法的路由模板用到了控制器上。但是,当我们导航到http://localhost:1234的时候,HomeController的Index()方法将不会被执行,反而出现404页面错误❌。要解决这个问题,请在Index()操作方法中包含以 / 开头的路径模板,如下所示:

 		[Route("/")]
		[Route("")]
        [Route("Index")]
        //返回学生的信息
        public ViewResult Index()
        {
            //查询所有的学生信息
            IEnumerable<Student> students = _studentRepository.GetAllStudents();
            //将学生列表传递到视图
            return View(students);
        }

​ 需要记住的是,如果操作方法上的路与规则以 /~/ 开头,则Controller路由模板不会与操作方法的路由模板组合在一起。

在属性路由中自定义路由

​ 属性路由通过将标记放在方括号中来支持标记替换。标记[controller][action]将替换为定义路径的控制器名称和操作名称的值。代码如下所示:

  [Route("[controller]")]
    public class DepartmentsController:Controller
    {
        [Route("[action]")]
        public string List()
        {
            return "我是Departments控制器的List()方法";
        }
        
        [Route("[action]")]
        public string Details()
        {
            return "我是Departments控制器的Details()方法";
        }
    }

​ 使用[controller][action]标记,导航到URL路径中的/Departments/List将进入DepartmentsController中执行List()方法。类似地,在URL路径的/Departments/Details将进入DepartmentsController中执行Details()方法。

​ 这是一种非常强大的功能,因为如果要重命名控制器或操作方法名称,我们就不必更改模板路径,如将Departments修改为Some。

​ 要使List()方法成为DepartmentsController的默认路由入口,读者仍可以使用空字符串包含的Route("")属性,如下所示:

  [Route("[controller]")]
    public class DepartmentsController:Controller
    {
        [Route("[action]")]
        [Route("")]    //使List()成为默认路由入口
        public string List()
        {
            return "我是Departments控制器的List()方法";
        }
        
        [Route("[action]")]
        public string Details()
        {
            return "我是Departments控制器的Details()方法";
        }
    }

​ 我们最好只在控制器上设置一次,而不是在控制器的每个操作方法中都包含[action]标记,如下所示:

 [Route("[controller]/[action]")]
    public class DepartmentsController:Controller
    {
        public string List()
        {
            return "我是Departments控制器的List()方法";
        }
        
        public string Details()
        {
            return "我是Departments控制器的Details()方法";
        }
    }

常规路由与属性路由对比

​ 使用属性路由时,属性路由需要在实际使用它们的操作方法上方设置。属性路由比传统路由提供了更大的灵活性。通常情况下,常规路由用于服务HTML页面的控制器,而属性路由则用户服务RESTful API的控制器。

当然,这只是规范和建议,如果读者的应用程序需要有更多的路由灵活性,我们也可以将常规路由与属性路由混合使用。

ASP.NET Core 中新增的路由中间件

​ 虽然我们要讨论的核心是EndpointRouting(终结点路由),它是在ASP.NET Core 2.2中引入的,但在3.0版本中它成为ASP.NET Core的“一等公民”。

​ 首先打开在Startup.cs文件下的ConfigureServices() 方法,修改如下:

  public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews().AddXmlSerializerFormatters();

            services.AddSingleton<IStudentRepository, StudentRepository>();
        }

​ 修改后查看Startup类的Configure() 方法,会发现app.UseMvc()中间件弹出了警告。如图所示:

在这里插入图片描述

​ 警告告诉我们,UseMvc()中间件不支持Routing Endpoints()中间件,要继续使用UseMvc()中间件需将EnableEndpointRouting的值设置为false,这也是之前我们一直将AddControllersWIthViews服务的值设置为false的原因。

​ 那么现在产生一个问题——EndpointRouting是什么。

路由中间件UseRouting

​ 在了解EndpointRouting之前,我们先了解一下UseRouting()中间件,它是ASP.NET Core3.0后新增的路由中间价,其主要作用是启用路由

  • UseRouting()中间件主要用于验证EndpointRoute()中间件服务是否加载进来。
  • 具体实现路由的验证与模板规则由EndpointRoute()中间件来完成。
  • UseRouting() 中间件需要放置在UseStaticFiles()之后。

路由中间件UseEndpoints

​ UseEndpoints()将会替代原有的路由规则和模板,在以后的开发中,基本都会通过UseEndpoints来设置路由规则,官方将它命名为终结点路由

​ 但是还有一个显而易见的问题没有解决,为什么要用UseEndpoints()呢,原有的路由中间件不好吗❓答案是原有的路由中间件功能不够丰富。

UseEndpoints是一个可以处理跨不同中间件系统(如MVC、Razor Pages、Blazor、SignalR和gRPC)的路由系统。 通过终结点路由可以使端点相互协作,并使系统比没有相互对话的终端中间件更全面。

当然在此,不会涉及Razor Pages、Blazor、SignalR和gRPC,但是为了项目的长远规划,dotnet开发团队推荐使用终结点路由。

  • UseEndpoints验证路由服务和EndpointRouting()中间件是否启用和注册到管道中。
  • 注册完毕后,改变中间件的配置,将路由规则运用到应用程序中。

接下来,我们利用UseEndpoints()中间件改造项目,代码如下:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseStaticFiles();

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

            //app.UseMvc();

            //app.UseMvcWithDefaultRoute();
        }
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值