resolved SPRNET-672 (support custom model persistence)

This commit is contained in:
eeichinger
2008-10-19 11:38:02 +00:00
parent e3b13def9a
commit f05fe4a509
13 changed files with 447 additions and 63 deletions

View File

@@ -1311,27 +1311,27 @@ class MyControl : Control, ISupportsWebDependencyInjection
each one:<orderedlist>
<listitem>
<para>When the page is initially loaded (<literal>IsPostback ==
false</literal>), the <literal>InitializeModel</literal> method is
false</literal>), the <literal>InitializeModel</literal>() 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, <literal>the SaveModel</literal> method will be invoked
and whatever the value it returns will be stored within the HTTP
Session. Finally, on each postback, <literal>the LoadModel</literal>
method will be called and the value returned by the previous call to
<literal>SaveModel</literal> will be passed to it as an
argument.</para>
is rendered, <literal>the SaveModel</literal>() method will be
invoked and whatever the value it returns will be stored within the
HTTP Session. Finally, on each postback, <literal>the
LoadModel()</literal> method will be called and the value returned
by the previous call to <literal>SaveModel</literal> will be passed
to it as an argument.</para>
<para>In this particular case the implementation is very simple
because our whole model is just the <literal>trip</literal> object.
As such, <literal>SaveModel</literal> simply returns the
<literal>trip</literal> object and <literal>LoadModel</literal>
casts the <literal>savedModel</literal> argument to
As such, <literal>SaveModel</literal>() simply returns the
<literal>trip</literal> object and <literal>LoadModel()</literal>
casts the <literal>savedModel</literal>() argument to
<literal>Trip</literal> and assigns it to the
<literal>trip</literal> field within the page. In the more complex
scenarios, you will typically return a dictionary containing your
model objects from the <literal>SaveModel</literal> method, and read
the values from that dictionary within the
<literal>LoadModel</literal>.</para>
model objects from the <literal>SaveModel</literal>() method, and
read the values from that dictionary within the
<literal>LoadModel</literal>().</para>
</listitem>
<listitem>
@@ -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.
</note>
</sect2>
<sect2>
<title>Customizing Model Persistence</title>
<para>As was already mentioned in the introduction to this chapter,
model management needs an application developer to override
<literal>InitializeModel()</literal>, <literal>SaveModel() </literal>and
<literal>LoadModel()</literal> 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:</para>
<para><programlisting language="myxml">&lt;object id="modelPersister" type="Sample.DatabaseModelPersistenceMedium, MyCode"/&gt;
&lt;object type="UserRegistration.aspx"&gt;
&lt;property name="ModelPersistenceMedium" ref="modelPersister"/&gt;
&lt;/object&gt;</programlisting>To implement any arbitrary persistence
strategy, one simply needs to implement the IModelPersistenceMedium
interface:</para>
<para><programlisting language="myxml">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 );
}</programlisting></para>
</sect2>
</sect1>
<sect1 xml:id="web-localization">

View File

@@ -128,6 +128,7 @@
<Compile Include="Web\Support\DefaultResultFactory.cs" />
<Compile Include="Web\Support\HandlerMap.cs" />
<Compile Include="Web\Support\HandlerMapEntry.cs" />
<Compile Include="Web\UI\IModelPersistenceMedium.cs" />
<Compile Include="Web\Support\IResult.cs" />
<Compile Include="Web\Support\IResultFactory.cs" />
<Compile Include="Web\Support\LocalResourceManager.cs" />
@@ -277,6 +278,7 @@
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Web\Support\DefaultResultWebNavigator.cs" />
<Compile Include="Web\UI\SessionModelPersistenceMedium.cs" />
<Compile Include="Web\UI\UserControl.cs">
<SubType>ASPXCodeBehind</SubType>
</Compile>

View File

@@ -0,0 +1,51 @@
#region License
/*
* Copyright <20> 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
{
/// <summary>
/// Abstracts storage strategy for storing model instances between requests.
/// All storage providers participating in UI model management must implement this interface.
/// </summary>
/// <seealso cref="SessionModelPersistenceMedium"/>
/// <author>Erich Eichinger</author>
public interface IModelPersistenceMedium
{
/// <summary>
/// Load the model for the specified control context.
/// </summary>
/// <param name="context">the control context.</param>
/// <returns>the model for the specified control context.</returns>
object LoadFromMedium( Control context );
/// <summary>
/// Save the specified model object.
/// </summary>
/// <param name="context">the control context.</param>
/// <param name="modelToSave">the model to save.</param>
void SaveToMedium( Control context, object modelToSave );
}
}

View File

@@ -584,15 +584,22 @@ namespace Spring.Web.UI
#region Model Management Support
private IModelPersistenceMedium modelPersistenceMedium = new SessionModelPersistenceMedium();
/// <summary>
/// Initializes data model when the page is first loaded.
/// Set the <see cref="IModelPersistenceMedium"/> strategy for storing model
/// instances between requests.
/// </summary>
/// <remarks>
/// This method should be overriden by the developer
/// in order to initialize data model for the page.
/// By default the <see cref="SessionModelPersistenceMedium"/> strategy is used.
/// </remarks>
protected virtual void InitializeModel()
public IModelPersistenceMedium ModelPersistenceMedium
{
set
{
AssertUtils.ArgumentNotNull(value, "ModelPersistenceMedium");
modelPersistenceMedium = value;
}
}
/// <summary>
@@ -604,7 +611,8 @@ namespace Spring.Web.UI
/// </remarks>
protected virtual object LoadModelFromPersistenceMedium()
{
return Session[Request.CurrentExecutionFilePath + ".Model"];
//return Session[Request.CurrentExecutionFilePath + ".Model"];
return modelPersistenceMedium.LoadFromMedium(this);
}
/// <summary>
@@ -616,7 +624,19 @@ namespace Spring.Web.UI
/// </remarks>
protected virtual void SaveModelToPersistenceMedium( object modelToSave )
{
Session[Request.CurrentExecutionFilePath + ".Model"] = modelToSave;
//Session[Request.CurrentExecutionFilePath + ".Model"] = modelToSave;
modelPersistenceMedium.SaveToMedium(this, modelToSave);
}
/// <summary>
/// Initializes data model when the page is first loaded.
/// </summary>
/// <remarks>
/// This method should be overriden by the developer
/// in order to initialize data model for the page.
/// </remarks>
protected virtual void InitializeModel()
{
}
/// <summary>

View File

@@ -0,0 +1,91 @@
#region License
/*
* Copyright <20> 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
{
/// <summary>
/// <see cref="SessionModelPersistenceMedium"/> implements <see cref="HttpSessionState"/>-based storage for
/// UI model management.
/// </summary>
/// <author>Erich Eichinger</author>
public class SessionModelPersistenceMedium : IModelPersistenceMedium
{
/// <summary>
/// Load the model for the specified control context.
/// </summary>
/// <remarks>
/// The key used for loading the model from the session dictionary is obtained by calling <see cref="GetKey"/>
/// </remarks>
/// <param name="context">the control context.</param>
/// <returns>the model for the specified control context.</returns>
/// <seealso cref="GetKey"/>
public object LoadFromMedium(Control context)
{
return GetItem(context, GetKey(context));
}
/// <summary>
/// Save the specified model object to session.
/// </summary>
/// <remarks>
/// The key used for storing the model into the session dictionary is obtained by calling <see cref="GetKey"/>
/// </remarks>
/// <param name="context">the control context.</param>
/// <param name="modelToSave">the model to save.</param>
/// <seealso cref="GetKey"/>
public void SaveToMedium(Control context, object modelToSave)
{
SetItem(context, GetKey(context), modelToSave);
}
/// <summary>
/// Create the key to be used for accessing the <see cref="HttpSessionState"/> dictionary.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
protected virtual string GetKey(Control context)
{
return context.Page.Request.CurrentExecutionFilePath + context.UniqueID + ".Model";
}
/// <summary>
/// Abstracts session access for unit testing.
/// </summary>
protected virtual object GetItem(Control context, string key)
{
return context.Page.Session[key];
}
/// <summary>
/// Abstracts session access for unit testing.
/// </summary>
protected virtual void SetItem(Control context, string key, object item)
{
context.Page.Session[key] = item;
}
}
}

View File

@@ -319,15 +319,22 @@ namespace Spring.Web.UI
#region Model Management Support
private IModelPersistenceMedium modelPersistenceMedium = new SessionModelPersistenceMedium();
/// <summary>
/// Initializes data model when the page is first loaded.
/// Set the <see cref="IModelPersistenceMedium"/> strategy for storing model
/// instances between requests.
/// </summary>
/// <remarks>
/// This method should be overriden by the developer
/// in order to initialize data model for the page.
/// By default the <see cref="SessionModelPersistenceMedium"/> strategy is used.
/// </remarks>
protected virtual void InitializeModel()
public IModelPersistenceMedium ModelPersistenceMedium
{
set
{
AssertUtils.ArgumentNotNull(value, "ModelPersistenceMedium");
modelPersistenceMedium = value;
}
}
/// <summary>
@@ -339,7 +346,8 @@ namespace Spring.Web.UI
/// </remarks>
protected virtual object LoadModelFromPersistenceMedium()
{
return Session[Request.CurrentExecutionFilePath + this.UniqueID + ".Model"];
//return Session[Request.CurrentExecutionFilePath + this.UniqueID + ".Model"];
return modelPersistenceMedium.LoadFromMedium(this);
}
/// <summary>
@@ -351,7 +359,19 @@ namespace Spring.Web.UI
/// </remarks>
protected virtual void SaveModelToPersistenceMedium( object modelToSave )
{
Session[Request.CurrentExecutionFilePath + this.UniqueID + ".Model"] = modelToSave;
//Session[Request.CurrentExecutionFilePath + this.UniqueID + ".Model"] = modelToSave;
modelPersistenceMedium.SaveToMedium(this, modelToSave);
}
/// <summary>
/// Initializes data model when the page is first loaded.
/// </summary>
/// <remarks>
/// This method should be overriden by the developer
/// in order to initialize data model for the page.
/// </remarks>
protected virtual void InitializeModel()
{
}
/// <summary>

View File

@@ -134,9 +134,15 @@
<Compile Include="Web\UI\Controls\HeadTests.cs" />
<Compile Include="Web\UI\Controls\ValidationErrorsTests.cs" />
<Compile Include="Web\UI\Controls\ValidationSummaryTests.cs" />
<Compile Include="TestSupport\DictionaryModelPersistenceMedium.cs" />
<Compile Include="Web\UI\PageTests.cs">
</Compile>
<Compile Include="Web\UI\UserControlTests.cs" />
<Compile Include="TestSupport\TestUserControl.cs">
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Web\UI\SessionModelPersistenceMediumTests.cs" />
<Compile Include="Web\UI\UserControlTests.cs">
</Compile>
<None Include="Data\Spring\Context\Support\WebApplicationContextTests\Web.Config" />
<None Include="Data\Spring\Web\Support\PageHandlerFactoryTests\Web.Config.net-1.1" />
<None Include="Data\Spring\Web\Support\PageHandlerFactoryTests\Web.Config.net-2.0" />

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -0,0 +1,82 @@
#region License
/*
* Copyright <20> 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
{
/// <summary>
///
/// </summary>
/// <author>Erich Eichinger</author>
[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 ) );
}
}
}

View File

@@ -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());
}
}
}