{"id":10699,"date":"2024-02-10T12:35:52","date_gmt":"2024-02-10T12:35:52","guid":{"rendered":"https:\/\/putridparrot.com\/blog\/?p=10699"},"modified":"2024-02-11T13:48:35","modified_gmt":"2024-02-11T13:48:35","slug":"messing-around-with-mediatr","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/messing-around-with-mediatr\/","title":{"rendered":"Messing around with MediatR"},"content":{"rendered":"<p><a href=\"https:\/\/github.com\/jbogard\/MediatR\" rel=\"noopener\" target=\"_blank\">MediatR<\/a> is an implementation of the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Mediator_pattern\" rel=\"noopener\" target=\"_blank\">Mediator pattern<\/a>. It doesn&#8217;t match the pattern exactly, but as the creator, Jimmy Bogard states that &#8220;It matches the problem description (reducing chaotic dependencies), the implementation doesn&#8217;t exactly match&#8230;&#8221;. It&#8217;s worth reading his post <a href=\"https:\/\/www.jimmybogard.com\/you-probably-dont-need-to-worry-about-mediatr\/\" rel=\"noopener\" target=\"_blank\">You Probably Don&#8217;t Need to Worry About MediatR<\/a>.<\/p>\n<p>This pattern is aimed at decoupling the likes of business logic from a UI layer or request\/response&#8217;s. <\/p>\n<p>There are several ways we can already achieve this in our code, for example, using interfaces to decouple the business logic from the UI or API layers as &#8220;services&#8221; as we&#8217;ve probably all done for years. The only drawback of this approach is it requires the interfaces to be either passed around in our code or via DI and is a great way to do things. Another way to do this is, as used within UI, using WPF, Xamarin Forms, MAUI and others where we often use in-process message queues to send messages around our application tell it to undertake some task and this is essentially what MediatR is giving us. <\/p>\n<p>Let&#8217;s have a look at using MediatR. I&#8217;m going to create an ASP.NET web API (obviously you could use MediatR in other types of solutions)<\/p>\n<ul>\n<li>Create an ASP.NET Core Web API. I&#8217;m using Minimal API, so feel free to check that or stick with controllers as you prefer.\n<li>\n<li>Add the nuget package MediatR<\/li>\n<li>To the Program.cs file add\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nbuilder.Services.AddMediatR(cfg =&gt; \r\n  cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));\r\n<\/pre>\n<\/ul>\n<p>At this point we have MediatR registering services for us at startup. We can passing multiple assemblies to the <em>RegisterServicesFromAssembly<\/em> method, so if we have all our reqeust\/response code in multiple assemblies we can supply just those assemblies. Obviously this makes our life simpler but at the cost of reflecting across our code at startup. <\/p>\n<p>The ASP.NET Core Web API creates the WeatherForecast example, we&#8217;ll just use this for our sample code as well. <\/p>\n<p>The first thing you&#8217;ll notice is that the route to the <em>weatherforecast<\/em> is tightly coupled to the sample code. Ofcourse it&#8217;s an example, so this is fine, but we&#8217;re going to clean things up here and move the implementation into a file named <em>GetWeatherForecastHandler<\/em> but before we do that&#8230;<\/p>\n<p><em>Note: Ofcourse we could just move the weather forecast code into an WeatherForecastService, create an IWeatherForecastService interface and there&#8217;s no reason not to do that, MediatR just offers and alternative way of doing things.<\/em><\/p>\n<p>MediatR will try to find a matching handler for your request. In this example we have no request parameters. This begs the question as to how MediatR will match against our <em>GetWeatherForecastHandler<\/em>. It needs a unique request type to map to our handler, in this case the simplest thing to do is create yourself the request type. Mine&#8217;s named <em>GetWeatherForecast<\/em> and looks like this<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic record GetWeatherForecast : IRequest&lt;WeatherForecast&#x5B;]&gt;\r\n{\r\n    public static GetWeatherForecast Default { get; } = new();\r\n}\r\n<\/pre>\n<p><em>Note: I&#8217;ve created a static method so we&#8217;re not creating an instance for every call, however this is not required and obviously when you are passing parameters you will be creating an instance of a type each time &#8211; this does obviously concern me a little if we need high performance and are trying to write allocation free code, but then we&#8217;d do lots differently then including probably not using MediatR.<\/em><\/p>\n<p>Now we&#8217;ll create the <em>GetWeatherForecastHandler<\/em> file and the code looks like this<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class GetWeatherForecastHandler : IRequestHandler&lt;GetWeatherForecast, WeatherForecast&#x5B;]&gt;\r\n{\r\n  private static readonly string&#x5B;] Summaries = new&#x5B;]\r\n  {\r\n    &quot;Freezing&quot;, &quot;Bracing&quot;, &quot;Chilly&quot;, &quot;Cool&quot;, &quot;Mild&quot;, &quot;Warm&quot;, &quot;Balmy&quot;, &quot;Hot&quot;, &quot;Sweltering&quot;, &quot;Scorching&quot;\r\n  };\r\n\r\n  public Task&lt;WeatherForecast&#x5B;]&gt; Handle(GetWeatherForecast request, CancellationToken cancellationToken)\r\n  {\r\n    var forecast = Enumerable.Range(1, 5).Select(index =&gt;\r\n      new WeatherForecast\r\n      {\r\n        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),\r\n        TemperatureC = Random.Shared.Next(-20, 55),\r\n        Summary = Summaries&#x5B;Random.Shared.Next(Summaries.Length)]\r\n      })\r\n    .ToArray();\r\n\r\n    return Task.FromResult(forecast);\r\n  }\r\n}\r\n<\/pre>\n<p>At this point we&#8217;ve created a way for MediatR to find the required handler (i.e. using the <em>GetWeatherForecast<\/em> type) and we&#8217;ve created a handler to create the response. In this example we&#8217;re not doing any async work, so we just wrap the result in a <em>Task.FromResult<\/em>.<\/p>\n<p>Next go back to the Program.cs or if you&#8217;ve used controllers, go to your controller. If using controller you&#8217;ll need the constructor to take the parameters <em>IMediator mediator<\/em> and assign to a readonly field in the usually way. <\/p>\n<p>For our minimal API example, go back to the Program.cs file remove the <em>summaries<\/em> variable\/code and then change the route code to look like this<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\napp.MapGet(&quot;\/weatherforecast&quot;,  (IMediator mediator) =&gt; \r\n  mediator.Send(GetWeatherForecast.Default))\r\n.WithName(&quot;GetWeatherForecast&quot;)\r\n.WithOpenApi();\r\n<\/pre>\n<p>We&#8217;re not really playing too nice in the code above, in that we&#8217;re not returning results code, so let&#8217;s add some basic result handling<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\napp.MapGet(&quot;\/weatherforecast&quot;,  async (IMediator mediator) =&gt; \r\n  await mediator.Send(GetWeatherForecast.Default) is var results \r\n    ? Results.Ok(results) \r\n    : Results.NotFound())\r\n  .WithName(&quot;GetWeatherForecast&quot;)\r\n  .WithOpenApi();\r\n<\/pre>\n<p>Now for each new HTTP method call, we would create a request object and a handler object. In this case we send no parameters, but as you can no doubt see, for a request that takes (for example) a string for your location, we&#8217;d create a specific type for wrapping that parameter and the handler can then be mapped to that request type. <\/p>\n<p>In our example we used the MediatR <em>Send<\/em> method. This sends a request to a single handler and expects a response of some type, but MediatR also has the ability to <em>Publish<\/em> to multiple handlers. These types of handlers are different, firstly they need to implement the <em>INotificationHandler<\/em> interface and secondly no response is expected when using <em>Publish<\/em>. These sorts of handlers are more like event broadcasts, so you might use then to send a message to an email service or database code which sends out an email upon request or updates a database.<\/p>\n<p>Or WeatherForecast sample doesn&#8217;t give me any good ideas for using Publish in it&#8217;s current setup, so let&#8217;s just assume we have a way to set the current location. Like I said this example&#8217;s a little contrived as we&#8217;re going to essentially set the location for everyone connecting to this service, but you get the idea.<\/p>\n<p>We&#8217;re going to add a <em>SetLocation<\/em> request type that looks like this<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic record SetLocation(string Location) : INotification;\r\n<\/pre>\n<p>Notice that for publish our type is implementing the INotification interface. Our handles look like this (my file is named SetLocationHandler.cs but I&#8217;ll put both handlers in there just to be a little lazy)<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class UpdateHandler1 : INotificationHandler&lt;SetLocation&gt;\r\n{\r\n  public Task Handle(SetLocation notification, CancellationToken cancellationToken)\r\n  {\r\n    Console.WriteLine(nameof(UpdateHandler1));\r\n    return Task.CompletedTask;\r\n  }\r\n}\r\n\r\npublic class UpdateHandler2 : INotificationHandler&lt;SetLocation&gt;\r\n{\r\n  public Task Handle(SetLocation notification, CancellationToken cancellationToken)\r\n  {\r\n    Console.WriteLine(nameof(UpdateHandler2));\r\n    return Task.CompletedTask;\r\n  }\r\n}\r\n<\/pre>\n<p>As you can see, the handlers need to implement <em>INotificationHandler<\/em> with the correct request type. In this sample we&#8217;ll just write messages to console, but you might have a more interesting set of handlers in mind.<\/p>\n<p>Finally let&#8217;s add the following to the Program.cs to publish a message<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\napp.MapGet(&quot;\/setlocation&quot;, (IMediator mediator, string location) =&gt;\r\n  mediator.Publish(new SetLocation(location)))\r\n.WithName(&quot;SetLocation&quot;)\r\n.WithOpenApi();\r\n<\/pre>\n<p>When you run up your server and use Swagger or call the <em>setlocation<\/em> method via it&#8217;s URL you&#8217;ll see that all your handlers that handle the request get called.<\/p>\n<p>Ofcourse we can also <em>Send<\/em> and <em>Post<\/em> messages\/request from our handlers, so maybe we get the weather forecast data then publish a message for some logging system to update the logs.<\/p>\n<p>MediatR also includes the ability to stream from a requests where our request type implements the <em>IStreamRequest<\/em> and our handlers implement <em>IStreamRequestHandler<\/em>.<\/p>\n<p>If we create a simple request type but this one implements <em>IStreamRequest<\/em> for example<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic record GetWeatherStream : IStreamRequest&lt;WeatherForecast&gt;;\r\n<\/pre>\n<p>and now add a handler which implements <em>IStreamRequestHandler<\/em>, something like this (which delay&#8217;s to just give a feel of getting data from somewhere else)<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class GetWeatherStreamHandler : IStreamRequestHandler&lt;GetWeatherStream, WeatherForecast&gt;\r\n{\r\n  public async IAsyncEnumerable&lt;WeatherForecast&gt; Handle(GetWeatherStream request, \r\n    &#x5B;EnumeratorCancellation] CancellationToken cancellationToken)\r\n  {\r\n    var index = 0;\r\n    while (!cancellationToken.IsCancellationRequested)\r\n    {\r\n      await Task.Delay(500, cancellationToken);\r\n      yield return new WeatherForecast\r\n      {\r\n        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),\r\n        TemperatureC = Random.Shared.Next(-20, 55),\r\n        Summary = Data.Summaries&#x5B;Random.Shared.Next(Data.Summaries.Length)]\r\n      };\r\n\r\n      index++;\r\n      if(index &gt; 10)\r\n        break;\r\n    }\r\n  }\r\n}\r\n<\/pre>\n<p>Finally we can declare our streaming route using Minimal API very simply, for example<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\napp.MapGet(&quot;\/stream&quot;, (IMediator mediator) =&gt;\r\n  mediator.CreateStream(new GetWeatherStream()))\r\n.WithName(&quot;Stream&quot;)\r\n.WithOpenApi();\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>MediatR is an implementation of the Mediator pattern. It doesn&#8217;t match the pattern exactly, but as the creator, Jimmy Bogard states that &#8220;It matches the problem description (reducing chaotic dependencies), the implementation doesn&#8217;t exactly match&#8230;&#8221;. It&#8217;s worth reading his post You Probably Don&#8217;t Need to Worry About MediatR. This pattern is aimed at decoupling the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[200,3,719],"tags":[],"class_list":["post-10699","post","type-post","status-publish","format-standard","hentry","category-asp-net-core","category-c","category-mediatr"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/10699","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=10699"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/10699\/revisions"}],"predecessor-version":[{"id":10710,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/10699\/revisions\/10710"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=10699"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=10699"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=10699"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}