Using the WebOperationContext
One common thing to do when hosting services using the WebHttpBinding binding is to read or write to the HTTP context. This can be done using the WebOperationContext class. There are a variety of reasons to access the HTTP context. You might want to read custom authentication or authorization headers, control caching, or set the content type, for example.Figure 13.3 shows a Web application that displays wallpaper images on the current machine. The entire application is built using a WCF service and is accessible using any Web browser.
Figure 13.3 Wallpaper Web application
Listing 13.12 shows code for the WallpaperService service. There is an Images operation that displays an HTML page of all images. This operation sets the ContentType header so that the browser interprets the output as HTML. It also sets the Cache-Control header so that additional images can be added to the application without the browser caching the display. Finally, there is an Image operation that returns an image to the browser. This operation sets both the ContentType and ETag header.
NOTE: Taking the .svc Out of REST
WCF Services hosted in IIS use the .svc extension. This does not follow common REST URI naming practices. For example, the service in Listing 13.12 is accessed using the following URI:http://localhost/Wallpaper/WallpaperService.svc/imagesYou can remove the .svc extension by using an ASP.NET HttpModule (with IIS 7.0 only) to call HttpContext.RewritePath to modify the URI. This would allow the URI to take the following form:http://localhost/Wallpaper/WallpaperService/images
using System;using System.Collections;using System.Collections.Generic;using System.IO;using System.Runtime.Serialization;using System.Text;using System.Web.UI;using System.Web.UI.WebControls;using System.ServiceModel;using System.ServiceModel.Activation;using System.ServiceModel.Web;
namespace EssentialWCF{ [DataContract] public class Image { string name; string uri;
public Image() { }
public Image(string name, string uri) { this.name = name; this.uri = uri; }
public Image(string name, Uri uri) { this.name = name; this.uri = uri.ToString(); }
[DataMember] public string Name { get { return this.name; } set { this.Name = value; } } [DataMember] public string Uri { get { return this.uri; } set { this.uri = value; } } }
[ServiceContract] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)] public class WallpaperService { private static UriTemplate ImageUriTemplate = new UriTemplate("/image/{name}"); private string ImagePath { get { return @"C:\Windows\Web\Wallpaper"; } }
private Image GetImage(string name, Uri baseUri) { return new Image(name, ImageUriTemplate.BindByPosition(baseUri, new string[] { name })); }
private void PopulateListOfImages(List<Image> list, Uri baseUri) { System.Web.HttpContext ctx = System.Web.HttpContext.Current; DirectoryInfo d = new DirectoryInfo(ImagePath); FileInfo[] files = d.GetFiles("*.jpg");
foreach (FileInfo f in files) { string fileName = f.Name.Split(new char[] { '.' })[0]; string etag = fileName + "_" + f.LastWriteTime.ToString(); list.Add(GetImage(fileName, baseUri)); } }
[OperationContract] [WebGet(UriTemplate = "/images")] public void Images() { WebOperationContext wctx = WebOperationContext.Current; wctx.OutgoingResponse.ContentType = "text/html"; wctx.OutgoingResponse.Headers.Add("Cache-Control", "no-cache");
Uri baseUri = wctx.IncomingRequest.UriTemplateMatch.BaseUri; List<Image> listOfImages = new List<Image>(); PopulateListOfImages(listOfImages, baseUri);
TextWriter sw = new StringWriter(); Html32TextWriter htmlWriter = new Html32TextWriter(sw);
htmlWriter.WriteFullBeginTag("HTML"); htmlWriter.WriteFullBeginTag("BODY"); htmlWriter.WriteFullBeginTag("H1"); htmlWriter.Write("Wallpaper"); htmlWriter.WriteEndTag("H1"); htmlWriter.WriteFullBeginTag("TABLE"); htmlWriter.WriteFullBeginTag("TR");
int i = 0; Image image; while (i < listOfImages.Count) { image = listOfImages[i];
htmlWriter.WriteFullBeginTag("TD"); htmlWriter.Write(image.Name); htmlWriter.WriteBreak(); htmlWriter.WriteBeginTag("IMG"); htmlWriter.WriteAttribute("SRC", image.Uri); htmlWriter.WriteAttribute("STYLE", "width:150px;height:150px"); htmlWriter.WriteEndTag("IMG"); htmlWriter.WriteEndTag("TD");
if (((i + 1) % 5) == 0) { htmlWriter.WriteEndTag("TR"); htmlWriter.WriteFullBeginTag("TR"); } i++; } htmlWriter.WriteEndTag("TR"); htmlWriter.WriteEndTag("TABLE"); htmlWriter.WriteEndTag("BODY"); htmlWriter.WriteEndTag("HTML");
System.Web.HttpContext ctx = System.Web.HttpContext.Current; ctx.Response.Write(sw.ToString()); }
[OperationContract] [WebGet(UriTemplate = "/image/{name}")] public void GetImage(string name) { WebOperationContext wctx = WebOperationContext.Current; wctx.OutgoingResponse.ContentType = "image/jpeg";
System.Web.HttpContext ctx = System.Web.HttpContext.Current; string fileName = null; byte[] fileBytes = null; try { fileName = string.Format(@"{0}\{1}.jpg", ImagePath, name); if (File.Exists(fileName)) { using (FileStream f = File.OpenRead(fileName)) { fileBytes = new byte[f.Length]; f.Read(fileBytes, 0,Convert.ToInt32(f.Length)); } } else wctx.OutgoingResponse.StatusCode =System.Net.HttpStatusCode.NotFound; } catch { wctx.OutgoingResponse.StatusCode =System.Net.HttpStatusCode.NotFound; }
FileInfo fi = new FileInfo(fileName); wctx.OutgoingResponse.ETag = fileName + "_" +fi.LastWriteTime.ToString(); ctx.Response.OutputStream.Write(fileBytes, 0,fileBytes.Length); } }}
The following configuration in Listing 13.13 is used to host the WallpaperService service. The service is hosted using the WebHttpBinding binding and the WebHttpBehavior endpoint behavior.
<system.serviceModel> <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/> <services> <service name="EssentialWCF.WallpaperService" behaviorConfiguration="MetadataBehavior"> <endpoint address="" behaviorConfiguration="WebBehavior" binding="webHttpBinding" contract="EssentialWCF.WallpaperService"/> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <behaviors> <endpointBehaviors> <behavior name="WebBehavior"> <webHttp /> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior name="MetadataBehavior"> <serviceMetadata httpGetEnabled="true" httpGetUrl="" /> </behavior> </serviceBehaviors> </behaviors></system.serviceModel>
Listing 13.14 shows the .svc file used to host the WallpaperService in IIS.
<%@ ServiceHost Language="C#" Debug="true"Service="EssentialWCF.WallpaperService"CodeBehind="~/App_Code/WallpaperService.cs" %>