+
+
+
diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/Default.aspx.cs b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/Default.aspx.cs
new file mode 100644
index 00000000..11ac7a8b
--- /dev/null
+++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/Default.aspx.cs
@@ -0,0 +1,9 @@
+///
+/// Notice that your web page has to extend Spring.Web.UI.Page class
+/// in order to enable data binding and many other features.
+///
+public partial class DataBinding_RobustEmployeeInfo_Default : Spring.Web.UI.Page
+{
+ private EmployeeInfoEditor employeeInfoEditor;
+}
+
diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/EmployeeInfoEditor.ascx b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/EmployeeInfoEditor.ascx
new file mode 100644
index 00000000..588b09d3
--- /dev/null
+++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/EmployeeInfoEditor.ascx
@@ -0,0 +1,87 @@
+<%@ Control Language="C#" AutoEventWireup="true" CodeFile="EmployeeInfoEditor.ascx.cs" Inherits="EmployeeInfoEditor" %>
+<%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %>
+
+
+
Employee ID:
+
+
+
+
+
+
+
First Name:
+
+
+
+
Last Name:
+
+
+
+
Date of Birth:
+
+
+
+
+
+
+
Gender:
+
+
+
+
+
+
+
+
+
+
Salary:
+
+
+
+
+
+
+
Address Type:
+
+
+
+
+
+
+
+
+
+
Street 1:
+
+
+
+
Street 2:
+
+
+
+
City:
+
+
+
+
State:
+
+
+
+
Postal Code/ZIP:
+
+
+
+
Country:
+
+
+
+
+
+
+
diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/EmployeeInfoEditor.ascx.cs b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/EmployeeInfoEditor.ascx.cs
new file mode 100644
index 00000000..7c78020a
--- /dev/null
+++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/EmployeeInfoEditor.ascx.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Diagnostics;
+using Spring.Globalization.Formatters;
+
+public partial class EmployeeInfoEditor : Spring.Web.UI.UserControl
+{
+ private EmployeeInfo employee = new EmployeeInfo();
+
+ public EmployeeInfo Employee
+ {
+ get { return employee; }
+ }
+
+ ///
+ /// In order to declare data bindings, all you need to do is
+ /// override InitializeDataBindings method and add all necessary
+ /// data bindings to the BindingManager.
+ ///
+ protected override void InitializeDataBindings()
+ {
+ BindingManager.AddBinding("txtId.Text", "Employee.Id")
+ .SetErrorMessage("ID has to be an integer", "id.errors", "summary");
+ BindingManager.AddBinding("txtFirstName.Text", "Employee.FirstName");
+ BindingManager.AddBinding("txtLastName.Text", "Employee.LastName");
+ BindingManager.AddBinding("txtDOB.Text", "Employee.DateOfBirth")
+ .SetErrorMessage("Invalid date value", "dob.errors", "summary");
+ BindingManager.AddBinding("txtSalary.Text", "Employee.Salary", new CurrencyFormatter())
+ .SetErrorMessage("Salary must be a valid currency value.", "salary.errors", "summary");
+ BindingManager.AddBinding("rbgGender.Value", "Employee.Gender");
+ BindingManager.AddBinding("ddlAddressType.SelectedValue", "Employee.MailingAddress.AddressType");
+ BindingManager.AddBinding("txtStreet1.Text", "Employee.MailingAddress.Street1");
+ BindingManager.AddBinding("txtStreet2.Text", "Employee.MailingAddress.Street2");
+ BindingManager.AddBinding("txtCity.Text", "Employee.MailingAddress.City");
+ BindingManager.AddBinding("txtState.Text", "Employee.MailingAddress.State");
+ BindingManager.AddBinding("txtPostalCode.Text", "Employee.MailingAddress.PostalCode");
+ BindingManager.AddBinding("txtCountry.Text", "Employee.MailingAddress.Country");
+ }
+
+ protected void btnSave_Click(object sender, EventArgs e)
+ {
+ // do something with employee such as:
+ // employeeDao.Save(Employee);
+
+ Debug.Write(Employee);
+ }
+}
diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/RobustEmployeeInfo/Default.aspx b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/RobustEmployeeInfo/Default.aspx
index c26beaf9..9b5ef98f 100644
--- a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/RobustEmployeeInfo/Default.aspx
+++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/RobustEmployeeInfo/Default.aspx
@@ -33,12 +33,14 @@
on the Debug.Write(Employee) statement in the event handler for the Save button,
so you can see how Employee object was populated by the framework.
+
+
Employee ID:
-
+
diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/RobustEmployeeInfo/Default.aspx.cs b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/RobustEmployeeInfo/Default.aspx.cs
index 662961fb..a03a0b11 100644
--- a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/RobustEmployeeInfo/Default.aspx.cs
+++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/RobustEmployeeInfo/Default.aspx.cs
@@ -23,7 +23,7 @@ public partial class DataBinding_RobustEmployeeInfo_Default : Spring.Web.UI.Page
protected override void InitializeDataBindings()
{
BindingManager.AddBinding("txtId.Text", "Employee.Id")
- .SetErrorMessage("ID has to be an integer", "id.errors");
+ .SetErrorMessage("ID has to be an integer", "id.errors"); // send msg to "id.errors" provider
BindingManager.AddBinding("txtFirstName.Text", "Employee.FirstName");
BindingManager.AddBinding("txtLastName.Text", "Employee.LastName");
BindingManager.AddBinding("txtDOB.Text", "Employee.DateOfBirth")
diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Web.Config b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Web.Config
index 7ba5cd4e..909cd73b 100644
--- a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Web.Config
+++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Web.Config
@@ -1,81 +1,73 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Spring/Spring.Web/Web/UI/Controls/AbstractValidationControl.cs b/src/Spring/Spring.Web/Web/UI/Controls/AbstractValidationControl.cs
index 5af92a02..49581020 100644
--- a/src/Spring/Spring.Web/Web/UI/Controls/AbstractValidationControl.cs
+++ b/src/Spring/Spring.Web/Web/UI/Controls/AbstractValidationControl.cs
@@ -20,8 +20,11 @@
#region Imports
+using System;
using System.Collections;
+using System.Diagnostics;
using System.Web.UI;
+using System.Web.UI.WebControls;
using Spring.Context;
using Spring.Util;
using Spring.Validation;
@@ -38,6 +41,7 @@ namespace Spring.Web.UI.Controls
public abstract class AbstractValidationControl : Control
{
private string provider;
+ private string validationContainerName;
private IValidationErrorsRenderer renderer;
#if !NET_2_0
@@ -70,6 +74,18 @@ namespace Spring.Web.UI.Controls
}
}
#endif
+ ///
+ /// If set, will resolve to the named control specified
+ /// by this property. The behavior of name resolution is identical to
+ /// , except that if the name
+ /// starts with "::", the resolution will start at the page level instead of relative to this
+ /// control
+ ///
+ public virtual string ValidationContainerName
+ {
+ get { return validationContainerName; }
+ set { validationContainerName = value; }
+ }
///
/// Gets or sets the provider.
@@ -146,15 +162,34 @@ namespace Spring.Web.UI.Controls
{
get
{
+ // is an explicit container specified?
+ if (ValidationContainerName != null)
+ {
+ Control start = this.NamingContainer;
+ string containerName = this.ValidationContainerName;
+ // shall we do a global search?
+ if (containerName.StartsWith("::"))
+ {
+ containerName = containerName.Substring(2);
+ start = this.Page;
+ }
+ IValidationContainer container = start.FindControl(containerName) as IValidationContainer;
+ if (container == null)
+ {
+ throw new ArgumentException(string.Format("Validation Container Control specified by {0} does not exist or does not implement IValidationContainer", this.ValidationContainerName));
+ }
+ return container;
+ }
+
for (Control parent = this.Parent; parent != null; parent = parent.Parent)
{
IValidationContainer container = parent as IValidationContainer;
- if (container != null)
+ if (container != null
+ && container.ValidationErrors != null)
{
return container;
}
}
- // throw new NotSupportedException(string.Format("Controls of Type {0} must be placed on a container control implementing IValidationContainer", this.GetType().Name));
return null;
}
}
@@ -201,7 +236,7 @@ namespace Spring.Web.UI.Controls
errorMessages = ResolveErrorMessages();
- Renderer.RenderErrors(Page as Spring.Web.UI.Page, writer, errorMessages);
+ Renderer.RenderErrors(Page as Page, writer, errorMessages);
}
}
}
\ 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 a9f3f591..de50ba92 100644
--- a/src/Spring/Spring.Web/Web/UI/UserControl.cs
+++ b/src/Spring/Spring.Web/Web/UI/UserControl.cs
@@ -676,7 +676,7 @@ namespace Spring.Web.UI
{
throw new ArgumentException( "Validator is not defined." );
}
- result = validator.Validate( validationContext, contextParams, this.validationErrors ) && result;
+ result = validator.Validate( validationContext, contextParams, this.ValidationErrors ) && result;
}
return result;
@@ -837,7 +837,7 @@ namespace Spring.Web.UI
{
if (BindingManager.HasBindings)
{
- BindingManager.BindTargetToSource( this, Controller, Page.ValidationErrors );
+ BindingManager.BindTargetToSource( this, Controller, this.ValidationErrors );
}
OnDataBound( EventArgs.Empty );
}
@@ -849,7 +849,7 @@ namespace Spring.Web.UI
{
if (BindingManager.HasBindings)
{
- BindingManager.BindSourceToTarget( this, Controller, Page.ValidationErrors );
+ BindingManager.BindSourceToTarget( this, Controller, this.ValidationErrors );
}
OnDataUnbound( EventArgs.Empty );
}
diff --git a/test/Spring/Spring.Web.Tests/Web/UI/Controls/AbstractValidationControlTests.cs b/test/Spring/Spring.Web.Tests/Web/UI/Controls/AbstractValidationControlTests.cs
index 6d152b69..ca2de191 100644
--- a/test/Spring/Spring.Web.Tests/Web/UI/Controls/AbstractValidationControlTests.cs
+++ b/test/Spring/Spring.Web.Tests/Web/UI/Controls/AbstractValidationControlTests.cs
@@ -179,6 +179,80 @@ namespace Spring.Web.UI.Controls
Assert.AreEqual(messageSource, ctl.TheMessageSource);
}
+ [Test]
+ public void ReadsErrorsFromClosestValidationContainerByDefault()
+ {
+ // indirect containment
+ Spring.Web.UI.Page page = new Spring.Web.UI.Page();
+ TestValidationControl vc = new TestValidationControl();
+ Spring.Web.UI.UserControl inBetweenControl = new Spring.Web.UI.UserControl();
+ inBetweenControl.Controls.Add(vc);
+ page.Controls.Add(inBetweenControl);
+ Assert.AreSame(inBetweenControl, vc.TheValidationContainer);
+ }
+
+ [Test]
+ public void DoesNotReadErrorsFromCrossValidationContainerByDefault()
+ {
+ // containment tree:
+ //
+ // Page : IValidationContainer
+ // - outerVC
+ // - inBetweenControl1 : IValidationContainer
+ // - innerVC
+
+ Spring.Web.UI.Page page = new Spring.Web.UI.Page();
+ Spring.Web.UI.UserControl inBetweenControl1 = new Spring.Web.UI.UserControl();
+ inBetweenControl1.ID = "InBetweenControl1";
+ TestValidationControl innerVC = new TestValidationControl();
+ inBetweenControl1.Controls.Add(innerVC);
+ page.Controls.Add(inBetweenControl1);
+ TestValidationControl outerVC = new TestValidationControl();
+ page.Controls.Add(outerVC);
+
+ inBetweenControl1.ValidationErrors.AddError("provider", new ErrorMessage("msg"));
+
+ Assert.AreSame(inBetweenControl1, innerVC.TheValidationContainer);
+ Assert.AreSame(page, outerVC.TheValidationContainer);
+ Assert.AreEqual(1, innerVC.TheValidationContainer.ValidationErrors.GetErrors("provider").Count);
+ Assert.AreEqual(0, outerVC.TheValidationContainer.ValidationErrors.GetErrors("provider").Count);
+ }
+
+ [Test]
+ public void CanReadErrorsFromCrossValidationContainerByDefault()
+ {
+ // containment tree:
+ //
+ // Page : IValidationContainer
+ // - outerVC
+ // - inBetweenControl1 : IValidationContainer
+ // - innerVC
+
+ Spring.Web.UI.Page page = new Spring.Web.UI.Page();
+ Spring.Web.UI.UserControl userControl = new Spring.Web.UI.UserControl();
+ userControl.ID = "userControl";
+ TestValidationControl innerVC = new TestValidationControl();
+ userControl.Controls.Add(innerVC);
+ page.Controls.Add(userControl);
+ TestValidationControl outerVC = new TestValidationControl();
+ page.Controls.Add(outerVC);
+
+ userControl.ValidationErrors.AddError("provider", new ErrorMessage("msg"));
+
+ Assert.AreSame(userControl, innerVC.TheValidationContainer);
+ Assert.AreSame(page, outerVC.TheValidationContainer);
+ Assert.AreEqual(1, innerVC.TheValidationContainer.ValidationErrors.GetErrors("provider").Count);
+ Assert.AreEqual(0, outerVC.TheValidationContainer.ValidationErrors.GetErrors("provider").Count);
+
+ // test local-relative name resolution
+ outerVC.ValidationContainerName = "userControl";
+ Assert.AreEqual(1, outerVC.TheValidationContainer.ValidationErrors.GetErrors("provider").Count);
+
+ // test global name resolution
+ outerVC.ValidationContainerName = "::userControl";
+ Assert.AreEqual(1, outerVC.TheValidationContainer.ValidationErrors.GetErrors("provider").Count);
+ }
+
#region TestValidationControl
private class TestValidationControl : AbstractValidationControl