1

I’ve got an ASP.net MVC (5.2) site that runs using several subdomains, where the name of the subdomain is the name of a client in my database. Basically what I want to do is use the subdomain as a variable within my action methods to allow me to get the correct data from my database.

I did something similar a few years back, but it’s messy and not intuitive, so was wondering if there’s a better way to do it than I was using before. Here’s what I did before:

protected override void OnActionExecuting(ActionExecutingContext filterContext) {
    Session["subdomain"] = GetSubDomain(Request.Url);
}

private static string GetSubDomain(Uri url) {
    string host = url.Host;
    if (host.Split('.').Length > 1) {
        int index = host.IndexOf(".");
        string subdomain = host.Substring(0, index);
        if (subdomain != "www") {
            return subdomain;
        }
    }
    return null;
}

Which basically assigned a key to the session variable if the subdomain was anything other than "www", but I’m really not happy with this way of doing it as it relies on me knowing that the session might contain this magic value!

Ideally I’d like to be able to create an attribute that I can decorate my classes/methods with that would extract the subdomain and then allow me to include a "subdomain" parameter in my action method that would contain the value extracted by the attribute. Is that even possible?

If that can’t be done, is there a better way of doing what I’m doing now without having to rely on the session?

Thanks,

Dylan

Dylan Parry
  • 3,373
  • 6
  • 26
  • 38
  • Surely the client would need to login? Wouldn't it just be easier to load in the name at that point? – James Nov 17 '14 at 12:48
  • I realised immediately after posting this that I could create a private variable within the controller, and have the OnACtionExecuting method write to that instead of the session, but that still means having to include the logic in every page, so I’m still after a cleaner way of doing it that doesn’t make me repeat code. – Dylan Parry Nov 17 '14 at 12:49
  • I think the `Session` is the correct place for the value to be stored, however, I think your idea of pulling it out from the URL *per request* is wrong. Like I said, a client must have to login at somepoint therefore there *must* be an association there with the client and the name - after a successful login I would just set the name in the `Session`. – James Nov 17 '14 at 12:51
  • No, this is for public facing sites based on the client’s data, so there’s no logging in required. Basically each client will have different data in the database, and the pages presented on the public facing site will need to use different data depending on the subdomain that is being used. I’ve basically created one site that serves several different sites from the same code base and the subdomain is the only thing used to differentiate between them. – Dylan Parry Nov 17 '14 at 12:52
  • Ah ok (similar to stackexchange :)). – James Nov 17 '14 at 12:57
  • Yes. It’s me either being very lazy, or super efficient. Means I can create a new site just by adding another client to my database—no setting up new subdomains as IIS is set up for wildcards :) – Dylan Parry Nov 17 '14 at 13:01
  • Definitely super efficient - there is no such word as lazy in a developers vocabulary ;) – James Nov 17 '14 at 13:05
  • possible duplicate of [ASP.NET MVC Pass object from Custom Action Filter to Action](http://stackoverflow.com/questions/1809042/asp-net-mvc-pass-object-from-custom-action-filter-to-action) – Dylan Parry Nov 17 '14 at 13:13

3 Answers3

0

You could set the name in the Session on the Session_Start event in the global.asax, this means it would only happen one time and would persist for the duration of the users' session

public void Session_Start(object sender, EventArgs e)
{
    Session["subdomain"] = GetSubDomain(Request.Url);
}
James
  • 80,725
  • 18
  • 167
  • 237
  • That’s a possibility. If the user goes to client1.example.com and then visits client2.example.com, that would start a new session, wouldn’t it? It’s actually pretty unlikely that a user would do that due to the nature of the sites, but need to be sure for edge cases! – Dylan Parry Nov 17 '14 at 12:59
  • @DylanParry I believe so, at least if you are using cookie based sessions (worth testing it to be sure). – James Nov 17 '14 at 13:04
0

Looks like there’s a good way of doing what I’m after at:

ASP.NET MVC Pass object from Custom Action Filter to Action

It essentially uses the route data to pass a custom parameter to the action, and can also pass objects other than simple strings etc.

On the plus side it avoids using the session and relying on magic values, but on the downside it means processing the URL for every request, which probably isn’t a good idea if a database is involved.

Community
  • 1
  • 1
Dylan Parry
  • 3,373
  • 6
  • 26
  • 38
  • I'm unsure as to why you think the `Session` is the wrong solution here? The `Session` object was designed for *exactly* this purpose - storing user-related data for the duration of the users visit. I would understand if memory allocation was a concern but we are talking about a string... – James Nov 17 '14 at 13:15
  • It’s more because it relies on me *knowing* that there’s a magic value in the session that was set somewhere higher up in the code, which has always felt like a hack to me. If anyone else takes over the code then it might not be immediately obvious where this value is coming from. – Dylan Parry Nov 17 '14 at 13:24
  • I'd consider that a bit of a non-issue myself, however, if you prefer not to use the `Session` but want to avoid extracting the sub-domain every time then I'd recommend [caching](http://msdn.microsoft.com/en-us/library/6hbbsfk6(v=vs.100).aspx) it. That way you would check the cache first and if it's not there extract it, if the answser you have linked to is the approach you are going to take I can close this as a duplicate. – James Nov 17 '14 at 13:30
0

Your right this doesn't need to be stored in Session and IMHO shouldn't be, I would refactor this out into its own class and use HttpContext.Current.

public interface ISubDomainProvider
{
    string SubDomain { get; set; }
}


public class SubDomainProvider : ISubDomainProvider
{
    public SubDomainProvider()
    {
        string host = HttpContext.Current.Request.Url.Host; // not checked (off the top of my head
        if (host.Split('.').Length > 1) 
        {
            int index = host.IndexOf(".");
            string subdomain = host.Substring(0, index);
            if (subdomain != "www") 
            {
                SubDomain = subdomain;
            }
        }
    }

    public string SubDomain { get; set; }
}

You choose how to use it, if your using an IoC container it would just be a case of injecting this class into your controller via the constructor, I like this because it is easier to Mock and Unit Test. Of course you can still do this:

public class SomeController : Controller
{
    private readonly ISubDomainProvider _subDomainProvider;
    public SomeController()
    {
        _subDomainProvider = new SubDomainProvider();
    }
} 

You could even create you own abstract Controller Class:

public abstract class MyAbstractController : Controller
{
    public MyAbstractController()
    {
        SubDomain = new SubDomainProvider();
    }

    protected string SubDomain {get; set; }
}

public class SomeController : MyAbstractController
{
    public ActionResult SomeAction()
    {
        // access the subdomain by calling the base base.SubDomain 
    }
}
SimonGates
  • 5,961
  • 4
  • 40
  • 52
  • That looks like a good way of doing it, and appears to be more flexible than using an attribute. Thanks! – Dylan Parry Nov 17 '14 at 14:11
  • @FooBar why don't you feel it shouldn't be stored in the session? It's user specific, it needs to be available for the duration of the session and is only required once (after the session starts). FYI having an external dependency like `HttpContext` buried in your `SubDomainProvider` isn't the best idea. A better approach would be to have a method like `GetSubDomain(string url)` and pass the request URL in. – James Nov 18 '14 at 12:24
  • @James Good Question! I'm of the opinion that this is request specific rather than user specific. It only needs to be available for the duration of the request, there is no need to persist between requests as the Request Url is available. Session Storage comes with overhead. So why use when it's not necessary? Session is also harder to Mock than an Interface when it comes to Unit Tests. – SimonGates Nov 18 '14 at 13:48
  • @FooBar I agree about testability, however, unless the OP actually intends on testing it then it's a non-benefit. Personally I just find it odd that you would give yourself extract work *per request* when you know *per session* that the sub-domain isn't going to change, do it once at the beginning of the session and that's it - I mean you don't even need to use the `Session` you could store it in the `Cache` if needs must. Regardless, this is a solution all the same, I was just interested in why you felt it didn't belong in the session state. – James Nov 18 '14 at 13:59