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. Spring adds the ability to perform dependency injection on standard asmx web services. Spring's .NET Web Services support also allows you to export a 'plain CLR object' as a .NET web service By "plain CLR object" we mean classes that do not contain infrastructure specific attributes, such as WebMethod. On the server side, Spring's .NET web service exporters will automatically create a proxy that adds web service attributes. On the client side you can use Spring IoC container to configure a client side proxy that you generated with standard command line tools. Additionally, Spring provides the functionality to create the web service proxy dynamically at runtime (much like running the command line tools but at runtime and without some of the tools quirks) and use dependency injection to configure the resulting proxy class. On both the server and client side, you can apply AOP advice to add behavior such as logging, exception handling, etc. that is not easily encapsulated within an inheritance hierarchy across the application.
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.
An example using the web service exporter can be found in quickstart example named 'calculator'. More information can be found here 'Web Services example'.
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 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.
If you are using IIS7 the following configuration is needed
<system.webServer> <validation validateIntegratedModeConfiguration="false"/> <handlers> <add name="SpringWebServiceSupport" verb="*" path="*.asmx" type="Spring.Web.Services.WebServiceHandlerFactory, Spring.Web"/> </handlers> </system.webServer>
2. Create an object definition for one's web service.
<object name="HelloWorld" 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 marked
as abstract
.
That's pretty much it as we 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 Spring.NET 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
.
Interface Requirements | |
---|---|
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
|
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 as 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 CLR object (a POCO). 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 lose
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.
To add specific attributes to the exported web service, you can set all the necessary values within the definition 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="MemberAttributes"> <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.
The WebServiceExporter also has a
TypeAttributes
IList property for applying attributes
at the type level.
Note | |
---|---|
The attribute to confirms to the WSI basic profile 1.1 is not
added by default. This will be added in a future release. In the
meantime use the TypeAttributes IList property to add
|
One can also export only certain interfaces that a service class
implements by setting the Interfaces
property of the
WebServiceExporter
.
Distributed Objects Warning | |
---|---|
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"/> </object>
That's it as 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.
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 services support is to enable the easy generation of client-side proxies that implement a specific service interface.
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="ProxyType" 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.
The WebServiceProxyFactory
can also dynamically
generate a web-service proxy. The XML object definition for this factory
object is shown below
<object id="calculatorService" type="Spring.Web.Services.WebServiceProxyFactory, Spring.Services"> <property name="ServiceUri" value="http://myServer/Calculator/calculatorService.asmx"/> <!--<property name="ServiceUri" value="file://~/calculatorService.wsdl"/>--> <property name="ServiceInterface" value="Spring.Calculator.Interfaces.IAdvancedCalculator, Spring.Calculator.Contract"/> <!-- Dependency injection on Factory's product : the proxy instance of type SoapHttpClientProtocol --> <property name="ProductTemplate"> <object> <property name="Timeout" value="10000" /> <!-- 10s --> </object> </property> </object>
One use-case where this proxy is very useful is when dealing with typed data sets through a web service. Leaving the pros and cons of this approach aside, the current behavior of the proxy generator in .NET is to create wrapper types for the typed dataset. This not only pollutes the solution with extraneous classes but also results in multiple wrapper types being created, one for each web service that uses the typed dataset. This can quickly get confusing. The proxy created by Spring allows you to reference you typed datasets directly, avoiding the above mentioned issues.
The WebServiceProxyFactory
also implements the
interface,
Spring.Objects.Factory.IConfigurableFactoryObject
,
allowing to specify configuration for the product that the
WebServiceProxyFactory
creates. This is done by
specifying the ProductTemplate property. This is particularly useful for
securing the web service. An example is shown below.
<object id="PublicarAltasWebService" type="Spring.Web.Services.WebServiceProxyFactory, Spring.Services"> <property name="ProxyType" value="My.WebService" /> <property name="ServiceInterface" value="My.IWebServiceInterface" /> <property name="ProductTemplate"> <object> <!-- Configure the web service URL --> <property name="Url" value="https://localhost/MyApp/webservice.jws" /> <!-- Configure the Username and password for the web service --> <property name="Credentials"> <object type="System.Net.NetworkCredential, System"> <property name="UserName" value="user"/> <property name="Password" value="password"/> </object> </property> <!-- Configure client certificate for the web service --> <property name="ClientCertificates"> <list> <object id="MyCertificate" type="System.Security.Cryptography.X509Certificates.X509Certificate2, System"> <constructor-arg name="fileName" value="Certificate.p12" /> <constructor-arg name="password" value="notgoingtotellyou" /> </object> </list> </property> </object> </property> </object>
For an example of how using SOAP headers for authentication using the WebServiceExporter and WebServiceProxyFactory, refer to this solution on our wiki.