diff --git a/doc/reference/src/web.xml b/doc/reference/src/web.xml index 4b3d4b40..4c1255a4 100644 --- a/doc/reference/src/web.xml +++ b/doc/reference/src/web.xml @@ -1311,27 +1311,27 @@ class MyControl : Control, ISupportsWebDependencyInjection each one: When the page is initially loaded (IsPostback == - false), the InitializeModel method is + false), the InitializeModel() method is called which initializes the trip object by creating a new instance and setting its properties to desired values. Right before the page - is rendered, the SaveModel method will be invoked - and whatever the value it returns will be stored within the HTTP - Session. Finally, on each postback, the LoadModel - method will be called and the value returned by the previous call to - SaveModel will be passed to it as an - argument. + is rendered, the SaveModel() method will be + invoked and whatever the value it returns will be stored within the + HTTP Session. Finally, on each postback, the + LoadModel() method will be called and the value returned + by the previous call to SaveModel will be passed + to it as an argument. In this particular case the implementation is very simple because our whole model is just the trip object. - As such, SaveModel simply returns the - trip object and LoadModel - casts the savedModel argument to + As such, SaveModel() simply returns the + trip object and LoadModel() + casts the savedModel() argument to Trip and assigns it to the trip field within the page. In the more complex scenarios, you will typically return a dictionary containing your - model objects from the SaveModel method, and read - the values from that dictionary within the - LoadModel. + model objects from the SaveModel() method, and + read the values from that dictionary within the + LoadModel(). @@ -1799,6 +1799,37 @@ protected override void InitializeDataBindings() The Visual Studio Web Form Editor will of course complain about binding attributes because it doesn't know them. You can safely ignore those warnings. + + + Customizing Model Persistence + + As was already mentioned in the introduction to this chapter, + model management needs an application developer to override + InitializeModel(), SaveModel() and + LoadModel() for storing model information between + requests in the user's session. On web farms this of course storing + information in a user's session is not a good strategy. Thus it is + possible to choose another persistence strategy by setting a + Spring.Web.UI.Page's resp. Spring.Web.UI.UserControl's + ModelPersistenceMedium property: + + <object id="modelPersister" type="Sample.DatabaseModelPersistenceMedium, MyCode"/> + +<object type="UserRegistration.aspx"> + <property name="ModelPersistenceMedium" ref="modelPersister"/> +</object>To implement any arbitrary persistence + strategy, one simply needs to implement the IModelPersistenceMedium + interface: + + public interface IModelPersistenceMedium +{ + // Load the model for the specified control context. + object LoadFromMedium( Control context ); + + // Save the specified model object. + void SaveToMedium( Control context, object modelToSave ); +} + diff --git a/src/Spring/Spring.Web/Spring.Web.2008.csproj b/src/Spring/Spring.Web/Spring.Web.2008.csproj index 01c70273..8897b40b 100644 --- a/src/Spring/Spring.Web/Spring.Web.2008.csproj +++ b/src/Spring/Spring.Web/Spring.Web.2008.csproj @@ -128,6 +128,7 @@ + @@ -277,6 +278,7 @@ ASPXCodeBehind + ASPXCodeBehind diff --git a/src/Spring/Spring.Web/Web/UI/IModelPersistenceMedium.cs b/src/Spring/Spring.Web/Web/UI/IModelPersistenceMedium.cs new file mode 100644 index 00000000..271d6ce3 --- /dev/null +++ b/src/Spring/Spring.Web/Web/UI/IModelPersistenceMedium.cs @@ -0,0 +1,51 @@ +#region License + +/* + * Copyright © 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +#region Imports + +using System.Web.UI; + +#endregion + +namespace Spring.Web.UI +{ + /// + /// Abstracts storage strategy for storing model instances between requests. + /// All storage providers participating in UI model management must implement this interface. + /// + /// + /// Erich Eichinger + public interface IModelPersistenceMedium + { + /// + /// Load the model for the specified control context. + /// + /// the control context. + /// the model for the specified control context. + object LoadFromMedium( Control context ); + + /// + /// Save the specified model object. + /// + /// the control context. + /// the model to save. + void SaveToMedium( Control context, object modelToSave ); + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/UI/Page.cs b/src/Spring/Spring.Web/Web/UI/Page.cs index e3b1e4bb..f7c9b8f7 100644 --- a/src/Spring/Spring.Web/Web/UI/Page.cs +++ b/src/Spring/Spring.Web/Web/UI/Page.cs @@ -584,15 +584,22 @@ namespace Spring.Web.UI #region Model Management Support + private IModelPersistenceMedium modelPersistenceMedium = new SessionModelPersistenceMedium(); + /// - /// Initializes data model when the page is first loaded. + /// Set the strategy for storing model + /// instances between requests. /// /// - /// This method should be overriden by the developer - /// in order to initialize data model for the page. + /// By default the strategy is used. /// - protected virtual void InitializeModel() + public IModelPersistenceMedium ModelPersistenceMedium { + set + { + AssertUtils.ArgumentNotNull(value, "ModelPersistenceMedium"); + modelPersistenceMedium = value; + } } /// @@ -604,7 +611,8 @@ namespace Spring.Web.UI /// protected virtual object LoadModelFromPersistenceMedium() { - return Session[Request.CurrentExecutionFilePath + ".Model"]; + //return Session[Request.CurrentExecutionFilePath + ".Model"]; + return modelPersistenceMedium.LoadFromMedium(this); } /// @@ -616,7 +624,19 @@ namespace Spring.Web.UI /// protected virtual void SaveModelToPersistenceMedium( object modelToSave ) { - Session[Request.CurrentExecutionFilePath + ".Model"] = modelToSave; + //Session[Request.CurrentExecutionFilePath + ".Model"] = modelToSave; + modelPersistenceMedium.SaveToMedium(this, modelToSave); + } + + /// + /// Initializes data model when the page is first loaded. + /// + /// + /// This method should be overriden by the developer + /// in order to initialize data model for the page. + /// + protected virtual void InitializeModel() + { } /// diff --git a/src/Spring/Spring.Web/Web/UI/SessionModelPersistenceMedium.cs b/src/Spring/Spring.Web/Web/UI/SessionModelPersistenceMedium.cs new file mode 100644 index 00000000..92946698 --- /dev/null +++ b/src/Spring/Spring.Web/Web/UI/SessionModelPersistenceMedium.cs @@ -0,0 +1,91 @@ +#region License + +/* + * Copyright © 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +#region Imports + +using System.Web.SessionState; +using System.Web.UI; + +#endregion + +namespace Spring.Web.UI +{ + /// + /// implements -based storage for + /// UI model management. + /// + /// Erich Eichinger + public class SessionModelPersistenceMedium : IModelPersistenceMedium + { + /// + /// Load the model for the specified control context. + /// + /// + /// The key used for loading the model from the session dictionary is obtained by calling + /// + /// the control context. + /// the model for the specified control context. + /// + public object LoadFromMedium(Control context) + { + return GetItem(context, GetKey(context)); + } + + /// + /// Save the specified model object to session. + /// + /// + /// The key used for storing the model into the session dictionary is obtained by calling + /// + /// the control context. + /// the model to save. + /// + public void SaveToMedium(Control context, object modelToSave) + { + SetItem(context, GetKey(context), modelToSave); + } + + /// + /// Create the key to be used for accessing the dictionary. + /// + /// + /// + protected virtual string GetKey(Control context) + { + return context.Page.Request.CurrentExecutionFilePath + context.UniqueID + ".Model"; + } + + /// + /// Abstracts session access for unit testing. + /// + protected virtual object GetItem(Control context, string key) + { + return context.Page.Session[key]; + } + + /// + /// Abstracts session access for unit testing. + /// + protected virtual void SetItem(Control context, string key, object item) + { + context.Page.Session[key] = item; + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/UI/UserControl.cs b/src/Spring/Spring.Web/Web/UI/UserControl.cs index 2fec016a..6e0806ac 100644 --- a/src/Spring/Spring.Web/Web/UI/UserControl.cs +++ b/src/Spring/Spring.Web/Web/UI/UserControl.cs @@ -319,15 +319,22 @@ namespace Spring.Web.UI #region Model Management Support + private IModelPersistenceMedium modelPersistenceMedium = new SessionModelPersistenceMedium(); + /// - /// Initializes data model when the page is first loaded. + /// Set the strategy for storing model + /// instances between requests. /// /// - /// This method should be overriden by the developer - /// in order to initialize data model for the page. + /// By default the strategy is used. /// - protected virtual void InitializeModel() + public IModelPersistenceMedium ModelPersistenceMedium { + set + { + AssertUtils.ArgumentNotNull(value, "ModelPersistenceMedium"); + modelPersistenceMedium = value; + } } /// @@ -339,7 +346,8 @@ namespace Spring.Web.UI /// protected virtual object LoadModelFromPersistenceMedium() { - return Session[Request.CurrentExecutionFilePath + this.UniqueID + ".Model"]; + //return Session[Request.CurrentExecutionFilePath + this.UniqueID + ".Model"]; + return modelPersistenceMedium.LoadFromMedium(this); } /// @@ -351,7 +359,19 @@ namespace Spring.Web.UI /// protected virtual void SaveModelToPersistenceMedium( object modelToSave ) { - Session[Request.CurrentExecutionFilePath + this.UniqueID + ".Model"] = modelToSave; + //Session[Request.CurrentExecutionFilePath + this.UniqueID + ".Model"] = modelToSave; + modelPersistenceMedium.SaveToMedium(this, modelToSave); + } + + /// + /// Initializes data model when the page is first loaded. + /// + /// + /// This method should be overriden by the developer + /// in order to initialize data model for the page. + /// + protected virtual void InitializeModel() + { } /// diff --git a/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2008.csproj b/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2008.csproj index 9feff1eb..79629f9d 100644 --- a/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2008.csproj +++ b/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2008.csproj @@ -134,9 +134,15 @@ + - + + ASPXCodeBehind + + + + diff --git a/test/Spring/Spring.Web.Tests/TestSupport/DictionaryModelPersistenceMedium.cs b/test/Spring/Spring.Web.Tests/TestSupport/DictionaryModelPersistenceMedium.cs new file mode 100644 index 00000000..05065750 --- /dev/null +++ b/test/Spring/Spring.Web.Tests/TestSupport/DictionaryModelPersistenceMedium.cs @@ -0,0 +1,21 @@ +using System.Collections; +using System.Web.UI; +using Spring.Web.UI; + +namespace Spring.TestSupport +{ + public class DictionaryModelPersistenceMedium : IModelPersistenceMedium + { + private Hashtable _storage = new Hashtable(); + + public object LoadFromMedium(Control context) + { + return _storage[context]; + } + + public void SaveToMedium(Control context, object modelToSave) + { + _storage[context] = modelToSave; + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Web.Tests/TestSupport/TestPage.cs b/test/Spring/Spring.Web.Tests/TestSupport/TestPage.cs index f0bc6ccd..35286d50 100644 --- a/test/Spring/Spring.Web.Tests/TestSupport/TestPage.cs +++ b/test/Spring/Spring.Web.Tests/TestSupport/TestPage.cs @@ -68,5 +68,15 @@ namespace Spring.TestSupport string result = sw.GetStringBuilder().ToString(); return result; } + + public new void SaveModelToPersistenceMedium(object model) + { + base.SaveModelToPersistenceMedium(model); + } + + public new object LoadModelFromPersistenceMedium() + { + return base.LoadModelFromPersistenceMedium(); + } } } diff --git a/test/Spring/Spring.Web.Tests/TestSupport/TestUserControl.cs b/test/Spring/Spring.Web.Tests/TestSupport/TestUserControl.cs new file mode 100644 index 00000000..730a3897 --- /dev/null +++ b/test/Spring/Spring.Web.Tests/TestSupport/TestUserControl.cs @@ -0,0 +1,52 @@ + +using System.Web.UI; +using UserControl=Spring.Web.UI.UserControl; + +namespace Spring.TestSupport +{ + public class TestUserControl : UserControl + { + private static int instanceCount = 0; + + public TestUserControl() + :this(string.Format("_ctl_{0}", instanceCount++), null) + { + } + + public TestUserControl(string id) + :this(id, null) + { + } + + public TestUserControl(Control parent) + :this(null, parent) + { + } + + public TestUserControl(string id, Control parent) + { + this.ID = id; + if (parent != null) parent.Controls.Add(this); + } + + public new void SetResult(string name) + { + base.SetResult(name); + } + + public override string ToString() + { + return string.Format("{0}[{1}]", base.ToString(), this.ClientID); + } + + public new void SaveModelToPersistenceMedium(object model) + { + base.SaveModelToPersistenceMedium(model); + } + + public new object LoadModelFromPersistenceMedium() + { + return base.LoadModelFromPersistenceMedium(); + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Web.Tests/Web/UI/PageTests.cs b/test/Spring/Spring.Web.Tests/Web/UI/PageTests.cs index 8afd089f..455b374b 100644 --- a/test/Spring/Spring.Web.Tests/Web/UI/PageTests.cs +++ b/test/Spring/Spring.Web.Tests/Web/UI/PageTests.cs @@ -124,5 +124,22 @@ namespace Spring.Web.UI page.SetResult("theResult"); mocks.VerifyAll(); } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void NoNullModelPersistenceMediumAllowed() + { + TestPage tuc = new TestPage(); + tuc.ModelPersistenceMedium = null; + } + + [Test] + public void StoresAndLoadsModelUsingModelPersistenceMedium() + { + TestPage tuc = new TestPage(); + tuc.ModelPersistenceMedium = new DictionaryModelPersistenceMedium(); + tuc.SaveModelToPersistenceMedium( this ); + Assert.AreEqual(this, tuc.LoadModelFromPersistenceMedium()); + } } } diff --git a/test/Spring/Spring.Web.Tests/Web/UI/SessionModelPersistenceMediumTests.cs b/test/Spring/Spring.Web.Tests/Web/UI/SessionModelPersistenceMediumTests.cs new file mode 100644 index 00000000..a4b5e7fd --- /dev/null +++ b/test/Spring/Spring.Web.Tests/Web/UI/SessionModelPersistenceMediumTests.cs @@ -0,0 +1,82 @@ +#region License + +/* + * Copyright © 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +#region Imports + +using System; +using System.Collections; +using System.Web.UI; +using NUnit.Framework; +using Spring.Collections; +using Spring.TestSupport; + +#endregion + +namespace Spring.Web.UI +{ + /// + /// + /// + /// Erich Eichinger + [TestFixture] + public class SessionModelPersistenceMediumTests + { + private class TestSessionModelPersistenceMedium : SessionModelPersistenceMedium + { + private Hashtable _sessionItems = new CaseInsensitiveHashtable(); + + public Hashtable SessionItems + { + get { return _sessionItems; } + } + + protected override object GetItem( System.Web.UI.Control context, string key ) + { + //return base.GetItem( context, key ); + return _sessionItems[key]; + } + + protected override void SetItem( System.Web.UI.Control context, string key, object item ) + { + //base.SetItem( context, key, item ); + _sessionItems[key] = item; + } + + protected override string GetKey( Control context ) + { + //return base.GetKey( context ); + return context.ID; + } + } + + [Test] + public void StoresAndRetrievesModelItem() + { + TestSessionModelPersistenceMedium pm = new TestSessionModelPersistenceMedium(); + Control tuc = new TestUserControl("TucID"); + pm.SaveToMedium( tuc, this ); + // ensure key was generated by GetKey() and Item was added to storage + Assert.AreEqual( this, pm.SessionItems["TucID"] ); + + // ensure key was generated by GetKey() and Item is retrieved from storage + Assert.AreEqual( this, pm.LoadFromMedium( tuc ) ); + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Web.Tests/Web/UI/UserControlTests.cs b/test/Spring/Spring.Web.Tests/Web/UI/UserControlTests.cs index 976855eb..854f436f 100644 --- a/test/Spring/Spring.Web.Tests/Web/UI/UserControlTests.cs +++ b/test/Spring/Spring.Web.Tests/Web/UI/UserControlTests.cs @@ -20,6 +20,7 @@ #region Imports +using System; using System.Web; using System.Web.UI; using NUnit.Framework; @@ -39,43 +40,6 @@ namespace Spring.Web.UI [TestFixture] public class UserControlTests : TestWebContextTests { - public class TestUserControl : UserControl - { - private static int instanceCount = 0; - - public TestUserControl() - :this(string.Format("_ctl_{0}", instanceCount++), null) - { - } - - public TestUserControl(string id) - :this(id, null) - { - } - - public TestUserControl(Control parent) - :this(null, parent) - { - } - - public TestUserControl(string id, Control parent) - { - this.ID = id; - if (parent != null) parent.Controls.Add(this); - } - - public new void SetResult(string name) - { - base.SetResult(name); - } - - public override string ToString() - { - return string.Format("{0}[{1}]", base.ToString(), this.ClientID); - } - - } - [Test] public void SetResultSelectsCorrectResult() { @@ -127,5 +91,22 @@ namespace Spring.Web.UI Assert.IsTrue(c1.Validate(c1, v1, v2)); } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void NoNullModelPersistenceMediumAllowed() + { + TestUserControl tuc = new TestUserControl(); + tuc.ModelPersistenceMedium = null; + } + + [Test] + public void StoresAndLoadsModelUsingModelPersistenceMedium() + { + TestUserControl tuc = new TestUserControl(); + tuc.ModelPersistenceMedium = new DictionaryModelPersistenceMedium(); + tuc.SaveModelToPersistenceMedium( this ); + Assert.AreEqual(this, tuc.LoadModelFromPersistenceMedium()); + } } } \ No newline at end of file