diff --git a/examples/Spring/Spring.WebQuickStart/Spring.WebQuickStart.2008.sln b/examples/Spring/Spring.WebQuickStart/Spring.WebQuickStart.2008.sln index 13b26c2e..d2c3e526 100644 --- a/examples/Spring/Spring.WebQuickStart/Spring.WebQuickStart.2008.sln +++ b/examples/Spring/Spring.WebQuickStart/Spring.WebQuickStart.2008.sln @@ -19,7 +19,7 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Spring.WebQuickStart.2005", Release.AspNetCompiler.ForceOverwrite = "true" Release.AspNetCompiler.FixedNames = "false" Release.AspNetCompiler.Debug = "False" - VWDPort = "81" + VWDPort = "4724" DefaultWebSiteLanguage = "Visual C#" EndProjectSection EndProject diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Default.aspx b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Default.aspx index 460b5055..195bd85b 100644 --- a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Default.aspx +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Default.aspx @@ -68,6 +68,10 @@ compose advanced validation rules that leverage validation groups, conditional validation, validator composition, as well as how to create and use your custom validators.

+

Navigation Flow

+

+ Demoes configurable navigation flow capabilities. +

diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Default.aspx b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Default.aspx new file mode 100644 index 00000000..2594d32e --- /dev/null +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Default.aspx @@ -0,0 +1,32 @@ +<%@ Page Language="C#" EnableViewState="true" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="Navigation_Default" %> +<%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %> + + +

+ Just like in the previous example, you should put a breakpoint + 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. +

+ + + + + + + + + + + + + + + + +
+ You need to enter a value greater than 21 to pass. +
Your age: + +
+
+
\ No newline at end of file diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Default.aspx.cs b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Default.aspx.cs new file mode 100644 index 00000000..1c953633 --- /dev/null +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Default.aspx.cs @@ -0,0 +1,38 @@ +using System; + +/// +/// 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 Navigation_Default : Spring.Web.UI.Page +{ + public int Age; + + protected override void FrameworkInitialize() + { + this.MasterPageFile = "Master.master"; + base.FrameworkInitialize(); + } + + protected void Page_Load( object sender, EventArgs e ) + { } + + + protected void btnContinue_Click( object sender, EventArgs e ) + { + if (Age > 21) + { + SetResult( "ok" ); + } + else + { + Args["input"] = txtAge.Text; + SetResult( "invalid_input" ); + } + } + + protected void btnReset_Click( object sender, EventArgs e ) + { + SetResult( "start" ); + } +} diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/InvalidInput.aspx b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/InvalidInput.aspx new file mode 100644 index 00000000..dfc693a4 --- /dev/null +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/InvalidInput.aspx @@ -0,0 +1,22 @@ +<%@ Page Language="C#" EnableViewState="true" AutoEventWireup="true" CodeFile="InvalidInput.aspx.cs" Inherits="Navigation_InvalidInput" %> +<%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %> + + +

+ The value you entered is invalid +

+ + + + + + + + + + +
Age you entered: + +
 
+
+
diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/InvalidInput.aspx.cs b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/InvalidInput.aspx.cs new file mode 100644 index 00000000..fdb8f902 --- /dev/null +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/InvalidInput.aspx.cs @@ -0,0 +1,21 @@ +using System; +using System.Diagnostics; + +/// +/// 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 Navigation_InvalidInput : Spring.Web.UI.Page +{ + protected void Page_Load(object sender, EventArgs e) + { + string input = (string) this.Context.Items["input"]; + } + + + protected void btnBack_Click(object sender, EventArgs e) + { + SetResult("start"); + } +} + diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Master.master b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Master.master new file mode 100644 index 00000000..7e2e84a9 --- /dev/null +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Master.master @@ -0,0 +1,21 @@ +<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Master.master.cs" Inherits="Navigation_Master" %> + + + + + + Prefix! + + + + +

Welcome to Spring.NET Web Framework Quick Start Guide

+
+
+

Navigation example

+ + +
+
+ + diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Master.master.cs b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Master.master.cs new file mode 100644 index 00000000..85904a58 --- /dev/null +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Master.master.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; + +public partial class Navigation_Master : Spring.Web.UI.MasterPage +{ + protected void Page_Load( object sender, EventArgs e ) + { + + } +} diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Ok.aspx b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Ok.aspx new file mode 100644 index 00000000..d590e0c8 --- /dev/null +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Ok.aspx @@ -0,0 +1,21 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Ok.aspx.cs" Inherits="Navigation_Ok" %> + + +

+ Passcode you entered was ok +

+ + + + + + + + + + +
The passcode is correct: + +
 
+
+
\ No newline at end of file diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Ok.aspx.cs b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Ok.aspx.cs new file mode 100644 index 00000000..0ec39b9c --- /dev/null +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Ok.aspx.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; + +public partial class Navigation_Ok : Spring.Web.UI.Page +{ + protected void Page_Load( object sender, EventArgs e ) + { + } + + protected void btnRestart_Click(object sender, EventArgs e) + { + SetResult("start"); + } +} diff --git a/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Web.config b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Web.config new file mode 100644 index 00000000..3aa4213f --- /dev/null +++ b/examples/Spring/Spring.WebQuickStart/src/Spring.WebQuickStart.2005/Navigation/Web.config @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Spring/Spring.Core/Objects/Factory/Support/AbstractObjectFactory.cs b/src/Spring/Spring.Core/Objects/Factory/Support/AbstractObjectFactory.cs index d543441e..b4bd41f0 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Support/AbstractObjectFactory.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Support/AbstractObjectFactory.cs @@ -1843,7 +1843,7 @@ namespace Spring.Objects.Factory.Support || suppressConfigure) { // Clone ObjectDefinition - mergedObjectDefinition = new RootObjectDefinition( mergedObjectDefinition ); + mergedObjectDefinition = CreateRootObjectDefinition(mergedObjectDefinition); mergedObjectDefinition.IsSingleton = false; if (arguments != null) { diff --git a/src/Spring/Spring.Web/Objects/Factory/Support/WebObjectUtils.cs b/src/Spring/Spring.Web/Objects/Factory/Support/WebObjectUtils.cs index 51dc3748..a14eb542 100644 --- a/src/Spring/Spring.Web/Objects/Factory/Support/WebObjectUtils.cs +++ b/src/Spring/Spring.Web/Objects/Factory/Support/WebObjectUtils.cs @@ -39,7 +39,7 @@ namespace Spring.Objects.Factory.Support /// Aleksandar Seovic public sealed class WebObjectUtils { - private static ILog s_log = LogManager.GetLogger(typeof(WebObjectUtils)); + private static ILog s_log = LogManager.GetLogger( typeof( WebObjectUtils ) ); // CLOVER:OFF @@ -64,7 +64,7 @@ namespace Spring.Objects.Factory.Support ///

/// private WebObjectUtils() - {} + { } // CLOVER:ON @@ -84,51 +84,33 @@ namespace Spring.Objects.Factory.Support /// page could not be instantiated (for whatever reason, such as the /// ASPX not actually existing). /// - public static IHttpHandler CreatePageInstance(string pageUrl) + public static IHttpHandler CreatePageInstance( string pageUrl ) { if (s_log.IsDebugEnabled) { - s_log.Debug("creating page instance '" + pageUrl + "'"); + s_log.Debug( "creating page instance '" + pageUrl + "'" ); } - if (HttpContext.Current == null) + + HttpContext ctx = HttpContext.Current; + if (ctx == null) { throw new ObjectCreationException( - "Unable to instantiate page. HttpContext is not defined."); + "Unable to instantiate page. HttpContext is not defined." ); } IHttpHandler page = null; try { -#if NET_1_1 - HttpContext context = HttpContext.Current; - string physicalPath = context.Server.MapPath(pageUrl); - s_log.Debug(string.Format("constructing page virtual path '{0}' from physical file '{1}'", pageUrl, physicalPath)); - page = PageParser.GetCompiledPageInstance(pageUrl, physicalPath, context); -#else - HttpContext ctx = HttpContext.Current; - if (ctx == null) - { - throw new ObjectCreationException("Unable to get page type. HttpContext is not defined."); - } - - string rootedVPath = WebUtils.CombineVirtualPaths(ctx.Request.CurrentExecutionFilePath, pageUrl); - if (s_log.IsDebugEnabled) - { - s_log.Debug("page vpath is " + rootedVPath); - } - - page = BuildManager.CreateInstanceFromVirtualPath(rootedVPath, - typeof(IHttpHandler)) as IHttpHandler; -#endif + page = CreateHandler( ctx, pageUrl ); } catch (HttpException httpEx) { - string msg = String.Format("Unable to instantiate page [{0}]: {1}", pageUrl, httpEx.Message); - if(httpEx.GetHttpCode() == 404) + string msg = String.Format( "Unable to instantiate page [{0}]: {1}", pageUrl, httpEx.Message ); + if (httpEx.GetHttpCode() == 404) { - throw new FileNotFoundException(msg); + throw new FileNotFoundException( msg ); } - s_log.Error(msg, httpEx); - throw new ObjectCreationException(msg, httpEx); + s_log.Error( msg, httpEx ); + throw new ObjectCreationException( msg, httpEx ); } catch (Exception ex) { @@ -136,17 +118,42 @@ namespace Spring.Objects.Factory.Support FileNotFoundException fnfe = ex as FileNotFoundException; if (fnfe != null) { - string fmsg = String.Format("Unable to instantiate page [{0}]: The file '{1}' does not exist.", pageUrl, fnfe.Message); - throw new FileNotFoundException(fmsg); + string fmsg = String.Format( "Unable to instantiate page [{0}]: The file '{1}' does not exist.", pageUrl, fnfe.Message ); + throw new FileNotFoundException( fmsg ); } - string msg = String.Format("Unable to instantiate page [{0}]", pageUrl); - s_log.Error(msg, ex); - throw new ObjectCreationException(msg, ex); + string msg = String.Format( "Unable to instantiate page [{0}]", pageUrl ); + s_log.Error( msg, ex ); + throw new ObjectCreationException( msg, ex ); } return page; } + /// + /// Creates the raw handler instance without any exception handling + /// + /// + /// + /// + internal static IHttpHandler CreateHandler( HttpContext ctx, string pageUrl ) + { + IHttpHandler page; +#if NET_1_1 + string physicalPath = ctx.Server.MapPath(pageUrl); + s_log.Debug(string.Format("constructing page virtual path '{0}' from physical file '{1}'", pageUrl, physicalPath)); + page = PageParser.GetCompiledPageInstance(pageUrl, physicalPath, ctx); +#else + string rootedVPath = WebUtils.CombineVirtualPaths( ctx.Request.CurrentExecutionFilePath, pageUrl ); + if (s_log.IsDebugEnabled) + { + s_log.Debug( "page vpath is " + rootedVPath ); + } + + page = BuildManager.CreateInstanceFromVirtualPath( rootedVPath, typeof( IHttpHandler ) ) as IHttpHandler; +#endif + return page; + } + /// /// Returns the of the ASPX page /// referred to by the supplied . @@ -177,26 +184,26 @@ namespace Spring.Objects.Factory.Support /// page could not be instantiated (for whatever reason, such as the /// ASPX not actually existing). /// - public static Type GetPageType(string pageUrl) + public static Type GetPageType( string pageUrl ) { - AssertUtils.ArgumentHasText(pageUrl, "pageUrl"); + AssertUtils.ArgumentHasText( pageUrl, "pageUrl" ); HttpContext ctx = HttpContext.Current; if (ctx == null) { - throw new ObjectCreationException("Unable to get page type. HttpContext is not defined."); + throw new ObjectCreationException( "Unable to get page type. HttpContext is not defined." ); } try { - Type pageType = GetCompiledPageType(pageUrl); + Type pageType = GetCompiledPageType( pageUrl ); return pageType; } catch (Exception ex) { - string msg = String.Format("Unable to get page type for url [{0}]", pageUrl); - s_log.Error(msg, ex); - throw new ObjectCreationException(msg, ex); + string msg = String.Format( "Unable to get page type for url [{0}]", pageUrl ); + s_log.Error( msg, ex ); + throw new ObjectCreationException( msg, ex ); } } @@ -211,29 +218,29 @@ namespace Spring.Objects.Factory.Support /// The of the ASPX page /// referred to by the supplied . /// - public static Type GetCompiledPageType(string pageUrl) + public static Type GetCompiledPageType( string pageUrl ) { if (s_log.IsDebugEnabled) { - s_log.Debug("getting page type for " + pageUrl); + s_log.Debug( "getting page type for " + pageUrl ); } - string rootedVPath = WebUtils.CombineVirtualPaths(HttpContext.Current.Request.CurrentExecutionFilePath, pageUrl); + string rootedVPath = WebUtils.CombineVirtualPaths( HttpContext.Current.Request.CurrentExecutionFilePath, pageUrl ); if (s_log.IsDebugEnabled) { - s_log.Debug("page vpath is " + rootedVPath); + s_log.Debug( "page vpath is " + rootedVPath ); } Type pageType = null; #if NET_2_0 - pageType = BuildManager.GetCompiledType(rootedVPath); // requires rooted virtual path! + pageType = BuildManager.GetCompiledType( rootedVPath ); // requires rooted virtual path! #else pageType = CreatePageInstance(pageUrl).GetType(); #endif if (s_log.IsDebugEnabled) { - s_log.Debug(string.Format("got page type '{0}' for vpath '{1}'", pageType.FullName, rootedVPath)); + s_log.Debug( string.Format( "got page type '{0}' for vpath '{1}'", pageType.FullName, rootedVPath ) ); } return pageType; } @@ -242,49 +249,49 @@ namespace Spring.Objects.Factory.Support /// /// Gets the controls type from a given filename /// - public static Type GetControlType(string controlName) + public static Type GetControlType( string controlName ) { - AssertUtils.ArgumentHasText(controlName, "controlName"); + AssertUtils.ArgumentHasText( controlName, "controlName" ); if (s_log.IsDebugEnabled) { - s_log.Debug("getting control type for " + controlName); + s_log.Debug( "getting control type for " + controlName ); } HttpContext ctx = HttpContext.Current; if (ctx == null) { - throw new ObjectCreationException("Unable to get control type. HttpContext is not defined."); + throw new ObjectCreationException( "Unable to get control type. HttpContext is not defined." ); } - string rootedVPath = WebUtils.CombineVirtualPaths(ctx.Request.CurrentExecutionFilePath, controlName); + string rootedVPath = WebUtils.CombineVirtualPaths( ctx.Request.CurrentExecutionFilePath, controlName ); if (s_log.IsDebugEnabled) { - s_log.Debug("control vpath is " + rootedVPath); + s_log.Debug( "control vpath is " + rootedVPath ); } Type controlType = null; try { #if NET_2_0 - controlType = BuildManager.GetCompiledType(rootedVPath); // requires rooted virtual path! + controlType = BuildManager.GetCompiledType( rootedVPath ); // requires rooted virtual path! #else controlType = (Type) miGetCompiledUserControlType.Invoke(null, new object[] { rootedVPath, null, ctx }); #endif } - catch(HttpException httpEx) + catch (HttpException httpEx) { // for better error-handling suppress 404 HttpExceptions here if (httpEx.GetHttpCode() == 404) { - throw new FileNotFoundException(string.Format("Control '{0}' does not exist", rootedVPath)); + throw new FileNotFoundException( string.Format( "Control '{0}' does not exist", rootedVPath ) ); } throw; } if (s_log.IsDebugEnabled) { - s_log.Debug(string.Format("got control type '{0}' for vpath '{1}'", controlType.FullName, rootedVPath)); + s_log.Debug( string.Format( "got control type '{0}' for vpath '{1}'", controlType.FullName, rootedVPath ) ); } return controlType; } diff --git a/src/Spring/Spring.Web/Spring.Web.2003.csproj b/src/Spring/Spring.Web/Spring.Web.2003.csproj index 6cd08a03..a01d0e24 100644 --- a/src/Spring/Spring.Web/Spring.Web.2003.csproj +++ b/src/Spring/Spring.Web/Spring.Web.2003.csproj @@ -295,21 +295,6 @@ SubType = "Code" BuildAction = "Compile" /> - - - - + + + + @@ -430,6 +430,16 @@ SubType = "Code" BuildAction = "Compile" /> + + + + + Code + + + + + + + + + @@ -260,6 +269,7 @@ ASPXCodeBehind + ASPXCodeBehind diff --git a/src/Spring/Spring.Web/Util/WebUtils.cs b/src/Spring/Spring.Web/Util/WebUtils.cs index 237fea4a..05b8aa62 100644 --- a/src/Spring/Spring.Web/Util/WebUtils.cs +++ b/src/Spring/Spring.Web/Util/WebUtils.cs @@ -21,8 +21,7 @@ #region Imports using System; - - +using System.Web.UI; #endregion @@ -241,5 +240,51 @@ namespace Spring.Util } return appRelativeVirtualPath; } + + /// + /// Returns the 'logical' parent of the specified control. Technically when dealing with masterpages and control hierarchy, + /// the order goes controls->masterpage->page. But one often wants the more logical order controls->page->masterpage. + /// + ///the control, who's parent is to be determined. + ///the logical parent or null if the top of the hierarchy is reached. + /// if is null + public static Control GetLogicalParent(Control control) + { + AssertUtils.ArgumentNotNull(control, "control"); + + // to determine "correct" order of bubbling control->page->masterpage, + // the trick below is necessary because technically the hierarchy goes + // control->masterpage->page + if (control is System.Web.UI.Page) + { +#if !NET_2_0 + control = ((Spring.Web.UI.Page)control).Master; +#else + control = ((System.Web.UI.Page)control).Master; +#endif + } + else if (IsMaster(control)) + { + control = null; + } + else if (IsMaster(control.Parent)) + { + control = control.Page; + } + else + { + control = control.Parent; + } + return control; + } + + private static bool IsMaster(Control control) + { +#if !NET_2_0 + return (control is Spring.Web.UI.MasterPage); +#else + return (control is System.Web.UI.MasterPage); +#endif + } } } \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/Support/AbstractHandlerFactory.cs b/src/Spring/Spring.Web/Web/Support/AbstractHandlerFactory.cs index 58e310ff..b5c9161b 100644 --- a/src/Spring/Spring.Web/Web/Support/AbstractHandlerFactory.cs +++ b/src/Spring/Spring.Web/Web/Support/AbstractHandlerFactory.cs @@ -1,7 +1,7 @@ #region License /* - * Copyright © 2002-2005 the original author or authors. + * 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. diff --git a/src/Spring/Spring.Web/Web/Support/DefaultResultFactory.cs b/src/Spring/Spring.Web/Web/Support/DefaultResultFactory.cs new file mode 100644 index 00000000..d2ac0b03 --- /dev/null +++ b/src/Spring/Spring.Web/Web/Support/DefaultResultFactory.cs @@ -0,0 +1,34 @@ +#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 + +#endregion + +namespace Spring.Web.Support +{ + public class DefaultResultFactory : IResultFactory + { + public IResult CreateResult(string resultMode, string resultText) + { + return new Result(resultMode, resultText); + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/Support/IHierarchicalWebNavigator.cs b/src/Spring/Spring.Web/Web/Support/IHierarchicalWebNavigator.cs new file mode 100644 index 00000000..f3932e52 --- /dev/null +++ b/src/Spring/Spring.Web/Web/Support/IHierarchicalWebNavigator.cs @@ -0,0 +1,31 @@ +#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 + +#endregion + +namespace Spring.Web.Support +{ + public interface IHierarchicalWebNavigator : IWebNavigator + { + IWebNavigator ParentNavigator { get; } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/Support/IResult.cs b/src/Spring/Spring.Web/Web/Support/IResult.cs new file mode 100644 index 00000000..cb03e182 --- /dev/null +++ b/src/Spring/Spring.Web/Web/Support/IResult.cs @@ -0,0 +1,8 @@ +namespace Spring.Web.Support +{ + public interface IResult + { + void Navigate( object context ); + string GetRedirectUri( object context ); + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/Support/IResultFactory.cs b/src/Spring/Spring.Web/Web/Support/IResultFactory.cs new file mode 100644 index 00000000..749dd61a --- /dev/null +++ b/src/Spring/Spring.Web/Web/Support/IResultFactory.cs @@ -0,0 +1,7 @@ +namespace Spring.Web.Support +{ + public interface IResultFactory + { + IResult CreateResult( string resultMode, string resultText ); + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/Support/IResultWebNavigator.cs b/src/Spring/Spring.Web/Web/Support/IResultWebNavigator.cs new file mode 100644 index 00000000..f125c787 --- /dev/null +++ b/src/Spring/Spring.Web/Web/Support/IResultWebNavigator.cs @@ -0,0 +1,33 @@ +#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.Collections; + +#endregion + +namespace Spring.Web.Support +{ + public interface IResultWebNavigator : IHierarchicalWebNavigator + { + IDictionary Results { get; set; } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/Support/IWebNavigable.cs b/src/Spring/Spring.Web/Web/Support/IWebNavigable.cs new file mode 100644 index 00000000..4717d345 --- /dev/null +++ b/src/Spring/Spring.Web/Web/Support/IWebNavigable.cs @@ -0,0 +1,33 @@ +#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 + + + +#endregion + +namespace Spring.Web.Support +{ + public interface IWebNavigable + { + IWebNavigator WebNavigator { get; } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/Support/IWebNavigator.cs b/src/Spring/Spring.Web/Web/Support/IWebNavigator.cs new file mode 100644 index 00000000..cf452099 --- /dev/null +++ b/src/Spring/Spring.Web/Web/Support/IWebNavigator.cs @@ -0,0 +1,39 @@ +#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 + +#endregion + +namespace Spring.Web.Support +{ + public interface IWebNavigator + { + /// + /// Determines, whether this navigator or one of its parents can + /// navigate to the result specified in . + /// + /// the name of the result + /// true, if this navigator or one of its parents can navigate to the result. + bool CanNavigateTo( string resultName ); + void NavigateTo( string resultName, object context ); + string GetResultUri( string resultName, object context ); + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/Support/PageHandlerFactory.cs b/src/Spring/Spring.Web/Web/Support/PageHandlerFactory.cs index 8e1b7743..466d116a 100644 --- a/src/Spring/Spring.Web/Web/Support/PageHandlerFactory.cs +++ b/src/Spring/Spring.Web/Web/Support/PageHandlerFactory.cs @@ -1,7 +1,7 @@ #region License /* - * Copyright 2002-2004 the original author or authors. + * 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. @@ -118,7 +118,8 @@ namespace Spring.Web.Support } else { - handler = WebObjectUtils.CreatePageInstance(url); + handler = WebObjectUtils.CreateHandler(context, url); + // is this a nested call (HttpServerUtility.Transfer() or HttpServerUtility.Execute())? if (context.Handler != null) { diff --git a/src/Spring/Spring.Web/Web/Support/Result.cs b/src/Spring/Spring.Web/Web/Support/Result.cs index 528676c9..086a3972 100644 --- a/src/Spring/Spring.Web/Web/Support/Result.cs +++ b/src/Spring/Spring.Web/Web/Support/Result.cs @@ -1,7 +1,7 @@ #region License /* - * Copyright © 2002-2005 the original author or authors. + * 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. @@ -24,6 +24,7 @@ using System; using System.Collections; using System.Text; using System.Web; +using Spring.Collections; using Spring.Expressions; using Spring.Util; @@ -73,7 +74,7 @@ namespace Spring.Web.Support /// Aleksandar Seovic /// Matan Shapira [Serializable] - public class Result + public class Result : IResult { #region Constants @@ -122,15 +123,52 @@ namespace Spring.Web.Support /// If the supplied is or /// contains only whitespace character(s). /// - public Result(string result) + /// if the result mode is unknown. + public Result( string result ) { - AssertUtils.ArgumentHasText(result, "result"); - result = ExtractAndSetResultMode(result); - int indexOfQueryStringDelimiter = result.IndexOf('?'); + AssertUtils.ArgumentHasText( result, "result" ); + + result = ExtractAndSetResultMode( result ); + + int indexOfQueryStringDelimiter = result.IndexOf( '?' ); if (indexOfQueryStringDelimiter > 0) { - ParseParameters(result.Substring(indexOfQueryStringDelimiter + 1)); - result = result.Substring(0, indexOfQueryStringDelimiter); + ParseParameters( result.Substring( indexOfQueryStringDelimiter + 1 ) ); + result = result.Substring( 0, indexOfQueryStringDelimiter ); + } + targetPage = result.Trim(); + } + + /// + /// Creates a new instance of the class. + /// + /// + ///

+ /// See both the class documentation () + /// and the reference documentation for the Spring.Web library for a + /// discussion and examples of what values the supplied + /// can have. + ///

+ ///
+ /// The desired result mode. May be null to use default mode. + /// The result descriptor (without resultMode prefix!). + /// + /// If the supplied is or + /// contains only whitespace character(s). + /// + /// if the result mode is unknown. + public Result( string resultMode, string resultText ) + { + AssertUtils.ArgumentHasText( resultText, "resultText" ); + + this.SetResultMode( resultMode ); + + string result = resultText; + int indexOfQueryStringDelimiter = result.IndexOf( '?' ); + if (indexOfQueryStringDelimiter > 0) + { + ParseParameters( result.Substring( indexOfQueryStringDelimiter + 1 ) ); + result = result.Substring( 0, indexOfQueryStringDelimiter ); } targetPage = result.Trim(); } @@ -202,23 +240,23 @@ namespace Spring.Web.Support /// Navigates to the /// defined by this result. ///
- /// + /// /// The context object for parameter resolution. This is typically /// a . /// - public virtual void Navigate(object page) + public virtual void Navigate( object context ) { switch (Mode) { case ResultMode.Redirect: - DoRedirect(page); + DoRedirect( context ); break; case ResultMode.Transfer: case ResultMode.TransferNoPreserve: - DoTransfer(page); + DoTransfer( context ); break; default: - throw new ArgumentOutOfRangeException(string.Format("Unknown ResultMode {0}", Mode)); + throw new ArgumentOutOfRangeException( string.Format( "Unknown ResultMode {0}", Mode ) ); } } @@ -226,33 +264,33 @@ namespace Spring.Web.Support /// Performs a server-side transfer to the /// defined by this result. /// - /// + /// /// The context object for parameter resolution. This is typically /// a . /// /// - protected virtual void DoTransfer(object page) + protected virtual void DoTransfer( object context ) { HttpContext ctx = HttpContext.Current; - SetTransferParameters(ctx.Items, page); - ctx.Server.Transfer(TargetPage, PreserveForm); + SetTransferParameters( ctx.Items, context ); + ctx.Server.Transfer( GetResolvedTargetPage( context ), PreserveForm ); } /// /// Resolves transfer parameters and stores them into instance. /// /// - /// - protected void SetTransferParameters(IDictionary contextDictionary, object page) + /// + protected void SetTransferParameters( IDictionary contextDictionary, object context ) { if (this.parameters != null && this.parameters.Count > 0) { foreach (DictionaryEntry entry in this.parameters) { string value = entry.Value.ToString(); - if (IsRuntimeExpression(value)) + if (IsRuntimeExpression( value )) { - contextDictionary[entry.Key] = ResolveRuntimeExpression(page, value); + contextDictionary[entry.Key] = ResolveRuntimeExpression( context, value ); } else { @@ -267,14 +305,14 @@ namespace Spring.Web.Support /// defined by this /// result. /// - /// + /// /// The context object for parameter resolution. This is typically /// a . /// /// - protected virtual void DoRedirect(object page) + protected virtual void DoRedirect( object context ) { - HttpContext.Current.Response.Redirect(GetRedirectUri(page)); + HttpContext.Current.Response.Redirect( GetRedirectUri( context ) ); } /// @@ -282,45 +320,74 @@ namespace Spring.Web.Support /// defined by this /// result. /// - /// + /// /// A redirect url string. /// - public string GetRedirectUri(object page) + public virtual string GetRedirectUri( object context ) { - StringBuilder url = new StringBuilder(256); - url.Append(TargetPage); - if (parameters != null && parameters.Count > 0) + string path = GetResolvedTargetPage( context ); + + IDictionary resolvedParameters = null; + if (this.Parameters != null && this.Parameters.Count > 0) + { + resolvedParameters = new CaseInsensitiveHashtable(); + foreach (DictionaryEntry entry in this.Parameters) + { + object key = ResolveRuntimeExpressionIfNecessary( context, entry.Key.ToString() ); + object value = ResolveRuntimeExpressionIfNecessary( context, entry.Value.ToString() ); + resolvedParameters[key] = value; + } + } + + return BuildUrl( path, resolvedParameters ); + } + + protected virtual string BuildUrl( string resolvedPath, IDictionary resolvedParameters ) + { + StringBuilder url = new StringBuilder( 256 ); + url.Append( resolvedPath ); + if (resolvedParameters != null && resolvedParameters.Count > 0) { char separator = '?'; - foreach (DictionaryEntry entry in parameters) + foreach (DictionaryEntry entry in resolvedParameters) { - url.Append(separator); - url.Append(entry.Key).Append('='); - string value = entry.Value.ToString(); - if (IsRuntimeExpression(value)) - { - url.Append(HttpContext.Current.Server.UrlEncode( - ResolveRuntimeExpression(page, value).ToString())); - } - else - { - url.Append(HttpContext.Current.Server.UrlEncode(value)); - } + url.Append( separator ); + url.Append( BuildUrlParameter( entry.Key.ToString(), entry.Value.ToString() ) ); separator = '&'; } } return url.ToString(); } - private static bool IsRuntimeExpression(string value) + protected virtual string BuildUrlParameter( string key, string value ) { - // allow for 2 alternative prefixes (SPRNET-864) - return (value.StartsWith("${") || value.StartsWith("%{")) && value.EndsWith("}"); + return UrlEncode( key ) + "=" + UrlEncode( value ); } - private static object ResolveRuntimeExpression(object page, string value) + protected static string UrlEncode( string value ) { - return ExpressionEvaluator.GetValue(page, value.Substring(2, value.Length - 3)); + HttpContext ctx = HttpContext.Current; + return (ctx == null) ? HttpUtility.UrlEncode( value ) : ctx.Server.UrlEncode( value ); + } + + protected object ResolveRuntimeExpressionIfNecessary( object context, string value ) + { + if (IsRuntimeExpression( value )) + { + return ResolveRuntimeExpression( context, value ); + } + return value; + } + + private static bool IsRuntimeExpression( string value ) + { + // allow for 2 alternative prefixes (SPRNET-864) + return (value.StartsWith( "${" ) || value.StartsWith( "%{" )) && value.EndsWith( "}" ); + } + + private static object ResolveRuntimeExpression( object context, string value ) + { + return ExpressionEvaluator.GetValue( context, value.Substring( 2, value.Length - 3 ) ); } /// @@ -345,44 +412,61 @@ namespace Spring.Web.Support /// If the supplied starts with an illegal /// result mode (see ). /// - private string ExtractAndSetResultMode(string result) + private string ExtractAndSetResultMode( string result ) { - int indexOfResultModeDelimiter = result.IndexOf(':'); + int indexOfResultModeDelimiter = result.IndexOf( ':' ); if (indexOfResultModeDelimiter > 0) { - try - { - Mode = (ResultMode)Enum.Parse(typeof(ResultMode), - result.Substring(0, indexOfResultModeDelimiter), true); - return result.Substring(indexOfResultModeDelimiter + 1); - } - catch - { - throw new ArgumentOutOfRangeException("result", result, "Illegal result mode."); - } + string resultMode = result.Substring( 0, indexOfResultModeDelimiter ); + SetResultMode( resultMode ); + return result.Substring( indexOfResultModeDelimiter + 1 ); } return result; } + /// + /// Set the actual from the parsed string. + /// + /// the parsed result mode + protected virtual void SetResultMode( string resultMode ) + { + try + { + if (StringUtils.HasText( resultMode )) + { + Mode = (ResultMode)Enum.Parse( typeof( ResultMode ), resultMode, true ); + } + } + catch + { + throw new ArgumentOutOfRangeException( "resultMode", resultMode, "Illegal result mode." ); + } + } + + protected string GetResolvedTargetPage( object context ) + { + return ResolveRuntimeExpressionIfNecessary( context, TargetPage ).ToString(); + } + /// /// Parses query parameters from the supplied . /// /// /// The query string (may be ). /// - private void ParseParameters(string queryString) + private void ParseParameters( string queryString ) { - if (StringUtils.HasText(queryString)) + if (StringUtils.HasText( queryString )) { - this.parameters = new Hashtable(); - string[] nameValuePairs = queryString.Split("&,".ToCharArray()); + this.parameters = new CaseInsensitiveHashtable(); + string[] nameValuePairs = queryString.Split( "&,".ToCharArray() ); foreach (string pair in nameValuePairs) { - int n = pair.IndexOf('='); + int n = pair.IndexOf( '=' ); if (n > 0) { - string name = pair.Substring(0, n); - string val = pair.Substring(n + 1).Trim(); + string name = pair.Substring( 0, n ); + string val = pair.Substring( n + 1 ).Trim(); this.parameters[name] = val; } } diff --git a/src/Spring/Spring.Web/Web/Support/ResultFactoryRegistry.cs b/src/Spring/Spring.Web/Web/Support/ResultFactoryRegistry.cs new file mode 100644 index 00000000..a437ab38 --- /dev/null +++ b/src/Spring/Spring.Web/Web/Support/ResultFactoryRegistry.cs @@ -0,0 +1,101 @@ +#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 Spring.Collections; +using Spring.Util; + +#endregion + +namespace Spring.Web.Support +{ + public class ResultFactoryRegistry + { + private static readonly IDictionary s_registeredFactories = new CaseInsensitiveHashtable(); + private static volatile IResultFactory s_defaultFactory; + + static ResultFactoryRegistry() + { + Reset(); + } + + public static void Reset() + { + s_defaultFactory = null; + s_registeredFactories.Clear(); + + SetDefaultFactory( new DefaultResultFactory() ); + + foreach(string resultMode in Enum.GetNames(typeof(ResultMode))) + { + RegisterResultMode( resultMode, DefaultResultFactory ); + } + } + + public static IResultFactory DefaultResultFactory + { + get { return s_defaultFactory; } + } + + public static IResultFactory SetDefaultFactory(IResultFactory resultFactory) + { + AssertUtils.ArgumentNotNull(resultFactory, "resultFactory"); + IResultFactory prevFactory = s_defaultFactory; + s_defaultFactory = resultFactory; + return prevFactory; + } + + public static IResultFactory RegisterResultMode( string resultMode, IResultFactory resultFactory ) + { + lock (s_registeredFactories.SyncRoot) + { + IResultFactory prevFactory = (IResultFactory) s_registeredFactories[resultMode]; + s_registeredFactories[resultMode] = resultFactory; + return prevFactory; + } + } + + public static IResult CreateResult( string resultText ) + { + IResultFactory resultFactory = null; + string resultMode = null; + + int indexOfResultModeDelimiter = resultText.IndexOf( ':' ); + if (indexOfResultModeDelimiter > 0) + { + resultMode = resultText.Substring( 0, indexOfResultModeDelimiter ).Trim(); + resultFactory = (IResultFactory) s_registeredFactories[resultMode]; + resultText = resultText.Substring( indexOfResultModeDelimiter + 1 ); + } + + if (resultFactory == null) + { + resultFactory = s_defaultFactory; + } + + IResult result = resultFactory.CreateResult( resultMode, resultText ); + AssertUtils.ArgumentNotNull(result, "ResultFactories must not return null results"); + return result; + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/Support/ResultWebNavigator.cs b/src/Spring/Spring.Web/Web/Support/ResultWebNavigator.cs new file mode 100644 index 00000000..4219624b --- /dev/null +++ b/src/Spring/Spring.Web/Web/Support/ResultWebNavigator.cs @@ -0,0 +1,169 @@ +#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 Spring.Collections; +using Spring.Core; +using Spring.Util; + +#endregion + +namespace Spring.Web.Support +{ + public class ResultWebNavigator : IResultWebNavigator + { + private IWebNavigator _parentNavigator; + private bool _ignoreCase; + private IDictionary _results; + + public bool IsCaseSensitive + { + get { return !_ignoreCase; } + } + + public virtual IWebNavigator ParentNavigator + { + get { return _parentNavigator; } + set + { + if (_parentNavigator != null) + { + AssertUtils.ArgumentNotNull( _parentNavigator, "Parent", "Navigator already has a parent" ); + } + _parentNavigator = value; + } + } + + /// + /// Gets or sets map of result names to target URLs + /// + public IDictionary Results + { + get + { + return _results; + } + set + { + _results = CreateResultsDictionary(value); + } + } + + public ResultWebNavigator() + :this(null, null, true) + {} + + public ResultWebNavigator( IWebNavigator parent, IDictionary results, bool ignoreCase ) + { + this._parentNavigator = parent; + this._ignoreCase = ignoreCase; + this._results = CreateResultsDictionary( results ); + } + + protected virtual IDictionary CreateResultsDictionary( IDictionary initialResults ) + { + IDictionary newResults = (_ignoreCase) ? new CaseInsensitiveHashtable() : new Hashtable(); + if (initialResults != null) + { + foreach(DictionaryEntry entry in initialResults) + { + newResults[entry.Key.ToString()] = entry.Value; + } + } + return newResults; + } + + public virtual bool CanNavigateTo( string resultName ) + { + if (_results.Contains( resultName )) + { + return true; + } + return (ParentNavigator != null) ? ParentNavigator.CanNavigateTo( resultName ) : false; + } + + /// + /// Redirects user to a URL mapped to specified result name. + /// + /// Name of the result. + /// The context to use for evaluating the SpEL expression in the Result. + public virtual void NavigateTo( string resultName, object context ) + { + IResult result = GetResult( resultName ); + if (result == null) + { + if (ParentNavigator != null) + { + ParentNavigator.NavigateTo( resultName, context ); + return; + } + throw new ArgumentException( string.Format( "No result mapping found for the specified name '{0}'.", resultName ), "resultName" ); + } + result.Navigate( context ); + } + + /// + /// Returns a redirect url string that points to the + /// defined by this + /// result evaluated using this Page for expression + /// + /// Name of the result. + /// The context to use for evaluating the SpEL expression in the Result + /// A redirect url string. + public virtual string GetResultUri( string resultName, object context ) + { + IResult result = GetResult( resultName ); + if (result == null) + { + if (ParentNavigator != null) + { + return result.GetRedirectUri( context ); + } + throw new ArgumentException( string.Format( "No result mapping found for the specified name '{0}'.", resultName ), "resultName" ); + } + return result.GetRedirectUri( context ); + } + + protected IResult GetResult( string name ) + { + object val = _results[name]; + if (val == null) + { + return null; + } + else if (val is IResult) + { + return (IResult)val; + } + else if (val is String) + { + return ResultFactoryRegistry.CreateResult( (string)val ); + } + else + { + throw new TypeMismatchException( + "Unable to create result object. Please use either String or Result instances to define results." ); + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/Support/WebFormsResultWebNavigator.cs b/src/Spring/Spring.Web/Web/Support/WebFormsResultWebNavigator.cs new file mode 100644 index 00000000..6b035054 --- /dev/null +++ b/src/Spring/Spring.Web/Web/Support/WebFormsResultWebNavigator.cs @@ -0,0 +1,98 @@ +#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.Collections; +using System.Web.UI; +using Spring.Util; + +#endregion + +namespace Spring.Web.Support +{ + /// + /// An implementation of specific for s. + /// The navigator hierarchy equals the control hierarchy when using a . + /// + public class WebFormsResultWebNavigator : ResultWebNavigator + { + /// + /// Finds the next up the control hierarchy, + /// starting at the specified . + /// + /// + /// This method checks both, for controls implementing or . In addition + /// when MasterPages are used, it interprets the control hierarchy as control->page->masterpage. + /// + /// the control to start the search with. + /// include checking the control itself or start search with its parent. + /// If found, the next up the hierarchy. null otherwise + public static IWebNavigator FindWebNavigator( Control control, bool includeSelf ) + { + while (control != null) + { + if (!includeSelf) + { + // Get next parent in hierarchy + control = WebUtils.GetLogicalParent( control ); + } + includeSelf = false; + + if (control is IWebNavigable || control is IWebNavigator) + { + return (control is IWebNavigable) ? ((IWebNavigable)control).WebNavigator : (IWebNavigator)control; + } + } + return null; + } + + private Control _owner; + + /// + /// Creates a new instance of a for the specified control. + /// + /// the control to be associated with this navigator. + /// a dictionary containing results + /// determines, whether to interpret result names case-sensitive or not. + public WebFormsResultWebNavigator( Control owner, IDictionary results, bool ignoreCase ) + : base( null, results, ignoreCase ) + { + AssertUtils.ArgumentNotNull(owner, "owner"); + _owner = owner; + } + + public override IWebNavigator ParentNavigator + { + get + { + if (base.ParentNavigator == null) + { + base.ParentNavigator = FindWebNavigator(_owner, false); + } + return base.ParentNavigator; + } + set + { + throw new System.NotSupportedException("cannot set parent navigator on a WebFormsResultWebNavigator"); + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Web/Web/UI/Controls/DataBindingPanel.cs b/src/Spring/Spring.Web/Web/UI/Controls/DataBindingPanel.cs index 446b9dcc..ef84d0b2 100644 --- a/src/Spring/Spring.Web/Web/UI/Controls/DataBindingPanel.cs +++ b/src/Spring/Spring.Web/Web/UI/Controls/DataBindingPanel.cs @@ -280,6 +280,7 @@ namespace Spring.Web.UI.Controls attributeCollection.Remove(ATTR_BINDINGTARGET); attributeCollection.Remove(ATTR_BINDINGSOURCE); attributeCollection.Remove(ATTR_BINDINGTYPE); + attributeCollection.Remove(ATTR_BINDINGDIRECTION); attributeCollection.Remove(ATTR_BINDINGFORMATTER); } diff --git a/src/Spring/Spring.Web/Web/UI/Controls/Form.cs b/src/Spring/Spring.Web/Web/UI/Controls/Form.cs index 58f0adc9..3c7b7f49 100644 --- a/src/Spring/Spring.Web/Web/UI/Controls/Form.cs +++ b/src/Spring/Spring.Web/Web/UI/Controls/Form.cs @@ -71,7 +71,11 @@ namespace Spring.Web.UI.Controls /// /// The url specified here is only rendered, if is true. /// +#if !NET_2_0 public string Action +#else + public new string Action +#endif { get { return this.action; } set { this.action = value; } diff --git a/src/Spring/Spring.Web/Web/UI/Controls/Panel.cs b/src/Spring/Spring.Web/Web/UI/Controls/Panel.cs index edbd3746..e16aeb87 100644 --- a/src/Spring/Spring.Web/Web/UI/Controls/Panel.cs +++ b/src/Spring/Spring.Web/Web/UI/Controls/Panel.cs @@ -88,6 +88,13 @@ namespace Spring.Web.UI.Controls private bool _suppressDependencyInjection; private bool _renderContainerTag; + private string _visibleConditionExpression; + + public string VisibleIf + { + get { return _visibleConditionExpression; } + set { _visibleConditionExpression = value; } + } /// /// This flag controls, whether DI on child controls will be done or not @@ -114,6 +121,12 @@ namespace Spring.Web.UI.Controls /// protected override void Render(HtmlTextWriter writer) { + bool visible = (_visibleConditionExpression != null) + ? Spring.Expressions.ExpressionEvaluator.GetValue(this, _visibleConditionExpression).Equals(true) + : this.Visible; + + if (!visible) return; + if (_renderContainerTag) { base.Render(writer); diff --git a/src/Spring/Spring.Web/Web/UI/MasterPage.cs b/src/Spring/Spring.Web/Web/UI/MasterPage.cs index 748f05e9..d50f8c60 100644 --- a/src/Spring/Spring.Web/Web/UI/MasterPage.cs +++ b/src/Spring/Spring.Web/Web/UI/MasterPage.cs @@ -24,6 +24,7 @@ using System; using System.Collections; using System.Collections.Specialized; using System.Web.UI; +using Spring.Collections; using Spring.Validation; using Spring.Web.UI.Controls; using IValidator = Spring.Validation.IValidator; @@ -100,13 +101,12 @@ namespace Spring.Web.UI #else #region ASP.NET 2.0 Spring Master Page Implementation - /// /// Spring.NET Master Page implementation for ASP.NET 2.0 /// /// Aleksandar Seovic - public class MasterPage : System.Web.UI.MasterPage, IApplicationContextAware, ISupportsWebDependencyInjection + public class MasterPage : System.Web.UI.MasterPage, IApplicationContextAware, ISupportsWebDependencyInjection, IWebNavigable { #region Instance Fields @@ -115,11 +115,21 @@ namespace Spring.Web.UI private IMessageSource messageSource; private IApplicationContext applicationContext; private IApplicationContext defaultApplicationContext; + private IWebNavigator webNavigator; + private IDictionary args; #endregion - #region Control lifecycle methods + #region Lifecycle methods + /// + /// Initialize a new MasterPage instance. + /// + public MasterPage() + { + InitializeNavigationSupport(); + } + /// /// Initializes user control. /// @@ -416,6 +426,148 @@ namespace Spring.Web.UI #endregion + #region Result support + + /// + /// Ensure, that is set to a valid instance. + /// + /// + /// If is not already set, creates and sets a new instance.
+ /// Override this method if you don't want to inject a navigator, but need a different default. + ///
+ protected virtual void InitializeNavigationSupport() + { + webNavigator = new WebFormsResultWebNavigator(this, null, true); + } + + /// + /// Gets/Sets the navigator to be used for handling calls. + /// + public IWebNavigator WebNavigator + { + get + { + return webNavigator; + } + set + { + webNavigator = value; + } + } + + /// + /// Gets or sets map of result names to target URLs + /// + /// + /// Using requires to implement . + /// + [Browsable( false )] + [DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )] + public virtual IDictionary Results + { + get + { + if (WebNavigator is IResultWebNavigator) + { + return ((IResultWebNavigator)WebNavigator).Results; + } + return null; + } + set + { + if (WebNavigator is IResultWebNavigator) + { + ((IResultWebNavigator)WebNavigator).Results = value; + return; + } + throw new NotSupportedException("WebNavigator must be of type IResultWebNavigator to support Results"); + } + } + + /// + /// A convenience, case-insensitive table that may be used to e.g. pass data into SpEL expressions"/>. + /// + /// + /// By default, e.g. passes the control instance into an expression. Using + /// is an easy way to pass additional parameters into the expression + /// + /// // config: + /// + /// <property Name="Results"> + /// <dictionary> + /// <entry key="ok_clicked" value="redirect:~/ShowResult.aspx?result=%{Args['result']}" /> + /// </dictionary> + /// </property> + /// + /// // code: + /// + /// void OnOkClicked(object sender, EventArgs e) + /// { + /// Args["result"] = txtUserInput.Text; + /// SetResult("ok_clicked"); + /// } + /// + /// + public IDictionary Args + { + get + { + if (args == null) + { + args = new CaseInsensitiveHashtable(); + } + return args; + } + } + + /// + /// Redirects user to a URL mapped to specified result name. + /// + /// Result name. + protected void SetResult( string resultName ) + { + WebNavigator.NavigateTo( resultName, this ); + } + + + /// + /// Redirects user to a URL mapped to specified result name. + /// + /// Name of the result. + /// The context to use for evaluating the SpEL expression in the Result. + protected void SetResult( string resultName, object context ) + { + WebNavigator.NavigateTo( resultName, context ); + } + + + /// + /// Returns a redirect url string that points to the + /// defined by this + /// result evaluated using this Page for expression + /// + /// Name of the result. + /// A redirect url string. + protected string GetResultUrl( string resultName ) + { + return ResolveUrl( WebNavigator.GetResultUri( resultName, this ) ); + } + + /// + /// Returns a redirect url string that points to the + /// defined by this + /// result evaluated using this Page for expression + /// + /// Name of the result. + /// The context to use for evaluating the SpEL expression in the Result + /// A redirect url string. + protected string GetResultUrl( string resultName, object context ) + { + return ResolveUrl( WebNavigator.GetResultUri( resultName, context ) ); + } + + #endregion + #region Validation support /// diff --git a/src/Spring/Spring.Web/Web/UI/Page.cs b/src/Spring/Spring.Web/Web/UI/Page.cs index e540fc6b..e93280c7 100644 --- a/src/Spring/Spring.Web/Web/UI/Page.cs +++ b/src/Spring/Spring.Web/Web/UI/Page.cs @@ -1,7 +1,7 @@ #region License /* - * Copyright 2002-2004 the original author or authors. + * 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. @@ -31,9 +31,9 @@ using System.Security.Permissions; using System.Threading; using System.Web; using System.Web.UI; +using Spring.Collections; using Spring.Context; using Spring.Context.Support; -using Spring.Core; using Spring.DataBinding; using Spring.Globalization; using Spring.Globalization.Resolvers; @@ -50,6 +50,10 @@ using IValidator = Spring.Validation.IValidator; namespace Spring.Web.UI { + #region Result support + + #endregion + /// /// Represents an .aspx file, also known as a Web Forms page, requested from a /// server that hosts an ASP.NET Web application. @@ -73,7 +77,7 @@ namespace Spring.Web.UI [AspNetHostingPermission( SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal )] [AspNetHostingPermission( SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal )] public class Page : System.Web.UI.Page, IHttpHandler, IApplicationContextAware, ISharedStateAware, - ISupportsWebDependencyInjection, IWebDataBound, IValidationContainer + ISupportsWebDependencyInjection, IWebDataBound, IValidationContainer, IWebNavigable { #region Constants @@ -108,15 +112,15 @@ namespace Spring.Web.UI private String masterPageFile; #endif private object controller; - private IDictionary sharedState; + private IDictionary sharedState = new CaseInsensitiveHashtable(); private ILocalizer localizer; private ICultureResolver cultureResolver = new DefaultWebCultureResolver(); private IMessageSource messageSource; private IBindingContainer bindingManager; private IValidationErrors validationErrors = new ValidationErrors(); - - private IDictionary results; + private IWebNavigator webNavigator; + private IDictionary args; private IApplicationContext applicationContext; private IApplicationContext defaultApplicationContext; private static readonly string traceCategory = "Spring.Page"; @@ -132,6 +136,11 @@ namespace Spring.Web.UI #region Page lifecycle methods + public Page() + { + InitializeNavigationSupport(); + } + #if !NET_2_0 /// /// Determines the type of request made for the Page class. @@ -1027,39 +1036,97 @@ namespace Spring.Web.UI #region Result support /// - /// Gets or sets map of result names to target URLs + /// Ensure, that is set to a valid instance. /// - [Browsable( false )] - [DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )] - public IDictionary Results + /// + /// If is not already set, creates and sets a new instance.
+ /// Override this method if you don't want to inject a navigator, but need a different default. + ///
+ protected virtual void InitializeNavigationSupport() + { + webNavigator = new WebFormsResultWebNavigator( this, null, true ); + } + + /// + /// Gets/Sets the navigator to be used for handling calls. + /// + public virtual IWebNavigator WebNavigator { get { - if (results == null) - { - results = new Hashtable(); - } - return results; + return webNavigator; } set { - results = new Hashtable(); - foreach (DictionaryEntry entry in value) + webNavigator = value; + } + } + + /// + /// Gets or sets map of result names to target URLs + /// + /// + /// Using requires to implement . + /// + [Browsable( false )] + [DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )] + public virtual IDictionary Results + { + get + { + if (WebNavigator is IResultWebNavigator) { - if (entry.Value is Result) - { - results[entry.Key] = entry.Value; - } - else if (entry.Value is String) - { - results[entry.Key] = new Result( (string)entry.Value ); - } - else - { - throw new TypeMismatchException( - "Unable to create result object. Please use either String or Result instances to define results." ); - } + return ((IResultWebNavigator)WebNavigator).Results; } + return null; + } + set + { + if (WebNavigator is IResultWebNavigator) + { + ((IResultWebNavigator)WebNavigator).Results = value; + return; + } + throw new NotSupportedException( "WebNavigator must be of type IResultWebNavigator to support Results" ); + } + } + + /// + /// A convenience, case-insensitive table that may be used to e.g. pass data into SpEL expressions"/>. + /// + /// + /// By default, e.g. passes the control instance into an expression. Using + /// is an easy way to pass additional parameters into the expression + /// + /// This example shows how to pass an arbitrary value 'age' into a result expression. + /// + /// // config: + /// + /// <property Name="Results"> + /// <dictionary> + /// <entry key="ok_clicked" value="redirect:~/ShowResult.aspx?age=%{Args['age']}" /> + /// </dictionary> + /// </property> + /// + /// // code: + /// + /// void OnOkClicked(object sender, EventArgs e) + /// { + /// Args["result"] = txtAge.Text; + /// SetResult("ok_clicked"); + /// } + /// + /// + /// + public IDictionary Args + { + get + { + if (args == null) + { + args = new CaseInsensitiveHashtable(); + } + return args; } } @@ -1067,23 +1134,21 @@ namespace Spring.Web.UI /// Redirects user to a URL mapped to specified result name. ///
/// Result name. - protected internal void SetResult( string resultName ) + protected void SetResult( string resultName ) { - GetResult( resultName ).Navigate( this ); + WebNavigator.NavigateTo( resultName, this ); } - /// /// Redirects user to a URL mapped to specified result name. /// /// Name of the result. /// The context to use for evaluating the SpEL expression in the Result. - protected internal void SetResult( string resultName, object context ) + protected void SetResult( string resultName, object context ) { - GetResult( resultName ).Navigate( context ); + WebNavigator.NavigateTo( resultName, context ); } - /// /// Returns a redirect url string that points to the /// defined by this @@ -1091,10 +1156,9 @@ namespace Spring.Web.UI /// /// Name of the result. /// A redirect url string. - protected internal string GetResultUrl( string resultName ) + protected string GetResultUrl( string resultName ) { - Result result = GetResult( resultName ); - return ResolveUrl( result.GetRedirectUri( this ) ); + return ResolveUrl( WebNavigator.GetResultUri( resultName, this ) ); } /// @@ -1105,26 +1169,11 @@ namespace Spring.Web.UI /// Name of the result. /// The context to use for evaluating the SpEL expression in the Result /// A redirect url string. - protected internal string GetResultUrl( string resultName, object context ) + protected string GetResultUrl( string resultName, object context ) { - Result result = GetResult( resultName ); - return ResolveUrl( result.GetRedirectUri( context ) ); + return ResolveUrl( WebNavigator.GetResultUri( resultName, context ) ); } - - private Result GetResult( string resultName ) - { - Result result = (Result)Results[resultName]; - if (result == null) - { - throw new ArgumentException( - string.Format( "No URL mapping found for the specified result '{0}'.", resultName ), "resultName" ); - } - return result; - } - - - #endregion #region Validation support diff --git a/src/Spring/Spring.Web/Web/UI/UserControl.cs b/src/Spring/Spring.Web/Web/UI/UserControl.cs index 6512a6c4..764ac286 100644 --- a/src/Spring/Spring.Web/Web/UI/UserControl.cs +++ b/src/Spring/Spring.Web/Web/UI/UserControl.cs @@ -38,6 +38,7 @@ using Spring.Context.Support; using Spring.Core; using Spring.DataBinding; using Spring.Globalization; +using Spring.Util; using Spring.Validation; using Spring.Web.Support; using IValidator = Spring.Validation.IValidator; @@ -51,7 +52,7 @@ namespace Spring.Web.UI /// /// Aleksandar Seovic public class UserControl : System.Web.UI.UserControl, IApplicationContextAware, IWebDataBound, ISupportsWebDependencyInjection, - IPostBackDataHandler,IValidationContainer + IPostBackDataHandler, IValidationContainer, IWebNavigable { #region Static fields @@ -67,10 +68,11 @@ namespace Spring.Web.UI private object controller; private ILocalizer localizer; private IMessageSource messageSource; - private IDictionary sharedState; + private IDictionary sharedState = new CaseInsensitiveHashtable(); private IBindingContainer bindingManager; private IValidationErrors validationErrors = new ValidationErrors(); - private IDictionary results; + private IWebNavigator webNavigator; + private IDictionary args; private IApplicationContext applicationContext; private IApplicationContext defaultApplicationContext; private bool needsUnbind = false; @@ -79,6 +81,14 @@ namespace Spring.Web.UI #region Control lifecycle methods + /// + /// Initialize a new UserControl instance. + /// + public UserControl() + { + InitializeNavigationSupport(); + } + #if !NET_2_0 /// /// Gets a value indicating whether this instance is in design mode. @@ -113,7 +123,7 @@ namespace Spring.Web.UI /// /// Initializes user control. /// - protected override void OnInit(EventArgs e) + protected override void OnInit( EventArgs e ) { InitializeMessageSource(); InitializeBindingManager(); @@ -124,23 +134,23 @@ namespace Spring.Web.UI } else { - LoadModel(LoadModelFromPersistenceMedium()); + LoadModel( LoadModelFromPersistenceMedium() ); } - base.OnInit(e); + base.OnInit( e ); - OnInitializeControls(EventArgs.Empty); + OnInitializeControls( EventArgs.Empty ); } /// /// Raises the event after page initialization. /// - protected internal virtual void OnPreLoadViewState(EventArgs e) + protected internal virtual void OnPreLoadViewState( EventArgs e ) { - EventHandler handler = (EventHandler) base.Events[EventPreLoadViewState]; + EventHandler handler = (EventHandler)base.Events[EventPreLoadViewState]; if (handler != null) { - handler(this, e); + handler( this, e ); } } @@ -158,8 +168,8 @@ namespace Spring.Web.UI /// public event EventHandler PreLoadViewState { - add { base.Events.AddHandler(EventPreLoadViewState, value); } - remove { base.Events.RemoveHandler(EventPreLoadViewState, value); } + add { base.Events.AddHandler( EventPreLoadViewState, value ); } + remove { base.Events.RemoveHandler( EventPreLoadViewState, value ); } } /// @@ -170,16 +180,16 @@ namespace Spring.Web.UI /// has been called during /// /// true if the server control's state changes as a result of the post back; otherwise false. - bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) + bool IPostBackDataHandler.LoadPostData( string postDataKey, NameValueCollection postCollection ) { - return LoadPostData(postDataKey, postCollection); + return LoadPostData( postDataKey, postCollection ); } /// /// This method is called during a postback if this control has been visible when being rendered to the client. /// /// true if the server control's state changes as a result of the post back; otherwise false. - protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) + protected virtual bool LoadPostData( string postDataKey, NameValueCollection postCollection ) { // mark this control for unbinding form data during OnLoad() this.needsUnbind = true; @@ -210,14 +220,14 @@ namespace Spring.Web.UI /// then raises Load event in order to execute all associated handlers. /// /// Event arguments. - protected override void OnLoad(EventArgs e) + protected override void OnLoad( EventArgs e ) { if (IsPostBack && needsUnbind) { // unbind form data UnbindFormData(); } - base.OnLoad(e); + base.OnLoad( e ); } /// @@ -225,32 +235,32 @@ namespace Spring.Web.UI /// PreRender event afterwards. /// /// Event arguments. - protected override void OnPreRender(EventArgs e) + protected override void OnPreRender( EventArgs e ) { if (Visible) { // causes IPostBackDataHandler.LoadPostData() to be called on next postback. // this is used for indicating a required call to UnbindFormData() - Page.RegisterRequiresPostBack(this); + Page.RegisterRequiresPostBack( this ); BindFormData(); if (localizer != null) { - localizer.ApplyResources(this, messageSource, UserCulture); + localizer.ApplyResources( this, messageSource, UserCulture ); } else if (Page.Localizer != null) { - Page.Localizer.ApplyResources(this, messageSource, UserCulture); + Page.Localizer.ApplyResources( this, messageSource, UserCulture ); } } - base.OnPreRender(e); + base.OnPreRender( e ); object modelToSave = SaveModel(); if (modelToSave != null) { - SaveModelToPersistenceMedium(modelToSave); + SaveModelToPersistenceMedium( modelToSave ); } } @@ -264,11 +274,11 @@ namespace Spring.Web.UI /// Raises InitializeControls event. /// /// Event arguments. - protected virtual void OnInitializeControls(EventArgs e) + protected virtual void OnInitializeControls( EventArgs e ) { if (InitializeControls != null) { - InitializeControls(this, e); + InitializeControls( this, e ); } } @@ -280,10 +290,10 @@ namespace Spring.Web.UI /// /// Returns the specified object, with dependencies injected. /// - protected virtual new Control LoadControl(string virtualPath) + protected virtual new Control LoadControl( string virtualPath ) { - Control control = base.LoadControl(virtualPath); - control = WebDependencyInjectionUtils.InjectDependenciesRecursive(defaultApplicationContext, control); + Control control = base.LoadControl( virtualPath ); + control = WebDependencyInjectionUtils.InjectDependenciesRecursive( defaultApplicationContext, control ); return control; } @@ -297,10 +307,10 @@ namespace Spring.Web.UI /// /// Returns the specified object, with dependencies injected. /// - protected virtual new Control LoadControl( Type t, params object[] parameters) + protected virtual new Control LoadControl( Type t, params object[] parameters ) { Control control = base.LoadControl( t, parameters ); - control = WebDependencyInjectionUtils.InjectDependenciesRecursive(defaultApplicationContext, control); + control = WebDependencyInjectionUtils.InjectDependenciesRecursive( defaultApplicationContext, control ); return control; } #endif @@ -339,7 +349,7 @@ namespace Spring.Web.UI /// The default implementation uses to store and retrieve /// the model for the current /// - protected virtual void SaveModelToPersistenceMedium(object modelToSave) + protected virtual void SaveModelToPersistenceMedium( object modelToSave ) { Session[Request.CurrentExecutionFilePath + this.UniqueID + ".Model"] = modelToSave; } @@ -351,7 +361,7 @@ namespace Spring.Web.UI /// This method should be overriden by the developer /// in order to load data model for the page. /// - protected virtual void LoadModel(object savedModel) + protected virtual void LoadModel( object savedModel ) { } @@ -386,7 +396,7 @@ namespace Spring.Web.UI public object Controller { get { return GetController(); } - set { SetController(value); } + set { SetController( value ); } } /// @@ -397,7 +407,7 @@ namespace Spring.Web.UI /// but must ensure to also change the behaviour of accordingly. /// /// Controller for the control. - protected virtual void SetController(object controller) + protected virtual void SetController( object controller ) { this.controller = controller; } @@ -425,7 +435,7 @@ namespace Spring.Web.UI /// protected virtual object GetController() { - if(controller == null) + if (controller == null) { return this; } @@ -440,8 +450,8 @@ namespace Spring.Web.UI /// Returns a thread-safe dictionary that contains state that is shared by /// all instances of this control. /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable( false )] + [DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )] protected IDictionary SharedState { get @@ -451,7 +461,7 @@ namespace Spring.Web.UI string thisTypeKey = this.GetType().FullName + this.GetType().GetHashCode() + ".SharedState"; sharedState = Application[thisTypeKey] as IDictionary; - if (sharedState==null) + if (sharedState == null) { Application.Lock(); try @@ -460,7 +470,7 @@ namespace Spring.Web.UI if (sharedState == null) { sharedState = new SynchronizedHashtable(); - Application.Add(thisTypeKey, sharedState); + Application.Add( thisTypeKey, sharedState ); } } finally @@ -478,103 +488,141 @@ namespace Spring.Web.UI #region Result support /// - /// Gets or sets map of result names to target URLs + /// Ensure, that is set to a valid instance. /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public IDictionary Results + /// + /// If is not already set, creates and sets a new instance.
+ /// Override this method if you don't want to inject a navigator, but need a different default. + ///
+ protected virtual void InitializeNavigationSupport() + { + webNavigator = new WebFormsResultWebNavigator(this, null, true); + } + + /// + /// Gets/Sets the navigator to be used for handling calls. + /// + public IWebNavigator WebNavigator { get { - if (results == null) - { - results = new Hashtable(); - } - return results; + return webNavigator; } set { - results = new Hashtable(); - foreach (DictionaryEntry entry in value) - { - if (entry.Value is Result) - { - results[entry.Key] = entry.Value; - } - else if (entry.Value is String) - { - results[entry.Key] = new Result((string)entry.Value); - } - else - { - throw new TypeMismatchException( - "Unable to create result object. Please use either String or Result instances to define results."); - } - } + webNavigator = value; } } /// - /// Redirects user to a URL mapped to specified result name. + /// Gets or sets map of result names to target URLs /// - /// Result name. - protected void SetResult(string resultName) + /// + /// Using requires to implement . + /// + [Browsable( false )] + [DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )] + public virtual IDictionary Results { - Result result = (Result)Results[resultName]; - if (result == null) + get { - // bubble up result in the hierarchy - Control parent = this.Parent; - while(parent != null) + if (WebNavigator is IResultWebNavigator) { - if (parent is UserControl) - { - ((UserControl)parent).SetResult(resultName); - return; - } - else if (parent is Page) - { - ((Page) parent).SetResult(resultName); - return; - } - parent = parent.Parent; + return ((IResultWebNavigator)WebNavigator).Results; } - throw new ArgumentException(string.Format("No URL mapping found for the specified result '{0}'.", resultName), "resultName"); + return null; } + set + { + if (WebNavigator is IResultWebNavigator) + { + ((IResultWebNavigator)WebNavigator).Results = value; + return; + } + throw new NotSupportedException("WebNavigator must be of type IResultWebNavigator to support Results"); + } + } - result.Navigate(this); + /// + /// A convenience, case-insensitive table that may be used to e.g. pass data into SpEL expressions"/>. + /// + /// + /// By default, e.g. passes the control instance into an expression. Using + /// is an easy way to pass additional parameters into the expression + /// + /// // config: + /// + /// <property Name="Results"> + /// <dictionary> + /// <entry key="ok_clicked" value="redirect:~/ShowResult.aspx?result=%{Args['result']}" /> + /// </dictionary> + /// </property> + /// + /// // code: + /// + /// void OnOkClicked(object sender, EventArgs e) + /// { + /// Args["result"] = txtUserInput.Text; + /// SetResult("ok_clicked"); + /// } + /// + /// + public IDictionary Args + { + get + { + if (args == null) + { + args = new CaseInsensitiveHashtable(); + } + return args; + } } /// /// Redirects user to a URL mapped to specified result name. /// /// Result name. + protected void SetResult( string resultName ) + { + WebNavigator.NavigateTo( resultName, this ); + } + + + /// + /// Redirects user to a URL mapped to specified result name. + /// + /// Name of the result. /// The context to use for evaluating the SpEL expression in the Result. - protected void SetResult(string resultName, object context) + protected void SetResult( string resultName, object context ) { - Result result = (Result)Results[resultName]; - if (result == null) - { - // bubble up result in the hierarchy - Control parent = this.Parent; - while (parent != null) - { - if (parent is UserControl) - { - ((UserControl)parent).SetResult(resultName, context); - return; - } - else if (parent is Page) - { - ((Page)parent).SetResult(resultName, context); - return; - } - parent = parent.Parent; - } - throw new ArgumentException(string.Format("No URL mapping found for the specified result '{0}'.", resultName), "resultName"); - } + WebNavigator.NavigateTo( resultName, context ); + } - result.Navigate(context); + + /// + /// Returns a redirect url string that points to the + /// defined by this + /// result evaluated using this Page for expression + /// + /// Name of the result. + /// A redirect url string. + protected string GetResultUrl( string resultName ) + { + return ResolveUrl( WebNavigator.GetResultUri( resultName, this ) ); + } + + /// + /// Returns a redirect url string that points to the + /// defined by this + /// result evaluated using this Page for expression + /// + /// Name of the result. + /// The context to use for evaluating the SpEL expression in the Result + /// A redirect url string. + protected string GetResultUrl( string resultName, object context ) + { + return ResolveUrl( WebNavigator.GetResultUri( resultName, context ) ); } #endregion @@ -598,7 +646,7 @@ namespace Spring.Web.UI /// /// True if all of the specified validators are valid, False otherwise. /// - public bool Validate(object validationContext, params IValidator[] validators) + public bool Validate( object validationContext, params IValidator[] validators ) { IDictionary contextParams = CreateValidatorParameters(); bool result = true; @@ -606,9 +654,9 @@ namespace Spring.Web.UI { if (validator == null) { - throw new ArgumentException("Validator is not defined."); + 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; @@ -660,7 +708,7 @@ namespace Spring.Web.UI /// Initializes the data bindings. ///
protected virtual void InitializeDataBindings() - {} + { } /// /// Returns the key to be used for looking up a cached @@ -707,7 +755,7 @@ namespace Spring.Web.UI this.bindingManager = sharedState[key] as BaseBindingManager; if (this.bindingManager == null) { - lock(sharedState.SyncRoot) + lock (sharedState.SyncRoot) { this.bindingManager = sharedState[key] as BaseBindingManager; if (this.bindingManager == null) @@ -715,10 +763,10 @@ namespace Spring.Web.UI try { this.bindingManager = CreateBindingManager(); - if(bindingManager == null) + if (bindingManager == null) { - throw new ArgumentNullException("bindingManager", - "CreateBindingManager() must not return null"); + throw new ArgumentNullException( "bindingManager", + "CreateBindingManager() must not return null" ); } InitializeDataBindings(); } @@ -728,7 +776,7 @@ namespace Spring.Web.UI throw; } sharedState[key] = this.bindingManager; - this.OnDataBindingsInitialized(EventArgs.Empty); + this.OnDataBindingsInitialized( EventArgs.Empty ); } } } @@ -737,13 +785,13 @@ namespace Spring.Web.UI /// /// Raises the event. /// - protected virtual void OnDataBindingsInitialized(EventArgs e) + protected virtual void OnDataBindingsInitialized( EventArgs e ) { EventHandler handler = (EventHandler)base.Events[EventDataBindingsInitialized]; - if(handler != null) + if (handler != null) { - handler(this, e); + handler( this, e ); } } @@ -754,11 +802,11 @@ namespace Spring.Web.UI { add { - base.Events.AddHandler(EventDataBindingsInitialized, value); + base.Events.AddHandler( EventDataBindingsInitialized, value ); } remove { - base.Events.RemoveHandler(EventDataBindingsInitialized, value); + base.Events.RemoveHandler( EventDataBindingsInitialized, value ); } } @@ -767,11 +815,11 @@ namespace Spring.Web.UI /// protected internal virtual void BindFormData() { - if(BindingManager.HasBindings) + if (BindingManager.HasBindings) { - BindingManager.BindTargetToSource(this, Controller, Page.ValidationErrors); + BindingManager.BindTargetToSource( this, Controller, Page.ValidationErrors ); } - OnDataBound(EventArgs.Empty); + OnDataBound( EventArgs.Empty ); } /// @@ -779,11 +827,11 @@ namespace Spring.Web.UI /// protected internal virtual void UnbindFormData() { - if(BindingManager.HasBindings) + if (BindingManager.HasBindings) { - BindingManager.BindSourceToTarget(this, Controller, Page.ValidationErrors); + BindingManager.BindSourceToTarget( this, Controller, Page.ValidationErrors ); } - OnDataUnbound(EventArgs.Empty); + OnDataUnbound( EventArgs.Empty ); } /// @@ -794,11 +842,11 @@ namespace Spring.Web.UI { add { - base.Events.AddHandler(EventDataBound, value); + base.Events.AddHandler( EventDataBound, value ); } remove { - base.Events.RemoveHandler(EventDataBound, value); + base.Events.RemoveHandler( EventDataBound, value ); } } @@ -806,12 +854,12 @@ namespace Spring.Web.UI /// Raises DataBound event. /// /// Event arguments. - protected virtual void OnDataBound(EventArgs e) + protected virtual void OnDataBound( EventArgs e ) { - EventHandler handler = (EventHandler) base.Events[EventDataBound]; + EventHandler handler = (EventHandler)base.Events[EventDataBound]; if (handler != null) { - handler(this, e); + handler( this, e ); } } @@ -823,11 +871,11 @@ namespace Spring.Web.UI { add { - base.Events.AddHandler(EventDataUnbound, value); + base.Events.AddHandler( EventDataUnbound, value ); } remove { - base.Events.RemoveHandler(EventDataUnbound, value); + base.Events.RemoveHandler( EventDataUnbound, value ); } } @@ -835,12 +883,12 @@ namespace Spring.Web.UI /// Raises DataBound event. ///
/// Event arguments. - protected virtual void OnDataUnbound(EventArgs e) + protected virtual void OnDataUnbound( EventArgs e ) { - EventHandler handler = (EventHandler) base.Events[EventDataUnbound]; + EventHandler handler = (EventHandler)base.Events[EventDataUnbound]; if (handler != null) { - handler(this, e); + handler( this, e ); } } @@ -875,8 +923,8 @@ namespace Spring.Web.UI /// If thrown by any application context methods. /// /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable( false )] + [DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )] public virtual IApplicationContext ApplicationContext { get { return applicationContext; } @@ -891,8 +939,8 @@ namespace Spring.Web.UI /// Gets or sets the localizer. ///
/// The localizer. - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable( false )] + [DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )] public ILocalizer Localizer { get { return localizer; } @@ -910,8 +958,8 @@ namespace Spring.Web.UI /// Gets or sets the local message source. ///
/// The local message source. - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable( false )] + [DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )] public IMessageSource MessageSource { get { return messageSource; } @@ -920,7 +968,7 @@ namespace Spring.Web.UI messageSource = value; if (messageSource != null && messageSource is AbstractMessageSource) { - ((AbstractMessageSource) messageSource).ParentMessageSource = applicationContext; + ((AbstractMessageSource)messageSource).ParentMessageSource = applicationContext; } } } @@ -932,12 +980,12 @@ namespace Spring.Web.UI { if (this.MessageSource == null) { - string key = CreateSharedStateKey("MessageSource"); + string key = CreateSharedStateKey( "MessageSource" ); IDictionary sharedState = this.SharedState; IMessageSource messageSource = sharedState[key] as IMessageSource; if (messageSource == null) { - lock(sharedState.SyncRoot) + lock (sharedState.SyncRoot) { messageSource = sharedState[key] as IMessageSource; if (messageSource == null) @@ -947,7 +995,7 @@ namespace Spring.Web.UI ResourceManager rm = GetLocalResourceManager(); if (rm != null) { - defaultMessageSource.ResourceManagers.Add(rm); + defaultMessageSource.ResourceManagers.Add( rm ); } sharedState[key] = defaultMessageSource; messageSource = defaultMessageSource; @@ -978,13 +1026,13 @@ namespace Spring.Web.UI } #else { - object resourceProvider = Page.GetLocalResourceProvider.Invoke(typeof(ResourceExpressionBuilder), new object[] {this}); + object resourceProvider = Page.GetLocalResourceProvider.Invoke( typeof( ResourceExpressionBuilder ), new object[] { this } ); MethodInfo GetLocalResourceAssembly = - resourceProvider.GetType().GetMethod("GetLocalResourceAssembly", BindingFlags.NonPublic | BindingFlags.Instance); - Assembly localResourceAssembly = (Assembly) GetLocalResourceAssembly.Invoke(resourceProvider, null); + resourceProvider.GetType().GetMethod( "GetLocalResourceAssembly", BindingFlags.NonPublic | BindingFlags.Instance ); + Assembly localResourceAssembly = (Assembly)GetLocalResourceAssembly.Invoke( resourceProvider, null ); if (localResourceAssembly != null) { - return new ResourceManager(VirtualPathUtility.GetFileName(this.AppRelativeVirtualPath), localResourceAssembly); + return new ResourceManager( VirtualPathUtility.GetFileName( this.AppRelativeVirtualPath ), localResourceAssembly ); } return null; } @@ -995,9 +1043,9 @@ namespace Spring.Web.UI /// /// Resource name. /// Message text. - public string GetMessage(string name) + public string GetMessage( string name ) { - return messageSource.GetMessage(name, UserCulture); + return messageSource.GetMessage( name, UserCulture ); } /// @@ -1006,9 +1054,9 @@ namespace Spring.Web.UI /// Resource name. /// Message arguments that will be used to format return value. /// Formatted message text. - public string GetMessage(string name, params object[] args) + public string GetMessage( string name, params object[] args ) { - return messageSource.GetMessage(name, UserCulture, args); + return messageSource.GetMessage( name, UserCulture, args ); } /// @@ -1016,16 +1064,16 @@ namespace Spring.Web.UI /// /// Resource name. /// Resource object. - public object GetResourceObject(string name) + public object GetResourceObject( string name ) { - return messageSource.GetResourceObject(name, UserCulture); + return messageSource.GetResourceObject( name, UserCulture ); } /// /// Gets or sets user's culture /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable( false )] + [DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )] public virtual CultureInfo UserCulture { get { return Page.UserCulture; } @@ -1040,11 +1088,11 @@ namespace Spring.Web.UI /// Overrides Page property to return /// instead of . /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable( false )] + [DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )] public new Page Page { - get { return (Page) base.Page; } + get { return (Page)base.Page; } } #endregion @@ -1057,7 +1105,7 @@ namespace Spring.Web.UI /// /// Key suffix /// Generated unique shared state key. - protected string CreateSharedStateKey(string key) + protected string CreateSharedStateKey( string key ) { return key; } @@ -1072,16 +1120,16 @@ namespace Spring.Web.UI IApplicationContext ISupportsWebDependencyInjection.DefaultApplicationContext { get { return defaultApplicationContext; } - set { defaultApplicationContext = value; } + set { defaultApplicationContext = value; } } /// /// Injects dependencies into control before adding it. /// - protected override void AddedControl(Control control,int index) + protected override void AddedControl( Control control, int index ) { WebDependencyInjectionUtils.InjectDependenciesRecursive( defaultApplicationContext, control ); - base.AddedControl(control,index); + base.AddedControl( control, index ); } #endregion Dependency Injection Support diff --git a/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj b/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj index 3a4c2f17..cd511db9 100644 --- a/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj +++ b/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj @@ -328,6 +328,11 @@ SubType = "Code" BuildAction = "Compile" /> + + Code diff --git a/test/Spring/Spring.Web.Tests/Web/Support/ResultFactoryRegistryTests.cs b/test/Spring/Spring.Web.Tests/Web/Support/ResultFactoryRegistryTests.cs new file mode 100644 index 00000000..a42caef5 --- /dev/null +++ b/test/Spring/Spring.Web.Tests/Web/Support/ResultFactoryRegistryTests.cs @@ -0,0 +1,189 @@ +#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 NUnit.Framework; +using Rhino.Mocks; +using Spring.Util; + +#endregion + +namespace Spring.Web.Support +{ + /// + /// + /// + /// Erich Eichinger + [TestFixture] + public class ResultFactoryRegistryTests + { + [SetUp] + public void SetUp() + { + ResultFactoryRegistry.Reset(); + } + + [Test] + public void SetDefaultFactory() + { + MockRepository mocks = new MockRepository(); + IResultFactory resultFactory = (IResultFactory)mocks.CreateMock( typeof( IResultFactory ) ); + + IResultFactory prevFactory = ResultFactoryRegistry.DefaultResultFactory; + Assert.AreSame( prevFactory, ResultFactoryRegistry.SetDefaultFactory( resultFactory ) ); + Assert.AreSame( resultFactory, ResultFactoryRegistry.DefaultResultFactory ); + + // verify default factory is used for unknown result mode + using (Record( mocks )) + { + Expect.Call( resultFactory.CreateResult( null, "resultText" ) ).Return( new Result() ); + Expect.Call( resultFactory.CreateResult( "resultMode", "resultText" ) ).Return( new Result() ); + } + + using (Playback( mocks )) + { + ResultFactoryRegistry.CreateResult( "resultText" ); + ResultFactoryRegistry.CreateResult( "resultMode:resultText" ); + } + } + + [Test] + public void ResultModeValuesHavePredefinedFactories() + { + MockRepository mocks = new MockRepository(); + IResultFactory defaultFactory = (IResultFactory)mocks.CreateMock( typeof( IResultFactory ) ); + + ResultFactoryRegistry.SetDefaultFactory( defaultFactory ); + + // verify factory registry knows all ResultModes + using (Record( mocks )) + { + // defaultFactory must never be called! + } + + using (Playback( mocks )) + { + foreach (string resultMode in Enum.GetNames( typeof( ResultMode ) )) + { + Assert.IsNotNull( ResultFactoryRegistry.CreateResult( resultMode+":resultText" ) ); + } + } + } + + [Test] + public void SelectsFactoryByResultMode() + { + MockRepository mocks = new MockRepository(); + IResultFactory resultFactory = (IResultFactory)mocks.CreateMock( typeof( IResultFactory ) ); + + ResultFactoryRegistry.RegisterResultMode( "resultMode", resultFactory ); + + Result result = new Result(); + + // verify factory registry does not allow nulls to be returned + using (Record( mocks )) + { + Expect.Call( resultFactory.CreateResult( "resultMode", "resultText" ) ).Return( result ); + } + + using (Playback( mocks )) + { + Assert.AreSame( result, ResultFactoryRegistry.CreateResult( "resultMode:resultText" ) ); + } + } + + [Test] + [ExpectedException( typeof( ArgumentNullException ) )] + public void BailsOnNullReturnedFromFactory() + { + MockRepository mocks = new MockRepository(); + IResultFactory resultFactory = (IResultFactory)mocks.CreateMock( typeof( IResultFactory ) ); + + ResultFactoryRegistry.RegisterResultMode( "resultMode", resultFactory ); + + // verify factory registry does not allow nulls to be returned + using (Record( mocks )) + { + Expect.Call( resultFactory.CreateResult( "resultMode", "resultText" ) ).Return( null ); + } + + using (Playback( mocks )) + { + ResultFactoryRegistry.CreateResult( "resultMode:resultText" ); + } + } + + #region Rhino.Mocks Compatibility Adapter + + private static IDisposable Record( MockRepository mocks ) + { +#if !NET_1_1 + return mocks.Record(); +#else + return new RecordModeChanger(mocks); +#endif + } + + private static IDisposable Playback( MockRepository mocks ) + { +#if !NET_1_1 + return mocks.Playback(); +#else + return new PlaybackModeChanger(mocks); +#endif + } + +#if NET_1_1 + private class RecordModeChanger : IDisposable + { + private MockRepository _mocks; + + public RecordModeChanger(MockRepository mocks) + { + _mocks = mocks; + } + + public void Dispose() + { + _mocks.ReplayAll(); + } + } + + private class PlaybackModeChanger : IDisposable + { + private MockRepository _mocks; + + public PlaybackModeChanger(MockRepository mocks) + { + _mocks = mocks; + } + + public void Dispose() + { + _mocks.VerifyAll(); + } + } +#endif + + #endregion Rhino.Mocks Compatibility Adapter + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Web.Tests/Web/Support/ResultTests.cs b/test/Spring/Spring.Web.Tests/Web/Support/ResultTests.cs index cd563f80..2b684fcc 100644 --- a/test/Spring/Spring.Web.Tests/Web/Support/ResultTests.cs +++ b/test/Spring/Spring.Web.Tests/Web/Support/ResultTests.cs @@ -21,7 +21,9 @@ #region Imports using System; +using System.Collections; using NUnit.Framework; +using Spring.Util; #endregion @@ -333,5 +335,32 @@ namespace Spring.Web.Support "The TargetPage property is not being correctly extracted " + "from the result string passed into the ctor."); } + + [Test] + public void TargetPageMayBeRuntimeExpression() + { + Result result = new Result("%{['var1']}"); + string resultUri = result.GetRedirectUri( MakeDictionary("var1", "val1") ); + Assert.AreEqual( "val1", resultUri ); + } + + [Test] + public void ParameterKeysAndValuesMayBeRuntimeExpression() + { + Result result = new Result("redirect:%{['var1']}?%{['var2']}=%{['var3']}"); + string resultUri = result.GetRedirectUri( MakeDictionary("var1", "val1", "var2", "val2", "var3", "val3") ); + Assert.AreEqual( "val1?val2=val3", resultUri ); + } + + private IDictionary MakeDictionary( params object[] args) + { + AssertUtils.IsTrue(args.Length % 2 == 0); + Hashtable ht= new Hashtable(); + for(int i=0;i