Table of Contents
- 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.3 Applying Domain-Driven Development (DDD)
- 4.3.1 Modeling an Example Domain
- 4.3.2 Ubiquitous Language
- 4.3.3 Aggregates and Simplification
- 4.3.4 Defining Repositories
- 4.4 Building Loosely Coupled Components
- 4.4.1 Using Dependency Injection
- 4.4.2 An MVC-Specific Dependency Injection Example
- 4.4.3 Using a Dependnecy Injection Container
- 4.5 Getting Started with Automated Testing
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:
where:
- This domain model contains
Member
s - Each
Member
hold a set ofBid
s - Each
Bid
is for anItem
- Each
Item
can have multipleBid
s from differentMember
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
overusers
,clearances
overroles
)
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
where:
Members
andItems
are both aggregate rootBids
can be accessed only in context of its own rootItem
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
andMembers
: 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
where:
- Component
MyEmailSender
send email, which implement an interfaceIEmailSender
(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:
- Remove any dependencies of the component on any concrete classes from component (e.g.
PasswordResetHelper
) - 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 decoupleMembersRepository
andAdminController
(which use instantiatedMembersRepository
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);
}
}