Nov 10, 2009

TempData of Controller

TempData is property of Controller. The TempData is used to save the state from page redirected from to the page redirected. So let say, your request is handled by controller1.method1, for some reason, it should be redirected to controller2.method2. Before the redirection take place, you can save some data in the TempData, then http status code 302 and new url are returned to browser, and browser make a new request using new url. In the new action method, the TempData can be restored. This behavior is showed in the following code in the Controller

//Controller member
protected override void ExecuteCore() {
    TempData.Load(ControllerContext, TempDataProvider);

    try {
        string actionName = RouteData.GetRequiredString("action");
        if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
            HandleUnknownAction(actionName);
        }
    }
    finally {
        TempData.Save(ControllerContext, TempDataProvider);
    }
}

This piece of code does not show how data is actually load and save, the job is actually performed by TempDataProvider. And the default provider is SessionStateTempDataProvider. We can override this TempDataProvider by injecting this property using custom ControllerFactory.

//Controller member
public ITempDataProvider TempDataProvider {
    get {
        if (_tempDataProvider == null) {
            _tempDataProvider = new SessionStateTempDataProvider();
        }
        return _tempDataProvider;
    }
    set {
        _tempDataProvider = value;
    }
}

//TempDataDictionary members
public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) {
    IDictionary<string, object> providerDictionary = tempDataProvider.LoadTempData(controllerContext);
    _data = (providerDictionary != null) ? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase) :
        new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
    _initialKeys = new HashSet<string>(_data.Keys);
    _modifiedKeys.Clear();
}

public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider) {
   //this is very important, if there is no modified, we don't need to save it
   //for next use 
   if (_modifiedKeys.Count > 0) {

        // Apply change tracking.
        foreach (string x in _initialKeys) {
            if (!_modifiedKeys.Contains(x)) {
                _data.Remove(x);
            }
        }

        // Store the dictionary
        tempDataProvider.SaveTempData(controllerContext, _data);
    }
}

public class SessionStateTempDataProvider : ITempDataProvider {
    internal const string TempDataSessionStateKey = "__ControllerTempData";

    public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext) {
        HttpContextBase httpContext = controllerContext.HttpContext;
        
        if (httpContext.Session == null) {
            throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
        }

        Dictionary<string, object> tempDataDictionary = httpContext.Session[TempDataSessionStateKey] as Dictionary<string, object>;

        if (tempDataDictionary != null) {
            // If we got it from Session, remove it so that no other request gets it
            httpContext.Session.Remove(TempDataSessionStateKey);
            return tempDataDictionary;
        }
        else {
            return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
        }
    }

    public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) {
        HttpContextBase httpContext = controllerContext.HttpContext;

        if (httpContext.Session == null) {
            throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
        }

        httpContext.Session[TempDataSessionStateKey] = values;
    }        
}