HTTP
HTTP requests are basically a must in every app. We didn’t want people to have to generate their API client, contracts, and then have to global add the boilerplate mediator contract and subsequent request handler, so… we found some very convenient ways to build this in. Imagine having to do something like this for every HTTP call?
public class MyHttpRequest : IRequest<MyResponse>{ public string SomeArg { get; set; }}
public class MyHttpRequestHandler<MyHttpRequest, MyResponse>(IConfiguration config) : IRequestHandler<MyHttpRequest, MyResponse>{ public async Task<MyResponse> Handle(MyHttpRequest request, CancellationToken cancellationToken) { var baseUri = config.GetValue<string>("HttpUri"); var httpClient = new HttpClient(); var json = JsonSerializer.Serialize(request); var content = new StringContent(json); var message = new HttpRequestMessage(HttpMethod.Post, baseUri + "/somerequest"); // what about headers and auth? OMG... too much
var response = await httpClient.PostAsync(message); response.EnsureSuccessStatusCode();
var responseJson = await response.Content.ReadAsStringAsync(cancellationToken); var result = JsonSerializer.Deserialize<MyResponse>(responseJson);
return result; }}
-
Let’s start with our mediator request contract - Notice that we have some attributes & interfaces that are specific to HTTP requests
using Shiny.Mediator;using Shiny.Mediator.Http;[Http(HttpVerb.Post, "/route/{Parameter}")]public class MyRequest : IHttpRequest<MyResponse>{// name of the route parameter[HttpParameter(HttpParameterType.Path)]public string Parameter { get; set; }// query string parameter appended to uri[HttpParameter(HttpParameterType.Query)]public string QueryValue { get; set; }// added to headers[HttpParameter(HttpParameterType.Header)]public string SomeHeaderValue { get; set; }// there can only be one body - serialized to json[HttpBody]public SomeOtherClass Body { get; set; }} -
Next, let’s create our configuration for the base URI. This assumes you use Microsoft.Extensions.Configuration JSON, but any other configuration provider with the same structure will work.
{"Mediator": {"Http": {"Your.Namespace.Contract": "https://yourapi.com",// OR"Your.Namespace.*": "https://yourapi.com"// OR"*": "https://yourapi.com"}}} -
Now - let’s make the request with mediator
var response = await mediator.Request(new MyRequest{Parameter = "someValue",QueryValue = "someQuery",SomeHeaderValue = "someHeader",Body = new SomeOtherClass{SomeProperty = "someValue"}});
Decorating the HTTP Request
A common scenario is having to refresh an access token, add it to the next HTTP request, and maybe add some additional header while you’re there. Shiny Mediator allows you to register
a different type of middleware of HTTP Requests
called IHttpRequestDecorator.
Here’s an example that already exists in Shiny.Mediator.Maui
that adds some headers to the request.
Decorators apply to all HTTP requests
public class MauiHttpRequestDecorator<TRequest, TResult>( IConfiguration configuration, IAppInfo appInfo, IDeviceInfo deviceInfo, IGeolocation geolocation) : IHttpRequestDecorator<TRequest, TResult> where TRequest : IHttpRequest<TResult>{ public async Task Decorate(HttpRequestMessage httpMessage, TRequest request) { httpMessage.Headers.Add("AppId", appInfo.PackageName); httpMessage.Headers.Add("AppVersion", appInfo.Version.ToString()); httpMessage.Headers.Add("DeviceManufacturer", deviceInfo.Manufacturer); httpMessage.Headers.Add("DeviceModel", deviceInfo.Model); httpMessage.Headers.Add("DevicePlatform", deviceInfo.Platform.ToString()); httpMessage.Headers.Add("DeviceVersion", deviceInfo.Version.ToString()); httpMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(CultureInfo.CurrentCulture.Name));
if (configuration["Mediator:Http:GpsHeader"] == "true") { var gps = await geolocation.GetLastKnownLocationAsync(); if (gps != null) httpMessage.Headers.Add("GpsCoords", $"{gps.Latitude},{gps.Longitude}"); }
// added to show authentication - does not exist in real MauiHttpRequestDecorator if (someAuthService.TokenExpiry > DateTimeOffset.UtcNow) await someAuthService.RefreshToken();
httpMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", someAuthService.AccessToken); }}
OpenAPI HTTP Contract Generation
Let’s reduce the boilerplate and mapping even further. You’ve already done all that work once in your APIs. Some WebAPIs can have a large API surface with many routes, parameters, and types. Generating OpenAPI files is pretty standard these days with things like NSwag, Swashbuckle, & Refitter. Shiny Mediator can help with this as well. You can generate all the contracts and responses from an OpenAPI file or URI to an OpenAPI document.
Simply edit your csproj file that has Shiny.Mediator
installed and add the following to it.
If there is no OperationId defined for the OpenAPI method, we cannot generate a contract.
<ItemGroup> <MediatorHttp Include="OpenApiDoc.json" Namespace="My.Namespace" ContractPrefix="optional" ContractPostfix="HttpRequest" Visible="false" /> <MediatorHttp Include="OpenApiRemote" Uri="https://someurl.com" Namespace="My.RemoteNamespace" ContractPrefix="optional" ContractPostfix="HttpRequest" Visible="false" /></ItemGroup>
:::note INFO
- If you’re setting the Uri attribute, you must still set the Include value to satisfy the ItemGroup. Also note, the use of Visible=“false”. This removes the registration of the msbuild variable from the IDE solution explorer.
- There are scenarios we don’t yet support like discrimated types. We’re looking at this for a future implementation. :::