I ran into a situation with the following network topology:
The intranet server served PDF documents via IIS, but was only accessible from the WCF server - which, for technical and political reasons, could not run in IIS but had to be a Windows Managed service that hosted a WCF service. Yet I needed to be able to download files from the
intranet server via an ordinary URL.
It turns out that you can write a WCF service mimicking a HTTP server pretty easily. If you can utilize .NET 3.5SP1, that is.
1. Data contract
There are few key points to the contract:
- Make a reference to System.ServiceModel.Web.dll
- Add a "using System.ServiceModel.Web"
- Define a method returning a Stream
- Decorate that method with a WebGet attribute
Your datacontract could look like this:
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace ProxyService
{
///
/// Service contract for a proxy that forwards a http request
[ServiceContract]
public interface IHttpProxy
{
[OperationContract, WebGet]
Stream GetProxyRequest(string target);
}
}
2. Implementation class
This is basically a modified version of a solution
based upon an ASP.NET page I described before.using System.IO;
using System.Net;
using System.ServiceModel.Web;
using System.Web;
namespace ProxyService
{
///
/// A proxy that forwards a http request
///
public class HttpProxy : IHttpProxy
{
public Stream GetProxyRequest(string target)
{
var urlToLoadFrom = HttpUtility.UrlDecode(target);
HttpWebRequest webRequest =
HttpWebRequest.Create(urlToLoadFrom) as HttpWebRequest;
// Important! Keeps the request from blocking after the first
// time!
webRequest.KeepAlive = false;
webRequest.Credentials = CredentialCache.DefaultCredentials;
using (var backendResponse =
(HttpWebResponse)webRequest.GetResponse())
{
using (var receiveStream =
backendResponse.GetResponseStream())
{
var ms = new MemoryStream();
var response =
WebOperationContext.Current.OutgoingResponse;
// Copy headers
// Check if header contains a contenth-lenght since IE
// goes bananas if this is missing
bool contentLenghtFound = false;
foreach (string header in backendResponse.Headers)
{
if (string.Compare(header,
"CONTENT-LENGTH", true) == 0)
{
contentLenghtFound = true;
}
response.Headers.Add(header,
backendResponse.Headers[header]);
}
// Copy contents
var buff = new byte[1024];
var length = 0;
int bytes;
while ((bytes = receiveStream.Read(buff, 0, 1024)) > 0)
{
length += bytes;
ms.Write(buff, 0, bytes);
}
// Add contentlength if it is missing
if (!contentLenghtFound) response.ContentLength = length;
// Set the stream to the start
ms.Position = 0;
return ms;
}
}
}
}
}
3. Configuration settings
To get it all to work, you will need some configuration settings in the App.config of the hosting application:
<system.serviceModel>
<services>
<service name="ProxyService.HttpProxy" >
<endpoint address="" binding="webHttpBinding"
behaviorConfiguration="WebHttpBehavior"
contract="ProxyService.IHttpProxy" >
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost:8002/ProxyService.HttpProxy" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="WebHttpBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Notice the
endpointBehaviors section: this is really important to get the stuff to work.
4. Using the proxy
You can now simply enter "http://yourhost:8002/ProxyService.HttpProxy/GetProxyRequest?target=
urlencodedurl" in your browser, and the WebGet attributes will automatically make the GetProxyRequest method get called with the value of target as value for the target parameter. The code of the proxy expects target to contain an URLEncoded URL (use HttpUtility.UrlEncode to encode the actual url to something that can be passed as a parameter on an URL).
Concluding remarks
The magic of the WebGet attribute is barely scratched by this example, but it makes creating REST services with WCF a real piece of cake. I am not sure if it was intended to be used this way, but it sure works like hell ;-)
5 comments:
Cool as always!
I can't get it to work - no matter what changes I make, it just won't load the page via the url!
Can I send you my project? It's just a few classes - exactly as described in the blog post. I am sure I am just missing something small...
Thank you!
Sure you can! Please email me at joost dot van dot schaik at gmail dot com
Nice article. I went word by word in web.config and don't know what I'm missing. getting 404 when using url.
Upon more tries I was able to get an laternatice url to work. If I specify
Service1.svc/operation
it works.
But I was hoping to specify something like myownservicename.myownname/operation.
Thanks.
Post a Comment