GitHub Repository: TechNetWiki ProxyDesignPattern

Introduction

A common requirement in applications is altering how information is presented depending on context. For example, information might be restricted to different roles. The fundamental behavior might be the same but the result should not contain information not relevant to the current user's role. Another example might be when additional information might need to be calculated based on context. For example, the location of an order might need to be determined if the status is in delivery but in all other statuses, it is not required.

There are many ways of implementing this and this article presents the use of a Proxy Design Pattern to handle this. With this pattern, a class is used to contain all the business logic of constructing another class. Take an example where a collection of data is being used to generate a model. This is often used in a web application were the underlying data entities are mapped to models used by the page to be shown to a user. If we use a three tier solution, the business logic of transforming the data into a suitable model for the user could be done at the data layer, business layer or the presentation layer. Ideally, this should be done at the business layer for several reasons. For maintenance reasons, it is best to contain business logic to a single layer as when spread across multiple layers. The data layer is best suited to responsibilities related to the handling of the underlying storage of entities. The presentation layer is best suited to the challenges of presenting the information to the user. The business layer should be primarily about containing the business logic of an application. This is ideal and most of us have worked on large enterprise solutions where business logic has been spread across multiple layers leading to a maintenance nightmare where one relatively minor change in business requirements might require multiple layers requiring logic. By containing business logic to a single layer, we simplify the maintenance of an application.

Another important consideration is security. If a model being returned to the presentation layer contains all information and it is the presentation layers responsibility to remove sensitive information, in a web application this would be a security concern. This is because most browsers support the ability to view the messages and content passed between a server and client so information could be viewed regardless of what is presented to the user in the UI.

Proxy Design Class Diagram

The following class diagram shows the basic concept:


The concept is an interface is used to define a proxy design class whose responsibility is to return a ViewModel given data. In the diagram, two realizations are shown and these could be based on roles, context or some other criteria.

Regristration Example

To illustrate, let's take an example of a collection of item registrations. These items have information about the item including a price as well as information about the seller. Imagine the ViewModel is being constructed for a grid or table showing all the registrations to the user. Depending on the user's role it may or may not have seller  information. The following diagram describes this:


IRegistrationViewModelProxy

The IRegistrationViewModelProxy contains a single method GetRegistrationViewModel which receives a Seller, BankDetails, Address and Item and returns a RegistrationViewModel. The interface is shown below:

1.interface IRegistrationViewModelProxy
2.{
3.    RegistrationViewModel GetRegistrationViewModel(Seller seller,
4.                                     BankDetails bankDetails,
5.                                     Address addres,
6.                                     Item item);
7.}

The UserRegistrationViewModelProxy only provides the information the user has access to which is the item name, price and the estimated lead time supplied by the seller:
01.public class UserRegistrationViewModelProxy : IRegistrationViewModelProxy
02.{
03.    public RegistrationViewModel GetRegistrationViewModel(Seller seller,
04.                                         BankDetails bankDetails,
05.                                         Address addres,
06.                                         Item item)
07.    {
08.        return new RegistrationViewModel
09.        {
10.            Item_Id = item.Id,
11.            Item_Name = item.Name,
12.            Item_Price = item.Price,
13.            Seller_EstimatedDeliveryLeadTime = seller.EstimatedDeliveryLeadTime               
14.        };
15.    }
16.}

The AdminRegistrationViewModelProxy contains additional information that the user does not have access to:
01.public class AdminRegistrationViewModelProxy : IRegistrationViewModelProxy
02.{
03.    public RegistrationViewModel GetRegistrationViewModel(Seller seller,
04.                                         BankDetails bankDetails,
05.                                         Address addres, 
06.                                         Item item)
07.    {
08.        return new RegistrationViewModel
09.        {
10.            Item_Id = item.Id,
11.            Item_Name = item.Name,
12.            Item_Price = item.Price,
13.            Item_AvailableFrom = item.AvailableFrom,
14.            Item_AvailableTo = item.AvailableTo,
15. 
16.            Seller_Id = seller.Id,
17.            Seller_Address = addres,
18.            Seller_BankDetails = bankDetails,
19.            Seller_Name = seller.Name,
20.            Seller_EstimatedDeliveryLeadTime = seller.EstimatedDeliveryLeadTime,               
21.        };
22.    }
23.}

The fundamental reason for this class is to abstract away the rules around building the ViewModel away from the logic responsible for retrieving the data. Let's look at this now.

RegistrationRepository

In this illustration I am using a Linq query to retrieve from in-memory collections. These collections are generic lists but could represent the result from a repository like SQL Server or CosmosDB. The concept is the same, we contain the logic about retrieving the data in the class responsible for interacting with backend storage. In this example, that is the RegistrationRepository class.

Please see the repo for more detail including how the lists are created but the important sections are shown below. First the constructor will receive our Design Proxy which will be stored as a member variable:
1.public RegistrationRepository(IRegistrationViewModelProxy registrationViewModelProxy)
2.{
3.    _registrationViewModelProxy = registrationViewModelProxy;
4.}

The GetRegistrationViewModels will return a collection of RegistrationViewModel objects using the Design Proxy and filtering by a page number and page size.
01.public IEnumerable<RegistrationViewModel> GetRegistrationViewModels(int pageNumer, int pageSize)
02.{
03.    var query = from seller in _sellers
04.                join address in _addresses on seller.Id equals address.SellerId
05.                join bank in _bankDetails on seller.Id equals bank.SellerId
06.                join item in _items on seller.Id equals item.SellerId                       
07.                select _registrationViewModelProxy
08.                     .GetRegistrationViewModel(seller, bank, address, item);
09. 
10.    return query.Skip(pageNumer * pageSize).Take(pageSize);
11.}

The reason for the page number and page size was just to illustrate that complexity around the retrieval should be contained in this class while the building of the result should be in the Design Proxy.

Dependency Injection

Dependency Injection will be used to determine the appropriate Design Proxy to be used, or what implementation of IRegistrationViewModelProxy to use. In this example, we are determining this based on the current user's role. In the startup of the program, we add a transient service which will determine each time the service is retrieved the appropriate implementation. In a web application, it would make more sense to use a scope created for the duration of the web request (i.e., AddScoped<>).
1.var serviceProvider = new ServiceCollection()
2.               .AddTransient<IRegistrationViewModelProxy>(GetRegistrationViewModelProtectionProxy)    
3.               .AddTransient<RegistrationRepository>()                                                        
4.               .BuildServiceProvider();

The AddTransient on line 2 supplies a method, GetRegistrationViewModelProtectionProxy, that returns the implementation of IRegistrationProxy to use:
01.private static IRegistrationViewModelProxy GetRegistrationViewModelProtectionProxy(IServiceProvider arg)
02.{
03.    // in Microsoft.AspNetCore.Http you could determine the user by checking their claims
04.    // var httpContextAccessor = arg.GetService<IHttpContextAccessor>();           
05. 
06.    // this does assume someone is set as currentuser
07.    switch(Security.CurrentUser.Role)
08.    {
09.        case Role.Admin:
10.            return new AdminRegistrationViewModelProxy();
11.        default:
12.            return new UserRegistrationViewModelProxy();
13.    }           

On line 7, the current user's role is used to determine if the AdminRegistrationViewModelProxy is returned, otherwise the UserRegistrationViewModelProxy is returned.

Illustration of Proxy Design

To complete the picture, the following method puts this into action:
1.private static void RunIllustrationOfProxyDesign(IServiceProvider serviceProvider, ILogger logger)
2.{
3.    var repository = serviceProvider.GetService<RegistrationRepository>();
4.    var result = repository.GetRegistrationViewModels(1, 1);
5. 
6.    logger.LogInformation(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));

On line 3 repository service is determined by the GetService<RegistrationRepository>() call. Using dependency injection, the appropriate proxy will be supplied to the repository. Then on line 4 the models are retrieved and in this example, we are requesting the 1st page with only 1 result per page. On line 6, the result is serialized and printed to the screen. 

When run this produces the following when called as a User role:


And when called as an Admin role:

 

Conclusion

The Proxy Design is a pattern that can help teams write cleaner and more maintainable code by abstracting the business logic used to construct models. This is not applicable in all cases. For example, in the illustration it might not make sense to use a shared repository class but instead use a separate repository classes depending on the current user's role instead. The concept though is that we do not want to leave this responsibility up to the presentation layer nor the data layer as it bleeds concerns and information across too many layers. This both compromises security (data protection) as well as leads to less maintainable code.