views:

93

answers:

4

tldr> Once a customer has been selected, how can all other controllers execute their actions always in the 'context' of that customer without manually passing the ID around?

I'm trying to figure out what the 'right' way is to handle the situation where one entire controller (or more) are all dependent on an idea from a previous controller. For instance, let's say you were building some kind of customer management system. There'd be all kinds of customer functions, probably located on CustomerController. But then when you got into order management, you'd likely want to have an OrderController.

If you had only one OrderController, your methods would probably look like:

public ActionResult Edit(string id){...}

The id would be the id of the order, right? Where I get a little lost is when I need to get back to the customer. It's like the 'context' of all the actions taking place are within the customer. You could do this by always adding the customer id onto actions (URLs):

http://site.com/Orders/Edit/1234?customerId=abc

But this seems like it gets quite tedious to be grabbing that value and jamming it onto every action. There are options like Session, but that seems sloppy.

What's the right way to do this?

+4  A: 

You can reduce general session sloppiness by writing a wrapper around the session, which exposes strongly typed properties.

//Sloppy weak typing:
int userId = (int)HttpContext.Current.Session["UserId"];

//Strongly typed goodness
int userId = SessionWrapper.UserId;
Evil Pigeon
Yeah, when I was playing around with going that route, I'd baked a couple methods into the base controller to do exactly that. Makes it a little better for sure. Still, as soon as this goes on a cluster of 3 servers, I've got a nasty problem to deal with. Never had good luck with Sql session storage scaling.
Jim
I'm pretty sure this is a solved problem, there's a way you can store the session in SQL server so it can be persisted across a cluster. Perhaps this could be useful: http://support.microsoft.com/kb/317604
Evil Pigeon
Yeah, it exists, it just sucks under heavy load.
Jim
Sucks in what way? Is it too slow? Don't want to be limited to serialisable objects? Are there other any other bugs? I am interested in hearing the issues you expect.
Evil Pigeon
The big issue we've run into in the past is speed/contention. I'm talking about clusters of servers under heavy user load. Especially as session grows, it seemed to tax the DB server quite a bit. App Fabric session would be a much better solution (someone else mentioned already), but that's a whole other beast to tackle as well.
Jim
+1  A: 

Session is a place to store things like that.

You can also use a distributed cache (http://msdn.microsoft.com/library/cc645013.aspx) which scales well. Sample code from link above

// CacheFactory class provides methods to return cache objects
// Create instance of CacheFactory (reads appconfig)
DataCacheFactory fac = new DataCacheFactory();
// Get a named cache from the factory
DataCache catalog = fac.GetCache("catalogcache");
//-------------------------------------------------------
// Simple Get/Put
catalog.Put("toy-101", new Toy("thomas", .,.));
// From the same or a different client
Toy toyObj = (Toy)catalog.Get("toy-101");
// ------------------------------------------------------
// Region based Get/Put
catalog.CreateRegion("toyRegion", true);
// Both toy and toyparts are put in the same region
catalog.Put("toy-101", new Toy( .,.), "toyRegion");
catalog.Put("toypart-100", new ToyParts(…), "toyRegion");
Toy toyObj = (Toy)catalog.Get("toy-101", "toyRegion");
Raj Kaimal
A: 

Serializing session info into an encrypted session cookie is another solution here. The benefit is the ability to avoid consumption of server resources and accomplish the same goal:

    private void setSessionCookie() {
        HttpCookie ck = new HttpCookie(XConstants.X_SESSION_COOKIE_KEY) {
            Expires = DateTime.Now.AddMinutes(_sessionInfo.SessionTimeoutMinutes)
        };
        DateTime now = DateTime.UtcNow;
        _sessionInfo.SessionLastValidatedAt = now;
        ck.HttpOnly = true; // server-only cookie
        ck["LastCheck"] = now.ToString(XConstants.XDATEFORMAT);
        ck["Content"] = new Cipher().Encrypt(Serializer.Serialize(_sessionInfo).OuterXml,
            ConfigurationManager.AppSettings[XConstants.X_APPKEY_KEY]);
        System.Web.HttpContext.Current.Response.Cookies.Add(ck);
    }
Tahbaza
A: 

If you really want to avoid storing your value into the session, you can use an action filter that takes care of the value. I've used this approach once and it goes like this:

  1. Create a new route that contains a placeholder for your value, e.g. customer id.
  2. When customer is selected, add the id to route for the next GET or POST call.
  3. Create an action filter that reads the id from route and adds it to action parameters before action takes place (OnActionExecuting). When action is done this same filter adds the id from action parameters back to route data (OnActionExecuted).
  4. Use this new action filter wherever you wish, probably at controller level.

Now your customer id is easily persisted in the URL even though you do not manually add it to all links. If you need to use the customer id in an action method, you just add a parameter with the correct name. It is persisted over action method call even if you do not take it as a parameter.

If you want to go further, in your new action filter you can get the data from database and add that to action parameters instead of adding only id. This way you can further simplify your action methods.

Tero