Putting it together
Combine all layers into a working mini framework.
Putting it together
The final lesson merges all pieces. So far we have seen the command pattern, async, reflection, attributes, the DI container, and a test. Now we add a Runner class that runs all commands in the container in turn.
Runner takes a Container; its async Task RunAllAsync() method walks every command name in the container, resolves it, runs it with await cmd.ExecuteAsync(), and prints "{name}: {result}". Main now runs not one command but all registered commands. This is the skeleton of a real application.
Adding a Names property to Container
For Runner to walk all commands, the container must expose the registered names. public IEnumerable<string> Names => _factories.Keys; provides this. IEnumerable<string> emphasizes the read-only nature; outside code cannot mutate the dictionary.
Runner iterates this Names with foreach. For each name it calls _container.Resolve<ICommand>(name); the result is an ICommand instance. It runs it with await and prints the output. This is the moment async + DI + reflection form a real whole.
Exercise Add Runner and Names
Add a public IEnumerable<string> Names => _factories.Keys; property to the Container class. Define a Runner class: its constructor takes a Container; its public async Task RunAllAsync() method iterates _container.Names with foreach, calls _container.Resolve<ICommand>(name) for each, and prints Console.WriteLine($"{name}: {result}") from await cmd.ExecuteAsync(). In Main, create a Container, register with Register("hello", () => new HelloCommand()), create a Runner, and call await runner.RunAllAsync().
Show example solution
public IEnumerable<string> Names => _factories.Keys;. Write class Runner { private readonly Container _container; public Runner(Container container) => _container = container; public async Task RunAllAsync() { foreach (var name in _container.Names) { var cmd = _container.Resolve<ICommand>(name); Console.WriteLine($"{name}: {await cmd.ExecuteAsync()}"); } } }. In Main: var container = new Container(); container.Register("hello", () => new HelloCommand()); var runner = new Runner(container); await runner.RunAllAsync();
Example solution:
using System;
using System.Threading.Tasks;
using System.Reflection;
using System.Collections.Generic;
[Command("hello")]
class HelloCommand : ICommand
{
public async Task<string> ExecuteAsync()
{
await Task.CompletedTask;
return "Merhaba";
}
}
class CommandAttribute : Attribute
{
public string Name { get; }
public CommandAttribute(string name) => Name = name;
}
interface ICommand
{
Task<string> ExecuteAsync();
}
class Container
{
private readonly Dictionary<string, Func<ICommand>> _factories = new();
public void Register<T>(string name, Func<T> factory) where T : class, ICommand =>
_factories[name] = () => factory();
public T Resolve<T>(string name) where T : class => (T)_factories[name]();
public IEnumerable<string> Names => _factories.Keys;
}
class Runner
{
private readonly Container _container;
public Runner(Container container) => _container = container;
public async Task RunAllAsync()
{
foreach (var name in _container.Names)
{
var cmd = _container.Resolve<ICommand>(name);
Console.WriteLine($"{name}: {await cmd.ExecuteAsync()}");
}
}
}
class Program
{
static async Task Main()
{
var container = new Container();
container.Register("hello", () => new HelloCommand());
var runner = new Runner(container);
await runner.RunAllAsync();
}
}
Exercise Add a second command and run
Add a ByeCommand : ICommand class to the existing structure. Mark it with [Command("bye")] and have its ExecuteAsync() method call await Task.CompletedTask and return "Hoşça kal". In Main, register the second command with container.Register("bye", () => new ByeCommand()); the Runner then runs both commands in turn. Thanks to the Names property, foreach walks both names.
Show example solution
ICommand { public async Task<string> ExecuteAsync() { await Task.CompletedTask; return "Hoşça kal"; } }. In Main add container.Register("bye", () => new ByeCommand());
Example solution:
using System;
using System.Threading.Tasks;
using System.Reflection;
using System.Collections.Generic;
[Command("hello")]
class HelloCommand : ICommand
{
public async Task<string> ExecuteAsync()
{
await Task.CompletedTask;
return "Merhaba";
}
}
[Command("bye")]
class ByeCommand : ICommand
{
public async Task<string> ExecuteAsync()
{
await Task.CompletedTask;
return "Hoşça kal";
}
}
class CommandAttribute : Attribute
{
public string Name { get; }
public CommandAttribute(string name) => Name = name;
}
interface ICommand
{
Task<string> ExecuteAsync();
}
class Container
{
private readonly Dictionary<string, Func<ICommand>> _factories = new();
public void Register<T>(string name, Func<T> factory) where T : class, ICommand =>
_factories[name] = () => factory();
public T Resolve<T>(string name) where T : class => (T)_factories[name]();
public IEnumerable<string> Names => _factories.Keys;
}
class Runner
{
private readonly Container _container;
public Runner(Container container) => _container = container;
public async Task RunAllAsync()
{
foreach (var name in _container.Names)
{
var cmd = _container.Resolve<ICommand>(name);
Console.WriteLine($"{name}: {await cmd.ExecuteAsync()}");
}
}
}
class Program
{
static async Task Main()
{
var container = new Container();
container.Register("hello", () => new HelloCommand());
container.Register("bye", () => new ByeCommand());
var runner = new Runner(container);
await runner.RunAllAsync();
}
}
Test yourself
-
What does Runner.RunAllAsync do?
- Runs all commands in the container in turn
- Runs only the first command
- Deletes commands
- Registers a new command
Show answer
Runs all commands in the container in turn — Correct. RunAllAsync resolves and runs every command in the container in turn.
-
Match integration pieces
- Runner
- Runs commands in turn
- Names
- Exposes registered names
- RunAllAsync
- Runs all commands
- foreach
- Iterates over names