Hei*_*ich 2 c# rest restful-url asp.net-core asp.net-core-webapi
我有一个 Rest 端点,我们称之为标签
\n\n\n\n它创建传递此 json 格式的标签对象:
\n\n[{\n "TagName" : "IntegerTag",\n "DataType" : 1,\n "IsRequired" : true\n}]\nRun Code Online (Sandbox Code Playgroud)\n\n如果我想维护相同的端点来创建新标签但具有不同的 json 格式。假设我想创建一个 ListTag
\n\n[{\n "TagName" : "ListTag",\n "DataType" : 5,\n "Values" : ["Value1", "Value2", "Value3"]\n "IsRequired" : true\n}]]\nRun Code Online (Sandbox Code Playgroud)\n\n或范围标签
\n\n[{\n "TagName" : "RangeTag",\n "DataType" : 6,\n "Min": 1,\n "Max": 10,\n "IsRequired" : true\n}]\nRun Code Online (Sandbox Code Playgroud)\n\n我在 C# 上在控制器 api 上创建一个新的 Dto 并将其作为不同的参数传递时没有任何问题,因为 C# 允许方法重载:
\n\nvoid CreateTags(TagForCreateDto1 dto){\xe2\x80\xa6}\n\nvoid CreateTags(TagForCreateDto2 dto){\xe2\x80\xa6}\nRun Code Online (Sandbox Code Playgroud)\n\n但是,当我需要在同一个控制器中维护两种带有 POST 请求的方法来创建标签时,mvc 不允许同一路由同时拥有这两种方法。
\n\n[HttpPost]\nvoid CreateTags(TagForCreateDto1 dto){\xe2\x80\xa6}\n[HttpPost]\nvoid CreateTags(TagForCreateDto2 dto){\xe2\x80\xa6}\nRun Code Online (Sandbox Code Playgroud)\n\n\n\n\n处理请求时发生未处理的异常。\n AmbigouslyActionException: 多个操作匹配。以下操作与路线数据匹配并满足所有约束。
\n
请指教
\nPOST 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; }
}
Run Code Online (Sandbox Code Playgroud)
那么,习俗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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
繁重的工作是通过方法来完成ReadJson的Create。Create接收一个JObject并在其内部检查该DataType属性以确定Tag其类型。然后,ReadJson只需继续调用Populate适当JsonSerializer的Type.
您需要告诉框架使用您的自定义转换器:
[JsonConverter(typeof(PolymorphicTagJsonConverter))]
public class BaseTag
{
// the same as before
}
Run Code Online (Sandbox Code Playgroud)
最后,您可以只拥有一个POST接受所有类型标签的端点:
[HttpPost]
public IActionResult Post(ICollection<BaseTag> tags)
{
return Ok(tags);
}
Run Code Online (Sandbox Code Playgroud)
一个缺点是switch转换器。你可能同意或不接受它..你可以做一些聪明的工作,并尝试让标签类以某种方式实现一些接口,这样你就可以调用Create它BaseTag,它会在运行时将调用转发到正确的接口,但我猜您可以开始使用此方法,如果复杂性增加,那么您可以考虑采用更智能/更自动的方式来查找正确的Tag类。
您可以利用工厂模式,该模式将根据 JSON 输入返回您想要创建的标签。创建一个工厂,称之为TagsFactory,它实现以下接口:
public interface ITagsFactory
{
string CreateTags(int dataType, string jsonInput);
}
Run Code Online (Sandbox Code Playgroud)
创建一个 TagsFactory 如下所示:
public class TagsFactory : ITagsFactory
{
public string CreateTags(int dataType, string jsonInput)
{
switch(dataType)
{
case 1:
var intTagsDto = JsonConvert.DeserializeObject<TagForCreateDto1(jsonInput);
// your logic to create the tags below
...
var tagsModel = GenerateTags();
return the JsonConvert.SerializeObject(tagsModel);
case 5:
var ListTagsDto = JsonConvert.DeserializeObject<TagForCreateDto2>(jsonInput);
// your logic to create the tags below
...
var tagsModel = GenerateTags();
return the JsonConvert.SerializeObject(tagsModel);
}
}
}
Run Code Online (Sandbox Code Playgroud)
为了更多地分离关注点,您可以将GenerateTags逻辑从工厂移到它自己的类中。
一旦上述内容到位,我建议对您的
TagsController. 将以下参数添加到CreateTags操作中
- 数据类型或标记名称。使用更容易处理和阅读的内容
[FromHeader]- jsonInput 并使用读取它
[FromBody]
然后,您的控制器将如下所示,利用通过 DI 注入的 ITagsFactory
[Route("api")]
public class TagsController : Controller
{
private readonly ITagsFactory _tagsFactory;
public TagsController(ITagsFactory tagsFactory)
{
_tagsFactory= tagsFactory;
}
[HttpPost]
[Route("tags")]
public IActionResult CreateTags([FromHeader(Name = "data-type")] string dataType, [FromBody] string jsonInput)
{
var tags = _tagsFactory.CreateTags(dataType, jsonInput);
return new ObjectResult(tags)
{
StatusCode = 200
};
}
}
Run Code Online (Sandbox Code Playgroud)
工作快完成了。但是,为了从正文中读取原始 JSON 输入,您需要添加CustomInputFormatter并在启动时注册它
public class RawRequestBodyInputFormatter : InputFormatter
{
public RawRequestBodyInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
}
public override bool CanRead(InputFormatterContext context)
{
return true;
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body))
{
var content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
}
Run Code Online (Sandbox Code Playgroud)
TagsFactory在启动中注册格式化程序和 ,如下所示:
services.AddSingleton<ITagsFactory, TagsFactory>();
services.AddMvc(options =>
{
options.InputFormatters.Insert(0, new RawRequestBodyInputFormatter());
}
Run Code Online (Sandbox Code Playgroud)
这样你的端点将保持不变。如果您需要添加更多 TagType,只需将该大小写添加到TagsFactory. 你可能会认为这是违反 OCP 的。然而,工厂需要知道它需要创建什么样的对象。如果你想更抽象,你可以使用 AbstractFactory,但我认为这有点矫枉过正了。
| 归档时间: |
|
| 查看次数: |
3007 次 |
| 最近记录: |