Before next chapter’s deep dive into a real ASP.NET MVC e-commerce development experience, it’s important to make sure you’re familiar with the architecture, design patterns, tools, and techniques that we’ll be using. By the end of this chapter, you’ll know about the following:
- MVC architecture
- Domain models and service classes
- Creating loosely coupled systems using a dependency injection (DI) container
- The basics of automated testing
- C# 3 language features that all ASP.NET MVC developers need to understand
§3.1 Understanding MVC Architecture
Your application will be split into (at least) three distinct pieces:
- Models
- Views
- Controller
§3.1.1 The Smart UI (Anti-Pattern)
ignore
§3.1.2 Separating Out the Domain Model
Model-View Architecture
architecture as shown:
§3.1.3 Three-Tier Architecture
architecture as shown:
§3.1.4 MVC Architecture
architecture as shown:
Implementation in ASP.NET MVC
nothing interesting
History and Benefits
ASP.NET MVC is hardly the first web platform to adopt MVC architecture. Ruby on Rails is the most famous MVC poster child, but Apache Struts, Spring MVC, and many others have already proven its benefits.
§3.1.5 Variations on MVC
Where’s the Data Access Code?
Putting Domain Logic Directly into Controllers
Model-View-Presenter
Model-View-View Model
§3.2 Domain Modeling
Take the real-world objects, processes, and rules from your software’s subject matter and encapsulate them in a component called a domain model.This component is extremly important to your software. Everything else, including controllers and views, is just a technical detail designed to support or permit interaction with the domain model.
The part of the software that specifically solves problems from the domain model usually constitutes only a small portion of the entire software system, although its importance is disproportionate to its size. To apply our best thinking, we need to be |
§3.2.1 An Example Domain Model
Let us talk an online auctions site, you might get started with something as follows:
This diagram indicates that the model contains a set of members who each hold a set of bids, and each bid is for an item. An item can have multiple bids from different members.
§3.2.2 Ubiquitous Language
A key benefit of implementing your domain model as a distinct component is the ability to design it according to the language and terminology of your choice.
§3.2.3 Aggregates and Simplification
A C# representation of our domain model so far looks like this:
public class Member { public string LoginName { get; set; }// The unique key public int ReputationPoint { 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 IList<Bid> Bids { get; private set; } } public class Bid { public Member Member { get; private set; } public DateTime DatePlaced { get; private set; } public decimal BidAmount { get; private set; } }§3.2.4 Keeping Data Access Code in Repositories
Sooner or later you’ll have to think about getting your domain objects into and out of some kind of persistent storage—usually a relational, object, or document database. Persistence is an independent concern , so you don’t want to mix persistence code with domain model code, either by embedding database access code directly into domain entity methods, or by putting loading or querying code into static methods on those same classes.
The usual way to keep this separation clean is to define repositories.When you’re working with aggregates, it’s normal to define a separate repository for each aggregate, because aggregates are the natural unit for persistence logic. For example, continuing the auctions example, you might start with the following two repositories :
public class MembersRepository { public void AddMember(Member member) { /* Implement me */ } public Member FetchByLoginName(string loginName) { return new Member(); /* Implement me */ } public void SubmitChanges() { /* Implement me */ } } public class ItemsRepository { public void AddItem(Item item) { /* Implement me */ } public Item FetchByID(int itemID) {return new Item(); /* Implement me */ } public IList<Item> ListItems(int pageSize, int pageIndex) { return new Item[] { new Item() }; /* Implement me */ } public void SubmitChanges() { /* Implement me */ } }Notice that repositories are concerned only with loading and saving data, and contain as little domain logic as is possible.In this example, you’ll see how to use an ORM tool (LINQ to SQL) to make your job easier.
§3.2.5 Using LINQ to SQL
It is an ORM tool, not as mature and sophisticated as alternatives such as NHibernate, but sometimes easier to use, considering its full support for LINQ and its inclusion by default in all editions of Visual Studio 2008 and 2010.
DataContext is your entry point to the whole LINQ to SQL API. It knows how to load, save, and query for any .NET type that has LINQ to SQL mappings . After it loads an object from the database, it keeps track of any changes you make to that object’s properties, so it can write those changes back to the database when you call its SubmitChanges() method. It’s lightweight (i.e., inexpensive to construct); it can manage its own database connectivity, opening and closing connections as needed; and it doesn’t even require you to remember to close or dispose of it.
There are various different ways to use LINQ to SQL. Here are the two main ones:
- You can take a database-first approach by first creating a SQL Server database schema. Then, as I just described, use LINQ to SQL’s visual designer to have it generate corresponding C# classes and a mapping configuration.
- You can take a code-first approach by first creating a clean, object-oriented domain model with interfaces for its repositories. Then create a SQL Server
database schema to match. Finally, either provide an XML mapping configuration or use mapping attributes to tell LINQ to SQL how to convert between the two. (Alternatively, just give LINQ to SQL the mapping configuration and ask it to create the initial SQL Server database for you.)You can keep persistence concerns separate from the domain classes, and you get total control over how they are structured and how their properties are encapsulated. Plus, you can freely update either the object-oriented or relational representation and update your mapping configuration to match.
Implementing the Auctions Domain Model
With LINQ to SQL, you can set up mappings between C# classes and an implied database schema either by decorating the classes with special attributes or by writing an XML configuration file.Let us use attributes here.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Linq; using System.Data.Linq.Mapping; namespace MvcProgram.Models { [Table(Name="Members")] public class Member { [Column(IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)] internal int MemberID { get; set; } [Column] public string LoginName { get; set; } [Column] public int ReputationPoint { get; set; } } [Table(Name = "Items")] public class Item { [Column(IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)] public int ItemID { get; internal set; } [Column] public string Title { get; set; } [Column] public string Description { get; set; } [Column] public DateTime AuctionEndDate { get; set; } [Association(OtherKey = "ItemID")] private EntitySet<Bid> _bids = new EntitySet<Bid>(); public IList<Bid> Bids { get { return _bids.ToList().AsReadOnly(); } } } [Table(Name = "Bids")] public class Bid { [Column(IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)] internal int BidID { get; set; } [Column] internal int ItemID { get; set; } [Column] public DateTime DatePlaced { get; internal set; } [Column] public decimal BidAmount { get; internal set; } [Column] internal int MemberID { get; set; } internal EntityRef<Member> _member; [Association(ThisKey = "MemberID", Storage = "_member")] public Member Member { get { return _member.Entity; } internal set { _member.Entity = value; MemberID = value.MemberID; } } } }Implementing the Auction Repositories
Now that the LINQ to SQL mappings are set up, it’s dead easy to provide a full implementation of the repositories outlined earlier:
THERE ARE STILL SOME TOOLS AND TECHNIQUES I WILL TALK IN THE OTHER CHAPTER EMBEDDED…
§ Summary
In this chapter, you got up to speed with the core concepts underpinning ASP.NET MVC, and the tools and techniques needed for successful web development with .NET and C# 3 or later. In the next chapter, you’ll use this knowledge to build a real ASP.NET MVC e-commerce application, combining MVC architecture, loosely coupled components, unit testing, and a clean domain model built with an objectrelational mapping (ORM) tool.
LET'S GO TO THE NEXT CHAPTER NOW~!