The Three Models of ASP.NET MVC Apps

12 June 2012  by 
 

We've inherited from the original MVC pattern a rather simplistic idea of what should be in the Model. In fact, in ASP.NET MVC, there are three distinct types of model: the domain model, view model and input model. As the data behind an ASP.NET MVC application becomes more complex, the more the view model and domain model may diverge.

When you use the standard project template for creating ASP.NET MVC 3 applications in Visual Studio 2010, the newly created project comes with a predefined folder named Models. The Models folder goes hand-in-hand with two other predefined folders called Views and Controllers. Models, Views, and Controllers form the triad of roles that underlie any application that conforms to the popular MVC (Model-View-Controller) design pattern.

In ASP.NET MVC, the Views and Controllers folders are not simply containers of classes; if you remove or rename Views and Controllers then your project won’t compile unless you do some (legal and documented) magic with the internal plumbing of the ASP.NET MVC runtime. Moreover, the Views folder must also be deployed as-is onto the production server. So in the end, Views and Controllers play a key role in ASP.NET MVC. What about Models?

First and foremost, the Models folder plays no decisive role in the working of an ASP.NET MVC project. You can freely remove or rename it; this won’t have any significant impact on your project. As the name itself seems to suggest, the intended role of the Models folder is acting as a central repository of model classes. Oh nice, but wait a moment! What’s a model class ultimately?

In the end, the term “model” is fairly overloaded these days and needs some qualifying attribute to be fully understood. This has to do with the evolution that the MVC pattern, and applications, underwent recently. ASP.NET MVC is just one implementation of the original MVC pattern, but some further considerations apply.

From the MVC Pattern to ASP.NET MVC

A few decades ago, when the MVC pattern was first formulated, software applications weren’t as tiered and sophisticated as they are today. The controller was responsible for just about everything, including grabbing input data from the view, orchestrating operations for producing a response and packing the response for the view. A single object model was used to manage the entire flow of data, and this object model was just called “the model.”

In the original formulation of the MVC pattern, the view and model know about each other and are connected through an observer relationship. The view writes its content to the model following users’ clicking; the controller reads input data from the model, orchestrates operations and then updates the model with computed results. Finally, the model notifies the view of occurred changes and the view reads data back and refreshes itself. The MVC pattern was formulated some 30 years. For quite some time, the relatively low level of complexity of most applications never required that the “model” got a more precise definition.

It would be ingenuous to pretend that the MVC pattern for modern ASP.NET applications is the same as it was 30 years ago. The MVC pattern was originally devised to better organize the entire application; today, instead, it is still an effective pattern but limited to the frontend of layered applications—the presentation layer.

In ASP.NET MVC, a controller is primarily the repository of presentation logic much like the code-behind class in Web and Windows Forms. The controller sets up and possibly delegates any further work required to serve the request. This role is nearly the same as in the original MVC pattern. The view grabs input data and produces the user interface. This role is also nearly the same as in the original MVC pattern. The model is in the original MVC pattern the representation of the data being worked on in the view.

This definition is way too limiting in modern layered applications such as those you may be writing with ASP.NET MVC. The concept of the “model” needs expansion.

Flavors of a Model

In general, there are three distinct types of a model. With regard to ASP.NET MVC, we can call them domain model, view model and input model. Let’s find out more.

The domain model describes the data you work with in the middle tier of the application. The domain model is expected to provide a faithful representation of the entities that populate the business domain. These entities are typically persisted by the data-access layer and consumed by services that implement business processes. The domain model is also often referred to as the entity model or simply as the data model. The domain model pushes a vision of data that is, in general, distinct from the vision of data you find in the presentation layer. (Note that the domain model “may” be the same model you work with in the presentation layer, but this should be taken as the exception rather than the rule.)

The view model describes the data being worked on in the presentation layer. Any data you present in the view (whether strings, collections, dates) finds its place in one of the properties of the view model classes. Any view model class represents the data that the controller transmits after processing operations for serving a response to the users. The view model results from the aggregation of multiple classes, one per view or page that you display.

Finally, the input model is the collection of classes that describe the input data flow that goes from the web page down to the controllers and backend. The input model faithfully represents the data being uploaded with individual HTTP requests. Usually, you manage this data as strings and read (serialized) values from collections such asQueryString and Form. Turning strings into strong types is entirely your responsibility in ASP.NET Web Forms. In ASP.NET MVC, the model-binding infrastructure does most of these chores for you and attempts to map incoming strings to matching properties of input model types. Let’s find out about the details.

Anatomy of the Input Model

In ASP.NET MVC, the input model can be envisaged as a collection of classes that model any data coming your way through an HTTP request. The input model is made of simple data transfer objects—just properties and no methods. Input model classes are used by controller methods as a way to receive posted data parameters or data being passed on the query string or HTTP headers. Here’s an example of a controller method leveraging a class in the input model:

public ActionResult Repeat(RepeatInfo model)
{
    var viewModel = new RepeatViewModel {Number = number, Text = text};
    return View(viewModel);
}

In the example, the Repeat method is invoked to repeat a given text a given number of times. Therefore, the pieces of information to transmit are two—a string and a number. All data is moved across the wire as strings packaged in the HTTP packet. Once the request is handed to the selected controller method, ASP.NET MVC does some magic to turn the content found in the body of the request into a fresh instance of the type declared as an argument of the controller. With an eye on the previous code snippet, the body of the HTTP request is revived into an instance of theRepeatInfo class.

The RepeatInfo class can be a plain data-transfer object defined as follows:

public class RepeatInfo  
{
    public String Text { get; set; }
    public Int32 Number { get; set; }
}

It is one of your tasks as a developer to decide about the name and structure of input model classes. There’s some freedom as far as naming and structure of input model classes are concerned. The structure outlined above is not the only possible one. Here’s another equally valid class:

public class AnotherRepeatInfoClass  
{
    public String Text { get; set; }
    public Long Number { get; set; }
}

As you may have guessed, RepeatInfo and AnotherRepeatInfoClass have member names in common—Text and Number. The type of these members may not be as important as their names but it is anyway required that the types are compatible as Int32 and Long. How does mapping between values in the HTTP request packet and properties declared on the input model class actually work? The model binder is the ASP.NET MVC component responsible for that.

Under the Hood of Model Binding

Any controller method executes under the control of the action invoker component. The developer decides about the signature of the controller method. For each declared parameter, the invoker obtains a model binder object. The binder’s job consists in trying to find a possible value for that parameter out of the HTTP request context. Each parameter can be bound to a specific model binder class; however, most of the time a default model binder class is used. The default model binder corresponds to the DefaultModelBinder class and it is the binder used for all parameters unless otherwise specified.

Each model binder uses its own algorithm to try to find a value for the parameters of a controller method. The default binder object makes an intensive of reflection. In particular, for each parameter the binder attempts to find a match between the parameter’s name and one of the values uploaded with the request. So if the parameter is named, say,Text then it looks in the collections of the request context (i.e., Form, QueryString) to find an entry with the same name. If a match is found then the binder attempts to convert the uploaded value (always a string) to the declared type of the parameter. If the conversion is successful, the computed value is then used in the call to the controller method; otherwise, an exception is thrown. Note that an exception is thrown on the first failure. This means that the controller method call proceeds successfully only if all of the declared parameters can be resolved by the binder. Be aware that an exception generated by the binder cannot be caught up in the controller method’s code. You need to set up a global error handler in global.asax in order to catch and handle those exceptions in some way. Note also that an exception is thrown only if the declared method parameter can’t be assigned null. For example, if you have an Int32 parameter failing in resolving the value will throw an exception because null is not a valid value for an Int32 variable. By simply changing Int32 to Nullable<Int32> in the controller method signature you fix the code.

The default binder looks into a variety of collections around the HTTP request context. The first place where it looks into is the Form collection. In this case, names of the input field are processed in a case-insensitive manner. If you have, say, an input text field named TextBox1 then a method parameter with the same name will be assigned. The content of the uploaded string will be converted to the type of the parameter if the conversion works. If a match can’t be found in Form, then the binder looks into the route parameters and then in the QueryString collection.

When you have several parameters to upload and map to variables, using primitive types and individual parameters may not be a great idea—the signature of method may become confusingly long. The nice thing about the default binder is that if the parameter type to fill in is a .NET complex type (such as the RepeatInfo class) then the binder attempts to populate the public properties based on the same name-matching algorithm. In other words, if the class to bind has a property named Text of type String (as in the example) then the property will be initialized to the value of any posted data with the same name. Put another way, if the ASP.NET form posting data has a text box with an ID of TextBox1, then the binder will try to find the match between that value and a property on the target type named TextBox1. If no match can be found, then the property on the target type will retain the default value for the type.

Custom Default Binders

The default model binder is generic enough to work most of the time. However, there might be situations in which you want to create a custom one. This happens specifically when you need to aggregate a few values being uploaded individually together to form a new object. For example, suppose that your ASP.NET form has three distinct fields for day, month and year. On the server, you’d like these three distinct fields to form a DateTime object. You can bind the individual values to distinct parameters and then perform the conversion manually in the controller method, as shown below:

public ActionResult Index(Int32 day, Int32 month, Int32 year)

In alternative, you can declare a parameter of type DateTime and bind it to a custom binder that just works to produce DateTime objects.

public ActionResult Index(
        [ModelBinder(typeof(DateTimeModelBinder))] DateTime theDate)

The binding expressed here will only occur when the Index method is invoked. You can also register a binder type for all occurrences of a given type (such as DateTime in this example). Here’s the code you need:

ModelBinders.Binders[typeof(DateTime)] = new DateTimeModelBinder();

A model binder object is a class that implements the IModelBinder interface or that simply inherits fromDefaultModelBinder. For more information and examples on a custom model binder you can check my book “Programming ASP.NET MVC”, 2nd edition from Microsoft Press.

Summary

The model in MVC is frequently a misunderstood concept because it is often mistaken for the domain model. In summary, the model in the MVC pattern refers to modeling the data as seen by the view. In this regard, I’d call it the view model. In addition to the view model, you have the domain model that describes the data you work on in the backend. These two models may coincide in CRUD applications and in some other relatively simple scenarios. However, not using distinct view and domain models when the two do not coincide may be a serious architectural weak point. Finally, in ASP.NET MVC, the way in which data flows into the controller may be modeled through another bunch of ad hoc classes—the input model. However, of the three models, the input model is probably the one that you can consider optional without compromising the stability of your architecture. The input model helps in the design and understanding of data flow; but if you consider it part of the view model you’re not necessarily doing a wrong thing.

原文地址:https://www.cnblogs.com/imust2008/p/4986271.html