《 Pro ASP.NET Core 6 》--- 读书随记(9)
Part 3
CHAPTER 28
内容来自书籍:
Pro ASP.NET Core 6
Develop Cloud-Ready Web Applications Using MVC, Blazor, and Razor Pages (Ninth Edition)Author: Adam Freeman
需要该电子书的小伙伴,可以留下邮箱,有空看到就会发送的
Using Model Binding
Understanding Model Binding
在 MVC 框架调用动作方法之前,id 参数的值是必需的,而找到一个合适的值是模型绑定系统的责任。模型绑定系统依赖于模型绑定器,模型绑定器是负责从请求或应用程序的一部分提供数据值的组件。默认的模型绑定器在这四个地方查找数据值
- Form data
- The request body (only for controllers decorated with ApiController)
- Routing segment variables
- Query strings
按顺序检查每个数据源,直到找到参数值为止。示例应用程序中没有表单数据,因此在那里不会找到任何值,而且 Form 控制器没有使用 ApiController 属性进行修饰,因此不会检查请求体。下一步是检查路由数据,其中包含一个名为 id 的段变量。这允许模型绑定系统提供一个允许调用 Index 操作方法的值。搜索在找到合适的数据值之后停止,这意味着查询字符串不会被搜索到数据值。
Binding Simple Data Types
请求数据值必须转换成 C # 值,这样才能用于调用操作或页面处理程序方法。简单类型是源自请求中可以从字符串解析的数据项的值
针对简单类型的数据绑定使得从请求中提取单个数据项变得非常容易,而不必通过处理上下文数据来找到它的定义位置
Understanding Default Binding Values
模型绑定是一个尽力而为的特性,这意味着模型绑定器将尝试获取方法参数的值,但是如果数据值无法定位,则仍将调用方法。
Binding Complex Types
当处理复杂类型时,模型绑定系统会大放异彩,复杂类型是任何不能从单个字符串值解析的类型。模型绑定过程检查复杂类型,并对其定义的每个公共属性执行绑定过程。这意味着我可以使用绑定器创建完整的 Product 对象,而不是处理诸如 name 和 price 之类的单个值
复杂类型的数据绑定过程仍然是一个尽力而为的特性,这意味着将为 Product 类定义的每个公共属性寻找一个值,但是缺少值不会阻止调用 action 方法。相反,无法找到任何值的属性将保留为属性类型的默认值。
Binding to a Property
复杂类型的数据绑定在Razor Page 中不能使用,因为参数常常重复页面模型类定义的属性
[BindProperty]
public Product Product { get; set; } = new();
使用 BindProperty 属性装饰属性表明其属性应该服从模型绑定过程,这意味着 OnPost 处理程序方法可以获得它所需的数据,而无需声明参数。使用 BindProperty 属性时,模型绑定器在查找数据值时使用属性名,因此不需要添加到输入元素的显式名称属性。默认情况下,BindProperty 不会为 GET 请求绑定数据,但是可以通过将 BindProperty 属性的 SupportsGet 参数设置为 true 来更改这一点。
Binding Nested Complex Types
如果使用复杂类型定义了受模型绑定约束的属性,那么模型绑定使用属性名作为前缀重复执行。
Specifying Custom Prefixes for Nested Complex Types
有时候,你生成的 HTML 与一种类型的对象有关,但你想把它绑定到另一种类型。这意味着包含视图的前缀不会对应于模型绑定所期望的结构,您的数据也不会得到正确的处理
<input class="form-control" asp-for="Product.Category.Name" />
[HttpPost]
public IActionResult SubmitForm([Bind(Prefix ="Category")] Category category) {
TempData["category"] = System.Text.Json.JsonSerializer.Serialize(category);
return RedirectToAction(nameof(Results));
}
Selectively Binding Properties
您可能很想简单地创建视图,省略敏感属性的 HTML 元素,但这不会阻止恶意用户创建包含值的 HTTP 请求,这种攻击称为过绑定攻击。为了防止模型绑定器对敏感属性使用值,可以指定应该绑定的属性列表
public IActionResult SubmitForm([Bind("Name", "Category")] Product product)
Selectively Binding in the Model Class
如果您正在使用 Razor 页面,或者您想在整个应用程序中使用相同的属性集来进行模型绑定,那么您可以将 BindNever 属性直接应用到模型类
[BindNever]
public decimal Price { get; set; }
Binding to Arrays and Collections
Binding to Arrays
数组的模型绑定需要将提供数组值的所有元素的 name 属性设置为相同的值。此页面显示三个输入元素,它们都具有名称属性值 Data。为了允许模型绑定器查找数组值,我使用 BindProperty 属性修饰了页面模型的 Data 属性,并使用了 Name 参数。
<form asp-page="Bindings" method="post">
<div class="form-group">
<label>Value #1</label>
<input class="form-control" name="Data" value="Item 1" />
</div>
<div class="form-group">
<label>Value #2</label>
<input class="form-control" name="Data" value="Item 2" />
</div>
<div class="form-group">
<label>Value #3</label>
<input class="form-control" name="Data" value="Item 3" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-secondary" asp-page="Bindings">Reset</a>
</form>
@functions {
public class BindingsModel : PageModel {
[BindProperty(Name = "Data")]
public string[] Data { get; set; } = Array.Empty<string>();
}
}
Specifying Index Positions for Array Values
<input class="form-control" name="Data[1]" value="Item 1" />
Binding to Simple Collections
和数组一样
[BindProperty(Name = "Data")]
public SortedSet<string> Data { get; set; } = new SortedSet<string>();
Binding to Dictionaries
对于其名称属性使用索引表示法表示的元素,模型绑定器在绑定到 Dictionary 时将使用索引作为键
<input class="form-control" name="Data[first]" value="Item 1" />
[BindProperty(Name = "Data")]
public Dictionary<string, string> Data { get; set; }
= new Dictionary<string, string>();
Binding to Collections of Complex Types
<input class="form-control" name="Data[0].Name" />
...
<input class="form-control" name="Data[0].Price" />
Specifying a Model Binding Source
正如我在本章开头所解释的,默认的模型绑定过程在四个地方查找数据: 表单数据值、请求主体(仅针对 Web 服务控制器)、路由数据和请求查询字符串。
默认的搜索顺序并不总是有帮助的,要么是因为您总是希望数据来自请求的特定部分,要么是因为您希望使用默认情况下不搜索的数据源。模型绑定特性包括一组用于覆盖默认搜索行为的属性
public async Task<IActionResult> Index([FromQuery] long? id)
Manually Model Binding
当为操作或处理程序方法定义参数或应用 BindProperty 属性时,将自动应用模型绑定。如果您能够始终如一地遵循名称约定,并且总是希望应用流程,那么自动模型绑定就可以很好地工作。如果需要控制绑定过程,或者希望有选择地执行绑定,那么可以手动执行模型绑定
手动模型绑定是使用 TryUpdateModelAsync 方法执行的,该方法由 PageModel 和 ControllerBase 类提供,这意味着它可用于 Razor 控制器 Page 和 MVC 控制器。
TryUpdateModelAsync<Product>(Data, "data", p => p.Name, p => p.Price);
TryUpdateModelAsync 方法的参数是模型绑定的对象、值的前缀和一系列选择进程中将包含的属性的表达式,尽管还有其他版本的 TryUpdateModelAsync 方法可用。
CHAPTER 30
Using Filters
过滤器为请求处理注入额外的逻辑。过滤器类似于应用于单个端点的中间件,可以是操作或页面处理程序方法,它们提供了管理特定请求集的优雅方法
Understanding Filters
NET Core 支持不同类型的过滤器,每种过滤器都有不同的用途。
过滤器可以使过滤器管道短路,以防止将请求转发到下一个过滤器。例如,授权筛选器可以使管道短路,并在用户未经身份验证时返回错误响应。资源、动作和页过滤器能够在端点处理请求之前和之后检查请求,允许这些类型的过滤器短路管道; 在处理请求之前更改请求; 或者更改响应。
每种类型的过滤器都是使用 ASP.NET Core 定义的接口实现的,ASP.NET Core 还提供了基类,这些基类使得将某些类型的过滤器应用为属性变得非常容易
Creating Custom Filters
过滤器都实现了 IFilterMetadata 接口
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IFilterMetadata { }
}
接口是空的,不需要过滤器来实现任何特定的行为。这是因为上一节中描述的每个过滤器类别都以不同的方式工作。过滤器以 FilterContext 对象的形式提供上下文数据
Understanding Authorization Filters
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IAuthorizationFilter : IFilterMetadata {
void OnAuthorization(AuthorizationFilterContext context);
}
}
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IAsyncAuthorizationFilter : IFilterMetadata {
Task OnAuthorizationAsync(AuthorizationFilterContext context);
}
}
Creating an Authorization Filter
namespace WebApp.Filters {
public class HttpsOnlyAttribute : Attribute, IAuthorizationFilter {
public void OnAuthorization(AuthorizationFilterContext context) {
if (!context.HttpContext.Request.IsHttps) {
context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
}
}
}
}
Authorization filters 可以使用在 controllers, action methods, 和 Razor Pages.
Understanding Resource Filters
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IResourceFilter : IFilterMetadata {
void OnResourceExecuting(ResourceExecutingContext context);
void OnResourceExecuted(ResourceExecutedContext context);
}
}
在处理请求时调用 OnResourceExecting 方法,在端点处理请求之后但在执行操作结果之前调用 OnResourceExecated 方法。
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IAsyncResourceFilter : IFilterMetadata {
Task OnResourceExecutionAsync(ResourceExecutingContext context,
ResourceExecutionDelegate next);
}
}
Creating a Resource Filter
注意: 作为属性应用的过滤器不能在其构造函数中声明依赖项,除非它们实现了 IFilterFactory 接口并直接负责创建实例
namespace WebApp.Filters {
public class SimpleCacheAttribute : Attribute, IResourceFilter {
private Dictionary<PathString, IActionResult> CachedResponses
= new Dictionary<PathString, IActionResult>();
public void OnResourceExecuting(ResourceExecutingContext context) {
PathString path = context.HttpContext.Request.Path;
if (CachedResponses.ContainsKey(path)) {
context.Result = CachedResponses[path];
CachedResponses.Remove(path);
}
}
public void OnResourceExecuted(ResourceExecutedContext context) {
if (context.Result != null) {
CachedResponses.Add(context.HttpContext.Request.Path, context.Result);
}
}
}
}
namespace WebApp.Filters {
public class SimpleCacheAttribute : Attribute, IAsyncResourceFilter {
private Dictionary<PathString, IActionResult> CachedResponses
= new Dictionary<PathString, IActionResult>();
public async Task OnResourceExecutionAsync(ResourceExecutingContext context,
ResourceExecutionDelegate next) {
PathString path = context.HttpContext.Request.Path;
if (CachedResponses.ContainsKey(path)) {
context.Result = CachedResponses[path];
CachedResponses.Remove(path);
} else {
ResourceExecutedContext execContext = await next();
if (execContext.Result != null) {
CachedResponses.Add(context.HttpContext.Request.Path,
execContext.Result);
}
}
}
}
}
Resource filters 可以使用在 controllers, action methods, 和 Razor Pages
Understanding Action Filters
与资源过滤器一样,操作过滤器执行两次。区别在于,操作过滤器是在模型绑定过程之后执行的,而资源过滤器是在模型绑定之前执行的。这意味着资源过滤器可以使管道短路,最小化 ASP.NET Core 对请求所做的工作。当需要模型绑定时,将使用操作过滤器,这意味着它们用于改变模型或强制验证等任务。
Action filters 只能用在 controllers 和 action methods
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IActionFilter : IFilterMetadata {
void OnActionExecuting(ActionExecutingContext context);
void OnActionExecuted(ActionExecutedContext context);
}
}
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IAsyncActionFilter : IFilterMetadata {
Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next);
}
}
Implementing an Action Filter Using the Attribute Base Class
Action 属性也可以通过从 ActionFilterAttribute 类派生来实现,该类扩展 Attribute 并继承 IActionFilter 和 IAsyncActionFilter 接口,以便实现类只覆盖它们需要的方法
namespace WebApp.Filters {
public class ChangeArgAttribute : ActionFilterAttribute {
public override async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next) {
if (context.ActionArguments.ContainsKey("message1")) {
context.ActionArguments["message1"] = "New message";
}
await next();
}
}
}
Using the Controller Filter Methods
Controller 类是渲染 Razor 视图的控制器的基础,它实现了 IActionFilter 和 IAsyncActionFilter 接口,这意味着你可以定义功能,并将其应用于控制器和任何派生控制器定义的操作
namespace WebApp.Controllers {
[HttpsOnly]
public class HomeController : Controller {
public IActionResult Index() {
return View("Message",
"This is the Index action on the Home controller");
}
public IActionResult Secure() {
return View("Message",
"This is the Secure action on the Home controller");
}
//[ChangeArg]
public IActionResult Messages(string message1, string message2 = "None") {
return View("Message", $"{message1}, {message2}");
}
public override void OnActionExecuting(ActionExecutingContext context) {
if (context.ActionArguments.ContainsKey("message1")) {
context.ActionArguments["message1"] = "New message";
}
}
}
}
Understanding Page Filters
Page filters是Razor Page版本的action filters
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IPageFilter : IFilterMetadata {
void OnPageHandlerSelected(PageHandlerSelectedContext context);
void OnPageHandlerExecuting(PageHandlerExecutingContext context);
void OnPageHandlerExecuted(PageHandlerExecutedContext context);
}
}
在 ASP.NET Core 已经选择了页面处理程序方法但是在执行模型绑定之前调用 OnPageHandlerSelected 方法,这意味着处理程序方法的参数尚未确定
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IAsyncPageFilter : IFilterMetadata {
Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context);
Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,
PageHandlerExecutionDelegate next);
}
}
Understanding Result Filters
结果筛选器在动作结果用于生成响应之前和之后执行,允许在端点处理响应之后修改响应
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IResultFilter : IFilterMetadata {
void OnResultExecuting(ResultExecutingContext context);
void OnResultExecuted(ResultExecutedContext context);
}
}
在端点生成操作结果之后调用 OnResultExecting 方法
在执行操作结果之后调用 OnResultExected 方法以为客户机生成响应。
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IAsyncResultFilter : IFilterMetadata {
Task OnResultExecutionAsync(ResultExecutingContext context,
ResultExecutionDelegate next);
}
}
Understanding Always-Run Result Filters
实现 IResultFilter 和 IAsyncResultFilter 接口的筛选器仅在端点正常处理请求时使用。如果另一个过滤器使管道短路或出现异常,则不使用它们。需要检查或更改响应(即使管道短路)的筛选器可以实现 IAlwaysRunResultFilter 或 IAsyncAlwaysRunResultFilter 接口。这些接口派生自 IResultFilter 和 IAsyncResultFilter,但没有定义任何新特性。相反,ASP.NET Core 检测always-run的接口并始终应用过滤器
Implementing a Result Filter Using the Attribute Base Class
public class ResultDiagnosticsAttribute : ResultFilterAttribute
Understanding Exception Filters
异常筛选器允许您响应异常,而无需在每个操作方法中编写 try... catch 块。异常筛选器可应用于controller classes、action methods、page model classes或handler methods。当异常未由端点或应用于端点的操作、页和结果筛选器处理时,将调用它们。(Action、 page 和 result 筛选器可以通过将其上下文对象的 ExceptionHandled 属性设置为 true 来处理未处理的异常。)
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IExceptionFilter : IFilterMetadata {
void OnException(ExceptionContext context);
}
}
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IAsyncExceptionFilter : IFilterMetadata {
Task OnExceptionAsync(ExceptionContext context);
}
}
Creating an Exception Filter
namespace WebApp.Filters {
public class RangeExceptionAttribute : ExceptionFilterAttribute {
public override void OnException(ExceptionContext context) {
if (context.Exception is ArgumentOutOfRangeException) {
context.Result = new ViewResult() {
ViewName = "/Views/Shared/Message.cshtml",
ViewData = new ViewDataDictionary(
new EmptyModelMetadataProvider(),
new ModelStateDictionary()) {
Model = @"The data received by the
application cannot be processed"
}
};
}
}
}
}
Managing the Filter Lifecycle
默认情况下,ASP.NET Core 管理它创建的筛选器对象,并将它们重用于后续请求。这并不总是理想的行为,在接下来的章节中,我将描述控制如何创建过滤器的不同方法
Creating Filter Factories
过滤器可以实现 IFilterFactory 接口,以负责创建过滤器的实例,并指定是否可以重用这些实例
public class GuidResponseAttribute : Attribute,
IAsyncAlwaysRunResultFilter, IFilterFactory {
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) {
return ActivatorUtilities
.GetServiceOrCreateInstance<GuidResponseAttribute>(serviceProvider);
}
...
}
我使用 GetServiceOrCreateInstance 方法创建新的筛选器对象,该方法由 Microsoft.Extensions.DependencyInjection 命名空间中的 ActivatorUtities 类定义。尽管可以使用 new 关键字创建筛选器,但是这种方法将解决通过筛选器的构造函数声明的服务的任何依赖关系。
Using Dependency Injection Scopes to Manage Filter Lifecycles
过滤器可以注册为服务,这允许通过依赖注入控制它们的生命周期
builder.Services.AddScoped<GuidResponseAttribute>();
Creating Global Filters
全局过滤器应用于 ASP.NET Core 处理的每个请求,这意味着它们不必应用于单个控制器或 Razor 页面。任何过滤器都可以用作全局过滤器,但是,操作过滤器只应用于端点为action method的请求,页面过滤器只应用于端点为 Razor Page的请求。
builder.Services.Configure<MvcOptions>(opts =>
opts.Filters.Add<HttpsOnlyAttribute>());
MvcOptions.过滤器属性返回一个集合,将过滤器添加到该集合中以便在全局范围内应用它们,可以使用 Add < T > 方法,也可以使用 AddService < T > 方法来处理也是服务的过滤器。还有一个没有泛型类型参数的 Add 方法,可用于将特定的对象注册为全局过滤器。
Understanding and Changing Filter Order
过滤器按特定顺序运行: 授权、资源、操作或页面,然后运行结果。但是,如果存在给定类型的多个过滤器,则应用过滤器的顺序取决于应用过滤器的作用域。
namespace WebApp.Controllers {
[Message("This is the controller-scoped filter")]
public class HomeController : Controller {
[Message("This is the first action-scoped filter")]
[Message("This is the second action-scoped filter")]
public IActionResult Index() {
return View("Message",
"This is the Index action on the Home controller");
}
}
}
builder.Services.Configure<MvcOptions>(opts => {
opts.Filters.Add(new MessageAttribute("This is the globally-scoped filter"));
});
默认情况下,ASP.NET Core 运行全局过滤器,然后应用于控制器或页面模型类的过滤器,最后应用于操作或处理程序方法的过滤器
Changing Filter Order
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IOrderedFilter : IFilterMetadata {
int Order { get; }
}
}
Order 属性返回一个 int 值,按照从小到大的顺序使用Filter
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek “源神”启动!「GitHub 热点速览」
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器