Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Routing Integration - WCF feature

3.78/5 (7 votes)
16 Apr 2012CPOL2 min read 29.1K  
Routing Integration with WCF is a feature newly introduced with .NET 4.0 and serves very nice.

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

C#
// IService.cs file is as below. Nothing special here. Contains one operation contract and one data contract.
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
    {
        /// <summary>
        /// Calculates the sequence summation.
        /// </summary>
        /// <param name="input">Input is of type SeqInput</param>
        /// <returns>SeqInput updated with the answer</returns>
        [OperationContract]
        SeqInput CalculateSeriesSummation(SeqInput input);
    }

    /// <summary>
    /// Data contract used as a entity which contains variables required for sequence summation
    /// Once calculated answer is assigned back and this class is sent back as response.
    /// </summary>
    [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);
        }
    }

} 
C++
//GPService.cs as below. Simply impliments the ISeriesService.
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
{
    //Applied to a service to indicate whether that service can be run in ASP.NET compatibility code.
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class GPService : ISeriesService
    {
        //Indicates a service operation is logically an invoke
        // operation and that it can be called by the REST programming model.
        [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;
        }
    }
} 
C++
 //APService.cs as below. Simply impliments the ISeriesService.
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
{
    //Applied to a service to indicate whether that service can be run in ASP.NET compatibility code.
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class APService : ISeriesService
    {
        //Indicates a service operation is logically an invoke
        // operation and that it can be called by the REST programming model.
        [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;
        }
    }
} 
C++
//Global.asax.cs,  Most important part. 
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
    {
        /// <summary>
        /// Adds the ServiceRoute to the RouteTable
        /// </summary>
        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)
        {

        }
    }
}
XML
<!--This is how Web.config looks-->
<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  
  <!-- Below part that needs to be added for routing integration -->
  <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>
          <!-- To avoid disclosing metadata information, set the value below 
                to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, 
                  set the value below to true.  Set to false before deployment 
                  to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <!--Set the aspNetCompatibilityEnabled attribute to true"-->
    <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.

C#
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:

C#
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)
{
    //Handle the exception
}

Points of Interest 

New features provided with .NET 4.0 for WCF are really interesting and makes any body curious.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)