Share via


WCF, IIS, and REST

Overview

Download the code: http://gallery.technet.microsoft.com/WCF-REST-and-IIS-130b242c

While I was performing mindless chores around the house this morning I came to the realization that they were not that mindless. After all, I was contemplating REST. Not the type of REST that has you falling into a hole in the couch that you can’t get out of for a few hours but rather Representational State Transfer. Why should I be thinking about REST? I think the better question is why not? REST is certainly gathering momentum and I have noticed this while loitering around the WCF Forum on MSDN. The usual question seems to be “How do I upload a file using REST?”. Therefore, that will be the topic of discussion today.

I intend to provide a fully functional implementation of a REST service that lives in IIS as an Application. It may seem elementary to do so but most of the examples are quite difficult to work with and that’s even for a seasoned WCFaholic like myself.

The implementation for our REST service will be done through a configuration file. Yes, I know, Microsoft has put in a lot of effort creating the WebServiceHost and WebServiceHostFactory classes to make our lives easier but if you take the time and get a deeper understanding of WCF configuration, I guarantee you will be weighing up the advantages of imperative or configuration programming more carefully.

The reason we need to upload files today is because we love to go fishing. When we go fishing we like to take pictures of our adventures. When we come home, we want to store those pictures on the internet so our families in other countries can get a better idea of our endeavours (Also to make them jealous of the fresh seafood Australia has to offer).  

ATTENTION, ATTENTION, ATTENTION

The code provided in this article is for demonstration purposes only. I would not recommend that you deploy such code directly into a production system. There is no error handling and nor is there any regard for proper coding practice or standards. 

 

Figure 1: My nephew onto a good size kingfish (The type of stuff we want uploaded)

Creating the REST Service

The reason we are choosing REST today is that all of our functionality is based around a resource. We will be adding new images using POST and retrieving images using GET. If we required operational functionality in the form of operations like Crop Image or Watermark image I probably would have used an operation centric approach such as SOAP. Hence there lies the difference. REST is resource centric, SOAP is operation centric.

Enough talk, more action. So, let’s create the REST service.

    -          Open Visual Studio 2010 (RunAs Administrator so you can create a website)
    -          File->New Website. This will open the Visual Studio Templates page
    -          Select WCF Service and name your site http://localhost/FishImagesService and click OK
    -          The project will be created in VB.NET (I hope to have a C# sample soon)

At this point Visual Studio will create the website as a Web Application and generate a whole bunch of boiler plate code.  First of all, what I would like you to do is delete IService.vb and Service.vb. We won’t be needing those. Now, to add our class:

    -          Right Click the App_Code folder and select ‘Add New Item…’ from the context menu
    -          When the ‘Add New Item’ window appears select Class 
    -          Name the Class FishImagesService.vb and click OK

Now that we have our class file, paste in the following Service Contract over whatever little code was generated by Visual Studio:

Imports System.IO
Imports System.Net
Imports System.ServiceModel.Channels
 
<ServiceContract()>
Public Interface IFishImagesService
    <OperationContract()>
    <WebGet(UriTemplate:="FishImagesGET")>
    Function FishImagesGET() As Message
    <OperationContract()>
    <WebGet(UriTemplate:="FishImagesGETFile/{filename}")>
    Function FishImagesGETFile(filename As String) As Message
    <OperationContract()>
    <WebInvoke(Method:="POST",
               UriTemplate:="FishImagesPOST/{filename}")>
    Sub FishImagesPOST(ByVal filename As String, file As Stream)
End Interface

Let’s have a closer look. We have two WebGet methods. First, FishImagesGET will return and xml representation of what files we have already uploaded. Second, FishImagesGETFile is the method we will call to send the actual image to the calling application. The calling application in this instance will be an ASP.NET Website. We will investigate this a little later in this article. Last, we have FishImagesPOST. This method will be invoked when we upload a file. What is really interesting about the WebGet methods is that they return a Message object back to the calling application. The reasons for this will become clearer later in this article. So apart from that, it’s a pretty simple service contract, right?

OK, now it’s time for the implementation. Copy and paste this code directly under the service contract:

<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerCall)>
 
Public Class FishImagesService
 
    Implements IFishImagesService
 
    Implements IDisposable
 
    Private filestream As FileStream
 
    Public Function FishImagesGET() As System.ServiceModel.Channels.Message Implements IFishImagesService.FishImagesGET
 
        Dim dir As New DirectoryInfo(fishImagesFolder)
 
        Dim files As XDocument = <?xml version="1.0"?><files><%= From f In dir.GetFiles("*.jpg") Select <file><%= f.Name %></file> %></files>
 
        WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml"
 
        Return WebOperationContext.Current.CreateTextResponse(files.ToString)
 
    End Function
 
    Public Function FishImagesGETFile(filename As String) As System.ServiceModel.Channels.Message Implements IFishImagesService.FishImagesGETFile
 
        filestream = New FileStream(fishImagesFolder() & filename, FileMode.Open, FileAccess.Read, FileShare.Read)
 
        Return WebOperationContext.Current.CreateStreamResponse(fileStream, "image/jpeg")
 
    End Function
 
    Public Sub FishImagesPOST(filename As String, file As System.IO.Stream) Implements IFishImagesService.FishImagesPOST
 
        Dim filestream As New FileStream(fishImagesFolder() & filename, FileMode.Create)
 
        Dim filebuf(WebOperationContext.Current.IncomingRequest.ContentLength) As Byte
 
        Dim bytesRead As Integer = file.Read(filebuf, 0, filebuf.Length)
 
        filestream.Write(filebuf, 0, bytesRead)
 
        filestream.Flush()
 
        filestream.Close()
 
    End Sub
 
    Private Function fishImagesFolder() As String
 
        Return ConfigurationManager.AppSettings("FishImagesLocation")
 
    End Function
 
    Private disposedValue As Boolean
 
    Protected Overridable Sub Dispose(disposing As Boolean)
 
        If Not Me.disposedValue Then
 
            If disposing Then
 
                If Not IsNothing(filestream) Then
 
                    filestream.Close()
 
                End If
 
            End If
 
        End If
 
        Me.disposedValue = True
 
    End Sub
 
    Public Sub Dispose() Implements IDisposable.Dispose
 
        Dispose(True)
 
        GC.SuppressFinalize(Me)
 
    End Sub
 
End Class

So we’re half way there (For the REST service anyway). I should probably give some details about the above implementation.

Method

Description

FishImagesGET

Simply enumerates the folder structure and generates an XDocument using LINQ. The XDocument is converted to a string and returned to the calling application using the WebOperationContext by creating a Text response. The content type has been set to application/xml so that it is interpreted as an xml document when it arrives on the other side of the call. This is the reason why our WebGet methods return a Message. When manipulating the message using WebOperationContext, the return type must be Message.

FishImagesGETFile

Now this is awesome. By using the WebOperationContext I am able to return a stream to the calling application with a content type of image/jpeg. This means that the URL for this operation can be set to the ImageUrl property of the Image control on the Webform. This will populate the Image control with the image. And people say white man can’t jump. I guarantee you that when I figured this out I could have jumped over my house.

FishImagesPOST

Finally the POST operation. Notice how there are two parameters on this method. One parameter will come from the URL specified by the calling application and the stream will arrive as part of the content. Although WCF hides this from us, that is what is actually happening. Here, we just take the stream and save it to a file using the file name provided.

 

You will notice that the message signatures for REST go against everything that is WCF. What I mean by this, and in particular the POST signature, is that it includes a value type (Although string is not a value type in .NET we will still refer to it that way), a Stream type and a Message type. WCF does not let you mix these when dealing with a standard SOAP service but with REST this is possible.

We now have to make changes to our Service.svc file. For those of you who don’t know, the Service.svc file is by which we access our REST service. It stores a pointer to our service class and code files. Open the Service.svc file that is on the root level of our site and replace all of the code with the following:

<%@ ServiceHost Language="VB" Debug="true" Service="FishImagesService" CodeBehind="~/App_Code/FishImagesService.vb" %>

Note: I have set debug to true here so we can debug our service

OK, last but not least is our web.config file. This file stores our endpoint for the REST service to say the least. There are also sections for the service, binding configurations and behaviour configurations. Copy and paste the following directly over the current content of the file:

<?xml version="1.0"?>
 
<configuration>
 
  <appSettings>
 
    <add key="FishImagesLocation" value="c:\temp\"/>
 
  </appSettings>
 
  <system.web>
 
    <compilation debug="true" strict="false" explicit="true" targetFramework="4.0" />   
 
    <pages>
 
      <namespaces>
 
        <add namespace="System.Runtime.Serialization"/>
 
        <add namespace="System.ServiceModel"/>
 
        <add namespace="System.ServiceModel.Web"/>
 
      </namespaces>
 
    </pages>      
 
  </system.web>
 
  <system.serviceModel>
 
    <services>
 
      <service name="FishImagesService">
 
        <endpoint address=""
 
                  binding="webHttpBinding"
 
                  contract="IFishImagesService"
 
                  behaviorConfiguration="webHttpBH"
 
                  bindingConfiguration="webHttpBG"/>
 
      </service>
 
    </services>
 
    <bindings>
 
      <webHttpBinding>
 
        <binding name="webHttpBG"
 
                 maxReceivedMessageSize="100000" />
 
      </webHttpBinding>
 
    </bindings>
 
    <behaviors>
 
      <endpointBehaviors>
 
        <behavior name="webHttpBH">
 
          <webHttp helpEnabled="true" />
 
        </behavior>
 
      </endpointBehaviors>
 
      <serviceBehaviors>
 
        <behavior>
 
          <serviceMetadata httpGetEnabled="true"/>
 
          <serviceDebug includeExceptionDetailInFaults="false"/>
 
        </behavior>
 
      </serviceBehaviors>
 
    </behaviors>
 
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
 
  </system.serviceModel>
 
  <system.webServer>
 
    <modules runAllManagedModulesForAllRequests="true"/>   
 
  </system.webServer>
 
</configuration>

There’s just one thing really worth mentioning here. I have set the MaxReceivedMessageSize to something astronomical. You really need to pay attention to this attribute as it is usually the cause of more problems than anything else. The default is 64K so be careful.

Well that’s it for our service. We’ve defined our contract, implemented our service, modified our Service.svc file and updated our configuration file. Our REST service is ready to go.

Creating the APS.NET Website

For our REST service to be of any value, we need to create an application that consumes it. The choice for today’s example will be an ASP.NET Website. You can create any type of application to consume this service. A question that comes up a lot is “How do I access a file from the client using VBScript or Javascript”. The short answer is that you cannot. The long answer is that you can by means of ActiveX objects and if you are running a truly internet based application then the configuration for this is a nightmare (Firefox lets you do this but I have only read about it). So I’m not even going to both explaining.

The way we will send our data to the REST service will be by using the FileUpload Control. Although it may seem superfluous to do so as we can just save the file on the server using the FileUpload Control functionality. The purpose of this article is to demonstrate how to send files to REST service, so don’t let twisted logic get in the way of what is truly a great example.

OK, I’m rambling again. Let’s get down to business.

    -          Open Visual Studio 2010 (RunAs Administrator so you can create a website)
    -          File->New Website. This will open the Visual Studio Templates page
    -          Select Empty Website and name your site http://localhost/FishImagesWebsite and click OK
    -          The project will be created in VB.NET (I hope to have a C# sample soon)

We need to add our Default.aspx page to the site.

    -          Right Click the site in Solution Explorer and select ‘Add New Item…’ from the context menu
    -          When the ‘Add New Item’ window appears select WebForm and click OK
    -          The default page name will be Default.aspx. Just what we need.

Right click the Default.aspx page in Solution Explorer and select ‘View Markup’ from the context menu. Remove all of the code on the page and replace it with the following:

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
 
<head runat="server">
 
    <title></title>
 
    </head>
 
<body>
 
    <form id="form1" runat="server">
 
    <div>   
 
        <asp:DataList ID="DataList1" runat="server"
 
        Height="88px" RepeatColumns="3"
 
            BackColor="White" BorderColor="#CCCCCC"
 
            BorderStyle="None" BorderWidth="1px"
 
            CellPadding="4" GridLines="Horizontal" ForeColor="Black">
 
            <FooterStyle BackColor="#CCCC99"
 
            ForeColor="Black" />
 
            <HeaderStyle BackColor="#333333"
 
            Font-Bold="True" ForeColor="White" />
 
            <ItemTemplate>
 
                <asp:Image ID="Image1"
 
                ImageUrl='<%# DataBinder.Eval(Container.DataItem, "ImageValue")%>'
 
                runat="server" Height="240px" Width="320px" />               
 
            </ItemTemplate>
 
            <SelectedItemStyle BackColor="#CC3333"
 
            Font-Bold="True" ForeColor="White" />
 
        </asp:DataList>
 
        <br />
 
        <asp:FileUpload ID="FileUpload1" runat="server" />
 
        <asp:Button ID="buttonUpload" runat="server"
 
        Height="22px" Text="Upload"
 
            style="margin-top: 1px" />   
 
    </div>
 
    </form>
 
</body>
 
</html>

Cool, that was easy.  Right click anywhere in the page markup and select ‘View Code’ from the context menu. This will take us into the code behind for the Default.aspx file. Remove any of the code that was generated by Visual Studio and replace it with the following:

Imports System.Net
 
Imports System.IO
 
Imports System.Data
 
Partial Class _Default
 
    Inherits System.Web.UI.Page
 
    Private fishImages As New FishImages
 
    Protected Sub buttonUpload_Click(sender As Object, e As System.EventArgs) Handles buttonUpload.Click
 
        FishImagesPOST()
 
    End Sub
 
    Protected Sub Page_Init(sender As Object, e As System.EventArgs) Handles Me.Init
 
        Me.EnableViewState = False
 
    End Sub
 
    Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
 
        If Not Page.IsPostBack Then
 
            FishImagesGET()
 
        End If
 
    End Sub
 
    Private Sub FishImagesGET()
 
        Dim requestUrl As String = "http://localhost/FishImagesService/Service.svc/FishImagesGET"
 
        Dim request As HttpWebRequest = HttpWebRequest.Create(requestUrl)
 
        request.Method = "GET"
 
        request.ContentType = "application/xml"
 
        Dim xmlfileString As String = New StreamReader(request.GetResponse.GetResponseStream).ReadToEnd
 
        Dim xmlfile As XDocument = XDocument.Parse(xmlfileString)
 
        Dim files = From f In xmlfile.Element("files").Elements Select f
 
        For Each file In files
 
            fishImages.AddFish(file.Value)
 
        Next
 
        DataList1.DataSource = fishImages.FishData
 
        DataList1.DataBind()
 
    End Sub
 
    Private Sub FishImagesPOST()
 
        Dim requestUrl As String = "http://localhost/FishImagesService/Service.svc/FishImagesPOST/" & FileUpload1.FileName
 
        Dim request As HttpWebRequest = HttpWebRequest.Create(requestUrl)
 
        request.Method = "POST"
 
        request.ContentType = "text/plain"
 
        Dim fileToSend() As Byte = FileUpload1.FileBytes
 
        request.ContentLength = fileToSend.Length
 
        Dim requestStream As Stream = request.GetRequestStream
 
        requestStream.Write(fileToSend, 0, fileToSend.Length)
 
        requestStream.Close()
 
        FishImagesGET()
 
    End Sub
 
End Class
 
Public Class FishImages
 
    Private _fishData As DataView
 
    Private dt As New DataTable()
 
    Private dr As DataRow
 
    Public Sub New()
 
        dt.Columns.Add(New DataColumn("ImageValue", GetType(String)))
 
    End Sub
 
    Public Sub AddFish(filename As String)
 
        dr = dt.NewRow()
 
        Dim img As String
 
        img = "http://localhost/FishImagesService/Service.svc/FishImagesGETFile/" & filename
 
        dr(0) = img
 
        dt.Rows.Add(dr)
 
        _fishData = New DataView(dt)
 
    End Sub
 
    Public Function FishData() As DataView
 
        Return _fishData
 
    End Function
 
End Class

After review of the code you will find that there is a method to GET resources from the REST service (FishImagesGET) and a method to POST resources to the REST service (FishImagesPOST). You will also notice another class in the picture named FishImages. We use this class as a data source for our DataList on the Default.aspx page.

Last but not lest is the configuration file for the Website.

<?xml version="1.0"?>
 
<configuration>
 
  <system.web>
 
    <compilation debug="true" strict="false"
 
                 explicit="true" targetFramework="4.0"/>
 
    <httpRuntime maxRequestLength="100000"/>
 
  </system.web>
 
</configuration>

It’s pretty obvious that there is only one thing worth mentioning here and that is maxRequestLength in the <httpRuntime> element. This ensures that the message will accept a decent size file that gets posted to our REST service. OK, we’re done! Now let’s test our implementation.

Testing the REST service using a Website

Open Internet Explorer and type in the following URL:

 http://localhost/FishImagesWebsite

You will see the following screen (pretty simple), Simply click the ‘Browse…’ button to select a file then click ‘Upload’ to upload the file to the REST service:

The same page after images have been uploaded:


**
**

Conclusion

Hopefully after today you have a better understanding of REST. We saw how to add a REST service to IIS and consume that service using a Website.