Using Chain of Responsibility Design Patterns in C# ASP .NET

modified

Introduction

A powerful design pattern geared towards handling an event is the Chain of Responsbility design pattern. This pattern helps decouple related algorithms in C# ASP .NET and can help your software become more scalable and easier to control.

In general, the Chain of Responsbility design pattern allows you to link together a serious of classes or algorithms in a hierarchy and pass a command (preferably a Command pattern object) into the mix to get handled by one of the members of the chain. One of the benefits of this design pattern is, not having to know the details of the members in the chain. You simply pass the command into the chain. Each class will attempt to handle the request until all classes have been used. While it is possible for a request to go unhandled, this is a powerful design pattern when used appropriately.

Chain of Responsibility Design Patterns in C# ASP .NET

Two Ways to Make a Chain

As with all first programming examples, the Hello World example is a good introduction to an implementation of the Chain of Responsbility design pattern in C# ASP .NET. However, first let’s take a look at the standard way of handling a request without using the Chain of Responsbility.

Standard Method of Handling a Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main()
{
   Command MyRequest = new Command();

   if (Handler1(MyRequest) == true)
   {
      return;
   }
   else if (Handler2(MyRequest) == true)
   {
      return;
   }
   else if (Handler3(MyRequest) == true)
   {
      return;
   }
}

As you can see in the above example, to handle MyRequest we pass it to each Handler function that we’ve defined. There are several drawbacks to this simplistic method, especially in an object oriented language such as C# .NET. Foremost, there are many conditionals which create confusion when reading the code. This can especially become a problem when performing team development and debugging. More importantly, detailed knowledge of the handler implementations are required since they are listed within the conditional.

The above example can be enhanced with a Chain of Responsbility as shown in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
void main()
{
   Command MyRequest = new Command();

   Handler MyHandler1 = new Handler1();
   Handler MyHandler2 = new Handler2();
   Handler MyHandler3 = new Handler3();

   MyHandler1.SetSuccessor(MyHandler2);
   MyHandler2.SetSuccessor(MyHandler3);

   MyHandler1.HandleRequest(MyRequest);
}

In this example, we have implemented a Chain of Responsiblity design pattern with C# ASP .NET which handles the request. After setting up the initial chain, we only need to pass MyRequest to the first handler (which is of the generic type ‘Handler’). Knowledge of which handler gets called next is preserved within the chain itself.

Say Hello to the Chain

By following the above example for implementing a Chain of Responsibility, we can apply it to create a simple Hello World example as shown in the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void Main(string[] args)
{
    Handler MyEnglishHandler = new EnglishHandler();
    Handler MyFrenchHandler = new FrenchHandler();
    Handler MySpanishHandler = new SpanishHandler();

    // Setup the chain of responsibility.
    MyEnglishHandler.SetSuccessor(MyFrenchHandler);
    MyFrenchHandler.SetSuccessor(MySpanishHandler);           

    // Throw the first handler a request and then throw two more for fun.
    MyEnglishHandler.HandleRequest("en-US Hello World");
    MyEnglishHandler.HandleRequest("es-US Hola Mundo");
    MyEnglishHandler.HandleRequest("fr-FR Bonjour Monde");

    Console.ReadKey();
}

Output:

1
2
3
Handled by EnglishHandler: Hello World
Dirigido por SpanishHandler: Hola Mundo
Manipule pres FrenchHandler: Bonjour Monde

Notice that there will be three handlers, an English, a French, and a Spanish handler. The command request will consist of two parts: a culture indicator and the text to display. For this example, a simple string was used in place of a Command design pattern. The culture indicator is just the first 5 characters of the string. Each handler will look at the culture indicator to determine whether it should handle the printing of the text to the console. If the handler can not handle the command, it passes it to the next handler in the chain, until all handlers have been ran.

To get to the meat of the Chain of Responsibility, we need to define the generic class that our Handlers will inherit from.

The Handler Class

We have chosen to use an abstract class in place of an interface for implementing the generic Handler class. This is due to the fact that our Handler class will implement the body of one of the functions as well as define a protected member. Also note that abstract classes, in general, perform faster than interfaces due to less memory lookups.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Handler
{
    protected Handler successor;

    public Handler()
    {
    }

    public void SetSuccessor(Handler aSuccessor)
    {
        successor = aSuccessor;
    }

    public abstract void HandleRequest(string strRequest);
}

This Handler definition is fairly simple. The most important part is the inclusion of a protected successor object, of type Handler. This allows a form of a linked list to implement our chain. We also implement a SetSuccessor function to assign the successor. The HandleRequest function will be required by all handlers to initiate a possible action.

Creating a Concrete Handler

Now that we have the abstract class Handler defined, we can now create some concrete handlers to create a complete Chain of Responsibility in C# ASP .NET. Our first handler will handle English strings by looking in the command for the culture identifier “en-us”.

The EnglishHandler Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class EnglishHandler : Handler
{
    private const string LANGUAGE_TAG = "en-us";

    public override void HandleRequest(string strRequest)
    {
        int iStart = strRequest.ToLower().IndexOf(LANGUAGE_TAG);

        if (iStart > -1)
        {
            string strResponse = strRequest.Substring(iStart + LANGUAGE_TAG.Length, strRequest.Length - (iStart + LANGUAGE_TAG.Length));
            Console.WriteLine("Handled by EnglishHandler:" + strResponse);
        }
        else if (successor != null)
        {
            successor.HandleRequest(strRequest);
        }
    }
}

Notice our handler inherits from our abstract base class Handler and defines the required HandleRequest function. The body of the function simply checks the first 5 characters of the command string to see if it contains “en-us”. If it does, the payload portion of the string is printed to the console, the command is deemed handled, and execution of the chain stops. If “en-us” is not found, the handler passes the command to the next handler in line. Of course, if no other handler exists, the command remains unhandled. Unhandled commands could be managed as well by including additional detection code.

We can create two more handlers to make the example more interesting, as follows.

The FrenchHandler Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class FrenchHandler : Handler
{
    private const string LANGUAGE_TAG = "fr-fr";

    public override void HandleRequest(string strRequest)
    {
        int iStart = strRequest.ToLower().IndexOf(LANGUAGE_TAG);

        if (iStart > -1)
        {
            string strResponse = strRequest.Substring(iStart + LANGUAGE_TAG.Length, strRequest.Length - (iStart + LANGUAGE_TAG.Length));
            strResponse = "Manipulé près FrenchHandler:" + strResponse;

            Console.WriteLine(strResponse);
        }
        else if (successor != null)
        {
            successor.HandleRequest(strRequest);
        }
    }
}

Note, the FrenchHandler differs only by the culture identifier that it is looking for and by the action performed on the payload. Just like the EnglishHandler, the FrenchHandler passes the command to the next in line if it fails to handle it. The FrenchHandler doesn’t need to know the details about its successor, only that it’s of type Handler.

The SpanishHandler Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SpanishHandler : Handler
{
    private const string LANGUAGE_TAG = "es-us";

    public override void HandleRequest(string strRequest)
    {
        int iStart = strRequest.ToLower().IndexOf(LANGUAGE_TAG);

        if (iStart > -1)
        {
            string strResponse = strRequest.Substring(iStart + LANGUAGE_TAG.Length, strRequest.Length - (iStart + LANGUAGE_TAG.Length));
            Console.WriteLine("Dirigido por SpanishHandler:" + strResponse);
        }
        else if (successor != null)
        {
            successor.HandleRequest(strRequest);
        }
    }
}

Now that we have created 3 handlers, our original main code can be executed. We begin by defining three handlers of each concrete handler type. We then setup the chain in our desired order. Typically, order doesn’t matter since each handler will have a chance at the request. However, speed issues may arise where order becomes critical. Finally, we send a request to the first member of the chain and the Chain of Responsibility handles the rest. Notice in the main program above that we execute 3 requests. Each one will be handled by one of the concrete handlers.

1
2
3
4
// Throw the first one a request.
MyEnglishHandler.HandleRequest("en-US Hello World");
MyEnglishHandler.HandleRequest("es-US Hola Mundo");
MyEnglishHandler.HandleRequest("fr-FR Bonjour Monde");

Since the concrete handlers inherit from the generic Handler class, they have no need to know the concrete details about their successors. This allows us to implement new concrete handlers (for other languages, for example), and insert them anywhere in the chain.

What about C# Multicast Delegates?

C# delegates can be a form of a chain of responsiblity in themselves. By creating a delegate and assigning it multiple functions for execution, a multicast delgate is created and may be used in a similar fashion to a chain of responsibility design pattern. When the delegate is invoked, each function added to the multicast delgate’s chain is executed. Where a typical chain of responsiblity pattern will only continue executing handlers until one succeeds, the multicast delegate will always execute each handler. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
delegate void MyMulticastDelegate(int x, int y);

void main()
{
   MyMulticastDelegate MyFunction = new MyMulticastDelegate(MyFunc1);
   MyFunction += new MyMulticastDelegate(MyFucn2);

   // Calling MyFunction will execute MyFunc1 and MyFunc2 in a chain.
   MyFunction(10, 20);
}

static public void MyFunc1(int x, int y)
{
    Console.WriteLine("MyFunc1");
}

static public void MyFunc2(int x, int y)
{
    Console.WriteLine("MyFunc2");
}

Conclusion

The Chain of Responsibility design pattern helps you create more scalable and reusable code in C# ASP .NET. By defining concrete handlers inidividually, you help decouple algorithms into separate and reusable classes. A request can be passed to the first handler and executed by any member of the chain, regardless of whether those handlers, or their concrete types, are known beforehand. C# .NET provides mutlicast delegates which may be considered a form of a chain of responsibility. Developers can take advantage of multicast delegates to perform event and callback functionality or design their own chain of responsiblity pattern with C# ASP .NET.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Share