Add WireMock.Net.AspNetCore.Middleware (#1175)

* Add WireMock.Net.AspNetCore.Middleware

* .

* WireMock.Net.Middleware.Tests

* .

* X-WireMock-Response-Delay
This commit is contained in:
Stef Heyenrath
2024-09-27 20:39:57 +02:00
committed by GitHub
parent c57590b2ba
commit 42306d1864
23 changed files with 641 additions and 1 deletions

View File

@@ -0,0 +1,9 @@
// Copyright © WireMock.Net
namespace WireMock.Net.AspNetCore.Middleware;
internal static class AppConstants
{
internal const string HEADER_REDIRECT = "X-WireMock-Redirect";
internal const string HEADER_RESPONSE_DELAY = "X-WireMock-Response-Delay";
}

View File

@@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Stef.Validation;
using WireMock.Server;
namespace WireMock.Net.AspNetCore.Middleware.HttpDelegatingHandler;
/// <summary>
/// DelegatingHandler that takes requests made via the <see cref="HttpClient"/>
/// and routes them to the <see cref="WireMockServer"/>.
/// </summary>
internal class WireMockDelegationHandler : DelegatingHandler
{
private readonly ILogger<WireMockDelegationHandler> _logger;
private readonly WireMockServerInstance _server;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly WireMockDelegationHandlerSettings _settings;
/// <summary>
/// Creates a new instance of <see cref="WireMockDelegationHandler"/>
/// </summary>
public WireMockDelegationHandler(
ILogger<WireMockDelegationHandler> logger,
WireMockServerInstance server,
IHttpContextAccessor httpContextAccessor,
WireMockDelegationHandlerSettings settings
)
{
_server = Guard.NotNull(server);
_httpContextAccessor = Guard.NotNull(httpContextAccessor);
_logger = Guard.NotNull(logger);
_settings = Guard.NotNull(settings);
}
/// <inheritdoc />
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Guard.NotNull(request);
Guard.NotNull(_httpContextAccessor.HttpContext);
if (_settings.AlwaysRedirect || IsWireMockRedirectHeaderSetToTrue())
{
_logger.LogDebug("Redirecting request to WireMock server");
if (_server.Instance?.Url != null)
{
request.RequestUri = new Uri(_server.Instance.Url + request.RequestUri!.PathAndQuery);
}
}
if (TryGetDelayHeaderValue(out var delayInMs))
{
await Task.Delay(delayInMs, cancellationToken);
}
return await base.SendAsync(request, cancellationToken);
}
private bool IsWireMockRedirectHeaderSetToTrue()
{
return
_httpContextAccessor.HttpContext!.Request.Headers.TryGetValue(AppConstants.HEADER_REDIRECT, out var values) &&
bool.TryParse(values.ToString(), out var shouldRedirectToWireMock) && shouldRedirectToWireMock;
}
private bool TryGetDelayHeaderValue(out int delayInMs)
{
delayInMs = 0;
return
_httpContextAccessor.HttpContext!.Request.Headers.TryGetValue(AppConstants.HEADER_RESPONSE_DELAY, out var values) &&
int.TryParse(values.ToString(), out delayInMs);
}
}

View File

@@ -0,0 +1,8 @@
// Copyright © WireMock.Net
namespace WireMock.Net.AspNetCore.Middleware.HttpDelegatingHandler;
internal class WireMockDelegationHandlerSettings
{
public bool AlwaysRedirect { get; set; }
}

View File

@@ -0,0 +1,52 @@
// Copyright © WireMock.Net
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Stef.Validation;
using WireMock.Net.AspNetCore.Middleware.HttpDelegatingHandler;
using WireMock.Server;
using WireMock.Settings;
namespace WireMock.Net.AspNetCore.Middleware;
/// <summary>
/// Extension methods for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds all the components necessary to run WireMock.Net as a background service.
/// </summary>
public static IServiceCollection AddWireMockService(
this IServiceCollection services,
Action<WireMockServer> configure,
bool alwaysRedirectToWireMock = true,
WireMockServerSettings? settings = null
)
{
Guard.NotNull(services);
Guard.NotNull(configure);
services.AddTransient<WireMockDelegationHandler>();
services.AddSingleton(new WireMockServerInstance(configure, settings));
services.AddSingleton(new WireMockDelegationHandlerSettings
{
AlwaysRedirect = alwaysRedirectToWireMock
});
services.AddHostedService<WireMockBackgroundService>();
services.AddHttpClient();
services.AddHttpContextAccessor();
services.ConfigureAll<HttpClientFactoryOptions>(options =>
{
options.HttpMessageHandlerBuilderActions.Add(builder =>
{
builder.AdditionalHandlers.Add(builder.Services.GetRequiredService<WireMockDelegationHandler>());
});
});
return services;
}
}

View File

@@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Description>Middleware which can be used to host WireMock.Net as a AspNetCore Middleware in a WebApplication</Description>
<AssemblyTitle>WireMock.Net.AspNetCore.Middleware</AssemblyTitle>
<Authors>Matt Yost;Stef Heyenrath</Authors>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.AspNetCore.Middleware</AssemblyName>
<PackageId>WireMock.Net.AspNetCore.Middleware</PackageId>
<PackageTags>dotnet;middleware;wiremock;service;webapplication</PackageTags>
<ProjectGuid>{B6269AAC-170A-4346-8B9A-579DED3D9A13}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>WireMock.Net-LogoAspire.png</PackageIcon>
<ApplicationIcon>../../resources/WireMock.Net-LogoAspire.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Compile Remove="IWireMockContext.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="../../resources/WireMock.Net-Logo.png" />
<None Include="../../resources/WireMock.Net-LogoAspire.png" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,39 @@
// Copyright © WireMock.Net
using Microsoft.Extensions.Hosting;
using Stef.Validation;
using WireMock.Server;
namespace WireMock.Net.AspNetCore.Middleware;
/// <summary>
/// A <see cref="BackgroundService"/> used to start/stop the <see cref="WireMockServer"/>
/// </summary>
internal class WireMockBackgroundService : BackgroundService
{
private readonly WireMockServerInstance _serverInstance;
/// <summary>
/// Creates a new <see cref="BackgroundService"/> using an instance
/// of <see cref="WireMockServerInstance"/>
/// </summary>
/// <param name="serverInstance"></param>
public WireMockBackgroundService(WireMockServerInstance serverInstance)
{
_serverInstance = Guard.NotNull(serverInstance);
}
/// <inheritdoc />
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_serverInstance.Start();
return Task.CompletedTask;
}
/// <inheritdoc />
public override Task StopAsync(CancellationToken cancellationToken)
{
_serverInstance.Stop();
return base.StopAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,49 @@
// Copyright © WireMock.Net
using Stef.Validation;
using WireMock.Server;
using WireMock.Settings;
namespace WireMock.Net.AspNetCore.Middleware;
/// <summary>
/// WireMockServer Instance object
/// </summary>
internal class WireMockServerInstance
{
private readonly Action<WireMockServer> _configureAction;
private readonly WireMockServerSettings? _settings;
/// <summary>
/// Creates a new instance and provides ability to add configuration
/// for the start method of <see cref="WireMockServer"/>
/// </summary>
public WireMockServerInstance(Action<WireMockServer> configure, WireMockServerSettings? settings = null)
{
_configureAction = Guard.NotNull(configure);
_settings = settings;
}
/// <summary>
/// Instance accessor for the <see cref="WireMockServer" />
/// </summary>
public WireMockServer? Instance { get; private set; }
/// <summary>
/// Configures and starts <see cref="WireMockServer"/> instance for use.
/// </summary>
public void Start()
{
Instance = _settings != null ? WireMockServer.Start(_settings) : WireMockServer.Start();
_configureAction.Invoke(Instance);
}
/// <summary>
/// Stops the <see cref="WireMockServer"/>
/// </summary>
public void Stop()
{
Instance?.Stop();
}
}