Поделиться через


How to Build a REST app in .NET (with WCF)

My prior post talked about how NOT to write a REST app in .NET. I mentioned WCF as the preferred option. In this post, I'll describe the steps for how you should do it.

Some background

First up, you should use WCF to build your REST app. WCF is the Windows Communication Foundation. ( Microsoft has such a strong penchant for creating acronyms, that there are web sites dedicated solely to cataloging them. ) WCF is part of the .NET Framework, first added in .NET 3.0. WCF is used by applications developers when building apps that communicate, whether using REST, SOAP, or something else. These days, most applications need to communicate.

The basic metaphor in WCF is that services receive and respond to incoming communication, and clients initiate those communications. The communication is modeled as an interface in code, and the service is a class or type that implements that interface. Services run within a host on any Windows computer - a desktop, laptop or a big server machine. You can also run services on Windows Mobile devices like smart phones!

Therefore for any WCF app, you need a service interface, a service implementation, and a host.

We'll be building a REST service - an application that receives and understands HTTP GET Requests according to the REST pattern.

No Visual Studio required

Contrary to what you may believe, you do not need to have Visual Studio in order to build .NET applications, including WCF applications. But all of the posts I have seen on the topic assume Visual Studio. VS2008 is the commercial application developer's tool that Microsoft sells; the 2008 version includes some nice WCF design and development tools. It is by far the easiest way to build a WCF app; using VS2008 to build WCF apps also allows you to take advantage of other Visual Studio goodies like unit testing, Intellisense, debugging, profiling, and so on. I highly recommend it. If, however, you just want to get something done quickly and you don't have and don't want to pay for the commercial tool, you can also build WCF apps using a text editor (like emacs), and the command line tools included in the .NET SDK (a free download). In this post, I will show you how.

Hosting a WCF Service

WCF provides various options when it comes to hosting a service. These options are:

  1. "Self hosted" - within a Console or Windows Forms application.
  2. Hosted within IIS
  3. As a Windows Service
  4. Hosted within Windows Activation Service. This option was added with IIS7, available in Windows Vista or Windows Server 2008.

You can read more about these options, and how to choose between them, here.

My Recommendation: Do your WCF Prototype work in a self-hosted app.

If you are developing an application to prototype some ideas, then you probably want the simplest environment possible. This, for me, is often the self-hosted route - it gives me the most control and the lightweight approach I need. Hosting WCF services within IIS can also be a lightweight approach. For this post, though, I'm going to show you how to write a WCF app that is self-hosted in a console EXE. This way you can use the console output as a diagnostic tool while tweaking and tuning the app. You can start it and stop it manually. It's easy and accessible.

There is a nifty WCF Service Host that is included as part of Visual Studio 2008, called WcfSvcHost.exe. This is a Windows Forms app that hosts any WCF service you create. It collapses into the system tray, it's got some nice usability features. On the other hand, (1) I'm presuming no Visual Studio, so you don't have WcfSvcHost.exe, dear reader. And (2), this hosting app doesn't give me the control I want. [In particular, it does not give me control over how to handle errors in the service layer, which is something I really want. ]

Rolling your own host for a WCF service is a pretty simple affair. Here's my code to do it.

1 namespace ionic.samples.WcfRest

2 {

3

4 // console-based app to Host the WCF / REST service

5 public class ConsoleServiceHost

6 {

7 public static void Main()

8 {

9 string addressRoot = "https://localhost:8080/";

10

11 // get the type we are hosting

12 System.Type t1 = typeof(ionic.samples.WcfRest.NorthwindDataService);

13

14 // Create a new ServiceHost to start and stop the service

15 // (The URI specified here is used for MEX inquiries)

16 System.ServiceModel.ServiceHost serviceHost1 =

17 new System.ServiceModel.ServiceHost(t1, new System.Uri(addressRoot));

18

19 // Create the REST binding

20 System.ServiceModel.WebHttpBinding restBinding = new System.ServiceModel.WebHttpBinding();

21

22 // Create the REST endpoint. We need to do this explicitly in order to add a custom behavior to it.

23 var restEndPoint =

24 new System.ServiceModel.Description.ServiceEndpoint

25 (System.ServiceModel.Description.ContractDescription.GetContract(typeof(ionic.samples.WcfRest.IDataService)),

26 restBinding,

27 new System.ServiceModel.EndpointAddress(addressRoot + "rest"));

28

29 // Add a customm behavior to the REST endpoint

30 restEndPoint.Behaviors.Add(new ionic.samples.WcfRest.FaultingWebHttpBehavior());

31

32 // Add the endpoint to the service

33 serviceHost1.Description.Endpoints.Add(restEndPoint);

34

35 // Open the ServiceHost to create listeners and start listening for messages

36 serviceHost1.Open();

37 System.Console.WriteLine("Service implementation: " + t1.ToString());

38 System.Console.WriteLine("The service is ready.");

39 System.Console.WriteLine("Service Address: " + addressRoot + "rest");

40 System.Console.WriteLine();

41 System.Console.WriteLine("Press <ENTER> to terminate the service.");

42 System.Console.WriteLine();

43 System.Console.ReadLine();

44

45 // Close to shutdown the service

46 serviceHost1.Close();

47 }

48 }

49 }

I will call your attention to some highlights there: First, you will see that things like base addresses, service interfaces, and service implementation classes are all specified in code (line 12, 17, 25, etc). This stuff could be specified in config files, and commonly is. But in this case, I am specifying all of it in code. There is no config file required here.

Second, is the use of WebHttpBinding, on line 20. This is the binding in WCF, new for .NET 3.5, that supports REST.

Finally, a subtle bit - notice the custom behavior I added to the endpoint. FaultingWebHttpBehavior on line 30. That behavior gives me the error handling control I mentioned earlier. More about that later.

Ok, that's the host. Now I need the service interface and the service implementation.

The Interface

The database I am using is the Northwind sample database which has been shipped with Microsoft database products for years. My service will simply return information for orders in the database - order date, customer, order contents, shipping address, and so on.

This is a simplistic interface, with just one method. It takes a single parameter as input - the order ID - and returns order information in an XML document. Here's the final product:

1 [ServiceContract(Namespace = "urn:ionic.samples.2008.03.wcfrest")]

2 public interface IDataService

3 {

4 [OperationContract]

5 [WebGet(

6 BodyStyle = WebMessageBodyStyle.Bare,

7 RequestFormat = WebMessageFormat.Xml,

8 ResponseFormat = WebMessageFormat.Xml,

9 UriTemplate = "Northwind/Order/{orderId}")]

10 OrderInfoMsg GetOrderInfo(string orderId);

11 }

Things to notice:

  1. Standard WCF-isms like OperationContract and ServiceContract.
  2. The WebGet attribute is new for .NET 3.5, and is the one you want to use for a method that handles REST requests.
  3. The UriTemplate specifies the form of the URI requests this method will be tickled for. Also it does automagic URI-to-method-parameter mapping.

The interface for a service in WCF is exhibited at multiple levels - in code (C# in this case), and on the wire (XML in this case). When doign web services design, a best practice for interoperability is to define the WSDL first, and then implement the services and clients from that WSDL. But REST has no WSDL, and the development style is more rapid iteration. In keeping with that tradition, in my case, I defined the interface in C#, and then iterated on it until I got XML in a shape I liked.

The OrderInfoMsg type, which is the response type I finally arrived at here, looks like this:

1 [DataContract(Namespace = "urn:ionic.samples.2008.03.Rest")]

2 public class OrderInfoMsg

3 {

4 [DataMember]

5 public OrderT Order;

6 [DataMember]

7 public ApplicationFaultT Fault;

8

9 [DataMember]

10 public MessageInfoT MessageInfo;

11

12

13 public OrderInfoMsg()

14 {

15 MessageInfo = new MessageInfoT();

16 }

17 }

There are 3 parts to the class - one part contains information about the order, one part to contain information about any fault that occurred on the server side, and a final part to contain generic message information. The fault information is key, because REST doesn't formally specify a way to handle faults, or what they look like. So your REST application has to consider that itself. My approach is to include a way to pass fault information back to the caller explicitly. The MessageInfo part gets filled implicitly, with a timestamp and other information. Think of that as a basic message "dog tags".

I won't show all the code for the other classes. They are just basic Data Transfer Objects.

The Service Implementation

The service implementation is obviously going to be pretty basic. All we need to do is query SQL Server for the order information, and then form an XML document in reply.

Here's where there is some fun involved. We can use LINQ-to-SQL to do the queries and fill the Data Transfer Object. Check the code:

1 public OrderInfoMsg GetOrderInfo(string orderId)

2 {

3 int n = 0;

4 try

5 {

6 n = System.Int32.Parse(orderId);

7 }

8 catch

9 {

10 return new OrderInfoMsg

11 {

12 Fault = new ApplicationFaultT

13 {

14 message = "Bad input, please pass a valid order number",

15 code = 11

16 }

17 };

18 }

19

20 OrderInfoMsg oim = null;

21 try

22 {

23 oim =

24 (from o in db.Orders

25 where o.OrderID == n

26 select new OrderInfoMsg

27 {

28 Order = new OrderT

29 {

30 OrderID = o.OrderID,

31 Customer = (from c in db.Customers

32 where o.CustomerID == c.CustomerID

33 select new CustomerT

34 {

35 CompanyName = c.CompanyName,

36 ContactName = c.ContactName,

37 ContactTitle = c.ContactTitle,

38 Phone = c.Phone

39 }).Single(),

40 OrderDate = o.OrderDate,

41 ShipInfo = new ShipInfoT

42 {

43 ShipName = o.ShipName,

44 ShipAddress = o.ShipAddress,

45 ShipCity = o.ShipCity,

46 ShipPostalCode = o.ShipPostalCode,

47 ShipCountry = o.ShipCountry,

48 },

49 Items =

50 (from od in db.Order_Details

51 where od.OrderID == o.OrderID

52 select new ItemT {

53 ProductName = od.Product.ProductName,

54 Quantity = od.Quantity,

55 UnitPrice = od.UnitPrice }).ToArray()

56 }

57 }).Single();

58

59 }

60 catch (System.Exception exc1)

61 {

62 oim = new OrderInfoMsg

63 {

64 Fault = new ApplicationFaultT

65 {

66 message = "Could not retrieve Order Information. Maybe you passed bad input?",

67 innerMessage = "Exception: " + exc1.Message,

68 code = 12

69 }

70 };

71 }

72

73 return oim;

74

75 }

Lines 4 through 18 are just validation of the input. The LINQ stuff starts on line 21. Line 26 is where I create the new OrderInfoMsg and the following lines fill that data structure based on information retrieved from the SQL database.

At first that LINQ stuff may look novel and confusing, but it is really a simple way to do database queries. Keep in mind though, that the REST stuff in WCF is not dependent on LINQ in any way. You could create and fill the OrderInfoMsg any old way, including using an old-fashioned DataReader if you like.

But I find LINQ a nice upgrade. At line 49, you can see I fill an array of line items for the order. That's nice, concise, readable code.

In line 62 you can see some error handling, and the use of the object initializers that are new for C# 3.0. Again, nice, clean code.

Where does this LINQ stuff come from?

The LINQ mappings for the Northwind database are defined in a generated C# file, which is not shown here, and which, I confess, I produced using Visual Studio. I know, I know. I promised that VS wasn't required. But I figured that digression was allowed, seeing as how the main point of this post is not LINQ but how to use WCF to build a REST app. You don't need LINQ to retrieve data. It's just a little extra BAM! for you in this sample.

The FaultingWebHttpBehavior

I found that when there was an error in the service, it would return an HTML page to the caller. This has been noticed by others. A fellow by the name of Andre posted code for an endpoint behavior that would prevent the generation of HTML messages. I liked it and used it here. This is the custom behavior I added to the endpoint, in the service host code, shown above.

1 namespace ionic.samples.WcfRest

2 {

3 /// <summary>

4 /// A <see cref="WebHttpBehavior"/> that returns error messages to the caller

5 /// when an exception occurs on the server/service.

6 /// </summary>

7

8 public class FaultingWebHttpBehavior : System.ServiceModel.Description.WebHttpBehavior

9 {

10

11

12 protected override void AddServerErrorHandlers(System.ServiceModel.Description.ServiceEndpoint endpoint,

13 System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)

14 {

15 endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();

16 endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new ErrorHandler());

17 }

18

19 public class ErrorHandler : System.ServiceModel.Dispatcher.IErrorHandler

20 {

21 #region IErrorHandler Members

22

23 public bool HandleError(System.Exception error)

24 {

25 return true;

26 }

27

28 public void ProvideFault(System.Exception error,

29 System.ServiceModel.Channels.MessageVersion version,

30 ref System.ServiceModel.Channels.Message fault)

31 {

32 System.ServiceModel.FaultCode faultCode =

33 System.ServiceModel.FaultCode.CreateSenderFaultCode(error.GetType().Name,

34 "urn:ionic.samples.serviceexception");

35 fault = System.ServiceModel.Channels.Message.CreateMessage(version, faultCode, error.Message, null);

36 }

37

38 #endregion

39 }

40 }

41 }

The REST messages

The request looks something like this:

1 GET /rest/Northwind/Order/11049 HTTP/1.1

2 Accept: */*

3 Accept-Language: en-us

4 UA-CPU: x86

5 Accept-Encoding: gzip, deflate

6 User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0;)

7 Host: dinoch-2:8080

8 Proxy-Connection: Keep-Alive

Obviously, any application on any computer can generate that sort of request. I used an IE browser, but it could be anything, and in a real REST app it won't be just a browser.

And the response looks like this:

1 HTTP/1.1 200 OK

2 Content-Length: 1189

3 Content-Type: application/xml; charset=utf-8

4 Server: Microsoft-HTTPAPI/2.0

5 Date: Thu, 20 Mar 2008 20:34:59 GMT

6

7 <OrderInfoMsg ...>

The prettified XML looks like this:

1 <OrderInfoMsg xmlns="urn:ionic.samples.2008.03.Rest" xmlns:i="https://www.w3.org/2001/XMLSchema-instance">

2 <Fault i:nil="true"/>

3 <MessageInfo>

4 <ApplicationId>319a80e4-3e64-4328-a5d1-468b354c370a</ApplicationId>

5 <ApplicationName>WcfRestService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</ApplicationName>

6 <OkToStore>true</OkToStore>

7 <generated>2008-03-20T13:34:59.3459363-07:00</generated>

8 <originatingHost>dinoch-2</originatingHost>

9 </MessageInfo>

10 <Order>

11 <Customer>

12 <CompanyName>Gourmet Lanchonetes</CompanyName>

13 <ContactName>André Fonseca</ContactName>

14 <ContactTitle>Sales Associate</ContactTitle>

15 <Phone>(11) 555-9482</Phone>

16 </Customer>

17 <Items>

18 <Item>

19 <ProductName>Chang</ProductName>

20 <Quantity>10</Quantity>

21 <UnitPrice>19.0000</UnitPrice>

22 </Item>

23 <Item>

24 <ProductName>Queso Manchego La Pastora</ProductName>

25 <Quantity>4</Quantity>

26 <UnitPrice>38.0000</UnitPrice>

27 </Item>

28 </Items>

29 <OrderDate>1996-05-24T00:00:00</OrderDate>

30 <OrderID>11049</OrderID>

31 <ShipInfo>

32 <ShipAddress>Av. Brasil, 442</ShipAddress>

33 <ShipCity>Campinas</ShipCity>

34 <ShipCountry>Brazil</ShipCountry>

35 <ShipName>Gourmet Lanchonetes</ShipName>

36 <ShipPostalCode>04876-786</ShipPostalCode>

37 <ShipRegion i:nil="true"/>

38 </ShipInfo>

39 </Order>

40 </OrderInfoMsg>

Clean, simple, easy.

But wait! There's more! What if you don't want XML but want that data in JSON? A simple flip of an attribute in the Service definition gives us this as a response:

1 {

2 "Fault":null,

3 "MessageInfo":{

4 "ApplicationId":"319a80e4-3e64-4328-a5d1-468b354c370a",

5 "ApplicationName":"WcfRestService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",

6 "OkToStore":true,

7 "generated":"\/Date(1206047182582-0700)\/",

8 "originatingHost":"dinoch-2"

9 },

10 "Order":{

11 "Customer":{

12 "CompanyName":"La maison d'Asie",

13 "ContactName":"Annette Roulet",

14 "ContactTitle":"Sales Manager",

15 "Phone":"61.77.61.10"

16 },

17 "Items":[

18 {"ProductName":"Guaraná Fantástica","Quantity":10,"UnitPrice":4.5000}

19 ],

20 "OrderDate":"\/Date(833180400000-0700)\/",

21 "OrderID":11051,

22 "ShipInfo":{

23 "ShipAddress":"1 rue Alsace-Lorraine",

24 "ShipCity":"Toulouse",

25 "ShipCountry":"France",

26 "ShipName":"La maison d'Asie",

27 "ShipPostalCode":"31000",

28 "ShipRegion":null

29 }

30 }

31 }

Pretty nifty, eh?

That's how to write a REST app in .NET.

Where are we?

Well, I hope I've shown you a couple things here. First, it's easy to use WCF to build REST apps, using the .NET Framework 3.5. Second, you can build WCF apps with free command line tools. Finally, LINQ is cool, and is a nice complement to REST approaches.

Grab the source code attached here and try it yourself.

WcfRest.zip

Comments

  • Anonymous
    March 23, 2008
    Sharepoint SharePoint for Developers Webcast Series [Via: benko ] Code Camps Twin Cities Code Camp...

  • Anonymous
    March 26, 2008
    .NET:HowtoBuildaRESTappin.NET(withWCF)IIS7-MobileAdminWhatiswrongwiththeASP.N...

  • Anonymous
    March 26, 2008
    .NET: How to Build a REST app in .NET (with WCF) IIS7 - Mobile Admin What is wrong with the ASP.NET Community

  • Anonymous
    March 26, 2008
    Hi Dino, Nice and thorough article.  One question ... Do you know of a way to get a WCF service to honor the 'Content-Type' HTTP header instead of hard coding the content type via an attribute on the method?  I would really like to create a service that has a single set of methods which returns JSON/XML based on the HTTP header(s) ... suggestions? Thanks, Kyle

  • Anonymous
    March 27, 2008
    Hey Kyle, I'm no REST expert, but what you describe seems to be not very RESTful.  If I understand you correctly, you want a single URL to return different Content-type, depending on... a header?   Also I'm imagining, even if you want to do this, it's just another boilerplate method definition, isn't it?  Or maybe you have 100's of methods and you don't want to duplicate them all?  

  • Anonymous
    March 28, 2008
    I'm not sure what it has to do with REST.  Isn't REST more about the appropriate use of HTTP verbs rather than how data is sent over the wire? Yes, it is simple to just create another method in the WCF service to support a content type ... but that doesn't meet the objective.  The goal is to have a single service that supports multiple content types from the same URL.  I think it's quite useful for a REST services to enable consumers to use JSON/XML.  And HTTP headers seem to be the way for a requestor to specify how they want to communicate.

  • Anonymous
    March 28, 2008
    In REST, the URI dictates the result.  To quote Roy Fielding, the guy who coined the term REST in his doctoral thesis, "all important resources must have URIs." The issue of content-type negotiation within the realm of REST architectures is one that comes up often, but Fielding is pretty consistent in saying that such negotiation is problematic and usually not very RESTful.  The URI/URL itself maps to a response, including the content type of the response.  In practice, you don't have a single URI that maps to a PDF file or an HTML file, depending on the Accept: header.   You have two similar but distinct URIs, one for the PDF doc, and one for the HTML.  And REST is entirely consistent with that model. Your statement "The goal is to have a single service that supports multiple content types from the same URL," seems to directly contradict the REST philosophy on that topic, which is "all important resources must have URIs." [1] http://tech.groups.yahoo.com/group/rest-discuss/message/5916 [2] http://tech.groups.yahoo.com/group/rest-discuss/message/5857

  • Anonymous
    March 28, 2008
    Just had a comment-exchange on my prior post on How to build a REST app in WCF . I thought I would reproduce

  • Anonymous
    March 31, 2008
    While this is an excellent article, this seems to be the wrong approach to me.  Why make a service to handle RESTful calls?  Isn't REST just an http api?  Wouldn't you just make a web application to do that?  For an MVC app, you'd just make a new web app that called into the same controller layer.  It would just add the capability to specify JSON or XML instead of html output (which I think the controllers should be built to handle).  Plus, I feel it's fine to make these decisions in the http headers as well, since I shouldn't have to write a whole new set of controller actions to handle the different output types.  That's something a framework can do, or something that can be handled in the actions themselves. Maybe my thinking is based on the assumption that you started with an MVC web app that you want to expose through a RESTful api.  I can see your approach being viable if you simply have services that you want to expose RESTfully.

  • Anonymous
    April 14, 2008
    I love Visual Studio, but sometimes I build code using no graphical IDE - just the .NET Framework SDK.

  • Anonymous
    July 01, 2008
    Welcome to the Connected .NET blog, where we’ll focus on the stuff in the .NET Framework that connects

  • Anonymous
    February 22, 2009
    A plethora of news this week...enjoy! General Wrapping up NHibernate : I've been playing with NHibernate for a few projects, and am really liking what I'm seeing. This post provides an elegant pattern for wrapping and consuming NHibernate. Why do we need

  • Anonymous
    August 07, 2009
    Lot a code there. I'm using JAX-RS on JBoss and you define /service/books/ISBN123 like this: @Path("/service") class BookService {   @Path("/books/{key}") Book getBook(@PathParam("key") String key)   {      return library.find( key );;   } } I would be thrilled if Microsoft would copy the JAX-RS REST specification. Took 10 years to get MVC and still no sign of a dependency injection framework (it's about 10 years since it became popular, too) but they could leap ahead by stealing JAX-RS.

  • Anonymous
    August 08, 2009
    The comment has been removed

  • Anonymous
    December 02, 2009
    Visual studio is not required, you can generate dbml file with sqlmetal.exe which comes with Windows SDK. May even be in vanilla distribution of .netfx but I always install SDK so can't be sure.