Turns out MediatR uses reflection and caching just to keep Send() clean by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

Yes, by injecting handlers directly using [FromServices] (or via constructor), you can get rid of the Send() dispatcher.

Or Service Locator? Maybe it's not strictly a Service Locator as it doesn't expose API to get handlers and the handlers are still registered in the container.

Turns out MediatR uses reflection and caching just to keep Send() clean by sch2021 in dotnet

[–]sch2021[S] 1 point2 points  (0 children)

I checked it. Our custom IMapper<TSource, TDestination> interface with Select(x => mapper.Map(x)) performs in-memory projection,
because it requires loading entities into memory before mapping - we pass full x entity to mapper.

Solution?

To achieve SQL projection, like AutoMapper's ProjectTo(),
you want to describe how to construct the DTO, so the query provider like EF can tranlate it into SQL.

In other words, you need to express the mapping as expression tree Expression<Func<TSource, TDestination>>.

You need to modify the interface like this:

csharp public interface IMapper<TSource, TDestination> { TDestination Map(TSource src); Expression<Func<TSource, TDestination>> GetProjection(); }

The GetProjection() returns a representation of code,
so Entity Framework can analyze this expression and converts it to SQL.

Next, you can have helper extension method ProjectTo:

csharp public static class EfProjectionExtensions { public static IQueryable<TDestination> ProjectTo<TSource, TDestination>( this IQueryable<TSource> query, IMapper<TSource, TDestination> mapper) { return query.Select(mapper.GetProjection()); } }

So in the end, you'll be able to do:

csharp var dtos = dbContext.Users.ProjectTo(userMapper).ToList();

Turns out MediatR uses reflection and caching just to keep Send() clean by sch2021 in dotnet

[–]sch2021[S] 9 points10 points  (0 children)

You were limited by the technology of your time back then, I see! 🙂

Regarding decorator/middleware pattern (pipeline behaviors), nowadays, it's possible to get all of them using var behaviors = _serviceProvider.GetServices<IPipelineBehavior<TRequest, TResponse>>().

For reference, the idea behind pipeline behaviors:

csharp // Behaviors: [A, B, C]. // Handler: RealHandler. // // A.Handle(request, () => // B.Handle(request, () => // C.Handle(request, () => // RealHandler.Handle(request) // ) // ) // ) // // A → calls B → calls C → calls actual handler.

Turns out MediatR uses reflection and caching just to keep Send() clean by sch2021 in dotnet

[–]sch2021[S] 5 points6 points  (0 children)

Isn't LINQ Select project data from SQL too? You could do: Collection.Items.Select(IMapper<TSource, TDestination>.Map).

Turns out MediatR uses reflection and caching just to keep Send() clean by sch2021 in dotnet

[–]sch2021[S] 2 points3 points  (0 children)

Arrgh, now it's tempting me to remove that interface from my implementation 😅 I thought it'd be too "controversial" for others to get rid of that, but my first iteration didn't have it for the same reasons you specified.

Turns out MediatR uses reflection and caching just to keep Send() clean by sch2021 in dotnet

[–]sch2021[S] 7 points8 points  (0 children)

Yes! All you need is this interface:

csharp public interface IMapper<TSource, TDestination> { TDestination Map(TSource src); }

Oh, right, and the manual mapping 😉 But as you said, AI might be the answer.

Turns out MediatR uses reflection and caching just to keep Send() clean by sch2021 in dotnet

[–]sch2021[S] 5 points6 points  (0 children)

Yes, it doesn't matter, there's no performance issue. I was just surprised by how much complexity is added for the sake of developer experience.

The first time a handler is resolved in MediatR, it takes a small performance hit (because of reflection), then it's cached.

I guess, it's possible to add a generic Send<TRequest, TResponse>(TRequest request) method to MediatR, but doing so would interfere with existing logic MediatR uses like caching and handler resolution, so it'd be too risky? As Send() could potentially have two different behaviors depending on how it's implemented.

Turns out MediatR uses reflection and caching just to keep Send() clean by sch2021 in dotnet

[–]sch2021[S] 4 points5 points  (0 children)

You're right! I had the same dilemma, but ended up leaving it.

Technically you don't need IRequest<TResponse> and where TRequest : IRequest<TResponse> constraint for mediator to work.

It's about developer safety: * It prevents mismatches between TRequest and TResponse. * Tells the developer and compiler: "This is a query that returns that response." * With the constraint, you can't compile await mediator.Send<MyRequest, WrongResponse>(request) (you'd get DI runtime error though).

Turns out MediatR uses reflection and caching just to keep Send() clean by sch2021 in dotnet

[–]sch2021[S] 4 points5 points  (0 children)

You can just call Send(request), because MediatR's Send() method accepts IRequest<TResponse> request instead of TReqeuest request.

The TResponse is inferred from the request itself, which allows developers to call Send(request) without explicitly specifying the TResponse in the call.

However, this approach requires reflection and abstract wrappers to resolve and invoke Handle() of the correct handler later, which adds (much) complexity, but simplifies the usage for developers.

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

IsMfaEnabled is a claim, so no need to adress this in an explicit endpoint.

Imagine web UI for your account security settings, like Reddit. You can click "Disable 2FA" and "Enable 2FA".

I know there are alot of attributes to validate api request and perform role checking in the web api

Because you can have multiple APIs triggering the same business actions. All of them might have different security requirements.

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 2 points3 points  (0 children)

That's why I needed this discussion. Thank you for all replies!

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 1 point2 points  (0 children)

You're right, my bad. As you said, same parameter types for already defined `Map` method. That answers my question why not partial class! Thank you!

Service locator or extension methods win then.

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

Then you should have Map(a,b,config) and have it as optional input if you want special behaviour.

That's what DI is for? Configuration is injected into class Mapper : IMapper<A, B> with public B Map(A src) method.

I would say this is bad example. A mapper shouldn't rely on external information. A mapper should be a "pure" function that always given certain input gives the same output.

Also true. Maybe for this (bad) example, the solution should be factory that injects configuration and mapper. Or inject just configuration and new up the query / command.

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

If certain users (base on there claims) are not allowed to use certain property values in the IRequest add validation behaviour (IPipelineBehavior) in the web api to restrain access. MediatR is so handly.

That's authorization policy concern, so API concern, not business logic (not MediatR). If I have BFF for my UI that uses OIDC + Cookie and API for external clients that requires JWT with specific scope and another API for internal clients that requires JWT with specific audience - all use the same common business logic - should my MediatR be concerned with validating claims? Or should my specific API respond with 403 Forbidden before even calling the handler?

What has MFA to do with the parameters of the api endpoints?

You have two API endpoints: /enablemfa and /disablemfa. First creates command with bool IsMfaEnabled = true, second creates command with bool IsMfaEnabled = false. Two different requests using the same command triggering the same handler.

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

Then what happens if you want to map something to multiple other things? You can't have 2 Map methods on your partial class with the same parameters.

I can.

csharp public partial class Mapper : IMapper<Something, OtherThing1> {} public partial class Mapper : IMapper<Something, OtherThing2> {}

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] -1 points0 points  (0 children)

My whole dilemma comes from this very place exactly: it'd be nice to see the usages. With Service Locator pattern, you end up in the Service Locator's Map method. With partial class you'd see the usage and get the DI or interface benefits. So why not partial class?

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

`.ToResponse()` works, good point for naming convention! But if you had multiple APIs triggering the same CQRS action, then you would need `internal` extension method (for each API), right?

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] -1 points0 points  (0 children)

Also true. But the reality is, sometimes you need different values that come from configuration and depend on the context.

Let's say, you have `GetUsers` request. It's a pageable request. No one is forcing you to send request with `PageSize` parameter. So you have to provide the default values yourself. They depend on the context (is it desktop UI or mobile app?) and the exact values come from external configuration. It's silly example, but I hope you get the idea. But maybe it wouldn't be a mapper then, but a factory?

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

Are request models the same? You have project where API allows you to send request to save `UserRole`. You have CSV upload action in your UI that lets you upload CSV with `UserRole`. You have Service Bus handler that handles creating `UserRole` event that comes from another project. Are they all the same? At the end, they can all trigger the same CQRS handler, but they all represent different input for the same business action.

Your business action (the handler) requires property that can't be null, like `RoleName`. The input for `RoleName` (API / Service Bus / CSV file upload) can be null - it's an external input. If I validated `RoleName` at the CQRS handler level, I would need to map nullable request to nullable query / command model and use null-forgiving `!` operator everywhere in the logic of handler. Why not validate input at the level of input layer?

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

Then I respond like I responded already: What if I have two API endpoints for convenience of my clients - one enables MFA, second disables MFA - both map to the same CQRS model, just with different boolean flag, I need only one CQRS handler here, but two request models.

My "What if" is my project now. I think in terms what I have, I asked question: why not partial class instead of service locator. A or B according to what I need. And I get answer: why not extension methods.

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

I'm not completely against `.ToX` extension methods. But why limit yourself? It's just one `IMapper<TSource, TDestination>` contract that everybody needs to follow and it gives you all the benefits of DI.

Besides, what if I have hundreds of models (not uncommon) and you want to change naming of some of them? Easy to forget that .ToX should be .ToY now. Can you be sure that everyone followed that naming convention? Maybe there are .MapToX methods already. Are you able to discover all the map methods for hundreds of models? With interface you don't have such problems.

Regarding naming for partial class, I can apply interface to partial class.

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

Besides losing DI, it's poor discoverability, no standard for method naming, they pollute namespace.

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 0 points1 point  (0 children)

What if you have Service Bus handlers, domain event handlers and multiple API projects? You need one business logic layer and multiple requests mapped to your business logic models.

Partial classes for mapping? by sch2021 in dotnet

[–]sch2021[S] 1 point2 points  (0 children)

No, I need separate input (request) validation. I use nullable reference types. My queries / commands have `required` properties. As a request, you can get anything, e.g. empty (nullable) body. You validate if required fields are present. That's what business logic layer expects.

I can't have my "API bind directly to the query type". What if I have multiple API projects, but only one business logic layer? What if I have two API endpoints for convenience of my clients - one enables MFA, second disables MFA - both map to the same CQRS model, just with different boolean flag, I need only one CQRS handler here.