情况:我有多个Web服务API调用来提供对象结构。目前,我声明显式类型将这些对象结构绑定在一起。为简单起见,这是一个例子:
[HttpGet]
[ProducesResponseType(typeof(MyType), 200)]
public MyType TestOriginal()
{
return new MyType { Speed: 5.0, Distance: 4 };
}
改进:我有很多这样的自定义类,例如MyType
,我很乐意使用通用容器。我遇到了命名元组,可以在我的控制器方法中成功使用它们:
[HttpGet]
[ProducesResponseType(typeof((double speed, int distance)), 200)]
public (double speed, int distance) Test()
{
return (speed: 5.0, distance: 4);
}
问题我面临的问题是,已解决的类型基于底层Tuple
,其中包含这些无意义的属性Item1
,Item2
等。示例:< / p>
问题:有没有人找到一个解决方案来将命名元组的名称序列化为我的JSON响应?或者,有人发现了一个通用的解决方案,允许为随机结构提供单个类/表示,以便JSON响应明确命名它包含的内容。
答案 0 :(得分:2)
您有一点竞标需求冲突
问题:
我有很多自定义类,如
MyType
,我很乐意使用 而是一个通用的容器
注释:
但是,我必须在ProducesResponseType中声明什么类型 属性明确显示我要返回的内容
基于以上内容 - 您应该使用已有的类型。这些类型在几个月后为其他开发人员/读者或您自己提供了有价值的代码文档。
从可读性的角度来看
[ProducesResponseType(typeof(Trip), 200)]
会更好
[ProducesResponseType(typeof((double speed, int distance)), 200)]
从可维护性的角度来看 添加/删除属性只需要在一个地方完成。使用通用方法时,您还需要记住更新属性。
答案 1 :(得分:1)
要序列化响应,只需在操作和自定义合同解析器上使用任何自定义属性(不幸的是,这只是解决方案,但我仍在寻找更多优雅)。
属性:
public class ReturnValueTupleAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var content = actionExecutedContext?.Response?.Content as ObjectContent;
if (!(content?.Formatter is JsonMediaTypeFormatter))
{
return;
}
var names = actionExecutedContext
.ActionContext
.ControllerContext
.ControllerDescriptor
.ControllerType
.GetMethod(actionExecutedContext.ActionContext.ActionDescriptor.ActionName)
?.ReturnParameter
?.GetCustomAttribute<TupleElementNamesAttribute>()
?.TransformNames;
var formatter = new JsonMediaTypeFormatter
{
SerializerSettings =
{
ContractResolver = new ValueTuplesContractResolver(names),
},
};
actionExecutedContext.Response.Content = new ObjectContent(content.ObjectType, content.Value, formatter);
}
}
ContractResolver :
public class ValueTuplesContractResolver : CamelCasePropertyNamesContractResolver
{
private readonly IList<string> _names;
public ValueTuplesContractResolver(IList<string> names)
{
_names = names;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
for (var i = 0; i < properties.Count; i++)
{
properties[i].PropertyName = _names[i];
}
return properties;
}
}
用法:
[ReturnValueTuple]
[HttpGet]
[Route("types")]
public IEnumerable<(int id, string name)> GetDocumentTypes()
{
return ServiceContainer.Db
.DocumentTypes
.AsEnumerable()
.Select(dt => (dt.Id, dt.Name));
}
此返回下一个JSON:
[
{
"id":0,
"name":"Other"
},
{
"id":1,
"name":"Shipping Document"
}
]
这是 Swagger用户界面的解决方案:
public class SwaggerValueTupleFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var action = apiDescription.ActionDescriptor;
var controller = action.ControllerDescriptor.ControllerType;
var method = controller.GetMethod(action.ActionName);
var names = method?.ReturnParameter?.GetCustomAttribute<TupleElementNamesAttribute>()?.TransformNames;
if (names == null)
{
return;
}
var responseType = apiDescription.ResponseDescription.DeclaredType;
FieldInfo[] tupleFields;
var props = new Dictionary<string, string>();
var isEnumer = responseType.GetInterface(nameof(IEnumerable)) != null;
if (isEnumer)
{
tupleFields = responseType
.GetGenericArguments()[0]
.GetFields();
}
else
{
tupleFields = responseType.GetFields();
}
for (var i = 0; i < tupleFields.Length; i++)
{
props.Add(names[i], tupleFields[i].FieldType.GetFriendlyName());
}
object result;
if (isEnumer)
{
result = new List<Dictionary<string, string>>
{
props,
};
}
else
{
result = props;
}
operation.responses.Clear();
operation.responses.Add("200", new Response
{
description = "OK",
schema = new Schema
{
example = result,
},
});
}
答案 2 :(得分:1)
改用匿名对象。
(double speed, int distance) = (5.0, 4);
return new { speed, distance };
答案 3 :(得分:0)
使用命名元组的问题是它们只是语法糖。
如果您选中named-and-unnamed-tuples documentation,则会找到部分内容:
这些同义词由编译器和语言处理,因此 您可以有效使用命名元组。 IDE和编辑器可以阅读这些内容 使用Roslyn API的语义名称。您可以引用元素 在相同位置的任何语义名称中命名元组 部件。 编译器将您定义的名称替换为Item * 生成编译输出时的等效项。编译 Microsoft中间语言(MSIL)不包括名称 您已经指定了这些元素。
因此,在运行时(而不是在编译期间)进行序列化时会遇到问题,并且您想使用在编译期间丢失的信息。一个人可以设计定制的序列化程序,在编译之前用一些代码对其进行初始化,以记住命名的元组名称,但是我想对于这个示例来说,这样的复杂性太高了。
答案 4 :(得分:0)
最简单的解决方案是使用dynamic
代码,即C#的ExpandoObject以您希望API具有的格式包装响应
public JsonResult<ExpandoObject> GetSomething(int param)
{
var (speed, distance) = DataLayer.GetData(param);
dynamic resultVM = new ExpandoObject();
resultVM.speed= speed;
resultVM.distance= distance;
return Json(resultVM);
}
“ GetData
”的返回类型为
(decimal speed, int distance)
这将以您期望的方式给出Json响应