SOAP or REST - this has always been a debated topic. In both the approaches, we have certain disadvantages as well as advantages. The internet is flooded with such articles, but what if we want to keep both parallel, yes of course we can do this because WCF gives us all the freedom to use both the approaches using the same Service Contract. There is a very nice article which covers this topic. You can read it from the link below:
Now what if we want to use Service Factory (I assume readers of this article are familiar with WSSF 2010, if not, then you can refer to this section, you can get everything you need to get started here.). Service Factory is designed to help you quickly and consistently construct WCF Web services that adhere to well known architecture. But along with this, WSSF does not support REST Service out of the box. We may either have to customize the WSSF Templates or may have to think something really out of the box and that's what I am going to show in this article.
There are a couple of reasons why I personally preferred the second approach and not to customize the template is for the design perspective if I keep both SOAP and REST in the same Contract, it will be tightly coupled and SOAP and REST work in different ways. And the same Service contract will have Dependency on both REST and SOAP. Also when we have to change something specific to REST or SOAP, we will end up changing for both even if we don't want to do so, this may force us to test both the clients of REST and SOAP. But this is just for case to case basis and I am leaving this to the reader. And now I am closing these design issues here and jumping directly to the implementation of the above.
Again, I am not going to cover the basics of SOAP or REST or WSSF. I am going to create a WSSF guidance package with Service contract, data contract and host models, canvas diagrams of the same is given below.
For this example, I am going to use the Northwind
database and to keep this simple and precise, I am using only Customer
table. With the above diagrams, I hope you can now figure out my Service Contract, Data Contract and Message Contract. You can download the Northwind
database from here.
For the ORM tool, I have used Entity Framework to keep this example simple, you may use your own Data Layer if you want. The generated solution structure looks like the one below, where I have done a few additions for REST highlighted in RED.
Here in the example, we have defined a separate Service contract for Rest Service called NorthwindRestService
which implements the contract INorthwindRestServiceContract
. These are the files which are not generated by WSSF. Now let's get more into the code of these files below.
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Web;
using WssfRestNorthwind.DataContracts;
namespace WssfRestNorthwind.ServiceContracts.Rest
{
[ServiceContract]
public partial interface INorthwindRestServiceContract
{
[WebGet(UriTemplate = "Customer",
RequestFormat = WebMessageFormat.Xml, ResponseFormat = WebMessageFormat.Xml)]
List<Customer> GetAllCustomers();
[OperationContract]
[WebGet(UriTemplate = "Customer/{customerID}")]
Customer GetCustomerById(string customerID);
[WebInvoke(Method = "POST", UriTemplate = "Customer/Add")]
void AddCustomer(Customer customer);
}
}
In the code of Rest Service contract, you can see that I have used the WebGet
and WebInvoke
attributes to expose my service as a REST Service. And for my Service implementation, I have used the code below:
using System.Collections.Generic;
using WssfRestNorthwind.ServiceContracts.Rest;
namespace WssfRestNorthwind.ServiceImplementation
{
public partial class NorthwindRestService : INorthwindRestServiceContract
{
BusinessLogic.Repository.CustomerRepository customerCotext =
new BusinessLogic.Repository.CustomerRepository();
#region INorthwindRestServiceContract Members
public List<DataContracts.Customer> GetAllCustomers()
{
List<DataContracts.Customer>
customers = new List<DataContracts.Customer>();
DataContracts.Customer customer = new DataContracts.Customer();
foreach (BusinessEntities.Customer cust in customerCotext.GetCustomers())
{
customers.Add(TranslateBetweenCustomerAndCustomer.TranslateCustomerToCustomer(cust));
}
return customers;
}
public DataContracts.Customer GetCustomerById(string customerID)
{
DataContracts.Customer customer = new DataContracts.Customer();
return TranslateBetweenCustomerAndCustomer.TranslateCustomerToCustomer
(customerCotext.GetCustomersById(customerID));
}
public void AddCustomer(DataContracts.Customer customer)
{
if (null != customer)
customerCotext.AddCustomer
(TranslateBetweenCustomerAndCustomer.TranslateCustomerToCustomer(customer));
}
#endregion
}
}
As you can notice, this service implementation is used for just passing the response and request objects from Service to Business Logic and Back to service. So we are reusing our common Business Logic, Data Contract and Business entities which are the heart of the service and we had put maximum effort in these layers. The only effort we are putting now in just creating the Service Contract and Service Implementation which is just few lines of code. Now let's have a quick look into our Data Layers and Business Layers. The data model of our database is given as below:
and we had used Repository Pattern to Query our models. You can get a quick look into the codes below for my CustomerRepository
and ICustomerRepository
respectively.
using System;
using System.Linq;
using WssfRestNorthwind.BusinessEntities;
namespace WssfRestNorthwind.BusinessLogic.Repository
{
public class CustomerRepository: ICustomerRepository
{
NorthwindEntities context = new NorthwindEntities();
#region ICustomerRepository Members
public IQueryable<Customer> GetCustomers()
{
var customers = from result in context.Customers
select result;
return customers;
}
public Customer GetCustomersById(string customerId)
{
var customer = (from result in context.Customers
where result.CustomerID == customerId
select result).FirstOrDefault();
return customer;
}
public IQueryable<Order> GetCustomerOrders(string customerId)
{
var customerOrders = from result in context.Orders
where result.CustomerID == customerId
select result;
return customerOrders;
}
public void AddCustomer(Customer customer)
{
context.AddToCustomers(customer);
context.SaveChanges();
}
public void UpdateCustomer(Customer customer)
{
throw new NotImplementedException();
}
public void DeleteCustomer()
{
throw new NotImplementedException();
}
#endregion
}
}
using System.Linq;
using WssfRestNorthwind.BusinessEntities;
namespace WssfRestNorthwind.BusinessLogic.Repository
{
interface ICustomerRepository
{
IQueryable<Customer> GetCustomers();
Customer GetCustomersById(string customerId);
IQueryable<Order> GetCustomerOrders(string customerId);
void AddCustomer(Customer customer);
void UpdateCustomer(Customer customer);
void DeleteCustomer();
}
}
The above code is just for reference only to be used for this article. In real scenarios, we may have huge amount of code in these places and also many more files for repository and also for BusinessLogic
. So if you have understood till now, these codes are common for both REST and SOAP service. We had also created a translator for converting the Business entities to Data Contract and from Data Contract back to Business Entities. Which is very much required if we are following this Service Factory Patterns. If you have any queries on basics of WSSF, you can go back to the link of WSSF Tutorials which I have already given above earlier.
With these codes, our Service is almost ready to consume, now the only thing which we are missing is the hosting of these services. For our example, I am using ASP.NET development server hosting but you can use any other hosting as per your requirements. And also the endpoints. Since for SOAP, we can use various amount of bindings which correspond to WS-*
standards, for REST we have to use webHttpBinding
. Code of my web.config of REST and SOAP Service is given below.
="1.0"
<configuration>
<connectionStrings>
<add name="NorthwindEntities"
connectionString="metadata=res://*/NorthwindModel.csdl|res://*/NorthwindModel.ssdl|
res://*/NorthwindModel.msl;provider=System.Data.SqlClient;
provider connection string="data source=.;
initial catalog=Northwind;integrated security=True;
multipleactiveresultsets=True;App=EntityFramework"
" providerName="System.Data.EntityClient" />
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="REST">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="EndPointBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="EndPointBehavior"
name="WssfRestNorthwind.ServiceImplementation.NorthwindRestService">
<endpoint address=""
behaviorConfiguration="REST"
binding="webHttpBinding"
contract="WssfRestNorthwind.ServiceContracts.Rest.INorthwindRestServiceContract" />
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
and for SOAP Service:
="1.0"
<configuration>
<connectionStrings>
<add name="NorthwindEntities"
connectionString="metadata=res://*/NorthwindModel.csdl|res://*/NorthwindModel.ssdl|
res://*/NorthwindModel.msl;provider=System.Data.SqlClient;
provider connection string="data source=.;
initial catalog=Northwind;integrated security=True;
multipleactiveresultsets=True;App=EntityFramework""
providerName="System.Data.EntityClient" />
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="REST">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="EndPointBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="EndPointBehavior"
name="WssfRestNorthwind.ServiceImplementation.NorthwindRestService">
<endpoint address=""
behaviorConfiguration="REST"
binding="webHttpBinding"
contract="WssfRestNorthwind.ServiceContracts.Rest.INorthwindRestServiceContract" />
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
So with this, I guess my Service is 100% ready to host, for SOAP Service, WSSF provides with generated Test Client for my REST Service, I have created a similar project only with few differences. To keep the article focused, I have included the Test Projects with the complete source code in the download section. Hosting model diagram is given below:
We can also test this using the browser with the following URL’s formats. Please note these URLs are for indication only actual URL’s Server names may not be the same for your environment
For Save operation, this method will pass a new customer for addition into the DB http://localhost:3188/WssfRestNorthwind.Rest.Host/NorthwindService.svc/Customer/Add
Get Customer
By Id http://localhost:3188/WssfRestNorthwind.Rest.Host/NorthwindService.svc/Customer/ALFKI
GetAllCustomers
http://localhost:3188/WssfRestNorthwind.Rest.Host/NorthwindService.svc/Customer
For those who want to have a Bird’s eye view of the Solution structure, they can click on the image to get the snapshot of the entire solution.
To get the feel of the above article and get more into the implementation logic, I recommend you to download the complete source from here.
My objective of this article to show you the approach, so I am not getting into the details of few things like What is WSSF, REST, WCF, SOAP, etc.
This is just one approach from my point of view. If readers of this article have a better idea or suggestion, please feel free to comment. Thanks in advance.