1 chap04
Jason Zhu edited this page 2021-08-18 13:27:29 +10:00

Chapter 4. The MVC Pattern

4.1 The History of MVC

4.2 Understanding the MVC Pattern

4.2.1 Understanding the Domain Model

4.2.2 The ASP.NET Implementation of MVC

4.2.3 Comparing MVC to Other Patterns

4.2.4 Understanding the Smart UI Pattern

4.2.4.1 Understanding the Model-View Architecture

4.2.4.2 Understanding classic Three-Tier Architectures

4.2.4.3 Understanding Variations on MVC

4.2.4.3.1 Understanding the Model-View-Presenter Pattern
4.2.4.3.2 Understanding the Model-View-View-Model Pattern

4.3 Applying Domain-Driven Development (DDD)

Domain model = heart of MVC app. Everything else (e.g. Controller, Views) are means to interact with domain model

3 key features of ASP.NET MVC for domain model:

  • Model binding: convention-based feature that populate model objects automatically using incoming data (e.g. registered router get data from HTTP requests to populate model)
  • Model metadata: developer describe meaning of the model classes to ASP.NET (e.g. [HttpPost] above controller)
  • Validation: performed during model binding and applies rules that defined as metadata

4.3.1 Modeling an Example Domain

Given a drafted model for an auction app (after brainstorming) as shown below:

The first draft model for an auction app

where:

  • This domain model contains Members
  • Each Member hold a set of Bids
  • Each Bid is for an Item
  • Each Item can have multiple Bids from different Member

4.3.2 Ubiquitous Language

Benefit of DDD:

  • Easy to show and adopt language and terminology for business domain
  • DDD prefer business domain terminology instead of developer jargon (e.g. Prefer agents over users, clearances over roles)

4.3.3 Aggregates and Simplification

DDD terminology of Aggregate:

  • Aggregate entity: group several domain model objects together.
  • Root entity: an aggregate entity used to identify the entire aggregate, also act as access point for validation and persistence operation (db access)

DDD rule of Aggregate:

  • A non-root entity object only hold persistent reference to its root entity, not to others (i.e. identity of a non-root ojbect needs to be unique only within its aggregate)

e.g. Aggregate objects in auction domain model

The auction domain model with aggregate

where:

  • Members and Items are both aggregate root
  • Bids can be accessed only in context of its own root Item

Benefit of DDD Aggregate:

  • simplify set of relationships btw objects in domain model

e.g. C# Auction Domain Model

public class Member {
    public string LoginName { get; set; } // Unique key
    public int ReputationPoints { get; set;}
}

public class Item {
    public int ItemID { get; private set;} // The unique key
    public string Title { get; set; }
    public string Description { get; set; }
    public DateTime AuctionEndDate { get; set; }
}

public class Bid {
    public Member Member { get; set; }
    public DateTime DataPlaced { get; set; }
    public decimal BidAmount { get; set; }
}

where:

  • Relationship btw Bids and Members: unidirectional (i.e. one bid can have multiple member; Each member may be also have multiple bids, but will complicate the relationship)

Benefit of DDD aggregate:

  • Add structure and accuracy to a domain model.
  • Make it easier to apply validation, are obvious units for persistence.

4.3.4 Defining Repositories

Repositories = object (instanciated by repository class) representations of underlying database (or file)

  • Instead of working with db directly, domain model calls method defined by repository class. The repository method then make calls to db to store and retrieve model data.
  • Repositories only concerns loading and saving data. No domain logic involved.
  • Chapter7 show how to use Entity Framework to implement repositories.
public class MembersRepository {
    // Repository classes for the Member domain classes
    public voic addMember(Member member) { /* Implementation */ }
    public Member FetchByLoginName(string loginName) { /* Implementation */ }
    public void SubmitChanges() { /* Implement me */ }
}

public class ItemsRepository {
    public void AddItem(Item item) { /* Implement me */ }
    public Item FetchByID(int itemID) { /* Implement me */ }
    public IList<Item> ListItems(int pageSize, int pageIndex) { /* Implement me */ }
    public void SubmitChanges() { /* Implement me */ }
}

4.4 Building Loosely Coupled Components

Loose Coupling = each app component knows nothing about each other and deal with other areas of app only through abstract interfaces. It has benefits:

  • Made modifying app easier (components can be swapped)
  • Made testing easier (swap real component with test mock)

e.g. Using interfaces to decouple components Using interfaces to decouple components

where:

  • Component MyEmailSender send email, which implement an interface IEmailSender (including public functions for sending emails)
  • Another component PasswordResetHelper want to send email. It calls interface instead of implementation
  • We can replace MyEmailSender with other email senders dynamically, or use mock for test

Summary: Interface can be used to decouple components

4.4.1 Using Dependency Injection

C# has interface for loose coupling, but does not provide built-in way to create objects of the class that implement interface. In C#, you can only create an instance of the concrete component

public class PasswordResetHelper {
    public void ResetPassword() {
        IEmailSender mySender = new MyEmailSender(); 
            // C# still need concreate object to implement the interface, hence it's not completely decoupled
        mySender.SendEmail();
    }
}

Problem of this implementation: PasswordResetHelper still depends on IEmailSender (interface) and MyEmailSender (the implementation of interface), it DID NOT ACHIEVE LOOSE COUPLING (i.e. should only rely on abstract interfaces)

Dependency Injection (DI) (aka inversion of controll (IoC)) provide way to obtain objects that implement an interface w/o create the object directly. Also enable dynamic swap of object

DI Pattern has two parts:

  1. Remove any dependencies of the component on any concrete classes from component (e.g. PasswordResetHelper)
  2. Pass implementations of required interfaces to class contructor

Hence, dependencies (concrete object) are injected into PasswordResetHelper object at runtime.

2 Types of DI:

  • Constructor Injection: Inject dependencies into constructor
  • Setting Injection: Inject dependencies through a public property
public class PasswordResetHelper {
    private IEmailSender emailSender;

    public PasswordResetHelper(IEmailSender emailSenderParam) {
        // Constructor Injection
        emailSender = emailSenderParam;
    }

    public void ResetPassword() {
        emailSender.SendEmail();
    }
}

4.4.2 An MVC-Specific Dependency Injection Example

Apply DI on Auction program. Object of this problem: create a controller class AdminController to use repository MembersRepository for persistence w/o directly coupling these two classes. ()

Steps:

  • Step 1: Define an interface IMemberRepository that will decouple MembersRepository and AdminController (which use instantiated MembersRepository for persistence access), and create an implementation of this interface (i.e. MembersRepository)
public interface IMembersRepository {
    // Interface for adding/fetching member for auction
    void AddMember(Member member);
    Member FetchByLoginName(string loginName);
    void SubmitChanges();
}

public class MembersRepository : IMembersRepository {
    public void AddMember(Member member) { /* Implementation */ }
    public Member FetchByLoginName
}
  • Step 2: Create controller class depending on interface
using System.Web.Mvc;

public class AdminController : Controller {
    IMembersRepository membersRepository;
    
    public AdminController(IMemberRepository repositoryParam) {
        // Constructor Injection
        membersRepository = repositoryParam;
    }

    public ActionResult ChangeLoginName(string oldLoginParam, string newLoginParam) {
        Member member = membersRepository.FetchByLoginName(oldLoginParam);
        member.LoginName = newLoginParam;
        membersRepository.SubmitChanges();
        // ... now render some view
    }
}

4.4.3 Using a Dependnecy Injection Container

What we learned on how to achieve Loose Coupling:

  • Inject dependencies (via interface) into contructors of class at runtime

Q: How to instantiate concrete implementation of interfaces w/o creating dependencies somewhere else (ripple) in app?

A: DI Container (i.e. IoC container) (by What is IoC Container or DI Container)

A DI Container is a framework to create dependencies and inject them automatically when required. It automatically create objects based on the request and injects them when required. DI Container help us to manage dependencies within app in a simple and easy way.

e.g. we can register IEmailSender interface with container and specify that an instance of MyEmailSender should be created when an implementation of IEmailSender is required.

Benefit of DI container, like Ninject:

  • Dependency chain resolution: if require a component that hasing its own dependencies, container will manage these dependencies.
  • Object life-cycle management: If a component are required more than once, DI container can let developer to configure its life cycle. e.g. singleton (same instance each time), transient (new instance each time), instance-per-thread, instance-per-HTTP-request, instance-from-a-pool etc.
  • Configuration of contrsuctor parameter values: if the interface implementation require parameter, DI container can set value based on configuration

4.5 Getting Started with Automated Testing

In broad term, there are 2 kinds of autmated testing in ASP.NET MVC Framework:

  • unit testing: specify and verify behavior of individual classes in isolation from teh rest of app
  • integration testing: specify and verify behavior of multiple components working together, including entire web app

4.5.1 Understanding Unit Testing

  • In .NET development, a separate test project in VS solution to hold test fixture
  • A test fixture is a C# class that defines a set of test methods, each method test verify single behavior
  • A test project has mutliple test fixture classes.

e.g. a test method followig Arrange/Act/Assert (AAA) pattern

[TestClass]
public class AdminControllerTest {

    private class FakeMembersReporsitory : IMemberRepository {
        public List<Member> Members = new List<Member>();
        public bool DidSubmitChanges = false;

        public voic AddMembers(Member member) {
            throw new NotImplementedException();
        }

        public Member FetchByLoginName(string LoginName) {
            return Members.First(m => m.LoginName == loginName);
        }
        
        public void SubmitChanges() {
            DidSubmitChanges = true
        }
    }

    [TestMethod]
    public void CanChangeLoginName() {
        // Arrange (set up a scenario)
        Member bob = new Member() { LoginName = "Bob"; }
        FakeMembersRepository repositoryParam = new FakeMembersRepository();
        repositoryParam.Members.Add(bob);
        AdminController target = new AdminController(repositoryParam);
        string oldLoginParam = bob.LoginName;
        string newLoginParam = "Anastasia";

        // Act (attempt the operation)
        target.ChangeLoginName(oldLoginParam, newLoginParam);

        // Assert (verify the result)
        Assert.AreEqual(newLoginParam, bob.LoginName);
        Assert.IsTrue(repositoryParam.DidSubmitChanges);

    }
}

4.5.1.1 Using TDD and Red-Green-Refactor Workflow

4.5.2 Understanding Integration Testing