views:

1510

answers:

3

I'm experimenting with MVC, and my question is - where I had Page_Load logic in Master Pages with WebForms, where should it go in MVC? Here's the business case:

  • Different Host Headers should cause different Page Titles to be displayed on the site's (one) Master Page, therefore all pages. For example, if the host header is hello.mydomain.com, the page title should be "Hello World" for all pages/views, while goodbye.mydomain.com should be "Goodbye World" for all pages/views.
  • If the host header is different than anything I have in the list, regardless of where in the application, it should redirect to /Error/NoHostHeader.

Previously, I'd put this in the MasterPage Load() event, and it looks like in MVC, I could do this either in every controller (doesn't feel right to have to call this functionality in every controller), or somewhere in Global.asax (seems too... global?).

Edit: I have gotten this to work successfully using the Global.asax method combined with a Controller for actually processing the data. Only problem at this point is, all of the host header information is in a database. I would normally store the "tenant" information if you will in a Session variable and only do a DB call when it's not there; is there a better way to do this?

+3  A: 

You could create a base controller that supplied the correct ViewData to your MVC Master Page View, then derive each of your actual controllers from that one. If you put the logic into the ActionExecuting method, you should be able to generate an exception or redirect to an error page if necessary.

tvanfosson
+3  A: 

You are thinking too "WebForms" and not enough MVC. A master page is just a wrapper of your view, and it should only contain layout html. You can send stuff to your master, but it's a one way road and you should strive for agnostic views. Bottom line: forget about the events that WebForms had as they aren't going to be used here.

Since you are dealing with Host headers I suppose you could put it in the Global.asax...great now I'm confused :P

Stolen code from http://forums.asp.net/t/1226272.aspx

protected void Application_BeginRequest(object sender, EventArgs e)
        {
            string host = string.Empty;

            if (this.Request.ServerVariables["HTTP_HOST"] == this.Request.Url.DnsSafeHost)
            {
                host = this.Request.Url.DnsSafeHost;
            }
            else
            {
                Regex regex = new Regex("http://[^/]*.host/([^/]*)(/.*)");
                Match match = regex.Match(this.Request.Url.AbsoluteUri);

                if (match.Success)
                {
                    host = match.Groups[1].Value;
                    Context.RewritePath(match.Groups[2].Value);
                }
            }

            // Match the host with the portal in the database
            ...
        }
rball
So, combining answers, does doing this in Global.asax and assigning relevant information in the Session, and then accessing those Session objects through a base controller sound reasonable?
routeNpingme
+8  A: 

There is no 1:1 equivalent in MVC for a reason, let's just recapitulate how to think about it the MVC way:

Model: "Pages of this site are always requested in a certain context, let's call it the tenant (or user, topic or whatever your sub domains represent). The domain model has a property representing the tenant of the current request."

View: "Render the page title depending on the tenant set in the model."

Controller: "Set the tenant in the model depending on the host header".

Keep in mind that what we want to avoid is mixing controller, view and business logic. Having controller logic in more then one place or a place, that is not called "controller" is not a problem, as long as it remains separated.

And now the good thing: You could do this "MVC style" even with Web Forms, and the solution still works with ASP.NET MVC!

You still have the request lifecycle (not the page lifecycle), so you could implement a custom HttpModule that contains this part of the controller logic for all requests. It handles the BeginRequest event, checks for the host header, and stores the tenant to something like HttpContext.Current.Items["tenant"]. (Of course you could have a static, typed wrapper for this dictionary entry.)

Then all your model objects (or a model base class, or whatever is appropriate for your solution) can access the HttpContext to provide access to this information like this:

public string Tenant
{
    get { return HttpContext.Current.Items["tenant"]; }
}

Advantages:

  • You have separated cause (host header) and effect (rendering page title), improving maintainability and testability
  • Therefore you could easily add additional behavior to your domain model based on this state, like loading content from the database depending on the current tenant.
  • You could easily make more parts of the view depend on the tenant, like CSS file you include, a logo image etc.
  • You could later change the controller logic to set the tenant in the model not only based on the sub domain, but maybe based on a cookie, a referrer, search term, user agent's language, or whatever you can think about, without modifying any of your code depending on the model.

Update re your edit: I don't like the idea of holding the state in the session, especially if your session cookie might apply not only to each sub domain, but to all domains. In this case you might serve inconsistent content if users visted another sub domain before. Probably the information in the database that is mapping host headers to tenants won't change very often, so you can cache it and don't need a database lookup for every request.

markus