如何在Asp.net Core运行时从DependencyInjection解析通用类型服务

时间:2018-11-11 14:28:32

标签: c# generics asp.net-core dependency-injection type-inference

我正在处理一个将XML消息发布到我的服务器的微信项目。该消息可以是几种类型中的任何一种。因此,我首先使用基本的“ WxMessage”将消息反序列化为相应的对象,然后将该对象返回给分派器,后者将找出正确的消息处理程序来处理该消息。与每种消息类型相对应的处理程序都使用IWxMessageHandler <>接口注册到Asp.net核心2.1的DependencyInjection。

services.AddScoped<IWxMessageHandler<WxMessage_Image>, WxMessageHandler_Image>();
services.AddScoped<IWxMessageHandler<WxMessage_Text>, WxMessageHandler_Text>();
services.AddScoped<IWxMessageHandler<WxMessage_File>, WxMessageHandler_File>();

这是消息服务:

    public async Task<string> Handle(string messageBody)
    {
        var reply = default(WxMessage);

        using (var deserial = new WxInMessageDeserializer())
        {
            var deserializedModel =  deserial.Parse(messageBody);

            reply = (WxReplyMessage_Text) await HandleMessage(deserializedModel);
        }
        return await Task.FromResult(BuildXmlContent(reply));
    }

    public async Task<WxMessage> HandleMessage<TMessage>(TMessage message) 
    {
        var _handler = _serviceProvider.GetService<IWxMessageHandler<TMessage>>();
        ......
    }

但是,由于WxInMessageDeserializer返回WxMessage的基础对象,它未能解决IWxMessageHandler <>的正确实现:

        var result = default(WxMessage);
        switch (_msgTYpe)
            {
                case "text": result = (WxMessage_Text)new XmlSerializer(typeof(WxMessage_Text))
                        .Deserialize(new StringReader(rawMessage));
                    break;
                case "image": result = (WxMessage_Image)new XmlSerializer(typeof(WxMessage_Image))
                        .Deserialize(new StringReader(rawMessage));
                    break;
                case "voice": result = (WxPushMessage_Voice)new XmlSerializer(typeof(WxPushMessage_Voice))
                        .Deserialize(new StringReader(rawMessage));
                    break;
                ......
                case "file": result = (WxMessage_File)new XmlSerializer(typeof(WxMessage_File))
                        .Deserialize(new StringReader(rawMessage));
                    break;
            }
        return result;

我猜想这是C#类型推断的局限性,因为当我使用诸如HandleMessage(somemessage)之类的显式调用方法时,结果是正确的。目前,我正在通过旋转所有服务的列表来解决此问题,然后选择所需的服务:

        var type = typeof(TMessage);
        var _handler = _handlers
            .Where(h => h.GetHandlingType().Equals(message.GetType()))
            .FirstOrDefault();
        var result = _handler.Handle(message);
        return await result;

但是我想知道是否有更好的解决方案来解决这个问题。 谢谢任何人的帮助。

1 个答案:

答案 0 :(得分:1)

问题在于泛型类型是在编译时求值的。因此,即使您的HandleMessage是通用的HandleMessage<TMessage>(TMessage message),使用的TMessage的类型也不是运行时类型。

调用方法的方式如下:

var deserializedModel = deserial.Parse(messageBody);
reply = (WxReplyMessage_Text) await HandleMessage(deserializedModel);

因此,deserializedModel的编译时类型决定了泛型类型参数TMessage将使用哪种类型。

由于Parse本身不是通用的,因此它可能会返回一个基本类型,然后该基本类型将用于HandleMessage。这样,在HandleMessage中,TMessage将是该基本类型,对GetService<IWxMessageHandler<TMessage>>的调用将使用该基本类型来检索处理程序服务实例。

如果要获取实际的处理程序,则必须从服务提供者中检索具体服务类型。为了从您的消息对象的运行时类型做到这一点,您将需要使用反射来检索和构造该类型:

public async Task<WxMessage> HandleMessage(IWxMessage message) 
{
    var messageType = message.GetType();
    var messageHandlerType = typeof(IWxMessageHandler<>).MakeGenericType(messageType);

    var handler = _serviceProvider.GetService(messageHandlerType);

    // …
}

这假设消息的基本类型为IWxMessage,并且使HandleMessage方法非通用。取而代之的是,处理程序类型将在编译时从具体消息类型中解析出来。请注意,您还应该为您的消息处理程序提供一些基本接口,该接口允许您在所有类型上调用方法(理想情况下只是采用IWxMessage),否则,您还必须使用反射来调用处理程序。