Skip to content

Commit

Permalink
add convenience ServiceCollectionExtensions
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusz96 committed Sep 20, 2024
1 parent 62dbd4b commit 2a3982c
Show file tree
Hide file tree
Showing 22 changed files with 286 additions and 65 deletions.
77 changes: 26 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dotnet add package PipelineNet
- [Middleware](#middleware)
- [Pipelines](#pipelines)
- [Chains of responsibility](#chains-of-responsibility)
- [Factories](#factories)
- [Middleware flow factories](#middleware-flow-factories)
- [Middleware resolver](#middleware-resolver)
- [ServiceProvider implementation](#serviceprovider-implementation)
- [Unity implementation](#unity-implementation)
Expand Down Expand Up @@ -197,7 +197,7 @@ result = await exceptionHandlersChain.Execute(new ArgumentException()); // Resul
result = await exceptionHandlersChain.Execute(new InvalidOperationException()); // Result will be false
```

## Factories
## Middleware flow factories
Instead of instantiating pipelines and chains of responsibility directly, you can use factories to instantiate them:
```C#
IAsyncPipelineFactory<Bitmap> pipelineFactory = new AsyncPipelineFactory<Bitmap>(new ActivatorMiddlewareResovler());
Expand Down Expand Up @@ -232,8 +232,16 @@ You can grab it from nuget with:
Install-Package PipelineNet.ServiceProvider
```

Use it as follows:
Use it with `ServiceCollectionExtensions`:
```C#
// The following line adds:
// - all middleware from assembly
// - service provider middleware resolver
// - open generic middleware flow factories
// with default scoped lifetime
services.AddPipelineNet(typeof(RoudCornersAsyncMiddleware).Assembly);
services.AddScoped<IMyService, MyService>();

public interface IMyService
{
Task DoSomething();
Expand All @@ -243,12 +251,12 @@ public class MyService : IMyService
{
private readonly IAsyncPipelineFactory<Bitmap> _pipelineFactory;

public MyPipelineFactory(IAsyncPipelineFactory<Bitmap> pipelineFactory)
public MyService(IAsyncPipelineFactory<Bitmap> pipelineFactory)
{
_pipelineFactory = pipelineFactory;
}

public Task DoSomething()
public async Task DoSomething()
{
IAsyncPipeline<Bitmap> pipeline = _pipelineFactory.Create()
.Add<RoudCornersAsyncMiddleware>()
Expand All @@ -265,7 +273,6 @@ public class RoudCornersAsyncMiddleware : IAsyncMiddleware<Bitmap>
{
private readonly ILogger<RoudCornersAsyncMiddleware> _logger;

// The following constructor arguments will be provided by IServiceProvider
public RoudCornersAsyncMiddleware(ILogger<RoudCornersAsyncMiddleware> logger)
{
_logger = logger;
Expand All @@ -280,60 +287,28 @@ public class RoudCornersAsyncMiddleware : IAsyncMiddleware<Bitmap>
}
```

Register it using `IHttpContextAccessor` (recommended):
Alternatively, you can instantiate `ServiceProviderMiddlewareResolver` directly:
```C#
services.AddSingleton<IMyService, MyService>();

services.AddMiddlewareFromAssembly(typeof(RoudCornersAsyncMiddleware).Assembly, ServiceLifetime.Scoped); // Add all middleware from assembly
services.AddHttpContextAccessor();

services.TryAddSingleton<IMiddlewareResolver, HttpContextAccessorMiddlewareResolver>(); // Add middleware resolver
services.TryAddSingleton(typeof(IPipelineFactory<>), typeof(AsyncPipelineFactory<>)); // Add pipeline and chain of responsibility factories
services.TryAddSingleton(typeof(IAsyncPipelineFactory<>), typeof(AsyncPipelineFactory<>));
services.TryAddSingleton(typeof(IResponsibilityChainFactory<,>), typeof(ResponsibilityChainFactory<,>));
services.TryAddSingleton(typeof(IAsyncResponsibilityChainFactory<,>), typeof(AsyncResponsibilityChainFactory<,>));
services.AddMiddlewareFromAssembly(typeof(RoudCornersAsyncMiddleware).Assembly);

public class HttpContextAccessorMiddlewareResolver : IMiddlewareResolver
public class MyService : IMyService
{
private readonly IHttpContextAccessor _httpContextAccessor;

public HttpContextAccessorMiddlewareResolver(IHttpContextAccessor httpContextAccessor)
public async Task DoSomething()
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException("httpContextAccessor",
"An instance of IHttpContextAccessor must be provided.");
}
IServiceProvider serviceProvider = GetServiceProvider();

public MiddlewareResolverResult Resolve(Type type)
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null) throw new InvalidOperationException("HttpContext must not be null.");
IAsyncPipeline<Bitmap> pipeline = new AsyncPipeline<Bitmap>(new ServiceProviderMiddlewareResolver(serviceProvider))
.Add<RoudCornersAsyncMiddleware>()
.Add<AddTransparencyAsyncMiddleware>()
.Add<AddWatermarkAsyncMiddleware>();

var middleware = httpContext.RequestServices.GetRequiredService(type);
bool isDisposable = false;
Bitmap image = (Bitmap) Image.FromFile("party-photo.png");

return new MiddlewareResolverResult
{
Middleware = middleware,
IsDisposable = isDisposable
};
await pipeline.Execute(image);
}
}
```

or `IServiceProvider`:
```C#
services.AddScoped<IMyService, MyService>();

services.AddMiddlewareFromAssembly(typeof(RoudCornersAsyncMiddleware).Assembly, ServiceLifetime.Scoped); // Add all middleware from assembly
services.TryAddScoped<IMiddlewareResolver, ServiceProviderMiddlewareResolver>(); // Add scoped middleware resolver so that scoped IServiceProvider is injected
services.TryAddScoped(typeof(IPipelineFactory<>), typeof(AsyncPipelineFactory<>)); // Add pipeline and chain of responsibility factories
services.TryAddScoped(typeof(IAsyncPipelineFactory<>), typeof(AsyncPipelineFactory<>));
services.TryAddScoped(typeof(IResponsibilityChainFactory<,>), typeof(ResponsibilityChainFactory<,>));
services.TryAddScoped(typeof(IAsyncResponsibilityChainFactory<,>), typeof(AsyncResponsibilityChainFactory<,>));
private IServiceProvider GetServiceProvider() => // Get service provider somehow
}
```

Note that `IServiceProvider` lifetime can vary based on the lifetime of the containing class. For example, if you resolve service from a scope, and it takes an `IServiceProvider`, it'll be a scoped instance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PipelineNet.Middleware;
using PipelineNet.PipelineFactories;
using PipelineNet.Pipelines;
using Xunit.Abstractions;

namespace PipelineNet.ServiceProvider.Tests
{
public class ServiceCollectionExtensionsTests
{
#region Parameter definitions
public class Image
{
public static Image FromFile(string filename) => new Bitmap();
}

public class Bitmap : Image
{
}
#endregion

#region Service definitions
public interface IMyService
{
Task DoSomething();
}

public class MyService : IMyService
{
private readonly IAsyncPipelineFactory<Bitmap> _pipelineFactory;

public MyService(IAsyncPipelineFactory<Bitmap> pipelineFactory)
{
_pipelineFactory = pipelineFactory;
}

public async Task DoSomething()
{
IAsyncPipeline<Bitmap> pipeline = _pipelineFactory.Create()
.Add<RoudCornersAsyncMiddleware>()
.Add<AddTransparencyAsyncMiddleware>()
.Add<AddWatermarkAsyncMiddleware>();

Bitmap image = (Bitmap) Image.FromFile("party-photo.png");

await pipeline.Execute(image);
}
}
#endregion

#region Middleware definitions
public class RoudCornersAsyncMiddleware : IAsyncMiddleware<Bitmap>
{
private readonly ILogger<RoudCornersAsyncMiddleware> _logger;

public RoudCornersAsyncMiddleware(ILogger<RoudCornersAsyncMiddleware> logger)
{
_logger = logger;
}

public async Task Run(Bitmap parameter, Func<Bitmap, Task> next)
{
_logger.LogInformation("Running RoudCornersAsyncMiddleware.");
// Handle somehow
await next(parameter);
}
}

public class AddTransparencyAsyncMiddleware : IAsyncMiddleware<Bitmap>
{
private readonly ILogger<AddTransparencyAsyncMiddleware> _logger;

public AddTransparencyAsyncMiddleware(ILogger<AddTransparencyAsyncMiddleware> logger)
{
_logger = logger;
}

public async Task Run(Bitmap parameter, Func<Bitmap, Task> next)
{
_logger.LogInformation("Running AddTransparencyAsyncMiddleware.");
// Handle somehow
await next(parameter);
}
}

public class AddWatermarkAsyncMiddleware : IAsyncMiddleware<Bitmap>
{
private readonly ILogger<AddWatermarkAsyncMiddleware> _logger;

public AddWatermarkAsyncMiddleware(ILogger<AddWatermarkAsyncMiddleware> logger)
{
_logger = logger;
}

public async Task Run(Bitmap parameter, Func<Bitmap, Task> next)
{
_logger.LogInformation("Running AddWatermarkAsyncMiddleware.");
// Handle somehow
await next(parameter);
}
}
#endregion

#region Logger definitions
public class TestOutputHelperLoggerProvider : ILoggerProvider
{
private readonly ITestOutputHelper _output;

public TestOutputHelperLoggerProvider(ITestOutputHelper output)
{
_output = output;
}

public ILogger CreateLogger(string categoryName) =>
new TestOutputHelperLogger(_output);

public void Dispose()
{
}
}

public class TestOutputHelperLogger : ILogger
{
private readonly ITestOutputHelper _output;

public TestOutputHelperLogger(ITestOutputHelper output)
{
_output = output;
}

public IDisposable? BeginScope<TState>(TState state) where TState : notnull =>
new NullScope();

public bool IsEnabled(LogLevel logLevel) => true;

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) =>
_output.WriteLine($"{eventId}:{logLevel}:{formatter(state, exception)}");

private class NullScope : IDisposable
{
public void Dispose()
{
}
}
}
#endregion

private readonly ITestOutputHelper _output;

public ServiceCollectionExtensionsTests(ITestOutputHelper output)
{
_output = output;
}

[Fact]
public async Task AddPipelineNet_Works_Readme()
{
var serviceProvider = new ServiceCollection()
.AddPipelineNet(typeof(RoudCornersAsyncMiddleware).Assembly)
.AddScoped<IMyService, MyService>()
.AddLogging(bulider => bulider.Services.AddSingleton<ILoggerProvider>(new TestOutputHelperLoggerProvider(_output)))
.BuildServiceProvider();
var scope = serviceProvider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IMyService>();

await service.DoSomething();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace PipelineNet.ServiceProvider.MiddlewareResolver
{
/// <summary>
/// An implementation of <see cref="IMiddlewareResolver"/> that creates
/// instances using the <see cref="ActivatorUtilities"/>.
/// instances using the <see cref="Microsoft.Extensions.DependencyInjection.ActivatorUtilities"/>.
/// </summary>
public class ActivatorUtilitiesMiddlewareResolver : IMiddlewareResolver
{
Expand Down
Loading

0 comments on commit 2a3982c

Please sign in to comment.