If you've been thinking about using ASP.NET Ajax in combination with WCF, life got a lot easier with .NET 3.5 and the new REST support. The webHttp binding along with the ASP.NET AJAX specific version makes it easy to use WCF instead of ASMX services with ASP.NET AJAX.
A valid question to ask though is: Do I need to use WCF or should I continue to use ASMX?
If ASMX services are working fine for you as is with MS AJAX you probably don't have to switch. In fact if your services are purely based on making AJAX callbacks from a pure Web app and you don't plan on reusing existing services, there's not much of an advantage using WCF over ASMX services. ASMX services continue to be easier to set up and work with and maybe more importantly they don't require .NET 3.5.
WCF REST services however provide features that ASMX does not, such as the ability to serve raw non-ASP.NET AJAX formatted data in 'bare' format. If you're working with non-ASP.NET AJAX clients like jQuery or Prototype this can be a bonus and lets you avoid using the MS AJax client libraries and ScriptManager. WCF REST also supports raw XML based access to services which is also useful and can't be directly accomplished with ASMX.
Another consideration: If you have existing services that rely on HttpContext, realize that WCF by default doesn't support access to the underlying 'host platform'. WCF is supposed to be host agnostic and WCF services can in fact be running outside of IIS. If you have existing code that relies on HttpContext access to the Request or Session object which is common , it's probably best to leave it in ASMX (although WCF has a way to do that as well).
But if you're starting a new app and you're using .NET 3.5 on the server anyway you might as well take advantage of WCF's REST/Ajax features and the wider range of options available to publish data.
Configuration, Configuration, Configuration
The biggest issue with any WCF application is configuration. Fortunately Microsoft figured this one for .NET 3.5 out and created a number of WebService Host Factories that provide default settings that apply to the most common service implementations. With these Service factories only a Factory tag is required on the Service, without any further configuration in web.config.
You can create a new WCF service and add the following Factory tag to the generated .SVC file:
<%@ ServiceHost Language="C#"
Service="WcfAjax.MsAjaxStockService"
CodeBehind="MsAjaxStockService.svc.cs"
Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
%>
For MS AJAX style Web Service the factory is WebScriptServiceFactory. If you want to provide other REST services such as for raw (un-wrapped) AJAX, XML or raw data there's a WebServiceHostFactory. When used no web.config settings need to be set manually and if there are settings in Web.config that apply to this service they are in fact ignored.
To put this into perspective here's a small service contract:
namespace WcfAjax
{
[ServiceContract(Name="StockService",Namespace="MsAjaxStockService")]
public interface IMsAjaxStockService
{
[OperationContract]
StockQuote GetStockQuote(string symbol);
[OperationContract]
StockQuote[] GetStockQuotes(string[] symbols);
[OperationContract]
StockHistory[] GetStockHistory(string symbol, int years);
[OperationContract()]
[WebInvoke(Method = "GET")]
Stream GetStockHistoryGraph(string[] symbol, string title, int width, int height, int years);
[OperationContract()]
[WebInvoke(Method = "GET")]
Stream GetStockHistoryGraphSingle(string symbol, string title, int width, int height, int years);
}
}
and its implementation class that implements the contract:
public class MsAjaxStockService : IMsAjaxStockService
{
private StockServer Stocks = new StockServer();
public StockQuote GetStockQuote(string symbol)
{
string path = HttpContext.Current.Request.Path;
return Stocks.GetStockQuote(symbol);
}
public StockQuote[] GetStockQuotes(string[] symbols)
{
return GetStockQuotes(symbols);
}
public StockQuote[] GetStockQuotesSafe(string symbolList)
{
string[] symbols = symbolList.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return Stocks.GetStockQuotes(symbols);
}
public StockHistory[] GetStockHistory(string symbol, int years)
{
return Stocks.GetStockHistory(symbol, years);
}
public Stream GetStockHistoryGraph(string[] symbols, string title, int width, int height, int years)
{
WebOperationContext.Current.OutgoingResponse.ContentType = "image/jpeg";
byte[] imgData = Stocks.GetStockHistoryGraph(symbols, title, width, height, years);
MemoryStream ms = new MemoryStream(imgData);
return ms;
}
public Stream GetStockHistoryGraphSafe(string symbolList, string title, int width, int height, int years)
{
string[] symbols = symbolList.Split(new char[1] {','},StringSplitOptions.RemoveEmptyEntries);
return this.GetStockHistoryGraph(symbols,title,width,height,years);
}
public Stream GetStockHistoryGraphSingle(string symbol, string title, int width, int height, int years)
{
WebOperationContext.Current.OutgoingResponse.ContentType = "image/jpeg";
byte[] imgData = Stocks.GetStockHistoryGraph(new string[1] { symbol }, title, width, height, years);
MemoryStream ms = new MemoryStream(imgData);
return ms;
}
}
With the Web Service Factory defined in the Web Service .SVC file in your web project you are pretty much done. The service can be accessed directly from an ASP.NET AJAX client page that has the script reference to the service embedded via script manager:
<asp:ScriptManager ID="ScriptManager" runat="server">
<Services>
<asp:ServiceReference Path="~/MsAjax/MsAjaxStockService.svc" />
</Services>
</asp:ScriptManager>
To debug that everything is working right with your service compilation and configuration access the service directly. So
MsAjaxStockService.svc/jsdebug
which is the URL that retrieves the client side JavaScript proxy that you can use to call the Web Service. I also like to look at the .js output in this file because it tells you EXACTLY what the name of the proxy is.
JavaScript Service Proxy Reference Naming
The reason for looking at the JSDEBUG content is that WCF defaults the name of the proxy instance as Namespace.Classname. The name might be a bit unexpected because unlike ASMX which used the .NET class namespace, WCF uses the URI namespace. Check out the generated proxy header for a default Web Service implementation that doesn't have a custom name or namespace set which has a namespace of http://tempuri.org):
Type.registerNamespace('tempuri.org');
tempuri.org.IMsAjaxStockService=function() {
tempuri.org.IMsAjaxStockService.initializeBase(this);
this._timeout = 0;
this._userContext = null;
this._succeeded = null;
this._failed = null;
}
tempuri.org.IMsAjaxStockService.prototype={
_get_path:function() {
var p = this.get_path();
if (p) return p;
else return tempuri.org.IMsAjaxStockService._staticInstance.get_path();},
GetStockQuote:function(symbol,succeededCallback, failedCallback, userContext) {
..
which results in client code like this for you to call the service from JavaScript client code:
tempuri.org.IMsAjaxStockService.MsAjaxStockService.GetStockQuote
(
"MSFT",
function(result)
{ alert(result.LastPrice); },
function(error)
{ alert(result.get_Message()); }
);
which is uhm a bit verbose for the namespace prefix. If you use your own regular URI based namespaces WCF strips out all the slashes and replaces them with dots. Originally I used a custom namespace - http://www.west-wind.com/services/MsAjaxStockService which is a pretty standard format for Web Services. It ended up in the JavaScript proxy like this:
www.westwind.com.services.MsAjaxStockService.StockService
which is a mouthful to say the least.
Thankfully you can override the naming of the service and namespace on the service contract however, which I applied in the service above:
[ServiceContract(Name="StockService",Namespace="MsAjaxStockService")]
This results in the more usable:
MsAjaxStockService.StockService.GetQuote()
If your service is locally consumed in your application only you can set the Namespace attribute to an empty string and skip the namespace altogether. Otherwise using the namespace is probably a good idea to avoid naming conflicts.
What you gain and what you lose
Using AJAX with WCF in this fashion doesn't really bring much in the way of new features over ASMX, but it does bring a more isolated service model by default. Creating a WCF REST service allows you to run your REST service potentially outside of IIS. These REST services can be hosted in any WCF Host container including standalone Windows applications, Services or Console applications.
But this isolated service environment also removes any dependencies on ASP.NET by default. This means that there's no HttpContext.Current available to read input data from the Request object, access the Session object etc. WCF includes its own ServiceContext that provides many of these same features. REST services in addition get a WebOperationRequestContext that allows access to the inbound and outbound HTTP headers.
For example WCF allows streaming of raw binary content to the client as part of the service API which allows you to publish binary data like images, but also arbitrary HTTP content like HTML or even something like an RSS feed. Here's an example of my service publishing a portfolio stock history graph:
public Stream GetStockHistoryGraph(string[] symbols, string title, int width, int height, int years)
{
WebOperationContext.Current.OutgoingResponse.ContentType = "image/jpeg";
byte[] imgData = Stocks.GetStockHistoryGraph(symbols, title, width, height, years);
MemoryStream ms = new MemoryStream(imgData);
return ms;
}
The WebOperationContext is required to set the output content type of the raw stream here. You can also use the OperationContext.Current instance to get addtional information about the service or the security applied to it.
WCF REST also supports accessing the HttpContext from the service by using a configuration setting, but this is generally not recommended because it ties the service directly to ASP.NET and will prevent it from being hosted outside of IIS/WAS.
To add ASP.NET compatibility though you can't use the ServiceHostFactory since it ignores configuration settings. So you need to manually configure the service.
Manual .Config File Configuraion of WCF Script Services
But you can also manually configure the service using traditional serviceModel settings in web.config (or .Config when running outside of IIS). I had a bit of trouble getting these configuration settings just right for this service - there are a few posts and articles out there (including one of my own <blush>) which are out of date with beta settings so I floundered around a bit.
The service mapping from SVC file to the .Config settings also isn't obvious and not mentioned in hte MSDN configuration documentation. Most of my problems were simply caused by having the wrong service name in web.config.
The following is the manual configuration for the above Script Service, plus it adds support for ASP.NET compatibility:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<services>
<service name="WcfAjax.MsAjaxStockService">
<endpoint address=""
behaviorConfiguration="MsAjaxStockServiceBehavior"
binding="webHttpBinding"
contract="WcfAjax.IMsAjaxStockService" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="MsAjaxStockServiceBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
The service name must match the name of the service class that implements the service. This should be the same value specified inside of the SVC file's Service="" attribute:
<%@ ServiceHost Language="C#"
Service="WcfAjax.MsAjaxStockService"
CodeBehind="MsAjaxStockService.svc.cs"
%>
Note again that if you have both web.config settings and a Factory tag in the service file, the Factory takes precendence and ignores any web.config settings. If things aren't working as you think be sure to check both the service file and the .config to make sure you're running the right configuration code!
The settings above are specific for an ASP.NET AJAX style script service. The MS Ajax implementation provides a few few special features like the ability to access the proxy via the /jsdebug switch of the service url as well as formatting messages using Microsoft's funky JSON formatting that is essentially a wrapped set of all the parameters and a type wrapped response message.
Http Context Usage in WCF Services
Back to this issue once more. If you want to use HttpContext in your WCF REST services you need to
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
to the web.config file. This is a global setting that then applies to all services hosted inside of IIS/ASP.NET.
In addition, you also need to specify that the class either Allows or Requires ASP.NET Compatibility:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MsAjaxStockService : IMsAjaxStockService
Note that once the setting is made in web.config each REST service MUST have this attribute, or ASP.NET will throw an exception to the affect that AspNetCompatibility is not specified. Odd design choice on the WCF team's part - the default should be if not specified to ignore it but apparently the WCF REST folks are very keen on hardcoding everything into attributes (which is not optimal for many things as you'll see in a minute).
Plain REST/Ajax Services
If your goal is to have a plain AJAX client like jQuery call WCF REST services you need to use a slightly different type of service implementation using the webHttp binding. Unlike the webScriptBehavior that MS AJAX uses this mechanism supports a variety of different formats for the data that is sent to the server and how it's returned. There are quite a few more options to specify and there's lots of configurability but it's also a bit more complex to get the configuration right.
Again let's do the easy route first - simply using a service factory like this:
<%@ ServiceHost Language="C#"
Debug="true" CodeBehind="RestStockService.svc.cs"
Service="WcfAjax.RestStockService"
Factory="System.ServiceModel.Activation.WebServiceHostFactory"
%>
In order to have both MS Ajax and Raw REST access to the service I have to now implement a complete separate contract. This is - unfortunately - because WCF requires that each service end point operation/method describes EXACTLY what type of data it expects. Here's the contract for the REST interface:
namespace WcfAjax
{
[ServiceContract]
public interface IRestStockService
{
[OperationContract]
[WebInvoke(
Method="GET",
RequestFormat =WebMessageFormat.Json,
ResponseFormat= WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped,
UriTemplate= "GetStockQuote/{symbol}")
]
StockQuote GetStockQuote(string symbol);
[OperationContract(Name="GetStockQuotes")]
[WebInvoke(
Method = "GET",
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate="GetStockQuotes/{symbols}")
]
StockQuote[] GetStockQuotesSafe(string symbols );
// *** This won't work with GET operation
//StockQuote[] GetStockQuotes(string[] symbols);
[OperationContract]
[WebInvoke(
Method = "GET",
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped)
//UriTemplate = "GetStockHistory/{symbol}/{years}") // doesn't work
]
StockHistory[] GetStockHistory(string symbol, int years);
[OperationContract(Name="GetStockHistoryGraph")]
[WebInvoke(Method = "GET")]
Stream GetStockHistoryGraphSafe(string symbols, string title, int width, int height, int years);
[OperationContract()]
[WebInvoke(Method = "GET")]
Stream GetStockHistoryGraphSingle(string symbol, string title, int width, int height, int years);
}
}
The plain REST interface requires that you are very explicit about what type of messages each operation/method can receive. You have to specify explicit GET or POST (or PUT,DELETE or custom) and the service will only respond to that type of message. In addition the body format - JSON or XML basically is also fixed so a single URL cannot serve both JSON and XML.
Even using the cool new UriTemplates which allow you to create more readable URLs, you still can't have one endpoint that serves data in different formats. I suspect that's a fairly common scenario that unfortunately doesn't work.
The implementation of the above interface looks identical to the implementation I showed before. But because WCF doesn't support multiple ServiceContracts implemented on a single type I can't just reuse the existing base class.
So this doesn't work:
public class MsAjaxStockService : IMsAjaxStockService, IRestStockService
Another bummer. But there's a workaround for this with a little trickery and by creating a base class. You still need to create separate contract interfaces for IMsAjaxStockService and IRestStockService, but you can have one base class:
public class StockServiceBase
{
// Full implementation code here
}
and then inherit from it with the concrete service implementations:
public class MsAjaxStockService : StockServiceBase, IMsAjaxStockService
{ // no code here }
public class RestStockService : StockServiceBase, IRestStockService
{ // no code here }
Each class now implements its specific service contract interface, but inherits the entire implementation from the StockServiceBase which contains all the implementation details.
Configuration of plain REST Services
REST services - like the MS AJAX style script service - can also be configured either via a service host factory or by configuring the .Config file. The configuration is similar but there are a few differences. Here's the configuration for the above service for the plain REST implementation:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<services>
<service name="WcfAjax.RestStockService">
<endpoint address=""
behaviorConfiguration="RestStockServiceBehavior"
binding="webHttpBinding"
contract="WcfAjax.IRestStockService" />
</service>
</services>
<behaviors>
<behavior name="RestStockServiceBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Note again that the SVC Service attribute should match the service name in the configuration. This is how the config settings are found. The key behavior setting here is specification of the <webHttp /> behavior. Without this the service will not work (even though you'd expect simply specifying the webHttpBinding would be enough).
You can of course mix multiple services together. So my particular web.config section that provides both for MS AJax and REST access looks like this:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<services>
<service name="WcfAjax.MsAjaxStockService">
<endpoint address=""
behaviorConfiguration="MsAjaxStockServiceBehavior"
binding="webHttpBinding"
contract="WcfAjax.IMsAjaxStockService" />
</service>
<service name="WcfAjax.RestStockService">
<endpoint address=""
behaviorConfiguration="RestStockServiceBehavior"
binding="webHttpBinding"
contract="WcfAjax.IRestStockService" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="MsAjaxStockServiceBehavior">
<enableWebScript />
</behavior>
<behavior name="RestStockServiceBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
The only real difference between the two modes is in the high level behavior that is used.
It's clear that the Host factory pre-configured settings are the way to go. It's easiers and more portable - one less thing to manually configure. REST services simply won't need a lot of tweaking and configuration since all the protocol issues are driven through HTTP. I suspect the main reason to not use the Service Factory will be for those that wish to still use HttpContext in their services for which manual configuration is required.
Other Posts you might also like