Asp.Net Core由零开始(六)———Routing路由

本文深入探讨了ASP.NET Core MVC中的路由机制,包括基于约定的路由和基于属性的路由,详细解释了如何配置路由以实现HTTP请求的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

路由具体可分为以下两种

1.Convention-based(按约定)

首先,ASP.NET Core 中间件需要一个方法来确定给定的 HTTP 请求是否应该发送给控制器进行处理,我们将这个过程称之为路由匹配

MVC 中间件将根据我们提供的 URL 和一些配置信息做出此决定

本章中,我们将定义这些配置信息,或者当我们添加 MVC 中间件时,可以在 Startup.cs 中说明路由信息

这种方法通常被称为基于约定的路由。

以下代码是常规路由的代码片段

routeBuilder.MapRoute(“Default”, “{controller=Home}/{action=Index}/{id?}”);
上面的代码中,我们定义了一个路由正则,告诉 MVC 如何查看 URL 并找到控制器名称和操作名称,其中控制器是 C# 类,操作是该类上的公共方法

前面几章节中,我们已经在应用程序中创建了一个控制器 ( HomeController ) ,它是一个 C# 类,不需要从基类派生或实现接口或具有任何特殊属性。 它是一个纯 C#类,名称为 HomeController,它包含返回字符串的 Index() 方法

using System;

namespace HelloWorld.Controllers
{
    public class HomeController
    {
        public HomeController()
        {
        }

        public string Index()
        { 
            return "你好,世界! 此消息来自 HomeController..."; 
        }
    }
}

这里,我们回到路由上来,我们将重点关注路由到控制器,我们也将尝试理解路由如何工作

现在,我们回到 Startup 类,将 MVC 中间件配置到我们的应用程序中。然后在 Configure方法中,使用方法 UseMvcWithDefaultRoute()

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

    app.UseFileServer();
    app.UseMvcWithDefaultRoute();
}

app.UseMvcWithDefaultRoute() 给了我们一个默认的路由规则,允许我们访问 HomeController
接下来我们不使用 app.UseMvcWithDefaultRoute() ,而是使用 UseMvc(),然后在私有方法 ConfigureRoute() 处配置路由

以下是按照这种思路的 Startup.cs 的完整代码

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

namespace HelloWorld
{
    public class Startup
    {
        public Startup() 
        { 
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("AppSettings.json");
            Configuration = builder.Build(); 
        }

        public IConfiguration Configuration { get; set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseFileServer();
            app.UseMvc(ConfigureRoute);

            app.Run(async (context) => {
                var msg = Configuration["message"];
                await context.Response.WriteAsync(msg);
            });
        }

        private void ConfigureRoute(IRouteBuilder routeBuilder)
        {
            //Home/Index 
            routeBuilder.MapRoute("Default", "{controller}/{action}/{id?}");
        }
    }
}

在 ConfigureRoute() 方法中,我们可以配置路由,也许你已经注意到了,该方法必须使用 IRouteBuilder 类型的参数

路由的目标是描述 ASP.NET Core MVC 用于处理 HTTP 请求并找到可响应该请求的控制器的规则

我们使用一条路由规则将请求映射到不同的控制器
我们告诉 routeBuilder 我们想要映射一个新的路由,它的名字是 Default,然后提供最重要的路由信息​​,路由模板
路由模板是一个字符串,它用于向 ASP.NET Core MVC 描述如何拆分 URL
在前面的章节中,我们添加了一个 HomeController,因此我们可以请求以下任何 URL,并且它们也将被定向到 HomeController 上的 Index 操作
https://localhost:5001
https://localhost:5001/Home
https://localhost:5001/Home/Index
当浏览器请求 http://mysite/ 或 http://mysite/ Home 时,它将得到 HomeController 的 Index 方法的输出的内容
我们可以通过更改浏览器中的 URL 来尝试此操作。在这个例子中,它是 https:// localhost:5001/ ,可能端口号有所不同
如果我们将 /Home 或 /Home/Index 追加到 URL 并按下 Enter 按钮,也会看到相同的结果
id 末尾的问号表示该参数是可选的。换句话说,ASP.NET Core MVC 在这里不需要看到某种类型的 id,可能是一个数字,也可能是一个字符串或 GUID

2.Attribute-based(基于路由属性配置的)

主要用于Web API
常用的Http Method有:Get,Post,Put整体修改更新,PATCH局部修改更新,Delete

namespace CoreBackend.Api.Controllers
{
    //[Route("api/product")]
    [Route("api/[controller]")]
    public class ProductController: Controller
    {
        [HttpGet]
        public JsonResult GetProducts()
        {
            return new JsonResult(new List<Product>
            {
                new Product
                {
                    Id = 1,
                    Name = "牛奶",
                    Price = 2.5f
                },
                new Product
                {
                    Id = 2,
                    Name = "面包",
                    Price = 4.5f
                }
            });
        }
    }
}

使用[Route(“api/[controller]”)], 它使得整个Controller下面所有action的uri前缀变成了"/api/product", 其中[controller]表示XxxController.cs中的Xxx(其实是小写).

也可以具体指定, [Route(“api/product”)], 这样做的好处是, 如果ProductController重构以后改名了, 只要不改Route里面的内容, 那么请求的地址不会发生变化.

然后在GetProducts方法上面, 写上HttpGet, 也可以写HttpGet(). 它里面还可以加参数,例如: HttpGet(“all”), 那么这个Action的请求的地址就变成了 “/api/product/All”.

创建Post Action

[Route("{id}", Name = "GetProduct")]
        public IActionResult GetProduct(int id)
        {
            var product = ProductService...(x => x.Id == id);
            if (product == null)
            {
                return NotFound();
            }
            return Ok(product);
        }

        [HttpPost]
        public IActionResult Post([FromBody] ProductCreation product)
        {
            if (product == null)
            {
                return BadRequest();
            }
            var maxId = ProductService.Max(x => x.Id);
            var newProduct = new Product
            {
                Id = ++maxId,
                Name = product.Name,
                Price = product.Price
            };
            ProductService.Add(newProduct);

            return CreatedAtRoute("GetProduct", new { id = newProduct.Id }, newProduct);
        }

[HttpPost] 表示请求的谓词是Post. 加上Controller的Route前缀, 那么访问这个Action的地址就应该是: ‘api/product’

后边也可以跟着自定义的路由地址, 例如 [HttpPost(“create”)], 那么这个Action的路由地址就应该是: ‘api/product/create’.

[FromBody] , 请求的body里面包含着方法需要的实体数据, 方法需要把这个数据Deserialize成ProductCreation, [FromBody]就是干这些活的.

客户端程序可能会发起一个Bad的Request, 导致数据不能被Deserialize, 这时候参数product就会变成null. 所以这是一个客户端发生的错误, 程序为让客户端知道是它引起了错误, 就应该返回一个Bad Request 400 (Bad Request表示客户端引起的错误)的 Status Code.

传递进来的model类型是 ProductCreation, 而我们最终操作的类型是Product, 所以需要进行一个Map操作, 目前还是挨个属性写代码进行Map吧, 以后会改成Automapper.

返回 CreatedAtRoute: 对于POST, 建议的返回Status Code 是 201 (Created), 可以使用CreatedAtRoute这个内置的Helper Method. 它可以返回一个带有地址Header的Response, 这个Location Header将会包含一个URI, 通过这个URI可以找到我们新创建的实体数据. 这里就是指之前写的GetProduct(int id)这个方法. 但是这个Action必须有一个路由的名字才可以引用它, 所以在GetProduct方法上的Route这个attribute里面加上Name=“GetProduct”, 然后在CreatedAtRoute方法第一个参数写上这个名字就可以了, 尽管进行了引用, 但是Post方法走完的时候并不会调用GetProduct方法. CreatedAtRoute第二个参数就是对应着GetProduct的参数列表, 使用匿名类即可, 最后一个参数是我们刚刚创建的数据实体.

运行程序试验一下, 注意需要在Headers里面设置Content-Type: application/json.

Put请求
put应该用于对model进行完整的更新.

首先最好还是单独为Put写一个Dto Model, 尽管属性可能都是一样的, 但是也建议这样写, 实在不想写也可以.

ProducModification.cs

public class ProductModification
    {
        [Display(Name = "产品名称")]
        [Required(ErrorMessage = "{0}是必填项")]
        [StringLength(10, MinimumLength = 2, ErrorMessage = "{0}的长度应该不小于{2}, 不大于{1}")]
        public string Name { get; set; }

        [Display(Name = "价格")]
        [Range(0, Double.MaxValue, ErrorMessage = "{0}的值必须大于{1}")]
        public float Price { get; set; }
    }

然后编写Controller的方法:

[HttpPut("{id}")]
        public IActionResult Put(int id, [FromBody] ProductModification product)
        {
            if (product == null)
            {
                return BadRequest();
            }

            if (product.Name == "产品")
            {
                ModelState.AddModelError("Name", "产品的名称不可以是'产品'二字");
            }

            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var model = ProductService.SingleOrDefault(x => x.Id == id);
            if (model == null)
            {
                return NotFound();
            }
            model.Name = product.Name;
            model.Price = product.Price;

            // return Ok(model);
            return NoContent();
        }

按照Http Put的约定, 需要一个id这样的参数, 用于查找现有的model.

由于Put做的是完整的更新, 所以把ProducModification整个Model作为参数.

进来之后, 进行了一套和POST一模一样的验证, 这地方肯定可以改进, 如果验证逻辑比较复杂的话, 到处写同样验证逻辑肯定是不好的, 所以建议使用FluentValidation.

然后, 把ProductModification的属性都映射查询找到给Product, 这个以后用AutoMapper来映射.

返回: PUT建议返回NoContent(), 因为更新是客户端发起的, 客户端已经有了最新的值, 无须服务器再给它传递一次, 当然了, 如果有些值是在后台更新的, 那么也可以使用Ok(xxx)然后把更新后的model作为参数一起传到前台.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值