Windows Communication Foundation and RESTful Web Services Primer(教你制作rest wcf)

转自:http://www.codeproject.com/Articles/55397/Windows-Communication-Foundation-and-RESTful-Web-S

Introduction to WCF 3.5 and REST

You want to know more about WCF or RESTful services with WCF, and don't know how to fast-track your way into making something quickly? Then, this tutorial is for you.

Estimated time needed: 3-4 hours.

You can find the PDF version here: Download PDF - 392.98 KB

What do you Need

  • Visual Studio 2008 SP1
  • .NET 3.5 SP1
  • Windows SDK (was included in my installation: check if you have C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\SvcConfigEditor.exe)

Both Visual Studio and .NET need to have SP1, not just .NET.

WCF Fundamentals

Since .NET 3.0 (codename .NET FX), Microsoft has added lots of new libraries to the .NET Framework. One of these isWCFWCF is expanded with RESTful features in .NET 3.5 (C# 3.0), and integrated with Workflow Foundation and other technologies. Many new .NET 3.5 technologies use WCF as their underlying basis too. In .NET 4.0, WCF gets more important, and in the future years to come, WCF will replace all Microsoft distributed development technologies.

Windows Communication Foundation (WCF) is all about communicating. In a stand-alone application, like say XML Spy, or Notepad, you would not generally use services.

In a business context, a lot of dollars go into creating systems that support the business. It's generally accepted that in such scenarios, you do not want to tie a function to a specific application. All applications, should they need that specific function, should be able to re-use it. You can do this by distributing a DLL with the application, but then, you also need to update scripts when a new version is released, and manage the user's access rights within Windows and the database, and people could try to run your application with the old DLLs. There are several disadvantages to this scenario.

Or, you can expose the functionality as a service, where one service is a building block of functionality offered by a central server or cluster of servers. This building block or service can use other services or DLLs. What is important is that the functionality is centrally managed and exposed to possible clients.

Some of the advantages are:

  • By separating groups of functions (like objects) into services, each group of function can be hosted on any server on the network, including the same one, or on multiple servers under load-balancing.
  • Easy to loose-couple services because generally proxy classes are used, so you can get intellisense, but the implementation itself runs on the server.

What is WCF all about then? It's about writing distributed systems and services, and exposing the functionality without being dependant on the method of communication, so that the method of communication can be freely chosen and changed.

WCF Service is a program that exposes a collection of Endpoints. Each Endpoint is a portal for communicating with the world. Communication is handled by WCF, the logic to respond to the messages by the developer. This enables the developer to develop logic that is not dependant on the way it is communicated. E.g.: TCP, Peer-to-Peer, SOAP, REST, whether they are authenticated or secured with certificates or not.

Client is a program that exchanges messages with one or more Endpoints. A Client may also expose an Endpoint to receive Messages from a Service in a duplex message exchange pattern.

message exchange pattern is a pattern of how the client and the service exchange messages. There are a number of message patterns. REST uses normal HTTP, so that's a request-reply pattern: the browser or client requests the page, and the HTTP server returns a reply.

WCF supports other patterns like Datagram (fire-and-forget) and Duplex (two-way). Sessions are also supported.

WCF is extensible: if a protocol is not supported yet, you can inject your own logic almost everywhere in the framework to ensure minimal development costs for changes in protocol, transport method, and the likes. This is called a Behaviour, more on that later.

Traditional WCF in .NET 3.0 did not have RESTful features; these were added in .NET 3.5. These features include AJAX support, Atom and RSS feeds, and more.

This decision tree helps decide what (binding) technology to use for your Endpoints in a non-RESTful scenario (aRESTful scenario would always use the same type of setup for Endpoints, as you will see later).

(Source: http://weblogs.asp.net/spano/archive/2007/10/02/choosing-the-right-wcf-binding.aspx.)

Don't be overwhelmed by this diagram. You will find it useful someday. When choosing a SOAP binding, be careful what you choose, your choice has serious performance implications. Always pick the fastest technology that supports your goals. Prefer two fast, specific Endpoints to one do-everything Endpoint over WS- so make that TCP Endpoint for .NET clients by all means.

That said, WCF is definitely faster than any technology it replaces, including Remoting, ASMX, and most of all, WSE 2.0.WCF is many times faster and results in the same endresult.

For the full performance comparison, see: http://msdn.microsoft.com/en-us/library/bb310550.aspx.

REST Fundamentals

REST is a resource-oriented architecture. The focus lies on the data, not on actions. Content, as we know it in WKB, is a prime example of a resource.

REST is different from SOAP. SOAP is a complex protocol for interoperating, offering many layers of security, versioning and more, and therefore bloats messages by a large amount. Usually, SOAP runs over HTTP, but in WCF, it can run over TCP or other connections too. SOAP bypasses a lot of HTTP semantics and implements them again as its own. REST, on the other hand, is a lightweight, simpler protocol that follows the HTTP way of working and all of its semantics: a header value of GET to retrieve something, PUT or POST to save something, and DELETE to delete a resource. SOAP is more versatile, but very complex and slow. REST is a lot faster, and offers a lot less, and is limited to HTTP.

Endpoints 'R Us

Service Endpoint has:

  • an Address,
  • Binding,
  • and a Contract.

= The ABC of Communication Foundation

The Endpoint's Address is a network address or URI =WHERE.

The Endpoint's Contract specifies what the Endpoint communicates, and is essentially a collection of messages organized in operations that have basic Message Exchange Patterns (MEPs) such as one-way, duplex, and request/reply. Generally, we use an interface with attributes to define a contract =WHAT.

There are several types of contracts:

Service contracts

Describes which operations the client can perform on the service.

  • ServiceContractAttribute

    Applied to the interface.

  • OperationContractAttribute

    Applied to the interface methods you want to expose.

Data contracts

Defines which custom data types are passed to and from the service (high-level).

  • DataContract

    Applied to the class that holds the data.

  • DataMember

    Applied to the class' properties you want to exchange.

- OR -

Message contracts

Interact directly with SOAP messages (low-level). Useful when there is an existing message format we have to comply with.

Fault contracts

Defines how the service handles and propagates errors to its clients.

The Endpoint's Binding specifies how the Endpoint communicates with the world, including things like transport protocol (e.g., TCP, HTTP), encoding (e.g., text, binary), and security requirements (e.g., SSL, SOAP message security) =HOW.

behavior is a class that implements a special interface for "plugging into" the execution process. This is your primaryWCF extensibility and customization point if something is not supported out of the box. =TWEAK WHERE/WHAT/HOW.

There are a few types of behaviours:

Service Behaviour
  • Applies to the entire service runtime in an application
  • Implements IServiceBehavior
Contract Behaviour
  • Applies to the entire contract
  • Implements IContractBehavior
Operation Behaviour
  • Applies to the service operation (think: method call)
  • Implements IOperationBehavior
Endpoint Behaviour
  • Applies to the Endpoints
  • Implements IEndpointBehavior

Creating behaviours is something you can try yourself after this session, or can request another session on. There are three ways:

Writing code
  • Implement the behaviour interface
  • Add your behaviour to the Behaviors collection
  • Start the service host
Decorating with an attribute

(Not for Endpoint behaviours)

  • Implement the behaviour interface
  • Inherit from Attribute
  • Decorate the wanted class with your class
Configuration

(Not for Contract or Operation behaviours)

It's important to note that behaviours are a server-side only thing: the WCF client classes make no use of them.

In many projects, behaviours won't be needed, and all you'll need is the ABC. It's common however to need to tweak things a little. This is where we add another B - ABBC. Almost ABBA, but not quite.

All of this may seem a bit daunting at first. Don't worry about the specifics too much yet. You will need to know more about them when you use SOAP. There is much to know because WCF is so powerful. Simple scenarios don't require you to know everything in-depth as you will see, and if you know some basics, you will get your service running in no time. For REST scenarios, knowing all the details is not needed to get you started, so we won't elaborate on them in this session.

Now, let's get started.

Exploring Your Hosting Options

On the Microsoft .NET platform, you have several types of managed Windows applications that you can create with Microsoft Visual Studio .NET:

  • WinForms applications
  • Console applications
  • Windows services
  • Web applications (ASP.NET) hosted on Internet Information Services (IIS)
  • WCF services inside IIS 7.0, and WAS on Windows Vista or Windows Server 2008

WCF doesn't block you from running your service in any other type of application as long as it provides you with a .NET application domain. It all comes down to the requirements you have for your host. To summarize the options, think about the following three generic categories of hosts for your WCF services:

  • Self-hosting in any managed .NET application
  • Hosting in a Windows service
  • Hosting in different versions of IIS/ASP.NET

We will be self-hosting the service eventually in a console window, creating your own web server (sort of)!

Creating a New Service Library

  • Create a new WCF Service Library.
  • Delete both Service.cs and IService.cs

Why did we start a Service Library only to toss everything out? Some project types add entries to menus. Like in this case, it will add the ability to visually edit the app.config.

Defining and Implementing a Contract

The easiest way to define a contract is creating an interface or a class and annotating it withServiceContractAttribute.

For example:

using System.ServiceModel;
 
//a WCF contract defined using an interface
[ServiceContract]
public interface IMath
{
    [OperationContract]
    int Add(int x, int y);
}

Then, create a class that implements IMath. That class becomes the WCF Service class. For example:

//the service class implements the interface
public class MathService : IMath
{
    public int Add(int x, int y)
    { return x + y; }
}

Defining Endpoints

Endpoints can be defined in code or in config. We'll be using config.

<configuration>
        <system.serviceModel>
               <behaviors>
                       <serviceBehaviors>
                               <behavior name="mexSvcBehaviour">
                                      <serviceMetadata httpGetEnabled="false" 
                                      httpsGetEnabled="false" />
                               </behavior>
                       </serviceBehaviors>
               </behaviors>
               <bindings />
               <services>
                       <service behaviorConfiguration="mexSvcBehaviour" 
                       name="WCFRest.MathService">
                               <endpoint address="http://localhost:41000/MathService/Ep1" 
                               binding="wsHttpBinding"
                                contract="WCFRest.IMath" />
                               <endpoint address="http://localhost:41000/MathService/mex" 
                               binding="mexHttpBinding"
                                bindingConfiguration="" contract="IMetadataExchange" />
                       </service>
               </services>
        </system.serviceModel>
</configuration>

Generally, config is preferred. In some project types, you will find the Edit WCF Configuration menu option by right-clicking on App.config.

Using this editor, you can easily set the options needed for your service. If not present, run C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\SvcConfigEditor.exe on app.config for the same editor.

For now, just copy-paste the App.config from above into yours.

The Test Client

If you run your service, you'll see the test client is automatically configured to run when you run the DLL.

Note: the test client is not useful for REST-tests. If you start a SyndicationLibrary project, IE will be used instead of theWCF Test Client.

Consuming the Service

There are two ways to generate classes:

SvcUtil.exe

It is located here: C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\svcutil.exe. It is a useful tool for generating classes based on WSDL.

Add Service Reference

Like Add Reference, except the menu option just below:

  • Add a new Windows Console project called WCFClient to the solution.
  • Add a service reference to the service in the solution as shown above, and use a namespace you'll remember.
  • Add a normal reference to System.ServiceModel.
  • In static void main, do the following:
internal class Program
{
    /// <summary>
    /// Starts the application
    /// </summary>
    /// <param name="args"></param>
    internal static void Main(string[] args)
    {
        MathClient client = new MathClient();
        Console.WriteLine(client.Add(10, 20));
        Console.ReadKey();
    }
}

Tip: Should your application stop working at a later point because the client cannot figure out what binding to connect to, change the following line:

MathClient client = new MathClient("WSHttpBinding_IMath");

Hosting the Service from the Console

  • Add a new Windows Console project to your solution called WCFHost.
  • Add a reference in the project to your service project.
  • Add a reference to System.ServiceModel.
  • Edit the properties of the WCF library project you referenced, and go to the tab WCF Options. Then, uncheck the following option:

  • Copy the App.config from the WCF library to your WCFHost project.
  • In the static void main method of your WCFHost project (type svm tab tab if you don't have one), do the following:
internal static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    internal static void Main()
    {

        using (ServiceHost serviceHost = 
                 new ServiceHost(typeof(WCFRest.MathService)))
        {
            try
            {
                // Open the ServiceHost to start listening for messages.
                serviceHost.Open();

                // The service can now be accessed.
                Console.WriteLine("The service is ready.");
                Console.WriteLine("Press <ENTER> to terminate service.");
                Console.ReadLine();

                // Close the ServiceHost.
                serviceHost.Close();
            } 
            catch (TimeoutException timeProblem)
            {
                Console.WriteLine(timeProblem.Message);
                Console.ReadLine();
            } 
            catch (CommunicationException commProblem)
            {
                Console.WriteLine(commProblem.Message);
                Console.ReadLine();
            }
        }
    }
}

Now, make sure that both projects run when you start. Right-click the solution and select Properties. In the Startup Project list, change the Action of the WCFClient and the WCFHost to Start.

Then, press the up-arrow button to make sure the WCFHost starts before the WCFClient project.

Run the application. The service should be started, and you should have '30' as a reply from your service.

What's happening?

  • WCFHost is hosting the service in a Windows application, any application can now host services.
  • WCFClient is connecting to that service via a generated proxy class so you can compile and get intellisense.
  • WCFClient calls methods via the proxy, and the proxy transparently calls the service.

Transforming the Service into a REST Web Service

Note: transforming the math service (an action-oriented service that does something), to a REST-service (aresource-oriented service (CRUD on data)) may not be a good practise. What is shown here can hardly be seen as good design, but here goes:

Back to your service project, and to the IMath interface:

  • To see your options, you'll have to first add a reference to System.ServiceModel.Web.
  • Then, change the interface as follows:
[WebInvoke(Method = "GET",
    ResponseFormat = WebMessageFormat.Json,
    BodyStyle = WebMessageBodyStyle.Bare,
    UriTemplate = "?x={x}&y={y}")]
int Add(int x, int y);

Note that you can use numbers and strings in your interface as parameters for the URI template, but only if they're part of the query string. Inside the URL itself, only strings are supported. You can return other classes or accept classes from the HTTP body instead (see later).

  • Add configuration for a binding as shown, and name it webHttpBindingConfig.

  • Under Advanced, define a new service endpoint behaviour, and add the webHttp element. Call itwebHttpEndpointBehaviour.

  • Then finally, add a new service endpoint and fill in the fields as shown below:

  • Save.

Run the application as before, the two applications should still work. While they are running, direct your favourite browser to http://localhost:41000/MathService/web/?x=10&y=20. Notice how this is the URL for the endpoint combined with the URL template for the method call.

This should give you a JSON reply. If you download it, it should contain '30'.

Congratulations on your first WCF REST service. It can add two numbers and tell you what the result is.

Returning Files or Large Amounts of Data

You can also return streams. Streams are recommended for large amounts of data, or for files you have on disk and want to be returned. For example, your Web Service might return links to images, and you need to return the images too.

Add a method to the interface like this:

[OperationContract]
[WebInvoke(Method = "GET",
    ResponseFormat = WebMessageFormat.Json,
    BodyStyle = WebMessageBodyStyle.Bare,
    UriTemplate = "notepad")]
Stream GetStream();

Note: you can also use the WebGet attribute which is specifically meant for GET requests. Try it out with a WebGetattribute instead.

Then, implement the new method in your concrete MathService class:

public Stream GetStream()
{
    return File.OpenRead(@"c:\windows\notepad.exe");
}

Run the application again and download your own Notepad by directing your browser to the following URL:http://localhost:41000/MathService/web/notepad.

Note that there are a few service settings that limit the size of the messages that can be sent or received. On top of that, if you're hosting inside IIS, the IIS server's limits need to be adjusted too in web.config.

More specifically, common settings that you may need to adjust to receive and send large files are the message sizes for the endpoint, the serviceand the client itself (added through built-in behaviours), and possibly for ASP.NET, theHTTP request size.

<httpRuntime maxRequestLength="xxx"/>

Returning a Feed

With WCF, there are two standard feed formats supported:

  • Atom 1.0
  • RSS 2.0

Generating a feed is done with the SyndicationFeed class.

  • Create the SyndicationFeed instance
  • Assign properties and content
  • Return and format it using a formatter
    • Atom10FeedFormatter
    • Rss20FeedFormatter

So let's get started.

Add the following code to your IMath interface:

[WebGet(UriTemplate = "feed?rss={rss}")]
[OperationContract]
SyndicationFeedFormatter GetFeed(bool rss);

Then, implement the new method as follows:

public SyndicationFeedFormatter GetFeed(bool rss)
{
    List<SyndicationItem> items = new List<SyndicationItem>();

    // make link to notepad
    SyndicationItem item = new SyndicationItem();
    item.Title = new TextSyndicationContent
                       ("Notepad on server.");
    item.Links.Add(new SyndicationLink(
       new Uri("http://localhost:41000/MathService/web/notepad")));
    item.PublishDate = DateTime.Now;
    items.Add(item);

    SyndicationFeed feed =
        new SyndicationFeed
            ("Notepad Feed",
            "Feed with links to notepad.exe",
            new Uri("http://www.google.be"), // alternate link is google
            items);
    feed.LastUpdatedTime = DateTime.Now;
    if (rss)
    {
        return new Rss20FeedFormatter(feed);
    }
    return new Atom10FeedFormatter(feed);
}

Run the application. The application works. Direct your browser to get the feed we just made:http://localhost:41000/MathService/web/feed.

Doh! An error occurred, and if you'd step through the method call while debugging, no exception happens at all. The webpage doesn't tell you anything useful. What gives?

Debugging WCF

To find out what's wrong, let's enable a few things we do not want to be active in a production scenario. We'll only edit the app.config of the WCFHost project, not the one that produces our DLL.

First, enable exception information. This is mostly for SOAP.

Edit the service behaviour that's in there by adding the serviceDebug element (the other one is to enable metadatafor the test client, and Add Service Reference to generate classes from).

Then, enable message logging and tracing.

Finally, Save your app.config and run the application again. Use your browser to visit the feed page again:http://localhost:41000/MathService/web/feed.

Then, open the folder of the WCFHost project.

Notice the new files app_tracelog.svclog and app_messages.svclog in the folder.

Open app_tracelog by double-clicking on it.

Microsoft Service Trace Viewer opens. Notice the red lines:

These indicate errors. Let's see what went wrong.

Select the part where the service throws an exception after GetFeed. The exception is displayed right here:

Apparently, we forgot to add the [ServiceKnownType] attribute, because we said we were going to return aSyndicationFeedFormatter, which is the base class of Atom10FeedFormatter and Rss20FeedFormatter. We returned an Atom10FeedFormatter, but the service expected a SyndicationFeedFormatter. TheServiceKnownType attribute allows us to specify derived classes off base classes or interfaces used in our service.

Let's fix that right now.

Looking up the ServiceKnownTypeAttribute class, the example indicates that you place one on top of an interface, your IMath interface: http://msdn.microsoft.com/en-us/library/system.servicemodel.serviceknowntypeattribute.aspx.

Add the attributes to the interface like this:

[ServiceContract]
[ServiceKnownType(typeof(Atom10FeedFormatter))]
[ServiceKnownType(typeof(Rss20FeedFormatter))]
public interface IMath

Close your browser window if it's IE, and start a new one. Revisit the URL to get your feed:http://localhost:41000/MathService/web/feed. You should see your feed operational:

Don't forget to turn message logging and tracing off again, and delete the service log files, or you'll spend minutes parsing the files when you actually need them!

Accepting data

WCF can accept input from JavaScript/XML (in the body) or from the URI. The system looks for all the method parameters that are not used in the URI template in the body. Members from AJAX (out of the box) or WebForms (with WCF REST starter kit on CodePlex: request a session if interested) can be used for communication. For AJAX, XML or JSON can be used to fill in members of classes in the method call.

AJAX

Asynchrnonous Javascript and XML, is used to update web pages without doing a full page postback and refreshing everything, saving processing time and bandwdith when used well. Theoretically, you'd have to use XML, but JSON is more common. AJAX doesn't sound very reliable though.

JSON

JavaScript Object Notation. In short, the way JavaScript writes down object variables.

XML

Extensible Markup Language. Another way of writing objects, in this context.

Note: WCF can cooperate with the ASP.NET ScriptManager and ASP.NET AJAX (if this is of interest to you, request a session). WCF can also cooperate with jQuery, the leading JavaScript library for dynamic page manipulation. Both of these topics are beyond the scope of this session. If it talks XML, SOAP or JSON, they can interoperate.

First, download and install Fiddler from: http://www.fiddler2.com/Fiddler2/version.asp.

Fiddler is an HTTP debugger that all0ws you to craft custom requests - many webmasters think Security Through Obscurity is a good thing, but we're not one of them.

Next, we'll prepare our service.

Create a new class named PostedData like this:

[DataContract]
public class PostedData
{
    [DataMember]
    public string FirstName { get; set; }
    [DataMember]
    public string LastName { get; set; }
}

Add the following to your service interface:

[OperationContract]
[WebInvoke(Method = "POST",
    ResponseFormat = WebMessageFormat.Json,
    BodyStyle = WebMessageBodyStyle.WrappedRequest,
    UriTemplate = "data")]
void PostData(PostedData data);

Then, implement the method in your service with the following code:

public void PostData(PostedData data)
{
    if (data == null)
    {
        Console.WriteLine("null");
        return;
    }

    Console.WriteLine("Received: " + data.FirstName + 
                      " " + data.LastName);
}

Run your application.

Fire up Fiddler and create a custom request as shown:

And press the button labeled Execute. You may be wondering why we've wrapped the sent data class with theFirstName and LastName properties in a parent object with a property called 'data'.

{"data":{"LastName":"Simpson", "FirstName": "Bart"}}

wrapped parent / object sent

This is because we've set the bodystyle to WrappedRequest in the interface attribute for the method call, which can be used to send in multiple parameters.

If we set the bodystyle to Bare, we would just send the object and not a wrapper object:

{"LastName":"Simpson", "FirstName", "Bart"}

Try it out now!

Sending JSON with C#

Working with REST services cannot be done with Add Service Reference like you could with SOAP. However, there are classes to help you get where you need to:

  • WebHttpRequest

    Makes HTTP requests where you place the body contents in a Stream and read the reply from another Stream.

  • DataContractJsonSerializer

    Serializes a type to JSON

Add a new Windows Console project called WCFJsonPost to the solution. Set it up to start when you run the application:

Add a reference in your project to System.ServiceModel.Web and System.Runtime.Serialization.

Then, add the following code to your Program class:

internal class Program
{
    internal static void Main(string[] args)
    {
        Thread.Sleep(10000);
        TestWcfRestPost();
    }

    private static void TestWcfRestPost()
    {
        // create the serializer that saves classes as JSON
        DataContractJsonSerializer serializer = 
           new DataContractJsonSerializer(typeof(PostedData));
        PostedData dataToPost = new PostedData
                                    {
                                        FirstName = "Bart",
                                        LastName = "Simpson"
                                    };
        // set up our request
        HttpWebRequest request = 
          (HttpWebRequest)HttpWebRequest.Create(
          @"http://localhost:41000/MathService/web/data");
        request.Accept = "*/*";
        // we're going to post JSON
        request.ContentType = "application/json";
        request.Method = "POST";
        using (Stream stream = request.GetRequestStream())
        {
            // send data
            serializer.WriteObject(stream, dataToPost);
            stream.Flush();
        }

        // get the response
        HttpWebResponse response = (HttpWebResponse)request.GetResponse();
        using(Stream stream = response.GetResponseStream())
        {
            using(TextReader reader = new StreamReader(stream))
            {
                Console.WriteLine(reader.ReadToEnd());
            }
        }
    }
}

Run the application to see the same results as the ones you had with Fiddler, that is, if you have put the bodystyle toBare at the end of the previous topic.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值