If you used ASP.NET MVC before, you are probably aware that Visual Studio provides ASP.NET MVC Web Application project template to create MVC based applications. There is no option to create MVC Web Sites. In this article I am going to explain how I solved this issue in one of my application.
Recently I migrated one of my ASP.NET 2.0 web sites to MVC 2.0. Though small the web site was having some 20 odd classes in App_Code folder and of course several web forms. While migrating the web site I did some "non-standard" things including:
- I developed the MVC version as a "web site" and not as a "web application". No Bin folder. No class libraries. No compiled assemblies.
- I deviated from the default folder structure for Models, Controllers and Views.
- At some places I needed to communicate between Web Forms and MVC Controllers. The Web Forms posted their data to a Controller and controller then took over.
When Microsoft released ASP.NET 1.0 there was no concept of project-less web sites. Based on developer feedback they included it as a part of ASP.NET 2.0. At the same time special folders such as App_Code were introduced to keep classes needed by the web site. Without going into any comparison or debate between project based and project-less development the fact remains that many web sites developed using ASP.NET 2.0 and 3.5 follow project-less structure. The sample web site I decided to migrate was no exception. Since the web site already had a well defined folder structure and classes I decided to follow "minimum changes" approach to this migration. That is why I decided to stick with "web site" and not "web application". However, if you are developing a brand new web site then more appropriate approach would be to follow the recommended project type (MVC Web Application).
Since I already had many classes in App_Code folder, I decided to put my controllers and models in App_Code folder instead of the default Controllers and Models folder. The existing web site was having web forms arranged in several folders. This arrangement was on the basis of functionality. For example, all blog post related web forms in one folder, all discussion forum related web forms in another folder etc. I was reluctant to change my folder structure just because the default MVC folder structure is something different. So I decided to deviate from the default folder structure. Note, however, that for a new MVC web application (or unless there is some strong reason) it makes sense to follow the default folder structure.
I decided to migrate the sample web site in phases. That means for some time there is going to be a mix of web forms and MVC based models-views-controllers in a single web site. At places I wanted to accept user input on a web form and wanted to pass it to a controller. I know this may sound ugly but you know, real world requirements are always strange than a controlled environment :-)
Now, let's see with a step-by-step example how all of the above can be achieved. Remember, however, that you should carefully evaluate these deviations on case to case basis.
For the rest of the article, I assume that you are already familiar with MVC design pattern and ASP.NET MVC in general.
Creating an empty web site
First, create a new ASP.NET Empty Web Site using Visual Studio 2010.
Modify the web.config file to have a <compilation> section as follows (you can download the complete code of this article. See "Associated Links" section) :
<compilation debug="false" targetFramework="4.0">
<assemblies>
<add assembly="System.Web.Abstractions, ... />
<add assembly="System.Web.Routing, ... />
<add assembly="System.Web.Mvc, ... />
</assemblies>
</compilation>
This is how ASP.NET will understand about MVC specific namespaces and classes. Also, add <pages> section as shown below:
<pages>
<namespaces>
<add namespace="System.Web.Mvc"/>
<add namespace="System.Web.Mvc.Ajax"/>
<add namespace="System.Web.Mvc.Html"/>
<add namespace="System.Web.Routing"/>
</namespaces>
</pages>
Rather than importing MVC specific namespaces in each and every view we can include them using <pages> section.
Creating model and controller
Next, add App_Code folder and add two classes viz. EmployeeModel and EmployeeController to it.
EmployeeModel class is our MVC data model that represents an Employee of a hypothetical employee management system. Code the EmployeeModel class as shown below:
public class EmployeeModel
{
public EmployeeModel() { }
public EmployeeModel(int id, string name)
{
this.ID = id;
this.Name = name;
}
public int ID { get; set; }
public string Name { get; set; }
}
The EmployeeModel class simply defines two public properties viz. ID and Name.
The EmployeeController class acts as an MVC controller and has one action - List. The complete code of List action is shown below:
public class EmployeeController: Controller
{
public ActionResult List()
{
List<EmployeeModel> items = new List<EmployeeModel>();
items.Add(new EmployeeModel(100, "Tom"));
items.Add(new EmployeeModel(200, "Jerry"));
ViewData["employeelist"] = items;
return View("~/EmployeeView.aspx");
}
}
The controller class EmployeeController simply creates a generic List with a few EmployeeModel objects, stores it in ViewData so that it can be accessed inside a view and then renders the view. Note that since we are not following the default folder structure for views, we must specify the complete path of the view (~/EmployeeView.aspx). We will revisit this issue again a bit later.
Creating a view
Now, add a new single file web form in the root folder and name it as EmployeeView.aspx. Replace the contents of the newly added web form with the following markup:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Employee View</title>
</head>
<body>
<table>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
<%
List<EmployeeModel> items =
(List<EmployeeModel>)ViewData["employeelist"];
%>
<% foreach (var item in items) { %>
<tr>
<td><%= item.ID %></td>
<td><%= item.Name %></td>
</tr>
<% } %>
</table>
</body>
</html>
Notice that the EmployeeView page inherits from System.Web.Mvc.ViewPage base class. Inside we retrieve previously stored employeelist object and display a list of employees in a table.
Defining routes
Next, add a Global Application Class (Global.asax) to the web site and handle Application_Start event as follows:
<%@ Application Language="C#" %>
<script runat="server">
void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
RouteTable.Routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {
controller = "EmployeeController",
action = "List",
id = UrlParameter.Optional
}
);
}
</script>
Notice how we are registering default route in the Application_Start event handler. This way ASP.NET will understand your requests correctly and pass them to appropriate controllers.
That's it! Now run the web site and give a hit to employee/list. A sample URL will be:
http://localhost:1234/MVCWebSite/employee/list
and you get to see a table with employees listed (see below).
Creating strongly typed views
So far so good. Now, let's try to use a strongly typed view. A strongly typed view inherits from System.Web.Mvc.ViewPage<IEnumerable<model_class>> instead of just System.Web.Mvc.ViewPage. Modify @page directive of the EmployeeView.aspx to reflect this change. Build the web site and you will get the following error:
Could not load type 'System.Web.Mvc.ViewPage<IEnumerable<EmployeeModel>>'.
To correct this error modify <pages> section from web.config as shown below (see download for full listing):
<pages pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, ..."
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, ..."
userControlBaseType="System.Web.Mvc.ViewUserControl, ...">
Here, we specify the default base class for web pages using pageBaseType. Similarly default base class for view user controls (though we are not using any in this example) is specified using userControlBaseType. The pageParserFilterType attribute specifies a parser filter class that is called by the page compilation process before the parsing step to accommodate changes made to the source code at run time.
Now the web site will build without any compilation errors. The next step is to modify the controller and view. Create one more action in the EmployeeController as shown below:
public ActionResult ListStronglyTyped()
{
List<EmployeeModel> items = new List<EmployeeModel>();
items.Add(new EmployeeModel(100, "Tom"));
items.Add(new EmployeeModel(200, "Jerry"));
return View("~/EmployeeView.aspx",items);
}
Here, we pass the model (items) to the view as a second parameter instead of passing it via ViewData. Finally, modify EmployeeView to use strongly typed model as shown below:
<%@ Page Language="C#"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<EmployeeModel>>" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Employee View</title>
</head>
<body>
<table border="1" cellpadding="5">
<tr>
<th>ID</th>
<th>Name</th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td><%= item.ID %></td>
<td><%= item.Name %></td>
</tr>
<% } %>
</table>
</body>
</html>
Notice the code marked in bold letters. The page now inherits from a ViewPage based on a strongly typed model. The model is then accessed in the foreach loop.
Using default folder structure for views
As I mentioned earlier, we need to use full path to the view file since we are deviating from the default folder structure. If you wish you can make use of the default folder structure just for view files (and keep controllers and models inside App_Code as before). See the following folder structure:
The EmployeeView now resides under Views\Employees folder. This is the default folder structure for views in MVC Web Application project. You can now add another controller named EmployeeView as follows:
public ActionResult EmployeeView()
{
List<EmployeeModel> items = new List<EmployeeModel>();
items.Add(new EmployeeModel(100, "Tom"));
items.Add(new EmployeeModel(200, "Jerry"));
return View(items);
}
As you can see we no longer specify ~ path for the view. Note, however, that for this to work your controller name and view name must be the same in addition to following the default folder structure.
Posting a Web Form to a controller action
Though rare you may require to post a web form to a controller. This can be done using PostBackUrl property of Button web control. You will design web form as you normally do and then set PostBackUrl property of the form's submit button to a controller action.
Design a web form as shown below and name it as PostToController.aspx. Change ID of the textboxes to name1 and name2 respectively.
Set the PostBackUrl property of the Submit button to employee/listpost and then add the listpost controller action as shown below:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ListPost(FormCollection collection)
{
List<EmployeeModel> items = new List<EmployeeModel>();
items.Add(new EmployeeModel(100, collection["name1"]));
items.Add(new EmployeeModel(200, collection["name2"]));
return View("~/EmployeeView.aspx", items);
}
Notice how we access textbox values using the FormCollection parameter. The EmployeeView then displays the employee listing as before.
Creating a Visual Studio Template
If you find yourself using MVC web sites again and again then it would be better to create project template for them. You can do so by selecting File > Export Template and follow the wizard to create a Visual Studio Project template.
Once created you can create new web sites based on this template.
The complete code download of what we discussed in this article is available in the "Associated Links" section. Open the web site in VS2010 and run the Default.aspx. It has links to all the controller actions we discussed above. Just select the required link and see the example in action.
That's it! Keep coding!!