Chapter 22. Spring.NET Web Framework

22.1. Introduction

Spring.NET's web application framework aims to increase your productivity writing ASP.NET WebForms applications. It offers a unique value proposition to creating ASP.NET applications not found in other .NET web frameworks.

The goal of the framework is to make it easy to write 'thin and clean' web applications. By thin, what is meant is that the WebForm's responsibility is to act as adapter between the HTML based world of the web and the oo world of your application. The application layer your web form communicates with is where the business logic resides, not in the web tier. By 'clean' what is meant that the web framework should have a good separation of concerns, leading ideally to an event-handler that does not contain any reference to UI elements. This makes it possible to test your event handler code in integration style tests. Last but not least, Spring's 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 has been processed by the application layer.

Highlights of Spring's Web framework are

  • Dependency Injection for all ASP.NET artifacts. This includes pages and user controls but also modules, providers and HTTP handlers. Your pages, controls, etc., do not have any Spring dependencies in order to be configured via dependency injection.

  • Bi-directional data binding. This allows you to declaratively define the data that will be marshaled out of your html/user controls and into a data model that in turn is generally submitted to the application layer. After the data model is updated in the application layer, those changes are automatically reflected in the html/user controls on post back. This removes large amounts of tedious, error prone boilerplate code.

  • Web object scopes. Object definitions can be defined at the application, session or request scope. This makes it easy to inject, say a session scoped shopping cart, into your page without having to do any lower level programming.

  • Data Model Management. While ASP.NET managed the view state of your form, it does not offer facilities to manage the data model that you build up to submit to the application layer. Spring provides a mechanism similar to view state to help manage your data model.

  • UI agnostic validation framework. Declaratively define complex validation rules, for example that take into account complex relationships in your data model. Error controls are provided to easily render validation failure. This allows you to centralize your validation logic and also reuse it on the server side, for example using parameter validation advice described in the aspect library chapter

  • Externalized page navigation through 'result mapping'. Instead of hard coding urls and data to direct where a page should go next, result mappings are externally defined and configured that associate logical names and a URL (+ data). This also allows for the encryption of values that are sent via Response.Redirect.

  • Improved localization and master page support - Advanced localization features (including image localization) as well as declarative configuration of what mater page to apply to different parts of your web application are easy to perform.

All you know about ASP.NET development still applies, Spring's approach is to 'embrace and extend' the basic ASP.NET programming model so you can be as productive as possible.

[Note]Note

Support for ASP.NET MVC is planned for Spring.NET 2.0 and previews of our integration with the MVC framework will be mode available when the final MVC framework ships.

What follows is a more detailed background and motivation of features and most importantly the detailed reference manual for using Spring's web framework. One of the great things about the framework is that it is not an all or nothing solution. If you choose to use only dependency injection and bi-directional data binding, that is just fine. You can incrementally adopt the web framework, addressing problems areas in your current web application with a specific feature. There is no need to go 'whole hog' into using all parts of the framework everywhere in your application.

The Spring.NET distribution ships with a number of Web QuickStarts and a complete reference application, SpringAir. Web QuickStarts are the best way to learn each Spring.Web feature by following simple examples, and the SpringAir reference application has a Spring.Web-enabled frontend which uses many best practices for Spring.NET web applications, so please do refer to it as you are reading this (reference) material (see Chapter 37, SpringAir - Reference Application).

22.2. Background

One of the objections many developers have to the ASP.NET programming model is that it is not a "true MVC" (Model-View-Controller) implementation, because controller-type logic within the page is too tightly coupled to the view. A good example of this are event handlers within the page class, which typically have references to view elements, such as input controls, all over the place. Without getting into academic discussion of what "true MVC" is, and whether it is even appropriate to try to fit form-based technology such as ASP.NET into traditionally request-based MVC pattern when MVP (Model-View-Presenter) or Presentation Model might be more appropriate, we'd like to agree with the critics on the most important point they are making: controller-type logic, such as the code within page event handlers in ASP.NET, should not depend on the view elements.

Having said that, there are good things about ASP.NET. Server-side forms and controls make developers significantly more productive and allow us 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 HTTP processing pipeline are also very powerful features. Finally, being able 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.

For these reasons, we decided that instead of developing a new, "pure and true MVC" web framework as part of Spring.NET, we should take a more pragmatic approach and extend ASP.NET in such a way that most, if not all of its shortcomings are eliminated. It should be noted that with the introduction of a 'true MVC framework' being added to .NET, with extension points for IoC containers such as Spring, Spring will continue to play a role within a MVC based model once that functionality is available from Microsoft. It is worth noting that Spring Java has a very popular MVC framework and much of that experience and added value can be transliterated to help developers be more productive when using the upcoming ASP.NET MVC support.

Spring.Web also adds support for applying the dependency injection principle to one's ASP.NET Pages and Controls as well as http modules and custom provider modules. This means that application developers can easily inject service dependencies into web controllers by leveraging the power of the Spring.NET IoC container. See Dependency Injection for ASP.NET Pages for more information.

As we said earlier, event handlers in code-behind classes really 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 DataSet. It is for that reason that the Spring.NET team implemented bidirectional data binding framework to handle the mapping of values to and from the controls on a page to the underlying data model. The data binding framework also transparently takes care of data type conversion and formatting, enabling application developers to work with fully typed data (domain) objects in the event handlers of code-behind files. See Bidirectional Data Binding and Model Management for more information.

The flow of control through an application is another area of concern that is addressed by Spring.NET Web Framework. Typical ASP.NET applications will use Response.Redirect or Server.Transfer calls within Page logic to navigate to an appropriate page after an action is executed. This typically leads to hard-coded target URLs in the Page, which is never a good thing. Result mapping solves this problem by allowing application developers to specify aliases for action results that map to target URLs based on information in an external configuration file that can easily be edited. Under consideration for future releases of Spring.NET is a process management framework, which will take this approach to another level, allowing you to control complex page flows in a very simple way. See Result Mapping for more information.

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 Page and user control, those resources are never used by the ASP.NET infrastructure. This means that application developers have to deal directly with resource managers whenever they need access to localized resources, which in the opinion of the Spring.NET team should not be the case. Spring.NET's Web Framework (hereafter referred to as Spring.Web) adds comprehensive support for localization using both local resource files and global resources that are configured within and for a Spring.NET container. See Localization and Message Sources for more information.

In addition to the aforementioned features that can be considered to be the 'core' features of the Spring.Web framework, Spring.Web also ships with a number of other lesser features that might be useful to a large number of 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 for more information.

In order to implement some of the above mentioned features the Spring.NET team had to extend (as in the object-oriented sense) the standard ASP.NET Page and UserControl classes. This means that in order to take advantage of the full feature stack of Spring.Web (most notably bidirectional data binding, localization and result mapping), your code-behind classes will have to extend Spring.Web specific base classes such as Spring.Web.UI.Page; however, some very powerful features such as dependency injection for ASP.NET Pages, Controls, and providers can be leveraged without having to extend Spring.Web-specific base classes. It is worth stating that by taking advantage of some of the more useful features offered by Spring.Web you will be coupling the presentation tier of your application(s) to Spring.Web. The choice of whether or not this is appropriate is, of course, left to you.

22.3. Automatic context loading and hierarchical contexts

22.3.1. Configuration

Unsurprisingly, 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. This also means that all of the controllers (ASP.NET Pages) that make up a typical Spring.Web enabled application will be configured using the same standard Spring.NET XML configuration syntax. Spring.Web uses a custom PageHandlerFactory implementation to load and configure a Spring.NET IoC container, which is in turn used to locate an appropriate Page to handle a HTTP request. The WebSupportModule configures miscellaneous Spring infrastructure classes for use in a web environment, for example setting the storage strategy of LogicalThreadContext to be HybridContextStorage.

The instantiation and configuration of the Spring.NET IoC container by the Spring.Web infrastructure is wholly transparent to application developers, who will typically never have to explicitly instantiate and configure an IoC container manually (by for example using the new operator in C#). In order to effect the transparent bootstrapping of the IoC container, the Spring.Web infrastructure requires the insertion of the following configuration snippet into each and every Spring.Web-enabled web application's root Web.config file (the verb and path properties can of course be changed from the values that are shown below):

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

Please note that this snippet of standard ASP.NET configuration is only required to be present in the root directory of each Spring.Web web application (i.e. in the web.config file present in the top level virtual directory of an ASP.NET web application).

The above XML configuration snippet will direct the ASP.NET infrastructure to use Spring.NET's page factory, which will in turn create instances of the appropriate .aspx Page, (possibly) inject dependencies into said Page (as required), and then forward the handling of the request to said Page.

After the Spring.Web page factory is configured, you will also need to define a root application context by adding a Spring.NET configuration section to that same web.config file. The final configuration file should look a little like this (your exact configuration will no doubt vary in particulars)...

<?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>

There are a few important points that need to be noted with regard to the above configuration:

  1. You must define a custom configuration section handler for the spring/context element. If you use Spring.NET for many applications on the same web server, it might be easier to move the whole definition of the Spring.NET section group to your machine.config file.

  2. The custom configuration section handler is of the type Spring.Context.Support.WebContextHandler which will in turn instantiate an IoC container of the type Spring.Context.Support.WebApplicationContext. This will ensure that all of the features provided by Spring.Web are handled properly (such as request and session-scoped object definitions).

  3. Within the <spring> element you need to define a root context, and resource locations that contain the object definitions that will be used within the web application (such as service or business tier objects) then need to be specified as child elements within the <context> element. Object definition resources can be fully-qualified paths or URLs, or non-qualified, as in the example above. Non-qualified resources will be loaded using the default resource type for the context, which for the WebApplicationContext is the WebResource type.

  4. Please note that the object definition resources do not all have to be the same resource type (e.g. all file://, all http://, all assembly://, etc). This means that you can load some object definitions from resources embedded directly within application assemblies (assembly://), while continuing to load other object definitions from web resources that can be more easily edited.

22.3.1.1. Configuration for IIS7

The configuration for IIS7 is 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>

22.3.2. Context Hierarchy

ASP.NET provides a hierarchical configuration mechanism by allowing 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 Web.config file overrides settings from the (lower level) machine.config file. In the same fashion, settings specified within the web.config file within a subdirectory of a web application will override settings from the root Web.config and so on. Lower level Web.config files can also add settings of their own that were not previously defined anywhere.

Spring.Web leverages this ASP.NET feature to provide support for a context hierarchy. Your lower level Web.config files can be used to add new object definitions or to override existing ones per virtual directory.

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 will generally contain only those definitions for the pages that the component consists of and (possibly) overrides for some of the definitions from the root context (for example, menus).

Because each such lower level component will usually contain only a few object definitions, application developers are encouraged to embed those object definitions directly into the Web.config for the lower level context instead of relying on an external resource containing object definitions. This is easily accomplished by creating a component Web.config similar to the following one:

<?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 <context/> element seen above (contained within the <spring/> element) simply tells the Spring.NET infrastructure code to load (its) object definitions from the spring/objects section of the configuration file.

You can (and should) avoid the need to specify <configSections/> element by moving the configuration handler definition for the <objects> element to a higher level (root) Web.config file, or even to the level of the machine.config file if Spring.NET is to be used for multiple applications on the same server.

A very important point to be aware of is that this component-level context can reference definitions from its parent context(s). Basically, if a referenced object definition is not found in the current context, Spring.NET will search all the ancestor contexts in the context hierarchy until it finds said object definition (or ultimately fails and throws an exception).

22.4. Dependency Injection for ASP.NET Pages

Spring.Web builds on top of the feature set and capabilities of ASP.NET; one example of this can be seen the way that Spring.Web has used the code-behind class of the Page mechanism to satisfy the Controller portion of the MVC architectural pattern. In MVC-based (web) applications, the Controller is typically a thin wrapper around one or more service objects. In the specific case of Spring.Web, the Spring.NET team realized that it was very important that service object dependencies be easily injected into Page Controllers. Accordingly, Spring.Web provides first class support for dependency injection in ASP.NET Pages. This allows application developers to inject any required service object dependencies (and indeed any other dependencies) into their Pages using standard Spring.NET configuration instead of having to rely on custom service locators or manual object lookups in a Spring.NET application context.

Once an application developer has configured the Spring.NET web application context, said 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>

This example contains three definitions:

  1. The first definition is an abstract definition for the base page that many other pages in the application will inherit from. In this case, the definition simply specifies which page is to be referenced as the master page, but it will typically also configure localization-related dependencies and root folders for images, scripts and CSS stylesheets.

  2. The second definition defines a login page that neither inherits from the base page nor references the master page. What it does show is how to inject a service object dependency into a page instance (the authenticationService is defined elsewhere).

  3. The final definition defines a default application page. In this case it simply inherits from the base page in order to inherit the master page dependency, but apart from that it doesn't need any additional dependency injection configuration.

One thing that slightly differentiates the configuration of ASP.NET pages from the configuration of other .NET classes is in the value passed to the type attribute. As can be seen in the above configuration snippet the type name is actually the path to the .aspx file for the Page, relative to the directory context it is defined in. In the case of the above example, those definitions are in the root context so Login.aspx and Default.aspx also must be in the root of the web application's virtual directory. The master page is defined using an absolute path because it could conceivably be referenced from child contexts that are defined within subdirectories of the web application.

The astute reader may have noticed that the definitions for the Login and Default pages don't specify either of the id and name attributes. This is in marked contrast to typical object definitions in Spring.NET, where the id or name attributes are typically mandatory (although not always, as in the case of inner object definitions). This is actually intentional, because in the case of Spring.Web Page Controller instances one typically wants to use the name of the .aspx file name as the identifier. If an id is not specified, the Spring.Web infrastructure will simply use the name of the .aspx file as the object identifier (minus any leading path information, and minus the file extension too).

Nothing prevents an application developer from specifying an id or name value explicitly; one use case when the explicit naming might be useful is when one wants to expose the same page multiple times using a slightly different configuration (Add / Edit pages for example). If you would like to use abstract object definitions and have your page inherit from them, the use of the name attribute should be used instead of the id attribute on the abstract object definition.

22.4.1. Injecting Dependencies into Controls

Spring.Web also allows application developers to inject dependencies into controls (both user controls and standard controls) that are contained within a page. This can be accomplished globally for all controls of a particular Type by using the location of the .ascx as the object type identifier. This is similar to injecting into .aspx pages shown above.

<object type="~/controls/MyControl.ascx" abstract="true">
   <!-- inject dependencies here... -->
</object>

In either case, be sure to mark the object definition as abstract (by adding abstract="true" to the attribute list of the <object/> element).

22.4.2. Injecting dependencies into custom HTTP modules

You can perform dependency injection on custom HTTP modules through the use of the class Spring.Context.Support.HttpApplicationConfigurer. You register your custom HTTP module as you would normally, for example a module of the type HtmlCommentAppenderModule, taken from the Web Quickstart, appends additional comments into the http response. It is registered as shown below

<httpModules>
  <add name="HtmlCommentAppender" type="HtmlCommentAppenderModule"/>
</httpModules>

To configure this module, naming conventions are used to identify the module name with configuration instructions in the Spring configuration file. The ModuleTemplates property of HttpApplicationConfigurer is a dictionary that takes as a key the name of the HTTP module, HtmlCommentAppender, and as a value the configuration instructions as you would normally use for configuring an object with Spring. An example is shown below. HttpApplicationConfigurer' ModuleTemplates property.

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

22.4.3. Injecting dependencies into HTTP handlers and handler factories

You can perform dependency injection on IHttpHandlers and IHttpHandlerFactory. This effectively allows for a fully Spring-managed <httpHandlers> configuration section. To configure an IHttpHandler or IHttpHandlerFactory you should register Spring's MappingHandlerFactory with a specific path or wildcard string (i.e. *.aspx) using the standard configuration of an <httpHandler> in web.config. This is shown below

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

The specialization of which specific handler is mapped to the path is done by configuration of Spring's MappingHandlerFactoryConfigurer class. The MappingHandlerFactoryConfigurer is configured by specifying a dictionary of key value paris, the key value is a regular expression that will match the request URL and the value is an instance of an IHttpHandler or IHttpHandlerFactory configured via dependency injection.

the configuration of MappingHandlerFactoryConfigurer is shown below

<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 DefaultHandlerFactory will use the .NET class System.Web.UI.SimpleHandlerFactory to create handler instaces and will configure each instances used an object definition whose name matches the request url's filename. The abstract object definition of DemoHandler.ashx is an example of this approach. You may also configure standard classes that implment the IHttpHandler interface as demonstrated in the example above for the class MyCustomHttpHandler.

Please refer to the Web Quickstart application too see this in action.

22.4.4. Injecting dependencies into custom providers

Custom providers can be configured with Spring. The approach to configuration is a family of adapters that correspond 1-to-1 with the standard ASP.NET providers that are registered using the standard ASP.NET mechanism. The adapters inherit from their correspondingly named provider class in the BCL.

  • MembershipProviderAdapter

  • ProfileProviderAdapter

  • RoleProviderAdapter

  • SiteMapProviderAdapter

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. For convenience there are configurable versions of the providers found in ASP.NET so that you can use the full functionality of spring to configure these standard provider implementations, for example using property place holders, etc. These are

  • ConfigurableActiveDirectoryMembershipProvider

  • ConfigurableSqlMembershipProvider

  • ConfigurableSqlProfileProvider

  • ConfigurableSqlRoleProvider

  • ConfigurableXmlSiteMapProvider

Here is an example configuration taken from the Web Quickstart that simply 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.

22.4.5. Customizing control dependency injection

There might be situations where it is necessary to customize Spring.Web's dependency injection processing. In particular when using GridViews, which create a large number of child controls, dependency injection can slow down your page. To overcome this problem, you may tell Spring to handle the dependency injection process yourself by implementing the interface ISupportsWebDependencyInjection as 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 );
  }
}

There is a Spring server control, Panel, that provides an easier way to turn of dependency injection for parts of your page. Example use is shown below

<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 using the attribute suppressDependencyInjection. By default <spring:Panel/> won't render a container tag (<div>, <span>, etc.). You can modify this behavior by setting the attribute "renderContainerTag" accordingly.

22.5. Web object scopes

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"/>

As you can see, there are three possible values for the scope attribute -- application, session or request. Application scope is the default, and will be used for all objects that don't have scope attribute defined. As its name says, it will result in a single instance of an object being created for the duration of the application, so it works exactly like the standard singleton objects in non-web applications. Session scope allows you to define objects in such a way that an instance is created for each HttpSession. This is the ideal scope for objects that you want bound to a single user such as user profile, shopping cart, etc. Request scope will result in a creation of an instance per HTTP request.

Unlike with prototype objects, calls to IApplicationContext.GetObject will return the same instance of the request-scoped object during a single HTTP request. This allows you, for example, to inject the same request-scoped object into multiple pages and then use server-side transfer to move from one page to another. As all the pages are executed within the single HTTP request in this case, they will share the same instance of the injected object.

One thing to keep in mind is that 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, session-scoped objects can reference both session and application-scoped objects, and finally, request-scoped objects can reference other request, session or application-scoped objects. Also, prototype objects (and that includes all ASP.NET web pages defined within Spring.NET context) can reference singleton objects from any scope, as well as other prototype objects.

22.6. Master Pages in ASP.NET 1.1

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.

The idea is that a web developer can define a layout template for the site as a master page and specify content place holders that other pages can then reference and populate. A sample master page (MasterLayout.ascx) could look like this:

<%@ 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>

As you can see from the above 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 (Child.aspx) that uses this master page 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 <spring:Content/> control in the above example uses the contentPlaceholderId attribute (property) to specify exactly which placeholder from the master page is to be overridden. Because this particular page does not define content elements for the head and title place holders, they will be displayed using the default content supplied in the master page.

Both the ContentPlaceHolder and Content controls can contain any valid ASP.NET markup: HTML, standard ASP.NET controls, user controls, etc.

[Tip]VS.NET 2003 issue

Technically, the <html> and <body> tags from the previous example are not strictly necessary because they are already defined in the master page. However, if these tags are omitted, then Visual Studio 2003 will complain about a schema and intellisense won't work, so it's much easier to work in the HTML view if those tags are included. They will be ignored when the page is rendered.

22.6.1. Linking child pages to their master

The Spring.Web.UI.Page class exposes a property called MasterPageFile, which can be used to specify the master page.

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 being used for a number of pages within a web application. Of course, the master page can still be overridden on a per context or per page basis by creating a new abstract page definition within a child context, or by specifying the MasterPageFile property directly.

22.7. Bidirectional Data Binding and Model Management

A problem with the existing data binding support in ASP.NET is that it is one-way only. It allows application developers to bind page controls to the data model and display information from said data model, but it doesn't allow for the extraction of values from the controls when the form is submitted. Spring.Web adds such 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 also doesn't provide any support for model management within the postbacks. Sure, it has a ViewState management, but that takes care of the control state only and not of the state of any presentation model objects these controls might be bound to. In order to manage model within ASP.NET, developers will typically use HTTP Session object to store the model between the postbacks. This results in a decent amount of boilerplate code that can and should be eliminated, which is exactly what Spring.Web does by providing a simple set of model management methods.

Please note that in order 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 requires you to extend a Spring.Web.UI.Page instead of the usual System.Web.UI.Page class.

Spring.Web data binding is very easy to use. Application developers simply need to override the protected InitializeDataBindings method and configure data binding rules for the page. They also need to override three model management methods: InitializeModel, LoadModel and SaveModel. This is perhaps best illustrated by an example from the SpringAir reference application. First, let's take a look at the page markup:

<%@ 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>&nbsp;</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, which will be described later when we discuss localization in Spring.NET. What is important for the purposes of our current discussion, is that we have a number of input controls defined: tripMode radio group, leavingFromAirportCode and goingToAirportCode dropdowns, as well as two Spring.NET Calendar controls, departureDate and returnDate.

Next, let's take a look at the model we will be binding this form to:

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, Trip class uses the TripPoint class to represent departure and return, which are exposed as StartingFrom and ReturningFrom properties. It also uses TripMode enumeration to specify whether the trip is one way or return trip, which is exposed as Mode property.

Finally, let's see 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
        }
    }
}

There are quite a few things that are happening in this relatively simple piece of code, so it's worth that we spend some time on each one:

  1. When the page is initially loaded (IsPostback == false), the InitializeModel() method is called which initializes the trip object by creating a new instance and setting its properties to desired values. Right before the page is rendered, the SaveModel() method will be invoked and whatever the value it returns will be stored within the HTTP Session. Finally, on each postback, the LoadModel() method will be called and the value returned by the previous call to SaveModel will be passed to it as an argument.

    In this particular case the implementation is very simple because our whole model is just the trip object. As such, SaveModel() simply returns the trip object and LoadModel() casts the savedModel() argument to Trip and assigns it to the trip field within the page. In the more complex scenarios, you will typically return a dictionary containing your model objects from the SaveModel() method, and read the values from that dictionary within the LoadModel().

  2. InitializeDataBindings method defines the binding rules for all five input controls on our form. It does so by invoking AddBinding method on the BindingManager exposed by the page. AddBinding method is heavily overloaded and it allows you to specify a binding direction and a formatter to use in addition to the source and target binding expressions that are used above. We'll discuss these optional parameters shortly, but for now let's focus on the source and target expressions.

    The Data Binding framework uses Spring.NET Expression Language to define binding expressions. In most cases, like in the example above, both source and target expression will evaluate to a property or a field within one of the controls or a data model. This is always the case when you are setting a bi-directional binding, as both binding expressions need to be "settable". What is important to remember about InitializeDataBindings method is that it is executed only once per page type. Basically, all of the binding expressions are parsed the first time the page is instantiated, and are the cached and used by all instances of that same page type that are created at a later time. This is done for performance reasons, as data binding expression parsing on every postback is unnecessary and would add a significant overhead to the overall page processing time.

  3. If you look at the SearchForFlights event handler, you will notice that it has no dependencies on the view elements. It simply uses the injected bookingAgent service and a trip object that in order to obtain a list of suggested flights. Furthermore, if you make any modifications to the trip object within your event handler, bound controls will be updated accordingly just before the page is rendered.

    This accomplishes one of the major goals we set out to achieve, allowing developers to remove view element references from the page event handlers and decouple controller-type methods from the view.

Now that you have a solid high-level picture of how Spring.NET data binding and model management are typically used in web applications, let's take a look at the details and see how data binding is actually implemented under the hood, what the extension points are, and what are some additional features that make data binding framework usable in real-world applications.

22.7.1. Data Binding Under the Hood

Spring.NET Data Binding framework revolves around two main interfaces: IBinding and IBindingContainer. The IBinding interface is definitely the more important one of the two, as it has to be implemented by all binding types. This interface defines several methods, with some of them being overloaded for convenience:

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);
}

As their names imply, BindSourceToTarget method is used to extract and copy bound values from the source object to the target object, while BindTargetToSource does the opposite. Both method names and parameter types are very generic for a good reason -- data binding framework can indeed be used to bind any two objects. Using it to bind web forms to model objects is just one of its possible uses, although a very common one and tightly integrated into the Spring.NET Web Framework.

The validationErrors parameter requires further explanation. While the data binding framework is not in any way coupled to the data validation framework, they are in some ways related. For example, while the data validation framework is best suited to validate the populated model according to the business rules, the data binding framework is in a better position to validate data types during the binding process. However, regardless of where specific validation is performed, all error messages should be presented to the user in a consistent manner. In order to accomplish this, Spring.NET Web Framework passes the same ValidationErrors instance to binding methods and to any validators that might be executed within your event handlers. This ensures that all error messages are stored together and are displayed consistently to the end user, using Spring.NET validation error controls.

The last method in the IBinding interface, SetErrorMessage, enables this by allowing you to specify the resource id of the error message to be displayed in the case of binding error, as well as the list of error providers that messages should be displayed in. We will see an example of the SetErrorMessage usage shortly.

The IBindingContainer interface extends the IBinding interface and adds the following members:

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);
}

As you can see, this interface has a number of overloaded AddBinding methods. The first one, AddBinding(IBinding binding) is the most generic one, as it can be used to add any binding type to the container. The other four are convenience methods that provide a simple way to add the most commonly used binding type, SimpleExpressionBinding. The SimpleExpressionBinding is what we used in the example at the beginning of this section to bind our web form to a Trip instance. It uses Spring.NET Expression Language to extract and to set values within source and target objects. We discussed sourceExpression and targetExpression arguments earlier, so let's focus on the remaining ones.

22.7.1.1. Binding Direction

The direction argument determines whether the binding is bidirectional or unidirectional. By default, all data bindings are bidirectional unless the direction argument is set to either BindingDirection.SourceToTarget or BindingDirection.TargetToSource. If one of these two values is specified, binding will be evaluated only when the appropriate BindDirection method is invoked, and will be completely ignored in the other direction. This is very useful when you want to bind some information from the model into non-input controls, such as labels.

However, unidirectional data bindings are also useful when your form doesn't have a simple one-to-one mapping to presentation model. In our 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 Airport class and modify our TripPoint class like this:

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 AirportCode, our TripPoint class now exposes an Airport property of type Airport, which is defined above. Now we have a problem: what used to be a simple string to string binding, with the airport code selected in a dropdown being copied directly into the TripPoint.AirportCode property and vice versa, now becomes a not so simple string to Airport binding, so let's see how we can solve this mismatch problem.

First of all, binding from the model to the control is still very straight forward. We just need to set up one-way bindings from the model to controls:

protected override void InitializeDataBindings()
    {
        BindingManager.AddBinding("leavingFromAirportCode.SelectedValue", "Trip.StartingFrom.Airport.Code", BindingDirection.TargetToSource);
        BindingManager.AddBinding("goingToAirportCode.SelectedValue", "Trip.ReturningFrom.Airport.Code", BindingDirection.TargetToSource);
        ...
    }

All we need to do is extract airport code value from the Trip.StartingFrom.Airport.Code instead of Trip.StartingFrom.AirportCode. Unfortunately, binding from the control to the model the same way won't work: we might be able to set Code property of the Airport object, but that will likely make the Airport.Name property invalid. What we really want do is find an instance of the Airport class based on the airport code and set the TripPoint.Airport property to it. Fortunately, this is very simple to do with Spring.NET data binding, especially because we already have airportDao object defined in the Spring context, which has GetAirport(string airportCode) finder method. All we need to do is set up data bindings from source to target that will invoke this finder method when evaluating the source expression. 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);
        ...
    }

That's it -- by using two unidirectional bindings with different expressions and by leveraging the fact that expressions can reference objects defined in the Spring context, we were able to solve this non-trivial data binding problem.

22.7.1.2. Formatters

The last argument to AddBinding method that we need to discuss is a formatter argument. This argument allows you to specify a formatter that should be used to parse string value from the typical input control before it is bound to the model, and to format strongly typed model value before it is bound to the control.

You will typically use one of the formatters provided in the Spring.Globalization.Formatters namespace, but if you have requirements that cannot be satisfied by one of the standard formatters it is easy enough to write your own -- all you need to do is implement a very simple IFormatter interface:

public interface IFormatter
{
    string Format(object value);
    object Parse(string value);
}

Standard formatters provided with Spring.NET are: CurrencyFormatter, DateTimeFormatter, FloatFormatter, IntegerFormatter, NumberFormatter and PercentFormatter, which should be sufficient for most usage scenarios.

22.7.1.3. Type Conversion

Because the data binding framework uses the same expression evaluation engine as the Spring.NET IoC container, it will use 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 automatically registered for you, but you can implement your own custom converters and register them using standard Spring.NET type converter registration mechanisms.

22.7.1.4. Data Binding Events

Spring.Web's base Page class adds two events to the standard .NET page lifecycle - DataBound and DataUnbound.

The DataUnbound event is fired after the data model has been updated using values from the controls. It is fired right after the Load event and only on postbacks, because it doesn't make sense to update the data model using the controls' initial values.

The DataBound is fired after controls have been updated using values from the data model. This happens right before the PreRender event.

The fact that data model is updated immediately after the Load event and that controls are updated right before the PreRender event means that your event handlers will be able to work with a correctly updated data model, as they execute after the Load event, and that any changes you make to the data model within event handlers will be reflected in the controls immediately afterwards, as they (the controls) are updated prior to the actual rendering.

22.7.1.5. Rendering Binding Errors

If there are errors in the databinding, for example, trying to bind a string 'hello' to an integer property on the model, you can specify how those fundamental binding errors should be rendered. An example of this shown below taken from the WebQuickStart '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 followed by a variable length list of strings that specify the collection of error providers message where the message should be displayed. In the above case the error provider will be rendered in Spring's ValidationError User Control. See

22.7.1.6. HttpRequestListBindingContainer

HttpRequestListBindingContainer extracts posted raw values from the request and populates the specified IList by creating objects of the type specified and populating each of these objects according to the requestBindings collection.

Please checkout the WebQuickStart sample's demo of HttpRequestListBindingContainer. Below

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);
}
[Note]Note
Due to the fact, that browsers don't send the values of unchecked checkboxes, you can't use HttpRequestListBindingContainer with <input type="checkbox" > html controls.

22.7.2. Using DataBindingPanel

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 for specifying additional, data binding related attributes to 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 the binding information can be specified directly on the control declaration. The following attributes are recognized by a DataBindingPanel:

  • BindingTarget

    corresponds to the target expression used in IBindingContainer.AddBinding()

  • BindingSource

    corresponds to the source expression used in IBindingContainer.AddBinding(). For standard controls you don't need to specify the source expression. If you are binding to some custom control, of course you must specific this attribute.

  • BindingDirection

    one of the values of the BindingDirection enumeration

  • BindingFormatter

    if you need a custom formatter, you can specific the object name of a formatter here. The formatter instance will be obtained by a call to IApplicationContext.GetObject() each time it is needed.

  • BindingType

    In case you need a completely customized binding, specify its type here. Note that a custom binding type must implement the following constructor signature:

    ctor(string source,string target, BindingDirection, IFormatter)

[Note]Note
The Visual Studio Web Form Editor will of course complain about binding attributes because it doesn't know them. You can safely ignore those warnings.

22.7.3. Customizing Model Persistence

As was already mentioned in the introduction to this chapter, model management needs an application developer to override InitializeModel(), SaveModel() and LoadModel() for storing model information between requests in the user's session. On web farms this of course storing information in a user's session is not a good strategy. Thus it is possible to choose another persistence strategy by setting a Spring.Web.UI.Page's resp. Spring.Web.UI.UserControl's ModelPersistenceMedium property:

<object id="modelPersister" type="Sample.DatabaseModelPersistenceMedium, MyCode"/>