mvc 统一修改路由请求头
介绍 (Introduction)
As usual, with these articles, I have a simple reason for writing this article. Over the last few days of working on a project, I have found bugs that were related entirely to misrouted HTTP requests due to mistakes I made in either my configuration or the attributes on my handlers. So let’s talk about the basics of using the .Net Core Http routing configuration and attribute hints. Maybe some of what I am fighting with and correcting in my code will save you some frustration.
像往常一样,在撰写这些文章时,我有一个撰写本文的简单理由。 在项目的最后几天里,我发现了一些错误,这些错误与我在配置或处理程序中的属性中的错误导致的HTTP请求路由错误有关。 因此,让我们谈谈使用.Net Core Http路由配置和属性提示的基础知识。 也许我正在努力解决并纠正代码中的某些内容会为您省去一些麻烦。
First, a quick overview of the topic, every .Net Core MVC web application consists of three major parts. Just as it says in the name, Model — where data is stored, View — where information is displayed, and Controller — where data is processed and manipulated. There can be some overlap in these where the Model knows more about how to handle parts of its internals, so it exposes methods or properties to let the Controller manipulate bits that the Controller shouldn’t be accessing. But for the most part, it is safe to think in the three major blocks. A more complex program will have additional components that the Controllers use to do truly complex work or an additional backend server that the Controller passes the data off to for full processing. But for this article, I am discussing a basic MVC application that is all contained in a single web project, with a set of models, views, and controllers that fit together to make a coherent whole.
首先,对该主题进行快速概述,每个.Net Core MVC Web应用程序都包含三个主要部分。 就像名称中所说的那样:模型(用于存储数据),视图(用于显示信息)和控制器(用于处理和操纵数据)。 在这些地方可能存在一些重叠,这些地方使模型对如何处理其内部的部分有了更多的了解,因此它公开了方法或属性,以使Controller可以控制Controller不应访问的位。 但在大多数情况下,可以在三个主要方面进行思考。 一个更复杂的程序将具有Controller用来完成真正复杂工作的其他组件,或者Controller会将数据传递给其进行完整处理的附加后端服务器。 但是对于本文,我将讨论一个基本的MVC应用程序,该应用程序全部包含在一个Web项目中,并且具有一组模型,视图和控制器,这些模型,视图和控制器可以组合在一起以形成一个连贯的整体。
How do they fit together? Well, the Model is easy. The other two components get models or ways to find models as input data. So, a view will get the Model it is working on as input as part of the definition, and a controller method will get either a model key value that lets it look the correct Model up and load it for processing, or the data the makes up the Model. Either way, the Model is the data that lets the View and the Controller communicate. What is that communication? Well, for the average application, it is HTTP traffic, usually either in the form of request messages from the View to the Controller or a mix of HTTP page/view/request to the browser showing the views that make up the UI.
它们如何配合在一起? 好了,模型很简单。 其他两个组件获得模型或查找模型作为输入数据的方式。 因此,视图将获取正在处理的模型作为定义的一部分作为输入,并且控制器方法将获取模型键值(使它可以查找正确的模型并加载以进行处理),或者获取数据。模型。 无论哪种方式,模型都是使视图和控制器进行通信的数据。 那是什么交流? 嗯,对于一般的应用程序来说,它是HTTP流量,通常是从视图到控制器的请求消息形式,或者是HTTP页面/视图/请求到浏览器的混合形式,以显示构成UI的视图。
Which brings us to the question of how the underlying software determines the handler for any incoming Html request? And decide to send that specific HTTP message to for it to get to the correct method or browser window? The full answer is complicated enough that for a long time, this was the most challenging part of network programming. There are Doctoral Dissertations still being written in how to do this in the least time-consuming or expensive way possible. Fortunately, .Net Core has a set of libraries that hide all that complexity from us. I say fortunately because back in the dark ages, I had to do this by hand multiple times, and let me assure you that writing network routing code is not a fun or trivial exercise. Trust your tools; don’t reinvent the wheel. The odds are if you try to do so, you will take longer and get something worse.
这就带来了一个问题,即底层软件如何确定任何传入HTML请求的处理程序? 并决定发送该特定的HTTP消息以使其到达正确的方法或浏览器窗口? 完整的答案非常复杂,以至于长期以来,这是网络编程中最具挑战性的部分。 尚有关于如何以最省时或最便宜的方式完成此工作的博士学位论文。 幸运的是,.Net Core具有一组库,这些库向我们隐藏了所有这些复杂性。 我说幸运的是,因为在黑暗时代,我不得不多次手动执行此操作,并且让我向您保证,编写网络路由代码不是一件有趣的事情。 相信您的工具; 不要重新发明轮子。 如果您尝试这样做,则可能会花费更长的时间,并且事情变得更糟。
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/7e331769945f704a29bc9f49da7c0ab2.png)
配置路由的基础 (Basics of Configuring Routing)
For the most basic MVC applications, the simple routing scheme that is created by the code generation wizard if you pick the “give me a .Net Core MVC application” path when you make your project works just fine. It adds a default route pattern to the application in the Startup class. This default route looks like :
对于最基本的MVC应用程序,如果在使项目正常工作时选择“给我一个.Net Core MVC应用程序”路径,则由代码生成向导创建的简单路由方案。 它将默认路由模式添加到Startup类中的应用程序。 此默认路由如下所示:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Breaking down the routing defined above into its parts, first app.UseEndpoints defines the set of route information that the application will tell the operating system it needs — specifically the network subsystem of the operating system. It takes a builder method that creates a set of route definitions that it can return. Which is what “endpoints” are defining. Here you are writing an inline method that will build that list by building “endpoints.MapControllerRoute” instances. Which while it can be more complex for the most part, we can use the same constructor used by the code generator. This version takes a route name and the pattern which defines what the routed HTTP address looks like matching it to a specific method in your Controller. Specifically, the above pattern “{controller=Home}/{action=Index}/{id?}” says to Route a blank call to your URI to the Home.Index method. That is something like https://myrandom.com would route the same as https://myrandom.com/Home/Index. This simple scheme works in general for simple routings, so a single controller with Create, Edit, Delete, and Index endpoints will work just fine. However, if you do something wild like have data that requires more than a single page to view to keep from taking, oh say, theoretically ten minutes to load the first page. Then things start getting interesting fairly quickly.
将上面定义的路由分解为第一个应用程序。UseEndpoints定义应用程序将告诉操作系统所需的路由信息集,特别是操作系统的网络子系统。 它采用一个builder方法,该方法创建可以返回的一组路由定义。 这是“端点”所定义的。 在这里,您正在编写一个内联方法,该方法将通过构建“ endpoints.MapControllerRoute”实例来构建该列表。 尽管在大多数情况下它可能更复杂,但是我们可以使用代码生成器使用的相同构造函数。 此版本采用路由名称和模式,该模式定义路由的HTTP地址看起来像将其与Controller中的特定方法进行匹配。 具体来说,上述模式“ {controller = Home} / {action = Index} / {id?}”表示将对URI的空白调用路由到Home.Index方法。 就像https://myrandom.com这样的路由将与https://myrandom.com/Home/Index相同。 这种简单的方案通常适用于简单的路由,因此具有创建,编辑,删除和索引端点的单个控制器就可以正常工作。 但是,如果您要执行一些疯狂的操作,例如要使数据需要多个页面才能查看,那么从理论上讲要花十分钟才能加载第一页。 然后事情开始变得相当有趣。
Surprise, this is exactly the situation I ran into, so I ended up adding several other routes to my endpoint list. So my Route now looked like:
令人惊讶的是,这正是我遇到的情况,因此我最终在我的端点列表中添加了其他几条路由。 所以我的路线现在看起来像:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "deeper",
pattern: "{controller}/{action}/{subaction}/{id?}");
endpoints.MapControllerRoute(
name: "paging",
pattern: "{controller}/{action}/{id} page={page}");
endpoints.MapControllerRoute(
name: "paging-deeper",
pattern: "{controller}/{action}/{subaction}/{id} page={page}");
});
I added additional routes without defaults to allow both sub-actions, which would look like https://myrandom.com/Home/Index/SomeRandom/5, which allows for related but different commands like Getting the index of “SomeRandom number 5” and having it call the method Home.SomeRandom with a parameter of five. To do that, you need some additional information in the HomeController you would need to have something like the following:
我添加了没有默认值的其他路由,以允许这两个子操作,这看起来像https://myrandom.com/Home/Index/SomeRandom/5 ,它允许相关但不同的命令,例如获取“ SomeRandom number 5”的索引并使用参数5调用方法Home.SomeRandom。 为此,您需要在HomeController中提供一些其他信息,您需要具有以下内容:
[HttpGet]
[Route("Index/SomeRandom/{id?}")]
public async Task<IActionResult> SomeRandom(int? id)
{...}
This attribute is the other half of the routing. It tells the receiving network code on the server where to route those incoming requests that we have told the OS we want to receive. These attributes hide a large complex set of code underneath that connects SomeRandom as the callback for an HTTP Get message coming in with the path “Index/SomeRandom,” and any number after the last slash gets parsed and passed into your method as an int, or if none it sends in a null.
此属性是路由的另一半。 它告诉服务器上的接收网络代码将我们已经告知操作系统要接收的那些传入请求路由到哪里。 这些属性在其下隐藏了一大堆复杂的代码集,这些代码集将SomeRandom连接为HTTP Get消息的回调,并以路径“ Index / SomeRandom”传入,最后一个斜杠之后的任何数字都将作为int进行解析并传递给您的方法,如果没有,则发送null。
These routing patterns are still a relatively simple set of routes, except for the cases where you need a sub-action, you wouldn’t need to worry about telling the routing code underlying .Net Core how to route incoming messages.
这些路由模式仍然是一组相对简单的路由,除了需要子操作的情况外,您无需担心告诉.Net Core基础的路由代码如何路由传入消息。
Then you add several — in my case, seven — more controller/view sets. The primary Index responses work correctly, but as soon as I called anything that took an id or handled anything except an HTTP Get, I started getting routing error messages on the client-side. Essentially listing off all of the possible matches that were even close with “These all kind of match and we can’t figure it out” but in computer errorish language rather than quite as quippy.
然后添加几个(在我的情况下为七个)更多的控制器/视图集。 主要的索引响应可以正常工作,但是一旦我调用了任何带有id的东西或处理了除HTTP Get以外的任何东西,我就开始在客户端获取路由错误消息。 本质上列出了所有可能的匹配,甚至用“这些匹配,我们都无法弄清楚”来列出,但是用计算机错误的语言而不是那么古怪。
Digging into things, I found that adding full routing information to each Controller solved the problem. What do I mean by full routing information? Well, in the declaration of each Controller, I added a route attribute to define the Route to the Controller. So my controller declarations look like this now:
深入研究,我发现向每个Controller添加完整的路由信息可以解决该问题。 完整的路由信息是什么意思? 好吧,在每个Controller的声明中,我添加了route属性以定义到Controller的Route。 所以我的控制器声明现在看起来像这样:
[Route("Home")]
public class HomeController : Controller
{...}[Route("StarCharts")]
public class StarChartsController : Controller
{...}[Route("Stars")]
public class StarsController : Controller
{...}[Route("Planets")]
public class PlanetsController : Controller
{...}
And so on for all of the controllers. This configuration sets up how the various routes are going to treat the controllers. Then for each method, I put in routing information where it was needed — and it turned out that I needed it in quite a few places.
对于所有控制器,依此类推。 此配置设置各种路由如何对待控制器。 然后,对于每种方法,我都会在需要的地方放置路由信息,结果发现我在很多地方都需要它。
I’ll take the Atmospheres Controller because it is representative of what I did for all of the controller and view pairs. Also, I am going to truncate most of these code blocks to the parts that are related to routing. While there is quite a bit of interesting activity happening beyond the routing covering everything will dilute this article and its purpose. I may mention some useful bits in passing but will not show entire methods or views to keep this down to a reasonable length.
我将使用“气氛控制器”,因为它代表了我对所有控制器和视图对所做的工作。 另外,我将把大多数代码块截断为与路由相关的部分。 尽管路由之外发生了很多有趣的活动,但所有内容都会稀释本文及其目的。 我可能会在传递中提及一些有用的信息,但不会显示将其缩减到合理长度的完整方法或视图。
First, the Controller, here I get the full list of atmosphere objects from the database context, set which page we are on, then use a NuGet Package called X Paged List to build up the current page of atmosphere objects and save it and the current page to the View Bag for use in the View. Then I give the index View the full list of atmospheres that it is expecting.
首先,控制器,在这里我从数据库上下文中获取大气层对象的完整列表,设置我们所在的页面,然后使用一个名为X页面列表的NuGet包来构建大气层对象的当前页面并保存它和当前页面。到“视图包”中以在视图中使用。 然后,为索引View提供期望的完整气氛列表。
[HttpGet]
[Route("Index")]
[Route("Index/{id?}")]
public async Task<IActionResult> Index(int? page)
{
_logger.LogInformation($"{_className}.Index(int? {page}) Starting");
var atmospheres = await _context.Atmospheres
.ToListAsync();
_logger.LogInformation($"{_className}.Index(int? {page}) Got stars");
int pageNum = page ?? 1;
IPagedList<Atmosphere> pageOfAtmosphere = atmospheres.ToPagedList<Atmosphere>(pageNum, GeneralCalculations.PageLength);
ViewBag.PageOfAtmospheres = pageOfAtmosphere;
ViewBag.CurrentPage = pageNum;
_logger.LogInformation($"{_className}.Index(int? {page}) Got page now view");
return View(atmospheres);
}
Then the View, the first half is just basic Razor/Html with a touch of CSS thrown in for spice. The most interesting bit is the two uses of View Bag when I can building the title so I can show the current and max pages to expect.
然后是View,上半部分只是基本的Razor / Html,带有一些CSS香料。 最有趣的一点是,当我可以构建标题时,可以使用View Bag的两种用法,以便可以显示期望的当前页面和最大页面。
@model IEnumerable<StarMap.Models.Atmosphere>@using X.PagedList.Mvc.Core;@using X.PagedList;@{
ViewData["Title"] = $"Atmosphere - Page {ViewBag.CurrentPage} of {ViewBag.PageOfAtmospheres.GetMetaData().PageCount}";
Layout = "_Layout";}<div class="text-center">
<h1 class="display-4">@ViewData["Title"]</h1>
</div><p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead class="table-info">
<tr class="d-xl-table-row">
<th class="d-xl-table-cell">@Html.DisplayNameFor(model => model.Name)
</th>
<th class="d-xl-table-cell">@Html.DisplayNameFor(model => model.AtmospherePrimary)
</th>
<th class="d-xl-table-cell">@Html.DisplayNameFor(model => model.AtmosphereComposition)
</th>
<th></th>
</tr>
</thead>
The next part of the View is more interesting. This section is where I use the paged list that was saved in the View Bag to show the current page of atmospheres along with Edit, Details, and Delete buttons. Then at the bottom, below the table, use the PagedListPager extension to Html to show a paging control with the action of paging set to route back to Index with the page number selected.
视图的下一部分更有趣。 在此部分中,我将使用保存在“查看包”中的分页列表来显示气氛的当前页面以及“编辑”,“详细信息”和“删除”按钮。 然后,在表格下方的底部,使用Html的PagedListPager扩展来显示具有分页操作的分页控件,该分页操作设置为路由回带有选定页号的Index。
<tbody>@foreach (Atmosphere item in ViewBag.PageOfAtmospheres)
{
<tr class="d-xl-table-row">
<td class="d-xl-table-cell"> @item.Name </td>
<td class="d-xl-table-cell"> @item.AtmospherePrimary </td>
<td class="d-xl-table-cell"> @item.AtmosphereComposition </td>
<td class="d-xl-table-cell"> @item.AtmospherePrimary</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>@Html.PagedListPager((IPagedList)ViewBag.PageOfAtmospheres, page => Url.Action("Index", new { page }))
This paging is less of an issue with Atmospheres with the current state of the database but Stars? There are over 45,000 stars, which comes out of more than 900 pages. Before I added paging and database optimizations, it was taking ten minutes to load the page and several minutes to scroll down. I will write another article on how I optimized the database sometime soon.
对于数据库的当前状态,但对于Stars而言,此分页对于Atmospheres来说不是一个问题。 有超过45,000个星星,来自900多个页面。 在添加分页和数据库优化之前,加载页面需要十分钟,向下滚动需要几分钟。 我将很快写另一篇关于如何优化数据库的文章。
The next bit is the Details controller and View. The details of the Details controller is basic and dull. The Controller looks up the id passed in and sends it to the View. The View displays the values. The useful thing to note is the routing information, where the attributes set up what type of Http message and path this method is handling.
下一位是Details控制器和View。 Details控制器的详细信息是基本且枯燥的。 控制器查找传入的ID并将其发送到视图。 视图显示值。 需要注意的有用的事情是路由信息,其中的属性设置此方法正在处理的Http消息类型和路径。
[HttpGet]
[Route("Details")]
[Route("Details/{id?}")]
public async Task<IActionResult> Details(Guid? id)
{
…
}
The View is just basic Razor/Html with nothing new or different in it.
视图只是基本的Razor / Html,没有任何新内容或不同之处。
The Create handler has just a bit more of interest. It has an Http Get handler that is dead simple,
Create处理程序只具有更多的兴趣。 它有一个非常简单的Http Get处理程序,
[HttpGet]
[Route("Create")]
public IActionResult Create()
{
_logger.LogInformation($"{_className}.Create()");
return View();
}
The routing attributes are the only interesting things, and they are there to distinguish this handler from all of the other Http Get handlers that exist both in this class and other classes.
路由属性是唯一有趣的东西,它们在那里可以将此处理程序与该类和其他类中存在的所有其他Http Get处理程序区分开。
Create also has an Http Post handler to deal with the newly created Atmosphere object. Validating that it is from the source we are expecting, validating that it is the Model we are expecting, then setting values that the user is not allowed to set — and finally, saving the data to the database. I am going to show the entire handler because it has interesting routing and security attributes, as well as some bits in the method itself that I want to discuss briefly. First, the routing, it has the Http Post, and Route attributes telling the system what this method is handling, which should be getting familiar by now. Then there is [ValidateAntiForgeryToken]. This attribute again hides a whole boatload of complexity that .Net Core has automated for us. The attribute tells the system to generate a security code for validating the connection between the sender and the handler. Confirming the sender as expected, and nothing has slipped into the middle modifying values in the transmitted packet — a horrible oversimplification but, in general, terms what is going on. Having to do this by hand even five or six years ago, let alone twenty years ago, was one reason that web applications were so notorious for being insecure. Writing even this baseline level of security by hand was a major undertaking that few companies were willing to spend time, money, or effort to have. Having these tools available is a wonderful shortcut. Don’t write your own when there are tools that do the work.
Create还具有一个Http Post处理程序来处理新创建的Atmosphere对象。 验证它是否来自我们期望的来源,验证它是否为我们期望的模型,然后设置不允许用户设置的值-最后,将数据保存到数据库中。 我将展示整个处理程序,因为它具有有趣的路由和安全属性,以及我想简要讨论的方法本身的一些内容。 首先,路由具有Http Post和Route属性,该属性告诉系统此方法正在处理的内容,这应该现在已经很熟悉了。 然后是[ValidateAntiForgeryToken]。 此属性再次隐藏了.Net Core为我们自动化的全部复杂性。 该属性告诉系统生成安全码,以验证发送方和处理程序之间的连接。 确认发送者是否如预期的那样,并且没有任何事情滑入传输数据包的中间修改值中,这简直太可怕了,但总的来说,这是怎么回事。 甚至在五,六年前(更不用说二十年前)都必须手动执行此操作,这是Web应用程序因不安全而臭名昭著的原因之一。 手工编写甚至达到此基本安全级别也是一项艰巨的任务,很少有公司愿意花时间,金钱或精力来拥有。 拥有这些工具是一个很棒的捷径。 有工作的工具时,请不要自己编写。
[HttpPost]
[ValidateAntiForgeryToken]
[Route("Create")]
public async Task<IActionResult> Create([Bind("Id,AtmospherePrimary,AtmosphereComposition,Name,Created,LastUpdated")] Atmosphere atmosphere)
{
_logger.LogInformation($"{_className}.Create([Bind] {atmosphere.ToShortString()})");
if (ModelState.IsValid)
{
_logger.LogInformation($"{_className}.Create([Bind] {atmosphere.ToShortString()}) Valid Model");
atmosphere.Id = Guid.NewGuid();
atmosphere.Created = DateTime.Now;
atmosphere.LastUpdated = DateTime.Now;
_context.Add(atmosphere);
await _context.SaveChangesAsync();
_logger.LogInformation($"{_className}.Create([Bind] {atmosphere.ToShortString()}) Go back to index");
return RedirectToAction(nameof(Index));
}
_logger.LogInformation($"{_className}.Create([Bind] {atmosphere.ToShortString()}) Invalid Model try again");
return View(atmosphere);
}
While some interesting things are going on in the View, there is nothing related to routing. From here, it is a matter of setting the route attributes for each method appropriately. So methods that send data to the client are 99% Http Get handlers. While methods that receive data from the client are 80% Http Post handlers. The reason for the difference is that there is another Http request message that sends data, that is the Http Put message, this is supposed to be the Update a record request. Many applications overload Post with this functionality and ignore Put messages at all. However, you should be aware that you may need a handler for this type of message as well.
尽管视图中发生了一些有趣的事情,但与路由没有任何关系。 从这里开始,适当地设置每种方法的路由属性就成为问题。 因此,将数据发送到客户端的方法是99%的Http Get处理程序。 从客户端接收数据的方法是80%的Http Post处理程序。 造成这种差异的原因是,还有另一个发送数据的Http请求消息,即Http Put消息,这应该是“更新记录”请求。 许多应用程序使用此功能使Post过载,而根本忽略了Put消息。 但是,您应该意识到,对于此类消息,您可能还需要一个处理程序。
结论 (Conclusion)
Using the .Net Core Http Routing system isn’t difficult. However, it does add a layer of complexity that you need to think about when writing MVC applications. This level of complexity is one of the reasons that some people are moving away from MVC applications to Page Model applications where the controller code is directly in the code behind for each page, and a page may handle multiple operations. Because my preference is still for the model view controller style of application, I live with the slight increase in design thought that I need to apply for the Controller’s routing needs. But if you don’t like this, then it may be that you would be more comfortable investigating the page model style. Either way, they are both attempting to do the same thing, provide a set of tools to let you get your vision onto a website as quickly and easily as possible.
使用.Net Core Http路由系统并不困难。 但是,它确实增加了编写MVC应用程序时需要考虑的复杂性。 这种复杂程度是某些人从MVC应用程序转移到页面模型应用程序的原因之一,在该页面模型应用程序中,控制器代码直接位于每个页面后面的代码中,并且一个页面可以处理多个操作。 因为我仍然偏爱模型视图控制器的应用程序样式,所以我的设计思想略有增加,因此我需要申请控制器的路由需求。 但是,如果您不喜欢这样,则可能是您更愿意研究页面模型样式。 无论哪种方式,他们都试图做同一件事,提供了一组工具,使您可以尽快,轻松地将自己的愿景带入网站。
Good luck, and have fun.
祝好运并玩得开心点。
翻译自: https://medium.com/the-innovation/net-core-mvc-http-request-routing-7821a7f7d2d2
mvc 统一修改路由请求头