3.2. Configuration for Client Server usage - pipaslot/Mediator GitHub Wiki
The client-server mediator configuration uses the same principles and interfaces as for In-process usage. The main difference is that you are configuring the mediator on both sides. This allows you to create middlewares on the client-side and provide for example global error handling or processing of side-effect results designed for frontend processing.
You can also define handlers on the client-side, but keep in mind that default behavior sends the actions over HTTP to the server. If you would like to process these actions directly on the frontend, then you have to configure action specific pipeline which executes the handler as the last step instead of sending over the network.

Client-specific mediator extends the core mediator. You can register it as the following example:
services.AddMediatorClient()
.AddActionsFromAssemblyOf<WeatherForecast.Request>() //Register all actions
.AddPipeline<..>() // Define pipelines - optional
.Use<...>();
The default server address (/_mediator/request) where all actions will be forwarded can be changed by overload which exposes a configuration.
services.AddMediatorClient(o =>
{
o.Endpoint = "/api/_mediator/request;
});
Server-specific mediator extends the core mediator. You can register it as the following example:
services.AddMediatorServer()
.AddActionsFromAssemblyOf<WeatherForecast.Request>() //Register all actions
.AddPipeline<..>() // Define pipelines - optional
.Use<...>();
The default server address (/_mediator/request) where all actions will be received from can be changed by overload which exposes a configuration.
services.AddMediatorClient(o =>
{
o.Endpoint = "/api/_mediator/request;
o.ErrorHttpStatusCode = 500;// Default value: 200
});
IMPORTANT: Mediator catches all exceptions that occur during middleware or action handler execution. If you have registered ASP.NET Core middleware processing uncaught exceptions, then exceptions from mediator will never be propagated to this ASP.NET Core middleware. If you want to process all these uncaught exceptions and for example notify the administrator, you have t implement your own Mediator middleware and put it to the beginning of all your pipelines.
NOTE: Since version 5.0, you can use IHttpContextAccessor and Set Response HTTP Status Code directly in your middleware to provide better logging or debugging experience. For example, you can set status 400(bad Request) in case of validation errors
The mediator supports only JSON format which is handled by the mediator internally. The mediator does not support multiple status code handling. The mediator server will always return status code 200(OK). It does not matter on action response status or if during processing was thrown unhandled exception because all exceptions are converted into ErrorMessages in Mediator Response. Note: HTTP Status Code 500(Server Error) is thrown only in case the mediator can not be invoked due to invalid server configuration or server error.
We recommend you to use MediatorExceptionLoggingMiddleware which logs all error messages in browser consoles together with action names causing these errors. The Mediator adds action names in Request URLs for easier action tracking over browser network activity.
Sample mediator request then can look like: https://localhost/api/mediator?type=Sample.Shared.Requests.WeatherForecastRequest
Generally TypeNameHandling applied in JSON content is not recommended for security reasons. The mediator is using a whitelist approach (credible types) for types that can be deserialized to reduce the risk that attackers could exploit this feature for executing classes that could harm your system.
We strongly recommend you to register all actions via method AddActionsFromAssemblyOf<...>() on client and server mediator to turn on type whitelisting before deserializing data from JSON to C# types. It will protect you against potential exploits of this feature for an attack on your client and server app.
Mediator Server has the whitelisting enabled by default. You only need to provide the location of all your actions transferred over the network. Server configuration:
services.AddMediatorServer()
.AddActionsFromAssemblyOf<MyAction>(); // OR .AddActionsFromAssembly(Assembly.GetExecutingAssembly());
It can be disabled. In that case, no action does not need to be specified.
services.AddMediatorServer(o => {
o.DeserializeOnlyCredibleActionTypes = false;
})
Mediator Client has the whitelisting disabled by default. Client minimal configuration:
services.AddMediatorClient();
If you want to enable the whitelisting, you will have to register all actions transferred over the network. Do not forget to register also result types attached to the response by the server middlewares via AddMediatorClient options.
services.AddMediatorClient(o =>
{
o.DeserializeOnlyCredibleResultTypes = true;
o.CredibleResultTypes = new Type[]
{
typeof(CommonResult)// Type assigned by server middleware
};
})
.AddActionsFromAssemblyOf<MyAction>(); // OR .AddActionsFromAssembly(Assembly.GetExecutingAssembly());
Serialization used for transferring data over HTTP is limiting polymorphism in comparison with in-process mediator scope. These limits come from the System.Json serializer and JSON format itself. The JSON format is not providing the expected type if you specify any object or its property as an interface or parent class.
Although there is one specific case where you can use interface or parent class. It is for the TResult action types of IRequest<TResult> or IMediatorAction<TResult>.
Working example
public class MyAction : IMediatorAction<IMyResult>{
...
}
public interface IMyResult{
...
}
public class MySpecificResult : IMyResult{
...
}
But it won't work if you would like to return a collection of IMyResult.
Since Version 6.0.0 mediator detects if the response already started. By default, the mediator starts writing JSON responses with all results aggregated during processing. There might be cases when another response is needed. For example, when mediator was executed via GET request and file download is expected. In such a case you can inject IHttpContextAccessor and write your own response in handler on in middleware.
public class DemoDownloadHandler : IMessageHandler<DemoDownload>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public DemoDownloadHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task Handle(DemoDownload action, CancellationToken cancellationToken)
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
{
throw new InvalidOperationException($"Action {nameof(DemoDownload)} can be used only for request over HTTP.");
}
httpContext.Response.ContentType = "text/plain";
httpContext.Response.Headers["Content-Disposition"] = $"attachment;filename={action.FileName}.txt";
await httpContext.Response.WriteAsync("Hello File!", cancellationToken);
}
}
Since Mediator.Http 7.2.0 and only for V3 serializer. Mediator serializes also read-only properties by default. That includes getters and properties without a public setter. As the contract is shared between the Client and Server, the value is also re-evaluated after the transfer (the serialization is redundant). As an alternative, you can ignore serialization for those properties by adding [JsonIgnore] attribute. To ignore them globally, apply the following lines to the mediator configuration on the client and server.
services.AddMediatorClient(o =>
//OR services.AddMediatorServer(o =>
{
o.SerializerType = SerializerType.V3;
o.IgnoreReadOnlyProperties = true;
})
WARNING: By enabling the ignore option, you will lose the option to use a private setter together with the constructor. Contracts defined as the following examples won't be serialized anymore.
public class ContractWithPrivateSetter
{
public ContractWithPrivateSetter(string name, int value)
{
Name = name;
Value = value;
}
public string Name { get; }
public int Value { get; }
}
But Records classes will still work as expected.