Click here to Skip to main content
15,884,629 members
Articles / Web Development / ASP.NET

Developing MVC applications using SOLID principles

Rate me:
Please Sign up or sign in to vote.
4.96/5 (46 votes)
28 Sep 2014CPOL11 min read 101.5K   77   21
We should consider the design principles while developing MVC applications.

Topics

Introduction

We can follow two different approaches for building web applications in ASP.NET

  • WebForms
  • MVC  

Webforms 

Webforms is the traditional way to develop web applications. It allows us to develop web applications using the same event handling model as the traditional desktop applications.This provides certain advantages such as:

Rapid application development    WebForms allows us to develop web applications very quickly. It provides server controls and other utilities using which we can develop web applications in a relatively short time.

Abstraction over HTTP    Webforms provides an abstraction over the HTTP protocol which makes our experience  of developing web applications just like windows application development.

Image 1

Though above are the obvious advantages but there are few problems in this traditional approach

  • Difficult to maintain and change because of tight coupling between different application components.
  • Unit testing is difficult because of dependency among different components.
  • Doesn’t allow us to work directly with the HTTP protocol because of abstractions such as server controls and view state.

MVC

MVC provides a solution to the above problems

Separation of Concerns  In MVC application consists of separate components with different responsibilities. Because of this separation of concerns we can very easily change one component without effecting the rest of the application as there is a loose coupling between the components.

Unit Testing  Because the requests directly invokes the action methods in our controllers ,so we can easily test the functionality in the controllers without invoking the request pipeline. This is unlike ASP.NET Web Forms where we have to run the request pipeline to test a single web form.

But as we start to develop applications specially large enterprise applications we may use anti patterns which may negate the benefits of the MVC architecture.Below are some of the common examples.

If we are using some functionality in the controller and want to reuse that functionality across the controllers then one approach that we are tempted to take is to copy the code across the different controllers. If we reuse functionality across controllers and models by copying code then we duplicate code. This is an example of an antipattern known as the Cut-and-Paste Programming.

If we require some values from the database and those values are not provided by the existing model class then we may directly put the data access code in the controller. Below is a method in a controller that access the database using the EF and updates the model object.

C#
public ActionResult UpdateUser(User user)

      {
          //A DBContext concrete object is created
          UserContext db = new UserContext();

          //it is updated in the database using the EF
          db.Users.Add(user);

          db.SaveChanges();
          return RedirectToAction("Index");

      }

The problems in the above controller are

  • There is a strong dependency on the data access technology ,Entity Framework.
  • Controller is accessing the database directly which  is the responsibility of the model object.

So as we now understand that using the MVC framework does require use of sound design principles to create applications that are flexible and easy to maintain.That is why we use the SOLID principles.

S.O.L.I.D   Prinicples for good object oriented design

SOLID is a set of principles which helps in good object oriented design and avoid problems like the ones mentioned above. The principles of SOLID are:

The Single Responsibility Principle(SRP)

The first principle ,the S in the Single Responsibility principle , states that each module in application should change only for a single reason.

If we look at it logically we can understand it very easily.We have different modules in an application so if we put more than one responsibility in a single module it is not easy to maintain and can easily break the rest of the application. As there are multiple responsibilities in a single module so a problem in one would affect  the others.

To understand the need for this pattern we can take the example of the above action method.

The above method is performing two responsibilities

  • Returning the view
  • Accessing the database.

Let’s say that if tomorrow we make some change to the data access technology and use ADO.NET to access the database. Now if there is some problem in our code and error occurs then it will propagate to our views which will inform users about the problem in our application. This is definitely not something which we would like to happen.

The solution is the single responsibility pattern which separates the responsibilities into different modules.

C#
public ActionResult UpdateUser(User user)
       {
           UserRepository repository = new UserRepository();
           repository.Save(user);
           return RedirectToAction("Index");
       }

As you can see in the above code we have separated the data access responsibility into a repository class.So now the only responsibility our action method has is to return the view (which is what it is supposed to do).

A common scenario in most of the applications is to validate users and only after validation allows them to access the application.A common approach is to have the validation logic in the Login action method itself.But this could lead to maintenance nightmares if are using the validation logic in other places also in our application.For example if the user is redirected to our application from some other site and we are validating the user in a different controller.

C#
       public ActionResult Login(User user)
        {
            UserRepository repository = new UserRepository();
            if(repository.IsValidUser(user))
            return RedirectToAction("Home");
            else
            return RedirectToAction("Index")     
        }

A more elegant way to handle such situations is to implement the IAuthenticationFilter interface provided by MVC.So if the validation logic changes we now have to change only one class ,the class implementing the IAuthenticationFilter interface instead of finding and changing the validation logic in the entire application.

So after this change our method consists of just a single statement for redirection.

C#
[CustomAuthenticationAttribute ]
public ActionResult Login(User user)
{
    return RedirectToAction("Home");
}

 

Open/Closed Principle (OCP)

This principle states that software entities (classes, modules, functions, etc.)  should be open for extension but closed for modification. The "closed" part of the rule states that once a module has been developed and tested, the code should only be changed to correct bugs. The "open" part says that you should be able to extend existing code in order to introduce new functionality.

The reason for not promoting code change in existing classes is that it may break the existing code and also there will be need for again testing the code if we change the class. If we have some client code using the class then that client will also need to be tested.

So if we have a requirement to add some functionality to an existing class then we can do it using inheritance.Below is an example of a model class that contains the business rules to calculate the price of the books.

C#
enum Category
   {
       student,
       corporate
   }

   class Book
   {
       public double CalculatePrice(double price,Category category)
       {
           if (category == Category.corporate)
           {
               price = price- (price * 10);
           }
           else if (category == Category.student)
           {
               price = price - (price * 20);
           }

           return price;
       }

   }

The  above Model class Book contains the logic to calculate the price of the book depending on the category the buyer belongs to and offers discounts based on it.Now if there is new category added for the discount ,the only way we can implement it is by changing the Book class.

The open / closed principle says that the class should be closed for modification but open for extension.So let's see how we can implement the discount functionality using this principle.

In the below code we have created an abstract class with  CalculatePrice method.Two classes StudentBook and CorporateBook extends this abstract class.One obvious advantage is if we have to add a new discount type then instead of modifying an existing class,which is not a very good design, we can create a new class which extends the class Book.

C#
abstract class Book
  {
      public abstract double CalculatePrice(double price);
  }

  class StudentBook : Book
  {
      public override double CalculatePrice(double price)
      {
          return price - (price * 20);
      }
  }

  class CorporateBook : Book
  {
      public override double CalculatePrice(double price)
      {
          return price - (price * 10);
      }
  }

Now we can easily add discount rules whenever required without effecting the existing code.

Liskov Substitution Principle

This principle says that  Derived types must be substitutable for their base types.

According to this principle we can replace the base class objects with the derived class objects in the client code and the application should work as expected.

Image 2

This is easy to understand if we consider that subclass and base class have “is a” relationship. Subclass is also an instance of a base class. So subclass object should also work in place of base class.

We can understand this with an example.

We have Contractor and Employee classes where Contractor  derives from employee base class. 

C#
class Employee
   {
       int _sal, _empId;
       public int CalculateSalary()
       {
           //calculate salary
           return _sal;
       }

       public int GetEmployeeId()
       {
           //fetch values from database
           return _empId;
       }
   }

   class Contractor : Employee
   {
       int _contractDuration;
       public int ContractDuration()
       {
           return _contractDuration;
       }

       public int GetEmployeeId()
       {
           throw new NotImplementedException();
       }

   }

At first it may look to be a reasonable class hierarchy. But  there is one problem in this. Though contractors work in the organization but they don’t have employee id. So if our client code calls the GetEmployeeId() method he may be surprised as that method is not implemented by the client. 

So here this parent child class relationship is not a correct relationship and both should be separate classes.

Interface Segregation Principle (ISP)

This principle states that Clients should not be forced to depend upon interfaces  that they don’t use. This means the number of members in the interface that is visible to the dependent class should be minimized.

In other words we can say that "the interface that is exposed to the client should contain only the methods required by the client not all".

According to this principle instead of having a single interface with all the methods we should create multiple interfaces with small number of methods.This way we are not forcing the client to implement the unnecessary methods.

In the following example we have an interface IOrganization that contains three methods for the different departments of the organization.If any class wants to implement that interface it will have to implement all the three methods.It's like we are saying "If any class wants to implement our interface it either implement all the functions ,required or not,or no need to implement our interface".

Image 3

Wouldn't it be nice if we can break the interface functionality into three separate interfaces?

In the following example we have a model class that implements the interface IEmployee.The interface Employee contains the following methods 

  • Manage
  • Salary
  • Department

We also have two classes that implements the interface IEmployee

  • Manager
  • Executive
C#
  interface IEmployee
    {
        public void Manage();
        public void Salary();
        public void Department();
    }


   class Manager : IEmployee
    {

        public void Manage()
        {
            //Manage employees
        }

        public void Salary()
        {
            //return salary
        }

        public void Department()
        {
           //return department
        }
    }

    class Executive : IEmployee
    {

        public void Manage()
        {
           //no employees to manage. No implementation ?
        }

        public void Salary()
        {
            //return salary
        }

        public void Department()
        {
            //return department
        }
    }

The problem with the above design is that IEmployee contains methods that may not belong to each employee. One such method is the Manage() method which is implemented only by manager employees. But the above design forces all the employees to implement the manage method irrespective of whether they manage employees or not. So this design violates the interface segregation principle.<o:p>

So after redesigning we have the interface and class structure as below<o:p>

C#
interface IManager
    {
        public void Manage();
    }

    interface IEmployee
    {
        public void Salary();
        public void Department();
    }

 class Manager : IEmployee, IManager

    {

        public void Manage()
        {
            //Manage employees
        }

        public void Salary()
        {
            //return salary
        }

        public void Department()
        {
           //return department
        }
    }

    class Executive : IEmployee
    {
        public void Salary()
        {
            //return salary
        }

        public void Department()
        {
            //return department
        }
    }

So our new design does not force every employee to implement the manage method.But at the same time Manager employees can implement the specific interface IManager.

Dependency Inversion Principle(DIP)

Dependency Inversion principle is defined as  "High level modules should not depend upon low level modules. Both should depend upon abstractions".

Image 4

Let's understand this with a very basic example.There are classes Manager and OrganizationThe organization class depends on the manager class as it references the concrete instance of the Manager class.

C#
class Manager
   {
       public string Name { get; set; }
       public string Designation { get; set; }
       public string Salary { get; set; }
   }

   public class Organization
   {
       List<Manager> lst;

       public void Add(Manager mgr)
       {
           lst.Add(mgr);
       }
   }

As you can see the Organization class has a Add() method.This method allows us to add the Manager in the List of managers in the organization. There is a problem in the code above.

Organization class is tightly coupled to the Manager class.So changing the manger class would also force us to change the Organization class.  This violates the DI principle.

Here is another version of the same class but now we have removed the concrete class dependency from the organization class.We have created an interface IEmployee which the organization class refers to.So even if the Manager class changes our Organization will be unaffected  as now the dependency has been removed.

C#
interface IEmployee
    {
        public string Name{get;set;}
        public string Salary { get; set; }
        public string Department { get; set; }
    }

 class Manager :IEmployee
    {
        public string Name { get; set; }
        public string Designation { get; set; }
        public string Salary { get; set; }
    }

  public class Organization
    {
       List<IEmployee> lst;
       
        public void Add(IEmployee emp)
        {
            lst.Add(emp);
        }
    }

 

Using the initial example of our controller ,in which we are using repository to access the database,as of now the controller depends on a concrete instance of type UserRepository.This does not agree with the dependency inversion principle and our controller will require change whenever the UserRepository changes.

In the below example we have created an Interface IRepository and passed an instance of type IRepository in the constructor.This removes the dependency from our controller and our controller will not require any changes even if the UserRepository changes.We can even replace the UserRepository with any other object as long as it implements the UserRepository  interface.

This is an example of Dependency injection which is used to implement dependency inversion principle. 

C#
IRepository _repository;
public void ApplController(IRepository repository)
 {
     _repository = repository;
 }
 public ActionResult UpdateUser(User user)
 {
     _repository.Save(user);
     return RedirectToAction("Index");
  }

  interface IRepository
 {
     void Save(User user);
 }

 

Designing MVC applications  

We can create good application design if we consider some of the best practices.These are some of the things to consider while developing MVC applications.

  • Use fine grained interfaces with only the common members.
  • Use abstract classes,wherever possible, to encapsulate the common functionality.
  • Design lightweight classes with only single responsibility and whenever new responsibility is identified create a new class.
  • Minimize dependency on external entities,use dependency injection ,constructor injection is good choice in most scenarios. If we create a decoupled architecture there is less risk that changing one entity or class will break the entire application.
  • There are many IOC containers containers available to implement dependency injection in our applications. Common examples are Unity,Castle Windsor,StructureMap.

 

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I have a keen interest in technology and software development.I have worked in C#,ASP.NET WebForms,MVC,HTML5 and SQL Server.I try to keep myself updated and learn and implement new technologies.
Please vote if you like my article ,also I would appreciate your suggestions.For more please visit Code Compiled

Comments and Discussions

 
QuestionComment Pin
pawan sharma6-Aug-21 5:35
pawan sharma6-Aug-21 5:35 
PraiseNice explanation Pin
Member 1204185022-Aug-18 6:53
Member 1204185022-Aug-18 6:53 
GeneralMy vote of 5 Pin
csharpbd30-Jan-17 21:48
professionalcsharpbd30-Jan-17 21:48 
QuestionI don't understand the meaning of this paragraph Pin
beefaroni3-Nov-16 5:41
beefaroni3-Nov-16 5:41 
QuestionClearest solid article Pin
TheKiw8-Oct-16 0:32
TheKiw8-Oct-16 0:32 
Hi. This is the best article i've read until now.

I have a question about DI injected on the constructor. How is in this case the repository object passed to the different controllers?

Thanks!
PraiseAwesome one Pin
Xin Gao6-Sep-16 15:27
Xin Gao6-Sep-16 15:27 
QuestionAmazing Pin
Member 1259859522-Jun-16 7:13
Member 1259859522-Jun-16 7:13 
AnswerRe: Amazing Pin
ashish__shukla23-Jun-16 2:14
ashish__shukla23-Jun-16 2:14 
QuestionQuestion ? Pin
cyberjp17-Nov-15 2:27
cyberjp17-Nov-15 2:27 
AnswerRe: Question ? Pin
ashish__shukla25-Dec-15 8:33
ashish__shukla25-Dec-15 8:33 
GeneralNice Explanation Pin
Alireza_136227-Sep-15 6:27
Alireza_136227-Sep-15 6:27 
GeneralMy vote of 5 Pin
ravithejag5-May-15 1:33
ravithejag5-May-15 1:33 
QuestionOpen/Closed Principle (OCP) Pin
Lechuss10-Dec-14 11:58
Lechuss10-Dec-14 11:58 
QuestionNice Intro Pin
Lechuss9-Dec-14 11:14
Lechuss9-Dec-14 11:14 
Question5 Pin
237416-Oct-14 12:39
237416-Oct-14 12:39 
AnswerRe: 5 Pin
ashish__shukla6-Oct-14 19:48
ashish__shukla6-Oct-14 19:48 
QuestionNice one Pin
gyanendranayak28-Sep-14 23:21
gyanendranayak28-Sep-14 23:21 
AnswerRe: Nice one Pin
ashish__shukla29-Sep-14 16:04
ashish__shukla29-Sep-14 16:04 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun28-Sep-14 19:22
Humayun Kabir Mamun28-Sep-14 19:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.