Feb 13

Web form post requests with WCF services are supported in WCF 3.5. We can achieve this by using WebInvoke attribute and webHttpBinding. There are tons of blog posts, forum entries where you can find detailed info on this. My goal is to map post parameters (name-value pairs) to operation's parameters. Parameter matching is done by UriTemplate. Unfortunately this wasn't very useful in my case. A sample on MSDN demonstrates different approach of handling post arguments. In my opinion it is too complicated and not very suitable for common use (operation methods must have NameValueCollection parameter Undecided).

I just wanted to simplify things and made basic matching of post parameters with method parameters via their names. MSDN sample was my code base. Parameter matching can be accomplished with custom IDispatchMessageFormatter in endpoints behavior. Current version support only requests with no response. Main method for deserializing post request looks like:

/// 
/// Get http post stream, retrieve form variables and pass their values to operation method.
/// 
public void DeserializeRequest(Message message, object[] parameters)
{
	// Check content type
	if (WebOperationContext.Current.IncomingRequest.ContentType != "application/x-www-form-urlencoded")
	{
		throw new InvalidDataException("Unexpected content type");
	}

	// Form stream
	Stream stream = StreamMessageHelper.GetStream(message);

	// Form string
	string formData = new StreamReader(stream).ReadToEnd();

	// Form variables
	NameValueCollection formVariables = System.Web.HttpUtility.ParseQueryString(formData);

	// Operation methods parameters
	ParameterInfo[] operationParameters = operation.SyncMethod.GetParameters();

	// Match form variables with operation parameters
	QueryStringConverter converter = new QueryStringConverter();
	for (int i = 0; i < parameters.Length; i++)
	{
		string value = formVariables[operationParameters[i].Name];
		string name = operationParameters[i].Name;
		if (value != null)
		{
			if (converter.CanConvert(operationParameters[i].ParameterType))
				parameters[i] = converter.ConvertStringToValue(value, operationParameters[i].ParameterType);
			else
				parameters[i] = value;
		}
	}
}

Hard work is done. Now we need to write custom behavior which uses our new message formatter. Easiest way is to extend WebHttpBehavior and return our message formatter if operation contract has set WebInvoke attribute.

public class FormPostBehavior : 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
		if (operationDescription.Behaviors.Find() != null)
		{
			return new FormPostRequestFormatter(operationDescription);
		}
		else
		{
			return base.GetRequestDispatchFormatter(operationDescription, endpoint);
		}
	}
}

All you need to do is to add new behavior to endpoint behavior:

ServiceHost sh = new ServiceHost(typeof(TestFormPostService));
sh.Description.Endpoints[0].Behaviors.Add(new FormPostBehavior());

I don't like to define all service hosts parameters in code. I prefer to use configuration files. First we need to write new behavior element which creates our new behavior. It is simple:

public class FormPostBehaviorElement : BehaviorExtensionElement
{
	public override Type BehaviorType
	{
		get { return typeof(FormPostBehavior); }
	}

	protected override object CreateBehavior()
	{
		return new FormPostBehavior();
	}
}

To test this behavior just prepare html as:

<form id="form1" action="http://localhost:8001/TestService/Invoke" method="post" >
Parameter 1: <input type="text" name="parameter1" value="" /><br>
<input type="submit" name="submit" value="Test" />
</form>

Tags:
Comments are closed