在 ASP.NET Core 中手动绑定表单数据
Manually bind form data in ASP.NET Core
我有一个网络 api,其中包含一些应该同时接收数据和文件的操作。为此,我接受 multipart/form-data 而不是 JSON 并使用 [FromForm]
:
绑定到模型
[Authorize(Policy = "MyCustomPolicy")]
public async Task<IActionResult> Create([FromForm]MyCustomDto myDto)
{
// Do some stuff here
}
public class MyCustomDto
{
public int Id { get; set; }
public string Title { get; set; }
public IEnumerable<IFormFile> Attachments { get; set; }
}
这工作正常并且正确绑定到 DTO。
使用 AuthorizationHandler 检查和执行该策略,它也可以正常工作。
在上述 AuthorizationHandler 中,我需要访问传递给控制器的一些内容,例如路由中的 ID 或 DTO。
使用 authContext.RouteData.Values["nameOfIdField"]
.
访问路线数据非常容易
然而,对于正文,我创建了一个辅助扩展方法来读取正文流并将其反序列化:
public static async Task<T> DeserializeBody<T>(this AuthorizationFilterContext context, string name)
{
var content = string.Empty;
using(var reader = new StreamReader(context.HttpContext.Request.Body, System.Text.Encoding.UTF8, true, 1024, true))
{
content = await reader.ReadToEndAsync();
reader.BaseStream.Seek(0, SeekOrigin.Begin);
}
if (string.IsNullOrWhiteSpace(content))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(content);
}
同样,这也很好用。但是,现在我遇到了 DTO 的问题,这些 DTO 没有作为 JSON 传递,而是 - 如开头所说 - 作为表单数据传递。
主体内容不是可以轻松序列化的 JSON:
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="id"
232
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="title"
test
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="attachments"; filename="C:\Temp\Test.jpg"
Content-Type: image/jpeg
有没有什么方法可以轻松地将它绑定到我的 DTO,而无需手动解析它?
In said AuthorizationHandler, I need to access some stuff passed to the controller, such as IDs in the route or DTOs
请不要那样做。您基本上是在复制关于如何从请求中解析参数的逻辑。
基本处理程序用于基本情况:例如,只有 "BookClub" 角色的经过身份验证的成员才能访问 BooksController
方法。太好了。
一旦您发现自己需要邮件本身的信息,请不要手动进行所有解析。让 ASP 做这件事并根据您给定的约束解析消息 然后 当消息完成时,对您获得的对象调用您的授权逻辑。
您最好在操作方法中处理这种情况:
public class SomeController : Controller
{
private readonly IAuthorizationService _authorizationService;
public SomeController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public async Task<IActionResult> Create([FromForm]MyCustomDto myDto)
{
var authorizationResult = await _authorizationService.AuthorizeAsync(User, myDto, "MyCustomPolicy");
if (!authorizationResult.Succeeded)
return User.Identity.IsAuthenticated ? Forbid() : (IActionResult)Challenge();
// Do some stuff here
}
然后您像这样定义您的授权处理程序:
public class MyCustomDtoAuthorizationHandler : AuthorizationHandler<MyCustomDtoRequirement, MyCustomDto>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyCustomDtoRequirement requirement, MyCustomDto resource)
{
// your authorization logic based on the resource argument...
}
}
选择 AuthorizeFilter 方式的最大问题是授权过滤器 在 模型绑定发生之前执行。 (只需查看 ResourceInvoker class 的来源。)您需要手动绑定您的模型以访问其中授权所需的信息。然后框架将完成其工作,导致模型绑定完成两次,从而导致性能下降。正如前面所述,这应该并且可以避免。
更新
我刚刚注意到我不小心在操作方法中遗漏了一段重要的代码。更正。
我有一个网络 api,其中包含一些应该同时接收数据和文件的操作。为此,我接受 multipart/form-data 而不是 JSON 并使用 [FromForm]
:
[Authorize(Policy = "MyCustomPolicy")]
public async Task<IActionResult> Create([FromForm]MyCustomDto myDto)
{
// Do some stuff here
}
public class MyCustomDto
{
public int Id { get; set; }
public string Title { get; set; }
public IEnumerable<IFormFile> Attachments { get; set; }
}
这工作正常并且正确绑定到 DTO。
使用 AuthorizationHandler 检查和执行该策略,它也可以正常工作。
在上述 AuthorizationHandler 中,我需要访问传递给控制器的一些内容,例如路由中的 ID 或 DTO。
使用 authContext.RouteData.Values["nameOfIdField"]
.
访问路线数据非常容易
然而,对于正文,我创建了一个辅助扩展方法来读取正文流并将其反序列化:
public static async Task<T> DeserializeBody<T>(this AuthorizationFilterContext context, string name)
{
var content = string.Empty;
using(var reader = new StreamReader(context.HttpContext.Request.Body, System.Text.Encoding.UTF8, true, 1024, true))
{
content = await reader.ReadToEndAsync();
reader.BaseStream.Seek(0, SeekOrigin.Begin);
}
if (string.IsNullOrWhiteSpace(content))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(content);
}
同样,这也很好用。但是,现在我遇到了 DTO 的问题,这些 DTO 没有作为 JSON 传递,而是 - 如开头所说 - 作为表单数据传递。
主体内容不是可以轻松序列化的 JSON:
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="id"
232
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="title"
test
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="attachments"; filename="C:\Temp\Test.jpg"
Content-Type: image/jpeg
有没有什么方法可以轻松地将它绑定到我的 DTO,而无需手动解析它?
In said AuthorizationHandler, I need to access some stuff passed to the controller, such as IDs in the route or DTOs
请不要那样做。您基本上是在复制关于如何从请求中解析参数的逻辑。
基本处理程序用于基本情况:例如,只有 "BookClub" 角色的经过身份验证的成员才能访问 BooksController
方法。太好了。
一旦您发现自己需要邮件本身的信息,请不要手动进行所有解析。让 ASP 做这件事并根据您给定的约束解析消息 然后 当消息完成时,对您获得的对象调用您的授权逻辑。
您最好在操作方法中处理这种情况:
public class SomeController : Controller
{
private readonly IAuthorizationService _authorizationService;
public SomeController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public async Task<IActionResult> Create([FromForm]MyCustomDto myDto)
{
var authorizationResult = await _authorizationService.AuthorizeAsync(User, myDto, "MyCustomPolicy");
if (!authorizationResult.Succeeded)
return User.Identity.IsAuthenticated ? Forbid() : (IActionResult)Challenge();
// Do some stuff here
}
然后您像这样定义您的授权处理程序:
public class MyCustomDtoAuthorizationHandler : AuthorizationHandler<MyCustomDtoRequirement, MyCustomDto>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyCustomDtoRequirement requirement, MyCustomDto resource)
{
// your authorization logic based on the resource argument...
}
}
选择 AuthorizeFilter 方式的最大问题是授权过滤器 在 模型绑定发生之前执行。 (只需查看 ResourceInvoker class 的来源。)您需要手动绑定您的模型以访问其中授权所需的信息。然后框架将完成其工作,导致模型绑定完成两次,从而导致性能下降。正如前面所述,这应该并且可以避免。
更新
我刚刚注意到我不小心在操作方法中遗漏了一段重要的代码。更正。