2

根据标题,我希望能够在 Startup.cs 中添加开放的通用 InputFormatter 和 OutputFormatter 实例作为 ConfigureServices 的一部分,类似于如何添加开放的通用服务。

我想要的看起来像这样:

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 将来自端点。

4

1 回答 1

2

我认为这是不可能的,它只是不是为了将当前模型类型作为通用类型参数提供给格式化程序(如果格式化程序是通用的)。

如果您测量到反射(通过使用非通用版本)会减慢您的速度,我建议您改用编译表达式树,并缓存它们。例如,在这种情况下,您需要:

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);
于 2018-04-10T08:06:40.287 回答