StateValue<T>: Thread Safe?

Mar 20, 2008 at 4:15 PM
In a recent discussion, my colleagues and I arrived at a question: is the use of StateValue<T> as a field on a service that is registered as a module (or global) service a good practice?

The reason: the service instances are created once the module initializer's addModuleServices() method fires, and that apparently only happens once for all users (please confirm that this is true).

If a value is assigned to the Value property on an object of type StateValue<T>, which is a field on one of those services, and those services are created only once, is the Session object that the Value property ultimately references the same object in memory for all, or does StateValue<T> reference Session in such a way that it is safe?

Does StateValue<T>.Value get a reference to Session from HttpContext.Current every time the .Value property is referenced?

Is the .Value property thread Safe?
Mar 25, 2008 at 3:07 PM

csustek wrote:
In a recent discussion, my colleagues and I arrived at a question: is the use of StateValue<T> as a field on a service that is registered as a module (or global) service a good practice?

The reason: the service instances are created once the module initializer's addModuleServices() method fires, and that apparently only happens once for all users (please confirm that this is true).


Bear in mind that I'm still learning - I generally start by creating a prototype, plugging in the source code, breaking open the docs (which are excellent) and applying basic principles and watch them work (stepping through the code). I've been through the StateValue process and may be able to provide some insight.

The code samples, e.g., ModularityQuickStart and OrderManagement, would suggest that the best practice would be to place StateValue() in the Controller. Placing state in the service could affect extensibility and reusability, i.e., I have services that will be used by both WCSF and SCSF.

The documentation's "Inspecting the Software Factory Assets | Application Blocks | Composite Web Application Block | Design | Application Class" article confirms that "AddModuleServices() fires once for all users". The Global.asax utilizes the CompositeWeb.WebClientApplication class - an excerpt follows:

protected virtual void Application_Start(object sender, EventArgs e)
{
	// Application lifecycle
	CreateApplicationBuilder();
	CreatePageBuilder();
	AddBuilderStrategies(ApplicationBuilder);
	AddBuilderStrategies(PageBuilder);
	CreateRootContainer();
	AddRequiredServices();
	LoadModules();                             // <=== ModuleInitilizer.Load (which executes AddGlobalServices, AddModulesServices, etc)
	ConfigureModules();
	Start();
}


If a value is assigned to the Value property on an object of type StateValue<T>, which is a field on one of those services, and those services are created only once, is the Session object that the Value property ultimately references the same object in memory for all, or does StateValue<T> reference Session in such a way that it is safe?

Does StateValue<T>.Value get a reference to Session from HttpContext.Current every time the .Value property is referenced?

Is the .Value property thread Safe?


The rules for Session state apply.

The following should be self-explanatory, with the exception of _session, which is set by the SessionLocatorService.cs GetSessionState, indicating that the Current.Session does not have to be accessed "every time".

StateValue.cs
		/// <summary>
		/// Gets or sets the session state.
		/// </summary>
		public IHttpSessionState SessionState
		{
			get { return _sessionState; }
			set { _sessionState = value; }
		/// <summary>
		/// Gets or sets the actual value from or to the session state.
		/// </summary>
		object IStateValue.Value
		{
			get
			{
				if (SessionState == null || SessionState[_keyName] == null)
					return _value;
				else
					return SessionState[_keyName];
			}
			set
			{
				_value = value;
 
				if (SessionState != null)
					SessionState[_keyName] = value;
			}
		}

HttpSessionState.cs
		/// <summary>
		/// See <see cref="System.Web.SessionState.IHttpSessionState"/> for more information.
		/// </summary>
		public object this[string name]
		{
			get { return Session[name]; }
			set { Session[name] = value; }
		}
 
		protected System.Web.SessionState.HttpSessionState Session
		{
			get
			{
				System.Web.SessionState.HttpSessionState result = _session ?? System.Web.HttpContext.Current.Session;
				if (result == null)
				{
					throw new Exception(Resources.HttpSessionStateNotInitialized);
				}
				return result;
			}
		}
 

SessionLocatorService.cs
		/// <summary>
		/// Gets the session state as a <see cref="System.Web.SessionState.IHttpSessionState"/> implementation.
		/// </summary>
		public IHttpSessionState GetSessionState()
		{
			//Uses HttpContext.Current.Session without using HttpContextLocatorService. 
			//This service was created to avoid using the ASP.NET session bag directly in the block code, to increase the testing surface.
			return new HttpSessionState(HttpContext.Current.Session);
		}

Mar 25, 2008 at 3:59 PM
His concerns are valid when using StateValue in a singleton service. Unless things have changed for version 2 (which I have not tested). The services we registered that had StateValue fields were getting the same value regardless of session when we attempted a similar structure. We resorted to an interface for data storage that got values from session directly and mockable for unit testing. Which if I'm not mistaken is why state value was created in the first place.
Mar 25, 2008 at 10:51 PM

katokay wrote:
and mockable for unit testing. Which if I'm not mistaken is why state value was created in the first place.


That helped fill in some blanks for me - the documentation's "How to: Use Session State with Unit Testing" covers StateValue with a greater detail to include recommended usage (in the module controller). It is definately a recommended reading for anyone dabbling in state as it covers StateValue, SessionStateKey and the new StateDependency attribute.


katokay wrote:
His concerns are valid when using StateValue in a singleton service. Unless things have changed for version 2 (which I have not tested).


After strategically placing trace statements in the ObjectBuilder (and applicable code) I would venture to guess that things have not changed. After reviewing the trace output it is apparent why the Controller is a logical place for initializing/updating state.

    public class PrototypeController : IPrototypeController
    {
        private StateValue<List<FinancialReportMenuData>> _Menus;
        private MenuController _menuController;
 
        [ServiceDependency]
        public MenuController MenuController
        {
            set {  
                _menuController = value;
                Trace.WriteLine(GetType().FullName, "[ServiceDependency]MenuController");
            }
        }
        [InjectionMethod]
        public void InitializeState([StateDependency("Menus")] StateValue<List<FinancialReportMenuData>> menus)
        {
            Trace.WriteLine(GetType().FullName, "[InjectionMethod]InitializeState");
 
            if (menus.Value == null)
            {
                menus.Value = new List<FinancialReportMenuData>();
                menus.Value.AddRange(_menuController.GetMenuList());
            }
        }
        public PrototypeController()
        {
        }
    }
The above code is the controller that is involved in the following trace output.

Note: The Page.Init routine kicks off the process via the WebClientApplication.BuildItemWithCurrentContext(). Disclaimer: I'm not actually placing the menu list in state - it is currently the only service I have created and I'm using it to learn the system ;)

17:29:27:062	ASP.prototype_default_aspx: WebClientApplication.cs:BuildItemWithCurrentContext
 
17:29:27:062	Microsoft.Practices.ObjectBuilder.Locator: WebClientApplication.cs:BuildItemWithCurrentContext:BuildUp()
 
17:29:27:062	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]ASP.prototype_default_aspx:: : Microsoft.Practices.CompositeWeb.BuilderStrategies.ContainerAwareTypeMappingStrategy::
 
17:29:27:062	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]ASP.prototype_default_aspx:: : Microsoft.Practices.ObjectBuilder.TypeMappingStrategy::
 
17:29:27:062	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]ASP.prototype_default_aspx:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.Strategies.SimplifiedSingletonStrategy::
 
17:29:27:062	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]System.Reflection.Emit.DynamicILGenerator:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.DynamicMethodPlan.Creation.CallConstructorStrategy::
 
17:29:27:077	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]System.Reflection.Emit.DynamicILGenerator:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.DynamicMethodPlan.Properties.SetPropertiesStrategy::
 
17:29:27:077	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]System.Reflection.Emit.DynamicILGenerator:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.DynamicMethodPlan.Method.CallMethodsStrategy::
 
17:29:27:077	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]System.Reflection.Emit.DynamicILGenerator:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.DynamicMethodPlan.Creation.CallConstructorStrategy::
 
17:29:27:077	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]System.Reflection.Emit.DynamicILGenerator:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.DynamicMethodPlan.Properties.SetPropertiesStrategy::
 
17:29:27:093	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]System.Reflection.Emit.DynamicILGenerator:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.DynamicMethodPlan.Method.CallMethodsStrategy::
 
17:29:27:093	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]System.Reflection.Emit.DynamicILGenerator:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.DynamicMethodPlan.Creation.CallConstructorStrategy::
 
17:29:27:093	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]System.Reflection.Emit.DynamicILGenerator:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.DynamicMethodPlan.Properties.SetPropertiesStrategy::
 
17:29:27:109	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]System.Reflection.Emit.DynamicILGenerator:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.DynamicMethodPlan.Method.CallMethodsStrategy::
 
17:29:27:109	[ServiceDependency]MenuController: FinancialReport.Prototype.PrototypeController
 
17:29:27:109	[InjectionMethod]InitializeState: FinancialReport.Prototype.PrototypeController
 
17:29:27:109	IStateValue.Value -- GET : Microsoft.Practices.CompositeWeb.Web.StateValue`1[[System.Collections.Generic.List`1[[FinancialReport.FinancialReportDAL.Interface.BusinessEntities.FinancialReportMenuData, FinancialReportDAL.Interface, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
 
17:29:27:124	IStateValue.Value -- SET : Microsoft.Practices.CompositeWeb.Web.StateValue`1[[System.Collections.Generic.List`1[[FinancialReport.FinancialReportDAL.Interface.BusinessEntities.FinancialReportMenuData, FinancialReportDAL.Interface, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
 
17:29:27:124	IStateValue.Value -- GET : Microsoft.Practices.CompositeWeb.Web.StateValue`1[[System.Collections.Generic.List`1[[FinancialReport.FinancialReportDAL.Interface.BusinessEntities.FinancialReportMenuData, FinancialReportDAL.Interface, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

The second time I click on my prototype menu there will be only one GET (as expected) and the System.Reflection.Emit.DynamicILGenerator will not be executed

17:29:27:140	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]FinancialReport.Prototype.PrototypeController:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.BuildPlanStrategy::
 
17:29:27:140	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]FinancialReport.Prototype.PrototypeController:: : Microsoft.Practices.CompositeWeb.BuilderStrategies.SessionStateBindingStrategy::
 
17:29:27:140	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]FinancialReport.Prototype.PrototypeController:: : Microsoft.Practices.ObjectBuilder.BuilderAwareStrategy::
 
17:29:27:140	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]FinancialReport.Prototype.Views.DefaultViewPresenter:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.BuildPlanStrategy::
 
17:29:27:156	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]FinancialReport.Prototype.Views.DefaultViewPresenter:: : Microsoft.Practices.CompositeWeb.BuilderStrategies.SessionStateBindingStrategy::
 
17:29:27:156	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]FinancialReport.Prototype.Views.DefaultViewPresenter:: : Microsoft.Practices.ObjectBuilder.BuilderAwareStrategy::
 
17:29:27:156	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]ASP.prototype_default_aspx:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.BuildPlanStrategy::
 
17:29:27:156	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]ASP.prototype_default_aspx:: : Microsoft.Practices.CompositeWeb.BuilderStrategies.SessionStateBindingStrategy::
 
17:29:27:156	OBJ-BS[d7358579-c57d-4700-addb-586369fe89fe]ASP.prototype_default_aspx:: : Microsoft.Practices.ObjectBuilder.BuilderAwareStrategy::
 
17:29:27:156	Microsoft.Practices.ObjectBuilder.Locator: WebClientApplication.cs:BuildItemWithCurrentContext:BuildUp COMPLETED
 
17:29:27:171	ASP.shared_defaultmaster_master: WebClientApplication.cs:BuildItemWithCurrentContext
 
17:29:27:171	Microsoft.Practices.ObjectBuilder.Locator: WebClientApplication.cs:BuildItemWithCurrentContext:BuildUp()
 
17:29:27:171	OBJ-BS[bea6884c-e460-49c0-9aa7-6d8c1dca3d72]ASP.shared_defaultmaster_master:: : Microsoft.Practices.CompositeWeb.BuilderStrategies.ContainerAwareTypeMappingStrategy::
 
17:29:27:171	OBJ-BS[bea6884c-e460-49c0-9aa7-6d8c1dca3d72]ASP.shared_defaultmaster_master:: : Microsoft.Practices.ObjectBuilder.TypeMappingStrategy::
 
17:29:27:171	OBJ-BS[bea6884c-e460-49c0-9aa7-6d8c1dca3d72]ASP.shared_defaultmaster_master:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.Strategies.SimplifiedSingletonStrategy::
 
17:29:27:171	OBJ-BS[bea6884c-e460-49c0-9aa7-6d8c1dca3d72]FinancialReport.Shell.MasterPages.DefaultMasterPresenter:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.BuildPlanStrategy::
 
17:29:27:171	OBJ-BS[bea6884c-e460-49c0-9aa7-6d8c1dca3d72]FinancialReport.Shell.MasterPages.DefaultMasterPresenter:: : Microsoft.Practices.CompositeWeb.BuilderStrategies.SessionStateBindingStrategy::
 
17:29:27:171	OBJ-BS[bea6884c-e460-49c0-9aa7-6d8c1dca3d72]FinancialReport.Shell.MasterPages.DefaultMasterPresenter:: : Microsoft.Practices.ObjectBuilder.BuilderAwareStrategy::
 
17:29:27:187	OBJ-BS[bea6884c-e460-49c0-9aa7-6d8c1dca3d72]ASP.shared_defaultmaster_master:: : Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.BuildPlanStrategy::
 
17:29:27:187	OBJ-BS[bea6884c-e460-49c0-9aa7-6d8c1dca3d72]ASP.shared_defaultmaster_master:: : Microsoft.Practices.CompositeWeb.BuilderStrategies.SessionStateBindingStrategy::
 
17:29:27:187	OBJ-BS[bea6884c-e460-49c0-9aa7-6d8c1dca3d72]ASP.shared_defaultmaster_master:: : Microsoft.Practices.ObjectBuilder.BuilderAwareStrategy::
 
17:29:27:187	Microsoft.Practices.ObjectBuilder.Locator: WebClientApplication.cs:BuildItemWithCurrentContext:BuildUp COMPLETED