Recommendation for resolving at runtime #129
Unanswered
CrispyDrone
asked this question in
Q&A
Replies: 1 comment 2 replies
-
|
Commands dispatching (I mean generic types) isn't the responsibility of DI. I propose the following solution: using System.Diagnostics.CodeAnalysis;
using Pure.DI;
var composition = new Composition();
try
{
args = ["Folder 1"];
await composition.Dispatcher.Execute(args);
args = ["sftp://user@host.com/my-folder"];
await composition.Dispatcher.Execute(args);
}
catch (InvalidOperationException error)
{
Console.Error.WriteLine("ERROR: " + error.Message);
}
// Commands
interface ICommand
{
public string Description { get; }
}
record ImportLocalFilesCommand(string Description, string ImportDirectory) : ICommand;
record ImportRemoteFilesCommand(string Description, string Url) : ICommand;
// Parsers
internal interface ICommandParser
{
bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out ICommand? command);
}
sealed class ImportRemoteFilesCommandParser : ICommandParser
{
public bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out ICommand? command)
{
if (args is [var url, ..] && url.StartsWith("sftp"))
{
command = new ImportRemoteFilesCommand("Importing files from a remote location.", url);
return true;
}
command = null;
return false;
}
}
sealed class ImportLocalFilesCommandParser : ICommandParser
{
public bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out ICommand? command)
{
if (args is [var importDirectory, ..])
{
command = new ImportLocalFilesCommand("Importing files from the local file system.", importDirectory);
return true;
}
command = null;
return false;
}
}
// Handlers
interface ICommandHandler
{
bool CanExecute(ICommand command);
Task Execute(ICommand command);
}
abstract class CommandHandler<TCommand>
where TCommand : ICommand
{
public bool CanExecute(ICommand command) => command is TCommand;
public Task Execute(ICommand command) => Execute((TCommand)command);
protected abstract Task Execute(TCommand command);
}
sealed class ImportLocalFilesCommandHandler(ILocalFileSystem localFileSystem)
: CommandHandler<ImportLocalFilesCommand>, ICommandHandler
{
protected override Task Execute(ImportLocalFilesCommand command)
{
var files = localFileSystem.GetFiles(new DirectoryInfo(command.ImportDirectory));
foreach (var file in files)
{
Console.WriteLine($"Reading {file}");
}
return Task.CompletedTask;
}
}
sealed class ImportRemoteFilesCommandHandler(IRemoteClient remoteClient)
: CommandHandler<ImportRemoteFilesCommand>, ICommandHandler
{
protected override Task Execute(ImportRemoteFilesCommand command)
{
var urls = remoteClient.GetDownloadUrls(command.Url);
foreach (var url in urls)
{
Console.WriteLine($"Downloading {url}");
}
return Task.CompletedTask;
}
}
// Services
interface ICommandDispatcher
{
Task Execute(IReadOnlyList<string> args);
}
sealed class CommandDispatcher(
IReadOnlyCollection<ICommandParser> parsers,
IReadOnlyCollection<ICommandHandler> handlers)
: ICommandDispatcher
{
public Task Execute(IReadOnlyList<string> args)
{
foreach(var parser in parsers)
{
if (!parser.TryParse(args, out var command))
{
continue;
}
Console.WriteLine(command.Description);
return Execute(command);
}
throw new InvalidOperationException("No parser found for the given arguments: " + string.Join(" ", args) + ".");
}
private async Task Execute(ICommand command)
{
foreach (var handler in handlers)
{
if (!handler.CanExecute(command))
{
continue;
}
await handler.Execute(command);
return;
}
throw new InvalidOperationException($"No handler found for {command.GetType().Name}");
}
}
interface IRemoteClient
{
public IEnumerable<string> GetDownloadUrls(string folder);
}
class RemoteClient : IRemoteClient
{
public IEnumerable<string> GetDownloadUrls(string folder)
{
yield return "url:A";
yield return "url:B";
}
}
public interface ILocalFileSystem
{
FileInfo GetFile(string path);
IEnumerable<FileInfo> GetFiles(DirectoryInfo directory);
}
public class LocalFileSystem : ILocalFileSystem
{
public FileInfo GetFile(string path)
{
return new FileInfo(path);
}
public IEnumerable<FileInfo> GetFiles(DirectoryInfo directory)
{
yield return new FileInfo("A");
yield return new FileInfo("B");
}
}
sealed partial class Composition
{
void Setup() =>
DI.Setup()
.DefaultLifetime(Lifetime.Singleton)
// Parsers
.Bind(Tag.Unique).To<ImportRemoteFilesCommandParser>()
.Bind(Tag.Unique).To<ImportLocalFilesCommandParser>()
// Handlers
.Bind(Tag.Unique).To<ImportRemoteFilesCommandHandler>()
.Bind(Tag.Unique).To<ImportLocalFilesCommandHandler>()
// Services
.Bind().To<CommandDispatcher>()
.Bind().To<RemoteClient>()
.Bind().To<LocalFileSystem>()
.Root<ICommandDispatcher>("Dispatcher");
}This solution complies with all SOLID principles and will ensure ease of maintenance. You can combine the parsing and command execution logic in a single class, but I don't recommend getting rid of the Instead of writing your own commands implementation, you might consider using the System.CommandLine. Here's an example of using it with the Pure.DI. A shorter composition: partial class Composition
{
void Setup() =>
DI.Setup()
// Parsers and handlers
.Singleton<ImportRemoteFilesCommandParser, ImportRemoteFilesCommandHandler>(Tag.Unique)
.Singleton<ImportLocalFilesCommandParser, ImportLocalFilesCommandHandler>(Tag.Unique)
// Services
.Singleton<CommandDispatcher, RemoteClient, LocalFileSystem>()
.Root<ICommandDispatcher>("Dispatcher");
} |
Beta Was this translation helpful? Give feedback.
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I'm trying to create a console application that will parse its arguments into a command and then handle this command. It relies on an
ICommandandICommandHandlerinterface à la MediatR to be able to dispatch the commands and have one or multiple handlers process the command.What is the best way to approach this with
Pure.DI?In many reflection-based dependency injection libraries, you can register open generics making this very trivial.
I've scoured through the discussions and other issues, and I understand
Pure.DIrequires a different mode of thinking and approach.I've tried different things, but tend to encounter errors at runtime, need to do some unfortunate casts, and/or have to register everything manually:
I've created a repository with some things I've tried, but here is one example:
This works but I have to reflection in the dispatcher, casting in the handlers, and register every single one manually, which definitely doesn't feel like the right way!
Beta Was this translation helpful? Give feedback.
All reactions