Understanding Model-View-Controller

Architecture

You should understand by now that ASP.NET MVC applications are built with MVC architecture.

But what exactly does that mean, and what is the point of it anyway? In high-level terms, it means that your application will be split into (at least) three distinct(截然不同的) pieces:

• A model, which represents the items, operations, and rules that are meaningful in the subject matter (domain) of your application. In banking, such items might include bank accounts and credit limits, operations might include funds(资金) transfers(转移), and rules might require that accounts stay within credit limits. The model also holds the state of your application’s universe(体系) at the present(现在的) moment, but is totally(完全的) disconnected from any notion(概念) of a UI.

• A set of views, which describe how to render some portion of the model as a visible UI, but otherwise contain no logic.

• A set of controllers, which handle incoming requests, perform operations on the model, and choose a view to render back to the user.

 

There are many variations(变种) on the MVC pattern, each having its own terminology(术语) and slight(轻微的) difference of emphasis(重点), but they all have the same primary goal: separation of concerns(关注点分离).

By keeping a clear division(划分) between concerns, your application will be easier to maintain(保持) and extend(扩展) over its lifetime, no matter how large it becomes. The following discussion will not labor(劳动) over the precise(精确的) academic(学术性的) or historical definitions of each possible twist(转变) on MVC; instead, you will learn why MVC is important and how it works effectively in ASP.NET MVC. In some ways, the easiest way to understand MVC is to understand what it is not, so let’s start by considering the alternatives(取舍).

The Smart UI (Anti-Pattern)

To build a Smart UI application, a developer first constructs(构建) a UI, usually by dragging a series of UI widgets(小玩意) onto a canvas(画布),1 and then fills in event handler code for each possible button click or other UI event. All application logic resides(居住) in these event handlers: logic to accept and validate user input, to perform(执行) data access and storage, and to provide feedback by updating the UI. The whole application consists(组成) of these event handlers. Essentially, this is what tends to(趋向) come out(出现) by default when you put a novice(初学者) in front of Visual Studio.

In this design, there’s no separation of concerns whatsoever. Everything is fused(融合[fju:z]) together, arranged(安排) only in terms of the different UI events that may occur. When logic or business rules need to be applied(申请) in more than one handler, the code is usually copied and pasted, or certain(肯定) randomly(随便的) chosen segments are factored out into static utility classes. For so many obvious(明显的) reasons, this kind of design pattern is often called an anti-pattern.

Let’s not sneer(讥笑) at Smart UIs for too long. We’ve all developed applications like this, and in fact, the design has genuine(真正的) advantages that make it the best possible choice in certain cases:

• It delivers(交付) visible results extremely quickly. In just days or even hours you might have something reasonably functional to show to a client or boss.

• If a project is so small (and will always remain(保持) so small) that complexity will never be a problem, then the costs of a more sophisticated(见多识广的) architecture outweigh(超过) its benefits.

• It has the most obvious possible association(结合) between GUI elements and code subroutines(子程序). This leads to a very simple mental(精神的) model for developers—hardly(几乎不) any cognitive(认识能力) friction(冲突)—which might be the only viable(切实可行的) option for development teams with less skill or experience. In that case, attempting a more sophisticated architecture may just waste time and lead to a worse result than Smart UI.

• Copy-paste code has a natural (though perverse)(天生的) kind of decoupling(解耦) built in. During maintenance, you can change an individual behavior or fix an individual bug without fear that your changes will affect any other parts of the application.

You have probably experienced the disadvantages of this design (anti) pattern firsthand(亲手地). Such applications become exponentially(以指数方式) harder to maintain as each new feature is added: there’s no particular(特定) structure, so you can’t possibly remember what each piece of code does; changes may need to be repeated in several places to avoid inconsistencies(矛盾) and there's obviously no way to set up unit tests. Within one or two person-years, these applications tend to collapse under their own weight.

It’s perfectly OK to make a deliberate(深思熟虑的) choice to build a Smart UI application when you feel it’s the best trade-off of pros and cons for your project (in which case, use classic WebForms, not ASP.NET MVC, because WebForms has an easier event model), as long as your business recognizes the limited life span of the resulting software.

Separating Out the Domain Model

Given the limitations of Smart UI architecture, there’s a widely accepted improvement that yields huge benefits for an application’s stability and maintainability.

By identifying(识别) the real-world entities, operations, and rules that exist in the industry or subject matter you’re targeting (the domain), and by creating a representation of that domain in software (usually an object-oriented representation backed by some kind of persistent(持久的) storage system, such as a relational database), you’re creating a domain model. What are the benefits of doing this?

• First, it’s a natural place to put business rules and other domain logic, so that no matter what particular UI code performs an operation on the domain (e.g., “open a new bank account”), the same business processes occur.

• Second, it gives you an obvious way to store and retrieve(找回) the state of your application’s universe at the current point in time, without duplicating that persistence code everywhere.

• Third, you can design and structure the domain model’s classes and inheritance graph according to the same terminology and language used by experts in your domain, permitting(允许) a ubiquitous(普遍存在的) language shared by your programmers and business experts, improving communication and increasing the chance that you deliver what the customer actually wants (e.g., programmers working on an accounting package may never

actually understand what an accrual is unless their code uses the same terminology).

In a .NET application, it makes sense to keep a domain model in a separate assembly (i.e., a C# class library project—or several of them) so that you’re constantly(不断的) reminded of the distinction between domain model and application UI. You would have a reference from the UI project to the domain model project, but no reference in the opposite direction, because the domain model shouldn’t know or care about the implementation of any UI that relies on it. For example, if you send a badly formed record to the domain model, it should return a data structure of validation errors, but would not attempt to display those errors on the screen in any way (that’s the UI’s job).

Model-View Architecture

If the only separation in your application is between UI and domain model, it’s called modelview architecture (see Figure 3-1).

 

Figure 3-1.Model-view architecture for the Web

It’s far better organized and more maintainable(可维持的) than Smart UI architecture, but still has two striking weaknesses:

• The model component contains a mass of repetitious(重复的) data access code that’s specific(明确的) to the vendor(小贩) of the particular database being used. That will be mixed in among code for the business processes and rules of the true domain model, obscuring both.

• Since both model and UI are tightly coupled(紧耦合的) to their respective(各自的) database and GUI platforms, it’s very hard (if not impossible) to do automated testing on either, or to reuse any of their code with different database or GUI technologies.

Three-Tier Architecture

Responding in part to these criticisms(评论), three-tier architecture cuts persistence(持久) code out of the domain model and places that in a separate, third component, called the data access layer (DAL) (see Figure 3-2).

 

Figure 3-2. Three-tier architecture

Often—though not necessarily—the DAL is built according to the repository pattern(仓库模式), in which an object-oriented representation of a data store acts as a facade(表面) on top of a relational database. For example, you might have a class called OrdersRepository, having methods such as GetAllOrders() or DeleteOrder(int orderID). These will use the underlying database to fetch instances of model objects that match stated criteria (or delete them, update them, etc.).

If you add in the abstract factory pattern, meaning that the model isn’t coupled to any concrete implementation of a data repository, but instead accesses repositories only through .NET interfaces or abstract base classes, then the model has become totally decoupled from the database technology. That means you can easily set up automated tests for its logic, using fake(伪造) or mock repositories to simulate(模仿) different conditions. You’ll see this technique at work in the next chapter.

Three-tier is among the most widely adopted architectures for business software today, because it can provide a good separation of concerns without being too complicated, and because it places no constraints(约束) over how the UI is implemented, so it’s perfectly compatible(兼容的) with a forms-and-controls–style GUI platform such as Windows Forms or ASP.NET WebForms.

Three-tier architecture is perfectly good for describing the overall design(整体设计) of a software product, but it doesn’t address what happens inside the UI layer. That’s not very helpful when, as in many projects, the UI component tends to balloon to a vast(巨大的) size, amassing(积累) logic like a great rolling snowball. It shouldn’t happen, but it does, because it’s quicker and easier to attach behaviors directly to an event handler (a la Smart UI) than it is to refactor the domain model.

When the UI layer is directly coupled to your GUI platform (Windows Forms, WebForms), it’s almost impossible to set up any automated tests on it, so all that sneaky new code escapes any kind of rigor. Three-tier’s failure to enforce(实施) discipline(纪律) in the UI layer means, in the worst case, that you can end up(最终成为) with a Smart UI application with a feeble(虚弱的) parody(滑稽的模仿) of a domain model stuck on its side.

Model-View-Controller Architecture

Recognizing(认识到) that even after you’ve factored out a domain model, UI code can still be big and complicated, MVC architecture splits that UI component in two (see Figure 3-3).

 

Figure 3-3. MVC architecture for the Web

In this architecture, requests are routed(路由) to a controller class, which processes user input and works with the domain model to handle the request. While the domain model holds domain logic (i.e., business objects and rules), controllers hold application logic, such as navigation through a multistep process or technical details like authentication. When it’s time to produce a visible UI for the user, the controller prepares the data to be displayed (the presentation model, or ViewData in ASP.NET MVC, which for example might be a list of Product objects matching the requested category), selects a view, and leaves it to complete the job.

Since controller classes aren’t coupled to the UI technology (HTML), they are just pure(纯净的), testable application logic.

Views are simple templates for converting ViewData into a finished piece of HTML. They are allowed to contain basic, presentation-only logic, such as the ability(能力) to iterate over a list of objects to produce an HTML table row for each object, or the ability to hide or show a section of the page according to a flag in ViewData, but nothing more complicated than that. Generally, you’re not advised(建议) to try automated testing for views’ output (the only way would be to test for specific HTML patterns, which is fragile), so you must keep them as simple as possible.

Don’t worry if this seems obscure(模糊的) at the moment; soon you’ll see lots of examples. If you’re struggling(争取) to understand how a view could be distinct from a controller, as I did when I first tried to learn MVC architecture (does a TextBox go into a view or into a controller?), it may be because you’ve only used technologies that make the division very hard or impossible, such as Windows Forms or classic ASP.NET WebForms. The answer to the TextBox conundrum is that you’ll no longer think in terms of UI widgets, but in terms of requests and responses, which is more appropriate for a web application.

Implementation in ASP.NET MVC

In ASP.NET MVC, controllers are .NET classes, usually derived from the built-in Controller base class. Each public method on a Controller-derived class is called an action method, which is automatically associated(关联) with a URL on your configurable URL schema, and after performing some operations, is able to render its choice of view. The mechanisms(机构) for both input (receiving data from an HTTP request) and output (rendering a view, redirecting to a different action, etc.) are designed for testability, so during implementation and testing, you’re not coupled to any live web server.

The framework supports a choice of view engines, but by default, views are streamlined ASP.NET WebForms pages, usually implemented purely as ASPX templates (with no codebehind class files) and always free of ViewState/postback complications. ASPX templates give a familiar, Visual Studio–assisted way to define HTML markup with inline C# code for injecting(注入) and responding to ViewData as supplied by the controller.

ASP.NET MVC leaves your model implementation entirely(完整的) up to you. It provides no particular infrastructure(基础设施) for a domain model, because that’s perfectly well handled by a plain vanilla C# class library, .NET’s extensive(广阔的) facilities(设备), and your choice of database and data access code or ORM tool. Even though default, new-born ASP.NET MVC projects contain a folder called /Models, it’s cleaner to keep your domain model code in a separate Visual Studio class library project. You’ll learn more about how to implement a domain model in this chapter.

History and Benefits

The term model-view-controller has been in use since the late 1970s and the Smalltalk project at Xerox PARC. It was originally(起初) conceived(设想) as a way to organize some of the first GUI applications, although some aspects of its meaning today, especially(特别地) in the context of web applications, are a little different than in the original Smalltalk world of “screens” and “tools.” For example, the original Smalltalk design expected a view to update itself whenever the underlying data model changed, following the observer(观察者) synchronization pattern, but that’s nonsense(无意义的) when the view is already rendered as a page of HTML in somebody’s browser.

These days, the essence(精髓) of the MVC design pattern turns out(出席) to work wonderfully for web applications, because

• Interaction(相互作用) with an MVC application follows a natural cycle of user actions and view updates, with the view assumed(假定) to be stateless, which maps well to a cycle of HTTP requests and responses.

• MVC applications enforce a natural separation of concerns. Firstly, that makes code easier to read and understand; secondly, controller logic is decoupled from the mess of HTML, so the bulk(体积) of the application’s UI layer can be subject to automated tests.

ASP.NET MVC is hardly the first web platform to adopt MVC architecture. Ruby on Rails is a recent MVC poster child, but Apache Struts, Spring MVC, and many others have already proven(已被证实) its benefits.

Variations on Model-View-Controller

You’ve seen the core design of an MVC application, especially as it’s commonly used in ASP.NET MVC, but others interpret(解释) MVC differently, adding, removing, or changing components according to the scope and subject of their project.

Where’s the Data Access Code?

MVC architecture places(放置) no constraints on how the model component is implemented. You can choose to perform data access through abstract repositories if you wish (and in fact this is what you’ll see in next chapter’s example), but it’s still MVC even if you don’t.

Putting Domain Logic Directly into Controllers from looking at the earlier diagram (Figure 3-3), you might realize(意识到) that there aren’t any strict(严格的) rules to force developers to correctly split logic between controllers and the domain model.

It is certainly possible to put domain logic into a controller, even though you shouldn’t, just because it seems expedient(权宜之计) at some pressured moment. The best way to protect against the indiscipline of merging model and controllers accidentally is to require good automated test coverage, because even from the naming of such tests it will be obvious when logic has been sited inappropriately.

Most ASP.NET MVC demonstrations(示范) and sample code, to save time, abandon(丢弃) the distinction between controllers and the domain model altogether, in what you might call controller-view architecture. This is inadvisable for a real application because it loses the benefits of a domain model, as listed earlier. You’ll learn more about domain modeling in the next part of this chapter.

Model-View-Presenter

Model-view-presenter (MVP) is a recent variation on MVC that’s designed to fit more easily with stateful GUI platforms such as Windows Forms or ASP.NET WebForms. You don’t need to know about MVP when you’re using ASP.NET MVC, but it’s worth(值得) explaining what it is so you can avoid confusion(混淆).

In this twist, the presenter has the same responsibilities(责任) as MVC’s controller, plus(另外) it also takes a more hands-on relationship to the stateful view, directly editing the values displayed in its UI widgets according to user input (instead of letting the view render itself from a template).

There are two main flavors(味):

• Passive(被动的) view, in which the view contains no logic, and merely has its UI widgets manipulated(控制) by the presenter.

• Supervising(监督) controller, in which the view may be responsible(有责任的) for certain(某些) presentation logic, such as data binding, having been given a reference to some data source in the model.

The difference between the two flavors is quite subjective(主观的) and simply relates to how intelligent the view is allowed to be. Either way, the presenter is decoupled from the GUI technology, so its logic can be followed easily and is suitable for automated testing.

Some folks(人们) contend(主张) that ASP.NET WebForms’ code-behind model is like an MVP design (supervising controller), in which the ASPX markup is the view and the code-behind class is the presenter. However, in reality, ASPX pages and their code-behind classes are so tightly fused that you can’t slide a hair between them. Consider, for example, a grid’s ItemDataBound event—that’s a view concern, but here it’s handled in the code-behind class: it doesn’t do justice(合理) to MVP. There are ways to implement a genuine MVP design with WebForms by accessing the control hierarchy(层次体系) only through an interface, but it’s complicated and you’re forever fighting against the platform. Many have tried, and many have given up.

ASP.NET MVC follows the MVC pattern rather than MVP because MVC remains more popular and is arguably simpler for a web application.

posted on 2009-09-30 10:05  YinWangLive  阅读(1507)  评论(4编辑  收藏  举报