Introduction
In our second article of the series "Exploring Windows Communication Foundation", we explore the customization of the service and working with faults and errors. Specifically, we look at the following:
- Hosting the service in IIS/ASP.NET Runtime
- Customizing the details of a service contract, like the name of contract, namespace-URI to which it belongs etc
- Customizing the details of an operation contract, like the name of contract, it's parameters, order etc
Hosting WCF Service in IIS/ASP.NET
To host a service in IIS/ASP.NET, we need to do the following:
- Install IIS, if not already installed.
- Register ASP.NET 2.0 with IIS, if not already registered.
- Register WCF with IIS and ASP.NET 2.0.
If you have installed IIS and .NET Framework 2.0 Redistributable in this order, you do not need to register ASP.NET Runtime. You can check if ASP.NET Runtime is registered with a website or not by checking the properties of the website as shown below:
If the ASP.NET Runtime is not registered with IIS, execute the following on the commandline for the registration:
%windir%\Microsoft.Net\Framework\v2.0.50727\aspnet_regiis.exe -i
To register the WCF with IIS and ASP.NET 2.0, execute the following on the commandline:
ServiceModelReg.exe -i -x
The application ServiceModelReg.exe
can be found in the folder "%windir%\Microsoft.Net\Framework\v3.0\Windows Communication Foundation"
. The -i
option is for registering WCF with IIS and ASP.Net Runtime and -x
option is to execute the custom action scripts also. The two important things that the registration does are registering .svc
extension with IIS asking it to be handled by ASP.Net 2.0 Runtime and the registering .svc
extension with the ASP.Net Runtime to be handled by its IHttpHandler
.
The next step is to create an ASP.Net Website. Let's call it ShoppingCart
. To the website created, add a new WCF service and name it ShoppingCartService.svc
.
Incase you do not find the template for adding a new WCF service, don't be disheartened. You can simply add a new text file and name it ShoppingCartService.svc
.
Open the ShoppingCartService.svc
file if not already opened. Modify it to have the following contents:
<%@ ServiceHost Debug="true" Service="ShoppingCartLibrary.ShoppingCartImpl" %>
Create the folder Bin
inside your project. Add the assembly ShoppingCartLibrary
that we created in our previous article. If you don't have it, you can get it from the previous article. For all those working with Visual Studio, you may wish to delete the class ShoppingCartService
created automatically for the code-behind, placed in App_Code
folder. We don't need it.
Modify the configuration file web.config
to include the following content:
="1.0"="utf-8"
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="HttpGetBehaviour">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="ShoppingCartLibrary.ShoppingCartImpl"
behaviorConfiguration="HttpGetBehaviour">
<endpoint binding="mexHttpBinding"
contract="ShoppingCartLibrary.IShoppingCartService"
address=""/>
</service>
</services>
</system.serviceModel>
</configuration>
Hurray! The service has been deployed. Browse to the location http://localhost/ShoppingCart/ShoppingCartService.svc
. If you see the output as shown below, the scriptmaps are not registered appropriately.
In that case (or uncompiled output), execute the following on the commandline:
ServiceModelReg.exe -s:W3SVC/1/ROOT/ShoppingCart
Refresh the page in browser. Now, you should be able to see an output as shown below:
Updating the client
The next step is to update the client (proxy class and configuration) to connect to our new service.
Ah! We don't need to regenerate everything. Open the client application configuration file and modify the endpoint-address (in the section configuration/system.serviceModel/client/endpoint
, the attribute address
) with a value of http://localhost/ShoppingCart/ShoppingCartService.svc
The client should give identical results as what we got while working with self-hosted service (created in previous article).
Service Customization
Let us now look at important aspect of decoupling the WCF contract names and .Net names (data-types and/or parameters).
In general, we do not want to have dependency on the field/parameter/data-type names that we choose in .Net and what we publish to the world. Not only it may put under risk the application but also may be mandatory. Say, for example, if we need to have a parameter by the name in
or for
, we cannot have it because these are keywords in C# (technically speaking, yes we can have it in C# but what about C++.Net or VB.Net?). As such, we would like to remove this interdependency.
Service Contract Name
The first requirement that we look at it isolating the service name and the namespace-URI to which it belongs, and the name of the interface.
The ServiceContractAttribute
has two properties - Name
and Namespace
that can be used to control this. The default of values of the two are the name of the interface and http://www.tempuri.org
. Let us change them to Shopping-Cart-Service
and http://www.edujinionline.com/ns/wcf/tutorial/scart
.
Update the interface IShoppingCartService
to as given below (code in bold are the changes done):
using System;
using System.ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceContract(Name="Shopping-Cart-Service",
Namespace="http://www.edujinionline.com/ns/wcf/tutorial/scart")]
public interface IShoppingCartService
{
[OperationContract]
bool CheckUserExists(string username);
[OperationContract]
DateTime? GetLastTransactionTime(string username);
}
}
Starting the hosting application that we created in the previous article. Browse to the location http://localhost:8080/ShoppingCartService?wsdl
. You will notice that the WSDL generated is conspicuously short and has reference to another WSDL. Browse to http://localhost:8080/ShoppingCartService?wsdl=wsdl0
. You will notice that all definitions of types, messages and operations (in portTypes section) now belong to the new namespace-URI that we gave.
Also notice that the new name of the portType is now Shopping-Cart-Service
instead of IShoppingCart
. Note that portType section is the one that defines the service-contract (operations and related messages). Also the bindings are now also from the type Shopping-Cart-Service
.
However, if we need to change the name of the service (in service section of WSDL) and the namespace-URI of the same, we need to change the service-behaviour rather than the contract details. The default value of the same are the name of the implementation class and http://tempuri.org. Let us update them to Shopping-Cart-Impl
and http://www.edujinionline.com/ns/wcf/tutorial/scart/impl
respectively.
We shall make use of the System.ServiceModel.ServiceBehaviorAttribute
to specify the name and namespace-uri of the service.
Update the class ShoppingCartImpl
to as given below (code in bold are the changes done):
using System;
using System.ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceBehavior(Name="Shopping-Cart-Impl",
Namespace="http://www.edujinionline.com/ns/wcf/tutorials/scart/impl")]
public class ShoppingCartImpl : IShoppingCartService
{
}
}
Operation Names
Similarly, to change the value of the operation, one can make use of Name
property of System.ServiceModel.OperationContractAttribute
. Let us update the name of the operations from their default names to UserExists<code> and <code>LastTransactionTime
respectively. Update the interface IShoppingCartService
to as shown below:
using System;
using System.ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceContract(Name="Shopping-Cart-Service",
Namespace="http://www.edujinionline.com/ns/wcf/tutorial/scart")]
public interface IShoppingCartService
{
[OperationContract(Name="UserExists")]
bool CheckUserExists(string username);
[OperationContract(Name="LastTransactionTime")]
DateTime? GetLastTransactionTime(string username);
}
}
Action URIs for input and output messages
Launch the hosting application and browse to http://localhost:8080/ShoppingCartService?wsdl=wsdl0
. Look at the updated operation names. However, the Action
of input and output messages doesn't look too good. Let us update them to http://www.myurl.com/scart/actions/UserExists
and http://www.
myurl.com/scart
/actions/LastTransactionTime
(input), and http://www.
myurl.com/scart
/actions/UserExistsOut
and http://www.
myurl.com/scart
/actions/LastTransactionTimeOut
(output) respectively.
The properties Action
and ReplyAction
may be used to customize the action-URI for the input and output operations. Again, make changes to the interface as shown below (code in bold are the changes done):
using System;
using System.ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceContract(Name="Shopping-Cart-Service",
Namespace="http://www.myurl.com/scart")]
public interface IShoppingCartService
{
[OperationContract(Name="UserExists",
Action="http://www.myurl.com/scart/actions/UserExists",
ReplyAction="http://www.myurl.com/scart/actions/UserExistsOut")]
bool CheckUserExists(string username);
[OperationContract(Name="LastTransactionTime",
Action="http://www.myurl.com/scart/actions/LastTranactionTime",
ReplyAction="www.myurl.com/scart/actions/LastTranactionTimeOut")]
DateTime? GetLastTransactionTime(string username);
}
}
Similarly, the order in which the operations should appear in the WSDL can be defined by setting the property Order
of the System.ServiceModel.OperationContractAttribute
appropriately. By default, the WCF engine would show the operations in the order it retrieves from the IL (using reflection).
Parameter Names
Let us say that our shopping cart works with email address as far the interface to the world is concerned. Internally, we would like to refer to it as username, like we have been doing so far.
System.ServiceModel.MessageParameterAttribute
can be used to customize the name of the input parameters as well as that of the output paramter in the response. Update the code to look as follows (code in bold shows the changes made):
using System;
using System.ServiceModel;
namespace ShoppingCartLibrary
{
[ServiceContract(Name="Shopping-Cart-Service",
Namespace="http://www.myurl.com/scart")]
public interface IShoppingCartService
{
[OperationContract(Name="UserExists",
Action="http://www.myurl.com/scart/actions/UserExists",
ReplyAction="http://www.myurl.com/scart/actions/UserExistsOut")]
[return: MessageParameter(Name="ExistsInfo")]
bool CheckUserExists(
[MessageParameter(Name="email")] string username);
[OperationContract(Name="LastTransactionTime",
Action="http://www.myurl.com/scart/actions/LastTranactionTime",
ReplyAction="http://www.myurl.com/scart/actions/LastTranactionTimeOut")]
[return: MessageParameter(Name="TimeInfo")]
DateTime? GetLastTransactionTime(
[MessageParameter(Name="email")] string username);
}
}
With all these changes, we need to regenerate the proxy-client (using svcutil
). You will notice that the default class file generated this time is Shopping-Cart-Service.cs
, which happens to be the name of our service!
Now, before proceeding ahead with any transaction, let us enable tracing to have a look at the message being transported. Update the application configuration file for the host to have the following entries:
<configuration>
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging"
switchValue="Verbose">
<listeners>
<add name="xml"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="..\..\..\wcf_scart.svclog" />
</listeners>
</source>
</sources>
<trace autoflush="true" />
</system.diagnostics>
<system.serviceModel>
<diagnostics>
<messageLogging logEntireMessage="true" maxMessagesToLog="300"
logMessagesAtServiceLevel="false"
logMalformedMessages="true"
logMessagesAtTransportLevel="true" />
</diagnostics>
< ... >
</system.ServiceModel>
</configuration>
Update the client application to as shown below (code in bold are the updates done):
using System;
namespace ShoppingCartConsumer
{
public class MainClass
{
public static void Main(string[] args)
{
ShoppingCartServiceClient client = new ShoppingCartServiceClient();
string[] users = new string[] { "gvaish", "edujini", "other" };
foreach(string user in users)
{
Console.WriteLine("User: {0}", user);
Console.WriteLine("\tExists: {0}", client.UserExists(user));
DateTime? time = client.LastTransactionTime(user);
if(time.HasValue)
{
Console.WriteLine("\tLast Transaction: {0}", time.Value);
} else
{
Console.WriteLine("\tNever transacted");
}
}
Console.WriteLine();
}
}
}
Notice that there no longer exists the methods CheckUserExists
and/or GetLastTransactionTime
in the proxy-client generated.
Launch the Service Trace Viewer
, the application SvcTraceViewer.exe
found in the bin
where the Windows SDK for Windows Vista and .Net Framework 3.0 Runtime Components
was installed. Open the file wcf_scart.svclog
generated. Notice the important changes in the request sent and response received.
Summary
We looked at how to host the service in IIS/ASP.Net. We also looked at how we can isolate the .Net parameter and operation paramter names. That's it for now. It close to midnight and I would discuss about the faults and error-handling in WCF in the next article.
Contact
- You may directly put a comment below and I shall response.
- You can contact me directly at gaurav[dot]vaish[at]gmail.