"
ASP.NET (snapshot 2017) Microsoft documentation and samples

Iteration #4 – Make the application loosely coupled (VB)

by Microsoft

Download Code

In this third iteration, we take advantage of several software design patterns to make it easier to maintain and modify the Contact Manager application. For example, we refactor our application to use the Repository pattern and the Dependency Injection pattern.

Building a Contact Management ASP.NET MVC Application (VB)

In this series of tutorials, we build an entire Contact Management application from start to finish. The Contact Manager application enables you to store contact information - names, phone numbers and email addresses - for a list of people.

We build the application over multiple iterations. With each iteration, we gradually improve the application. The goal of this multiple iteration approach is to enable you to understand the reason for each change.

This Iteration

In this fourth iteration of the Contact Manager application, we refactor the application to make the application more loosely coupled. When an application is loosely coupled, you can modify the code in one part of the application without needing to modify the code in other parts of the application. Loosely coupled applications are more resilient to change.

Currently, all of the data access and validation logic used by the Contact Manager application is contained in the controller classes. This is a bad idea. Whenever you need to modify one part of your application, you risk introducing bugs into another part of your application. For example, if you modify your validation logic, you risk introducing new bugs into your data access or controller logic.

[!NOTE]

(SRP), a class should never have more than one reason to change. Mixing controller, validation, and database logic is a massive violation of the Single Responsibility Principle.

There are several reasons that you might need to modify your application. You might need to add a new feature to your application, you might need to fix a bug in your application, or you might need to modify how a feature of your application is implemented. Applications are rarely static. They tend to grow and mutate over time.

Imagine, for example, that you decide to change how you implement your data access layer. Right now, the Contact Manager application uses the Microsoft Entity Framework to access the database. However, you might decide to migrate to a new or alternative data access technology such as ADO.NET Data Services or NHibernate. However, because the data access code is not isolated from the validation and controller code, there is no way to modify the data access code in your application without modifying other code that is not directly related to data access.

When an application is loosely coupled, on the other hand, you can make changes to one part of an application without touching other parts of an application. For example, you can switch data access technologies without modifying your validation or controller logic.

In this iteration, we take advantage of several software design patterns that enable us to refactor our Contact Manager application into a more loosely coupled application. When we are done, the Contact Manager won t do anything that it didn t do before. However, we’ll be able to change the application more easily in the future.

[!NOTE]

Refactoring is the process of rewriting an application in such a way that it does not lose any existing functionality.

Using the Repository Software Design Pattern

Our first change is to take advantage of a software design pattern called the Repository pattern. We’ll use the Repository pattern to isolate our data access code from the rest of our application.

Implementing the Repository pattern requires us to complete the following two steps:

  1. Create an interface
  2. Create a concrete class that implements the interface

First, we need to create an interface that describes all of the data access methods that we need to perform. The IContactManagerRepository interface is contained in Listing 1. This interface describes five methods: CreateContact(), DeleteContact(), EditContact(), GetContact, and ListContacts().

Listing 1 - Models.vb

[!code-vbMain]

   1:  Public Interface IContactManagerRepository
   2:  Function CreateContact(ByVal contactToCreate As Contact) As Contact
   3:  Sub DeleteContact(ByVal contactToDelete As Contact)
   4:  Function EditContact(ByVal contactToUpdate As Contact) As Contact
   5:  Function GetContact(ByVal id As Integer) As Contact
   6:  Function ListContacts() As IEnumerable(Of Contact)
   7:  End Interface

Next, we need to create a concrete class that implements the IContactManagerRepository interface. Because we are using the Microsoft Entity Framework to access the database, we’ll create a new class named EntityContactManagerRepository. This class is contained in Listing 2.

Listing 2 - Models.vb

[!code-vbMain]

   1:  Public Class EntityContactManagerRepository
   2:  Implements IContactManagerRepository
   3:   
   4:  Private _entities As New ContactManagerDBEntities()
   5:   
   6:  Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerRepository.GetContact
   7:      Return (From c In _entities.ContactSet _
   8:              Where c.Id = id _
   9:              Select c).FirstOrDefault()
  10:  End Function
  11:   
  12:   
  13:  Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerRepository.ListContacts
  14:      Return _entities.ContactSet.ToList()
  15:  End Function
  16:   
  17:   
  18:  Public Function CreateContact(ByVal contactToCreate As Contact) As Contact Implements IContactManagerRepository.CreateContact
  19:      _entities.AddToContactSet(contactToCreate)
  20:      _entities.SaveChanges()
  21:      Return contactToCreate
  22:  End Function
  23:   
  24:   
  25:  Public Function EditContact(ByVal contactToEdit As Contact) As Contact Implements IContactManagerRepository.EditContact
  26:      Dim originalContact = GetContact(contactToEdit.Id)
  27:      _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit)
  28:      _entities.SaveChanges()
  29:      Return contactToEdit
  30:  End Function
  31:   
  32:   
  33:  Public Sub DeleteContact(ByVal contactToDelete As Contact) Implements IContactManagerRepository.DeleteContact
  34:      Dim originalContact = GetContact(contactToDelete.Id)
  35:      _entities.DeleteObject(originalContact)
  36:      _entities.SaveChanges()
  37:  End Sub
  38:   
  39:  End Class

Notice that the EntityContactManagerRepository class implements the IContactManagerRepository interface. The class implements all five of the methods described by that interface.

You might wonder why we need to bother with an interface. Why do we need to create both an interface and a class that implements it?

With one exception, the remainder of our application will interact with the interface and not the concrete class. Instead of calling the methods exposed by the EntityContactManagerRepository class, we’ll call the methods exposed by the IContactManagerRepository interface.

That way, we can implement the interface with a new class without needing to modify the remainder of our application. For example, at some future date, we might want to implement an DataServicesContactManagerRepository class that implements the IContactManagerRepository interface. The DataServicesContactManagerRepository class might use ADO.NET Data Services to access a database instead of the Microsoft Entity Framework.

If our application code is programmed against the IContactManagerRepository interface instead of the concrete EntityContactManagerRepository class then we can switch concrete classes without modifying any of the rest of our code. For example, we can switch from the EntityContactManagerRepository class to the DataServicesContactManagerRepository class without modifying our data access or validation logic.

Programming against interfaces (abstractions) instead of concrete classes makes our application more resilient to change.

[!NOTE]

You can quickly create an interface from a concrete class within Visual Studio by selecting the menu option Refactor, Extract Interface. For example, you can create the EntityContactManagerRepository class first and then use Extract Interface to generate the IContactManagerRepository interface automatically.

Using the Dependency Injection Software Design Pattern

Now that we have migrated our data access code to a separate Repository class, we need to modify our Contact controller to use this class. We will take advantage of a software design pattern called Dependency Injection to use the Repository class in our controller.

The modified Contact controller is contained in Listing 3.

Listing 3 - Controllers.vb

[!code-vbMain]

   1:  Public Class ContactController
   2:      Inherits System.Web.Mvc.Controller
   3:   
   4:      Private _repository As IContactManagerRepository 
   5:   
   6:      Sub New()
   7:          Me.New(new EntityContactManagerRepository())
   8:      End Sub
   9:   
  10:      Sub New(repository As IContactManagerRepository)
  11:          _repository = repository
  12:      End Sub
  13:   
  14:      Protected Sub ValidateContact(contactToValidate As Contact)
  15:          If contactToValidate.FirstName.Trim().Length = 0 Then
  16:              ModelState.AddModelError("FirstName", "First name is required.")
  17:          End If
  18:          If contactToValidate.LastName.Trim().Length = 0 Then
  19:              ModelState.AddModelError("LastName", "Last name is required.")
  20:          End If
  21:          If (contactToValidate.Phone.Length > 0 AndAlso Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
  22:              ModelState.AddModelError("Phone", "Invalid phone number.")
  23:          End If        
  24:          If (contactToValidate.Email.Length > 0 AndAlso  Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
  25:              ModelState.AddModelError("Email", "Invalid email address.")
  26:          End If
  27:      End Sub
  28:   
  29:      Function Index() As ActionResult
  30:          Return View(_repository.ListContacts())
  31:      End Function
  32:   
  33:      Function Create() As ActionResult
  34:          Return View()
  35:      End Function
  36:   
  37:      <AcceptVerbs(HttpVerbs.Post)> _
  38:      Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
  39:          ' Validation logic
  40:          ValidateContact(contactToCreate)
  41:          If Not ModelState.IsValid Then
  42:              Return View()
  43:          End If
  44:   
  45:          ' Database logic
  46:          Try
  47:              _repository.CreateContact(contactToCreate)
  48:              Return RedirectToAction("Index")
  49:          Catch
  50:              Return View()
  51:          End Try
  52:      End Function
  53:   
  54:      Function Edit(ByVal id As Integer) As ActionResult
  55:          Return View(_repository.GetContact(id))
  56:      End Function
  57:   
  58:      <AcceptVerbs(HttpVerbs.Post)> _
  59:      Function Edit(ByVal contactToEdit As Contact) As ActionResult
  60:          ' Validation logic
  61:          ValidateContact(contactToEdit)
  62:          If Not ModelState.IsValid Then
  63:              Return View()
  64:          End If
  65:   
  66:          ' Database logic
  67:          Try
  68:              _repository.EditContact(contactToEdit)
  69:              Return RedirectToAction("Index")
  70:          Catch
  71:              Return View()
  72:          End Try
  73:      End Function
  74:   
  75:      Function Delete(ByVal id As Integer) As ActionResult
  76:          Return View(_repository.GetContact(id))
  77:      End Function
  78:   
  79:      <AcceptVerbs(HttpVerbs.Post)> _
  80:      Function Delete(ByVal contactToDelete As Contact) As ActionResult
  81:          Try
  82:              _repository.DeleteContact(contactToDelete)
  83:              Return RedirectToAction("Index")
  84:          Catch
  85:              Return View()
  86:          End Try
  87:      End Function
  88:   
  89:  End Class

Notice that the Contact controller in Listing 3 has two constructors. The first constructor passes a concrete instance of the IContactManagerRepository interface to the second constructor. The Contact controller class uses Constructor Dependency Injection.

The one and only place that the EntityContactManagerRepository class is used is in the first constructor. The remainder of the class uses the IContactManagerRepository interface instead of the concrete EntityContactManagerRepository class.

This makes it easy to switch implementations of the IContactManagerRepository class in the future. If you want to use the DataServicesContactRepository class instead of the EntityContactManagerRepository class, just modify the first constructor.

Constructor Dependency injection also makes the Contact controller class very testable. In your unit tests, you can instantiate the Contact controller by passing a mock implementation of the IContactManagerRepository class. This feature of Dependency Injection will be very important to us in the next iteration when we build unit tests for the Contact Manager application.

[!NOTE]

If you want to completely decouple the Contact controller class from a particular implementation of the IContactManagerRepository interface then you can take advantage of a framework that supports Dependency Injection such as StructureMap or the Microsoft Entity Framework (MEF). By taking advantage of a Dependency Injection framework, you never need to refer to a concrete class in your code.

Creating a Service Layer

You might have noticed that our validation logic is still mixed up with our controller logic in the modified controller class in Listing 3. For the same reason that it is a good idea to isolate our data access logic, it is a good idea to isolate our validation logic.

To fix this problem, we can create a separate service layer. The service layer is a separate layer that we can insert between our controller and repository classes. The service layer contains our business logic including all of our validation logic.

The ContactManagerService is contained in Listing 4. It contains the validation logic from the Contact controller class.

Listing 4 - Models.vb

[!code-vbMain]

   1:  Public Class ContactManagerService
   2:  Implements IContactManagerService
   3:   
   4:  Private _validationDictionary As IValidationDictionary
   5:  Private _repository As IContactManagerRepository
   6:   
   7:   
   8:  Public Sub New(ByVal validationDictionary As IValidationDictionary)
   9:      Me.New(validationDictionary, New EntityContactManagerRepository())
  10:  End Sub
  11:   
  12:   
  13:  Public Sub New(ByVal validationDictionary As IValidationDictionary, ByVal repository As IContactManagerRepository)
  14:      _validationDictionary = validationDictionary
  15:      _repository = repository
  16:  End Sub
  17:   
  18:   
  19:  Public Function ValidateContact(ByVal contactToValidate As Contact) As Boolean
  20:      If contactToValidate.FirstName.Trim().Length = 0 Then
  21:          _validationDictionary.AddError("FirstName", "First name is required.")
  22:      End If
  23:      If contactToValidate.LastName.Trim().Length = 0 Then
  24:          _validationDictionary.AddError("LastName", "Last name is required.")
  25:      End If
  26:      If contactToValidate.Phone.Length > 0 AndAlso (Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}")) Then
  27:          _validationDictionary.AddError("Phone", "Invalid phone number.")
  28:      End If
  29:      If contactToValidate.Email.Length > 0 AndAlso (Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$")) Then
  30:          _validationDictionary.AddError("Email", "Invalid email address.")
  31:      End If
  32:      Return _validationDictionary.IsValid
  33:  End Function
  34:   
  35:   
  36:  #Region "IContactManagerService Members"
  37:   
  38:  Public Function CreateContact(ByVal contactToCreate As Contact) As Boolean Implements IContactManagerService.CreateContact
  39:      ' Validation logic
  40:      If Not ValidateContact(contactToCreate) Then
  41:          Return False
  42:      End If
  43:   
  44:      ' Database logic
  45:      Try
  46:          _repository.CreateContact(contactToCreate)
  47:      Catch
  48:          Return False
  49:      End Try
  50:      Return True
  51:  End Function
  52:   
  53:  Public Function EditContact(ByVal contactToEdit As Contact) As Boolean Implements IContactManagerService.EditContact
  54:      ' Validation logic
  55:      If Not ValidateContact(contactToEdit) Then
  56:          Return False
  57:      End If
  58:   
  59:      ' Database logic
  60:      Try
  61:          _repository.EditContact(contactToEdit)
  62:      Catch
  63:          Return False
  64:      End Try
  65:      Return True
  66:  End Function
  67:   
  68:  Public Function DeleteContact(ByVal contactToDelete As Contact) As Boolean Implements IContactManagerService.DeleteContact
  69:      Try
  70:          _repository.DeleteContact(contactToDelete)
  71:      Catch
  72:          Return False
  73:      End Try
  74:      Return True
  75:  End Function
  76:   
  77:  Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerService.GetContact
  78:      Return _repository.GetContact(id)
  79:  End Function
  80:   
  81:  Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerService.ListContacts
  82:      Return _repository.ListContacts()
  83:  End Function
  84:   
  85:  #End Region
  86:  End Class

Notice that the constructor for the ContactManagerService requires a ValidationDictionary. The service layer communicates with the controller layer through this ValidationDictionary. We discuss the ValidationDictionary in detail in the following section when we discuss the Decorator pattern.

Notice, furthermore, that the ContactManagerService implements the IContactManagerService interface. You should always strive to program against interfaces instead of concrete classes. Other classes in the Contact Manager application do not interact with the ContactManagerService class directly. Instead, with one exception, the remainder of the Contact Manager application is programmed against the IContactManagerService interface.

The IContactManagerService interface is contained in Listing 5.

Listing 5 - Models.vb

[!code-vbMain]

   1:  Public Interface IContactManagerService
   2:  Function CreateContact(ByVal contactToCreate As Contact) As Boolean
   3:  Function DeleteContact(ByVal contactToDelete As Contact) As Boolean
   4:  Function EditContact(ByVal contactToEdit As Contact) As Boolean
   5:  Function GetContact(ByVal id As Integer) As Contact
   6:  Function ListContacts() As IEnumerable(Of Contact)
   7:  End Interface

The modified Contact controller class is contained in Listing 6. Notice that the Contact controller no longer interacts with the ContactManager repository. Instead, the Contact controller interacts with the ContactManager service. Each layer is isolated as much as possible from other layers.

Listing 6 - Controllers.vb

[!code-vbMain]

   1:  Public Class ContactController
   2:      Inherits System.Web.Mvc.Controller
   3:   
   4:      Private _service As IContactManagerService 
   5:   
   6:      Sub New()
   7:          _service = new ContactManagerService(New ModelStateWrapper(ModelState))
   8:      End Sub
   9:   
  10:      Sub New(service As IContactManagerService)
  11:          _service = service
  12:      End Sub
  13:   
  14:      Function Index() As ActionResult
  15:          Return View(_service.ListContacts())
  16:      End Function
  17:   
  18:      Function Create() As ActionResult
  19:          Return View()
  20:      End Function
  21:   
  22:      <AcceptVerbs(HttpVerbs.Post)> _
  23:      Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
  24:          If _service.CreateContact(contactToCreate) Then
  25:              Return RedirectToAction("Index")        
  26:          End If
  27:          Return View()
  28:      End Function
  29:   
  30:      Function Edit(ByVal id As Integer) As ActionResult
  31:          Return View(_service.GetContact(id))
  32:      End Function
  33:   
  34:      <AcceptVerbs(HttpVerbs.Post)> _
  35:      Function Edit(ByVal contactToEdit As Contact) As ActionResult
  36:          If _service.EditContact(contactToEdit) Then
  37:              Return RedirectToAction("Index")        
  38:          End If
  39:          Return View()
  40:      End Function
  41:   
  42:      Function Delete(ByVal id As Integer) As ActionResult
  43:          Return View(_service.GetContact(id))
  44:      End Function
  45:   
  46:      <AcceptVerbs(HttpVerbs.Post)> _
  47:      Function Delete(ByVal contactToDelete As Contact) As ActionResult
  48:          If _service.DeleteContact(contactToDelete) Then
  49:              return RedirectToAction("Index")
  50:          End If
  51:          Return View()
  52:      End Function
  53:   
  54:  End Class

Our application no longer runs afoul of the Single Responsibility Principle (SRP). The Contact controller in Listing 6 has been stripped of every responsibility other than controlling the flow of application execution. All the validation logic has been removed from the Contact controller and pushed into the service layer. All of the database logic has been pushed into the repository layer.

Using the Decorator Pattern

We want to be able to completely decouple our service layer from our controller layer. In principle, we should be able to compile our service layer in a separate assembly from our controller layer without needing to add a reference to our MVC application.

However, our service layer needs to be able to pass validation error messages back to the controller layer. How can we enable the service layer to communicate validation error messages without coupling the controller and service layer? We can take advantage of a software design pattern named the Decorator pattern.

A controller uses a ModelStateDictionary named ModelState to represent validation errors. Therefore, you might be tempted to pass ModelState from the controller layer to the service layer. However, using ModelState in the service layer would make your service layer dependent on a feature of the ASP.NET MVC framework. This would be bad because, someday, you might want to use the service layer with a WPF application instead of an ASP.NET MVC application. In that case, you wouldn t want to reference the ASP.NET MVC framework to use the ModelStateDictionary class.

The Decorator pattern enables you to wrap an existing class in a new class in order to implement an interface. Our Contact Manager project includes the ModelStateWrapper class contained in Listing 7. The ModelStateWrapper class implements the interface in Listing 8.

Listing 7 - Models.vb

[!code-vbMain]

   1:  Public Class ModelStateWrapper
   2:  Implements IValidationDictionary
   3:   
   4:  Private _modelState As ModelStateDictionary
   5:   
   6:  Public Sub New(ByVal modelState As ModelStateDictionary)
   7:      _modelState = modelState
   8:  End Sub
   9:   
  10:  Public Sub AddError(ByVal key As String, ByVal errorMessage As String) Implements IValidationDictionary.AddError
  11:      _modelState.AddModelError(key, errorMessage)
  12:  End Sub
  13:   
  14:  Public ReadOnly Property IsValid() As Boolean Implements IValidationDictionary.IsValid
  15:      Get
  16:          Return _modelState.IsValid
  17:      End Get
  18:  End Property
  19:   
  20:  End Class

Listing 8 - Models.vb

[!code-vbMain]

   1:  Public Interface IValidationDictionary
   2:   
   3:  Sub AddError(ByVal key As String, ByVal errorMessage As String)
   4:  ReadOnly Property IsValid() As Boolean
   5:   
   6:  End Interface

If you take a close look at Listing 5 then you’ll see that the ContactManager service layer uses the IValidationDictionary interface exclusively. The ContactManager service is not dependent on the ModelStateDictionary class. When the Contact controller creates the ContactManager service, the controller wraps its ModelState like this:

[!code-vbMain]

   1:  _service = new ContactManagerService(New ModelStateWrapper(ModelState))

Summary

In this iteration, we did not add any new functionality to the Contact Manager application. The goal of this iteration was to refactor the Contact Manager application so that is easier to maintain and modify.

First, we implemented the Repository software design pattern. We migrated all of the data access code to a separate ContactManager repository class.

We also isolated our validation logic from our controller logic. We created a separate service layer that contains all of our validation code. The controller layer interacts with the service layer, and the service layer interacts with the repository layer.

When we created the service layer, we took advantage of the Decorator pattern to isolate ModelState from our service layer. In our service layer, we programmed against the IValidationDictionary interface instead of ModelState.

Finally, we took advantage of a software design pattern named the Dependency Injection pattern. This pattern enables us to program against interfaces (abstractions) instead of concrete classes. Implementing the Dependency Injection design pattern also makes our code more testable. In the next iteration, we add unit tests to our project.

Previous Next



Comments ( )
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnet/mvc/overview/older-versions-1/contact-manager/iteration-4-make-the-application-loosely-coupled-vb.htm
< THANKS ME>