【问题标题】:Is it possible to combine [FromRoute] and [FromBody] in ASP.NET Core?是否可以在 ASP.NET Core 中结合 [FromRoute] 和 [FromBody]?
【发布时间】:2018-07-13 00:44:46
【问题描述】:

我在 API 控制器上有这样的操作:

[HttpPost]
public async Task<IActionResult> StartDeployment(
    [FromQuery]Guid deploymentId,
    [FromRoute]RequestInfo requestInfo,
    [FromBody]DeploymenRequest deploymentRequest)
{
}

可通过复杂的 url (requestInfo) 获得并接收 HTTP POST 请求负载 (deploymentRequest)。

是否可以将[FromRoute][FromBody] 结合起来,所以我将拥有单一请求模型:

public class UberDeploymentRequestInfo
{
    [FromQuery]public Guid DeploymentId { get; set; }

    [FromRoute]public RequestInfo RequestInfo { get; set; }

    [FromBody]public DeploymenRequest DeploymentRequest { get; set; }
}

所以我可以使用 Fluent Validation 拥有单个验证器:

internal class UberDeploymentRequestInfoValidator : AbstractValidator<UberDeploymentRequestInfo>
{
    public UberDeploymentRequestInfoValidator()
    {
        // validation rules can access both url and payload
    }
}

【问题讨论】:

  • 我对这个问题很感兴趣 :-) 到目前为止我走的路线是在控制器方法中将路线参数应用于模型,然后验证模型 - 不是理想的解决方案。
  • @john:这里也一样:我可以访问所有参数的唯一地方是控制器本身,因此必须在那里执行验证。我想要单独的验证器和清理操作。
  • 您可以在自定义模型绑定器中执行此操作?
  • @DavidG:酷!希望看到一些示例/指针。谢谢!
  • @abatishchev 既然您显然在github.com/aspnet/Mvc/issues/8111 得到了答案,您介意在此处发布以确保完整性吗?

标签: c# asp.net-core fluentvalidation asp.net-core-routing


【解决方案1】:

正如评论中提到的,它可以通过自定义模型绑定器来实现。下面是一些用于连接所有内容的代码 sn-ps,通过示例,您可以将带有以下 JSON 正文的 http 请求发送到 API /api/cats?From=james&amp;Days=20

{
    "Name":"",
    "EyeColor":"Red"
}

一些课程,你也可以在这里找到它们:https://github.com/atwayne/so-51316269

// We read Cat from request body
public class Cat
{
    public string Name { get; set; }
    public string EyeColor { get; set; }
}

// AdoptionRequest from Query String or Route
public class AdoptionRequest
{
    public string From { get; set; }
    public string Days { get; set; }
}

// One class to merge them together
[ModelBinder(BinderType = typeof(CatAdoptionEntityBinder))]
public class CatAdoptionRequest
{
    public Cat Cat { get; set; }
    public AdoptionRequest AdoptionRequest { get; set; }
}


public class CatAdoptionEntityBinder : IModelBinder
{
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        // Read Cat from Body
        var memoryStream = new MemoryStream();
        var body = bindingContext.HttpContext.Request.Body;
        var reader = new StreamReader(body, Encoding.UTF8);
        var text = reader.ReadToEnd();
        var cat = JsonConvert.DeserializeObject<Cat>(text);

        // Read Adoption Request from query or route
        var adoptionRequest = new AdoptionRequest();
        var properties = typeof(AdoptionRequest).GetProperties();
        foreach (var property in properties)
        {
            var valueProvider = bindingContext.ValueProvider.GetValue(property.Name);
            if (valueProvider != null)
            {
                property.SetValue(adoptionRequest, valueProvider.FirstValue);
            }
        }

        // Merge
        var model = new CatAdoptionRequest()
        {
            Cat = cat,
            AdoptionRequest = adoptionRequest
        };

        bindingContext.Result = ModelBindingResult.Success(model);
        return;
    }
}


// Controller
[HttpPost()]
public bool Post([CustomizeValidator]CatAdoptionRequest adoptionRequest)
{
    return ModelState.IsValid;
}

public class CatAdoptionRequestValidator : AbstractValidator<CatAdoptionRequest>
{
    public CatAdoptionRequestValidator()
    {
        RuleFor(profile => profile.Cat).NotNull();
        RuleFor(profile => profile.AdoptionRequest).NotNull();
        RuleFor(profile => profile.Cat.Name).NotEmpty();
    }
}

// and in our Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddFluentValidation();
    services.AddTransient<IValidator<CatAdoptionRequest>, CatAdoptionRequestValidator>();
}

【讨论】:

  • 在这种情况下,我明确指定从哪里读取什么内容,对吗?如何实现相同的动态/读取相应的属性?因为我有不止一个模型。
  • 我认为如果你在一个模型中拥有所有东西是可能的(比如用空属性值序列化 http 正文并填充来自其他地方的数据)。否则,您将不得不在某个地方指定它。如果您能分享您的案例,那就太好了
  • 此外,如果您使用属性来执行此操作,则始终可以在 bindModel 方法中读取 Type 的属性并相应地进行序列化。
【解决方案2】:

我进一步定制了上面的 ModelBinder,使它更通用,现在可以在许多不同的合约上工作。我想我不妨在这里分享一下,我在这里找到了下面的大部分代码。

public class BodyAndQueryAndRouteModelBinder<T> : IModelBinder where T : new()
{
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        // Read Cat from Body
        var memoryStream = new MemoryStream();
        var body = bindingContext.HttpContext.Request.Body;
        var reader = new StreamReader(body);
        var text = await reader.ReadToEndAsync();
        var contract = JsonConvert.DeserializeObject<T>(text);

        var properties = typeof(T).GetProperties();
        foreach (var property in properties)
        {
            var valueProvider = bindingContext.ValueProvider.GetValue(property.Name);
            if (valueProvider.FirstValue.IsNotNullOrEmpty())
            {
                property.SetValue(contract, valueProvider.FirstValue);
            }
        }

        bindingContext.Result = ModelBindingResult.Success(contract);
    }
}

然后我在父合同上使用活页夹:

[ModelBinder(BinderType = typeof(BodyAndQueryAndRouteModelBinder<ConfirmStatusRequest>))]
    public class ConfirmStatusRequest
    {
        public string ShortCode { get; set; }
        public IEnumerable<DependantRequest> Dependants { get; set; }
        public IEnumerable<CheckinQuestionAnswer> Answers { get; set; }
    }

【讨论】:

    【解决方案3】:

    我找到了另一种解决方案,将IActionContextAccessor 注入验证器。有了这个,我可以访问 ROUTE 参数而不需要特殊的模型绑定。

    Startup.cs

    services.AddHttpContextAccessor();
    services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
    

    CatValidator.cs

    public class CatValidator : AbstractValidator<CatDto>
    {
        public CatValidator(IActionContextAccessor actionContextAccessor)
        {
            RuleFor(item => item.Age)
                .MustAsync(async (context, age, propertyValidatorContext, cancellationToken) =>
                {
                    var catId = (string)actionContextAccessor.ActionContext.RouteData.Values
                    .Where(o => o.Key == "catId")
                    .Select(o => o.Value)
                    .FirstOrDefault();
    
                    return true;
                });
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-21
      • 1970-01-01
      • 1970-01-01
      • 2016-04-29
      • 1970-01-01
      • 2017-03-15
      • 2020-01-08
      • 2013-03-04
      相关资源
      最近更新 更多