【问题标题】:Manually bind form data in ASP.NET Core在 ASP.NET Core 中手动绑定表单数据
【发布时间】:2019-01-22 09:24:11
【问题描述】:

我有一个 web 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);
}

同样,这也很好用。但是,现在我遇到了不作为 JSON 传递而是作为表单数据传递的 DTO(如开头所述)的问题。
正文的内容不是可以轻松序列化的 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,而无需手动解析它?

【问题讨论】:

  • 也许在为你解析并创建数据之后,基于消息数据进行授权会更好?
  • @nvoigt 你有任何链接或资源吗?因为当我查找如何进行授权时,上面的解决方案(某种)是我发现的,没有其他方法。
  • 我没有任何参考资料,但这似乎是常识......解析中间件中的所有不同消息并了解它们的语义将是一个巨大的泥球。只有实际的控制器才能知道您传递的 ItemId 是否需要检查读取访问或写入访问权限,因为这取决于它将如何处理它。
  • 此外,在您的授权方法中复制所有用于数据传输的逻辑意味着您将所有这些作为一个大冗余。您不能只交换 [FromQuery][FromRoute],因为虽然看起来很简单,但您已经在隐藏的地方复制了该逻辑,它会失败。

标签: c# asp.net-web-api asp.net-core asp.net-authorization


【解决方案1】:

在上述 AuthorizationHandler 中,我需要访问一些传递给控制器​​的东西,例如路由中的 ID 或 DTO

请不要那样做。您基本上是在复制已经存在的关于如何从请求中解析参数的逻辑。

基本处理程序用于基本情况:例如,只有经过身份验证的“BookClub”角色成员才能访问BooksController 方法。太好了。

一旦您发现自己需要来自消息本身的信息,请不要手动进行所有这些解析。让 ASP 做这件事并根据您给定的约束解析消息然后当消息完成时,在您获得的对象上调用您的授权逻辑。

Microsoft Example

【讨论】:

    【解决方案2】:

    您最好在您的操作方法中处理这种情况:

    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 类的源代码。)您需要手动绑定模型以访问所需的信息以在其中进行授权。然后框架将完成其工作,导致模型绑定被完成两次,从而导致性能下降。如前所述,这应该也可以避免。

    更新

    我刚刚注意到我不小心在操作方法中留下了一段重要的代码。已更正。

    【讨论】:

      猜你喜欢
      • 2017-01-02
      • 2011-10-28
      • 2011-04-04
      • 2012-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-25
      相关资源
      最近更新 更多