Skip to content

Commit

Permalink
Add advanced channel configuration options and update docs
Browse files Browse the repository at this point in the history
Expanded the channel configuration options in the dependency injection library, allowing more control over channel settings. Bounded and unbounded channels can now be created and configured with various parameters. Updated documentation in README.md to include examples of advanced usage of the channels. Introduced a new ChannelSettings class for handling these configurations. Also, refactored some parts of service collection extension methods for better code organization and efficiency.
  • Loading branch information
frankhaugen committed Feb 24, 2024
1 parent d201fdb commit 5e9d7cd
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 25 deletions.
31 changes: 26 additions & 5 deletions Frank.Channels.DependencyInjection/ChannelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,37 @@

namespace Frank.Channels.DependencyInjection;

public class ChannelFactory : IChannelFactory
internal class ChannelFactory : IChannelFactory
{
private readonly ConcurrentDictionary<string, object> _cache = new();

public Channel<T> CreateChannel<T>() where T : class => _cache.GetOrAdd(typeof(T).Name, Value<T>) as Channel<T> ?? throw new InvalidOperationException($"Channel<{typeof(T).Name}> not found");
/// <inheritdoc />
public Channel<T> CreateUnboundedChannel<T>(ChannelSettings? options = null) where T : class => _cache.GetOrAdd(typeof(T).Name, name => CreateUnbounded<T>(name, options ?? new ChannelSettings())) as Channel<T> ?? throw new InvalidOperationException($"Channel<{typeof(T).Name}> not found");

private static Channel<T> Value<T>(string arg) where T : class =>
/// <inheritdoc />
public Channel<T> CreateBoundedChannel<T>(ChannelSettings? options = null) where T : class => _cache.GetOrAdd(typeof(T).Name, name => CreateBounded<T>(name, options ?? new ChannelSettings())) as Channel<T> ?? throw new InvalidOperationException($"Channel<{typeof(T).Name}> not found");

private static Channel<T> CreateBounded<T>(string name, ChannelSettings options) where T : class =>
Channel.CreateBounded<T>(new BoundedChannelOptions(options.BoundedCapacity)
{
FullMode = options.BoundedFullMode
});

private static Channel<T> CreateUnbounded<T>(string arg, ChannelSettings options) where T : class =>
Channel.CreateUnbounded<T>(new UnboundedChannelOptions()
{
SingleReader = true,
SingleWriter = true
SingleReader = options.SingleReader,
SingleWriter = options.SingleWriter
});
}

public class ChannelSettings
{
public bool SingleReader { get; set; } = true;

public bool SingleWriter { get; set; } = true;

public int BoundedCapacity { get; set; } = 100;

public BoundedChannelFullMode BoundedFullMode { get; set; } = BoundedChannelFullMode.Wait;
}
7 changes: 7 additions & 0 deletions Frank.Channels.DependencyInjection/ChannelType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Frank.Channels.DependencyInjection;

public enum ChannelType
{
Unbounded,
Bounded
}
6 changes: 4 additions & 2 deletions Frank.Channels.DependencyInjection/IChannelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Frank.Channels.DependencyInjection;

public interface IChannelFactory
internal interface IChannelFactory
{
Channel<T> CreateChannel<T>() where T : class;
Channel<T> CreateUnboundedChannel<T>(ChannelSettings? options = null) where T : class;

Channel<T> CreateBoundedChannel<T>(ChannelSettings? options = null) where T : class;
}
109 changes: 91 additions & 18 deletions Frank.Channels.DependencyInjection/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,8 @@ namespace Frank.Channels.DependencyInjection;

public static class ServiceCollectionExtensions
{
internal static bool Contains<TService>(this IServiceCollection services)
{
return services.Any(descriptor => descriptor.ServiceType == typeof(TService));
}

internal static IServiceCollection AddSingletonIfNotExists<TService, TImplementation>(this IServiceCollection services) where TService : class where TImplementation : class, TService
{
if (!services.Contains<TService>())
services.AddSingleton<TService, TImplementation>();
return services;
}

/// <summary>
/// Adds a channel of type <typeparamref name="T"/> to the service collection.
/// Adds an unbounded channel of type <typeparamref name="T"/> to the service collection, with default settings.
/// </summary>
/// <remarks>
/// The channel is added as a singleton with its reader and writer as singletons, and injected as follows:
Expand All @@ -31,12 +19,97 @@ internal static IServiceCollection AddSingletonIfNotExists<TService, TImplementa
/// <param name="services"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IServiceCollection AddChannel<T>(this IServiceCollection services) where T : class
public static IServiceCollection AddChannel<T>(this IServiceCollection services) where T : class =>
services.AddChannel<T>(ChannelType.Unbounded, new ChannelSettings());

/// <summary>
/// Adds a channel of type <typeparamref name="T"/> to the IServiceCollection.
/// </summary>
/// <typeparam name="T">The type of the channel.</typeparam>
/// <param name="services">The IServiceCollection to add the channel to.</param>
/// <param name="channelType">The type of channel to add.</param>
/// <returns>The same instance of the IServiceCollection after the channel has been added.</returns>
/// <remarks>
/// This method adds a channel of type <typeparamref name="T"/> to the IServiceCollection.
/// It allows specifying the channel type (unbounded or bounded) and optional settings for the channel.
/// After the channel is added, it will be available for injection as a singleton instance of Channel{T}.
/// The respective ChannelReader{T} and ChannelWriter{T} instances are also added as singletons.
/// </remarks>
public static IServiceCollection AddChannel<T>(this IServiceCollection services, ChannelType channelType) where T : class =>
services.AddChannel<T>(channelType, new ChannelSettings());

/// <summary>
/// Adds an unbounded channel of type <typeparamref name="T"/> to the IServiceCollection.
/// </summary>
/// <typeparam name="T">The type of the channel.</typeparam>
/// <param name="services">The IServiceCollection to add the channel to.</param>
/// <returns>The same instance of the IServiceCollection after the channel has been added.</returns>
public static IServiceCollection AddUnboundedChannel<T>(this IServiceCollection services) where T : class =>
services.AddChannel<T>(ChannelType.Unbounded, new ChannelSettings());

/// <summary>
/// Adds an unbounded channel of type <typeparamref name="T"/> to the IServiceCollection.
/// </summary>
/// <typeparam name="T">The type of the channel.</typeparam>
/// <param name="services">The IServiceCollection to add the channel to.</param>
/// <returns>The same instance of the IServiceCollection after the channel has been added.</returns>
public static IServiceCollection AddUnboundedChannel<T>(this IServiceCollection services, ChannelSettings settings) where T : class =>

Check warning on line 56 in Frank.Channels.DependencyInjection/ServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Merge Job / Publish Preview Job

Parameter 'settings' has no matching param tag in the XML comment for 'ServiceCollectionExtensions.AddUnboundedChannel<T>(IServiceCollection, ChannelSettings)' (but other parameters do)

Check warning on line 56 in Frank.Channels.DependencyInjection/ServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Merge Job / Publish Preview Job

Parameter 'settings' has no matching param tag in the XML comment for 'ServiceCollectionExtensions.AddUnboundedChannel<T>(IServiceCollection, ChannelSettings)' (but other parameters do)

Check warning on line 56 in Frank.Channels.DependencyInjection/ServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Release Job / Release Job

Parameter 'settings' has no matching param tag in the XML comment for 'ServiceCollectionExtensions.AddUnboundedChannel<T>(IServiceCollection, ChannelSettings)' (but other parameters do)

Check warning on line 56 in Frank.Channels.DependencyInjection/ServiceCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / Release Job / Release Job

Parameter 'settings' has no matching param tag in the XML comment for 'ServiceCollectionExtensions.AddUnboundedChannel<T>(IServiceCollection, ChannelSettings)' (but other parameters do)
services.AddChannel<T>(ChannelType.Unbounded, settings);

/// <summary>
/// Adds a bounded channel of type <typeparamref name="T"/> to the IServiceCollection.
/// </summary>
/// <typeparam name="T">The type of the channel.</typeparam>
/// <param name="services">The IServiceCollection to add the channel to.</param>
/// <returns>The same instance of the IServiceCollection after the channel has been added.</returns>
public static IServiceCollection AddBoundedChannel<T>(this IServiceCollection services) where T : class =>
services.AddChannel<T>(ChannelType.Bounded, new ChannelSettings());

/// <summary>
/// Adds a bounded channel of type <typeparamref name="T"/> to the IServiceCollection.
/// </summary>
/// <typeparam name="T">The type of the channel.</typeparam>
/// <param name="services">The IServiceCollection to add the channel to.</param>
/// <param name="settings">The settings for the channel.</param>
/// <returns>The same instance of the IServiceCollection after the channel has been added.</returns>
public static IServiceCollection AddBoundedChannel<T>(this IServiceCollection services, ChannelSettings settings) where T : class =>
services.AddChannel<T>(ChannelType.Bounded, settings);

/// <summary>
/// Adds a channel of type <typeparamref name="T"/> to the IServiceCollection.
/// </summary>
/// <typeparam name="T">The type of the channel.</typeparam>
/// <param name="services">The IServiceCollection to add the channel to.</param>
/// <param name="channelType">The type of channel to add.</param>
/// <param name="settings">The settings for the channel.</param>
/// <returns>The same instance of the IServiceCollection after the channel has been added.</returns>
public static IServiceCollection AddChannel<T>(this IServiceCollection services, ChannelType channelType, ChannelSettings settings) where T : class =>
services
.ThrowIfContains<Channel<T>>()
.AddSingletonIfNotExists<IChannelFactory, ChannelFactory>()
.AddSingleton<Channel<T>>(provider => channelType switch
{
ChannelType.Unbounded => provider.GetRequiredService<IChannelFactory>().CreateUnboundedChannel<T>(settings),
ChannelType.Bounded => provider.GetRequiredService<IChannelFactory>().CreateBoundedChannel<T>(settings),
_ => throw new ArgumentOutOfRangeException(nameof(channelType), channelType, null)
})
.AddSingleton<ChannelReader<T>>(provider => provider.GetRequiredService<Channel<T>>().Reader)
.AddSingleton<ChannelWriter<T>>(provider => provider.GetRequiredService<Channel<T>>().Writer);

private static IServiceCollection AddSingletonIfNotExists<TService, TImplementation>(this IServiceCollection services) where TService : class where TImplementation : class, TService
{
services.AddSingletonIfNotExists<IChannelFactory, ChannelFactory>();
services.AddSingleton<Channel<T>>(provider => provider.GetRequiredService<IChannelFactory>().CreateChannel<T>());
services.AddSingleton<ChannelReader<T>>(provider => provider.GetRequiredService<Channel<T>>().Reader);
services.AddSingleton<ChannelWriter<T>>(provider => provider.GetRequiredService<Channel<T>>().Writer);
if (!services.Contains<TService>())
services.AddSingleton<TService, TImplementation>();
return services;
}

private static IServiceCollection ThrowIfContains<TService>(this IServiceCollection services) where TService : class
{
if (services.Contains<TService>())
throw new InvalidOperationException($"Service of type {typeof(TService).Name} already exists in the service collection.");
return services;
}

private static bool Contains<TService>(this IServiceCollection services)
=> services.Any(descriptor => descriptor.ServiceType == typeof(TService));
}
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ using Frank.Channels.DependencyInjection;
// Register the channel of a type as a dependency:
services.AddChannel<string>();

// Use the channel as a dependency in various ways:
var channel = provider.GetRequiredService<Channel<string>>();
var channelWriter = provider.GetRequiredService<ChannelWriter<string>>();
var channelReader = provider.GetRequiredService<ChannelReader<string>>();
```

## Advanced usage

```csharp
using Frank.Channels.DependencyInjection;

// Register the channel of a type as a dependency with a custom configuration:
services.AddChannel<string>(options =>
{
options.BoundedCapacity = 100;
options.FullMode = BoundedChannelFullMode.Wait;
options.SingleReader = true;
options.SingleWriter = true;
});

// Use the channel as a dependency in various ways:
var channel = provider.GetRequiredService<Channel<string>>();
var channelWriter = provider.GetRequiredService<ChannelWriter<string>>();
Expand Down

0 comments on commit 5e9d7cd

Please sign in to comment.