Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ServiceCollectionExtensions and ServiceProviderMiddlewareResolver #19

Merged
merged 7 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 52 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ dotnet add package PipelineNet
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Simple example](#simple-example)
- [Pipeline vs Chain of responsibility](#pipeline-vs-chain-of-responsibility)
- [Middleware](#middleware)
- [Pipelines](#pipelines)
- [Chains of responsibility](#chains-of-responsibility)
- [Middleware resolver](#middleware-resolver)
- [License](#license)
- [Simple example](#simple-example)
- [Pipeline vs Chain of responsibility](#pipeline-vs-chain-of-responsibility)
- [Middleware](#middleware)
- [Pipelines](#pipelines)
- [Chains of responsibility](#chains-of-responsibility)
- [Middleware resolver](#middleware-resolver)
- [ServiceProvider implementation](#serviceprovider-implementation)
- [Unity implementation](#unity-implementation)
- [License](#license)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -212,39 +214,43 @@ You can grab it from nuget with:
Install-Package PipelineNet.ServiceProvider
```

Use it as follows:

Use it with dependency injection:
```C#
services.AddScoped<IMyPipelineFactory, MyPipelineFactory>();

public interface IMyPipelineFactory
services.AddMiddlewareFromAssembly(typeof(RoudCornersAsyncMiddleware).Assembly);
services.AddScoped<IAsyncPipeline<Bitmap>>(serviceProvider =>
{
return new AsyncPipeline<Bitmap>(new ServiceProviderMiddlewareResolver(serviceProvider))
.Add<RoudCornersAsyncMiddleware>()
.Add<AddTransparencyAsyncMiddleware>()
.Add<AddWatermarkAsyncMiddleware>();
});
services.AddScoped<IMyService, MyService>();

public interface IMyService
{
IAsyncPipeline<Bitmap> CreatePipeline();
Task DoSomething();
}

public class MyPipelineFactory : IMyPipelineFactory
public class MyService : IMyService
{
private readonly IServiceProvider _serviceProvider;
private readonly IAsyncPipeline<Bitmap> _pipeline;

public MyPipelineFactory(IServiceProvider serviceProvider)
public MyService(IAsyncPipeline<Bitmap> pipeline)
{
_serviceProvider = serviceProvider;
_pipeline = pipeline;
}

public IAsyncPipeline<Bitmap> CreatePipeline()
public async Task DoSomething()
{
return new AsyncPipeline<Bitmap>(new ActivatorUtilitiesMiddlewareResolver(_serviceProvider)) // Pass ActivatorUtilitiesMiddlewareResolver
.Add<RoudCornersAsyncMiddleware>()
.Add<AddTransparencyAsyncMiddleware>()
.Add<AddWatermarkAsyncMiddleware>();
Bitmap image = (Bitmap) Image.FromFile("party-photo.png");
await _pipeline.Execute(image);
}
}

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

// The following constructor argument will be provided by IServiceProvider
public RoudCornersAsyncMiddleware(ILogger<RoudCornersAsyncMiddleware> logger)
{
_logger = logger;
Expand All @@ -259,6 +265,29 @@ public class RoudCornersAsyncMiddleware : IAsyncMiddleware<Bitmap>
}
```

Or instantiate pipeline/chain of responsibility directly:
```C#
services.AddMiddlewareFromAssembly(typeof(RoudCornersAsyncMiddleware).Assembly);

public class MyService : IMyService
{
public async Task DoSomething()
{
IServiceProvider serviceProvider = GetServiceProvider();

IAsyncPipeline<Bitmap> pipeline = new AsyncPipeline<Bitmap>(new ServiceProviderMiddlewareResolver(serviceProvider))
.Add<RoudCornersAsyncMiddleware>()
.Add<AddTransparencyAsyncMiddleware>()
.Add<AddWatermarkAsyncMiddleware>();

Bitmap image = (Bitmap) Image.FromFile("party-photo.png");
await pipeline.Execute(image);
}

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.

For more information on dependency injection, see: [Dependency injection - .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ public void Resolve_ResolvesDisposableMiddleware()
}

[Fact]
public void Resolve_TransientServiceGetsDisposed1()
public void Resolve_TransientServiceGetsDisposedWhenRootServiceProviderIsDisposed()
{
var serviceProvider = new ServiceCollection()
.AddTransient<ITransientService, TransientService>()
Expand All @@ -201,7 +201,7 @@ public void Resolve_TransientServiceGetsDisposed1()
}

[Fact]
public void Resolve_TransientServiceGetsDisposed2()
public void Resolve_TransientServiceGetsDisposedWhenScopeIsDisposed()
{
var serviceProvider = new ServiceCollection()
.AddTransient<ITransientService, TransientService>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
using Microsoft.Extensions.DependencyInjection;
using PipelineNet.ServiceProvider.MiddlewareResolver;
using PipelineNet.Middleware;

namespace PipelineNet.ServiceProvider.Tests.MiddlewareResolver
{
public class ServiceProviderMiddlewareResolverTests
{
#region Service defintions
public interface ITransientService
{
}

public class TransientService : ITransientService
{
}

public interface IScopedService
{
}

public class ScopedService : IScopedService
{
}

public interface ISingletonService
{
}

public class SingletonService : ISingletonService
{
}
#endregion

#region Middleware definitions
public class ParameterlessConstructorMiddleware : IMiddleware<object>
{
public void Run(object parameter, Action<object> next)
{
}
}

public class TransientMiddleware : IMiddleware<object>
{
public ITransientService Service { get; }

public TransientMiddleware(ITransientService service)
{
Service = service;
}

public void Run(object parameter, Action<object> next)
{
}
}

public class ScopedMiddleware : IMiddleware<object>
{
public IScopedService Service { get; }

public ScopedMiddleware(IScopedService service)
{
Service = service;
}

public void Run(object parameter, Action<object> next)
{
}
}

public class SingletonMiddleware : IMiddleware<object>
{
public ISingletonService Service { get; }

public SingletonMiddleware(ISingletonService service)
{
Service = service;
}

public void Run(object parameter, Action<object> next)
{
}
}

public class DisposableMiddleware : IMiddleware<object>, IDisposable
{
public void Run(object parameter, Action<object> next)
{
}

public void Dispose()
{
}
}
#endregion

[Fact]
public void Resolve_ResolvesParameterlessConstructorMiddleware()
{
var serviceProvider = new ServiceCollection()
.AddTransient<ParameterlessConstructorMiddleware>()
.BuildServiceProvider(validateScopes: true);
var resolver = new ServiceProviderMiddlewareResolver(serviceProvider);

var resolverResult = resolver.Resolve(typeof(ParameterlessConstructorMiddleware));

Assert.NotNull(resolverResult.Middleware);
Assert.False(resolverResult.IsDisposable);
}

[Fact]
public void Resolve_ResolvesTransientMiddleware()
{
var serviceProvider = new ServiceCollection()
.AddTransient<TransientMiddleware>()
.AddTransient<ITransientService, TransientService>()
.BuildServiceProvider(validateScopes: true);
var resolver = new ServiceProviderMiddlewareResolver(serviceProvider);

var resolverResult = resolver.Resolve(typeof(TransientMiddleware));

Assert.NotNull(resolverResult.Middleware);
Assert.False(resolverResult.IsDisposable);
}

[Fact]
public void Resolve_ResolvesScopedMiddleware()
{
var serviceProvider = new ServiceCollection()
.AddScoped<ScopedMiddleware>()
.AddScoped<IScopedService, ScopedService>()
.BuildServiceProvider(validateScopes: true);
var scope = serviceProvider.CreateScope();
var resolver = new ServiceProviderMiddlewareResolver(scope.ServiceProvider);

var resolverResult = resolver.Resolve(typeof(ScopedMiddleware));

Assert.NotNull(resolverResult.Middleware);
Assert.False(resolverResult.IsDisposable);
}

[Fact]
public void Resolve_ResolvesSingletonMiddleware()
{
var serviceProvider = new ServiceCollection()
.AddSingleton<SingletonMiddleware>()
.AddSingleton<ISingletonService, SingletonService>()
.BuildServiceProvider(validateScopes: true);
var resolver = new ServiceProviderMiddlewareResolver(serviceProvider);

var resolverResult = resolver.Resolve(typeof(SingletonMiddleware));

Assert.NotNull(resolverResult.Middleware);
Assert.False(resolverResult.IsDisposable);
}

[Fact]
public void Resolve_ResolvesDisposableMiddleware()
{
var serviceProvider = new ServiceCollection()
.AddSingleton<DisposableMiddleware>()
.AddSingleton<ISingletonService, SingletonService>()
.BuildServiceProvider(validateScopes: true);
var resolver = new ServiceProviderMiddlewareResolver(serviceProvider);

var resolverResult = resolver.Resolve(typeof(DisposableMiddleware));

Assert.NotNull(resolverResult.Middleware);
Assert.False(resolverResult.IsDisposable);
}

[Fact]
public void Resolve_ThrowsInvalidOperationExceptionWhenServiceIsNotRegistered()
{
var serviceProvider = new ServiceCollection()
.BuildServiceProvider(validateScopes: true);
var resolver = new ServiceProviderMiddlewareResolver(serviceProvider);

Assert.Throws<InvalidOperationException>(() =>
resolver.Resolve(typeof(TransientMiddleware)));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand All @@ -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
Loading
Loading