CodeBlanks Logo Icon CodeBlanks

Putting it together

C# course · Advanced · Lesson 83/83

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

  1. 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.

  2. Match integration pieces

    Runner
    Runs commands in turn
    Names
    Exposes registered names
    RunAllAsync
    Runs all commands
    foreach
    Iterates over names

All lessons in Advanced