When developing a WCF service using the new web programming features, it's natural to make sure all the basics are taken care of by entering the service address into a browser.  When the service is running correctly, this page is returned:

This indicates the service is running but little else.  There's lots of other information that would be useful to include, maybe something like this (I'm not an html wizard so this could be greatly improved:)

 

This is made possible by including an OperationContract like this in the ServiceContract:

[WebInvoke(UriTemplate = "*", Method = "*")]

[OperationContract]

Message Help(Message m);

 

The method can be named anything and UriTemplate and Method can be tweaked to serve custom help for only certain situations.  As shown, any message routed to the service that doesn't match another operation contract will call Help.  To implement this method I created two classes, WebHttpHelpPageGenerator and WebHttpHelpPageMessage.  WebHttpHelpPageGenerator handles collecting the information to display and WebHttpHelpPageMessage derives from Message and handles formatting that information into html.  These are used to implement Help like this:

public Message Help(Message m)

{

    return WebHttpHelpPageGenerator.GenerateHelpPage();

}

WebHttpHelpPageGenerator.GenerateHelpPage is able to accomplish its task by using OperationContext.Current.EndpointDispatch.ContractName and OperationContext.Current.Host.Description.Endpoints to find the current ServiceEndpoint which contains a list of Operations and the CLR type of the contract:

ServiceEndpointCollection endpoints = OperationContext.Current.Host.Description.Endpoints;

OperationDescriptionCollection operations = null;

Type contract = null;

foreach (ServiceEndpoint endpoint in endpoints)

{

        if (endpoint.Contract.Name == OperationContext.Current.EndpointDispatcher.ContractName)

        {

             contract = endpoint.Contract.ContractType;

             operations = endpoint.Contract.Operations;

             break;

        }

} 

So with that list and a little reflection all the necessary information can be exctracted:

message.TitleTextString = contract.Name;

foreach (OperationDescription operation in operations)

{

      WebGetAttribute get = operation.Behaviors.Find<WebGetAttribute>();

      WebInvokeAttribute invoke = operation.Behaviors.Find<WebInvokeAttribute>();

 

      if (get == null && invoke == null) continue;

 

      MethodInfo method = contract.GetMethod(operation.Name);

      OperationInfo info = new OperationInfo

      {

           OperationName = operation.Name,

           Method = get != null ? "GET" : invoke.Method,

           BodyStyle = get != null ? get.BodyStyle.ToString() : invoke.BodyStyle.ToString(),

           UriTemplate = get != null ? get.UriTemplate : invoke.UriTemplate,

           ReturnType = method.ReturnType.ToString(),

           Parameters = GetFormattedParameters(method.GetParameters()),

           RequestFormat = get != null ? get.RequestFormat.ToString() : invoke.ResponseFormat.ToString(),

           ResponseFormat = get != null ? get.ResponseFormat.ToString() : invoke.ResponseFormat.ToString()

       };

 

       info.OperationName = operation.Name;

       message.Operations.Add(info);                 

}

OperationInfo is just a helper class to encapsulate all the data collected.  Finally the new WebOperationContext class is used to set the content type to text/html:

WebOperationContext.Current.OutgoingResponse.ContentType = "text/html"; 

This builds a general purpose framework that can be called from any service using the new web programming features, though I'm sure I missed plenty of corner cases (like async programming.)