Dynamic Service Result Builder (Exception Pattern, Result Pattern, Decorator Pattern)

Tarek Najem
13 min readOct 11, 2020

What type of result should the service method return to the caller

Introduction

What type will we return when calling Methods of Services?

What properties will it contain?

Will the expected result of this method be sufficient?

These questions are always in our minds when we start designing our special services for our program. In this topic we will try to talk about some scenarios about how the Methods of Services communicate with the ASP.NET Core 3.1 Controller.

At first you will think that this topic is special, but after you finish reading it, you will find that you can apply it in any scenario in which you need to return a meaningful result to the Caller.

We used to return one result for service methods, which is the possible result of that function (like Boolean, Integer, Models, etc.).

But before we start, we expect that you have sufficient experience or familiarity with ASP.NET Core and some basic object-oriented programming (such as Inheritance, Polymorphism, Interfaces, Generics … etc.) and you will find that we have written the code in C# 8.0. You can read more about C# 8.0 here.

Notice:

You will find that in some classes we have removed the unnecessary codes for this topic, but at the bottom of the topic you will find a link to download all the programs that we have created.

Here is an illustrative example of a service:

public class xxxService : Service, IxxxService
{
/// <summary>
/// Return model
/// </summary>
public IModel GetByID(Guid id)
{
return new xxxModel() { … };
}
/// <summary>
/// Return Integer
/// </summary>
public int GetLastIndex()
{
return 0;
}
/// <summary>
/// Return Boolean
/// </summary>
public bool Exists(IModel model)
{
return true;
}
}

But in some Methods of Services, we find ourselves that we want to tell the Caller some additional information about why he got such a result, for example if we want to tell him about the error that happened and why this error occurred, the type of returned data will not be sufficient to add the other explanatory information.

public class xxxService : Service, IxxxService
{
/// <summary>
/// Return model
/// </summary>
public IModel GetByID(Guid id)
{
try{
return new xxxModel() { … };
}catch (Exception){
// Is this result sufficient?
return null;
}
}
/// <summary>
/// Return Integer
/// </summary>
public int GetLastIndex()
{
try{
return 1;
}catch (Exception){
// Is this result sufficient?
return -1;
}
}

/// <summary>
/// Return Boolean
/// </summary>
public bool? Exists(IModel model)
{
try{
return true;
}catch (Exception){
// Is this result sufficient?
return null;
}
}
}

We will use the most famous examples in this topic which is the method used by programs to log in.

Service Base Class:

Login Service:

The Controller Class:

But sometimes the username and password are correct, but the cause of the error is the lack of a connection with the database or the presence of any other unexpected error, so we can say here that the returned result is not sufficient to explain the reasons for the failure.

How can we tell the Caller this, so that he, in turn, explains the error to the user?

The answer to this question is the focus of our topic for today.

We will include some scenarios that will help us return enough information to clarify Methods of Services and try to explain the programming procedures in a simple way.

We will not go into this post about other classes design methods, that such a program needs. And about the methods used in its design such as (Repository Pattern, Unit of Work Patterns, Services in Domain-Driven, Design ... etc.), but we will only talk about the result data types that the Methods of Services will return to the Caller.

Scenario 1 - Throw Exceptions Or Exception Pattern

We will first start with one of the scenarios that do not require a large cost of time to apply to the services that we previously created, as it only needs Throw Exceptions so that the Caller can build more accurate results, to inform the user about the reasons for the failure of this process.

Create Custom Exceptions Classes:

First, we will create a new class inherited from Exception class and this hierarchy of exceptions is called User-Defined Exceptions or Custom Exceptions and it will in turn be the Base Class for the rest of the exceptions that we will create for our project.

Why do we need to create custom exceptions?

There are no specific answers that benefit this purpose, some see it as an overburden and the other finds it useful, from my experience, it depends on how much you have to do once you discover the exception, for example if you only want to throw this exception then there is no need to create them, but if you want handling the exception and sending it back to the Caller, with new exception more accurate, the Caller can recognize and deal with it more accurately, so here you have to create them.

· If we do not find any of the pre-existing exception classes, we can specify the exception that we expect to occur.

· To do some complex work depending on what the exception is and how it will be thrown.

· Hierarchy of exceptions. To help make it easier to catch errors, we can catch a set of exceptions by capturing the Base class:

try
{
// ...
}
// Here we can catch all exceptions inherited from ServiceException
catch(ServiceException ex)
{
// ...
}

· You do not have a clash.

· Provide meaningful messages.

And now we can throw the exceptions for our project that we created previously so that the Caller can collect some additional information about the workflow of this function and what exceptions it encountered.

Base classes for all our Exceptions:

At first we will notice that the HttpStatusCodeException is sufficient to proceed in the program, but if we look closely, we will notice that we need to pass the HttpStatusCode every time we need to create an instance from this class, and we must every time test this property while catch this exception. To make this easier, we will create some special classes from this base class to have more precise classes and to maintain consistency of the program so that there is no inconsistency in understanding the exceptions catching plan.

All other exceptions classes:

When starting to implement this service, we will follow the usual method to return the expected value, but taking into account what we have talked about earlier to implement this pattern. For example, when the required user is not found, we must throw an exception instead of returning the value of null, and so on whenever we want to send additional information to the Caller, we will throw an exception, and we will reformulate the unexpected exceptions, with new significant exceptions in our program, to allow us when the errors start to discover a more clear formulation of a new exception, and so on until we see that we have covered all the unexpected exceptions in our program.

And in our controller we must catch the exceptions that will be thrown, and gather as much information as possible to show clear results to the user:

Advantages:

Special exceptions are one of the means to understand how the program works and what exceptions are expected to occur, but we cannot list all exceptions, there are exceptions that we cannot expect so all programs are subject to a test driven design and a manual test plan simultaneously.

Program stability is one of the main advantages of the exceptions.

Disadvantages:

It requires high CPU time so it reduces the performance of the applications, but not enough to worry about it because exceptions help stabilize the programs.

Additional workloads because we need to create a new class for each exception.

Ideas for improving design:

To improve this design, you can use one of the error handling techniques in ASP.NET Core such as: Exception middleware or Exception filters.

To read about this site, you can visit these sites:

  1. Handle errors in ASP.NET Core web APIs.
  2. Handle errors in ASP.NET Core.
  3. Exception filters

Scenario 2 – Result Object Or Result Pattern

If we were to apply some design principles to our application such as Separation of Concerns or Single-Responsibility Principle (SRP) to enforce some of the more stringent rules to keep our program consistent.

We will find that the first scenario is not sufficient to apply these principles, so we must think in another direction to find a new way through which we can return more than one value to the Caller.

In this scenario we will create a new object through which we can expand the dialogue between the Methods of Services and the Caller. The first thing that will come to our minds is:

What properties will this new class contain?

Simply, there are a lot of properties that we can add. But in our topic, we will add the most important properties and leave the rest of the properties to the needs of the scenario that you implement.

The first property added, of course, is the expected result of our service method, and the second property is information about the exception, and finally, we can add an additional property to alert the user about something that has happened or about something that needs to be done. On some service methods, we want to return the result with a warning. For example, in our example, we want to remind the user that he must change the password, as the result is just not sufficient for that, in such cases we use this property if we want to tell him how many failed attempts he made to log in … etc.

The final design for this class will look like this:

We have modified the type that the Authenticate Method will return. Instead of returning LoginDto, will return ServiceResult<LoginDto> of the type we previously talked about, and the final design for this LoginService will be as follows:

Here we will display the Controller, which we modified to suit the new way to call this method, and how will he perform the testing the Kind property of the result returned from the service method to show clear results to the user:

We will notice that the Controller are clean, clear and is only responsible for handling requests.

Here we will be listing the Controller Base classes:

Finally, the extensions method for moving the result processing to another unit:

In this scenario we were able to achieve the Separation of Concerns because we had ensured that our error logic went within our implementation logic, and we were also able to achieve the Single-Responsibility Principle (SRP) because we have noticed that the controllers are responsible for handling the requests and returns an obvious responses.

These are the results of the Postman:

Response.json
Return warning
valid user
Blocked user
Not Found

Scenario 3 – All with Aspect-Oriented Programming Or Decorator Pattern

Aspect Oriented Programming (AOP): We will not talk about all the advantages of this design, but we will talk about the most prominent features. It is a very effective way to divide the work of the program into sections that are easy to lead, maintain and develop without harming other parts of the Modularity program. The main idea is to add new behavior to the existing code without making any changes in the code itself. The new code is supposed to be public so that it can be applied to any object and the object should know nothing about the behavior. AOP also allows the developer to apply Separation Of Cross-Cutting Concerns and facilitates a Single-Responsibility Principle (SRP).

Here in the program we will talk about the parts that we have modified, and as they talked at the beginning of this topic, you can download all the programs that we have developed for this article below.

First, we slightly tweaked the service to return the expected value, and we made an asynchronous version of the method for testing asynchronous programming.

Then we created a new class inherited from DispatchProxy. This type has been present in .NET Core from the start of the platform and provides a mechanism for creating Proxy Objects and processing their Dispatch Method. And a Proxy Object can be created from it by using this code:

var proxy = DispatchProxy.Create<Interface, Proxy>();

Now the important part of this program is implementing the GenericDecorator to be used in all programs. The code will come later, which is a subclass from DispatchProxy and then we create a new subclass to match the new behavior we want to add to the services. In AOP it's called the Aspect.

An aspect is the part of the app that crosscuts the basic concerns of multiple beings, therefore violating its separation of concerns that tries to encapsulate unrelated functions.

The Generic Decorator or Generic Proxy:

But there is a problem in this scenario is that it is not safe in concurrency applications (Multithreading) because we are saving the ‘Object states’ in the global variables of this object so we will not implement it because of the risks that we will face.

- The other scenario, which we will implement: Creating a new type in runtime that inherits the original object to the expected result of service methods and inserts this information into it.

The disadvantages of this scenario is that it is somewhat difficult and requires some experience in System.Reflection.Emit, or we can use the Code Generation with Roslyn. In our topic, we used this System.Reflection.Emit.

We will put the code here, but we will not explain it because it is a big topic and outside the scope of our topic, but if you want to know more about System.Reflection.Emit check this link.

We will explain how we can use the Object Builder that we have created, so that we can develop it in the future and transfer it to other projects if we want to.

Another disadvantage of this scenario is that we will not be able to get a Null result because we are always returning the new object that we created in the runtime. However, we added a new property so that we can see if the original result is Null or not.

As for the advantage that we will reap:

· There are no problems with Concurrency (Multithreading).

· The Separation Of Concerns, by transferring the responsibility of creating the object in the runtime to another object.

· Single-Responsibility Principle (SRP): The ResultPatternAspect is only responsible for processing the result and creating a new result that fits into this scenario that we are implementing.

· By applying the previous two principles, we have divided our program into small parts, or in other words, we are close to achieving modularity in our program.

The Result Pattern Aspect:

By applying the principle of Programming for Interface not implementation, you will notice that we rely a lot on Interface to make our program based on Abstraction and not on Concrete Objects. Program behavior can also be changed in Runtime, and it also helps us write programs that are much better from a maintenance point of view. To make the Object deal with the Methods it only needs, this is one of the principles of SOLID, which is the Interface Segregation (ISP).

In order to facilitate the process of creating an object from the ResultPatternAspect, we applied the Factory Pattern to facilitate the process of modifying the strategy of creating this aspect.

We'll talk about how to register objects in ASP.NET Core 3.1 Dependency Injection later.

Dynamic Object Builder:

The Builder Pattern allows us to create complex objects step by step. It also allows us to produce different types and representations of an object using the same building code.

Now let's talk a little bit about how to use SimpleDynamicObjectBuilder, first we will show its code:

To create a Dynamic Instance in the Runtime, we just need to create an Instance from this DynamicObjectBuilder and then follow these simple steps:

var dynamicObjectBuilder = new DynamicObjectBuilder("Domain_Assembly");

Here we have created some functions to create a dynamic object in the runtime:

To use the ObjectWithBaseClassAndInterfaceAndCustomProperty and ObjectWithInterface that we created earlier, we used long names to illustrate:

How to register objects in ASP.NET Core 3.1 Dependency Injection.

We created new extension to register the all objects necessary to fulfill this scenario.

Factory Injection In ASP.NET CORE

In our main program and in the Startup file, we used this extension and registered the Services using Factory Injection In ASP.NET Core 3.1 to be able to use the ResultPatternAspec to create a Service Proxy.

When we talk about Factory, we're referring to a mechanism within the program that is responsible for creating instantiating classes and returning those instances.

Just as we created ServiceResultExtensions, it contains a GetResult<T> function whose mission is to read the properties and create result instances from the object that we created in the runtime using SimpleDynamicObjectBuilder.

We will notice that the Controllers are still clean and clear, and their task is limited to handling requests only.

Conclusion

In this post, I showcased various ways to return a meaningful result for Caller

This code works well in the scenarios we have explained. If you have any examples of situations where this code does not work, or have ideas on how to improve this code, then I hope you explain this in any way.

Hope you liked the article please share your opinions in the comments section below.

You can find the source code on GitHub.

--

--