按照标题,我希望能够在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/

10-17 02:26