5

I am developing an MVC app to serve multiple domains - each is a branch of a larger company.

A LocalBranch class stores details such as phone, address, email, location coordinates etc.

I want to create a single instance of this class per http request and have it available throughout the application - from within controllers, views, some helper classes and other code.

Is there a recommended way of doing this?

Right now I have it as a property on a BaseController and use ViewBagto pass it to views. But I would prefer it strongly typed in Views if possible.

I don't want to put it in an application variable, because we need to serve different values to different domains.

I would rather avoid a session variable if possible because we might scale up to use multiple servers in the future, and I've heard this doesn't play well with sessions.

Please feel free to update tags / title if you think there is a clearer way of expressing what I'm after. Thank you.

Martin Hansen Lennox
  • 2,837
  • 2
  • 23
  • 64
  • You can use a [Child Action](http://stackoverflow.com/questions/12530016/asp-net-mvc-child-action-in). Or save the `LocalBranch` in a Session variable and read it from the view. – Jasen Jan 27 '15 at 21:35
  • I want the data available in the application rather than displayed in a view. A session variable might work though. Thanks :) – Martin Hansen Lennox Jan 27 '15 at 22:49
  • Is the branch information just based off the domain or a logged in user? – Mike Jan 28 '15 at 17:38
  • It's just based off the domain, so it would be the same for all users of a single domain – Martin Hansen Lennox Jan 28 '15 at 18:01
  • Note that only if you use the "In Process" session provider is storing information like this in the session a problem. There are other providers that will work properly with web farms, and you can implement your own as well – Andrew Whitaker Feb 01 '15 at 15:13

3 Answers3

6

The best way to maintain your state in a web application per request is simply use the HttpContext class.

You need to store your state(LocalBranch) as an Item in the HttpContext:

HttpContext.Current.Items.Add("LocalBranch", GetLocalBranch());

You can fetch the Item all across your application like this:

LocalBranch branch = HttpContext.Current.Items["LocalBranch"] as LocalBranch;

The Items property is simply a key value Dictionary. The value is an object. You will have to check for nulls and this is really similar to the Session object you know.
The main difference is the scope. The HttpContext is a dot net object that has a lifetime of an http request.

Now using the HttpContext the way I've shown you is the simplest way to do it.

You can go two steps forward and use a framework called Unity and add a lifetime to your objects.
Unity does much more and the lifetime management is just one gem.

You can create a custom HttpContext lifetime that generates objects per request.
Something like this.

And them all you need to do is:

1.Register you LocalBranch class with the HttpContext lifetime.
2.Add a static Current property which will use the Unity container and resolve the correct instance of LocalBranch.
3.Use it something like this: LocalBranch.Current

BTW, you can use Unity's dependency injection for injecting objects into controllers and other modules. That's a better practice then just using the static Current property.

Community
  • 1
  • 1
Amir Popovich
  • 29,350
  • 9
  • 53
  • 99
  • Amir, I was really torn between both your answer and Juventus18, both were very useful to me in their own way. Ultimately I went for the other one because that was the approach I used in the end. But I feel really bad because yours was equally helpful and beautifully explained. Thank you. – Martin Hansen Lennox Feb 09 '15 at 00:52
5

You kind of have two questions here. The first is "How do I create a single instance of this class per HttpRequest?" The second is "How do I make this available to strongly typed views?"

The first has pretty much been answered by @amir-popovich to use dependency injection. However, FWIW I would probably use Ninject instead of Unity (just preference, really) and I would probably implement it differently. I would not use HttpContext, and simply build a service (which is instanciated using Ninject's OnePerHttpRequest Module, passing the domain as an argument to get the proper values).

Then, in order to add these LocalBranch values to your strongly typed View Model, you can first create a base view model which holds this type:

public class BaseViewModel
{
    public LocalBranch Branch {get;set;}
}

Then, make all of your current view models inherit this base type

public MyViewModel : BaseViewModel
{
    public string SomeValue {get;set;}
}

Then in your controller, it is easy enough to add these values from the service you created from the first step

public ActionResult SomeAction()
{
    var vm = new MyViewModel();
    vm.Branch = LocalBranchService.GetLocalBranchValues(); //Local Branch Service has been injected with Ninject

    //do other stuff

    return View(vm);
}

However, that gets pretty tedious to add that to each controller action, so you can instead create a Result Filter to add it for you:

public class LocalBranchResultFilter : FilterAttribute, IResultFilter 
{
    public void OnResultExecuting(ResultExecutingContext filterContext)
    {
        //This method gets invoked before the ActionResult is executed.
        filterContext.Controller.ViewData.Model.Branch = LocalBranchService.GetLocalBranchValues(); //Local Branch Service has been injected with Ninject
    } 
}

Now, you can just decorate your Controller and/or Actions with the filter (you could even set it in the Global Filters if you want).

solidau
  • 4,021
  • 3
  • 24
  • 45
  • +1 for avoiding HttpContext directly. It makes it a lot tedious to test even if you use HttpContextBase and inject that, as there is a long tree to mock and traverse. A service and a filter would cover all use cases – Jun Wei Lee Feb 03 '15 at 00:24
1

You can embed the child actions into your layout or a view. You can even cache its output so you don't keep re-querying the database.

controller

[ChildActionOnly]
[OutputCache(Duration=500, VaryByParam="*")]
public ActionResult Info()
{
    var localBranch = db.GetLocalBranch();
    return PartialView("_Info", localBranch);
}

_Info view

This bit will get inserted into your other views

@model LocalBranch
<span>@Model.address</span>
<span>@Model.phone</span>

Use in _Layout or other view

<p>lorem ipsum...</p>
@Html.Action("Info")
Jasen
  • 14,030
  • 3
  • 51
  • 68
  • 1
    Thanks for answer. Child actions are really handy. But what I'm after though is to be able to access the variable throughout the application, not to display the data in one place. – Martin Hansen Lennox Jan 27 '15 at 22:45