Design Pattern
.NET .NET Core C# Patterns Visual Studio

Using Design Patterns in .NET Core – Part 4 – Adapter

Welcome to today’s post.

In today’s post I will be discussing the Adapter design pattern. The Adapter design pattern is a structural pattern. An adapter structural pattern takes an existing model or DTO class and transforms or remaps its structure to a different structure with the data copied across into the destination structure.

Before I show how to apply the Adapter design pattern within a .NET Core application, I will explain what the Adapter design pattern is in the first section,

Defining the Adapter Design Pattern

The Adapter design pattern consists of three classes:

  1. A client
  2. An adapter
  3. An adaptee

A client is a container class that declares an instance of an adapter class. The client then uses an instance of the adapter class to map into an instance of an adaptee class.

The adapter and adaptee classes both have incompatible interfaces, where neither can be inherited. What the pattern does is to allow the adapter class to re-use the functionality of the adaptee class by including it as a member. This preserves the functionality of the adapter and adaptee classes.

A diagram reflecting this pattern is shown below:

What this does is to allow the client to make calls to the functionality of the adaptee through the adapter class. A typical call would be:

adapter.adaptee.methodB()

Another use of the Adapter Pattern is mentioned in the next section.

Uses of the Adapter Pattern in Web User Interfaces

Another purpose of the Adapter design pattern can be to take a more complex, detailed structure and transform or shape it into a structure that can be used more purposefully in an application artifact such as a user interface or encapsulate parameter collections in method invocations.

In the MVC (Model, View, Controller) web architectural pattern, the use of the Adapter design pattern is commonly used to transform structures of models into models that are compatible with models that are used within user interfaces or to provide model that are used for controller method input and response parameter collections.

In Web API applications, the use of the Adapter design pattern is commonly used to transform structures returned from or when calling an API method in a HTTP GET or POST request.

Some common libraries or packages that has been used to implement the Adapter design pattern are AutoMapper, Dapper or StructureMap. Their purpose is to provide for more cleaner code and prevent usage of repetitive code when mapping structures and data.  In one of my other posts I discussed how to use AutoMapper within a .NET Core Web API application.

In the next section, I will show how the Adapter pattern is used to map between incompatible class structures.

Uses of the Adapter Pattern to Map DTO Class Structures

We can of course use the alternative method of defining custom mapping routines to take a source structure and map it across to a destination structure.

Here is an example of where we take a source view model, BookViewModel structured as shown:

public class BookViewModel
{
    public int ID { get; set; }
    public string Title { get; set; }
    public string Author { get; set; }
    public int YearPublished { get; set; }
    public string Genre { get; set; }
    public string Edition { get; set; }
    public string ISBN { get; set; }
    public string Location { get; set; }
    public DateTime DateCreated { get; set; }
    public DateTime DateUpdated { get; set; }
}

And map it to this structure, LoanReportDto:

public class LoanReportDto
{
    public string Status { get; set; }
    public string Title { get; set; }
    public DateTime DateLoaned { get; set; }
    public DateTime DateDue { get; set; }
    public DateTime DateReturn { get; set; }
    public string Borrower { get; set; }
}

In the following method we take an instance of the source structure BookStatusViewModel, then return a re-mapped structure LoanReportDto using a custom mapping function:

private LoanReportDto BookToBookLoanStatus(BookViewModel input)
{
    BookLoan.Models.BookStatusViewModel bookStatus = 
        _loanService.GetBookLoanStatus(input.ID).GetAwaiter().GetResult();
         
    return new LoanReportDto()
    {
        Title = input.Title,
        DateLoaned = bookStatus.DateLoaned,
        DateReturn = bookStatus.DateReturn,
        DateDue = bookStatus.DateDue,
        Status = bookStatus.Status,
        Borrower = bookStatus.Borrower
    };
}

If we had to repeat this in our application, then it would violate the DRY principle. The above method could be moved into a shared utility class and reused where it could be used application wide.

We could also use AutoMapper to cleanly map between the above structures.

Setting up AutoMapper in .NET Core requires setting up the dependency injection in ConfigureServices() using the extension method:

services.AddAutoMapper(typeof(Startup));

Then configure the mapping by using a mapping bootstrap as shown:

CreateMap<BookStatusViewModel, LoanReportDto>()
    .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status))
    .ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
    .ForMember(dest => dest.Borrower, opt => opt.MapFrom(src => src.Borrower))
    .ForMember(dest => dest.DateLoaned, opt => opt.MapFrom(src => src.DateLoaned))
    .ForMember(dest => dest.DateReturn, opt => opt.MapFrom(src => src.DateReturn))
    .ForMember(dest => dest.DateDue, opt => opt.MapFrom(src => src.DateDue));

Referencing the AutoMapper mapping requires injecting the IMapper interface into a class as follows:

private readonly IMapper _mapper;

public ReportService(…
            IMapper mapper)
{
    …
    _mapper = mapper;
}

The mapping would then look much cleaner as shown:

private LoanReportDto BookToBookLoanStatus(BookViewModel input)
{
    BookLoan.Models.BookStatusViewModel bookStatus = 
    _loanService.GetBookLoanStatus(input.ID).GetAwaiter().GetResult();
    bookStatus.Title = input.Title;

    var loanReport =_mapper.Map<LoanReportDto>(bookStatus);
      
    return loanReport;
}

Our container class, ReportService (the client) applies the adapter to convert a source class, BookStatusViewModel (the adaptee) to the target class, which is an instance of the LoanReportDto DTO class (the target). Our adapter is the class that will apply a mapping between the types. Our adapter can be a mapping class such as AutoMapper or a custom transformation class. In the example below we implement a custom adapter to convert a BookStatusViewModel class into a LoanReportDto class:

using BookLoan.Models;

namespace BookLoan.Loan.API.Adapter
{
    public class BookLoanStatusToLoanReportDtoAdapter: IBookLoanStatusToLoanReportDtoAdapter
    {
        public LoanReportDto BookLoanStatusToLoanReportDto(BookStatusViewModel input)
        {
            return new LoanReportDto()
            {
                Status = input.Status,
                Title = input.Title,
                Borrower = input.Borrower,
                DateLoaned = input.DateLoaned,
                DateReturn = input.DateReturn,
                DateDue = input.DateDue
            };
        }
    }
}

The key observation to note is that our mapping has taken one interface or model and mapped it across to another interface or model. This is unlike a Facade design pattern, which takes multiple interfaces or models and maps them to one interface.

The Adapter design pattern satisfies the SOLID principles of dependency injection, contributes towards the DRY principle where multiple uses of the same mapping are required, and makes our code cleaner. Where the mapping is not overly complicated, a mapping package such as AutoMapper can simplify our code further.

That’s all for today’s post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial