Notes on running MS MVC on IIS 6 and migrating to MS MVC from Webforms

I took some steps to see how well MS MVC would coexist with Webforms on a hosted environment.  One of the things I really like about the new MS MVC is the ability to be run inside the same project/web application as webforms.  This will allow for a migration plan vs. just a complete total rewrite.

The first step was to add in the 3 assemblies required.  The assemblies in preview 2 were separated from the System.Web.Extensions, these assemblies are:

System.Web.Mvc (dependent on Routing)
System.Web.Routing (dependent on Abstractions)   <— note: this can be used outside of MS MVC
System.Web.Abstractions

Web.config updates:

Under ‘system.web’

<!-- MS MVC-->
      <namespaces>
        <add namespace="System.Web.Mvc"/>
        <add namespace="System.Web.Routing"/>
        <add namespace="System.Linq"/>
        <add namespace="System.Collections.Generic"/>
      </namespaces>

Under ‘assemblies’

<!-- MS MVC -->
      <add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

Under ‘httpModules’

  <!-- MS MVC -->
      <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

Under ‘system.webServer’

<!-- MS MVC -->
      <remove name="UrlRoutingModule"/>
      <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      
<add name="MvcScriptMap" preCondition="classicMode,bitness32" verb="*" path="*.mvc" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll"/>
      <add name="MvcScriptMap64" preCondition="classicMode,bitness64" verb="*" path="*.mvc" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll"/>
      

The next steps were done to the project structure:

Added a Controllers, Views, and Models folder

Updated the Global application (HttpApplication) to add routing:

protected void Session_Start(object sender, EventArgs e)
        {
            RegisterRoutes(RouteTable.Routes);
        }

public static void RegisterRoutes(RouteCollection routes)
        {
            // Note: Change the URL to "{controller}.mvc/{action}/{id}" to enable
            //       automatic support on IIS6 and IIS7 classic mode

see the note above:  when adding routes with IIS6 and II7 classic mode, you must include the .mvc (or any unique extension).  This is important.  Your host will also need to map .mvc to the ISAPI .net.  Although, as I hope to show, you can actually just use routing to get around this.

Here are some examples of my registered routes:

routes.Add(new Route("{controller}.mvc/{action}/{id}", new MvcRouteHandler())
            {
                Defaults = new RouteValueDictionary(new { action = "Index", id = "" }),
            });

            routes.Add(new Route("{controller}.mvc", new MvcRouteHandler())
            {
                Defaults = new RouteValueDictionary(new { action = "Index" }),
            });


            routes.Add(new Route("ViewMissionSummary.aspx", new MvcRouteHandler())
            {
                Defaults = new RouteValueDictionary(new { controller = "Mission", action = "ViewMissionSummary" }),
            });

Basically, the routing will let you intercept any call to a page and route it to a controller and action!  So in the above example for ‘ViewMissionSummary’, I took an existing page in Webforms and created a new controller, action and view.

Example controller:

namespace FSWeb.Controllers
{
    public class MissionController : FSController
    {
        public void ViewMissionSummary()
        {
            ICampaignDao campaignDao = DaoFactory.GetCampaignDao();
            TargetsDestroyed targets = campaignDao.GetCampaignData();

            IMissionDao missionDao = DaoFactory.GetMissionDao();
            MissionSummaryViewData summaryData = new MissionSummaryViewData();
            summaryData.MisSummary = missionDao.GetMissionSummariesForCurrentWeek();
            summaryData.CurrentCampaign = targets.CurrentCampaign;

            RenderView("ViewMissionSummary", summaryData);
        }
    }
}

To help with binding to the view I created a new ‘MissionSummaryViewData’ struct.  As you see above, the RenderView takes the name of the view and an object ‘viewData’.  The viewData is a way to attach a specific data structure type to a view

public struct MissionSummaryViewData
    {
        private List<MissionSummary> misSummary;
        private string currentCampaign;

        public List<MissionSummary> MisSummary
        {
            get { return misSummary; }
            set { misSummary = value; }
        }

        public string CurrentCampaign
        {
            get { return currentCampaign; }
            set { currentCampaign = value; }
        }
    }

On the view code behind I tell the ‘ViewMissionSummary’ view the data type I’m going to ‘attach’ to it, ie.

public partial class ViewMissionSummary : ViewPage<MissionSummaryViewData>
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            ViewMissionSummaryRepeater.DataSource = ViewData.MisSummary;
            ViewMissionSummaryRepeater.DataBind();
        }

        public static string ConvertData(object inputString)
        {
            DateTime dt = new DateTime(Convert.ToInt64(inputString));
            return dt.ToShortDateString();
        }
    }

I don’t have to do this, I can just pass key pair values, ie. ViewData[“MissionSummaryView”] = missionSummaryViewData and call ‘RenderView’.  However, making it typed allows me to use intellisense and have type checks in my view!

ie.

Missions Flown:  <%= ViewData.MisSummary.Count.ToString()%>

ViewData is a struct defined above that has a List<MissionSummary> , which has a ‘count’ – this is very nice feature of MS MVC!  You might have noticed above that I still have ‘databinding’ going on in MS MVC.  My solution uses Repeaters which are ‘MS MVC friendly’ and I was able to bind my list to the repeater.

Routing

Now that we have this in place, lets talk about how the routing will work.  Just deploying the current code above, I can access my new code via the following url:

http://www.forgottenskies.com/ForgottenWars/Mission.mvc/ViewMissionSummary

This corresponds to the route:

{controller}.mvc/{action}

In my code though, this ViewMissionSummary still has a ViewMissionSummary.aspx webform page.  I want to replace it with the above MVC code.  Instead of changing all my links in my solution to point to this new url (which isn’t very friendly!) I create a new route:

routes.Add(new Route("ViewMissionSummary.aspx", new MvcRouteHandler())
            {
                Defaults = new RouteValueDictionary(new { controller = "Mission", action = "ViewMissionSummary" }),
            });

This tells IIS that when ViewMissionSummary.aspx is requested, forward it to the Mission controller and corresponding action!

If a user selects ‘Mission’ on the menu, it will load:

http://www.forgottenskies.com/ForgottenWars/ViewMissionSummary.aspx

Which is mapped to my specified action!

One question that comes up with this is how to use querystrings in this example.  (I’m not going to cover RESTful urls right now, as I’m assuming we’re in ‘migration mode’).

A couple of updates are required:

I update the action to take a parameter:

public void ViewMissionSummary(int? id)

I made this nullable for now, as when the page initially is loaded, it will load all the summaries if id is null.  I can pass the id via the querystring as follows:

ForgottenWars/ViewMissionSummary.aspx?id=12

Presto!

Advertisements

One thought on “Notes on running MS MVC on IIS 6 and migrating to MS MVC from Webforms

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s