简体   繁体   中英

How to resolve a generic type service from the DependencyInjection at Asp.net Core runtime

I am dealing with a WeChat project that post XML message to my server. The message could be any one of several types. Hence I first deserialize the message to corresponding object with a base "WxMessage", and then return the object to a dispatcher, which will find out a correct message handler to handle the message. The handlers, corresponding to each type of messages, were registered to the DependencyInjection of Asp.net core 2.1 with a IWxMessageHandler<> interface.

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

This is the message service:

    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>>();
        ......
    }

However it failed to resolve the correct implement of IWxMessageHandler<> as the WxInMessageDeserializer return a base object of WxMessage:

        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;

I guess this is a limitation of the C# Type Inferencing, as the result was correct when I use explicit calling method like HandleMessage(somemessage). Currently I am solving this issue by revolving a list of all services and then select the one I want:

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

Yet I am wondering if there is any better solutions to this issue. Thanks if anyone can help.

The problem is that generic types are evaluated at compile time. So even if your HandleMessage is a generic HandleMessage<TMessage>(TMessage message) , the type of TMessage that is used is not a runtime type.

The way you call your method is like this:

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

So the compile-time type of deserializedModel determines what type will be used for the generic type argument TMessage .

Since Parse is not generic itself, it will likely return a base type, and that base type will then be used for HandleMessage . As such, inside HandleMessage , TMessage will be that base type and the call to GetService<IWxMessageHandler<TMessage>> will use that base type to retrieve the handler service instance.

If you want to get the actual handler, you will have to retrieve the concrete service type from the service provider. And in order to do that from a run-time type of your message object, you will need to use reflection to retrieve and construct that type:

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

    var handler = _serviceProvider.GetService(messageHandlerType);

    // …
}

This assumes that you have a base type IWxMessage for the message and makes the HandleMessage method non-generic. Instead, the handler type will be resolved at compile-time from the concrete message type. Note that you should also have some base interface for your message handler that allows you to call a method on all types (ideally just taking an IWxMessage ), otherwise you will also have to use reflection to call the handler.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM