Mediatr с универсальным обработчиком и запросом

Я работаю над приложением веб-API ASP.NET Core 2.2 с Mediatr.

У меня есть обработчик, который выглядит так -

public class MyQueryHandler<T> : IRequestHanlder<MyQuery<T>, IQueryable<T>>
{
   public Task<IQueryable<T>> Handle(MyQuery<T> myquery, CancellationToken cancellationToken)
   {
       //perform query

       IQueryable<T> models = someDatabaseQuery.ProjectTo<T>();
   }       
}

Это запрос -

public class MyQuery<T> : IRequest<IQueryable<T>>
{
   //some properties
}

Когда я пытаюсь сделать такой запрос -

var result = await _mediator.Send(new MyQuery<SomeModel> {/* set the properties on the query */})

У меня исключение -

An unhandled exception occurred while processing the request.

InvalidOperationException: Handler was not found for request of type MediatR.IRequestHandler`2[MyQuery`1[SomeModel],System.Linq.IQueryable`1[SomeModel]]. Register your handlers with the container. See the samples in GitHub for examples.

Я потратил довольно много часов, пробовал много чего, но ни один из них не помог. Я даже устал использовать Autofac вместе со сбором сервисов, следуя примеру, приведенному в репозитории Mediator на github.


person Bryan    schedule 15.08.2019    source источник
comment
Я думаю, что каждый объект Query должен иметь плоскую структуру типа Dtos, чтобы его обработчик можно было легко зарегистрировать во время выполнения. Если вы хотите создать общий обработчик запросов, почему бы просто не использовать Behaviors?   -  person GoldenAge    schedule 15.08.2019
comment
Подскажите, пожалуйста, какова была бы цель MyQuery<T>   -  person GoldenAge    schedule 15.08.2019
comment
В обработчике я использую проекции automapper, чтобы ограничить то, что запрашивается из рассматриваемой таблицы db. ‹T› позволяет вызывающему абоненту сообщать запрос и, в свою очередь, обработчику форму требуемых данных.   -  person Bryan    schedule 15.08.2019
comment
Я обновил свой ответ. Это ответ на ваш вопрос или вы хотели чего-то другого? Если нет, пожалуйста, дайте мне более подробную информацию, чтобы я попытался помочь. Я часами боролся с этой библиотекой, поэтому знаю вашу боль: D   -  person GoldenAge    schedule 16.08.2019
comment
У меня есть решение моей конкретной проблемы, постараюсь в ближайшее время написать ответ.   -  person Bryan    schedule 30.08.2019
comment
Вы нашли решение этой проблемы, @Bryan?   -  person MorganR    schedule 03.01.2020
comment
Да, я создал базовый обработчик и базовый запрос со всеми необходимыми мне свойствами, а затем создал определенные пустые запросы для нужных мне вариантов. После этого Д. работал нормально.   -  person Bryan    schedule 09.01.2020


Ответы (3)


Каждый запрос должен иметь конкретный тип / плоскую структуру, чтобы его обработчик мог быть легко зарегистрирован контейнером внедрения зависимостей во время выполнения. Я считаю, что регистрация универсального обработчика запросов, который вы привели в качестве примера, просто невозможна, поскольку контейнер DI может иметь проблемы с регистрацией универсальных типов. Я считаю, что создание поведения - это то, что вам нужно делать. Это может дать вам возможность обрабатывать все запросы или команды в одном месте, поэтому вы можете запустить некоторую дополнительную / общую логику, например, вести журнал и т. Д., Прежде чем вы попадете в обработчик данного _1 _ / _ 2_.

ИЗМЕНИТЬ

В обработчике я использую проекции automapper, чтобы ограничить то, что запрашивается из рассматриваемой таблицы db. Позволяет вызывающей стороне сообщать запросу и, в свою очередь, обработчику форму требуемых данных.

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

Итак, пример, например, Order сущность.

public class OrderDto
{
    public string Name { get; set; }

    public int Amount { get; set; }
}

public class FilterOrdersQuery : IRequest<List<OrderDto>>
{
    public string Filter { get; set; }
}

public class FilterOrdersQueryHandler : IRequestHandler<FilterOrdersQuery, List<OrderDto>>
{
    public Task<List<OrderDto>> Handle(FilterOrdersQuery notification, CancellationToken cancellationToken)
    {
        var dataSource = new List<OrderDto>(){
            new OrderDto()
            {
                Name = "blah",
                Amount = 65
            },
            new OrderDto()
            {
                Name = "foo",
                Amount = 12
            },
        };

        var result = dataSource
            .Where(x => x.Name.Contains(notification.Filter))              
            .ToList();

        return Task.FromResult(result);
    }
}

Это всего лишь простой пример, который показывает, как отфильтровать данную сущность и вернуть список отфильтрованных объектов. Вы также можете добавить логику для разбивки на страницы, OrderBy и т. Д. И т. Д.

person GoldenAge    schedule 15.08.2019

Если ваш обработчик находится в отдельной сборке, вам необходимо сообщить MediatR, где его искать. Метод AddMediatR принимает список сборок или типов, которые MediatR использует для поиска обработчиков в той же сборке.

В ConfigureServices вашего Startup класса вы добавляете MediatR, вероятно, следующим образом:

services.AddMediatR(typeof(Startup));

or

services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly);

Оба дают одинаковый результат - MediatR ищет обработчики в сборке, где находится класс Startup.

Если ваш обработчик находится в другой сборке, вы можете добавить его в MediatR следующим образом:

services.AddMediatR(typeof(Startup),
                    typeof(FooBar),
                    typeof(Some.Other.Class.In.Another.Assembly));
person Peter Hedberg    schedule 04.02.2021

Вам нужно добавить Autofac в свой Program.cs

// In your Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseServiceProviderFactory(new AutofacServiceProviderFactory())
        .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });

И в конце вашего запуска вам нужно добавить что-то вроде этого:

public void ConfigureContainer(ContainerBuilder builder)
    {
        Type[] entityTypes =
        {
            typeof(SomeModel)
            // add all the models you want
        };

        var handlerTypes = new List<Type>
        {
            typeof(MyQuery<>)
            // add all the handlers you want
        };

        foreach (Type entityType in entityTypes)
        foreach (Type handlerType in handlerTypes)
        {
            var handlerGenericType = (TypeInfo) handlerType.MakeGenericType(entityType);
            foreach (Type genericType in handlerGenericType.ImplementedInterfaces)
                builder.RegisterType(handlerGenericType).As(genericType);
        }
    }
person pdouelle    schedule 23.04.2021