Tags: , , | Posted by Kevin Babcock on 5/13/2009 1:54 AM | Comments (1)

When it comes to web services, there are no shortage of options for .NET developers. And with performance expectations continuing to rise, web services are becoming an integral part of our web applications. They allow developers to transfer the bare minimum of data from server to client, leaving UI updates to the browser and client script. But of the available web service implementation options, which are the most commonly used? PageMethods are extremely simple and allow you to reference static methods in the code-behind of your page from the client. Unfortunately, this method doesn’t make sense if you need to reuse the service elsewhere. Next there’s the ASMX web services to which we’ve all grown accustomed. This option lets us create more flexible web services that aren’t necessarily tied to a particular application. And finally, there’s WCF. While WCF is much more than just web services, it is the platform for web services that Microsoft plans to invest in moving forward. Unfortunately, it’s also the most difficult to set up. Or is it?

When choosing between these different options, there is a tradeoff between functionality and ease of use (or implementation). WCF offers a wide range of options and is highly configurable. PageMethods is fairly constricting and only useful for specific needs. ASMX web services sit somewhere in between.Image demonstrating the tradeoff of ease of use and functionality between web service implementations in .NET

Just because WCF has a lot of options and is highly configurable doesn’t mean you can’t get started with it just as easily as with the other two methods. As of .NET 3.5, several new namespaces and classes were added to System.ServiceModel.Web.dll including two new classes, WebServiceHostFactory and WebScriptServiceHostFactory, which extend System.ServiceModel.Activation.ServiceHostFactory. These classes offer a simple and easy way to configure WCF service endpoints without the need to touch any of the configuration settings in web.config.

Screenshot of the contents of System.ServiceModel.Web.dll in .NET ReflectorIf you take a look at the declaration of your WCF service’s ServiceHost directive (which you can open by right-clicking on the .svc file and selecting View Markup) you’ll notice that there is an attribute called Factory. You can use either of the two new ServiceHostFactory classes by simply setting Factory to the fully qualified type name.

<%@ ServiceHost 
    CodeBehind="MyService.svc.cs"
    Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
    Language="C#" 
    Service="Examples.Services.MyService" 
%>

So you’re probably wondering what you get when you use either of these factory classes. Here’s a quick overview of the functionality that each factory class configures for you automatically:

WebServiceHostFactory
Creates an endpoint with all of the basic settings at the service’s base HTTP and HTTPS addresses, unless an endpoint has already been configured. The WebHttpBehavior is also added to all endpoints whose behavior is not configured.

WebScriptServiceHostFactory
Creates an endpoint with all of the basic settings, adding the WebHttpBinding and WebScriptEnablingBehavior. WebHttpBinding is used for web services that are exposed via HTTP requests. WebScriptEnablingBehavior enables WCF and ASP.NET AJAX integration, adding a JavaScript proxy endpoint at which the script used to call the service from the client can be downloaded. The factory creates a service endpoint at an address relative to the service path (your .svc file). It is important to remember that the address at which the factory creates the endpoint to the web service must be unique.

Let’s take a look at the implementation of a simple WCF service that uses the WebServiceHostFactory class to configure its endpoint. The code below implements a RESTful service which uses WebGet and WebInvoke attributes to specify which HTTP verb should be used to call the service methods, as well as the request/response format and a UriTemplate to distinguish each method call with a unique URI. The service requires no additional configuration.

<%@ ServiceHost 
    CodeBehind="CustomerService.svc.cs"
    Debug="true"
    Factory="System.ServiceModel.Activation.WebServiceHostFactory"
    Language="C#" 
    Service="MyViewState.Examples.Services.CustomerService" 
%>
namespace MyViewState.Examples.Services
{
    [ServiceContract]
    public class CustomerService
    {
        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Xml, 
            UriTemplate = "/")]
        public List<Customer> GetCustomers()
        {
            using (var northwind = new NorthwindDataContext())
            {
                var query = northwind.Customers;
                var customers = query.ToList<Customer>();
                return customers;
            }
        }

        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Xml, 
            UriTemplate = "/{name}")]
        public List<Customer> GetCustomer(string name)
        {
            using (var northwind = new NorthwindDataContext())
            {
                var query =
                    from c in northwind.Customers
                    where c.CompanyName.ToLower().Contains(name.ToLower())
                    select c;
                var customers = query.ToList<Customer>();
                return customers;
            }
        }

        [OperationContract]
        [WebInvoke(Method = "POST",
            BodyStyle = WebMessageBodyStyle.Bare,
            RequestFormat = WebMessageFormat.Xml, 
            ResponseFormat = WebMessageFormat.Xml,
            UriTemplate = "/")]
        public bool InsertCustomer(Customer customer)
        {
            var success = false;
            using (var northwind = new NorthwindDataContext())
            {
                try
                {
                    var id = Guid.NewGuid().ToString().ToUpper().Substring(0, 5);
                    customer.CustomerID = id;
                    northwind.Customers.InsertOnSubmit(customer);
                    northwind.SubmitChanges();
                    success = true;
                }
                catch (Exception) { }
            }
            return success;
        }

        [OperationContract]
        [WebInvoke(Method = "POST",
            BodyStyle = WebMessageBodyStyle.Bare,
            RequestFormat = WebMessageFormat.Xml, 
            ResponseFormat = WebMessageFormat.Xml,
            UriTemplate = "/{id}")]
        public bool UpdateCustomer(string id, Customer customer)
        {
            var success = false;
            using (var northwind = new NorthwindDataContext())
            {
                try
                {
                    var query =
                        from c in northwind.Customers
                        where c.CustomerID == id
                        select c;
                    var oldCustomer = query.SingleOrDefault<Customer>();
                    if (oldCustomer != null)
                    {
                        oldCustomer.CompanyName = customer.CompanyName;
                        oldCustomer.Address = customer.Address;
                        oldCustomer.City = customer.City;
                        oldCustomer.Region = customer.Region;
                        oldCustomer.Country = customer.Country;
                        oldCustomer.PostalCode = customer.PostalCode;
                        northwind.SubmitChanges();
                        success = true;
                    }
                }
                catch (Exception) { }
            }
            return success;
        }

        [OperationContract]
        [WebInvoke(Method = "POST", 
            UriTemplate = "/delete/{id}")]
        public bool DeleteCustomer(string id)
        {
            var success = false;
            using (var northwind = new NorthwindDataContext())
            {
                try
                {
                    var query =
                        from c in northwind.Customers
                        where c.CustomerID == id
                        select c;
                    var customer = query.SingleOrDefault<Customer>();
                    if (customer != null)
                        northwind.Customers.DeleteOnSubmit(customer);
                    northwind.SubmitChanges();
                    success = true;
                }
                catch (Exception) { }
            }
            return success;
        }
    }
}

If we launch the service in IE we can quickly test this new service by navigating to a URL that matches one of the service’s UriTemplates. Obviously this will only work for those service methods that use HTTP GET. If you want to test out the remaining service methods you can do so by building a simple client that is capable of making HTTP POST requests. Or you can stay tuned to a future post, in which I will use jQuery and client templates to implement a simple client which can consume the service.

Display of XML data returned from web service call 

This is a great example of how easy it is to get up and running with WCF web services. It is highly configurable, but can also be implemented just as easily as PageMethods or ASMX services. The beautiful thing about taking this approach to creating web services is that if you want to take advantage of other WCF features, you can easily modify the service configuration as needed without changing its implementation. If you had gone with one of the other approaches, you’d have less flexibility.

If you are interested in learning about the your .NET web service implementation options, check out the following links:

kick it on DotNetKicks.com

Comments

Jay on 6/20/2009 12:11 PM This looks very interesting. Can I get the source code for me try it out?