Setting a master page programatically in ASP.NET MVC

OK, I’ve been out of the programming since last winter and I’m really rusty.

What I’m trying to do is set a master page programmatically. I googled it and found this:

public class ThemedViewEngine : WebFormViewEngine
{
	public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
	{
		if (string.IsNullOrEmpty(masterName))
		{
			masterName = "Site";
		}
		return base.FindView(controllerContext, viewName, masterName, useCache);
	}
}

but how do I implement it in an ActionResult?

My current ActionResult is:

public ActionResult Index(string pageID)
{
	if (pageID == null)
	{
		pageID = pageRepository.HomeOrLowest();
	}
	ViewData["currentLink"] = pageID.Replace(" ", "-");
	var p = pageRepository.ByStub(pageID);
	if (p == null) { return RedirectToAction("NotFound", "Error"); }

	return View(p);
}

I think I’ve got it:

public ActionResult Index(string pageID)
{
	if (pageID == null)
	{
		pageID = pageRepository.HomeOrLowest();
	}
	ViewData["currentLink"] = pageID.Replace(" ", "-");
	var p = pageRepository.ByStub(pageID);
	if (p == null) { return RedirectToAction("NotFound", "Error"); }//p = Page.Error404(); Response.StatusCode = 404; }
	var view = View(p);
	view.MasterName = "Admin";
	return view;

I’ve added this to my Page Repository:

private static Func<myContext, string, string> _masterPage =
	CompiledQuery.Compile((myContext context, string pageTitle) =>
		(from mP in context.Pages
		 where mP.pageTitle.Replace(" ", "-") == pageTitle
		 select mP).SingleOrDefault().masterPage);
public String MasterPage(string pageTitle)
{
	return _masterPage(context, pageTitle);
}

and changed my ActionResult to this:

public ActionResult Index(string pageID)
{
	if (pageID == null)
	{
		pageID = pageRepository.HomeOrLowest();
	}
	ViewData["currentLink"] = pageID.Replace(" ", "-");
	var p = pageRepository.ByStub(pageID);
	if (p == null) { return RedirectToAction("NotFound", "Error"); }
	var view = View(p);
	view.MasterName = pageRepository.MasterPage(pageID); 
	return view;
}

Now I can change the MasterPage using a field in the database. Exactly what I needed. :smiley:

I know that feeling, when you’re able to figure out your problems by yourself! :smiley:

It seems that a problem that I’ve been looking at for days, after I post about it here I figure it out a few seconds later. :smiley:

I was using an example from another site but it was overriding the page directive’s MasterPageFile attribute with Site.Master using this:

public class ThemedViewEngine : WebFormViewEngine
{
	public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
	{
		if (string.IsNullOrEmpty(masterName))
		{
			masterName = "Site";
		}
		return base.FindView(controllerContext, viewName, masterName, useCache);
	}
}

So I changed it to this:

public class ThemedViewEngine : WebFormViewEngine
{
	public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
	{
		return base.FindView(controllerContext, viewName, masterName, useCache);
	}
}

Now it uses the Page directive’s MasterPageFile unless one is specified in the action. Seems like a better plan.

What a cheap way to get over 1,000 posts. :wink: Congrats.

Is this where they double my salary? :lol:

Interesting :slight_smile:

Added to my site with a sample project: http://imaginekitty.com/716/choosing-a-master-page-programmatically-in-asp-net-mvc/

I’m not sure having the view engine do this is a good idea. It limits that view engine to a single application. What I would do is create a base controller class and override the View method. Then simply call the proper base.View method from there. The View method is already overloaded and one takes master as a param.

View(“viewName”, “masterName”, viewObj)

Hmm. I’m not sure I understand completely but it seems that it makes sense to use it if it’s built in.

Is overriding the View method even necessary? It works without it.

Why do I always go the long way to get nowhere? :smiley:

Here’s the theory Mark.

The theme engine is part of the mvc framework. It’s purpose is to build file paths and assemble the views. The only reason to derive a new view engine is when you need to change how the paths are constructed.

In your case, the path building process isn’t being changed. Only the MasterView parameter is. Restricting and/or formatting this parameter is dependant on the application, and should therefore be handled within the application.

Having the view engine handle this forces an upwards dependancy from engine to application.

Now, you are right, the View() override I mentioned works fine by itself. However, in order for all the actions on each controller you have to take advantage of it, you would have to process the pageId in every action before returning the ViewResult.

By creating a base class for your controllers, and housing a method there, you eliminate repeated code.

public class BaseController
{
private IPageRepository _pageRepository;
protected BaseController(IPageRepository pageRepository)
{
_pageRepository = pageRepository;
}
protected ActionResult ThemedView(string pageId, Object viewObject)
{
// format pageId
if (pageId == null) pageId = pageRepository.HomeOrLowest();
// build masterView
string masterView = _pageRepository.ByStub(pageId);
// test masterView and display error if needed
if (masterView == null) return RedirectToAction("NotFound", "Error");
// if the above displays error, this will never be reached
// it does not need to be above the prior line of code
// also, should look into a base viewmodel to house this
ViewData["currentLink"] = pageId.Replace(" ", "-");
// return the view
return View(View.ViewName, masterView, viewObject);
 
}
}
public class MyController : BaseController
{
public MyController(IPageRepository pageRepository) : base(pageRepository)
{ 
}
public ActionResult Index(string pageId)
{
// optionally pass in a view model where null is
return ThemedView(pageId, null);
}
}

This is untested code! Make sure to test it!

You might even consider making an application service for the formatting of the pageId, to separate the concerns of formatting and processing even more clearly. But that’s another post.

Ah, I see what you’re saying. There is only one action using the pageID so I think using it as is should be fine. Am I correct?

It sure is. =)

Though I would make two points here. First, if you find yourself adding more later, then abstracting it is in order. And second, controllers should be kept as slim as possible. There role is to control, or delegate, processing to other more specialized classes and simply return a result.

I get the impression that in your app, this isn’t really required, just keep it in mind.

This is going to be my new motto