Channel Express

Introductions

Terralect.ChannelExpress is an implementation of the Mediator Pattern, and was built on the Terralect.Channels package. The Channels package is a Pipes & Filters implementation.


To setup Channel Express, you need to do the following:


    ChannelExpressSetup.Configure(config =>
   {
       config.ConfigureDependencyRegistrar(diManager);
   });


There is another way to setup the dependency injection, using the static class:

    DependencyInjectionManager.SetDependencyManager(diManager);


However, instead of implementing your own dependency injection manager/proxy, we've already created one for Microsoft.Extensions.DependencyInjection, which is called Terralect.ChannelExpress.MicrosoftDependencyInjection. If using this package, you can setup Channel Express with the following code:


    serviceCollection.AddChannelExpress();


You can also setup Channel Express with Microsoft DI in the following way:


    ChannelExpressSetup.Configure(config =>
   {
       config.ConfigureDependencyRegistrar(new DependencyManager(serviceCollection));
   });



There are a few additional options available using the optional configuration delegate:


    serviceCollection.AddChannelExpress(config =>
   {
       config.SetConfigurationActionLog(Console.WriteLine);
   });

Send Requests

Create a class that represents a request, and another class that represents the result/response. Inject IChannelExpress into a class to use it. Use any of the various Send() methods to initiate a request.


    private readonly IChannelExpress channel;


    public LocalService(IChannelExpress channel)

    {

        this.channel = channel;

    }


    private void DemoSendOne()

    {

        var result = channel.Send<PlaceOrderCommand, PlaceOrderResult>(new PlaceOrderCommand(10, "ROCK"));

        Console.WriteLine($" * Result of order: {result.Value.Success} = {result.Value.Message}");

    }

Handle Requests

Decorate a handler class with RequestHandler<,>. Request handlers are the last 'layer' to receive requests.


    public class FetchOrderStatusHandler : RequestHandler<FetchOrderStatusQuery, FetchOrderStatusResult>
   {
       public override Task<FetchOrderStatusResult> Handle(FetchOrderStatusQuery input)
       {
           return Task.FromResult(new FetchOrderStatusResult("Shipping in progress"));
       }
   }



If you are sending a request but are not expecting a result/response, then your handler should make use of the NoResult object:


    //No response type defined when sending a request

    channelExpress.Send(new Request());


    //The handler class using NoResult

    public class NoResultHandler : RequestHandler<Request, NoResult>
   {
       public override Task<NoResult> Handle(Request input)
       {
           return NoResult.Void;
       }
   }

Filter Requests Globally

Decorate a class with GlobalRequestFilter to filter all requests. Global request filters will receive a request first.


    [FilterOrder(1)] //Optional order parameter. The lower ordered filters execute first

    public class MyGlobalLogFilter : GlobalRequestFilter

    {

        public override async Task<IEnumerable<object>> Process(IEnumerable<object> input)

        {

            Console.WriteLine("Global filter enter");

            var watch = new Stopwatch();

            watch.Start();

            var results = await Next(input);

            watch.Stop();

            Console.WriteLine($"Global filter exit. It took {watch.ElapsedMilliseconds} ms to execute {input.GetType().Name}");

            return results;

        }

    }

Filter Specific Requests

Decorate a class with RequestFilter<,> to filter specific types of requests. Specific request filters will receive requests after they have passed through Global Request Filters.


    public class OrderFilter : RequestFilter<PlaceOrderCommand, PlaceOrderResult>

    {

        public override async Task<IEnumerable<PlaceOrderResult>> Process(IEnumerable<PlaceOrderCommand> input)

        {

            Console.WriteLine($"Order filter enter with {input.Count()} items");

            var filteredOrders = input.Where(i => i.Quantity < 50);

            var next = await Next(filteredOrders);

            Console.WriteLine($"Order filter exit with {filteredOrders.Count()} items");

            return next;

        }

    }

Send Multiple Requests

    var manyOrders = new List<PlaceOrderCommand>
   {
       new PlaceOrderCommand(38, "ROCK"),
       new PlaceOrderCommand(42, "PAPER"),
       new PlaceOrderCommand(77, "SCISSORS")
   };
   var result = channel.SendMany<PlaceOrderCommand, PlaceOrderResult>(manyOrders);

Use the Channel Bag

The ChannelBag allows you to store objects in a dictionary that can be injected in any Layer in the Channel.


    private readonly ChannelBag channelBag;


    public OrderHandler(ChannelBag channelBag)

    {

        this.channelBag = channelBag;

    }


Add objects to the bag:

    var dbConnection = new CurrentDbConnection();
   channelBag.Set(dbConnection);


Retrieve objects from the bag:
   var currentDbConnection = channelBag.Get<CurrentDbConnection>();

or

    var someValue = channelBag.GetWithDefault("SomeKey", defaultValue);

Access the Current DI Scope

Inject the ChannelServiceProvider to access the current dependency injection scope.


    public OrderHandler(ChannelServiceProvider scopedServiceProvider)

    {

        var myCalculator = scopedServiceProvider.Instance.GetService<MyCalculator>();

    }

Prebuilt Filters

We have created some filters using Channel Express that help facilitate seamless DB access. These filters manage your database connection for you, and also make it easy to chain an existing connection up to multiple components.

Downloads

ChannelExpress2.Demo (zip)

Download