There are many good reasons why you might want to migrate a Web Pages site to MVC, but it's important to state at the outset that performance and scalability should not be among them. A Razor Web Pages site is based on the same ASP.NET framework as MVC and Web Forms sites, and pound for pound, will scale and perform just the same as the other two types of site. Whatever your reasons for wanting to make the change, you should bear in mind that the Web Pages framework was partly introduced as a smoother on-ramp to ASP.NET development because it was (and still is) considered that MVC is a complex framework. It has a number of moving parts and their roles will be explained here by comparing them to equivalent parts of the Web Pages framework. Your work with Razor Web Pages certainly puts you at an advantage in terms of learning about MVC.
There is little point in retaining the dynamic-based Database
helper when moving across to MVC, so this migration will ditch it in favour of the Entity Framework for data access.
The Bakery Site
Just to recap, the Bakery template site that comes with WebMatrix is a simple application that features a three-step ordering process: bakery items are displayed; the user selects one by clicking on it; the user completes a form and submits it. Product details are stored in a database and their images in the file system. As such, the application includes enough features at a basic level to help illustrate the foundations of MVC.
Model, View, Controller
Model View Controller is an architectural pattern chiefly concerned with generating appropriate UI based on user input. It is often referred to as a Presentation Pattern. One of the key selling points behind the pattern is that it promotes "separation of concerns". The concerns that should be separated are the logical layers within an application. These include presentation, business logic, data access and any service layers. The reasons why you should consider separating your concerns include easier maintenance, greater possible reuse, ease of testing. The Razor Web Pages development model doesn't do anything to promote good separation. A typical page consists of database calls, business logic such as validation and calculations, service layer artifacts like email generation and sending, and of course, presentational HTML intermixed with server side code. If you have tried to keep as much server-side code as possible in a code block at the top of the page, you have already exercised a certain separation and that will make your migration much eaiser. The Bakery template, along with the Photo Gallery and Starter Site templates all demonstrate some discipline in terms of where the server side code goes.
So what goes where in an MVC application? The View belongs in the presentational layer and generally, the stuff below the code block in your Razor Web Page will allow itself to be transplanted straight into an MVC Razor View file with few complaints (so long as you don't have any database calls down there...). Database calls, emailing and validation are all part of the Model, which is really a catch-all area for server-side logic. Therefore the contents of your top-of-the-page code block will find itself in one form or another somewhere in the Model. The Controller is the new bit to Web Pages developers. Its role is to process incoming requests, ensure that appropriate application logic is executed based on the request, and to see that the correct response is generated by calling a particular View. It basically controls the flow of the application between browser and server.
Some of these ideas can seem abstract at first, but they soon become clearer through example. And so to work.
Creating an MVC Application
ASP.NET MVC applications are built using what is known as the Web Application Project type. Web Pages sites are built using the Web Site Project type. The chief difference between the two of them is that Web Application projects must be precompiled before they are deployed to a web server, while you can FTP raw source files from a Web Site project to a web server, and they will be compiled on demand when the first request comes into the application. WebMatrix only supports Web Site projects, so it is unsuitable for building MVC sites. So the migration will involve creating a new MVC application in Visual Studio, and porting as much code across to it as possible.
-
Choose the New Project option in Visual Studio and select ASP.NET Web Application
-
Provide a name for your application and click OK.
-
Choose MVC from the available templates and click OK.
You now have a basic ASP.NET MVC web site. The structure of the default site is illustrated below
There are folders for all three parts of MVC - a Models folder, a Views folder and a Controllers folder. The only one that the framework relies on by default is the Views folder. ASP.NET MVC expects to find view files there. You can place controllers and model classes pretty much anywhere within your application. Typically, most developers put controller classes into the Controllers folder, but will place other classes that belong to the Model wherever they like. Some even delete the Models folder up front. In the second and third parts of this tutorial, you will place Model code in a number of different locations.
At this stage, you should copy across the database and image files from the existing Bakery site. You should do this by right clicking on folders and choosing Add Existing Item. That way the items are included in the project automatically. However, you might just want to use Windows Explorer to copy the images across, and then click the Show All Files icon at the top of Solution Explorer (with the red box around it in the preceding image) and then right click on the images folder and choose Include in Project.
Layouts and Views
The Views folder contains a folder per controller and one called Shared. By convention, you place layout and "partial" files into the Shared folder. Partial files are the MVC equivalent to the content blocks which are called via the RenderPage
method in Web Pages. The following code shows the Bakery template layout page transplanted to the Views\Shared\_Layout.cshtml file. Changes to the code are highlighted in yellow:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Fourth Coffee - @ViewBag.Title</title> <link href="~/Content/Site.css" rel="stylesheet" /> <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" /> <script src="~/Scripts/modernizr-2.6.2.js"></script> <script src="~/Scripts/jquery-1.10.2.min.js"></script> @RenderSection("Scripts", required: false) </head> <body> <div id="page"> <header> <p class="site-title"><a href="~/">Fourth Coffee</a></p> <nav> <ul> <li><a href="~/">Home</a></li> <li><a href="~/About">About Us</a></li> </ul> </nav> </header> <div id="body"> @RenderBody() </div> <footer> ©@DateTime.Now.Year - Fourth Coffee </footer> </div> </body> </html>Only one change was necessary:
Page.Title
became ViewBag.Title
. ViewBag
is MVC's equivalent mechanism for passing small pieces of data from page to page, or controller to page. In the WebMatrix Bakery site, the layout is specified in _PageStart.cshtml. The equivalent in MVC is a file named _ViewStart.cshtml. You can find that in the root of the Views folder with the following single line of code:
@{ Layout = "~/Views/Shared/_Layout.cshtml"; }
If you run the application at this stage, you will see that the home page has adopted the Bakey layout just as expected:
However, if you click on the About Us link, you will get a Page Not Found error - despite the fact that there is an About.cshtml view file in the Views\Home folder. In order to be able to fix this, you need to know a little about Routing and Controllers.
A Little About Routing And Controllers
By default, incoming requests to a Razor Web Pages site are mapped to physical files on disk. So a request for http://www.yourdomain.com/about
will be matched to a file called about.cshtml in the root folder of your site as I have described in a previous article on routing in Web Pages. The Web Pages framework receives a request and locates the appropriate file based on the URL, then executes the code in the file, returning the result (usually HTML, but could be JSON, XML, text, binary data etc.) as a response. In ASP.NET MVC, requests are not mapped to files. They are mapped to methods on controller classes instead. A request comes in and the framework locates the correct controller, instantiates an instance of it, then calls the method associated with the request, returning the result as a response to the client. The following code shows the HomeController
with its Index()
method:
public class HomeController : Controller { public ActionResult Index() { return View(); } }
When the Index()
method is called, it in turn calls the Controller.View()
method which locates the appropriate View file based on a convention, and executes the logic in it, returning the result. The convention used to locate the view is first to look in the Views folder for a subfolder named after the current controller (Home), then to look for a file in that folder named after the current method being executed (Index).
It's not difficult to conceptualise a system locating physical files based on a URL - you can write code yourself easily enough to do that, but how does a URL get matched up to a method on a class? The mechanism reponsible for that is called Routing. The following piece of code comes from App_Start\RouteConfig.cs. It features a method called RegisterRoutes
which is responsible for defining how URLs are to be mapped to controller methods.
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
The RouteCollection.MapRoute
method call sets up the default mapping between URLs and the controller method that is invoked. The route is given a name of Default
, and a url pattern is specified. The pattern says that the first segment of the URL should be treated as the name of the controller to be invoked, and the second segment should be matched to the name of the method to be called on that controller. The last part of the pattern represents parameters that might be passed in to the controller's method. So a request for http://www.domain.com/products/show/3
will be mapped to a method called Show()
that accepts an int
parameter on a controller called Products
. The default route is a method called Index()
on a controller called Home
. Quite often, this route configuration covers all of the needs for a site.
Without any alterations, the existing About page (Views\Home\About.cshtml) will be reached at http://www.domain.com/home/about
, but the link in the Bakery site's layout page that we just migrated points to http://www.domain.com/about
(without the name of the controller). A new route needs to be registered to cater for that. The highlighted block shows how to specify it.
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "About", url: "about", defaults: new { controller = "Home", action = "About"} ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
It says that if the URL consists of just one segment with the value "about", invoke the About
method on the Home
controller. It has been placed above the default route because routes are registered in the order that they are declared, and when the routing system looks at the RouteTable
for a route that matches the incoming URL, it will take the first match and ignore all other possible matches.
Having defined a route, it's time to migrate the About page.
-
Make the alteration outlined above to the RouteConfig.cs file.
-
Replace the content of the Views\Home\About.cshtml file with the HTML from the Bakery About.cshtml file.
Open the HomeController.cs file in the Controllers folder and make the highlighted change to the
About
method:public ActionResult About() { ViewBag.Title = "About"; return View(); }
Rebuild the project by pressing Shift+Ctrl+B.
-
Run the application by pressing Ctrl+F5 and then click the About Us link in the layout page. The About page should appear.
The change that you made to the controller was to replace the unused ViewBag.Message
with ViewBag.Title
. ViewBag.Title
is used to set the <title>
element in the layout page. You could also have left this declaration in a code block at the top of your view file. Ideally, you should strive to minimise the amount of server-side code you place in a view files. But whichever approach you take, you should aim for consistency.
Summary
You have begun the process of migrating a Razor Web Pages site to ASP.NET MVC. You have looked at the roles of the View and the Controller in MVC. You've seen how routing is configured to determine which controller's method is invoked in response to a requested URL, and you have managed to get a page to run. It is perfectly possible to build an entire application with just this knowledge so long as the application is static HTML. However, the Bakery site is dynamic and includes data, validation and sending of email - all aspects of the Model. The second part of this tutorial will explore MVC's Model in more detail, and will start the process of migrating the code blocks to the new site.