ASP.NET学习记录-08-MVC路由
1、什么是controller?为什么用controller?
模型-视图-控制器 (MVC) 体系结构模式将应用分成 3 个主要组件:模型 (M)、视图 (V) 和控制器 (C) 。 MVC 模式有助于创建比传统单片应用更易于测试和更新的应用。
基于 MVC 的应用包含:
- 模型 (M):是表示应用数据的类。 模型类使用验证逻辑来对该数据强制实施业务规则。 通常,模型对象检索模型状态并将其存储在数据库中。 本教程中,
Movie模型将从数据库中检索电影数据,并将其提供给视图或对其进行更新。 更新后的数据将写入到数据库。 - 视图 (V):视图是显示应用用户界面 (UI) 的组件。 此 UI 通常会显示模型数据。
- 控制器 (C):具有以下特征的类:
- 处理浏览器请求。
- 检索模型数据。
- 调用返回响应的视图模板。
在 MVC 应用中,视图仅显示信息。 控制器处理用户输入和交互并对其进行响应。 例如,控制器处理 URL 段和查询字符串值,并将这些值传递给模型。 该模型可使用这些值查询数据库。 例如:
https://localhost:5001/Home/Privacy:指定Home控制器和Privacy操作。https://localhost:5001/Movies/Edit/5:是使用Movies控制器和Edit操作编辑 ID=5 的电影的请求,本教程稍后将对此进行详细介绍。
本教程的后续部分中将介绍路由数据。
MVC 体系结构模式将应用分成 3 组主要组件:模型、视图和控制器。 此模式有助于实现关注点分离:UI 逻辑位于视图中。 输入逻辑位于控制器中。 业务逻辑位于模型中。 这种隔离有助于控制构建应用时的复杂程度,因为它可用于一次处理一个实现特性,而不影响其他特性的代码。 例如,处理视图代码时不必依赖业务逻辑代码。
ChatGPT:
在 ASP.NET(尤其是 ASP.NET MVC 和 ASP.NET Core MVC)中,Controller 是应用程序的核心组件之一,负责处理用户请求并决定如何生成响应。它是基于 MVC(Model-View-Controller)架构模式的一部分,负责连接用户界面(View)和业务逻辑(Model)。
什么是 Controller?
定义
Controller 是一个类,通常继承自ControllerBase或Controller类,用来处理用户通过 HTTP 请求发送到应用的任务。例如:接收 GET、POST、PUT 或 DELETE 请求。职责
- 解析和处理用户请求(包括路由和参数)。
- 调用业务逻辑(通常通过服务或模型)来完成具体任务。
- 根据处理结果返回适当的响应,如 JSON 数据、视图(HTML 页面)或 HTTP 状态码。
基本示例 在 ASP.NET Core 中,一个简单的 Controller 示例:
1
2
3
4
5
6
7
8
9
10[]
[]
public class ProductsController : ControllerBase
{
[]
public IEnumerable<string> Get()
{
return new string[] { "Product1", "Product2" };
}
}- 当用户通过 HTTP GET 请求访问
/Products时,Get方法会被执行并返回结果。
- 当用户通过 HTTP GET 请求访问
为什么使用 Controller?
- 解耦逻辑
- 将用户界面、业务逻辑和数据逻辑分离,使代码更易于维护和扩展。
- 通过 MVC 模式,Controller 专注于处理请求,而模型处理数据逻辑,视图负责渲染界面。
- 提升代码的可读性与可维护性
- Controller 的方法往往小而清晰,每个方法处理特定的任务,使代码更加模块化。
- 开发者可以轻松地对功能进行更新或扩展,而不会影响其他部分。
- 内置功能
- ASP.NET Core Controller 提供内置的路由、模型绑定、验证等功能,大大简化了开发任务。
- 提供对 JSON、XML 等多种格式的支持,使构建 API 更加方便。
- 支持测试
- Controller 易于进行单元测试,开发者可以用 Mock 服务来测试不同的请求和响应路径,而无需依赖真实的底层资源(如数据库)。
总结
Controller 是 ASP.NET 应用程序中用于处理请求的核心组件,它通过 MVC 模式的分工,简化了代码结构,提升了扩展性和维护性。Controller 的存在,让开发者能够专注于实现具体的业务逻辑,同时轻松处理复杂的请求与响应逻辑。
2、MVC-属性路由
在 ASP.NET 中,可以通过以下几个特征来识别、判断一个类是否为 Controller:
1、判断一个类是否为Controller
1. 命名规范
通常,ASP.NET 的 Controller 类以 Controller 结尾。例如:
HomeControllerProductsController
这是 MVC 模式的约定,虽然并非强制,但符合此命名惯例的类更容易被识别为 Controller。
2. 基类继承
在 ASP.NET Core 中,Controller 类通常继承以下基类之一:
ControllerBase(用于 API 控制器)。Controller(用于 MVC 控制器,支持视图和 Razor Pages)。
示例:
1 | public class ProductsController : ControllerBase |
如果一个类继承了上述基类,那么它很可能是一个 Controller。
3. 特性标注(Attributes)
Controller 通常会通过特性标注来定义行为或路由规则:
[ApiController]:标识该类是 Web API 的控制器。[Route]或[HttpGet]等:定义路由和请求类型。
示例:
1 | [] |
使用了这些特性的类通常是 Controller。
4. 类的职责
一个 Controller 类的核心职责包括:
- 处理 HTTP 请求:例如 GET、POST、PUT、DELETE 等。
- 返回响应:通常通过
IActionResult或数据模型返回结果。 - 连接业务逻辑与用户界面:通过调用服务或处理数据,向前端传递数据。
如果一个类包含这些职责,很可能是一个 Controller。
5. 文件位置
在 ASP.NET 项目中,Controller 类通常被组织在一个名为 Controllers 的文件夹中。例如:
1 | /MyApp |
虽然文件夹名称不是强制要求,但这是约定俗成的组织方式。
6. 方法签名
Controller 类中的方法通常具有以下特征:
- 与 HTTP 动作相关联(如 GET、POST 等)。
- 返回类型为
IActionResult或直接返回数据(如string或List)。
示例:
1 | public IActionResult GetProduct(int id) |
总结
要判断一个类是否是 Controller,可以综合考虑以下因素:
- 是否命名为
Controller结尾。 - 是否继承了
ControllerBase或Controller。 - 是否使用了特性如
[ApiController]或[Route]。 - 是否位于
Controllers文件夹。 - 是否负责处理 HTTP 请求并返回响应。
这些特性组合起来能明确判断一个类是否为 ASP.NET 中的 Controller。
2、注册一个类为Controller
在 ASP.NET 中,注册一个类为 Controller 是通过框架内置机制完成的。通常,只需按照框架的约定设置类即可,ASP.NET 会自动识别它并注册为控制器。
以下是如何确保你的类被注册为控制器的步骤:
1. 确保继承正确的基类
在 ASP.NET Core 中,控制器类需要继承以下基类之一:
ControllerBase:用于 Web API 控制器(不需要视图支持)。Controller:用于 MVC 控制器(支持视图和 Razor Pages)。
例如:
1 | using Microsoft.AspNetCore.Mvc; |
如果类继承了 ControllerBase 或 Controller,框架会自动识别它为控制器。
2. 特性标注(可选)
你可以通过特性(Attributes)明确声明控制器的行为:
[ApiController]:标识该类是 Web API 的控制器。[Route]:定义控制器的路由规则。
示例:
1 | [] |
使用 [ApiController] 特性可以启用额外功能,如模型验证自动处理。
3. 配置 ASP.NET Core 应用程序
控制器的注册通常通过 Program.cs 中的配置完成:
(1) 使用依赖注入
如果控制器需要依赖其他服务,你可以在依赖注入容器中注册这些服务。例如:
1 | builder.Services.AddControllers(); |
这行代码会告诉 ASP.NET Core 自动扫描项目中的控制器类并注册它们。
(2) 配置中间件
在 ASP.NET Core 中,使用 MapControllers() 添加控制器路由:
1 | var app = builder.Build(); |
这确保所有符合控制器约定的类被注册并正确映射到对应的 HTTP 请求。
4. 项目结构与约定
控制器通常按照约定存放在项目的 Controllers/ 文件夹中:
1 | /MyApp |
虽然不是必须,但这种组织方式符合惯例,并且便于框架自动发现控制器。
5. 动态注册(特殊需求)
如果需要动态注册某个控制器,可以通过反射或自定义路由处理器来实现,但这通常用于高级场景。以下是示例:
1 | app.UseEndpoints(endpoints => |
总结
要注册一个类为控制器,只需:
- 继承
ControllerBase或Controller。 - 添加相关的特性(如
[ApiController]和[Route])。 - 确保在
Program.cs中通过AddControllers()和MapControllers()注册和启用控制器。
DepartmentsController.cs
1 | using Microsoft.AspNetCore.Mvc; // 引入MVC核心功能命名空间 |
Program.cs
1 | var builder = WebApplication.CreateBuilder(args); // 创建Web应用构建器实例 |

3、MVC-传统路由
在 ASP.NET 中,传统路由(Traditional Routing) 是一种基于 路由表 的 URL 路由机制,用于将用户请求映射到特定的控制器和动作方法。这种路由方式最早出现在 ASP.NET MVC 和 Web API 中,其核心是通过定义固定的路由模板来解析请求。
1. 传统路由的工作机制
传统路由依赖一个全局的路由表,该表由开发者在应用程序启动时定义。这些路由通过模板和参数描述 URL 模式,并将其与相应的控制器和动作方法关联。
- 传统路由的典型定义在
Startup.cs(ASP.NET Core)或Global.asax(早期 ASP.NET)中完成。 - 它基于顺序匹配:框架会从上到下检查路由规则,一旦匹配成功,就停止继续匹配。
2. 传统路由的示例
以下是一个 ASP.NET Core 的传统路由示例:
1 | app.UseEndpoints(endpoints => |
解释:
{controller}:表示控制器名称,如HomeController。{action}:表示控制器中的动作方法,如Index。{id?}:表示一个可选的参数,通常用于传递 ID 值。
示例请求:
/Home/Index会映射到HomeController的Index方法。/Products/Details/5会映射到ProductsController的Details方法,并将id参数设置为 5。
3. 传统路由的优缺点
优点:
- 简单直观:基于固定模板的规则易于理解和使用。
- 灵活性高:可以定义复杂的路由模板以满足多样化需求。
缺点:
- 依赖顺序匹配:如果路由规则很多,可能会降低性能或导致错误的匹配。
- 不够自动化:需要明确定义每个路由规则,管理成本较高。
- 局限性:对于现代 RESTful API 或 SPA(单页应用程序)开发,传统路由不够高效。
4. 与端点路由(Endpoint Routing)的对比
在 ASP.NET Core 3.0 之后,引入了 端点路由(Endpoint Routing),逐渐替代了传统路由。相比之下:
- 传统路由 依赖
UseMvc()来添加路由表,而 端点路由 使用MapControllers()或MapGet()来定义路由。 - 端点路由 更灵活,支持全局路由过滤器和动态路由规则。
总结
传统路由是 ASP.NET 中用于将 URL 模式映射到控制器和动作方法的早期机制,具有规则明确和简单易用的特点,但逐渐被更现代的端点路由取代。在实际开发中,了解传统路由的基础有助于理解路由系统的演变和其在复杂应用中的适用场景。
DepartmentsController.cs
1 | using Microsoft.AspNetCore.Mvc; // 引入ASP.NET Core MVC核心功能命名空间 |
Program.cs
1 | var builder = WebApplication.CreateBuilder(args); // 创建Web应用构建器实例(加载配置文件和环境配置) |
Department.cs
1 | using Microsoft.AspNetCore.Mvc; // 引入ASP.NET Core MVC框架核心功能 |
DepartmentsController.cs
1 | using Microsoft.AspNetCore.Mvc; // 引入ASP.NET Core MVC框架核心功能 |
Program.cs
1 | var builder = WebApplication.CreateBuilder(args); // 创建Web应用构建器,加载配置参数 |
数据依据表单、查询字符串等内容进行自动解析、映射、匹配。但不能将复杂类型绑定到HTTP的Header中。

5、模型绑定优先级
ASP.NET Core 中的控制器模型绑定源优先级(Model Binding in controllers: Binding source priorities)是一个明确规定了请求数据来源解析顺序的机制。当控制器动作方法需要绑定参数时,框架会按照固定优先级顺序尝试从不同数据源获取值。以下是基于图片的详细解析:
1.模型绑定基础
模型绑定(Model Binding)是 ASP.NET Core 将 HTTP 请求中的数据(如路由参数、查询字符串、表单字段等)自动映射到控制器方法参数或模型对象的过程。绑定源优先级决定了当多个数据源存在同名参数时,框架优先选择哪一个来源的值。
2.绑定源优先级层级(从高到低)
Explicit (显式指定)
通过特性(如[FromQuery],[FromBody],[FromRoute]等)明确指定参数来源时,优先级最高。
示例:1
public IActionResult GetUser([FromRoute] int id) { ... }
BindAsync 接口
如果参数类型实现了IBindableFromHttpContext<T>.BindAsync接口,框架会调用此方法进行绑定(常见于自定义复杂类型)。表单字段绑定(Form Fields)
支持将任意类型(简单或复杂)绑定到 HTTP POST 请求的Form数据(application/x-www-form-urlencoded或multipart/form-data)。简单类型绑定到路由参数
当参数为简单类型(如int,string)且未显式指定来源时,默认优先从路由参数(URL Path)中获取值。查询字符串绑定(Query String)
若参数未通过上述方式获取,框架会尝试从 URL 的查询字符串(Query String)中解析值。数组绑定到查询字符串或 Headers
特殊场景:当参数是数组类型时,会尝试从查询字符串或 HTTP 请求头(Headers)中解析值。
3.关键规则与场景
- 显式优先于隐式
使用[From*]特性时,无论优先级层级如何,都会直接采用指定来源的数据。 - 复杂对象绑定逻辑
对于复杂类型(如 DTO 对象),默认会从表单字段、请求体(Body)或路由参数中按优先级组合绑定属性。 - 冲突处理
当多个来源存在同名参数时(如路由参数和查询字符串同名),优先级高的来源会覆盖低优先级的值。
4.开发注意事项
- 避免歧义:建议显式指定参数来源(如
[FromQuery]),提高代码可读性并避免意外绑定行为。 - 性能优化:高频使用的参数优先通过路由或查询字符串传递,避免频繁解析请求体。
- 数组处理:数组绑定需遵循格式(如
?ids=1&ids=2或X-Array-Header: [1,2,3])。

6、模型输入格式
默认情况下只能输入Json格式的数据,如果想输入其他数据,例如XML则需要指定
1 | // 创建新部门(HTTP POST方法) |
Program.cs
1 | // 创建Web应用程序构建器实例,并加载配置参数 |

7、模型状态
如果缺少某些内容或者参数不正确,该状态下的代码并不会报错,而是可以继续运行,这并不正确。
通过继承Controller基类从而利用框架,便可以通过ModelState判断控制错误。


但如果设定为[ApiController]则会立即报错。

8、MVC-结果处理
在 ASP.NET Core 中,Action Result(动作结果) 代表控制器操作返回的不同类型的 HTTP 响应。根据你提供的图片信息,这里是对以下几种常见的 Action Result 的解析:
1. ViewResult
用于返回 HTML 视图页面(通常用于 MVC 应用)。它会渲染 Razor 视图,并将数据传递给前端页面。 示例:
1 | public IActionResult Index() |
💡 适用场景:当你需要返回完整的 HTML 视图给客户端时,例如网页 UI。
2. ContentResult
用于返回纯文本或 HTML 片段,而不是完整的视图页面。可以指定返回的内容类型(如 text/plain 或 text/html)。 示例:
1 | public IActionResult SimpleText() |
💡 适用场景:当你需要返回一个简单的字符串(例如 API 返回消息),而不涉及视图渲染。
3. JsonResult
用于返回 JSON 数据,特别适用于 Web API。它会自动将对象序列化为 JSON 格式。 示例:
1 | public IActionResult GetData() |
💡 适用场景:用于 API 或 AJAX 请求,需要返回 JSON 结构化数据给前端。
4. FileResult
用于返回文件作为 HTTP 响应(如 PDF、图片、Excel)。它允许客户端下载或显示文件内容。 示例:
1 | public IActionResult DownloadFile() |
💡 适用场景:当你需要提供文件下载或直接展示文件时。
5. Redirect Results
用于执行页面重定向。ASP.NET 提供多种重定向方式:
Redirect():临时重定向(302)RedirectPermanent():永久重定向(301)RedirectToAction():重定向到控制器的另一个操作RedirectToRoute():重定向到特定的路由
示例:
1 | public IActionResult RedirectExample() |
💡 适用场景:当你希望用户访问另一个 URL,比如登录后跳转到首页。
总结
这些不同类型的 Action Result 提供了灵活的 HTTP 响应方式:
- ViewResult → 适用于 MVC 视图返回
- ContentResult → 适用于返回纯文本数据
- JsonResult → 适用于 JSON 数据 API
- FileResult → 适用于文件下载
- Redirect Results → 适用于页面跳转
1、ContentResult
1 | // ContentResult - 返回纯文本或 HTML 片段 |

2、JsonResult
1 | public object Details(int? id) |
3、FileResult
关于文件结果类型

ASP.NET Core 提供了三种主要的文件结果类型,可以根据文件的存储方式和访问需求,返回不同类型的文件响应:
VirtualFileResult
- 用于返回存储在
wwwroot目录中的文件。该目录是 ASP.NET Core 的默认静态文件目录。 - 适用于应用程序的静态文件(如图片、CSS 文件)。
示例:
1
2
3
4public IActionResult GetVirtualFile()
{
return File("~/wwwroot/files/sample.pdf", "application/pdf");
}- 用于返回存储在
PhysicalFileResult
- 用于返回存储在
wwwroot之外的文件。这些文件可能存储在服务器的任意路径中,但需要开发者确保文件路径的正确性和安全性。 - 适合返回服务器上的私有文件。
示例:
1
2
3
4
5public IActionResult GetPhysicalFile()
{
string filePath = Path.Combine(Directory.GetCurrentDirectory(), "files/sample.pdf");
return PhysicalFile(filePath, "application/pdf");
}- 用于返回存储在
FileContentResult
- 用于直接从内存中返回文件内容,而无需依赖物理文件。这种方式适合返回动态生成的文件数据或只存储在内存中的内容。
- 适用于生成临时文件或二进制内容。
示例:
1
2
3
4
5public IActionResult GetFileContent()
{
byte[] fileContent = System.IO.File.ReadAllBytes("files/sample.pdf");
return File(fileContent, "application/pdf", "download.pdf");
}
总结
通过以上文件结果类型,ASP.NET Core 提供了灵活的文件响应机制,允许开发者根据文件的存储位置和访问方式选择适合的结果类型:
- VirtualFileResult → 针对
wwwroot的静态文件。 - PhysicalFileResult → 服务器中任何文件。
- FileContentResult → 内存中的动态文件数据。
1 | // 处理虚拟文件下载请求的路由 |

1 | // 处理物理文件下载请求的路由 |

1 | // 处理字节流文件下载请求的路由 |

4、Redirect Results
图片中的内容展示的是与 ASP.NET Core MVC 框架中 重定向结果(Redirect Results)相关的三种常见类型,标题为 “Producing Results: Redirect Results”。下面我将详细介绍每一种重定向结果,并探讨它们各自的应用场景和背后的意义:
RedirectToActionResult
这种结果类型主要用于在控制器中重定向到另一个动作方法(action)。当你处理完当前请求后,可能需要将用户导航到另一处逻辑上更适合处理后续操作的地方,例如转入另一控制器的动作或返回一个成功提示页面。在这种情况下,利用RedirectToActionResult,你可以仅指定目标动作的方法名、控制器名称以及必要的路由参数,利用路由系统自动生成正确的 URL,从而避免硬编码 URL 的风险和维护问题。例如,在完成一个表单提交后,使用这种结果可以让用户跳转到一个“操作成功”的页面,同时确保路由的灵活性和一致性。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20using Microsoft.AspNetCore.Mvc;
namespace MyApp.Controllers
{
public class HomeController : Controller
{
// 默认页面:完成操作后重定向到 Success 动作
public IActionResult Index()
{
// 执行完相关逻辑后重定向到 Success 动作
return RedirectToAction("Success");
}
public IActionResult Success()
{
ViewBag.Message = "操作成功!";
return View();
}
}
}LocalRedirectResult
这种结果在安全性上更为严格,当需要重定向到一个“本地” URL 时使用。所谓“本地”指的是同一应用内部的页面或资源。通过使用LocalRedirectResult,可以有效防范开放重定向攻击——也就是防止恶意用户利用重定向漏洞,将用户诱导到外部危险或恶意网站。系统在执行重定向时,会先验证目标 URL 是否是应用内部允许的地址,只有符合条件的 URL 才会被接受。如果验证失败,通常会抛出异常或者返回错误信息,从而保护应用和用户双方的安全。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19using Microsoft.AspNetCore.Mvc;
namespace MyApp.Controllers
{
public class AccountController : Controller
{
public IActionResult Login(string returnUrl)
{
// 验证 returnUrl 是否为本地 URL
if (!Url.IsLocalUrl(returnUrl))
{
// 非本地 URL时,可以重定向到一个安全的默认页面
return RedirectToAction("Index", "Home");
}
// 安全跳转到传入的本地 URL
return LocalRedirect(returnUrl);
}
}
}RedirectResult
这是最基础和直接的重定向类型,它接受一个明确的 URL 字符串作为目标地址,并立即向客户端发出重定向指令。与RedirectToActionResult不同,RedirectResult不会处理控制器或路由信息,它适用于那些 URL 已经确定且无需依赖应用内路由配置的情况。虽然它提供了很高的灵活性,允许跳转到完全任意的地址,但这也要求开发者自己确保目标 URL 的正确性和安全性,否则可能会引入重定向漏洞。1
2
3
4
5
6
7
8
9
10
11
12
13using Microsoft.AspNetCore.Mvc;
namespace MyApp.Controllers
{
public class HomeController : Controller
{
public IActionResult ExternalRedirect()
{
// 重定向到外部 URL
return Redirect("https://www.example.com");
}
}
}
EmployeesController
1 | // 引入ASP.NET Core MVC框架的核心功能 |
RedirectToActionResult
1 | // 处理部门详情的GET请求 |

LocalRedirectResult
1 | // 处理部门详情的GET请求 |
RedirectResult
1 | // 处理部门详情的GET请求 |
9、重构学习代码
1.Department
1 | // 引入必要的命名空间 |
2.DepartmentsRepository
1 | using System.Xml.Linq; |
3.DepartmentsController
1 | using Microsoft.AspNetCore.Mvc; |
4.Program
1 | // 创建Web应用构建器,用于配置和构建Web应用 |
