Vous êtes sur la page 1sur 61

Justin Jones 21 st February 2013 Version 1.1

Setting up an MVC4 Multi-Tenant Site

Contents

Introduction

2

Prerequisites

2

Requirements Overview

3

Design Structure (High Level)

4

Setting Up

5

Dynamic Layout Pages

15

Custom Attribute Setting

23

Custom Routing & Overrides

26

Partial Views and Strongly Typed Models

30

IFrame Hosting

32

Running Specific Client Scripts

33

MVC Areas, Area Views And Partial Views

37

MVC Area Custom Routing, Overrides and Client Specific Views

45

Appendix A Using MVC 4 Functionality to Override Area Controllers Only

57

Appendix B Overriding View Error

60

Resources

61

Introduction

Justin Jones 21 st February 2013 Version 1.1

In certain circumstances, you may need to distribute an MVC Application to different clients, who use shared functionality with specific customisations. A visual example of such a case is shown below.

Client 1 Specifics Client 1 UI Core Functionality Client 2 UI Client 2 Specifics
Client 1 Specifics
Client 1 UI
Core
Functionality
Client 2 UI
Client 2 Specifics

Having all core and client code in a single project distributed to all would be ideal. Unfortunately it is not always possible due to commercial and/or security issues. Without proper management, it also risks damaging the integrity of core functionality as client specific modifications are added. An example would be ‘Generic’ functionality with if(client == “client1”) statements.

One option would be to load specific parts dynamically, but this can introduce complexities you may wish to avoid (for example they are often harder to debug).

This demo provides a structure that would have the functionality shown in the diagram, while avoiding some of the pitfalls mentioned above.

It is not a complete business solution and is intentionally simplistic for the purpose of the demonstration. It uses only standard MVC functionality.

Prerequisites

To run through this tutorial, you will need Visual Studio 2012, or Visual Web Developer Express 2012. You will also need to have a basic knowledge of ASP.NET MVC.

Requirements Overview

Justin Jones 21 st February 2013 Version 1.1

The list below shows general requirements deemed necessary for a multi-tenant site.

 

Requirement

1

The system must allow for a generic version containing core functionality from which clients can have specific customisations.

2

Clients/Users must be able to have specific themes. The images must not be visible to other customers.

3

Clients must be able to set specific properties within their own project. For example, they may have certain flags (e.g. hide log out button) that they wish to set.

4

Clients must be able to change functions of default functionality (e.g. have a next button go to a different page, or have custom validation).

5

The solution must be able to support partial views.

6

The site must work in an iFrame.

7

The solution must enable clients to include specific JavaScript, such as a heartbeat, which can be reached across all pages.

8

The solution should be able to support Areas in MVC.

9

MVC Areas should be able to change/override default functionality (e.g. have a next button go to a different page, custom validation) and provide client specific views (for example, one client may have paid for custom UI controls and not want competitors to have them).

The rest of this document will detail how the proposed structure can meet these requirements.

Design Structure (High Level)

Justin Jones 21 st February 2013 Version 1.1

The structure shown in this demo will have a genericproject, to which all core functionality will be added.

It will then have a client project that hooks into this functionality and makes customisations as required. In theory, many different clients could be added with their own specific customisations.

The demo structure will rely only on standard .NET/MVC functionality.

The generic project will have all the core controllers and views. The client project will reference the generic project and map to the generic controllers. An order of preference will be provided in the MVC routings to use custom controllers/views where required.

Generic views will be copied to a folder in the client when the project is built and the client project will have view registrations to ensure the views are found.

While the approach is simple, it appears to meet most challenges thrown at it, and could easily be built upon.

Setting Up

Justin Jones 21 st February 2013 Version 1.1

Requirement

The system must allow for a generic version containing core functionality from which clients can have specific customisations.

To meet this requirement, we will need to have a ‘Generic’ project that is referenced by all other projects that need to use it. It will have routes that are registered with client projects in the Global.asax and build views to a temp file, so the views are readily available to any client project.

To do this:

1. Create an ASP.NET MVC4 project called Generic.

1. Create an ASP.NET MVC4 project called ‘ Generic ’ . 2. On the next screen,

2. On the next screen, ensure the project type is empty.

1. Create an ASP.NET MVC4 project called ‘ Generic ’ . 2. On the next screen,

Justin Jones 21 st February 2013 Version 1.1

3. You will now have a standard MVC4 Project setup as shown below.

will now have a standard MVC4 Project setup as shown below. 4. Set the project type

4. Set the project type to ‘Class Library’ (if it not already). To do this, right click on the Generic Project, select ‘Properties’ and change the Output type to ‘Class Library’ as shown below.

the Output type to ‘Class Library’ as shown below. 5. In the Generic project, right click

5. In the Generic project, right click the references and add a reference to System.Runtime.Remoting and click ‘Ok’.

In the Generic project, right click the references and add a reference to System.Runtime.Remoting and click
In the Generic project, right click the references and add a reference to System.Runtime.Remoting and click

Justin Jones 21 st February 2013 Version 1.1

6. At the top level of the Generic project, add a class file called EmbeddedResourceViewEngine.csand add the following code.

Note: the tmpreferences are critical, as this is where the views will be copied to. Note2: This structure will be replaced with something more detailed when we look at areas later.

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; using System.Web.Mvc;

namespace Generic

{

public class EmbeddedResourceViewEngine : RazorViewEngine

{

/// <summary> /// Set up the view locations (including the temp locations we will build to) /// and take the embedded views and put them in the tmp file. /// NOTE: This file should not need to be updated for new Areas to become part /// of the solution. /// </summary> public EmbeddedResourceViewEngine()

{ViewLocationFormats = new[] {

"~/Views/{1}/{0}.aspx",

"~/Views/{1}/{0}.ascx",

"~/Views/Shared/{0}.aspx",

"~/Views/Shared/{0}.ascx",

"~/Views/{1}/{0}.cshtml",

"~/Views/{1}/{0}.vbhtml",

"~/Views/Shared/{0}.cshtml",

"~/Views/Shared/{0}.vbhtml",

// Code above will search the client before generic.

// Improved Temp Locations // The embedded view will be copied to a tmp folder // using a similar structure to the View Folder

"~/tmp/Views/{1}/{0}.cshtml",

"~/tmp/Views/{1}/{0}.vbhtml",

};

PartialViewLocationFormats = new[]

{

"~/Views/Shared/{0}.cshtml", //Client first not tested in demo "~/tmp/Views/Shared/{0}.cshtml", //Improved method.

};

SaveAllViewsToTempLocation();

}

Justin Jones 21 st February 2013 Version 1.1

/// <summary> /// Get the embedded views within the project and save the info to the tmp /// location. /// </summary> private static void SaveAllViewsToTempLocation()

{

IEnumerable<string> resources = typeof(EmbeddedResourceViewEngine).Assembly.GetManifestResourceNames( ).Where(name => name.EndsWith(".cshtml")); foreach (string res in resources) { SaveViewToTempLocation(res); }

}

/// <summary> /// Save Resource To The Temp File. /// </summary>

/// <param name="res"></param> private static void SaveViewToTempLocation(string res)

{

 

// Get the file path to manipulate and the fileName for re-addition later. string[] resArray = res.Split('.');

// rebuild split to get the paths. string filePath = String.Join("/", resArray, 0, resArray.Count() - 2) + "/"; string fileName = String.Join(".", resArray, resArray.Count() - 2, 2);

// replace name of project, with temp file to save to. string rootPath = filePath.Replace("Generic", "~/tmp"); //Set in line with the server folder rootPath = HttpContext.Current.Server.MapPath(rootPath);

if (!Directory.Exists(rootPath)) Directory.CreateDirectory(rootPath);

//Save the file to the new location. string saveToLocation = rootPath + fileName; Stream resStream = typeof(EmbeddedResourceViewEngine).Assembly.GetManifestResourceStream(res);

System.Runtime.Remoting.MetadataServices.MetaData.SaveStreamToFile(resStream , saveToLocation);

}

}

}

7. Build the Generic project.

Justin Jones 21 st February 2013 Version 1.1

8. Create a new Client project (MVC4 Empty – I have called mine ‘Client1Basic’).

9. Once created, right click the References and add a reference to the shared (Generic) project as shown below.

). 9. Once created, right click the References and add a reference to the shared (Generic)
). 9. Once created, right click the References and add a reference to the shared (Generic)

Justin Jones 21 st February 2013 Version 1.1

10. In the client project, update the global.asax file with the code below.

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing;

namespace Client1Basic

{

 

// Note: For instructions on enabling IIS6 or IIS7 classic mode, // visit http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication

{

public static void RegisterGlobalFilters(GlobalFilterCollection filters)

{

filters.Add(new HandleErrorAttribute());

}

/// <summary> /// Standard Route registration. /// </summary>

/// <param name="routes"></param> public static void RegisterRoutes(RouteCollection routes)

{

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults

);

}

/// <summary> /// standard application start, with additional code to call generic. /// </summary> protected void Application_Start()

{

AreaRegistration.RegisterAllAreas();

RegisterGlobalFilters(GlobalFilters.Filters);

RegisterRoutes(RouteTable.Routes);

RegisterCustomViewEngines(ViewEngines.Engines);

}

/// <summary> /// This will add views from Generic /// </summary> /// <param name="viewEngines"></param> public static void RegisterCustomViewEngines(ViewEngineCollection viewEngines)

{

viewEngines.Clear(); //Remove this if there are complications viewEngines.Add(new Generic.EmbeddedResourceViewEngine());

}

}

}

Justin Jones 21 st February 2013 Version 1.1

11. In the Client project, add a new controller called ‘Home’.

Justin Jones 21 s t February 2013 Version 1.1 11. In the Client project, add a
Justin Jones 21 s t February 2013 Version 1.1 11. In the Client project, add a

Justin Jones 21 st February 2013 Version 1.1

12. In the Home controller, add an empty View called ‘Index’. Add some starter text such as ‘Hello World’.

Add some starter text such as ‘Hello World’ . 13. Right click the Client project and
Add some starter text such as ‘Hello World’ . 13. Right click the Client project and

13. Right click the Client project and select ‘Set As Startup Project’.

14. Run the project to make sure all builds ok so far.

Justin Jones 21 st February 2013 Version 1.1

15. In the Generic project, add a controller called ‘TestPageController’ and create a view for it. Leave the name as ‘Index’. Once created, add a bit of text to identify it (e.g. hello test world).

add a bit of text to identify it (e.g. hello test world). 16. Right click the

16. Right click the view, go to the properties window and SET THE VIEWS BUILD ACTION TO EMBEDDED RESOURCE! All views in the generic project must be embedded resources. If you run the project and get a View not found error it is likely the views build action property has not been set correctly.

the views build action property has not been set correctly. 17. In the Client project Home

17. In the Client project Home > Index view, create a link to the shared pages as shown below. Notice we do not need to reference ‘Generic’, the build all hooks up automatically.

@{ }
@{
}

ViewBag.Title = "Index";

<h2>Yatta!</h2>

<br />

@* @
@*
@

Link Text

, Action, Controller ->

Html.ActionLink("Test From Shared", "Index", "TestPage")

*@
*@

Justin Jones 21 st February 2013 Version 1.1

18. If you try to run the project now, you will get an error as shown below:

run the project now, you will get an error as shown below: This is because the

This is because the view can no longer see the base type from the web.config.

19. Update the Generic view with a reference to MVCWebPage a format you will need to follow for all generic views .e.g.

@inherits @{
@inherits
@{

System.Web.Mvc.WebViewPage

ViewBag.Title = "Index";

}
}

<h2>Yatta!</h2>

It will now work : )

} < h2 > Yatta! </ h2 > It will now work : ) You will

You will also be able to set a break point on the controller and debug everything as usual.

WARNING When you deploy the project, it is highly likely you will get an error when you load

the project. You need to give the APP POOL you are using (full) rights to the folder (and specifically

the tmp folder) of the published site.

Dynamic Layout Pages

Justin Jones 21 st February 2013 Version 1.1

Requirement

Clients/Users must be able to have specific themes. The images must not be visible to other customers.

NOTE: This could be used for custom themes, browsers or types (e.g. mobile).

NOTE 2: The work above will mean that all views must be Embedded Resources and will be built to a file called ‘tmp’. If you get errors about missing views, you have probably not set the file to ‘Embedded Resource’. In the ‘Client’ project, if you view hidden files, you will be able to see the tmp folder and the built generic views inside.

1. In Generic > Views, add a folder called ‘Shared’ (if it doesn’t exist already).

2. In the shared folder, create a layout page. To do this right click Shared > Add new Item > MVC 4 Layout Page. When you name the layout page (and in fact anything in the ‘Shared’ folder), the convention is to use an underscore before the name e.g. ‘_GenericLayoutPage.cshtml’.

to use an underscore before the name e.g. ‘_GenericLayoutPage.cshtml’ . 3. Set the page as an

3. Set the page as an Embedded Resource.

Justin Jones 21 st February 2013 Version 1.1

4. Due to the embedded resource setup, the associations are broken, so you will need to add an ‘inherits’ statement at the top. An example is shown below.

@inherits

System.Web.Mvc.WebViewPage

<!DOCTYPE html>

<html> <head> <meta name="viewport" content="width=device-width" />

<title>

@
@

ViewBag.Title</title>

</head> <body> <div> I am in generic layout. </div> <div>

@ RenderBody() </div>
@
RenderBody()
</div>

</body>

</html>

5. The LayoutPage will be called from a file called ‘_ViewStart’. ViewStart would normally contain a static reference to the layout page, but we are going to create a dynamic lookup. To do this we will need an HTML Helper. This can be extended/made classier in the future.

In the Generic project, add a folder called Helpers, then a class file within that called ‘LayoutHelper’. Add the code as show below. This will be used to do a custom lookup on the layout and can be extended/altered later.

using System; using System.Collections.Generic; using System.Linq; using System.Web;

namespace Generic.Helpers

{

 

public class LayoutHelper

{

 

public static string GetLayout()

{

// Default to the generic layout // Note the output will be in the tmp folder when published. string layoutName = "~/tmp/Views/Shared/_GenericLayoutPage.cshtml";

return layoutName;

}

 

}

}

Justin Jones 21 st February 2013 Version 1.1

The current file structure will look something like the screenshot below.

Jones 21 s t February 2013 Version 1.1 The current file structure will look something like

Justin Jones 21 st February 2013 Version 1.1

6. We now need to create the _ViewStart page to call the layout. Right Click Views Folder > Add New Item, Select MVC 4 View Page with Layout (Razor).

Add New Item, Select MVC 4 View Page with Layout (Razor). 7. On the next screen

7. On the next screen of the Wizard, select _GenericLayoutPage, though we will overwrite that soon.

_GenericLayoutPage, though we will overwrite that soon. 8. Set the _ViewS tart file to ‘ Embedded

8. Set the _ViewStart file to ‘Embedded Resource’.

9. On the file created, set it to inherit from

@inherits

Justin Jones 21 st February 2013 Version 1.1

System.Web.WebPages.StartPage, add a

using statement for the helper and use the Generic helper to select the layout dynamically as shown below.

@inherits @ @{
@inherits
@
@{

System.Web.WebPages.StartPage

using Generic.Helpers

Layout =LayoutHelper.GetLayout();

}
}

10. Run the project. When you call the page in Generic now, it will call the generic layout.

.GetLayout(); } 10. Run the project. When you call the page in Generic now, it will

Justin Jones 21 st February 2013 Version 1.1

11. Now we will update the project to be able to switch to a client layout page.

12. In the Clientproject, add a folder - Views > Shared. In this, add another MVC4 Layout Page (Right click Shared Folder > Add > New Item > MVC4 Layout Page (Razor). Call it ‘_ClientLayoutPage’. Client Views/Layout Pages must NOT be set as an Embedded Resource and will not need an inheritsstatement.

Client Views/Layout Pages must NOT be set as an Embedded Resource and will not need an

Justin Jones 21 st February 2013 Version 1.1

13. Add a bit of code so we can differentiate it from the Generic. An example is shown below.

it from the Generic. An example is shown below. 14. Back in the Generic LayoutHelper file,

14. Back in the Generic LayoutHelper file, add some code that will find the client file instead of the generic.

using System; using System.Collections.Generic; using System.Linq; using System.Web;

namespace Generic.Helpers

{

 

public class LayoutHelper

{

public static string GetLayout()

{

 

// Default to the generic layout // Note the output will be in the tmp folder when published. string layoutName = "~/tmp/Views/Shared/_GenericLayoutPage.cshtml";

// If condition is met, use a different layout. if (1 == 1) // NOTE: Condition will be met.

{

layoutName = "~/Views/Shared/_ClientLayoutPage.cshtml";

}

return layoutName;

 

}

}

}

Justin Jones 21 st February 2013 Version 1.1

15. Now run the project again, it will find the client layout is used instead of generic.

it will find the client layout is used instead of generic. 16. From the different layout

16. From the different layout pages, different CSS, Javascript and image files can be referenced. It would also allow you to package custom images specific to certain clients.

<link href="

<link href="

<script src="

href =" < link href =" < script src =" @Url.Content( "~/Content/ClientSpecific.css" ) "

@Url.Content("~/Content/ClientSpecific.css")" rel="stylesheet" type="text/css" /> @Url.Content("~/Content/Generic1.css")" rel="stylesheet" type="text/css" />

@Url.Content("~/Scripts/jquery-X.Y.Z.min.js")" type="text/javascript"></script>

Custom Attribute Setting

Justin Jones 21 st February 2013 Version 1.1

Requirement

Clients must be able to set specific properties within their own project. For example, they may have certain flags (e.g. hide log out button) that they wish to set.

To meet this requirement should be quite simple. The client project already contains a reference to the Generic project, so properties will be easily accessible.

This example will just use a Singleton and the ViewBag as a proof of concept. The Singleton is likely to be a good permanent solution, but the ViewBag could probably be improved to use a ViewModel/Strongly typed view in practice.

1. To create the singleton, go to the Generic project. Add a folder called ‘Session’. Add a class within that called SessionSingleton. Create the singleton to use the .NET session with just one property (a Boolean for proof of concept). You could have classes within the singleton we will just use a boolean for now. Copy and Paste in the code below.

using System; using System.Collections.Generic; using System.Linq; using System.Web;

namespace Generic.Session

{

public class SessionSingleton

{

 

public SessionSingleton()

{

}

public static SessionSingleton _instance = new SessionSingleton();

public static SessionSingleton Instance

{

get

{

 

return _instance;

 

}

}

public bool IsSetFromClient

{

get { return (System.Web.HttpContext.Current.Session["IsSetFromClient"] != null) ? (bool)System.Web.HttpContext.Current.Session["IsSetFromClient"] : false; } set { System.Web.HttpContext.Current.Session["IsSetFromClient"] = value; }

}

}

 

}

You now have an easily accessible strongly typed store.

Justin Jones 21 st February 2013 Version 1.1

2. In our Generic TestPageController (created earlier in the demo), assign the Boolean property to a ViewBag property as shown below (viewbag is dynamic we have just created the Viewbag property on the fly). Note this could be improved with making a ViewModel or something like that. For now, we are happy to prove the concept.

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc;

namespace Generic.Controllers

{

public partial class TestPageController : Controller

{

// // GET: /TestPage/

public ActionResult Index()

{

ViewBag.IsSetFromClient = Generic.Session.SessionSingleton.Instance.IsSetFromClient;

}

}

return View();

}

3. In the Generic TestPage > Index.cshtml view, add a statement to make the output obvious as to whether the item has been set or not.

@inherits @{
@inherits
@{

System.Web.Mvc.WebViewPage

ViewBag.Title = "Index";

}
}

<h2>Yatta!</h2>

if (ViewBag.IsSetFromClient == true)

{

 

<p>I am set from the client : )</p>

}

else

{

 

<p>I am NOT SET from the client : (</p>

}

Justin Jones 21 st February 2013 Version 1.1

4. Run the client. The output should look like the example below.

the client. The output should look like the example below. 5. Now go to the client
the client. The output should look like the example below. 5. Now go to the client

5. Now go to the client project. In the HomeController (as we know this will be hit) add some code to set the property as shown below.

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc;

namespace Client1Basic.Controllers

{

public class HomeController : Controller

{

// // GET: /Home/

public ActionResult Index()

{

Generic.Session.SessionSingleton.Instance.IsSetFromClient = true; return View();

}

}

}

6. Run the project again. It should look like the example below.

= true ; return View(); } } } 6. Run the project again. It should look
= true ; return View(); } } } 6. Run the project again. It should look

Custom Routing & Overrides

Justin Jones 21 st February 2013 Version 1.1

Requirement

Clients must be able to change functions of default functionality (e.g. have a next button go to a different page, or have custom validation).

1. This requirement can be achieved via a mix of MVC routing and inheritance.

2. In the Generic project Views/TestPage/Index.cshtml, add a using statement at the top -

@
@

using System.Web.Mvc.Html

3. In the same file, add a simple form, that will post to an action. Example code below:

@inherits @ @{
@inherits
@
@{

System.Web.Mvc.WebViewPage

using System.Web.Mvc.Html

ViewBag.Title = "Index";

}
}

<h2>Yatta!</h2>

if (ViewBag.IsSetFromClient == true)

{

 

<p>I am set from the client : )</p>

}

else

{

 

<p>I am NOT SET from the client : (</p>

}

<hr />

<!-- The HTML helper needs the using reference above.--> <!-- This code will test overriding for client specific actions.-->

@using
@using

{

(Html.BeginForm("Submit", "TestPage"))

<input type="submit" name="btn_test" value="Test Click" />

}

Justin Jones 21 st February 2013 Version 1.1

4. Go the Generic project TestPage controller. Add an action called “Submit” and a virtual string method. We are going to override the virtual method later.

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc;

namespace Generic.Controllers

{

 

public partial class TestPageController : Controller

{

 

// // GET: /TestPage/

public ActionResult Index()

{

 

ViewBag.IsSetFromClient = Generic.Session.SessionSingleton.Instance.IsSetFromClient;

return View();

 

}

/// <summary> /// This is the method for trialling overrides /// </summary> /// <returns></returns> public ActionResult Submit()

{

 

string response = BreakHereIfYouHitMe(); return RedirectToAction("Index", "Home");

 

}

/// <summary>

/// NOTE: - this is virtual, as we are going to override it in the

 

///

client project.

 

/// </summary> /// <returns></returns> public virtual string BreakHereIfYouHitMe()

{

 

string test = "break here - i am generic."; return test;

 

}

 

}

}

5. Run the project. The method BreakHereIfYouHitMe() will be hit. You can add a break point to make sure if you wish.

Justin Jones 21 st February 2013 Version 1.1

6. In the Client project, create a controller with the same name as the one in the Generic project (i.e. TestPageController). Set it to inherit from the Generic TestPageController. Add an override for the BreakHereIfYouHitMe() method E.g.

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc;

namespace Client1Basic.Controllers

{

public partial class TestPageController :

Generic.Controllers.TestPageController

{

public override string BreakHereIfYouHitMe()

{

string test = "break here - i am specific to the client."; return test;

}

}

}

Justin Jones 21 st February 2013 Version 1.1

7. At this point, the class structure is correct, but MVC will not know the correct class to map to.

To sort the routings, go to the Client Global.asax, Add a route that picks up the namespace of the client. This MUST be above the default routing in the code. The way MVC works is that it will pass through the routes until it gets what it wants. In the case there are 2 items with the same name and no preference is given in the routing, the system will error.

/// <summary> /// Standard & New Route Registration. /// </summary>

/// <param name="routes"></param> public static void RegisterRoutes(RouteCollection routes)

{

routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // NEW routes.MapRoute( "Client", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional },// Parameter defaults

null, new string[] { "Client1Basic.Controllers" } //NOTE: namespace to check

); // STANDARD routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults

}

);

8. Put a breakpoint in the client ‘BreakHereIfYouHitMe()method and run the project. The override will now be hit. This could be used for custom navigation, validation etc.

Justin Jones 21 st February 2013 Version 1.1

Partial Views and Strongly Typed Models

Requirement

The solution must be able to support partial views.

This part of the solution will include partial views and also strongly typed models. It will not use .ascx user controls. The ‘files to find’ code in Generic > Global.asax of this document would need to be updated for .ascx to work. As everything should be moved to the new format anyway, ascx files will not be considered.

1. In the Generic Project, create a class called StringContainer in the Models folder. This is just a dummy file for a strongly typed model.

using System; using System.Collections.Generic; using System.Linq; using System.Web;

namespace Generic.Models

{

public class StringContainer

{

public string Detail { get; set; }

}

}

2. Build the project.

3. In Generic > Views > Shared, right click and add a new View named ‘_TestPartial’. Check the box ‘Create as Partial View’ and make it strongly typed to use the StringContainer class. The setup should look like the screenshot below.

View’ and make it strongly typed to use the StringContainer class. The setup should look like

4. Set the Partial View to ‘Embedded Resource’.

Justin Jones 21 st February 2013 Version 1.1

5. The top of the file will state an @model declaration. This must be replaced with the generic inherits statement that includes a strongly typed class (due to the embedded resource not being able to see the web.config as mentioned previously). E.g.

@inherits

System.Web.Mvc.WebViewPage<Generic.Models.StringContainer>

NOTE: the inherit is WebViewPage NOT ViewUserControl!!!!!!

6. Add something random to the file, so we know it has been created.

@inherits

System.Web.Mvc.WebViewPage<Generic.Models.StringContainer>

<p>This is all i have to say:

@
@

Model.Detail</p>

7. In the Generic Project > Views > TestPage > Index.cshtml, add an instance of the partial view, with some dummy data. E.g.

<hr />

var model1 = new Generic.Models.StringContainer() { Detail = "Woo Hoo!" }; Html.RenderPartial("_TestPartial", model1);

8. Run the project. The output should look as shown in the screenshot below.

"_TestPartial" , model1); 8. Run the project. The output should look as shown in the screenshot

IFrame Hosting

Justin Jones 21 st February 2013 Version 1.1

Requirement

The site must work in an iFrame.

This is just a check to ensure the solution works as expected. Further checks would be required during development.

To test the solution in an iFrame.

1. Run the project on localhost and copy the URL.

3. Copy in the localhost address into the iframe src and test the site.

4. The site works as expected.

). 3. Copy in the localhost address into the iframe src and test the site. 4.

Running Specific Client Scripts

Justin Jones 21 st February 2013 Version 1.1

Requirement

The solution must enable clients to include specific JavaScript, such as a heartbeat, which can be reached across all pages.

Obviously the super simple solution for this would just be for the client to have a specific layout page and include a script in that. The problem with this is that a whole new layout may be required for just one script addition. A simple strategy has been outlined below.

1. We need to use the Generic layout for this demo, so change the LayoutHelper.cs in Generic to hit the generic layout page as shown below. Here, I have just changed the condition to something that cannot be met.

to hit the generic layout page as shown below. Here, I have just changed the condition
to hit the generic layout page as shown below. Here, I have just changed the condition

Justin Jones 21 st February 2013 Version 1.1

2. Now we need something that will dynamically pick up scripts from a project. In the Generic Helpers folder, create a class called ClientJavascriptHelper.cs. In it, copy and paste the following code.

I have commented the code to explain what is going on. Basically, we are going to look for a folder called ‘Javascript’ at the top level of the built project, find all the files in it and update the UI to reference them.

using System; using System.IO; using System.Linq; using System.Text; using System.Web;

namespace Generic.Helpers

{

 

public class ClientJavascriptHelper

{

/// <summary> /// This helper method will search for Javascript in a loaded project and /// register the scripts. /// This has not been implemented as an htmlHelper extension, as info on /// the web shows it will /// run a lot quicker. /// </summary> /// <returns></returns> public static HtmlString LoadClientJavascript()

{

 

StringBuilder clientScriptsBuilder = new StringBuilder();

// All client specific files must be placed in a folder called // "Javascript" (we can change this if we //want) DirectoryInfo directory = new DirectoryInfo(HttpContext.Current.Server.MapPath(@"~\Javascript"));

// If there is no folder or no files in the folder, dont do anything if (directory == null || directory.GetFiles() == null) return new HtmlString(clientScriptsBuilder.ToString());

// Get all the files in the folder var files = directory.GetFiles().ToList();

// Loop through the files in the folder. Register each one as a script // in the page. foreach (var file in files)

{

 

string script = @"/Javascript/" + file; string jswrap = String.Format("<script type=\"text/javascript\" src=\"" + script + "\"></script>"); clientScriptsBuilder.AppendLine(jswrap);

 

}

// return the code as HTML (otherwise the Html.Encode will just output // it as text in the UI) return new HtmlString(clientScriptsBuilder.ToString());

 

}

}

}

Justin Jones 21 st February 2013 Version 1.1

3. We now need the Generic Layout to call the method. Access Generic > Views > Shared > GenericLayoutPage.cshtml. You will need to add a using statement at the top for the helper

class -

have followed standard convention and included it in the head tag as shown below.

@
@

using Generic.Helpers. Then call the helper method in the appropriate place. I

@inherits @
@inherits
@

System.Web.Mvc.WebViewPage

using Generic.Helpers

<!DOCTYPE html>

<html> <head> <meta name="viewport" content="width=device-width" />

<title> @ ViewBag.Title</title> @ ClientJavascriptHelper.LoadClientJavascript()
<title>
@
ViewBag.Title</title>
@
ClientJavascriptHelper.LoadClientJavascript()

</head> <body> <div> I am in generic layout. </div> <div>

@ RenderBody() </div>
@
RenderBody()
</div>

</body>

</html>

The Generic project is all ready to accept client specific Javascript files.

4. Run the Client project. When you navigate to TestPage, it should look normal and if you view the source code, you will see it looks as you would expect.

5. We will now set up a client with scripts to be dynamically included. Access the client project and add a new folder called ‘Javascript’ at the top level. Within that, add two Javascript files (Javascript Folder > Right Click > Add > New Item > Find Javascript file > Enter the name )as shown below.

Find Javascript file > Enter the name )as shown below. 6. Open up the first script

6. Open up the first script file and add something simple like:

window.onload = function () { alert("Hello from the client injected script!"); };

Justin Jones 21 st February 2013 Version 1.1

7. Open the second script and add something that uses a different handler e.g.

window.onmousedown = function () { alert("Hello again from the client injected script!"); };

8. Run the project from client 1. When you navigate to the page that uses the Generic layout, the first message will appear.

that uses the Generic layout, the first message will appear. 9. Click OK on the message,

9. Click OK on the message, and then click the screen again. The second message will appear.

then click the screen again. The second message will appear. 10. View the source and you

10. View the source and you will see the scripts have been inserted in the head tag.

Justin Jones 21 st February 2013 Version 1.1

MVC Areas, Area Views And Partial Views

Requirement

The solution should be able to support Areas in MVC.

This example will now go through how a Generic MVC area could easily be used within the site structure. The finalised solution shown below requires little configuration to start, with everything happening automatically after the initial setup.

To set the project up for MVC Areas:

1. In the Client Project, ZERO updates are required. The work has already been done when we Registered the custom view engines as shown in the screenshot below.

required. The work has already been done when we Registered the custom view engines as shown

Justin Jones 21 st February 2013 Version 1.1

2. In the Generic project, we need to update the EmbeddedResourceViewEngine to handle Area Views and Partial Area Views. The entire code for the file has been included below and is commented with details of what is happening.

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; using System.Web.Mvc;

namespace Generic

{

public class EmbeddedResourceViewEngine : RazorViewEngine

{

/// <summary> /// Set up the view locations (including the temp locations we will build to)

/// and take the embedded views and put them in the tmp file. /// NOTE: This file should not need to be updated in for new Areas to become part of /// the solution. /// </summary> public EmbeddedResourceViewEngine()

{

 

ViewLocationFormats = new[] {

"~/Views/{1}/{0}.aspx",

"~/Views/{1}/{0}.ascx",

"~/Views/Shared/{0}.aspx",

"~/Views/Shared/{0}.ascx",

"~/Views/{1}/{0}.cshtml",

"~/Views/{1}/{0}.vbhtml",

"~/Views/Shared/{0}.cshtml",

"~/Views/Shared/{0}.vbhtml",

// Improved Temp Locations // The embedded view will be copied to a temp folder // using a similar structure to the View Folder

"~/tmp/Views/{1}/{0}.cshtml",

"~/tmp/Views/{1}/{0}.vbhtml",

};

PartialViewLocationFormats = new[]

{

"~/Views/Shared/{0}.cshtml", // Client first- not fully tested in this demo "~/tmp/Views/Shared/{0}.cshtml", //Improved method. - Get generic if no client

};

// HANDLES ALL AREAS - Generically! AreaViewLocationFormats = new[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", //client Specific Tested in this Demo! "~/tmp/Areas/{2}/Views/{1}/{0}.cshtml", //Pick up generic if no client

};

AreaPartialViewLocationFormats = new[]

{

"~/Areas/{2}/Views/Shared/{0}.cshtml", //Client Specific not used in demo "~/tmp/Areas/{2}/Views/Shared/{0}.cshtml" //Pick up generic if no client

};

// Save ALL views to the tmp file location. SaveAllViewsToTempLocation();

}

Justin Jones 21 st February 2013 Version 1.1

/// <summary> /// Get the embedded views within the project and save the info to the tmp location. /// </summary> private static void SaveAllViewsToTempLocation()

{

IEnumerable<string> resources = typeof(EmbeddedResourceViewEngine).Assembly.GetManifestResourceNames().Where(name => name.EndsWith(".cshtml")); foreach (string res in resources) { SaveViewToTempLocation(res); }

}

/// <summary>

/// Save Resource To The Temp File. /// res will enter looking like -> Generic.Areas.TestArea.Views.Home.AreaView.cshtml /// (or something simpler), /// we need to change that to look like /// ~/tmp/Areas/TestArea/Views/Home/AreaView.cshtml /// and save it to the temp location. /// </summary> /// <param name="res"></param> private static void SaveViewToTempLocation(string res)

{

//Get the file path to manipulate and the fileName for re-addition later. string[] resArray = res.Split('.');

string filePath = String.Join("/", resArray, 0, resArray.Count() - 2) + "/"; string fileName = String.Join(".", resArray, resArray.Count() - 2, 2);

// replace name of project, with temp file to save to. string rootPath = filePath.Replace("Generic", "~/tmp");

//Set in line with the server folder rootPath = HttpContext.Current.Server.MapPath(rootPath);

if (!Directory.Exists(rootPath)) Directory.CreateDirectory(rootPath);

//Save the file to the new location. string saveToLocation = rootPath + fileName; Stream resStream = typeof(EmbeddedResourceViewEngine).Assembly.GetManifestResourceStream(res);

System.Runtime.Remoting.MetadataServices.MetaData.SaveStreamToFile(resStream,

saveToLocation);

}

}

}

WARNING: Following this update, you may later get an error relating to System.Web.Optimization. If so, see Appendix B Overriding View Errorfor a simple resolution.

Justin Jones 21 st February 2013 Version 1.1

3. The Generic file is now ready to handle any type of Area so lets give it a bash. Firstly, delete the two client Javascript files we created earlier (this is just because they will be annoying otherwise). Rebuild and run the solution to make sure all is well.

Rebuild and run the solution to make sure all is well. 4. In the Generic project,

4. In the Generic project, right click on the project file and Add a new MVC Area (Add > Area…). Call it ‘TestArea’. The first file the designer will show you is TestAreaRegistration.cs. Note:

Due to the work you did in the EmbeddedResouceViewEngine, you will not need to alter this file.

you will not need to alter this file. 5. Add a new (empty) controller called ‘Home’

5. Add a new (empty) controller called ‘Home’ in the Area > TestArea > Controllers folder (Right Click > Add > Controller).

6. Add an empty View to the controller (make sure the ‘create strongly typed checkboxes are not checked.

the ‘create strongly typed checkboxes are not checked. Justin Jones 21 s t February 2013 Version
the ‘create strongly typed checkboxes are not checked. Justin Jones 21 s t February 2013 Version

Justin Jones 21 st February 2013 Version 1.1 and ‘partial’

7. Set the View to be an Embedded Resource!

8. Add

@inherits

System.Web.Mvc.WebViewPage statement as with the previous generic views.

Add a bit of text to identify it. The result will look like the example below.

@inherits @{
@inherits
@{

System.Web.Mvc.WebViewPage

ViewBag.Title = "Index";

}
}

<h2>I am in the area view</h2>

Justin Jones 21 st February 2013 Version 1.1

9. Now go to the Client > Home > Index view. Add a link to the Generic Area as shown below. The main part is the ActionLink to the Area. Note the call is a standard MVC function.

@
@

Html.ActionLink("Visit Area", "Index", "Home", new { area = "TestArea" },

null)

, new { area = "TestArea" }, null ) 10. Run the project. Click the link

10. Run the project. Click the link to the area from the Home Page. You should now be able to see the page you have just created.

should now be able to see the page you have just created. NOTE: In Windows Explorer,

NOTE: In Windows Explorer, you will see the content has been copied into the tmp file of the Client Project with the appropriate structure.

{TopLevelFolder}\Client1Basic\tmp\Areas\TestArea\Views\Home\Index.cshtml

11. We will now test a partial view.

Justin Jones 21 st February 2013 Version 1.1

12. In Generic > TestArea > Views > Shared, create a partial view called ‘_TestPartialArea.cshtml’ (Right Click > Add > View).

.cshtml’ (Right Click > Add > View). 13. Set it to be an ‘ Embedded Resource

13. Set it to be an Embedded Resourceand set the inherits statement (shown below) . Put some random identifying text in.

@inherits

System.Web.Mvc.WebViewPage

I am in the area and I'm partial

(screenshot)

random identifying text in. @inherits System.Web.Mvc. WebViewPage I am in the area and I'm partial (screenshot)

Justin Jones 21 st February 2013 Version 1.1

14. Go to the Areas > TestArea > Views > Home > Index.cshtml file. Add a using statement for

@
@

using System.Web.Mvc.Html. and a refrerence to the partial view. Note that because

there is no Model in this case, we are passing the Model through as null.

@inherits @ @{
@inherits
@
@{

System.Web.Mvc.WebViewPage

using System.Web.Mvc.Html

ViewBag.Title = "Index";

}
}

<h2>I am the Area View : )</h2>

@{
@{

Html.RenderPartial("_TestPartialArea", null);

}
}

15. Run the project. You should be able to see the partial view.

"_TestPartialArea" , null ); } 15. Run the project. You should be able to see the

Justin Jones 21 st February 2013 Version 1.1

MVC Area Custom Routing, Overrides and Client Specific Views

Requirement

MVC Areas should be able to change/override default functionality (e.g. have a next button go to a different page, custom validation) and provide client specific views (for example, one client may have paid for custom UI controls and not want competitors to have them).

Note: In MVC 4, Controllers do not actually need to be placed in specific folders (http://www.asp.net/whitepapers/mvc4-release-notes#_Toc303253820 ). We could utilise this to avoid creating whole new Areas for minor client overrides. An example is shown in Appendix A.

Note 2: Registration order of areas does not appear to be an issue in this demo, however Appendix A shows a way to force registration order of areas.

For Area Routing and Overrides:

1. In Generic > Areas > TestArea > Views > Home > Index.cshtml, add a simple form with a button so we can submit to a controller. This will allow us to have a function to override. An

example output is shown below, with the

@
@

using (Html.BeginFormbeing the bit of interest.

@inherits @ @{
@inherits
@
@{

System.Web.Mvc.WebViewPage

using System.Web.Mvc.Html

ViewBag.Title = "Index";

}
}

<h2>I am the Area View : )</h2>

@{ }
@{
}

Html.RenderPartial("_TestPartialArea", null);

<br />

<br />

@
@

using (Html.BeginForm("Submit", "Home", new{ Area = "TestArea" }))

{

<input type="submit" name="btn_test" value="Test Click" />

}

Justin Jones 21 st February 2013 Version 1.1

2. In the controller at Generic > Areas > TestArea > Controllers > HomeController.cs, add a submit function along with a virtual method we can override later.

namespace Generic.Areas.TestArea.Controllers

{

 

public class HomeController : Controller

{

// // GET: /TestArea/Home/

public virtual ActionResult Index()

{

return View();

}

/// <summary>

/// This is the method for trialling overrides /// </summary> /// <returns></returns> public ActionResult Submit()

{

string response = BreakHereIfYoureInTheArea(); return RedirectToAction("Index", "Home", new { Area="" });

}

/// <summary> /// Note - this is virtual, as we are going to override it in the client /// project. /// </summary> /// <returns></returns> public virtual string BreakHereIfYoureInTheArea()

{

string test = "break here - i am generic."; return test;

}

}

}

Justin Jones 21 st February 2013 Version 1.1

3. Run the project to ensure the Generic function is being hit as shown below.

21 s t February 2013 Version 1.1 3. Run the project to ensure the Generic function
21 s t February 2013 Version 1.1 3. Run the project to ensure the Generic function

Justin Jones 21 st February 2013 Version 1.1

4. We now need to head to the Client project to create the overriding controller. In it, we will override the BreakHereIfYoureInTheArea()’ method, a point at which custom functionality could be provided.

Note: If you will only ever override controllers, you could use the method in Appendix A. The continuing solution here will be far more flexible though.

5. In the client Project, add a new Area with the same name as in the ‘Generic’ project.

5. In the cl ient Project, add a new Area with the same name as in

Area Name: TestArea

5. In the cl ient Project, add a new Area with the same name as in

Justin Jones 21 st February 2013 Version 1.1

6. In the client ‘TestArea’ add a HomeController to mirror the one in Generic.

add a HomeController to mirror the one in Generic. 7. Set it to inherit from the

7. Set it to inherit from the Generic controller, so we can override methods within that controller. Comment out the Index() ActionResult. We are not going to work with this yet. The result will look like that shown below:

out the Index() ActionResult. We are not going to work with this yet. The result will

Justin Jones 21 st February 2013 Version 1.1

8. Put a break point in the overriding method and Start Debugging. Go to the ‘Visit Area’ link, then click the ‘Test Click’ button. Your breakpoint in the override controller will be hit.

‘Visit Area’ link, then click the ‘Test Click’ button. Your breakpoint in the override controller will
‘Visit Area’ link, then click the ‘Test Click’ button. Your breakpoint in the override controller will
‘Visit Area’ link, then click the ‘Test Click’ button. Your breakpoint in the override controller will

Justin Jones 21 st February 2013 Version 1.1

9. Now we have seen we can have custom overriding within controllers, we will create a custom view that will be used instead of the generic one. Note This view could use any layout it wants and could include partial views, but we are going to make it plain for simplicity.

Back in our EmbeddedResourceViewEngine (see ‘MVC Areas, Area Views And Partial Views- step 2),

we set the views to look in the client project before generic. This will make it easy to create an overriding view or partial view.

10. Although not necessary, if we wanted to override the ActionResult Index() itself, this could be done by making the Generic controller virtual and the client override it as shown in the screenshots below.

the client override it as shown in the screenshots below. NOTE: If you have two Index()
the client override it as shown in the screenshots below. NOTE: If you have two Index()

NOTE: If you have two Index() methods with no preference (virtual/override), MVC not know which to use and provide the following error:

“The current request for action on controller type is ambiguous between the following action methods) as it won’t know which to use.”

Justin Jones 21 st February 2013 Version 1.1

11. To create an overriding view, uncomment the Client’s Index() ActionResult (if it is still commented out) and add a View. This will NOT need to be an embedded resource.

add a View. This will NOT need to be an embedded resource. 12. Give it the

12. Give it the standard settings

be an embedded resource. 12. Give it the standard settings 13. In the view, add some

13. In the view, add some text so we can easily identify it.

be an embedded resource. 12. Give it the standard settings 13. In the view, add some

Justin Jones 21 st February 2013 Version 1.1

14. Comment out the Client’s ActionResult to use the one from Generic (you could keep it in to create a completely custom override, but for the purpose of this demo, comment it out).

override, but for the purpose of this demo, comment it out). 15. If you run the

15. If you run the project, and click on the ‘Visit Area’ link again, you will now be taken to the overriding view.

WARNING: If you get an error here (System.Web.Optimization), see Appendix B Overriding View Error for a simple resolution.

you get an error here ( System.Web.Optimization ), see Appendix B – Overriding View Error for
you get an error here ( System.Web.Optimization ), see Appendix B – Overriding View Error for

Justin Jones 21 st February 2013 Version 1.1

16. Here, the new view does not contain a submit button. For the sake of completeness, we will add it now. The button could submit to the generic controller, or an override within the client controller.

17. Add a submit button to your client override view as you had done in the generic view. The result will look like the screenshot below:

@{
@{

ViewBag.Title = "Index";

}
}

<h2>I am an overriding view!</h2> <br /> <br />

@
@

using (Html.BeginForm("Submit", "Home", new{ Area = "TestArea" }))

{

<input type="submit" name="btn_test" value="Test Click" />

}

Justin Jones 21 st February 2013 Version 1.1 18. Put a breakpoint in the Generic ‘Submit()’ ActionResult and run the project. When you go to ‘Visit Area’ and click the test button, you will be able to access the Generic controllers submit action.

be able to access the Generic controllers submit action. As you can see, the structure allows
be able to access the Generic controllers submit action. As you can see, the structure allows

As you can see, the structure allows you to mix and match between client and generic functionality as required.

*** END OF DEMO ***

Justin Jones 21 st February 2013 Version 1.1

Justin Jones 21 st February 2013 Version 1.1

Appendix A Using MVC 4 Functionality to Override Area Controllers Only

If you will never have overriding views, you could create your override structure in a neater way than creating a whole new area. It is highly unlikely, but this section would continue from part 4 of MVC Area Custom Routing and Overrides.

1. In the ‘Client’ project, add a folder called ‘Areas’, then under that a folder called ‘TestArea’, then a folder under that called ‘Controllers’ (i.e. matching the area structure in Generic) .In that, add a controller called HomeController.cs. The result should look like the structure below.

The result should look like the structure below. 2. In the client home controller file, set

2. In the client home controller file, set it to inherit from the Generic Area Home Controller and override the ‘BreakHereIfYoureInTheArea()’ method. The result should look like the code shown below.

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc;

namespace Client1Basic.Areas.TestArea.Controllers

{

 

public class HomeController : Generic.Areas.TestArea.Controllers.HomeController

{

/// <summary> /// This is just an override so we can break here and know we are hitting /// the override controller, /// not the generic one. /// </summary> /// <returns></returns> public override string BreakHereIfYoureInTheArea()

{

string test = "break here - i am sneaking into the area from the client.";

return test;

}

}

}

Justin Jones 21 st February 2013 Version 1.1

3. We now need to update the routing so the Client override controller will be hit instead of the Generic.

A bit of extra work is required here, as standard Area registration runs in an unpredictable

order when using this method.

The basic concept is that we are going to create a new AreaRegistrationContext for the Client Area (which will relate to the standard routing RouteTable.Routes), and then add our Client Area mapping to it (ahead of the standard Area registration). If we were to do this for another Area, we would need to create a new AreaRegistrationContext and another mapping.

In future, this sort of functionality could use reflection to look up the Area/Namespace, or

more simply have a dictionary of AreaName/Namespaces and loop through creating the mappings.

4. In the ‘ClientGlobal.asax, add a new method called RegisterAreaOverrides() below RegisterRoutes(). The code is shown below.

/// <summary> /// This method will register areas in a custom manner, so we can ensure the order /// of registration is correct. /// This could be moved to a more generic method with a loop, but has been kept /// simple for ease of reading. /// </summary> public void RegisterAreaOverrides()

{

// Important - The area name in the registration context must be the name of // the area to override. // Important - A new context must be created for each different area. AreaRegistrationContext context = new AreaRegistrationContext("TestArea", RouteTable.Routes);

//Map the override route. context.MapRoute( "TestArea_override", "TestArea/{controller}/{action}/{id}",

//Just a unique key //Used 'TestArea/'

// as this is the area the routing will search for. new { action = "Index", controller="Home", id = UrlParameter.Optional },

null, new string[] { "Client1Basic.Areas.TestArea.Controllers" } //Namespace of the overriding controller (above)

}

);

Justin Jones 21 st February 2013 Version 1.1

5. We now need to ensure this is called before the standard area registration. To do this, call

‘RegisterAreaOverrides()’ above AreaRegistration.RegisterAllAreas() in

Application_Start() as shown in the example below.

/// <summary> /// standard application start, with additional code to call generic. /// </summary> protected void Application_Start()

{

 

// Register the overrides before the areas to ensure the routing is // correct! RegisterAreaOverrides();

// This standard function will still register all non-overridden areas, // but they will be the fallback, giving the overrides precedence. AreaRegistration.RegisterAllAreas();

RegisterGlobalFilters(GlobalFilters.Filters);

RegisterRoutes(RouteTable.Routes);

RegisterCustomViewEngines(ViewEngines.Engines);

}

6. Put a breakpoint in the overriding controller and test the Area Page. When you click the button, the override method will be hit.

in the overriding controller and test the Area Page. When you click the button, the override

Appendix B Overriding View Error

Justin Jones 21 st February 2013 Version 1.1

WARNING: If you have overriding views, you may get the following error:

Could not load file or assembly 'System.Web.Optimization, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.

If you do, in visual studio, go to Tools > Library Package Manager > Package Manager Console and enter the following command

PM> Install-Package Microsoft.Web.Optimization -Pre