Как разрешить службу универсального типа из DependencyInjection во время выполнения Asp.net Core - PullRequest
0 голосов
/ 11 ноября 2018

Я имею дело с проектом WeChat, который отправляет сообщение XML на мой сервер. Сообщение может быть любого из нескольких типов. Поэтому я сначала десериализирую сообщение в соответствующий объект с базовым «WxMessage», а затем возвращаю объект диспетчеру, который найдет правильный обработчик сообщения для обработки сообщения. Обработчики, соответствующие каждому типу сообщений, были зарегистрированы в DependencyInjection ядра 2.1 Asp.net с интерфейсом IWxMessageHandler <>.

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

Однако не удалось найти правильную реализацию IWxMessageHandler <>, так как WxInMessageDeserializer возвращает базовый объект 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;

Я полагаю, что это ограничение Inferencing типа 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 голосов
/ 11 ноября 2018

Проблема в том, что универсальные типы оцениваются во время компиляции. Таким образом, даже если ваш 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), в противном случае вам также придется использовать отражение для вызова обработчика.

...