在ASP.NET Core中手动绑定表单数据

时间:2018-08-15 13:32:30

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

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

同样,这也很好用。但是,现在我遇到了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,而无需手动解析它?

2 个答案:

答案 0 :(得分:1)

  

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

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

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

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

Microsoft Example

答案 1 :(得分:1)

最好采用行动方法来处理这种情况:

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

更新

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