#region License
/*
* Copyright 2002-2004 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.ComponentModel;
using System.Reflection;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Common.Logging;
using Spring.Core;
using Spring.Core.TypeResolution;
using Spring.DataBinding;
using Spring.Globalization;
using Spring.Objects;
using Spring.Util;
using AttributeCollectionSystem.Web.UI.AttributeCollection;
using BindingDirectionSpring.DataBinding.BindingDirection;
#endregion
namespace Spring.Web.UI.Controls{
/// <summary>
/// Any WebControl placed on a DataBindingPanel may be bound
/// to a model by adding an attribute "BindingTarget"
/// </summary>
/// <author>Erich Eichinger</author>
[PersistChildren(true),
ToolboxData("<{0}:DataBindingPanel runat=\"server\" Width=\"125px\" Height=\"50px\"> </{0}:DataBindingPanel>"),
ParseChildren(false),
#if NET_1_1
Designer("System.Web.UI.Design.WebControls.PanelDesigner, System.Design, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"),
#else
Designer("System.Web.UI.Design.WebControls.PanelContainerDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"),
#endif
AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal),
AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
public class DataBindingPanel : Panel
{
private const string ATTR_BINDINGTARGET = "BindingTarget";
private const string ATTR_BINDINGSOURCE = "BindingSource";
private const string ATTR_BINDINGDIRECTION = "BindingDirection";
private const string ATTR_BINDINGFORMATTER = "BindingFormatter";
private const string ATTR_BINDINGTYPE = "BindingType";
private const string ATTR_MESSAGEID = "MessageId";
private const string ATTR_ERRORPROVIDERS = "ErrorProviders";
private static readonly ILog Log = LogManager.GetLogger(typeof(DataBindingPanel));
private delegate void TraversalAction(IWebDataBound bindingContainer, WebControl wc);
/// <summary>
/// Registers this control for the <see cref="UserControl.DataBindingsInitialized"/> event of it's container.
/// </summary>
protected override void OnInit(EventArgs e)
{
RegisterDataControl(this, new EventHandler(BindingOwner_DataBindingsInitialized));
base.OnInit(e);
}
/// <summary>
/// Overridden to remove custom binding attributes before rendering.
/// </summary>
/// <param name="e"></param>
protected override void OnPreRender(EventArgs e)
{
// remove custom attributes
if (this.HasControls())
{
this.TraverseControls(null, this.Controls, new TraversalAction(this.RemoveBindingAttributes));
}
base.OnPreRender(e);
}
/// <summary>
/// Overriden to suppress rendering this control's tag
/// </summary>
/// <param name="writer"></param>
protected override void Render(HtmlTextWriter writer)
{
base.RenderContents(writer);
}
/// <summary>
/// Called by the containing <see cref="UserControl"/> if bindings must be initialized.
/// </summary>
private void BindingOwner_DataBindingsInitialized(object sender, EventArgs e)
{
IWebDataBound bindingContainer = (IWebDataBound) sender;
if (this.HasControls())
{
this.TraverseControls(bindingContainer, this.Controls, new TraversalAction(this.BindControl));
}
}
/// <summary>
/// Adds all controls on this panel to the containing <see cref="UserControl.BindingManager"/>'s binding collection.
/// </summary>
/// <param name="bindingContainer">the usercontrol containing this panel</param>
/// <param name="controls">the <see cref="ControlCollection"/> of controls to be bound</param>
/// <param name="action">action to be performed on matching controls</param>
private void TraverseControls(IWebDataBound bindingContainer, ControlCollection controls, TraversalAction action)
{
foreach (Control control in controls)
{
// DataBindingPanels must not be nested
if (control is DataBindingPanel)
{
throw new HttpException("Controls of type DataBindingPanel must not be nested");
}
// abort recursion on LiteralControl or nested UserControl
if (control is LiteralControl
|| control is UserControl)
{
continue;
}
// if it's a WebControl, check for binding-related attributes
WebControl wc = control as WebControl;
if (wc != null)
{
try
{
action(bindingContainer, wc);
}
catch (Exception ex)
{
string msg =
string.Format("Error executing action on control '{0}' of type '{1}'", wc.UniqueID,
wc.GetType().FullName);
Log.Error(msg, ex);
throw new HttpException(msg, ex);
}
}
if (control.HasControls())
{
this.TraverseControls(bindingContainer, control.Controls, action);
}
}
}
/// <summary>
/// Retrieves custom binding attributes from a webcontrol and adds a new binding
/// instance to the container's <see cref="UserControl.BindingManager"/> if necessary.
/// </summary>
private void BindControl(IWebDataBound dataBound, WebControl theControl)
{
// special handling of adapted controls
DataBindingAdapter adapterControl = theControl as DataBindingAdapter;
Control wc = (adapterControl != null && adapterControl.WrappedControl != null) ? adapterControl.WrappedControl : theControl;
AttributeCollection attributeCollection = theControl.Attributes;
string bindingTarget = attributeCollection[ATTR_BINDINGTARGET];
// at least a BindingTarget must be specified
if (bindingTarget == null)
{
return;
}
attributeCollection.Remove(ATTR_BINDINGTARGET);
// determine direction
BindingDirection bindingDirection = BindingDirection.Bidirectional;
string strBindingDirection = attributeCollection[ATTR_BINDINGDIRECTION];
if (strBindingDirection != null)
{
bindingDirection = (BindingDirection) Enum.Parse(typeof(BindingDirection), strBindingDirection);
}
// determine BindingSource
string bindingSource = attributeCollection[ATTR_BINDINGSOURCE];
if (bindingSource == null)
{
bindingSource = AutoProbeSourceProperty(wc);
}
attributeCollection.Remove(ATTR_BINDINGSOURCE);
// get formatter if any
IFormatter bindingFormatter = null;
string bindingFormatterName = attributeCollection[ATTR_BINDINGFORMATTER];
if (bindingFormatterName != null)
{
bindingFormatter = (IFormatter) dataBound.ApplicationContext.GetObject(bindingFormatterName);
attributeCollection.Remove(ATTR_BINDINGFORMATTER);
}
// determine source expression
string containerName = dataBound.UniqueID;
string controlName = wc.UniqueID;
string relativeControlName = null;
if ( dataBound is System.Web.UI.Page )
{
relativeControlName = string.Format("FindControl('{0}')", controlName);
}
else if ( (Control)dataBound != this.NamingContainer)
{
relativeControlName = (controlName.StartsWith(containerName)) ? controlName.Substring(containerName.Length + 1) : controlName;
relativeControlName = string.Format("FindControl('{0}')", relativeControlName);
}
else
{
relativeControlName = wc.ID;
}
// if no bindingSource, expression evaluates to the bound control
bindingSource = (StringUtils.HasLength(bindingSource))
? relativeControlName + "." + bindingSource
: relativeControlName;
Log.Debug(
string.Format("binding control '{0}' relative to '{1}' using expression '{2}'", controlName,
containerName, bindingSource));
//get bindingType if any
IBinding binding = null;
string bindingTypeName = attributeCollection[ATTR_BINDINGTYPE];
if (bindingTypeName == null)
{
bindingTypeName = AutoProbeBindingType(wc);
}
// get messageId and errorProviders list
string messageId = attributeCollection[ATTR_MESSAGEID];
string errorProvidersText = attributeCollection[ATTR_ERRORPROVIDERS];
string[] errorProviders = null;
if (StringUtils.HasLength(errorProvidersText))
{
errorProviders = (string[])new Spring.Core.TypeConversion.StringArrayConverter().ConvertFrom(errorProvidersText);
}
// add binding to BindingManager
if (bindingTypeName != null)
{
binding = CreateBindingInstance(bindingTypeName, bindingSource, bindingTarget, bindingDirection, bindingFormatter);
binding = dataBound.BindingManager.AddBinding(binding);
}
else
{
binding = dataBound.BindingManager.AddBinding(bindingSource, bindingTarget, bindingDirection, bindingFormatter);
}
// set error message
if (StringUtils.HasLength(messageId))
{
binding.SetErrorMessage( messageId, errorProviders );
}
}
/// <summary>
/// Removes custom binding attributes from a webcontrol to avoid them being rendered.
/// </summary>
private void RemoveBindingAttributes(IWebDataBound dataBound, WebControl wc)
{
AttributeCollection attributeCollection = wc.Attributes;
attributeCollection.Remove(ATTR_BINDINGTARGET);
attributeCollection.Remove(ATTR_BINDINGSOURCE);
attributeCollection.Remove(ATTR_BINDINGTYPE);
attributeCollection.Remove(ATTR_BINDINGDIRECTION);
attributeCollection.Remove(ATTR_BINDINGFORMATTER);
}
/// <summary>
/// Probe for bindingType of know controls
/// </summary>
/// <param name="wc">the control, who's bindingType is to be determined</param>
/// <returns>null, if standard binding is to be used or the fully qualified typename of the binding implementation</returns>
protected virtual string AutoProbeBindingType(Control wc)
{
if (wc is ListBox && ((ListBox) wc).SelectionMode == ListSelectionMode.Multiple)
{
return typeof(MultipleSelectionListControlBinding).FullName;
}
return null;
}
/// <summary>
/// Probes for a few know controls and their properties.
/// </summary>
/// <returns>
/// The 'BindingSource' expression to be used for binding this control.
/// </returns>
protected virtual string AutoProbeSourceProperty(Control wc)
{
if (wc is ListBox && ((ListBox) wc).SelectionMode == ListSelectionMode.Multiple)
{
return null; // force evaluate to control itself
}
else if (wc is CheckBoxList)
{
return "SelectedValues";
}
else if (wc is ListControl)
{
return "SelectedValue";
}
else if (wc is CheckBox)
{
return "Checked";
}
else if (wc is TextBox)
{
return "Text";
}
#if NET_2_0
else if (wc is HiddenField)
{
return "Value";
}
#endif
else if (wc is RadioButtonGroup)
{
return "Value";
}
else if (wc is DataBindingAdapter)
{
throw new ArgumentNullException("Attribute 'BindingSource' is mandatory when using DataBindingAdapter");
}
throw new ArgumentNullException("Attribute 'BindingSource' is missing and control is of unknown type");
}
private static IBinding CreateBindingInstance(string bindingTypeName, string bindingSource, string bindingTarget, BindingDirection bindingDirection, IFormatter bindingFormatter)
{
IBinding binding;
Type bindingType = TypeResolutionUtils.ResolveType(bindingTypeName);
ConstructorInfo ctor =
bindingType.GetConstructor(new Type[] {typeof(string), typeof(string), typeof(BindingDirection), typeof(IFormatter)});
if (ctor == null)
{
throw new ArgumentException(string.Format("Specified BindingType '{0}' does not implement constructor (string,string,BindingDirection,IFormatter)",bindingTypeName));
}
binding =
(IBinding)
ObjectUtils.InstantiateType(ctor, new object[] {bindingSource, bindingTarget, bindingDirection, bindingFormatter});
return binding;
}
private static void RegisterDataControl(Control control, EventHandler initializeBindingHandler)
{
GetBindingContainerControl(control).DataBindingsInitialized += initializeBindingHandler;
}
private static IWebDataBound GetBindingContainerControl(Control control)
{
Control parent = control;
while (parent != null)
{
if (parent is IWebDataBound)
{
return (IWebDataBound) parent;
}
parent = parent.Parent;
}
return null;
}
}
}
|