(Available in 1.1)
It is the considered opinion of the Spring.NET team that ASP.NET already provides a decent implementation of the basic MVC pattern (with very few shortcomings) and that Spring.NET's web library must only provide those extensions that are necessary to overcome these shortcomings. The separation between HTML view code in an .aspx file and the code-behind class is a good one and is more than sufficient for the vast majority of enterprise applications; the code-behind class can be used as a controller (in the classic MVC sense), with event handlers effectively representing actions that the controller executes.
Having said that, event handlers in controllers (code-behind classes) should not have to deal with ASP.NET UI controls directly. Such event handlers should rather work with the data 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 to handle the mapping of values to and from the controls on a page to the underlying data model. Data binding transparently also takes care of type conversion and formatting, enabling application developers to work with fully typed data (domain) objects in the event handler's of code-behind files.
The flow of control through an application is another area of concern that is addressed by Spring.NET's web support. 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 easily can be edited.
Standard localization support is also limited in versions of ASP.NET prior to ASP.NET 2.0. Even though Visual Studio.NET 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 library (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.
Spring.Web also adds support for applying the dependency injection principle to one's ASP.NET Pages and Controls. This means that application developers can easily inject service dependencies into web controllers by leveraging the power of the Spring.NET IoC container.
In addition to the aforementioned features that can be considered to be the 'core' features of the Spring.Web library, 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); others are simply features that are generally useful, such as support for wizards (pages where the business process spans multiple distinct requests and / or form submissions).
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. 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 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 view tier of your application (s) to Spring.Web... the choice of whether or not this is appropriate is of course left to the end user.
Finally, please be aware that the standard Spring.NET distribution (as of v1.1) ships with a complete reference application, SpringAir. This reference application has a Spring.Web-enabled ASP.NET web frontend, so please do refer to it as you are reading this (reference) material (see Chapter 18, SpringAir - Reference Application).
Unsurprisingly, the Spring.Web library 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 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 instatiate 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>
...
</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.ContextHandler, Spring.Core"/>
</sectionGroup>
</configSections>
<spring>
<context type="Spring.Context.Support.WebApplicationContext, Spring.Web">
<resource uri="~/Config/CommonObjects.xml"/>
<resource uri="~/Config/CommonPages.xml"/>
</context>
</spring>
<system.web>
<httpHandlers>
<add verb="*" path="*.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/>
</httpHandlers>
</system.web>
</configuration>
There are a few important points that need to be noted with regard to the above configuration...
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.
Within the <spring> element you will need to define a root context and specify the Type of the container that is be be instantiated as Spring.Context.Support.WebApplicationContext. This will ensure that all of the features provided by Spring.Web are handled properly (such as request-scoped object definitions).
The resources that contain the object definitions that will be used within the web application (such as service or busniness 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.
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 easily more easily edited.
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.
Needless to say, 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 it's 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).
Spring.Web builds on top of the featureset 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>
<object id="masterPage" type="~/Master.aspx" />
<object id="basePage" abstract="true">
<property name="Master" ref="masterPage"/>
</object>
<object type="Login.aspx">
<property name="Authenticator" ref="authenticationService"/>
</object>
<object type="Default.aspx" parent="basePage"/>
</objects>This example contains four definitions:
The first definition specifies the Page that is to be used as a Master Page.
The second 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.
The third 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).
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 is 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).
Of course, 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).
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 in two ways - either globally for all the controls of a particular Type, or locally, for a specific control within a page.
In order to inject dependencies globally, the fully qualified Type name of the control needs to be used as the object definition identifier; please note that the assembly name that usually has to be supplied with fully qualified Type names in Spring.NET object definitions must not be specified.
<object id="MyProject.MyControl" abstract="true"> <!-- inject dependencies here... --> </object>
In order to inject dependencies locally, the control's UniqueID needs to used as the object definition identifier:
<object id="myContainerControl:myTargetControl" 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).
The easiest way to obtain a control's UniqueID is by turning tracing on for the application and looking for the UniqueID in the control hierarchy section of the resulting page trace. Yes, this is... unfortunate.
Please note that both global and local definitions for the same control can be specified, in which case the resulting definitions will be merged, with values from the local definition overriding values from the global definition as necessary.
Due to context scoping issues, application developers will probably want to define global control definitions in the root context so that dependencies are injected consistently throughout the web application. In contrast, local control definitions are better defined within the child context for the component, in order to avoid naming conflicts between different components.
![]() | |
Unfortunately, it is currently impossible to inject dependencies into controls before their Init event is fired, which means objects supplied via dependency injection cannot be used (referenced) within the control's OnInit method (or in any event handler for the Init event). This is because dependencies are injected after the Init event has fired, but before the Load event has fired; what this means in practical terms is that the event handler for the Load event is the first place in the code where dependency injected objects and values can safely be used. | |
Support for 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 (Master.aspx) could look like this:
<%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %>
<%@ Page language="c#" Codebehind="Master.aspx.cs" AutoEventWireup="false" Inherits="ArtFair.Web.UI.Master" %>
<!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 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 by the master page.
Both the ContentPlaceHolder and Content controls can contain any valid ASP.NET markup: HTML, standard ASP.NET controls, user controls, etc.
![]() | |
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.NET.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. | |
The Spring.Web.UI.Page class exposes a property called Master that can be used to supply a reference to the child page's owning master page. There is also a property called MasterFile that allows one to specify the master page using the file name of said 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>
<object id="masterPage" type="~/Master.aspx" />
<object id="basePage" abstract="true">
<property name="Master" ref="masterPage"/>
</object>
<object type="Child.aspx" parent="basePage">
<!-- inject other objects that page needs -->
</object>
</objects>This approach alows 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 page basis by specifiying the Master or MasterFile properties directly.
A problem with the existing data binding support in ASP.NET is that 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 using custom attributes that application developers can use to describe bindings, and also by providing data binding logic within a base Page class.
Please note that in order to take advantage of the bidirectional data binding support providd by Spring.Web, an application developer will have to couple his or her view layer to Spring.Web; this is because the bidirectional data binding support requires extending a Spring.Web Page (and not the usual System.Web.UI.Page class).
Spring.Web data binding is very easy to use. Application developers need only attribute control declarations within the Page that is to benefit from Spring.Web's bidirectional data binding support; this is perhaps best illustrated by an example:
public class UserRegistration : Spring.Web.UI.Page
{
[Binding("Text", "UserInfo.Email")]
protected TextBox email;
[Binding("Text", "UserInfo.Password")]
protected TextBox password;
protected TextBox passwordConfirmation;
[Binding("Text", "UserInfo.FullName")]
protected TextBox name;
[Binding("Text", "UserInfo.Address.Street1")]
protected TextBox street1;
[Binding("Text", "UserInfo.Address.Street2")]
protected TextBox street2;
[Binding("Text", "UserInfo.Address.City")]
protected TextBox city;
[Binding("Text", "UserInfo.Address.State")]
protected TextBox state;
[Binding("Text", "UserInfo.Address.PostalCode")]
protected TextBox postalCode;
[Binding("Text", "UserInfo.Address.Country")]
protected TextBox country;
private User user;
// in this example, this User property is the 'data model'...
public User UserInfo
{
get { return user; }
set { user = value; }
}
// the rest of the class definition omitted...
}The first parameter to the Binding attribute is the name of the control property that is being bound. The second argument is an object navigation expression that will be used both to get and set the value in the underlying data model. For more information on the supported object navigation expression syntax please do refer to Chapter 7, Object Navigation.
The Binding attribute also has a third (optional) parameter, OneWay. If the OneWay value is set to true (it is false by default), Spring.Web will not attempt to update the underlying data model with the value of the bound control's property. This is useful when a control is bound to a calculated read-only property in a data model.
Another (again optional) parameter is Format, which allows an application developer to specify how the boun value must be formatted for display. This parameter is usually used in conjunction with the OneWay attribute to provide custom formatting for date or numeric values. Any of the usual format expressions supported by the String.Format method can be used.
The Spring.Web data binder will attempt to convert types when copying values from controls to data model (and vice versa). If the Format parameter is specified in an applied Binding attribute, the Spring.Web data binder will use the value of the said Format to convert the data model value to a String. Otherwise, the Spring.Web data binder will rely on the standard .NET TypeConverter mechanism for conversion.
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.
While recognizing that the .NET framework has excellent support for localization, the support within ASP.NET 1.x is somewhat incomplete.
Every .aspx page in an ASP.NET project has a resource file associated with it, but those resources are never used (by the current ASP.NEt infrastructure). ASP.NET 2.0 will change that and allow application developers to use local resources for pages. In the meantime, the Spring.NET team built support for using local pages resources into Spring.Web thus allowing application developers to start using ASP.NET 2.0-like page resources immediately.
Spring.Web supports several different approaches to localization within a web application, which can be mixed and matched as appropriate. Both push and pull mechanisms are supported, as well as the fallback to globally defined resources when a local resource cannot be found. Spring.Web also provides support for user culture management and image localization, which are described in the later sections.
![]() | |
Introductory material covering ASP.NET Globalization and Localization can be found at the following URLs; Globalization Architecture for ASP.NET and Localization Practices for ASP.NET 2.0 by Michele Leroux Bustamante. | |
The central idea behind 'push' localization is that an application developer should be able to specify localization resources in the resource file for the page and have those resources automatically applied to the user controls on the page by the framework. For example, an application developer could define a page such as UserRegistration.aspx...
<%@ 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><asp:Label id="street1Label" Runat="server"/></td>
<td><asp:TextBox id="street1" Runat="server" Width="150px"/></td>
</tr>
<tr>
<td><asp:Label id="street2Label" Runat="server"/></td>
<td><asp:TextBox id="street2" Runat="server" Width="150px"/></td>
</tr>
<tr>
<td><asp:Label id="cityLabel" Runat="server"/></td>
<td><asp:TextBox id="city" Runat="server" Width="150px"/></td>
</tr>
<tr>
<td><asp:Label id="stateLabel" Runat="server"/></td>
<td><asp:TextBox id="state" Runat="server" Width="30px"/></td>
</tr>
<tr>
<td><asp:Label id="postalCodeLabel" Runat="server"/></td>
<td><asp:TextBox id="postalCode" Runat="server" Width="60px"/></td>
</tr>
<tr>
<td><asp:Label id="countryLabel" Runat="server"/></td>
<td><asp:TextBox id="country" 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>A close inspection of the above .aspx code reveals that none of the Label or Button controls have had a value assigned to the Text property. The values of the Text property for these controls are stored in the local resource file (of the page) using the following convention to identify the resource (string).
$this.controlId.propertyName
The corresponding local resource file, UserRegistration.aspx.resx, is shown below.
<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.street1Label.Text">
<value>Street 1:</value>
</data>
<data name="$this.street2Label.Text">
<value>Street 2:</value>
</data>
<data name="$this.cityLabel.Text">
<value>City:</value>
</data>
<data name="$this.stateLabel.Text">
<value>State:</value>
</data>
<data name="$this.postalCodeLabel.Text">
<value>ZIP:</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>The last two resource definitions require some additional explanation. 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 Save and Cancel buttons globally as they will probably be used throughout the application.
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.
Taling the case of the above example, this will tell the localizer to use the save and cancel portions of the resource key as lookup keys to retrieve the actual values from a global message source. The important thing to remember is that one need only define a resource redirect once, typically in the invariant resource file – any lookup for a resource redirect will simply fall back to the invariant culture, and result in a global message source lookup using the correct culture.
![]() | |
To view the .resx file for a page, you may need to enable "Project/Show All Files" in Visual Studio.NET. When "Show All Files" is enabled, the .resx file appears like a "child" of the code-behind page. When Visual Studio.NET creates the .resx file, it will include a xds:schemaelement and several reshead elements. Your data elements will follow the reshead elements. When working with the .resx files, you may want to chose "Open With" from the context menu and select the "Source Code" text editor. | |
In order to apply such resources automatically, a localizer needs to be injected into all such oages requiring this feature (typically accomplished using a base oage definition that other oages will inherit from). The injected localizer will inspect the resource file when the page is first requested, cache the resources that start with the '$this' marker string value, and apply the values to the controls that populate the page prior to the page being rendered.
A localizer is simply an object that implements the Spring.Globalization.ILocalizer interface. Spring.Globalization.AbstractLocalizer is provided as a convenient base class for localization: this class has one abstract method, LoadResources. This method must load and return a list of all the resources that must be automatically applied from the resource store.
Spring.NET ships with one concrete implementation of a localizer, Spring.Globalization.Localizers.ResourceSetLocalizer, that retrieves a list of resources to apply from the local resource file. Future releases of Spring.NET will provide other localizers that read resources from an XML file or even a flat text file that contains resource name-value pairs which will allow application developers to store resources within the files in a web application instead of as embedded resources in an assembly. Of course, if an application developer would rather store such resources in a database, he or she can write their own ILocalizer implementation that will load a list of resources to apply from a database.
As mentioned previously, one would typically configure the localizer to be used within an abstract base definition for those pages that require localization as shown below.
<object id="localizer" type="Spring.Globalization.Localizers.ResourceSetLocalizer, Spring.Core"/>
<object id="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 UserControl instances will (by default) inherit the localizer and other localization settings from the page that they are contained within, but one can similarly also override that behavior using explicit dependency injection.
While automatic localization as described above works great for many form-like pages, it doesn't work nearly as well for the controls defined within any iterative controls because the IDs for such iterative controls are not fixed. It also doesn't work well in those cases where one needs 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 18, SpringAir - Reference Application).
In these situations, one should use a pull-style mechanism for localization, which boils down to a simple GetMessage call as shown below.
<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 GetMessage method is available within both the Spring.Web.UI.Page and Spring.Web.UI.UserControl classes, and it will automatically fall back to a global message source lookup if a local resource is not found.
Spring.Web provides an easy (and consistent) way to localize images within a web application. 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 application developers to localize images within the page as easily as text resources.
The Spring.Web Page class exposes the ImagesRoot property, which is used to define the root directory where images are stored. The default value is 'Images', which means that the localizer expects to find an 'Images' directory within the application root, but one can set it to any value in the definition of the page.
In order to localize images, one needs to create a directory for each localized culture under the ImagesRoot directory as shown below.
/MyApp
/Images
/en
/en-US
/fr
/fr-CA
/sr-SP-Cyrl
/sr-SP-Latn
...Once an appropriate folder hierarchy in is place all one need do is put the localized images in the appropriate directories and make sure that different translations of the same image are named the same. In order to place a localized image on a page, one needs to use the <spring:LocalizedImage> as shown below.
<%@ 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 'en-US', the localizer will look for the spring-air-logo.jpg file in Images/en-US, then in Images/en and finally, if the image file has still not been found, in the root Images directory (which for all practical purposes serves as an invariant culture folder).
Global resources are (on a per-context basis) defined as a plain vanilla object definition using the reserved name of 'messageSource', which one can add to one's Spring.NET configuration file as shown below.
<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>The global resources are cached within the Spring.NET IApplicationContext and are accessible through the Spring.NET IMessageSource interface.
The Spring.Web Page and UserControl classes have a reference to their owning IApplicationContext and it's associated IMessageSource. As such, they will automatically redirect resource lookups to a global message source if a local resource cannot be found.
Currently, the ResourceSetMessageSource is the only message source implementation that ships with Spring.NET.
In addition to global and local resource management, Spring.Web also adds support for user culture management by exposing the current CultureInfo through the UserCulture property on the Page and UserControl classes.
The UserCulture property will simply delegate culture resolution to an implementation of Spring.Globalization.ICultureResolver interface. One can specify exactly which culture resolver to use by configuring the CultureResolver property of the Page class in the relevant object definition as shown below.
<object id="BasePage" abstract="true">
<property name="Master" ref="MasterPage"/>
<property name="CultureResolver">
<object type="Spring.Globalization.Resolvers.CookieCultureResolver, Spring.Web"/>
</property>
</object>Several useful implementations of the ICultureResolver ship as part of Spring.Web, so it is unlikely that application developers will have to implement their own culture resolver. However, if one does have such a requirement, the resulting implementation should be fairly straightforward as there are only two methods that one need implement. The following sections discuss each available implementation of the ICultureResolver interface.
This is default culture resolver implementation. It will be used if one does not specify a culture resolver for a page, or if one explicitly injects a DefaultWebCultureResolver into a page definition explicity. The latter case (explicit injection) is sometimes useful because it allows one to to specify a culture that should always be used by providing a value to the DefaultCulture property on the resolver.
The DefaultWebCultureResolver will first look at the DefaultCulture property and return its value if said property value is not null. If it is null, the DefaultWebCultureResolver will fall back to request header inspection, and finally, if no 'Accept-Lang' request headers are present it will return the UI culture of the currently executing thread.
This resolver works in a similar way to the DefaultWebCultureResolver with the exception that it always checks request headers first, and only then falls back to the value of the DefaultCulture property or the culture cound to the current thread.
This resolver will look for culture information in the user's session and return it if it finds one. If not, it will fall back to the behavior of the DefaultWebCultureResolver.
This resolver will look for culture information in a cookie, and return it if it finds one. If not, it will fall back to the behavior of the DefaultWebCultureResolver.
![]() | |
CookieCultureResolver will not work if your application uses localhost as the server URL, which is a typical setting in a development environment. In order to work around this limitation you should use SessionCultureResolver during development and switch to CookieCultureResolver before you deploy application in a production. This is easily accomplished in Spring.Web (simply change the config file) but is something that you should be aware of. | |
In order to be able to change the culture application developers will need to use one of the culture resolvers that support culture changes, such as SessionCultureResolver or CookieCultureResolver. One could also write a custom ICultureResolver that will persist culture information in a database, as part of a user's profile.
Once that requirement is satisfied, all that one need do is to set the UserCulture property to a new CultureInfo object before the page is rendered. In the following .aspx example, there are two link buttons that can be used to change the user's culture. In the code-behind, this is all one need do to set the new culture. A code snippet for the code-behind file (UserRegistration.aspx.cs) is shown below.
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);
}One of the problems evident in many ASP.NET applications is that there is no built-in way to externalize the flow of an application. The most common way of defining application flow is by hardcoding calls to the Response.Redirect and Server.Transfer methods within event handlers.
This approach is problematic because any changes to the flow of an application neccessitates code changes (with the attendant recompilation, testing, redeployment, etc). A much better way, and one that has been proven to work successfully in many MVC ( Model-View-Controller) web frameworks is to provide the means to externalize the mapping of action results to target pages.
Spring.Web adds this functionality to ASP.NET by allowing one 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 Result class; because of this one can configure results just like any other object:
<objects>
<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 that you must supply a value for each and every result is the TargetPage property. The value of the Mode property can be either Transfer or Redirect, and defaults to Transfer if none is specified.
If one's target page requires parameters, one can define them using the Parameters dictionary property. One simply specifies either literal values or object navigation expressions for such parameter values; if one specifies an expression, this expression will be evaluated in the context of the page in which the result is being referenced... in the specific case of the above example, this means that any page that uses the homePageResult needs to expose a UserInfo property on the page class itself.
Parameters will be handled differently depending on the result mode. For redirect results, every parameter will be converted to a string, then URL encoded, and finally appended to a redirect query string. On the other hand, parameters for transfer results will be added to the HttpContext.Items collection before the request is transferred to the target page. This means that transfers are more flexible because any object can be passed as a parameter between pages. They are also more efficient because they don't require a round-trip to the client and back to the server, so transfer mode is recommended as the preferred result mode (it is also the current default).
The above example shows independent result object definitions, which are useful for global results such as a home- and login- page. Result definitions that are only going to be used by one page should be simply embedded within the definition of a page, either as inner object definitions or using a special shortcut notation for defining a result definition:
<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]
There are two possible values for the mode value referred to in the above notation snippet; they are...
redirect
transfer
One thing to notice is that a comma is used instead of an ampersand to separate parameters; this is done so as to avoid the need for laborious ampersand escaping within an XML object definition. The use of the ampersand character is still supported if required, but one will then have to specify it using the well known & entity reference.
Once one has defined one's results, it is very simple to use them within the event handlers of one's pages (UserRegistration.apsx.cs)...
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);
}
One could of course further refactor the above example and use defined constants. This would be a good thing to do in the case of a logical result name such as "home" that is likely to be referenced by many pages.
ASP.NET has decent support for client-side scripting through the use of the Page.RegisterClientScriptBlock and Page.RegisterStartupScript methods.
However, neither of these two methods allows you to output a registered script markup within a <head> section of a page, which is (in many cases) exactly what you would like to do.
Spring.Web adds several methods to enhance client-side scripting to the base Spring.Web.UI.Page class: RegisterHeadScriptBlock and RegisterHeadScriptFile, each with a few overrides. You can call these methods from your custom pages and controls in order to register script blocks and script files that must be included in the <head> section of the final HTML page.
The only additional thing that is required to make this work is that you use the <spring:Head> server-side control to define your <head> section instead of using the standard HTML <head> element. This is shown below.
<%@ 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 example above shows you how you would typically set-up a <head> section within a master page template in order to be able to change the title value and to add additional elements to the <head> section from the child pages using <spring:ContentPlaceholder> controls. However, only the <spring:Head> declaration is required in order for Spring.NET Register* scripts to work properly.
TODO : insert example
In a similar fashion, you can add references to CSS files, or even specific styles, directly to the <head> HTML section using Page.RegisterStyle and Page.RegisterStyleFile methods. The latter one simply allows you to include a reference to an external CSS file, while the former one allows you to define embedded style definitions by specifying the style name and definition as the parameters. The final list of style definitions registered this way will be rendered within the single embedded style section of the final HTML document.
TODO : insert example
In order to make the manual inclusion of client-side scripts, CSS files and images easier, the Spring.Web Page class exposes several properties that help you reference such artifacts using absolute paths. This affords web application developers a great deal of convenince functionality straight out of the box if they stick to common conventions such as a web application (directory) structure..
These properties are ScriptsRoot, CssRoot and ImagesRoot. They have default values of Scripts, CSS and Images, which will work just fine if you create and use these directories in your web application root. However, if you prefer to place them somewhere else, you can always override default values by injecting new values into your page definitions (you will typically inject these values only in the base page definition, as they are normally shared by all the pages in the application). An example of such configuration is shown below:
<object id="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 following properties....
</description>
<property name="CssRoot" value="Web/CSS"/>
<property name="ImagesRoot" value="Web/Images"/>
</object>
While the out-of-the-box support for web services in .NET is excellent, there are a few areas that the Spring.NET thought could use some improvement.
One thing that the Spring.NET team didn't like much is that we had to have all these .asmx files lying around when all said files did was specify which class to instantiate to handle web service requests.
Second, the Spring.NET team also wanted to be able to use the Spring.NET IoC container to inject dependencies into our web service instances. Typically, a web service will rely on other objects, service objects for example, so being able to configure which service object implementation to use is very useful.
Last, but not least, the Spring.NET team did not like the fact that creating a web service is an implementation task. Most (although not all) services are best implemented as normal classes that use coarse-grained service interfaces, and the decision as to whether a particular service should be exposed as a remote object, web service, or even an enterprise (COM+) component, should only be a matter of configuration, and not implementation.
On the client side, the main objection the Spring.NET team has is that client code becomes tied to a proxy class, and not to a service interface. Unless you make the proxy class implement the service interface manually, as described by Juval Lowy in his book "Programming .NET Components", application code will be less flexible and it becomes very difficult to plug in different service implementation in the case when one decides to use a new and improved web service implementation or a local service instead of a web service.
The goal for Spring.NET's web sevices support is to enable the easy generation of the client-side proxies that implement a specific service interface.
Unlike web pages, which use .aspx files to store presentation code, and code-behind classes for the logic, web services are completely implemented within the code-behind class. This means that .asmx files serve no useful purpose, and as such they should neither be necessary nor indeed required at all.
Spring.NET allows application developers to expose existing web services easily by registering a custom implementation of the WebServiceHandlerFactory class and by creating a standard Spring.NET object definition for the service.
By way of an example, consider the following web service...
namespace MyComany.MyApp.Services
{
[WebService(Namespace="http://myCompany/services")]
public class HelloWorldService
{
[WebMethod]
public string HelloWorld()
{
return "Hello World!";
}
}
}This is just a standard class that has methods decorated with the WebMethod attribute and (at the class-level) the WebService attribute. Application developers can create this web service within Visual Studio.NET just like any other class.
All that one need to do in order to publish this web service is:
1. Register the Spring.Web.Services.WebServiceFactoryHandler as the HTTP handler for *.asmx requests within one's web.config file.
<system.web>
<httpHandlers>
<add verb="*" path="*.asmx" type="Spring.Web.Services.WebServiceHandlerFactory, Spring.Web"/>
</httpHandlers>
</system.web>Of course, one can register any other extension as well, but typically there is no need as Spring.NET's handler factory will behave exactly the same as a standard handler factory if said handler factory cannot find the object definition for the specified service name. In that case the handler factory will simply look for an .asmx file.
2. Create an object definition for one's web service.
<object name="HelloWorld.asmx" type="MyComany.MyApp.Services.HelloWorldService, MyAssembly" abstract="true"/>
Note that one is not absolutely required to make the web service object definition abstract (via the abstract="true" attribute), but this is a recommended best practice in order to avoid creating an unnecessary instance of the service. Because the .NET infrastructure creates instances of the target service object internally for each request, all Spring.NET needs to provide is the System.Type of the service class, which can be retrieved from the object definition even if it is amrked as abstract.
That's pretty much it – one can access this web service using the value specified for the name attribute of the object definition as the service name:
http://localhost/MyWebApp/HelloWorld.asmx
For arguments sake, let's say that we want to change the implementation of the HelloWorld method to make the returned message configurable.
One way to do it would be to use some kind of message locator to retrieve an appropriate message, but that locator needs to implemented. Also, it would certainly be an odd architecture that used dependency injection throughout the application to configure objects, but that resorted to the service locator approach when dealing with web services.
Ideally, one should be able to define a property for the message within one's web service class and have Spring.NET inject the message value into it:
namespace MyApp.Services
{
public interface IHelloWorld
{
string HelloWorld();
}
[WebService(Namespace="http://myCompany/services")]
public class HelloWorldService : IHelloWorld
{
private string message;
public string Message
{
set { message = value; }
}
[WebMethod]
public string HelloWorld()
{
return this.message;
}
}
}The problem with standard SpringNET DI usage in this case is that Spring.NEt does not control the instantiation of the web service. This happens deep in the internals of the .NET framework, thus making it quite difficult to plug in the code that will perform the configuration.
The solution is to create a dynamic server-side proxy that will wrap the web service and configure it. That way, the .NET framework gets a reference to a proxy type from Spring.NET and instantiates it. The proxy then asks a Spring.NET application context for the actual web service instance that will process requests.
This proxying requires that one export the web service explicitly using the Spring.Web.Services.WebServiceExporter class; in the specific case of this example, one must also not forget to configure the Message property for said service:
<object id="HelloWorld" type="MyApp.Services.HelloWorldService, MyApp">
<property name="Message" value="Hello, World!"/>
</object>
<object id="HelloWorldExporter" type="Spring.Web.Services.WebServiceExporter, Spring.Web">
<property name="TargetName" value="HelloWorld"/>
</object>The WebServiceExporter copies the existing web service and method attribute values to the proxy implementation (if indeed any are defined). Please note however that existing values can be overridden by setting properties on the WebServiceExporter.
![]() | |
In order to support some advanced usage scenarios, such as the ability to expose an AOP proxy as a web service (allowing the addition of AOP advices to web service methods), Spring.NET requires those objects that need to be exported as web services to implement a (service) interface. Only methods that belong to an interface will be exported by the WebServiceExporter. | |
Now that we are generating a server-side proxy for the service, there is really no need for it to have all the attributes that web services need to have, such as WebMethod. Because .NET infrastructure code never really sees the "real" service, those attributes are redundant – the proxy needs to have them on its methods, because that's what .NET deals with, but they are not necessary on the target service's methods.
This means that we can safely remove the WebService and WebMethod attribute declarations from the service implementation, and what we are left with is a plain old .NET object (a PONO). The example above would still work, because the proxy generator will automatically add WebMethod attributes to all methods of the exported interfaces.
However, that is still not the ideal solution. You would looe information that the optional WebService and WebMethod attributes provide, such as service namespace, description, transaction mode, etc. One way to keep those values is to leave them within the service class and the proxy generator will simply copy them to the proxy class instead of creating empty ones, but that really does defeat the purpose.
A second, better way, is to set all the necessary values within the definoitn of the service exporter, like so...
<object id="HelloWorldExporter" type="Spring.Web.Services.WebServiceExporter, Spring.Web">
<property name="TargetName" value="HelloWorld"/>
<property name="Namespace" value="http://myCompany/services"/>
<property name="Description" value="My exported HelloWorld web service"/>
<property name="Methods">
<dictionary>
<entry key="HelloWorld">
<object type="System.Web.Services.WebMethodAttribute, System.Web.Services">
<property name="Description" value="My Spring-configured HelloWorld method."/>
<property name="MessageName" value="ZdravoSvete"/>
</object>
</entry>
</dictionary>
</property>
</object>
// or, once configuration improvements are implemented...
<web:service targetName="HelloWorld" namespace="http://myCompany/services">
<description>My exported HelloWorld web service.</description>
<methods>
<method name="HelloWorld" messageName="ZdravoSvete">
<description>My Spring-configured HelloWorld method.</description>
</method>
</methods>
</web:service>Based on the configuration above, Spring.NET will generate a web service proxy for all the interfaces implemented by a target and add attributes as necessary. This accomplishes the same goal while at the same time moving web service metadata from implementation class to configuration, which allows one to export pretty much any class as a web service.
One can also export only certain interfaces that a service class implements by setting the Interfaces property of the WebServiceExporter...
![]() | |
Distributed Objects Warning Just because you can export any object as a web service, doesn't mean that you should. Distributed computing principles still apply and you need to make sure that your services are not chatty and that arguments and return values are serializable. You still need to exercise common sense when deciding whether to use web services (or remoting in general) at all, or if local service objects are all you need. | |
It is often useful to be able to export an AOP proxy as a web service. For example, consider the case where you have a service that is wrapped with an AOP proxy that you want to access both locally and remotely (as a web service). The local client would simply obtain a reference to an AOP proxy directly, but any remote client needs to obtain a reference to an exported web service proxy, that delegates calls to an AOP proxy, that in turn delegates them to a target object while applying any configured AOP advice.
Effecting this setup is actually fairly straightforward; because an AOP proxy is an object just like any other object, all you need to do is set the WebServiceExporter's TargetName property to the id (or indeed the name or alias) of the AOP proxy. The following code snippets show how to do this...
<object id="DebugAdvice" type="MyApp.AOP.DebugAdvice, MyApp"/>
<object id="TimerAdvice" type="MyApp.AOP.TimerAdvice, MyApp"/>
<object id="MyService" type="MyApp.Services.MyService, MyApp"/>
<object id="MyServiceProxy" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
<property name="TargetName" value="MyService"/>
<property name="IsSingleton" value="true"/>
<property name="InterceptorNames">
<list>
<value>DebugAdvice</value>
<value>TimerAdvice</value>
</list>
</property>
</object>
<object id="MyServiceExporter" type="Spring.Web.Services.WebServiceExporter, Spring.Web">
<property name="TargetName" value="MyServiceProxy"/>
<property name="Name" value="MyService"/>
<property name="Namespace" value="http://myApp/webservices"/>
<property name="Description" value="My web service"/>
<property name="Interfaces">
<list>
<value>MyApp.Services.IMyService, MyApp</value>
</list>
</property>
</object> That's it – every call to the methods of the exported web service will be intercepted by the target AOP proxy, which in turn will apply the configured debugging and timing advice to it.
The problem with the web-service proxy classes that are generated by VS.NET or the WSDL command line utility is that they don't implement a service interface. This tightly couples client code with web services and makes it impossible to change the implementation at a later date without modifying and recompiling the client.
Spring.NET provides a simple IFactoryObject implementation that will generate a "proxy for proxy" (however obtuse that may sound). Basically, the Spring.Web.Services.WebServiceProxyFactory class will create a proxy for the VS.NET- / WSDL-generated proxy that implements a specified service interface (thus solving the problem with the web-service proxy classes mentioned in the preceding paragraph).
At this point, an example may well be more illustrative in conveying what is happening; consider the following interface definition that we wish to expose as a web service...
namespace MyCompany.Services
{
public interface IHelloWorld
{
string HelloWorld();
}
} In order to be able to reference a web service endpoint through this interface, you need to add a definition similar to the example shown below to your client's application context:
<object id="HelloWorld" type="Spring.Web.Services.WebServiceProxyFactory, Spring.Services">
<property name="ProxyClass" value="MyCompany.WebServices.HelloWorld, MyClientApp"/>
<property name="ServiceInterface" value="MyCompany.Services.IHelloWorld, MyServices"/>
</object>What is important to notice is that the underlying implementation class for the web service does not have to implement the same IHelloWorld service interface... so long as matching methods with compliant signatures exist (a kind of duck typing), Spring.NET will be able to create a proxy and delegate method calls appropriately. If a matching method cannot be found, the Spring.NET infrastructure code will throw an exception.
That said, if you control both the client and the server it is probably a good idea to make sure that the web service class on the server implements the service interface, especially if you plan on exporting it using Spring.NET's WebServiceExporter, which requires an interface in order to work.