Necessary digression: not so long ago I posted an article intended for the print edition . The article below has the same fate: it did not go to press due to the plight of the magazine. Like last time, I decided to publish an article on Habré, since the subject matter falls under the format. It should be noted that the article is framed and contains text for the magazine, if it was prepared for Habr, then some parts could be changed. I hope you enjoy the article.

Recently, it is noticeable that Microsoft pays increased attention to the development of its development tools, new tools and software development mechanisms on its .net platform. The C # language is rapidly developing, the fourth version of which is just around the corner. Introduced and actively promoted new language F #. The Entity Framework has been developed for database developers, which is already available as a final version in the first service pack for .Net Framework 3.5 and Visual Studio 2008. Microsoft is actively engaged in the client part of developing web projects. For our attention, the development path proposed Ajax.Net 4.0. Internet Explorer 8 is increasingly conforming to standards and is becoming an attractive tool for web programmers, for example, its Developer Tools tab includes a JavaScript profiler. A very good news recently was the announcement of full support and the inclusion of the jQuery JavaScript library in the next Visual Studio update. In this light, the question arises, what is the proposed ASP.NET developers? The answer is MVC Framework. The purpose of this article is to consider some common problems that programmers may encounter when deciding to use the MVC Framework for their web projects and their solutions.
O MVC
Pattern Model-view-controller or in English Model-view-controller has been used for a long time. Back in 1979, he was described by Trigve Reenskaug in his work “Developing applications on Smalltalk-80: how to use Model-view-controller”. Since then, the pattern has established itself as a very successful software architecture.
The user, working with the interface, controls the controller that intercepts user actions. Next, the controller notifies the model of user actions, thereby changing the state of the model. The controller also notifies the view. The view, using the current state of the model, builds the user interface.
The basis of the pattern is the separation of the application data model, its logic and data presentation from each other. Thus, following the rule of "divide and conquer", it is possible to build coherent software, in which, firstly, the model does not depend on presentation and logic, and secondly, the user interface is reliably separated from the control logic.
')
At the moment, the MVC pattern is implemented in one form or another for most of the programming languages ​​used to develop web applications. PHP has the largest number of implementations, but for Java, Perl, Python, Ruby has its own variants. Before the advent of the MVC version of Microsoft, for the .NET platform, there were also different versions: Maverick.NET and MonoRail.
ASP.NET MVC Framework
On December 10, 2007, Microsoft introduced its version of the MVC implementation for ASP.NET. It is still based on .aspx, .ascx and .master files, fully supports form-based authentication, roles, data caching, session state management, health monitoring, configuration, provider architecture, and more.
On the other hand, the MVC Framework does not involve the use of classic web forms and web controls, it lacks mechanisms such as callbacks (postbacks) and view state (viewstate). The MVC Framework also offers the use of URL-mapping and REST architecture as a query model, which will positively affect the search engine optimization of web-projects.
--- | Classic ASP.NET | MVC |
Query model | postback | REST |
Data model | through the cs-file code of the page (code-behind) | determined by MVC pattern |
Interface development | web-controls | pure html MVC UI Helpers |
Auto id generation | Yes | not |
Viewstate | there is | not |
URL mapping | not | there is |
In general, expressing my opinion, I can say that the MVC Framework has proposed a new style for ASP.NET developers that is focused on the quality of client code. The generated MVC code of the pages does not contain anything automatically generated, there are no bloated identifiers, no huge viewstate, writing client scripts is simplified due to the fact that the page code is pure, created by the HTML programmer himself. In an era when the concept of web 2.0 firmly entered into our lives full control over the page on the client side - this is the key to the success of any web-project.
MVC Framework Versions
From version to version, the MVC Framework is improving, and for the period from September to October, two versions were released that brought a lot of new things, so the following changes occurred in Preview 5:
- Added support for rendering partial views, in fact, you can insert your custom elements into the view;
- Added attribute AcceptVerbs, which allows you to set the action for a specific type of request (POST or GET);
- Added attribute ActionName, which allows you to set the name of the method action. By default, the names of action and method name are the same, but now it is possible to make them different;
- Changed the operation of the attribute HandleError. Now developers can see standard "yellow pages of death" with information about errors;
- Added support for complex types through ModelBinder.
The MVC Framework Beta, released in mid-October, also contains a lot of add-ons:
- New "Add View" menu in Visual Studio
- \ Scripts folder and jQuery support
- Built-in support for Model Binder for complex types
- Rebuilt Infrastructure Model Binder
- Improved UpdateModel and TryUpdateModel methods
- Improved testing of UpdateModel and TryUpdateModel scripts
- The AcceptVerbs attribute is typed. Added HttpVerbs listing.
- Improved default error messages during validation
- Some helper methods have been modified. Changed form creation. Methods of steel extension-methods of class HtmlHelper.
- Silverlight 2 project support
- The ASP.NET MVC Futures build for this beta is rendered separately and is not included in the delivery.
- GAC Assembly Placement Support
Tips
How to set on the page html element select
To create an element, use the DropDownList Html class helper method:
<% = Html.DropDownList ("", "sampleSelect")%>
The first parameter specifies the string that will be added to the list of the element, as the default string, the second parameter is the name of our select.
The most interesting thing is how to add data to select. Suppose we have the following data model:
public class Product
{
public string Name
{
get ;
set ;
}
public int Id
{
get ;
set ;
}
}
* This source code was highlighted with Source Code Highlighter .
To place a data set of type Product in select, you must do the following:
var products = GetProducts ();
ViewData ["sampleSelect"] = new SelectList (products, "Id", "Name");
Where
GetProducts is a certain method, the data source is in the form of IEnumerable, such data sources should be located in the data model, here is simplified for example;
SelectList is a helper class defined in System.Web.MVC.
Here, through ViewData, the set of data created using the SelectList class is passed, the constructor of which, through the first parameter, receives data in the form of products. The names of the properties defined in the Product class for the values ​​and output text in select are passed in the second and third parameters.
How to display a user control
In order to display your control, the Html.RenderPartial (...) method presented in preview 5 is used. In order to use a user control in its projects, it must be created as an MVC View User Control, and not as a regular Web User Control. The difference is in what class the created element is inherited. In the case of MVC, the element will be inherited from System.Web.Mvc.ViewUserControl.
Displaying a custom item is easy:
<% Html.RenderPartial ("SampleUserCtrl"); %>
Where SampleUserCtrl is the name of the class that represents the user control.How to assign an attribute class to an element created through an Html class
The HTML helper class is a very useful MVC Framework tool that allows you to consistently create controls on a page. And, of course, the methods of this class allows you to set the attributes of html-elements. In the example, we will set a hyperlink with the text SampleLink, the action ActionName with the parameter product.Id tag rel = "nofollow".
Html.ActionLink ("SampleLink", ActionName, new {product.Id}, new {rel = "nofollow"})
The problem begins when the attribute name matches the reserved word in C #, which of course is “class”. The solution is simple:
Html.ActionLink ("SampleLink", ActionName, new {product.Id}, new {@class = "sample-css-class"})
When little ViewData
Sometimes, it is required to transfer data from one action to another. For example, on the page with the input field during the processing of the entered data, we had an exceptional situation, which must be reported, returning back to the page. Since the processing of the entered data was done by one action, and the handling of the page is managed by another, then ViewData, which is used in other cases, will not help us.
For such cases, there is TempData in MVC, a data structure that exists only at the time of the request and is then deleted.
<% if (TempData ["Message"]! = null) {%>
<% = HttpUtility.HtmlEncode (TempData ["Message"]. ToString ())%>
<%}%>
This code will display a message in case it exists. The message itself is elementary:
if ( String .IsNullOrEmpty(UserText))
{
TempData[ "Message" ] = " " ;
return RedirectToAction( "Index" );
}
* This source code was highlighted with Source Code Highlighter .
We share the logic of GET and POST requests
The MVC Framework preview 5 brought, among other things, one remarkable mechanism that allows you to separate the logic for POST and GET requests. This separation is made through the AcceptVerbs attribute. For example, let's say we have a form on the main page for entering a login and password. There is also an action Login, which is responsible for checking the entered login and password, user authentication and authorization. For security, you can limit the action of this action by forcing it to process only POST requests from the form on the page. And all other requests to send to another action, which would simply return the same page.
[AcceptVerbs( "GET" )]
public ActionResult Login()
{
return View( "Index" );
}
[AcceptVerbs( "POST" )]
public ActionResult Login( string userLogin, string userPass)
{
[… …]
return RedirectToAction( "Index" , "Home" );
}
* This source code was highlighted with Source Code Highlighter .
In addition, the HttpVerbs enumeration appeared in the MVC Framework Beta, with which you can set the AcceptVerbs attribute parameter to a strongly typed value.
[AcceptVerbs (HttpVerbs.Post)]
Caching control
MVC Framework allows you to manage the caching of the result of each action. That is, you can specify how and how much a particular request will be cached on the server or on the client. For this, there is an analogue of the classic asp.net directive
<% @ OutputCache%> attribute [OutputCache]. Below are its parameters:
Parameter | Description |
VaryByHeader | a line with semicolon-separated values ​​of http-headers by which conditional caching will be performed |
VaryByParam | sets conditional caching based on the values ​​of the query string during GET or parameters during POST |
VaryByContentEncoding | specifies caching condition depending on the contents of the Accept-Encoding http-header directive |
Duration | Duration sets the time value in seconds during which the page or user element is cached. |
NoStore | takes a boolean value. If true, adds the no-store parameter to the Cache-Control http-header directive. |
Cacheprofile | used to specify a caching profile specified via web.config and the caching section |
OutputCacheLocation | This parameter describes the rule for the cache storage location and takes one of the values ​​of the OutputCacheLocation enumeration. |
VaryByCustom | any text to control caching. If this text is equal to “browser”, then caching will be done conditionally by the name of the browser and its major version. If a string is specified for VaryByCustom, then you must override the GetVaryByCustomString method in the Global.asax file to implement conditional caching. |
Using OutputCache is very basic. The following example shows how to cache the result of the action Index for 10 seconds.
[OutputCache(Duration = 10)]
public ActionResult Index()
{
…
}
* This source code was highlighted with Source Code Highlighter .
Complex pages and custom models
Often in the creation of pages involved a lot of the most heterogeneous data. For example, on the personal page of a user of a portal, there can be data with his selected news, his profile data, data about messages sent by other participants, weather data or date and exact time, and a lot of different data.
Such data, when stored in databases, are not stored in one table, but in several, maybe even several dozen. In this case, if you use the ORM - database object model, then to create a single MVC Framework page, you will have to initialize a lot of list or other variables and fill them with data.
In this case, I recommend making your complex data model by implementing the letter M from the word MVC. Below I will give an example of one of the options for implementing such a model:
public class BankInfoModel
{
public Bank Bank;
public IEnumerable <BankBranch> BankBranches;
}
* This source code was highlighted with Source Code Highlighter .
A model is created that contains the value of Bank and the transfer of all branches of this bank (for example, for several reasons, we cannot get a list of branches simply through the Bank).
To use our model transparently, we need to do the following: remake the base class of our view with
public partial class Index: ViewPage
on
public partial class Index : ViewPage<BankInfoModel>
* This source code was highlighted with Source Code Highlighter .
Now, in the view code, an instantiated instance of the BankInfoModel model is available to us via ViewData.Model. Of course, the controller must initialize it for us, but this is elementary:
public ActionResult Info( int ? id)
{
var info = new BankInfoModel {Bank = db.Banks.Single(x => x.id == id)};
info.BankBranches = info.Bank.LocationBranches
.Where(x => x.Address.Street.cityId == 1).SelectMany(x => x.BankBranches);
return View(info);
}
* This source code was highlighted with Source Code Highlighter .
Where is the int? id is a parameter that points to a bank idUsing an instance of our model in a view is also simple:
< div class ="vcard" >
< h1 >< span class ="fn org" > <% =ViewData.Model.Bank.shortName %> </ span ></ h1 >
< p > : < span class ="fn phone" > <% =ViewData.Model.Bank.phone %> </ span ></ p >
</ div >
* This source code was highlighted with Source Code Highlighter .
Complex types, ModelBinder
MVC Framework is so arranged that the transfer of form values ​​is done through action parameters. For example, below is a form with two fields for entering login and password data.
< form method ="post" action ="<%= Html.AttributeEncode(Url.Action(" Login ", " Home ")) %>" >
< ul >< li >
< label for ="txtLogin" > </ label > <% = Html.TextBox( "userLogin" , “”, new {id = "txtLogin" }) %> </ li >< li >
< label for ="txtPass" > </ label > <% = Html.Password( "userPass" , “”, new {id = "txtPass" }) %> </ li >< li >
< input type ="submit" value ="" />
</ li ></ ul >
</ form >
* This source code was highlighted with Source Code Highlighter .
The data that the user entered in this form will be passed into action through the appropriate parameters, as shown below:
[AcceptVerbs ("POST")]
public ActionResult Login (string userLogin, string userPass)
But what to do when the form contains dozens of input fields? Is it possible to create dozens of parameters for action? No, the MVC Framework contains a mechanism that allows you to avoid such an ugly step as numerous method parameters. Such a mechanism is called ModelBinder. First, let's declare a class that describes our form:
public class LoginModel
{
public string userLogin { get ; set ; }
public string userPass { get ; set ; }
public LoginModel()
{
}
}
* This source code was highlighted with Source Code Highlighter .
Then, we need to define a class that implements the IModelBinder interface:
public class LoginModelBinder : IModelBinder
{
public object GetValue(ControllerContext controllerContext, string modelName,
Type modelType, ModelStateDictionary modelState)
{
LoginModel modelData = new LoginModel ();
modelData.userLogin = controllerContext. HttpContext .Request[ "userLogin" ];
modelData.userPass = controllerContext. HttpContext .Request[ "userPass" ];
return modelData;
}
}
* This source code was highlighted with Source Code Highlighter .
Please note that we define the GetValue method and fill in the model data through the request context.
Before the final step, we need to specify the ModelBinder attribute for our LoginModel class:
[ModelBinder( typeof (LoginModelBinder))]
public class LoginModel
{
public string userLogin { get ; set ; }
public string userPass { get ; set ; }
public LoginModel()
{
}
}
* This source code was highlighted with Source Code Highlighter .
In conclusion, we use our constructions to automatically initialize the parameters passed from the form:
[AcceptVerbs ("POST")]
public ActionResult Login ([ModelBinder (typeof (LoginModelBinder))] LoginModel loginModelData)
This option appeared in the MVC Framework preview 5.
MVC Framework Beta has brought significant improvements in this plan. Now there is a built-in binder that automatically serves the transfer of standard .NET types. That is, instead of creating your ModelBinder in the previous example, we can create simplified code:
[AcceptVerbs (HttpVerbs.Post)]
public ActionResult Login (LoginModel loginModelData)
In addition, the Bind attribute is available for developers, which allows you to manage the prefix of the names of form parameters, so it is possible to change its value or indicate that the prefix will not be at all. The same attribute allows you to set "white" or "black" lists of form properties that will be associated with the values ​​of a complex type parameter.
[AcceptVerbs (HttpVerbs.Post)]
public ActionResult Login ([Bind (Prefix = ””, Include = ”userLogin, userPass”)] LoginModel loginModelData)
And although we are not obliged to create our own ModelBinder variants, this tool can still be useful and will be useful for fine-tuning the processing of the values ​​of complex types submitted from the form.
Interception of the raw errors, incorrect URL
It is known that nothing confuses the user as an incomprehensible error that occurred out of the blue. Even if such errors occur in your project from time to time, you need to ensure that the user receives the notification as friendly as possible. For such purpose, error pages, interception of unhandled exceptions, as well as handling 404 http error “Page not found” are used.
To catch unhandled exceptions, you need to create in the Views / Shared folder an Error.aspx page with error information that contains, for example, the following code:
< span >
! , . < br />
: <% = ViewData.Model.Exception.Message %>
</ span >
* This source code was highlighted with Source Code Highlighter .
In order for all unhandled exceptions to be redirected to our page, each controller must specify the HandleError attribute:
[HandleError]
public class HomeController: Controller
Further, in order to process all invalid URLs that do not fall under our route specified in global.asax, you need to create another route that would send all invalid requests to a special page:
routes.MapRoute ("Error", "{* url}", new
{
controller = "Error",
action = "Http404"
});
As you can see, all incorrect URLs will result in the action Http404 of the Error controller. You need to create such a controller and add an action:
public class ErrorController : Controller
{
public ActionResult Index()
{
return RedirectToAction( "Http404" );
}
public ActionResult Http404()
{
Response.StatusCode = 404;
return View();
}
}
* This source code was highlighted with Source Code Highlighter .
The content of the Http404.aspx view is elementary:
< h1 > 404 </ h1 >
< p > </ p >
* This source code was highlighted with Source Code Highlighter .
Thus, we catch attempts to navigate the wrong route, but what if the route falls under the pattern, but is still incorrect? An exit can be a field check with an exception being thrown:
public ActionResult Info( int ? id)
{
if (!id.HasValue)
throw new HttpException( "404" );
[ … … ]
}
* This source code was highlighted with Source Code Highlighter .
To intercept such a custom exception, you must create or modify the customErrors section in web.config:
< customErrors mode ="RemoteOnly" >
< error statusCode ="404" redirect ="~/Error/Http404" />
</ customErrors >
* This source code was highlighted with Source Code Highlighter .
Thus, all of our custom 404 exceptions will also be redirected to our Http404.aspx page, which will keep the general approach and combine the concept of “not found page” with both invalid URLs and URLs entered with an error or due to some other unacceptable reasons for handling, for example due to an access violation.
It should be noted that for the correct operation of error interception in IIS7, the following parameters must be set in the “Error pages” section of your site settings.
Conclusion
In the article I tried to describe some aspects of working with the MVC Framework. Many moments are elementary, some are not as simple as they seem, some are well described on the Internet, some are not described at all. In all cases, the above material can do a good job both for those who are just starting out with the MVC Framework, and for those who already have experience creating web applications with it.
The MVC Framework today is just a beta version of the product that will be in the end. But now this tool allows you to create web applications using all the power of the MVC pattern. Perhaps when you read this article, the final release of the MVC Framework will be released, which is expected by the end of 2008, but it can be assumed that most of the functions will not be changed.
