![]() |
The Spring.NET Web Framework increases your productivity when you write ASP.NET WebForms applications by offering capabilities not found in other .NET web frameworks. The Spring.NET Web Framework makes it easy to write 'thin and clean' web applications. "Thin" refers to WebForm's role as a small as possible adapter between the HTML- based world of the web and the Object-oriented world of your application. The business logic does not reside in the web tier; it resides in the application layer with which your web form communicates. "Clean" refers to the framework's appropriate separation of concerns, separating web specific processing such as copying data out and into from element from a data model from calling into a buiness tier and redirecting to the next page. This results in an event-handler that does not contain any reference to UI elements thereby making it possible to test your event handler code in integration style tests. The Spring.NET Web Framework reduces the incidental complexity of common tasks in the web tier, for example, the conversion of HTML control data to objects and then vice-versa after the request is processed by the application layer. Highlights of Spring's Web framework are:
All you know about ASP.NET development still applies. Spring's approach is to 'embrace and extend' the basic ASP.NET programming model to make you as productive as possible.
This chapter describes the Spring.NET Web Framework in detail. The framework is not an all-or-nothing solution. For example, you can choose to use only dependency injection and bi-directional data binding. You can adopt the web framework incrementally, addressing problems areas in your current web application with a specific feature. The Spring.NET distribution ships with a Web Quick Start application and a complete reference application, SpringAir. The Web QuickStart is the best way to learn each Spring.NET Web Framework (also referred to in this document as Spring.Web) feature, by following simple examples. The SpringAir reference application has a Spring.Web-enabled frontend that uses many best practices for Spring.NET web applications, so refer to it as you are reading this (reference) material (see Chapter 41, SpringAir - Reference Application). Many developers dislike the ASP.NET programming model because currently it is not a "true MVC" (model-view-controller) implementation; controller-type logic within the page is too tightly coupled to the view. For example, event handlers within the page class typically have references to view elements, such as input controls, in many code behind locations, most typically the event handler. Controller-type logic, such as the code within page event handlers in ASP.NET, should not depend on the view elements. However, ASP.NET has its good points. Server-side forms and controls make developers significantly more productive and allow you to significantly simplify page markup. They also make cross-browser issues easier to deal with, as each control can make sure that it renders correct markup based on the user's browser. The ability to hook custom logic into the lifecycle of the page, as well as to customize the HTTP processing pipeline, are also very powerful features. The ability to interact with the strongly typed server-side controls instead of manipulating string-based HTTP request collections, such as Form and QueryString, is a much needed layer of abstraction in web development. Thus, instead of developing a new, pure and true MVC web framework as part of Spring.NET, Spring decided to extend ASP.NET so that most of its shortcomings are eliminated. With the introduction of a 'true MVC framework' to .NET there are several opportunities for integration with IoC containers such as Spring.NET. Furthermore, as Spring for Java has a very popular MVC framework, much of that experience and added value can be transliterated to help developers be more productive when using Spring's future support for ASP.NET MVC. Spring.Web also supports the application of the dependency injection
principle to one's ASP.NET Event handlers in code-behind classes should not have to deal with
ASP.NET UI controls directly. Such event handlers should rather work with
the presentation model of the page, represented either as a hierarchy of
domain objects or an ADO.NET The Spring.NET Web Framework also addresses concerns about the flow
of control through an application. Typical ASP.NET applications use
Standard localization support is also limited in versions of ASP.NET
prior to ASP.NET 2.0. Even though Visual Studio 2003 generates a local
resource file for each ASP.NET In addition to the aforementioned core features, Spring.Web ships with lesser features that might be useful to many application developers. Some of these additional features include back-ports of ASP.NET 2.0 features that can be used with ASP.NET 1.1, such as Master Page support. See Master Pages in ASP.NET 1.1 . To implement some features, the Spring.NET team had to extend (as in
the object-oriented sense) the standard ASP.NET Spring.Web builds on top of the Spring.NET IoC container, and
makes heavy use (internally) of the easy pluggability and standardized
configuration afforded by the IoC container. ASP.NET
Spring.Web uses a custom The instantiation and configuration of the Spring.NET IoC
container by the Spring.Web infrastructure is wholly transparent to
application developers, who typically never have to explicitly
instantiate and configure an IoC container manually (by, for example,
using the
<system.web> <httpHandlers> <add verb="*" path="*.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/> </httpHandlers> <httpModules> <add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/> </httpModules> ... </system.web> This snippet of standard ASP.NET configuration is only required in
the root directory of each Spring.Web web
application (that is, in the The above XML configuration snippet directs the ASP.NET
infrastructure to use Spring.NET's page factory, which in turn creates
instances of the appropriate After the Spring.Web page factory is configured, you also need to
define a root application context by adding a Spring.NET configuration
section to that same <?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.WebContextHandler, Spring.Web"/> </sectionGroup> </configSections> <spring> <context> <resource uri="~/Config/CommonObjects.xml"/> <resource uri="~/Config/CommonPages.xml"/> <!-- TEST CONFIGURATION --> <!-- <resource uri="~/Config/Test/Services.xml"/> <resource uri="~/Config/Test/Dao.xml"/> --> <!-- PRODUCTION CONFIGURATION --> <resource uri="~/Config/Production/Services.xml"/> <resource uri="~/Config/Production/Dao.xml"/> </context> </spring> <system.web> <httpHandlers> <add verb="*" path="*.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/> </httpHandlers> <httpModules> <add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/> </httpModules> </system.web> </configuration> Notes about the preceding configuration:
There is some configuration that is specific to using IIS7, the appropriate code snippit to place in web.config shown below. <system.webServer> <validation validateIntegratedModeConfiguration="false"/> <modules> <add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/> </modules> <handlers> <add name="SpringPageHandler" verb="*" path="*.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/> <add name="SpringContextMonitor" verb="*" path="ContextMonitor.ashx" type="Spring.Web.Support.ContextMonitor, Spring.Web"/> </handlers> </system.webServer> ASP.NET has a hierarchical configuration mechanism that enables application developers to override configuration settings specified at a higher level in the web application directory hierarchy with configuration settings specified at the lower level. For example, a web application's root
Spring.Web leverages this ASP.NET feature to provide support for a
context hierarchy. You can add new object definitions to lower level
What this means to application developers is that one can easily componentize an application by creating a virtual directory per component and creating a custom context for each component that contains the necessary configuration info for that particular context. The configuration for a lower level component generally contains only those definitions for the pages that the component consists of and (possibly) overrides for some definitions from the root context (for example, menus). Because each such lower level component usually contains only a
few object definitions, application developers are encouraged to embed
those object definitions directly into the <?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <sectionGroup name="spring"> <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <context type="Spring.Context.Support.WebApplicationContext, Spring.Web"> <resource uri="config://spring/objects"/> </context> <objects xmlns="http://www.springframework.net"> <object type="MyPage.aspx" parent="basePage"> <property name="MyRootService" ref="myServiceDefinedInRootContext"/> <property name="MyLocalService" ref="myServiceDefinedLocally"/> <property name="Results"> <!-- ... --> </property> </object> <object id="myServiceDefinedLocally" type="MyCompany.MyProject.Services.MyServiceImpl, MyAssembly"/> </objects> </spring> </configuration> The If Spring.NET is used for multiple applications on the same
server, you can avoid the need to specify the
This component-level context can reference definitions from its parent context(s). If a referenced object definition is not found in the current context, Spring.NET searches all ancestor contexts in the context hierarchy until it finds the object definition (or ultimately fails and throws an exception). An example of how Spring.Web builds on the capabilities of ASP.NET
is the way in which Spring.Web has used the code-behind class of the
After an application developer configures the Spring.NET web application context, the developer can easily create object definitions for the pages that compose that web application. <objects xmlns="http://www.springframework.net"> <object name="basePage" abstract="true"> <property name="MasterPageFile" value="~/Web/StandardTemplate.master"/> </object> <object type="Login.aspx"> <property name="Authenticator" ref="authenticationService"/> </object> <object type="Default.aspx" parent="basePage"/> </objects> The preceding example contains three definitions:
The configuration of ASP.NET pages differs from the configuration of
other .NET classes in the value passed to the In the case of the above example, those definitions are in the root
context ,so The definitions for the Nothing prevents an application developer from specifying an
Spring.Web also allows application developers to inject
dependencies into controls (both user controls and standard controls)
that are contained within a page. You can accomplish this globally for
all controls of a particular <object type="~/controls/MyControl.ascx" abstract="true"> <!-- inject dependencies here... --> </object>
You can inject dependencies into custom HTTP modules by using the
class
<httpModules> <add name="HtmlCommentAppender" type="HtmlCommentAppenderModule"/> </httpModules> To configure this module, you use naming conventions to identify
the module name with configuration instructions in the Spring
configuration file. The <object name="HttpApplicationConfigurer" type="Spring.Context.Support.HttpApplicationConfigurer, Spring.Web"> <property name="ModuleTemplates"> <dictionary> <entry key="HtmlCommentAppender"> <!-- this name must match the module name --> <object> <!-- select "view source" in your browser on any page to see the appended html comment --> <property name="AppendText" value="My configured comment!" /> </object> </entry> </dictionary> </property> </object> You can see this example in action in the Web Quick Start. Performing dependency injection on instances of
<system.web> <httpHandlers> <!-- the lines below map *any* request ending with *.ashx or *.whatever to the global(!) MappingHandlerFactory. Further "specialication" of which handler to map to is done within MappingHandlerFactory's configuration - use MappingHandlerFactoryConfigurer for this (see below) --> <add verb="*" path="*.ashx" type="Spring.Web.Support.MappingHandlerFactory, Spring.Web" validate="true"/> <add verb="*" path="*.whatever" type="Spring.Web.Support.MappingHandlerFactory, Spring.Web" validate="false"/> </httpHandlers> </system.web> Spring's MappingHandlerFactory serves a layer of indirection so that you can configure multiple handler mappings with Spring. You do this by configuring a IDictionary HandlerMap property on the class MappingHandlerFactoryConfigurer. The dictionary key is a regular expression that matches the request URL, and the value is a reference to the name of a Spring managed instance of an IHttpHandler or IHttpHandlerFactory . The Spring managed instance is configured via dependency injection using the standard <object/> XML configuraiton schema. The configuration of MappingHandlerFactoryConfigurer is shown: <objects xmlns="http://www.springframework.net"> <!-- configures the global GenericHandlerFactory instance --> <object name="mappingHandlerFactoryConfigurer" type="Spring.Web.Support.MappingHandlerFactoryConfigurer, Spring.Web"> <property name="HandlerMap"> <dictionary> <!-- map any request ending with *.whatever to NoOpHandler --> <entry key="\.whatever$" value="myCustomHandler" /> <entry key="\.ashx$" value="standardHandlerFactory" /> </dictionary> </property> </object> <object name="standardHandlerFactory" type="Spring.Web.Support.DefaultHandlerFactory, Spring.Web" /> <!-- defines a standard singleton that will handle *.whatever requests --> <object name="myCustomHandler" type="MyCustomHttpHandler, App_Code"> <property name="MessageText" value="This text is injected via Spring" /> </object> <!-- used for configuring ~/DemoHandler.ashx custom handler note, that this is an abstract definition because 'type' is not specified --> <object name="DemoHandler.ashx"> <property name="OutputText"> <value>This text is injected via Spring</value> </property> </object> </objects> Spring's Refer to the Web Quick Start application too see this in action. Custom providers can be configured via dependency injection with Spring. The approach to configuration for providers is to use a family of adapters that correspond 1-to-1 with the standard ASP.NET providers that are registered via the standard ASP.NET mechanism. The adapters inherit from their correspondingly named provider class in the .NET class library.
Here is an example of how to register the adapter for membership providers. <membership defaultProvider="mySqlMembershipProvider"> <providers> <clear/> <add connectionStringName="" name="mySqlMembershipProvider" type="Spring.Web.Providers.MembershipProviderAdapter, Spring.Web"/> </providers> </membership> The name of the provider must match the name of the object in the Spring configuration that will serve as the actual provider implementation. Configurable versions of the providers are found in ASP.NET so that you can use the full functionality of Spring to configure these standard provider implementations, by using property placeholders, and so on. The providers are:
This example configuration taken from the Web Quick Start application sets the description property and connection string. <object id="mySqlMembershipProvider" type="Spring.Web.Providers.ConfigurableSqlMembershipProvider"> <property name="connectionStringName" value="MyLocalSQLServer" /> <property name="parameters"> <name-values> <add key="description" value="membershipprovider description" /> </name-values> </property> </object> Your own custom providers of course will contain additional configuration specific to your implementation. You may need to customize Spring.Web's dependency injection processing, such as when using GridViews or other complex 3rd party custom controls. Often these controls are not configured using dependency injection but Spring considers each control and its nested child controls as candidates for DI. With very large (>1000) nested controls that candidate evaluation process can unecessarily slow down your page. To address this problem, you can tell Spring to not attempt to configure via DI the sections of your page that contain these controls or implementing the interface ISupportsWebDependencyInjection and explicitly ask Spring to inject dependencies on a particular contorol. These approaches are shown below [C#] class MyControl : Control, ISupportsWebDependencyInjection { private IApplicationContext _defaultApplicationContext; public IApplicationContext DefaultApplicationContext { get { return _defaultApplicationContext; } set { _defaultApplicationContext = value; } } override protected AddedControl( Control control, int index ) { // handle DI for children ourselves - // defaults to a call to InjectDependenciesRecursive WebUtils.InjectDependenciesRecursive( _defaultApplicationContext, control ); base.AddedControl( control, index ); } } A Spring server control, Panel, provides an easier way to turn off dependency injection for parts of your page: <spring:Panel runat="server" suppressDependencyInjection="true" renderContainerTag="false"> .. put your heavy controls here - they won't be touched by DI </spring:Panel> By wrapping the performance-sensitive parts of your page within this panel, you can easily turn off DI by setting the attribute suppressDependencyInjection to true. By default <spring:Panel/> will not render a container tag (<div>, <span>, and so on). You can modify this behavior by setting the attribute renderContainerTag accordingly. Spring.NET web applications support an additional attribute within object definition elements that allows you to control the scope of an object: <object id="myObject" type="MyType, MyAssembly" scope="application | session | request"/> Possible
values for the scope attribute are application,
session, and Request scope creates one instance per HTTP request. Unlike calls to
prototype objects, calls to
Objects can only reference other objects that are in the same or broader scope. This means that application-scoped objects can only reference other application-scoped objects, session-scoped objects can reference both session and application-scoped objects, and request-scoped objects can reference other request-, session-, or application-scoped objects. Also, prototype objects (including all ASP.NET web pages defined within Spring.NET context) can reference singleton objects from any scope, as well as other prototype objects. Support for ASP.NET 1.1 master pages in Spring.Web is very similar to the support for master pages in ASP.NET 2.0. A web developer can define a layout template for the site as a
master page and specify content placeholders that other pages can then
reference and populate. A sample master page
( <%@ Control language="c#" Codebehind="MasterLayout.ascx.cs" AutoEventWireup="false" Inherits="MyApp.Web.UI.MasterLyout" %> <%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <html> <head> <title>Master Page</title> <link rel="stylesheet" type="text/css" href="<%= Context.Request.ApplicationPath %>/css/styles.css"> <spring:ContentPlaceHolder id="head" runat="server"/> </head> <body> <form runat="server"> <table cellPadding="3" width="100%" border="1"> <tr> <td colspan="2"> <spring:ContentPlaceHolder id="title" runat="server"> <!-- default title content --> </spring:ContentPlaceHolder> </td> </tr> <tr> <td> <spring:ContentPlaceHolder id="leftSidebar" runat="server"> <!-- default left side content --> </spring:ContentPlaceHolder> </td> <td> <spring:ContentPlaceHolder id="main" runat="server"> <!-- default main area content --> </spring:ContentPlaceHolder> </td> </tr> </table> </form> </body> </html> In the preceding code, the master page defines the overall layout for the page, in addition to four content placeholders that other pages can override. The master page can also include default content within the placeholder that will be displayed if a derived page does not override the placeholder. A page that uses this master page (Child.aspx) might look like this: <%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %> <%@ Page language="c#" Codebehind="Child.aspx.cs" AutoEventWireup="false" Inherits="ArtFair.Web.UI.Forms.Child" %> <html> <body> <spring:Content id="leftSidebarContent" contentPlaceholderId="leftSidebar" runat="server"> <!-- left sidebar content --> </spring:Content> <spring:Content id="mainContent" contentPlaceholderId="main" runat="server"> <!-- main area content --> </spring:Content> </body> </html> The Both the
The The recommended way to do this is by leveraging the Spring.NET IoC container and creating definitions similar to the following: <?xml version="1.0" encoding="utf-8" ?> <objects xmlns="http://www.springframework.net"> <object name="basePage" abstract="true"> <property name="MasterPageFile" value="~/MasterLayout.ascx"/> </object> <object type="Child.aspx" parent="basePage"> <!-- inject other objects that page needs --> </object> </objects> This approach allows application developers to change the master
page for a number of pages within a web application. You can still
override the master page on a per context or per page basis by creating
a new abstract page definition within a child context, or by specifying
the The existing data binding support in ASP.NET is one-way only. It allows application developers to bind page controls to the data model and display information from the data model, but it does not permit the extraction of values from the controls when the form is submitted. Spring.Web adds bidirectional data binding to ASP.NET by allowing developers to specify data binding rules for their page, and by automatically evaluating configured data binding rules at the appropriate time in the page's lifecycle. ASP.NET does support model management within the postbacks. It has a ViewState management, but that takes care of the control state only and does not address the state of any presentation model objects to which these controls are bound. To manage a model within ASP.NET, developers typically use an HTTP session object to store the model between the postbacks. This process results in boilerplate code that can and should be eliminated, which is exactly what Spring.Web does by providing a simple set of model management methods. To take advantage of the bidirectional data binding and model
management support provided by Spring.Web, you will
have to couple your presentation layer to Spring.Web; this is because
features require you to extend a
Spring.Web data binding is very easy to use. Simply override the
protected <%@ Page Language="c#" Inherits="TripForm" CodeFile="TripForm.aspx.cs" %> <asp:Content ID="body" ContentPlaceHolderID="body" runat="server"> <div style="text-align: center"> <h4><asp:Label ID="caption" runat="server"></asp:Label></h4> <table> <tr class="formLabel"> <td> </td> <td colspan="3"> <spring:RadioButtonGroup ID="tripMode" runat="server"> <asp:RadioButton ID="OneWay" runat="server" /> <asp:RadioButton ID="RoundTrip" runat="server" /> </spring:RadioButtonGroup> </td> </tr> <tr> <td class="formLabel" align="right"> <asp:Label ID="leavingFrom" runat="server" /></td> <td nowrap="nowrap"> <asp:DropDownList ID="leavingFromAirportCode" runat="server" /> </td> <td class="formLabel" align="right"> <asp:Label ID="goingTo" runat="server" /></td> <td nowrap="nowrap"> <asp:DropDownList ID="goingToAirportCode" runat="server" /> </td> </tr> <tr> <td class="formLabel" align="right"> <asp:Label ID="leavingOn" runat="server" /></td> <td nowrap="nowrap"> <spring:Calendar ID="departureDate" runat="server" Width="75px" AllowEditing="true" Skin="system" /> </td> <td class="formLabel" align="right"> <asp:Label ID="returningOn" runat="server" /></td> <td nowrap="nowrap"> <div id="returningOnCalendar"> <spring:Calendar ID="returnDate" runat="server" Width="75px" AllowEditing="true" Skin="system" /> </div> </td> </tr> <tr> <td class="buttonBar" colspan="4"> <br/> <asp:Button ID="findFlights" runat="server"/></td> </tr> </table> </div> </asp:Content> Ignore for the moment the fact that none of the label
controls have text defined; defining label controls is described later
when we discuss localization in Spring.NET. For the purposes of the
current discussion, a number of input controls are defined:
Take a look at the model to which you bind the form: namespace SpringAir.Domain { [Serializable] public class Trip { // fields private TripMode mode; private TripPoint startingFrom; private TripPoint returningFrom; // constructors public Trip() { this.mode = TripMode.RoundTrip; this.startingFrom = new TripPoint(); this.returningFrom = new TripPoint(); } public Trip(TripMode mode, TripPoint startingFrom, TripPoint returningFrom) { this.mode = mode; this.startingFrom = startingFrom; this.returningFrom = returningFrom; } // properties public TripMode Mode { get { return this.mode; } set { this.mode = value; } } public TripPoint StartingFrom { get { return this.startingFrom; } set { this.startingFrom = value; } } public TripPoint ReturningFrom { get { return this.returningFrom; } set { this.returningFrom = value; } } } [Serializable] public class TripPoint { // fields private string airportCode; private DateTime date; // constructors public TripPoint() {} public TripPoint(string airportCode, DateTime date) { this.airportCode = airportCode; this.date = date; } // properties public string AirportCode { get { return this.airportCode; } set { this.airportCode = value; } } public DateTime Date { get { return this.date; } set { this.date = value; } } } [Serializable] public enum TripMode { OneWay, RoundTrip } } As you can see, Here is the code-behind class that ties everything together: public class TripForm : Spring.Web.UI.Page { // model private Trip trip; public Trip Trip { get { return trip; } set { trip = value; } } // service dependency, injected by Spring IoC container private IBookingAgent bookingAgent; public IBookingAgent BookingAgent { set { bookingAgent = value; } } // model management methods protected override void InitializeModel() { trip = new Trip(); trip.Mode = TripMode.RoundTrip; trip.StartingFrom.Date = DateTime.Today; trip.ReturningFrom.Date = DateTime.Today.AddDays(1); } protected override void LoadModel(object savedModel) { trip = (Trip) savedModel; } protected override object SaveModel() { return trip; } // data binding rules protected override void InitializeDataBindings() { BindingManager.AddBinding("tripMode.Value", "Trip.Mode"); BindingManager.AddBinding("leavingFromAirportCode.SelectedValue", "Trip.StartingFrom.AirportCode"); BindingManager.AddBinding("goingToAirportCode.SelectedValue", "Trip.ReturningFrom.AirportCode"); BindingManager.AddBinding("departureDate.SelectedDate", "Trip.StartingFrom.Date"); BindingManager.AddBinding("returnDate.SelectedDate", "Trip.ReturningFrom.Date"); } // event handler for findFlights button, uses injected 'bookingAgent' // service and model 'trip' object to find flights private void SearchForFlights(object sender, EventArgs e) { FlightSuggestions suggestions = bookingAgent.SuggestFlights(trip); if (suggestions.HasOutboundFlights) { // redirect to SuggestedFlights page } } } Note the following about the three preceding pieces of code:
This section describes how data binding is actually implemented, the extension points, and additional features that make the data binding framework usable in real-world applications. The Spring.NET data binding framework revolves around two main
interfaces: public interface IBinding { void BindSourceToTarget(object source, object target, ValidationErrors validationErrors); void BindSourceToTarget(object source, object target, ValidationErrors validationErrors, IDictionary variables); void BindTargetToSource(object source, object target, ValidationErrors validationErrors); void BindTargetToSource(object source, object target, ValidationErrors validationErrors, IDictionary variables); void SetErrorMessage(string messageId, params string[] errorProviders); } The The The last method in the The public interface IBindingContainer : IBinding { bool HasBindings { get; } IBinding AddBinding(IBinding binding); IBinding AddBinding(string sourceExpression, string targetExpression); IBinding AddBinding(string sourceExpression, string targetExpression, BindingDirection direction); IBinding AddBinding(string sourceExpression, string targetExpression, IFormatter formatter); IBinding AddBinding(string sourceExpression, string targetExpression, BindingDirection direction, IFormatter formatter); } The In the TripForm example, the configuration of the BindingManager
shows the basic usage of how SpEL can be used to specify a
protected override void InitializeDataBindings() { BindingManager.AddBinding("tripMode.Value", "Trip.Mode"); BindingManager.AddBinding("leavingFromAirportCode.SelectedValue", "Trip.StartingFrom.AirportCode"); BindingManager.AddBinding("goingToAirportCode.SelectedValue", "Trip.ReturningFrom.AirportCode"); BindingManager.AddBinding("departureDate.SelectedDate", "Trip.StartingFrom.Date"); BindingManager.AddBinding("returnDate.SelectedDate", "Trip.ReturningFrom.Date"); } In this case, the first argument is a
The However, unidirectional data bindings are also useful when your
form does not have a simple one-to-one mapping to a presentation
model. In the earlier trip form example, the presentation model was
intentionally designed to allow for simple one-to-one mappings. For
the sake of discussion, let's add the namespace SpringAir.Domain { [Serializable] public class TripPoint { // fields private Airport airport; private DateTime date; // constructors public TripPoint() {} public TripPoint(Airport airport, DateTime date) { this.airport = airport; this.date = date; } // properties public Airport Airport { get { return this.airport; } set { this.airport = value; } } public DateTime Date { get { return this.date; } set { this.date = value; } } } [Serializable] public class Airport { // fields private string code; private string name; // properties public string Code { get { return this.code; } set { this.code = value; } } public string Name { get { return this.name; } set { this.name = value; } } } } Instead of the string property Binding from the model to the control, namely the Airport to the string, is still very straightforward. You set up one-way bindings from the model to controls: The Model-To-Control is represented more generally by the enumeration, BindingDirection.TargetToSource. protected override void InitializeDataBindings() { BindingManager.AddBinding("leavingFromAirportCode.SelectedValue", "Trip.StartingFrom.Airport.Code", BindingDirection.TargetToSource); BindingManager.AddBinding("goingToAirportCode.SelectedValue", "Trip.ReturningFrom.Airport.Code", BindingDirection.TargetToSource); ... } You extract the airport code value from the
Our complete set of bindings for these two drop-down lists will then look like this: protected override void InitializeDataBindings() { BindingManager.AddBinding("@(airportDao).GetAirport(leavingFromAirportCode.SelectedValue)", "Trip.StartingFrom.Airport", BindingDirection.SourceToTarget); BindingManager.AddBinding("leavingFromAirportCode.SelectedValue", "Trip.StartingFrom.Airport.Code", BindingDirection.TargetToSource); BindingManager.AddBinding("@(airportDao).GetAirport(goingToAirportCode.SelectedValue)", "Trip.ReturningFrom.Airport", BindingDirection.SourceToTarget); BindingManager.AddBinding("goingToAirportCode.SelectedValue", "Trip.ReturningFrom.Airport.Code", BindingDirection.TargetToSource); ... } By using a pair of bindings for each control, one for each direction and using SpEL's feature to reference objects defined in the Spring context, you can resolve this data binding issue. The last overloaded methods of
IBindingContainer we need to discuss
are those that take a You typically use one of the formatters provided in the Spring.Globalization.Formatters namespace, but if your requirements cannot be satisfied by a standard formatter, you can write your own by implementing a simple IFormatter interface: public interface IFormatter { string Format(object value); object Parse(string value); } Standard formatters provided with Spring.NET are:
Because the data binding framework uses the same expression evaluation engine as the Spring.NET IoC container, it uses any registered type converters to perform data binding. Many type converters are included with Spring.NET (take a look at the classes in Spring.Objects.TypeConverters namespace) and are automatically registered for you, but you can implement your own custom converters and register them by using standard Spring.NET type converter registration mechanisms. Spring.Web's base You can register for an The The fact that the data model is updated immediately after the
If errors occur in the databinding (for example, in trying to bind a string 'hello' to an integer property on the model), you can specify how those fundamental binding errors should be rendered. The following snippet is from the Web Quick Start 'RobustEmployeeInfo' example: [Default.aspx.cs] protected override void InitializeDataBindings() { // collect txtId.Text binding errors in "id.errors" collection BindingManager.AddBinding("txtId.Text", "Employee.Id").SetErrorMessage("ID has to be an integer", "id.errors"); ... [Default.aspx] ... <asp:TextBox ID="txtId" runat="server" /> <!-- output validation errors from "id.errors" collection --> <spring:ValidationError Provider="id.errors" runat="server" /> ... The SetErrorMessage specifies the message text or resource id of the error message to be displayed. This is followed by a a variable length list of strings that serve to as a means to assign a friendly name to associate with this error should it occur. The same 'tag', or error provider name, can be used across different calls to 'AddBinding'. This is commonly the case if you want to present several errors together in the page. In the preceding example, the 'tag' or error provider name is "id.errors" will be rendered in Spring's ValidationError User Control, for example as shown below in this fragment of page markup. Validation controls are discussed more extensively in this section. <td> <asp:TextBox ID="txtId" runat="server" EnableViewState="false" /> <spring:ValidationError ID="errId" Provider="id.errors" runat="server" /><!-- read msg from "id.error" provider --> </td> HttpRequestListBindingContainer
extracts posted raw values from the request and populates
the specified IList by creating objects of the type specified and
populating each object according to the Please check out the Web Quick Start sample's demo of HttpRequestListBindingContainer. Below is an exerpt from that example showing how to use a HttpRequestListBindingContainer. protected override void InitializeDataBindings() { // HttpRequestListBindingContainer unbinds specified values from Request -> Productlist HttpRequestListBindingContainer requestBindings = new HttpRequestListBindingContainer("sku,name,quantity,price", "Products", typeof(ProductInfo)); requestBindings.AddBinding("sku", "Sku"); requestBindings.AddBinding("name", "Name"); requestBindings.AddBinding("quantity", "Quantity", quantityFormatter); requestBindings.AddBinding("price", "Price", priceFormatter); BindingManager.AddBinding(requestBindings); }
To simplify use of Spring's Data Binding feature on web pages and controls, Spring.Web provides a special DataBindingPanel container control. A DataBindingPanel does not render any html code itself, but allows you to define additional, data binding-related attributes for its child controls. <%@ Page Language="C#" CodeFile="Default.aspx.cs" Inherits="DataBinding_EasyEmployeeInfo_Default" %> <%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %> <html> <body> <spring:DataBindingPanel ID="ctlDataBindingPanel" runat="server"> <table cellpadding="3" cellspacing="3" border="0"> <tr> <td>Employee ID:</td> <td> <asp:TextBox ID="txtId" runat="server" BindingTarget="Employee.Id" /> </td> </tr> <tr> <td>First Name:</td> <td><asp:TextBox ID="txtFirstName" runat="server" BindingTarget="Employee.FirstName" /></td> </tr> </table> </spring.DataBindingPanel> </body> </html> Using DataBindingPanel, you can specify the binding information directly on the control declaration. The following attributes are recognized by a DataBindingPanel:
As mentioned in the chapter introduction, model management needs
an application developer to override
<object id="modelPersister" type="Sample.DatabaseModelPersistenceMedium, MyCode"/> <object type="UserRegistration.aspx"> <property name="ModelPersistenceMedium" ref="modelPersister"/> </object> To implement any arbitrary persistence strategy, implement the IModelPersistenceMedium interface: public interface IModelPersistenceMedium { // Load the model for the specified control context. object LoadFromMedium( Control context ); // Save the specified model object. void SaveToMedium( Control context, object modelToSave ); } Although the .NET framework has excellent localization support, the support within ASP.NET 1.x is incomplete. Spring provides support for localization in ASP.NET 1.1 apps in the manner of ASP.NET 2.0. Despite the initial focus on righer localization for ASP.NET 1.1 applications, using Spring's localization features in ASP.NET 2.0 or higher applications does provide some useful additional features with a similar programming model, such as image localization, push mechansims, and built-in support for user culture management via various mechansims. Every Spring.Web supports several different approaches to localization within a web application, which can be mixed and matched as appropriate. You can use push and pull mechanisms, as well as globally defined resources when a local resource cannot be found. Spring.Web also supports user culture management and image localization, which are described in later sections.
A localizer is an object that implements the
To apply resources automatically, a localizer needs to be injected
into all pages that require automatic resource application. You
typically accomplish configuration using dependency injection of a page
base page definition that other page definitions will inherit from. The
injected localizer inspects the resource file when the page is first
requested, caches the resources that start with the
Spring.NET ships with one concrete implementation of a localizer,
You typically configure the localizer to be used within an abstract base definition for those pages that require localization: <object id="localizer" type="Spring.Globalization.Localizers.ResourceSetLocalizer, Spring.Core"/> <object name="basePage" abstract="true"> <description> Pages that reference this definition as their parent (see examples below) will automatically inherit following properties. </description> <property name="Localizer" ref="localizer"/> </object> Of course, nothing prevents an application developer from defining a different localizer for each page in the application; in any case, one can always override the localizer defined in a base (page) definition. Alternatively, if one does want any resources to be applied automatically one can completely omit the localizer definition. One last thing to note is that Spring.NET
With push localization, an application developer specifies
localization resources in the resource file for the page, and the
framework automatically applies those resources to the user controls on
the page. For example, an application developer could define a page such
as <%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %> <%@ Page language="c#" Codebehind="UserRegistration.aspx.cs" AutoEventWireup="false" Inherits="ArtFair.Web.UI.Forms.UserRegistration" %> <html> <body> <spring:Content id="mainContent" contentPlaceholderId="main" runat="server"> <div align="right"> <asp:LinkButton ID="english" Runat="server" CommandArgument="en-US">English</asp:LinkButton> <asp:LinkButton ID="serbian" Runat="server" CommandArgument="sr-SP-Latn">Srpski</asp:LinkButton> </div> <table> <tr> <td><asp:Label id="emailLabel" Runat="server"/></td> <td><asp:TextBox id="email" Runat="server" Width="150px"/></td> </tr> <tr> <td><asp:Label id="passwordLabel" Runat="server"/></td> <td><asp:TextBox id="password" Runat="server" Width="150px"/></td> </tr> <tr> <td><asp:Label id="passwordConfirmationLabel" Runat="server"/></td> <td><asp:TextBox id="passwordConfirmation" Runat="server" Width="150px"/></td> </tr> <tr> <td><asp:Label id="nameLabel" Runat="server"/></td> <td><asp:TextBox id="name" Runat="server" Width="150px"/></td> </tr> ... <tr> <td colspan="2"> <asp:Button id="saveButton" Runat="server"/> <asp:Button id="cancelButton" Runat="server"/> </td> </tr> </table> </spring:Content> </body> </html> In the preceding $this.controlId.propertyName The corresponding local resource file,
<root> <data name="$this.emailLabel.Text"> <value>Email:</value> </data> <data name="$this.passwordLabel.Text"> <value>Password:</value> </data> <data name="$this.passwordConfirmationLabel.Text"> <value>Confirm password:</value> </data> <data name="$this.nameLabel.Text"> <value>Full name:</value> </data> ... <data name="$this.countryLabel.Text"> <value>Country:</value> </data> <data name="$this.saveButton.Text"> <value>$messageSource.save</value> </data> <data name="$this.cancelButton.Text"> <value>$messageSource.cancel</value> </data> </root>
You must create a localizer for the page to enable automatic localization: <object id="localizer" type="Spring.Globalization.Localizers.ResourceSetLocalizer, Spring.Core"/> <object type="UserRegistration.aspx"> <property name="Localizer" ref="localizer"/> </object> For more information on configuring localizers see Section 22.8.1, “Working with localizers” Two resource definitions from the previous section require some additional explanation: <data name="$this.saveButton.Text"> <value>$messageSource.save</value> </data> <data name="$this.cancelButton.Text"> <value>$messageSource.cancel</value> </data> In some cases it makes sense to apply a resource that is defined
globally as opposed to locally. In this example, it
makes better sense to define values for the The above example demonstrates how one can achieve that by defining a resource redirection expression as the value of a local resource by prefixing a global resource name with the following string. $messageSource. In the preceding example, this string tells the localizer to use
the Global resources are (on a per-context basis) defined as a plain
vanilla object definition using the reserved name of
<object id="messageSource" type="Spring.Context.Support.ResourceSetMessageSource, Spring.Core"> <property name="ResourceManagers"> <list> <value>MyApp.Web.Resources.Strings, MyApp.Web</value> </list> </property> </object>
See the SpringAir example application for more. The global
resources are cached within the Spring.NET
The Spring.Web Currently, the Although automatic localization as described above works well for many form-like pages, it doesn't work nearly as well for controls defined within any iterative controls, because the IDs for such iterative controls are not fixed. Nor does automatic localization work well if you need to display the same resource multiple times within the same page. For example, think of the header columns for outgoing and return flights tables within the SpringAir application (see Chapter 41, SpringAir - Reference Application). These situations call for a pull-style mechanism for localization,
which is a simple <asp:Repeater id="outboundFlightList" Runat="server"> <HeaderTemplate> <table border="0" width="90%" cellpadding="0" cellspacing="0" align="center" class="suggestedTable"> <thead> <tr class="suggestedTableCaption"> <th colspan="6"> <%= GetMessage("outboundFlights") %> </th> </tr> <tr class="suggestedTableColnames"> <th><%= GetMessage("flightNumber") %></th> <th><%= GetMessage("departureDate") %></th> <th><%= GetMessage("departureAirport") %></th> <th><%= GetMessage("destinationAirport") %></th> <th><%= GetMessage("aircraft") %></th> <th><%= GetMessage("seatPlan") %></th> </tr> </thead> <tbody> </HeaderTemplate> The Unlike text resources, which can be stored within embedded resource files, XML files, or even a database, images in a typical web application are usually stored as files on the file system. Using a combination of directory naming conventions and a custom ASP.NET control, Spring.Web allows you to localize images within the page as easily as you do text resources. The Spring.Web To localize images, you create a directory for each localized
culture under the /MyApp /Images /en /en-US /fr /fr-CA /sr-SP-Cyrl /sr-SP-Latn ... Once an appropriate folder hierarchy is in place, you put the
localized images in the appropriate directories and make sure that
different translations of the same image have the same image name within
the folders. To place a localized image on a page, you use the
<%@ Page language="c#" Codebehind="StandardTemplate.aspx.cs" AutoEventWireup="false" Inherits="SpringAir.Web.StandardTemplate" %> <%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <html> <body> <spring:LocalizedImage id="logoImage" imageName="spring-air-logo.jpg" borderWidth="0" runat="server" /> </body> </html> This control will find the most specific directory that contains
an image with the specified name using standard localization fallback
rules and the user's culture. For example, if the user's culture is
In addition to global and local resource management, Spring.Web
supports user culture management by exposing the current
The <object name="BasePage" abstract="true"> <property name="CultureResolver"> <object type="Spring.Globalization.Resolvers.CookieCultureResolver, Spring.Web"/> </property> </object> Several useful implementations of
The The RequestCultureResolver resolver operates similar to the
The This resolver looks for culture information in a cookie, and
return it if it finds one. If not, it falls back to the behavior of
the
To change the culture, application developers need to define one
of the culture resolvers that support culture changes, such as
You also can write a custom <object id="cultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web" /> Once that requirement is satisfied, you set the
protected override void OnInit(EventArgs e) { InitializeComponent(); this.english.Command += new CommandEventHandler(this.SetLanguage); this.serbian.Command += new CommandEventHandler(this.SetLanguage); base.OnInit(e); } private void SetLanguage(object sender, CommandEventArgs e) { this.UserCulture = new CultureInfo((string) e.CommandArgument); } In many ASP.NET applications, no built-in way exists to externalize
the flow of the application. The most common way of defining application
flow is by hardcoding calls to the This approach is problematic because any changes to the flow of an application necessitates code changes (with the attendant recompilation, testing, redeployment, and so on). A better way, which works in many MVC ( Model-View-Controller) web frameworks, is to enable you to externalize the mapping of action results to target pages. Spring.Web adds this functionality to ASP.NET by allowing you to define result mappings within the definition of a page, and to then simply use logical result names within event handlers to control application flow. In Spring.Web, a logical result is encapsulated and defined by the
<objects xmlns="http://www.springframework.net"> <object id="homePageResult" type="Spring.Web.Support.Result, Spring.Web"> <property name="TargetPage" value="~/Default.aspx"/> <property name="Mode" value="Transfer"/> <property name="Parameters"> <dictionary> <entry key="literal" value="My Text"/> <entry key="name" value="%{UserInfo.FullName}"/> <entry key="host" value="%{Request.UserHostName}"/> </dictionary> </property> </object> <object id="loginPageResult" type="Spring.Web.Support.Result, Spring.Web"> <property name="TargetPage" value="Login.aspx"/> <property name="Mode" value="Redirect"/> </object> <object type="UserRegistration.aspx" parent="basePage"> <property name="UserManager" ref="userManager"/> <property name="Results"> <dictionary> <entry key="userSaved" value-ref="homePageResult"/> <entry key="cancel" value-ref="loginPageResult"/> </dictionary> </property> </object> </objects> The only property for which you must supply a
value for each result is the If your target page requires parameters, you can define them with
the
Parameters are handled differently depending on the result mode. For
redirect results, every parameter is converted to a string, then URL
encoded, and finally appended to a redirect query string. Parameters for
transfer results are added to the
The preceding example shows independent result object definitions,
which are useful for global results such as a home- and login- page.
<object type="~/UI/Forms/UserRegistration.aspx" parent="basePage"> <property name="UserManager"> <ref object="userManager"/> </property> <property name="Results"> <dictionary> <entry key="userSaved" value="redirect:UserRegistered.aspx?status=Registration Successful,user=${UserInfo}"/> <entry key="cancel" value-ref="homePageResult"/> </dictionary> </property> </object> The short notation for the result must adhere to the following format... [<mode>:]<targetPage>[?param1,param2,...,paramN] Possible values for the
These values correspond to the values of the ResultMode enumeration. A comma separates parameters instead of an ampersand; this avoids laborious ampersand escaping within an XML object definition. The use of the ampersand character is still supported if required, but you then have to specify the ampersand character using the well known & entity reference. After you define your results, you can use them within the event
handlers of your pages
( private void SaveUser(object sender, EventArgs e) { UserManager.SaveUser(UserInfo); SetResult("userSaved"); } public void Cancel(object sender, EventArgs e) { SetResult("cancel"); } protected override void OnInit(EventArgs e) { InitializeComponent(); this.saveButton.Click += new EventHandler(this.SaveUser); this.cancelButton.Click += new EventHandler(this.Cancel); base.OnInit(e); } You can further refactor the preceding example and use defined constants, which is advisable when a logical result name such as "home" is likely to be referenced by many pages. You can also register a custom interpreter that can parse the shorthand string representation that creates a Result object. To do this you should view the result mapping string representation as consisting of two parts: <resultmode>:<textual result representation> The interface public interface IResultFactory { IResult CreateResult( string resultMode, string resultText ); } You use a class MySpecialResultLogic : IResult { ... } class MySpecialResultLogicFactory : IResultFactory { IResult Create( string mode, string expression ) { /* ... convert 'expression' into MySpecialResultLogic */ } } // register with global factory ResultFactoryRegistry.RegisterResultFactory( "mySpecialMode", new MySpecialResultLogicFactory ); You then use the custom continue mode in your page: <-- configure your Results --> <object type="mypage.aspx"> <property name="Results"> <dictionary> <entry key="continue" value="mySpecialMode:<some MySpecialResultLogic string representation>" /> </dictionary> </property> </object> The result redirection is done as before, by calling
ASP.NET supports client-side scripting through the use of the
Spring.Web adds several methods to enhance client-side scripting
to the base You must use the <%@ Page language="c#" Codebehind="StandardTemplate.aspx.cs" AutoEventWireup="false" Inherits="SpringAir.Web.StandardTemplate" %> <%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <html> <spring:Head runat="server" id="Head1"> <title> <spring:ContentPlaceHolder id="title" runat="server"> <%= GetMessage("default.title") %> </spring:ContentPlaceHolder> </title> <LINK href="<%= CssRoot %>/default.css" type="text/css" rel="stylesheet"> <spring:ContentPlaceHolder id="head" runat="server"></spring:ContentPlaceHolder> </spring:Head> <body> ... </body> </html> The preceding example above shows how you typically set-up a
In a similar fashion, you can add references to CSS files, or even
specific styles, directly to the To make the manual inclusion of client-side scripts, CSS files and
images easier, the Spring.Web These properties are <object name="basePage" abstract="true"> <description> Convenience base page definition for all the pages. Pages that reference this definition as their parent (see the examples below) will automatically inherit the following properties.... </description> <property name="CssRoot" value="Web/CSS"/> <property name="ImagesRoot" value="Web/Images"/> </object> Spring provides several custom user controls that are located in the
You can specify the location in the web page where validation
errors are to be rendered by using the
Some standard controls are not easy to use with Spring's
databinding support. Examples are check boxes and ratio button groups.
Here you should use the A pop-up DHTML calendar control is provided. It is a slightly modified version of the Dynarch.com DHTML Calendar control written by Mihai Bazon. You can suppress dependency injection for controls inside your ASP.NET by using the Panel control. See Customizing control dependency injection .
|