61
Justin Jones 21 st February 2013 Version 1.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

Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

  • Upload
    others

  • View
    4

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.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

Page 2: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

2

Introduction

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 UI

Client 2 UI

Client 1 Specifics

Client 2 Specifics

CoreFunctionality

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.

Page 3: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

3

Requirements Overview

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.

Page 4: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

4

Design Structure (High Level)

The structure shown in this demo will have a ‘generic’ project, 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.

Page 5: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

5

Setting Up

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’.

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

Page 6: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

6

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

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.

5. In the Generic project, right click the references and add a reference to

System.Runtime.Remoting and click ‘Ok’.

Page 7: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

7

6. At the top level of the Generic project, add a class file called

‘EmbeddedResourceViewEngine.cs’ and add the following code.

Note: the ‘tmp’ references 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(); }

Page 8: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

8

/// <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.

Page 9: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

9

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.

Page 10: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

10

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()); } } }

Page 11: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

11

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

Page 12: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

12

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

‘Hello World’.

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

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

Page 13: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

13

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).

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.

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")

Page 14: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

14

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

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 System.Web.Mvc.WebViewPage @{ ViewBag.Title = "Index"; }

<h2>Yatta!</h2>

It will now work : )

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.

Page 15: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

15

Dynamic Layout Pages

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’.

3. Set the page as an Embedded Resource.

Page 16: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

16

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> </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; } } }

Page 17: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

17

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

Page 18: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

18

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).

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

soon.

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

Page 19: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

19

9. On the file created, set it to inherit from @inherits 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 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.

Page 20: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

20

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

12. In the ‘Client’ project, 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 ‘inherits’ statement.

Page 21: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

21

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

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; } } }

Page 22: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

22

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

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="@Url.Content("~/Content/ClientSpecific.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/Generic1.css")" rel="stylesheet" type="text/css" />

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

Page 23: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

23

Custom Attribute Setting

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.

Page 24: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

24

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 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> } }

Page 25: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

25

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

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.

Page 26: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

26

Custom Routing & Overrides

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 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 (Html.BeginForm("Submit", "TestPage")) { <input type="submit" name="btn_test" value="Test Click" />

}

Page 27: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

27

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.

Page 28: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

28

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; } } }

Page 29: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

29

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.

Page 30: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

30

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.

Page 31: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

31

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

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.

Page 32: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

32

IFrame Hosting

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.

2. Go to http://www.w3schools.com iframe try it yourself page (currently at

http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_iframe ).

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

4. The site works as expected.

Page 33: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

33

Running Specific Client Scripts

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.

Page 34: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

34

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, don’t 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()); } } }

Page 35: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

35

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 - @using Generic.Helpers. Then call the helper method in the appropriate place. I

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

@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() </head> <body> <div> I am in generic layout. </div> <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.

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

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

};

Page 36: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

36

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.

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

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

Page 37: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

37

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.

Page 38: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

38

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(); }

Page 39: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

39

/// <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 Error’ for a simple

resolution.

Page 40: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

40

3. The Generic file is now ready to handle any type of Area – so let’s 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.

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.

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

Click > Add > Controller).

Page 41: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

41

6. Add an empty View to the controller (make sure the ‘create strongly typed..’ and ‘partial’

checkboxes are not checked.

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 System.Web.Mvc.WebViewPage @{ ViewBag.Title = "Index"; }

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

Page 42: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

42

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)

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.

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’

Page 43: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

43

11. We will now test a partial view.

12. In Generic > TestArea > Views > Shared, create a partial view called ‘_TestPartialArea.cshtml’

(Right Click > Add > View).

13. Set it to be an ‘Embedded Resource’ and 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)

Page 44: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

44

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 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.

Page 45: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

45

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.BeginForm… being the bit of interest.

@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" /> }

Page 46: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

46

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; } } }

Page 47: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

47

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

Page 48: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

48

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.

Area Name: TestArea

Page 49: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

49

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

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:

Page 50: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

50

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.

Page 51: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

51

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.

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.”

Page 52: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

52

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.

12. Give it the standard settings

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

Page 53: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

53

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).

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.

Page 54: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

54

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" /> }

Page 55: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

55

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.

As you can see, the structure allows you to mix and match between client and generic

functionality as required.

Page 56: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

56

*** END OF DEMO ***

Page 57: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

57

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.

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; } } }

Page 58: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

58

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 ‘Client’ Global.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", //Just a unique key "TestArea/{controller}/{action}/{id}", //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) );

}

Page 59: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

59

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.

Page 60: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

60

Appendix B – Overriding View Error

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

More details can be found at http://stackoverflow.com/questions/11590822/getting-system-web-

optimization-to-work-in-a-razor-view-within-a-class-library

Page 61: Setting up an MVC4 Multi Tenant Sitedthomas-development.co.uk/.../05/Setting-up-an-MVC4... · 3. You will now have a standard MVC4 Project setup as shown below. 4. Set the project

Justin Jones 21st February 2013

Version 1.1

61

Resources

Some of the main resources used are listed below.

1. http://stackoverflow.com/questions/4800819/sharing-controllers-and-views-with-multiple-

web-applications/11629904#11629904

2. http://stackoverflow.com/questions/8127462/the-view-must-derive-from-webviewpage-or-

webviewpagetmodel

3. http://www.picnet.com.au/blogs/Guido/post/2009/08/12/Sharing-MVC-Views-Across-

Projects

4. http://www.asp.net/mvc/tutorials/controllers-and-routing/creating-custom-routes-cs

5. http://www.asp.net/mvc/tutorials/older-versions/views/creating-custom-html-helpers-cs

6. http://msdn.microsoft.com/en-us/vs11trainingcourse_aspnetmvc4.aspx

7. http://vimeo.com/9217399

8. http://oredev.org/2010/sessions/pluggable-web-applications-with-asp-net-mvc

9. http://lonetechie.com/2012/09/25/multi-tenant-architecture-with-asp-net-mvc-4/

10. http://www.asp.net/whitepapers/mvc4-release-notes#_Toc303253820

11. http://stackoverflow.com/questions/11590822/getting-system-web-optimization-to-work-

in-a-razor-view-within-a-class-library