Introduction
Imagine a situation where you are writing a RESTful service using WCF and decided to host that in IIS.
So here comes some questions and let us answer them.
Q:How are you going to host WCF service in IIS?
A: By placing .svc file in the virtual directory.
Q: How will you make a request to that service?
A: By using the URI which looks something like this : http://some.com/SomeService.svc .
Q: Is the above URI optimal for REST service?
A: No.
Q: Why?
A: URI(Uniform Resource Identifier) of RESTful services identifies a specific resource and having extensions like .svc is not good.
Q: Why?
A: Even though having .svc in URI is non-refined but technically is not unRESTful. But are we not leaking implementation detail here by showing .svc?
Background
.Net 4.0 adds lot of features to the WCF and one of them is System.Web.Routing Integration. This feature allows WCF RESTful service to be hosted in IIS without svc.
A ROUTE is nothing but a URL pattern which is mapped to a handler and in this case handler will be a class which serves the request.
Sample Code (Server)
Let us build a RESTful service which performs arithmetic and geometric sequence summation.
Step 1: Create a new WCF project and delete the .svc file.
Step 2: Add a file IService.cs.
Step 3: Add two files GPService.cs and APService.cs.
Step 4: Add Global.asax file if it is not there.
Routing Integration Steps:
1: In Global.asax.cs in Application_Start event add the ServiceRoute to the RouteTable. These routes added represents the virtual URIs which service will respond when called.
2: In the Web.config file add the System.Web.Routing.UrlRoutingModule module and set the runAllManagedModulesForAllRequests attribute to true. Also add the UrlRoutingHandler handler to the <system.webServer> element as shown below.
3: Enable ASP.NET compatibility requirements for the class that implements service (in our case GPService and APService).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace RoutingIntegration
{
[ServiceContract]
public interface ISeriesService
{
[OperationContract]
SeqInput CalculateSeriesSummation(SeqInput input);
}
[DataContract]
public class SeqInput
{
[DataMember]
public int numberOfItems;
[DataMember]
public int firstTerm;
[DataMember]
public int commonDiff;
[DataMember]
public double Answer;
public SeqInput(int numberOfItems1, int firstTerm1, int commonDiff1)
{
this.numberOfItems = numberOfItems1;
this.firstTerm = firstTerm1;
this.commonDiff = commonDiff1;
}
public override string ToString()
{
return String.Format("{0} {1} {2}", numberOfItems, firstTerm, commonDiff);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.ServiceModel.Activation;
using System.ComponentModel;
namespace RoutingIntegration
{
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class GPService : ISeriesService
{
[WebInvoke(Method = "POST", UriTemplate = ""), Description("Geometric Sequences.")]
public SeqInput CalculateSeriesSummation(SeqInput input)
{
double answer = 0;
answer = (double.Parse((Math.Pow(input.commonDiff,
input.numberOfItems) - 1).ToString() )/ (input.commonDiff - 1));
answer = answer * input.firstTerm;
input.Answer = answer;
return input;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.ServiceModel.Activation;
using System.ComponentModel;
namespace RoutingIntegration
{
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class APService : ISeriesService
{
[WebInvoke(Method = "POST", UriTemplate = ""), Description("Arithmetic Sequences.")]
public SeqInput CalculateSeriesSummation(SeqInput input)
{
double answer = 0;
answer = ((2 * input.firstTerm) + (input.numberOfItems - 1) * input.commonDiff);
answer = answer * (double.Parse(input.numberOfItems.ToString()) / 2);
input.Answer = answer;
return input;
}
}
}
using System;
using System.Collections.Generic;
using System.ServiceModel.Activation;
using System.ServiceModel.Syndication;
using System.Web.Routing;
namespace RoutingIntegration
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Add(new ServiceRoute("GPService", new WebServiceHostFactory(), typeof(GPService)));
RouteTable.Routes.Add(new ServiceRoute("APService", new WebServiceHostFactory(), typeof(APService)));
}
protected void Session_Start(object sender, EventArgs e)
{
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
}
protected void Application_Error(object sender, EventArgs e)
{
}
protected void Session_End(object sender, EventArgs e)
{
}
protected void Application_End(object sender, EventArgs e)
{
}
}
}
="1.0"
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule"
type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
<handlers>
<add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd"/>
</handlers>
</system.webServer>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true"
aspNetCompatibilityEnabled ="true" />
</system.serviceModel>
</configuration>
Sample Code (Client)
Let us build a small client to consume our above RESTful service.
For demo let me take only APService only.
Its left to the creativity of the reader to create a console app or
WinForms or web app and design a consumption. I am going to explain main part of the service consumption only.
Step 1: We need our SeqInput
class in client also as CalculateSeriesSummation
method of the service accepts parameter of type SeqInput
and returns the same after updating the answer to it.
namespace RoutingIntegration
{
[DataContract]
public class SeqInput
{
[DataMember]
public int numberOfItems;
[DataMember]
public int firstTerm;
[DataMember]
public int commonDiff;
[DataMember]
public double Answer;
public SeqInput(int numberOfItems1, int firstTerm1, int commonDiff1)
{
this.numberOfItems = numberOfItems1;
this.firstTerm = firstTerm1;
this.commonDiff = commonDiff1;
}
public override string ToString()
{
return String.Format("{0} {1} {2}", numberOfItems, firstTerm, commonDiff);
}
}
}
Step 2:
static readonly DataContractSerializer customerSerializer =
new DataContractSerializer(typeof(RoutingIntegration.SeqInput));
try
{
Uri address = null;
HttpWebRequest request = null;
RoutingIntegration.SeqInput finalAnswer = null;
address = new Uri("http://localhost:1254/APService");
request = (HttpWebRequest)WebRequest.Create(address);
request.Method = "POST";
request.ContentType = "application/xml";
RoutingIntegration.SeqInput si = new RoutingIntegration.SeqInput(1,1,1);
using (Stream requestStream = request.GetRequestStream())
{
customerSerializer.WriteObject(requestStream, si);
}
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
finalAnswer = (RoutingIntegration.SeqInput)customerSerializer.ReadObject(responseStream);
}
response.Close();
lblAnswer.Text = "Answer is: " + finalAnswer.Answer.ToString();
}
catch (Exception ex)
{
}
Points of Interest
New features provided with .NET 4.0 for WCF are really interesting and makes any body curious.