按照标题,我希望能够在Startup.cs的ConfigureServices中添加开放的通用InputFormatter和OutputFormatter实例,类似于添加开放的通用服务的方式。
我想要的看起来像这样:
services.AddMvc(options =>
{
options.InputFormatters.Add(new ProtobufInputFormatter<>());
options.OutputFormatters.Add(new ProtobufOutputFormatter<>());
options.FormatterMappings.SetMediaTypeMappingForFormat("protobuf", MediaTypeHeaderValue.Parse("application/x-protobuf"));
});
这有可能吗?
编辑:
当前实现的ProtobufOutputFormatter的示例
public class ProtobufInputFormatter : InputFormatter
{
static MediaTypeHeaderValue protoMediaType = MediaTypeHeaderValue.Parse("application/x-protobuf");
public override bool CanRead(InputFormatterContext context)
{
var request = context.HttpContext.Request;
MediaTypeHeaderValue requestContentType = null;
MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
if (requestContentType == null)
{
return false;
}
return requestContentType.IsSubsetOf(protoMediaType);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
try
{
var request = context.HttpContext.Request;
var obj = (IMessage)Activator.CreateInstance(context.ModelType);
obj.MergeFrom(request.Body);
return InputFormatterResult.SuccessAsync(obj);
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex);
return InputFormatterResult.FailureAsync();
}
}
}
问题是使用反射不像我能够使用通用的Protobuf解串器那样高效,例如:
public class ProtobufInputFormatter<T> : InputFormatter where T : IMessage<T>, new()
{
static MediaTypeHeaderValue protoMediaType = MediaTypeHeaderValue.Parse("application/x-protobuf");
public override bool CanRead(InputFormatterContext context)
{
var request = context.HttpContext.Request;
MediaTypeHeaderValue requestContentType = null;
MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
if (requestContentType == null)
{
return false;
}
return requestContentType.IsSubsetOf(protoMediaType);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
try
{
var request = context.HttpContext.Request;
var serialiser = new ProtobufSerialiser<T>();
var obj = serialiser.Deserialise(request.Body);
return InputFormatterResult.SuccessAsync(obj);
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex);
return InputFormatterResult.FailureAsync();
}
}
ProtobufSerialiser是Google的包装器。Protobuf(出于性能方面的考虑,与缓冲池配合使用,而对Activator.CreateInstance的需求则颠覆了这一点,如上述实际实现和工作的非通用示例所示)。
T将来自端点。
最佳答案
我认为这是不可能的,只是没有将当前模型类型作为格式化程序的通用类型参数提供(如果格式化程序是通用的)。
如果您测量到反射(通过使用非通用版本)使您的速度变慢,我建议改用编译的表达式树,并缓存它们。例如,在这种情况下,您需要:
var serialiser = new ProtobufSerialiser<T>();
var obj = serialiser.Deserialise(request.Body);
可以表示为表达式:
Expression<Func<Stream, object>> body => new ProtobufSerializer<T>().Deserialize(body);
假设您
ProtobufSerializer<T>
是这样的:class ProtobufSerializer<T> where T: new() {
public T Deserialize(Stream body) {
...
}
}
您可以在运行时像上面这样构造表达式:
public static class SerializersCache {
private static readonly ConcurrentDictionary<Type, Func<Stream, object>> _cache = new ConcurrentDictionary<Type, Func<Stream, object>>();
public static object Deserialize(Type type, Stream body) {
var handler = _cache.GetOrAdd(type, (key) => {
var arg = Expression.Parameter(typeof(Stream), "body");
var genericSerializer = typeof(ProtobufSerializer<>).MakeGenericType(key);
// new ProtobufSerializer<T>();
var instance = Expression.New(genericSerializer.GetConstructor(new Type[0]));
// new ProtobufSerializer<T>().Deserialize(body);
var call = Expression.Call(instance, "Deserialize", new Type[0], arg);
// body => new ProtobufSerializer<T>().Deserialize(body);
return Expression.Lambda<Func<Stream, object>>(call, arg).Compile();
});
return handler(body);
}
}
您的(非通用)输入格式化程序将变为:
var request = context.HttpContext.Request;
var obj = SerializersCache.Deserialize(context.ModelType, request.Body);
关于c# - 有什么方法可以通过Startup中的ConfigureServices将开放的通用格式器添加到ASP.Net Core 2.0中,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/49746943/