HTML Form Handler Sample

Download sample

This sample shows how to extend the Windows Communication Foundation (WCF) Web Programming Model to handle HTML form posts, such as those produced by a Web browser.

Note

This sample requires that .NET Framework version 3.5 is installed to build and run. Visual Studio 2008 is required to open the project and solution files.

Parsing Form Data

HTML form posts are encoded as a series of name-value pairs inside of an HTTP POST entity body with a content type of application/x-www-form-urlencoded. The ParseQueryString method is capable of parsing these values into a NameValueCollection when presented with a raw entity body string. To allow this name/value collection to be passed as a parameter to a WCF service operation, the FormDataProcessor class in the sample uses the IDispatchMessageFormatter extensibility point.

The FormDataProcessor class’s implementation of DeserializeRequest uses ParseQueryString to parse the entity body in a NameValueCollection. A Microsoft Language Integrated Query (LINQ) is used to populate additional method parameters whose values are available through the UriTemplateMatch used to dispatch the request to the operation.

public void DeserializeRequest(System.ServiceModel.Channels.Message message, object[] parameters)
{
    if (WebOperationContext.Current.IncomingRequest.ContentType 
                         != "application/x-www-form-urlencoded")
        throw new InvalidDataException("Unexpected content type");
    Stream s = StreamMessageHelper.GetStream(message);
    string formData = new StreamReader(s).ReadToEnd();
    NameValueCollection parsedForm = 
            System.Web.HttpUtility.ParseQueryString(formData);
    UriTemplateMatch match = 
     message.Properties["UriTemplateMatchResults"] as UriTemplateMatch;
    ParameterInfo[] paramInfos = operation.SyncMethod.GetParameters();
    var binder = CreateParameterBinder( match );
    object[] values = (from p in paramInfos
                       select binder(p)).ToArray<Object>();
    values[paramInfos.Length - 1] = parsedForm;
    values.CopyTo(parameters, 0);
}

private Func<ParameterInfo, object> CreateParameterBinder(UriTemplateMatch match)
{
    QueryStringConverter converter = new QueryStringConverter();
    return delegate( ParameterInfo pi )
    {
        string value = match.BoundVariables[pi.Name];
        if (converter.CanConvert(pi.ParameterType) && value != null)
            return converter.ConvertStringToValue(value, 
                                                   pi.ParameterType);
        else
        return value;
    };
}

Extending WebHttpBehavior with a custom RequestFormatter

You can derive a class from the WebHttpBehavior to extend the WCF runtime for each operation. In the sample, FormProcessingBehavior overrides GetRequestDispatchFormatter to plug in a FormDataFormatter for any Web invoke operation whose last parameter is a NameValueCollection.

public class FormProcessingBehavior : WebHttpBehavior
{
    protected override IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
    {
        //Messages[0] is the request message
        MessagePartDescriptionCollection parts = 
                 operationDescription.Messages[0].Body.Parts;

        //This formatter looks for [WebInvoke] operations that have
        // their last parameter typed as NameValueCollection
        if (operationDescription.Behaviors.Find<WebInvokeAttribute>() 
                != null &&
            parts.Count > 0 &&
            parts[parts.Count - 1].Type == typeof(NameValueCollection))
        {
            return new FormDataRequestFormatter(operationDescription);
        }
        else
        {
            return base.GetRequestDispatchFormatter(
                      operationDescription, endpoint);
        }
    }
}

Implementing a Form Processing Service

The FormProcessingBehavior hides the details of HTML form processing. The service implementation can then be written without special knowledge of HTML forms, as shown in the following sample code.

[OperationContract]
[WebInvoke(UriTemplate = "ProcessForm/{templateParam1}/{templateParam2}")]
public Message ProcessForm(string templateParam1, string templateParam2, NameValueCollection formData)
{
    DumpValues(Console.Out, templateParam1, templateParam2, formData);

    return StreamMessageHelper.CreateMessage(
        MessageVersion.None, "",
        "text/plain",
        delegate(Stream output)
        {
          TextWriter writer = new StreamWriter(output);
          DumpValues(writer, templateParam1, templateParam2, formData);
        }
        );
}

Note

For a detailed description of the StreamMessageHelper class, see the Push-Style Streaming Sample.

Hosting the Form Processing Service

The service is hosted using the ServiceHost class. The custom FormProcessingBehavior is added to the ServiceEndpoint manually before calling Open as shown in the following sample code.

ServiceHost host = new ServiceHost(typeof(Service), new Uri("http://localhost:8000/FormTest"));

ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(Service), new WebHttpBinding(), "");
endpoint.Behaviors.Add(new FormProcessingBehavior());

Additionally, the HTTP GET endpoint that exists by default (the endpoint that produces the default HTML help page) is removed by disabling the ServiceMetadataBehavior and ServiceDebugBehavior as shown in the following sample code.

ServiceMetadataBehavior smb = host.Description.Behaviors.Find<ServiceMetadataBehavior>();

if (smb != null)
      {
    smb.HttpGetEnabled = false;
    smb.HttpsGetEnabled = false;
}

ServiceDebugBehavior sdb = host.Description.Behaviors.Find<ServiceDebugBehavior>();
if (sdb != null)
{
    sdb.HttpHelpPageEnabled = false;
}

Running the sample

To view the output of the sample, compile and run the HtmlFormProcessing project and then navigate to http://localhost:8000/FormTest with a Web browser.

See Also

Other Resources

Push-Style Streaming Sample

© 2007 Microsoft Corporation. All rights reserved.