From 71f2834158d0be113fcdb5dbefaa1677b030c0a8 Mon Sep 17 00:00:00 2001 From: eeichinger Date: Thu, 16 Apr 2009 23:07:09 +0000 Subject: [PATCH] fixed SPRNET-1118 + added sample "NestedEmployeeInfo" to WebQuickStart --- .../DataBinding/Default.aspx | 4 + .../NestedEmployeeInfo/Default.aspx | 25 +++ .../NestedEmployeeInfo/Default.aspx.cs | 9 ++ .../EmployeeInfoEditor.ascx | 87 +++++++++++ .../EmployeeInfoEditor.ascx.cs | 46 ++++++ .../RobustEmployeeInfo/Default.aspx | 4 +- .../RobustEmployeeInfo/Default.aspx.cs | 2 +- .../src/Spring.WebQuickStart.2005/Web.Config | 144 +++++++++--------- .../UI/Controls/AbstractValidationControl.cs | 41 ++++- src/Spring/Spring.Web/Web/UI/UserControl.cs | 6 +- .../AbstractValidationControlTests.cs | 74 +++++++++ 11 files changed, 358 insertions(+), 84 deletions(-) create mode 100644 examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/Default.aspx create mode 100644 examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/Default.aspx.cs create mode 100644 examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/EmployeeInfoEditor.ascx create mode 100644 examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/EmployeeInfoEditor.ascx.cs diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/Default.aspx b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/Default.aspx index 1bf778b9..75672ee1 100644 --- a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/Default.aspx +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/Default.aspx @@ -35,6 +35,10 @@

Demonstrates basic model validation capabilities

+

Nested Employee Info

+

+ Demonstrates basic model validation capabilities on controls +

Lists

Demonstrates bi-directional binding of multi-selection listboxes diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/Default.aspx b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/Default.aspx new file mode 100644 index 00000000..3e00668a --- /dev/null +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/DataBinding/NestedEmployeeInfo/Default.aspx @@ -0,0 +1,25 @@ +<%@ Page Language="C#" EnableViewState="false" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="DataBinding_RobustEmployeeInfo_Default" %> +<%-- @ Reference Control="EmployeeInfoEditor.ascx" --%> +<%@ Register TagPrefix="user" TagName="EmployeeInfoEditor" Src="EmployeeInfoEditor.ascx" %> + + + + + Data Binding - Nested Employee Info example + + +

Welcome to Spring.NET Web Framework Quick Start Guide

+

Bi-directional DataBinding

+
+
+

Nested Employee Info example

+

+ This is the same as Robust Employee Info example except that all controls are placed on nested a user control +

+ + + +
+
+ + 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.

+ + 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
Employee ID: - +