Replacing the “You have created a service” message

September 4, 2011

Introduction

You all know the default page you get when you enter a WCF service’s URL in a browser (e.g. http://localhost/Service.svc). You are greeted by a blue and white (mostly white) screen that informs you that you have just created a service.

You have created a service

(Sorry for the Dutch localization in the screenshot.)

So apart from informing you that you have created a service (handy if you suffer from alzheimer), it also tells you where you can find the WSDL (just append ?wsdl), how to generate proxy classes and how to use these proxies.

That’s all fine, but you know all of this stuff already. Besides, you don’t want to display this message on public-facing services. Time to get rid of it.

Table Of Contents

Hello, World!

Instead of the default generated landing page which is displayed when you enter the service’s address in a browser I want to display a custom page. To showcase the custom page I created a simple “Hello, World!”-isch service.

[ServiceContract]
public interface IHelloWorld
{
    [OperationContract]
    string SayHi(string name);
}

The service’s implementation could not be any simpler.

public class HelloWorld : IHelloWorld
{
    public string SayHi(string name)
    {
        return String.Format("Hello, {0}.", name);
    }
}

Let’s add some configuration to the application’s configuration file (Web.config / app.config).

<system.serviceModel>
  <services>
    <service name="WcfServiceLibrary.HelloWorld">
      <endpoint address="HelloWorld"
                binding="basicHttpBinding"
                contract="WcfServiceLibrary.IHelloWorld"/>
      <endpoint address="mex" 
                binding="mexHttpBinding" 
                contract="IMetadataExchange" />
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:8732/Service"/>
        </baseAddresses>
      </host>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <serviceMetadata httpGetEnabled="true"/>
        <serviceDebug includeExceptionDetailInFaults="false"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

If you navigate to the service’s address (http://localhost:8732/HelloWorld/) you get the default landing page.

WCF Service - Default Landing Page

Top of page

Service Metadata

Let’s stop exposing the metadata first. Just set the httpGetEnabled property of your service behavior to false.

<configuration>
  <system.serviceModel> 
    <behaviors>
      <serviceBehaviors>
        <behavior name="...">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Well, that was easy. If you navigate to the address’ URL now, you’ll still see the same page, but this time no URL pointing to the service’s WSDL. “Ha…I’ll manually append ‘?wsdl’ to the URL” you say? Try it, you’ll get a blank page and nothing more. While you are it, at also remove the metadata exchange (mex) endpoint from the service.

Top of page

Unmatched Message Handler

Let’s create a custom landing page. To do so, you need to create a new service contract called ILandingPage.

[ServiceContract]
public interface ILandingPage
{
    [OperationContract(Action="*", ReplyAction="*")]
    Message Index();
}

This service operation handles all messages that cannot be directed to a service operation. This is done by specifying an asterisk (*) as the value for the Action property of the OperationContract attribute.

This is also called an unmatched message handler. One of the conditions is that it can only take a parameter of the type Message and either returns void or also an instance of the Message type. Here we’re only interested in the return message which represents the custom landing page.

The implementation for this new service contract is quite simple. Just have the service for which you want to create a custom landing page implement it.

public class HelloWorld : IHelloWorld, ILandingPage
{
    //...

    public Message Index()
    {
        return new LandigePageMessage();
    }
}

Just have it return a new descendant of the Message type which represents your custom landing page. In this case I called it LandingPageMessage.

Top of page

Custom Landing Page

Time to create the custom Message descendant for the custom landing page.

public class LandingPageMessage: Message
{
    private readonly MessageHeaders _headers;
    private readonly MessageProperties _properties;

    public LandigePageMessage()
    {
        this._headers = new MessageHeaders(MessageVersion.None);
        this._properties = new MessageProperties();
    }

    public override MessageHeaders Headers
    {
        get { return this._headers; }
    }

    public override MessageProperties Properties
    {
        get { return this._properties; }
    }

    public override MessageVersion Version
    {
        get { return this._headers.MessageVersion; }
    }

    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        writer.WriteStartElement("HTML");
        writer.WriteStartElement("HEAD");
        writer.WriteStartElement("BODY");
        writer.WriteStartElement("SPAN");
        writer.WriteString("This is a test page.");
        writer.WriteEndElement();
        writer.WriteEndElement();
        writer.WriteEndElement();
        writer.WriteEndElement();
    }
}

The new LandingPageMessage type implements all of the abstract members of the Message type. This is quite straightforward. Only the OnWriteBodyContents(…) method contains a bit of logic. Here you can generate some HTML output using the XmlDictionaryWriter object which is passed into this method.

Time to update the application’s configuration file. First add a new endpoint to the service.

<service name="WcfServiceLibrary.HelloWorld">
  <endpoint address="HelloWorld" 
            binding="basicHttpBinding" 
            contract="WcfServiceLibrary.IHelloWorld"/>
  <endpoint address="" 
            binding="customBinding" 
            bindingConfiguration="landingPage" 
            contract="WcfServiceLibrary.ILandingPage"/>
  <host>
    <baseAddresses>
      <add baseAddress="http://localhost:8732/Service"/>
    </baseAddresses>
  </host>
</service>

As you can see the new endpoint uses a custom binding, which is declared as follows:

<bindings>
  <customBinding>
    <binding name="landingPage">
      <textMessageEncoding messageVersion="None"/>
      <httpTransport/>
    </binding>
  </customBinding>
</bindings>

Top of page

Service Host

To test the “Hello, World!” service I quickly created a console application to act as the service host. Apart from an application configuration file (App.config) which contains the configuration code listed so far, it only contains the following lines of code:

class Program
{
    static void Main()
    {
        var service = new ServiceHost(typeof (HelloWorld));
        service.Description.Behaviors.Remove<ServiceDebugBehavior>();
        service.Open();

        Console.WriteLine("Service started.");
        Console.ReadKey();
    }
}

The ServiceDebugBehavior is removed from the behaviors of the service to ensure the custom page will be shown.

After you start the host application and open the service’s URL (http://localhost:8732/Service) in a browser you’ll get the following page instead:

Custom Landing Page

Top of page

Content Type

Hrm, why is the custom landing page being treated as an XML document? Let’s open the page in FireFox and see what FireBug has to tell.

FireBug - Response Headers

The content type is “application/xml; charset=utf-8″? That’s not right. Luckily this can easily be fixed using the WebOperationContext helper class. Add a reference to the System.ServiceModel.Web assembly for the project which contains your service and update the implementation of ILandingPage’s Index method as shown in the following code sample:

public Message Index()
{
    var context = WebOperationContext.Current;
    context.OutgoingResponse.ContentType = "text/html";
    return new LandingPageMessage();
}

Restart the host application and refresh your browser. Now the landing page will be shown correctly because you specified “text/html” as the content type instead of defaulting to “application/xml”.

Correct Landing Page

Top of page

Further Customization

Personally I don’t want to create a HTML page by building it in code using a XmlDictionaryWriter object. Notepad should be enough. So let’s do just that. Open Notepad and create a simple HTML page.

For example:

<html>
  <head>
    <title></title>
  </head>
  <body>
    <div style="background-color: yellow; border: 1px solid black;">
      This is a test.
    </div>    
  </body>
</html>

Save the HTML file (e.g. Default.htm) and add it to the project which contains your WCF service.

Solution Explorer

Be sure to set its “Copy to Output Directory” property to “Copy Always”.

Copy to Output Directory

Next its time to refactor the OnWriteBodyContents(…) method of the CustomLandingPage type. Just replace the existing code with the following lines of code:

protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
    var document = new HtmlDocument();
    document.Load("Default.htm");

    var bodyNode = document.DocumentNode.SelectSingleNode("//html");
    writer.WriteStartElement("HTML");
    writer.WriteRaw(bodyNode.InnerHtml);
    writer.WriteEndElement();
}

Here the HTML document which you created earlier is parsed and its contents are being written to the XmlDictionaryWriter object. If you restart the host application once more and visit the service’s base URL in a browser you’ll see that your custom HTML page will be displayed.

Custom Landing Page

Remark: In order to parse the HTML document I used the HTML Agility Pack which can be downloaded from CodePlex or NuGet.

That about wraps it up. Hope you enjoyed the article. You can download the source code accompanying this article from the download page. If you have any questions or suggestions please drop me an e-mail or submit a comment.

Top of page

About these ads

11 Responses to “Replacing the “You have created a service” message”

  1. John Says:

    How did you manage to configure your IIS or Web.Config file to display: “You have created service” with the address: http://localhost:8732/HelloWorld/ instead of sth like: http://localhost:8732/HelloWorld.svc
    ?

  2. John Says:

    Thank You for the answer, will give it a try.

    But coming back to the article.
    I am trying to implement the custom page.

    (I am new to WCF and I have a few questions.)

    I have in IIS 7.0 the page configured to:
    http://192.168.1.15:1748/index.svc address.
    Here at this address I see the page with
    “You have created a service.”.

    1. If I give my contract endpoint address name: “MyService”
    and another endpoint with empty address will be configured
    for landing page(just like in your example) then at which location in IIS I should see the custom page?

    2. Now If I enter in web browser the address like:
    http://192.168.1.15:1748/index.svc/somethingelse

    I get error: The message with To ‘http://win-jo2oaxdnz83:1748/index.svc/somethingelse’
    cannot be processed at the receiver, due to an AddressFilter mismatch at the EndpointDispatcher.
    Check that the sender and receiver’s EndpointAddresses agree.

    3. One more interesting thing.

    The IIS couldn’t find my ILandingPage unless I add:
    ConfigurationName for ILandingPage interface.

    e.g.: [ServiceContract(ConfigurationName = "ILandingPage")]

    Is this a correct behaviour?

    Thanks for some clarifications

  3. John Says:

    I have just discovered that custom page is visible at: http://192.168.1.15:1748/index.svc/
    default page at: http://192.168.1.15:1748/index.svc.

    Where might be the problem with: “/”?

  4. AlphaKilo45 Says:

    Hey I was wondering if you might be able to make the code available for this? I can’t seem to quite get things working on my machine and would like a baseline to try and figure out what is different.

  5. Xono Says:

    Nice Article. This was very informative. I was wondering if it is possible to replace the ‘Method not Found’ page in WCF Rest Application.

  6. Manish Jain Says:

    For IIS7.5 http://localhost shows me a custom page but http://localhost/service.svc still shows standard wcf page. Any ideas what am I missing?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 341 other followers

%d bloggers like this: