首页 > 解决方案 > 在 asp.net core api 中为复杂类型保持相同的休息端点

问题描述

我有一个 Rest 端点,我们称之为标签

http://api/tags

它创建传递此 json 格式的标签对象:

[{
   "TagName" : "IntegerTag",
   "DataType" : 1,
   "IsRequired" : true
}]

如果我想维护相同的端点来创建新标签但具有不同的 json 格式。假设我想创建一个 ListTag

[{
   "TagName" : "ListTag",
   "DataType" : 5,
   "Values" : ["Value1", "Value2", "Value3"]
   "IsRequired" : true
}]]

或 RangeTag

[{
   "TagName" : "RangeTag",
   "DataType" : 6,
   "Min": 1,
   "Max": 10,
   "IsRequired" : true
}]

我对 C# 在我的控制器 api 上创建一个新的 Dto 并将其作为不同的参数传递没有任何问题,因为 C# 承认方法重载:

void CreateTags(TagForCreateDto1 dto){…}

void CreateTags(TagForCreateDto2 dto){…}

但是当我需要在同一个控制器中使用 POST 请求来维护这两种方法来创建标签时,mvc 不允许相同的路由同时拥有这两种方法。

[HttpPost]
void CreateTags(TagForCreateDto1 dto){…}
[HttpPost]
void CreateTags(TagForCreateDto2 dto){…}

处理请求时发生未处理的异常。AmbiguousActionException:匹配多个操作。以下操作匹配路由数据并满足所有约束。

请指教

标签: c#restasp.net-coreasp.net-core-webapirestful-url

解决方案


实现您想要的一种方法,即拥有一个POST endpoint同时能够发布不同“版本”的方法Tags是创建一个自定义JsonConverter.

基本上,由于您已经拥有一个DataType可用于识别Tag它是哪种类型的属性,因此很容易将其序列化为正确的类型。所以,在代码中它看起来像这样:

BaseTag> ListTag,RangeTag

public class BaseTag
{
    public string TagName { get; set; }

    public int DataType { get; set; }

    public bool IsRequired { get; set; }
}

public sealed class ListTag : BaseTag
{
    public ICollection<string> Values { get; set; }
}

public sealed class RangeTag: BaseTag
{
    public int Min { get; set; }

    public int Max { get; set; }
}

然后,习俗PolymorphicTagJsonConverter

public class PolymorphicTagJsonConverter : JsonConverter
{
    public override bool CanWrite => false;

    public override bool CanConvert(Type objectType) 
        => typeof(BaseTag).IsAssignableFrom(objectType);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        => throw new NotImplementedException();

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader == null) throw new ArgumentNullException("reader");
        if (serializer == null) throw new ArgumentNullException("serializer");
        if (reader.TokenType == JsonToken.Null)
            return null;

        var jObject = JObject.Load(reader);

        var target = CreateTag(jObject);
        serializer.Populate(jObject.CreateReader(), target);
        return target;
    }       

    private BaseTag CreateTag(JObject jObject)
    {
        if (jObject == null) throw new ArgumentNullException("jObject");
        if (jObject["DataType"] == null) throw new ArgumentNullException("DataType");

        switch ((int)jObject["DataType"])
        {
            case 5:
                return new ListTag();
            case 6:
                return new RangeTag();
            default:
                return new BaseTag();
        }
    }
}

繁重的工作是在ReadJsonCreate方法上完成的。Create接收一个JObject并在其中检查DataType属性以确定Tag它是哪种类型。然后,继续为适当的ReadJson调用。PopulateJsonSerializerType

然后您需要告诉框架使用您的自定义转换器:

[JsonConverter(typeof(PolymorphicTagJsonConverter))]
public class BaseTag 
{ 
   // the same as before
}

最后,您可以只拥有一个POST可以接受所有类型标签的端点:

[HttpPost]
public IActionResult Post(ICollection<BaseTag> tags)
{
    return Ok(tags);
}

一个缺点是switch在转换器上。你可能会同意或不同意......你可以做一些聪明的工作并尝试让标签类以某种方式实现一些接口,这样你就可以调用CreateBaseTag,它会在运行时将调用转发给正确的接口,但我猜您可以开始使用它,如果复杂性增加,那么您可以考虑以更智能/更自动的方式找到正确的Tag类。


推荐阅读