Make names of named tuples appear in serialized JSON responses(使命名元组的名称出现在序列化的 JSON 响应中)
问题描述
情况:我有多个传递对象结构的 Web 服务 API 调用.目前,我声明了显式类型以将这些对象结构绑定在一起.为简单起见,举个例子:
Situation: I have multiple Web service API calls that deliver object structures. Currently, I declare explicit types to bind those object structures together. For the sake of simplicity, here's an example:
[HttpGet]
[ProducesResponseType(typeof(MyType), 200)]
public MyType TestOriginal()
{
return new MyType { Speed: 5.0, Distance: 4 };
}
改进:我有很多这些自定义类,例如 MyType
,我希望改用通用容器.我遇到了命名元组,并且可以在我的控制器方法中成功使用它们,如下所示:
Improvement: I have loads of these custom classes like MyType
and would love to use a generic container instead. I came across named tuples and can successfully use them in my controller methods like this:
[HttpGet]
[ProducesResponseType(typeof((double speed, int distance)), 200)]
public (double speed, int distance) Test()
{
return (speed: 5.0, distance: 4);
}
问题我面临的是解析的类型是基于底层的 Tuple
包含这些无意义的属性 Item1
, Item2
等示例:
Problem I am facing is that the resolved type is based on the underlying Tuple
which contains these meaningless properties Item1
, Item2
etc. Example:
问题:有没有人找到将命名元组的名称序列化到我的 JSON 响应中的解决方案?或者,有没有人找到一个通用的解决方案,允许为 random 结构提供单个类/表示,以便 JSON 响应明确命名它包含的内容.
Question: Has anyone found a solution to get the names of the named tuples serialized into my JSON responses? Alternatively, has anyone found a generic solution that allows to have a single class/representation for random structures that can be used so that the JSON response explicitly names what it contains.
推荐答案
对于序列化响应,只需在操作和自定义合同解析器上使用任何自定义属性(不幸的是,这只是解决方案,但我仍在寻找任何更优雅的一个).
For serializing response just use any custom attribute on action and custom contract resolver (this is only solution, unfortunately, but I'm still looking for any more elegance one).
属性:
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 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);
if (type.Name.Contains(nameof(ValueTuple)))
{
for (var i = 0; i < properties.Count; i++)
{
properties[i].PropertyName = _names[i];
}
_names = _names.Skip(properties.Count).ToList();
}
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:
This one returns next JSON:
[
{
"id":0,
"name":"Other"
},
{
"id":1,
"name":"Shipping Document"
}
]
<小时>
Swagger UI 的解决方案如下:
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,
},
});
}
这篇关于使命名元组的名称出现在序列化的 JSON 响应中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:使命名元组的名称出现在序列化的 JSON 响应中
- MoreLinq maxBy vs LINQ max + where 2022-01-01
- C#MongoDB使用Builders查找派生对象 2022-09-04
- C# 中多线程网络服务器的模式 2022-01-01
- 输入按键事件处理程序 2022-01-01
- 良好实践:如何重用 .csproj 和 .sln 文件来为 CI 创建 2022-01-01
- Web Api 中的 Swagger .netcore 3.1,使用 swagger UI 设置日期时间格式 2022-01-01
- 在哪里可以找到使用中的C#/XML文档注释的好例子? 2022-01-01
- WebMatrix WebSecurity PasswordSalt 2022-01-01
- 如何用自己压缩一个 IEnumerable 2022-01-01
- 带有服务/守护程序应用程序的 Microsoft Graph CSharp SDK 和 OneDrive for Business - 配额方面返回 null 2022-01-01