Как десериализовать сообщение в строго типизированный объект, а затем динамически вызвать назначенный обработчику для этого сообщения обработчик - PullRequest
0 голосов
/ 31 мая 2018

Фу, какой заголовок ...

Я работаю на сервере для учебного проекта.Я потратил довольно много времени, пытаясь понять, как правильно сформулировать этот вопрос.В начале, я не думаю, что я даже точно знал, чего я пытался достичь.

Я работаю с сервером, который имеет N компонентов (N> = 0).

  • Каждый компонент добавляется во время выполнения с использованием DI.
  • Каждый компонент представляет собой «черный ящик».Когда он получает сообщение, он считается «единицей работы» и имеет все необходимое для перехода от начала к концу.
  • Каждый компонент отвечает за предоставление информации от subscribing до message,и предоставив handler для этого сообщения.Я планирую добиться этого, используя атрибут функции handler.

Пример «обработчика»:

[Handles(ExampleMessage)]
private void handleExampleMessage(ExampleMessage message)
{
    DoStuff();
}

Это самый ясный способ, который я могу придуматьОбрамляя мой вопрос:

Как я могу получить типизированную систему "брокера сообщений", например, как ASP.NET MVC предоставляет типизированные модели для controller actionс сериализованного входа.

Итак, я бы хотел добиться:

Serialized message -> Strongly typed message -> message service -> call handler function with *strongly typed* message as an argument

Я подумал о нескольких вещах:

Первое, что я попробовал, было просто десериализовать сообщение в dynamic, но при этом не было слишком большой проверки и проверки во время компиляции.Стоимость для простоты dynamic для меня.

Затем я попытался создать статические десериализационные методы во время выполнения, используя отражение и используя возвращаемое значение из них для вызова «обработчиков», но получилось так некрасиво и спагетти, я просто должен был бросить его (хотя я, конечно, все еще открыт для этого варианта, если у кого-то есть элеганет, с точки зрения производительности)

Наконец я попытался использовать тип Message, от которого наследуются все сообщения, но в итоге я застреваю, когда пытаюсь использовать Dictionary<Action<Message>, Message> для вызова соответствующих обработчиков

1 Ответ

0 голосов
/ 31 мая 2018

Это возможно, и только немного сложнее.Вам нужно будет выполнить поиск в ваших компонентах методов, имеющих атрибут Handles, и вызвать их с помощью отражения.

Предположим, у нас есть следующие интерфейсы:

public interface IComponent
{
}

public interface IMessage
{
};

Давайте также создадим атрибут Handles, который позволит нам помечать методы как обрабатывающие определенный тип сообщения:

[AttributeUsage(AttributeTargets.Method)]
public class HandlesAttribute : Attribute
{
    public Type MessageType { get; private set; }

    public HandlesAttribute(Type messageType)
    {
        MessageType = messageType;
    }
};

Теперь мы создадим брокер сообщений, который будет отвечать за поиск всех методов обработки сообщений вданный список компонентов.Мы будем использовать отражение, чтобы сделать это.Сначала мы найдем все методы с атрибутом Handles, а затем проверим, имеют ли они обязательный единственный параметр IMessage:

public class MessageBroker
{
    // Encapsulates a target object and a method to call on that object.
    // This is essentially our own version of a delegate that doesn't require
    // us to explicitly name the type of the arguments the method takes.
    private class Handler
    {
        public IComponent Component;
        public MethodInfo Method;
    };

    private Dictionary<Type, List<Handler>> m_messageHandlers = new Dictionary<Type, List<Handler>>();

    public MessageBroker(List<IComponent> components)
    {
        foreach (var component in components)
        {
            var componentType = component.GetType();

            // Get all private and public methods.
            var methods = componentType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
            foreach (var method in methods)
            {
                // If this method doesn't have the Handles attribute then ignore it.
                var handlesAttributes = (HandlesAttribute[])method.GetCustomAttributes(typeof(HandlesAttribute), false);
                if (handlesAttributes.Length != 1)
                    continue;

                // The method must have only one argument.
                var parameters = method.GetParameters();
                if (parameters.Length != 1)
                {
                    Console.WriteLine(string.Format("Method {0} has too many arguments", method.Name));
                    continue;
                }

                // That one argument must be derived from IMessage.
                if (!typeof(IMessage).IsAssignableFrom(parameters[0].ParameterType))
                {
                    Console.WriteLine(string.Format("Method {0} does not have an IMessage as an argument", method.Name));
                    continue;
                }

                // Success, so register!
                RegisterHandler(handlesAttributes[0].MessageType, component, method);
            }
        }
    }

    // Register methodInfo on component as a handler for messageType messages.
    private void RegisterHandler(Type messageType, IComponent component, MethodInfo methodInfo)
    {
        List<Handler> handlers = null;
        if (!m_messageHandlers.TryGetValue(messageType, out handlers))
        {
            // If there are no handlers attached to this message type, create a new list.
            handlers = new List<Handler>();
            m_messageHandlers[messageType] = handlers;
        }

        var handler = new Handler() { Component = component, Method = methodInfo };
        handlers.Add(handler);
    }
}

Конструктор, приведенный выше, регистрирует предупреждающее сообщение и игнорируетлюбые методы, которые не соответствуют сигнатуре, которая нам нужна (т.е. один параметр, который получается из IMessage).

Теперь давайте добавим метод для обработки сообщения.Это вызовет любые зарегистрированные обработчики, использующие Invoke:

    // Passes the given message to all registered handlers that are capable of handling this message.
    public void HandleMessage(IMessage message)
    {
        List<Handler> handlers = null;
        var messageType = message.GetType();
        if (m_messageHandlers.TryGetValue(messageType, out handlers))
        {
            foreach (var handler in handlers)
            {
                var target = handler.Component;
                var methodInfo = handler.Method;

                // Invoke the method directly and pass in the method object.
                // Note that this assumes that the target method takes only one parameter of type IMessage.
                methodInfo.Invoke(target, new object[] { message });
            }
        }
        else
        {
            Console.WriteLine(string.Format("No handler found for message of type {0}", messageType.FullName));
        }
    }
};

И теперь, чтобы проверить это, мы будем использовать эти примеры сообщений и компонентов.Я также добавил несколько неправильно настроенных методов для тестирования (то есть, неверные параметры):

public class ExampleMessageA : IMessage
{
};

public class ExampleMessageB : IMessage
{
};

public class ExampleComponent : IComponent
{
    [Handles(typeof(ExampleMessageA))]
    public void HandleMessageA(ExampleMessageA message)
    {
        Console.WriteLine(string.Format("Handling message of type ExampleMessageA: {0}", message.GetType().FullName));
    }

    [Handles(typeof(ExampleMessageB))]
    public void HandleMessageB(ExampleMessageB message)
    {
        Console.WriteLine(string.Format("Handling message of type ExampleMessageB: {0}", message.GetType().FullName));
    }

    [Handles(typeof(ExampleMessageA))]
    public void HandleMessageA_WrongType(object foo)
    {
        Console.WriteLine(string.Format("HandleMessageA_WrongType: {0}", foo.GetType().FullName));
    }

    [Handles(typeof(ExampleMessageA))]
    public void HandleMessageA_MultipleArgs(object foo, object bar)
    {
        Console.WriteLine(string.Format("HandleMessageA_WrongType: {0}", foo.GetType().FullName));
    }
}

И, наконец, чтобы собрать все это вместе:

var components = new List<IComponent>() { new ExampleComponent() };
var messageBroker = new MessageBroker(components);

// A message has been received and deserialised into the correct type.
// For prototyping here we will just instantiate it.
var messageA = new ExampleMessageA();
messageBroker.HandleMessage(messageA);

var messageB = new ExampleMessageB();
messageBroker.HandleMessage(messageB);

Вы должны получить следующий вывод:

Method HandleMessageA_WrongType does not have an IMessage as an argument
Method HandleMessageA_MultipleArgs has too many arguments
Handling message of type ExampleMessageA: Program+ExampleMessageA
Handling message of type ExampleMessageB: Program+ExampleMessageB

Полная скрипка, с которой вы можете играть: здесь .

Чтобы улучшить производительность вызова метода, вы можете переписать MethodInfo.Invoke, используя упомянутые методы здесь .

...