【问题标题】:Multiple types [FromBody] on same method .net core web api同一方法.net core web api上的多种类型[FromBody]
【发布时间】:2018-11-04 18:15:34
【问题描述】:

我有一个带有一个 POST 方法的控制器,它将接收一个 xml 字符串,它可以是 2 种类型。例如:

[HttpPost("postObj")]
    public async Task<IActionResult> postObj([FromBody]firstClass data)
    {
        if (data != null)...

我希望能够绑定到同一路由上的多个类型 ([HttpPost("postObj")]) 这样我就可以在 http://127.0.0.1:5000/api/postObj 上接收正文中的 firstClass xml 或正文中的 secondClass xml,并采取相应的行动。

我尝试使用相同路线但不同类型的另一种方法,例如:

    [HttpPost("postObj")]
    public async Task<IActionResult> postObj([FromBody]secondClass data)
    {
        if (data != null)...

但正如预期的那样,我得到“请求匹配的多个操作导致歧义”。

我尝试读取正文并进行检查,然后将 xml 序列化为相应的对象,但这大大降低了性能。

我预计每秒最多 100 个请求,并且使用 FromBody 绑定给了我,但是手动读取正文和序列化只给了我大约 15 个。

我怎样才能做到这一点?

【问题讨论】:

  • “我怎样才能做到这一点?”实现什么?......你不能有多个“[FromBody]”......它是一个身体,你只有一个。我确实觉得这可能是软件工程的问题:softwareengineering.stackexchange.com
  • 动作方法不能重载

标签: c# xml asp.net-core .net-core asp.net-core-webapi


【解决方案1】:

一直在玩同样的问题,这是我最终的结果:

我希望有以下 API:

PATCH /persons/1
{"name": "Alex"}

PATCH /persons/1
{"age": 33}

我还希望有单独的控制器操作,例如:

[HttpPatch]
[Route("person/{id:int:min(1)}")]
public void PatchPersonName(int id, [FromBody]PatchPersonName model) {}

[HttpPatch]
[Route("person/{id:int:min(1)}")]
public void PatchPersonAge(int id, [FromBody]PatchPersonAge model) {}

因此 Swashbuckle 可以在生成 API 文档时使用它们。

更重要的是我希望内置验证工作(在任何其他建议的解决方案中都不起作用)。

为了实现这一点,我们将创建自己的操作方法选择器属性,该属性将尝试反序列化传入的请求正文,如果能够这样做,则将选择操作,否则将检查下一个操作。

public class PatchForAttribute : ActionMethodSelectorAttribute
{
    public Type Type { get; }

    public PatchForAttribute(Type type)
    {
        Type = type;
    }

    public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
    {
        routeContext.HttpContext.Request.EnableRewind();
        var body = new StreamReader(routeContext.HttpContext.Request.Body).ReadToEnd();
        try
        {
            JsonConvert.DeserializeObject(body, Type, new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Error });
            return true;
        }
        catch (Exception)
        {
            return false;
        }
        finally
        {
            routeContext.HttpContext.Request.Body.Position = 0;
        }
    }
}

优点:验证有效,不需要第三个操作和/或基础模型,可以与 swashbuckle 一起使用

缺点:对于这个动作,我们读取和反序列化 body 两次

注意:倒带很重要,否则其他人将无法阅读正文

我们的控制器现在看起来像这样:

[HttpPatch]
[Route("person/{id:int:min(1)}")]
[PatchFor(typeof(PatchPersonName))]
public void PatchPersonName(int id, [FromBody]PatchPersonName model) {}

[HttpPatch]
[Route("person/{id:int:min(1)}")]
[PatchFor(typeof(PatchPersonAge))]
public void PatchPersonAge(int id, [FromBody]PatchPersonAge model) {}

完整的示例代码可以在here找到

【讨论】:

    【解决方案2】:

    你不能用相同的路由定义两个动作。 Action Selector 不考虑它们的参数类型。那么,为什么不合并这些操作;

    public async Task<IActionResult> postObj([FromBody]EntireData data)
    {
        if (data.FirstClass != null)
        {
            //Do something
        }
        if (data.SecondClass != null)
        {
            //Do something
        }
    }
    
    public class EntireData
    {
        public FirstClass  firstClass { get; set; }
    
        public SecondClass secondClass { get; set; }
    }
    

    【讨论】:

    • 请注意,在这种情况下,您必须以不同的方式发布对象。因此,如果您之前将 firstClass 数据发布为 JSON,那么现在您必须将其发布为 {"firstClass": {}}
    • @juunas,是的,它必须是这样的。谢谢提醒。
    • 谢谢,但我之前也尝试过。问题是我无法控制发布的对象,所以它对我不起作用。我正在尝试基于此的东西,我们会看到。
    • 它不符合所需的完整方式,例如我希望使用以下端点:PATCH /user/1 {"gender": "male"}PATCH /user/1 {"age": 33}。是的,我可以拥有可能同时具有年龄和名称的模型并检查空值,但是当用户决定删除某些信息时,我将如何执行PATCH /user/1 {"gender": null}?似乎解决方案应该在自定义激活/约束/绑定周围的某个地方
    【解决方案3】:

    作为免责声明,我认为这有点不合时宜。我会推动将发送给您的对象更改为一个可以代表任何一种情况的对象,或者将两种不同的对象类型发布到两个不同的 URI。

    除此之外,让它工作的一个选项是按照本教程创建一个自定义 IModelBinder: https://dotnetcoretutorials.com/2016/12/28/custom-model-binders-asp-net-core/

    这里的关键是您要绑定的模型将是一个基类,您的两个不同的类都派生自它。

    • 您的自定义模型绑定器将根据输入数据选择要创建的模型并执行此操作。
    • 您的控制器操作将输入基本类型并配置为使用您的自定义活页夹。
    • 您的操作需要检查传入的对象的实际类型并进行相应的处理。

    【讨论】:

    • 从技术上讲,这应该可行,但该解决方案存在一个问题 - 它会破坏像 Swashbuckle 这样的工具,因为它们使用反射 - 生成的文档将有 1 个带有基类的端点,而不是带有具体模型的多个端点。
    猜你喜欢
    • 2018-11-06
    • 1970-01-01
    • 2021-02-22
    • 2019-01-01
    • 1970-01-01
    • 2015-08-07
    • 2019-10-09
    • 2019-09-24
    • 2021-06-30
    相关资源
    最近更新 更多