Monthly Archives: September 2011

WCF Web API–Custom parameter conversion

This is the twelfth post on a series about the WCF Web API Preview 4 and 5 releases. Last post focused on operation handlers. The present post shows how to use this handler type to perform custom operation parameter conversion.

 

Operation parameters

On a WCF Web API operation, the URI template variables can be injected as operation’s parameters. The following code excerpt shows a typical example, where the resource identifier (an integer) is obtained from the URI and used as a parameter.

[WebGet(UriTemplate = "{id}")]
public HttpResponseMessage<Contact> Get(int id){...}

 

As stated in a previous post, these parameters are retrieved from the URI template match result and added into the operation parameter collection by the UriTemplateHandler handler. However, this handlers outputs these parameters as strings. What if the operation expects a different type, such as the integer id used in the above example? Web API runtime knows how to convert these strings into other types, such as integers. However it doesn’t support user-defined types. For instance, consider the following example, using the user-defined AComplexType and AnotherComplexType types.

class AComplexType
{
    public string Value { get; set; }
}
class AnotherComplexType
{
    public int Value { get; set; }
}

[WebGet(UriTemplate = "{prm1}/{prm2}")]
HttpResponseMessage Get(AComplexType prm1, AnotherComplexType prm2)
{
...
}

How is it possible to define the conversion from string into AComplexType and AnotherComplexType, and have it done before the operation is called?

One way of doing this is by using custom operation handlers that:

  • receives prm1 and prm2 as strings;
  • outputs them as AComplexType and AnotherComplexType.

The following handler does exactly this for a generic T type:

public class ParameterConversionOperationHandler<T> : HttpOperationHandler
{
    private readonly HttpOperationDescription _desc;
    private readonly Func<string, T> _conv;

    public ParameterConversionOperationHandler(HttpOperationDescription desc, Func<string, T> conv)
    {
        _desc = desc;
        _conv = conv;
    }

    protected override IEnumerable<HttpParameter> OnGetInputParameters()
    {
        return _desc.InputParameters
            .Where(prm => prm.ParameterType == typeof(T))
            .Select(prm => new HttpParameter(prm.Name, typeof(string)));
    }

    protected override IEnumerable<HttpParameter> OnGetOutputParameters()
    {
        return _desc.InputParameters
            .Where(prm => prm.ParameterType == typeof(T));
    }

    protected override object[] OnHandle(object[] input)
    {
        var res = new object[input.Length];
        for (var i = 0; i < input.Length; ++i)
        {
            res[i] = _conv(input[i] as string);
        }
        return res;
    }
 }

 

  • The constructor receives a Func<string, T>, i.e. a function that converts a string into a T.
  • The OnGetInputParameters finds all the operation’s parameters that have type T and returns them as string input parameters. This means that the Web API runtime will input string parameters into the handler, when processing a request.
  • Finally, the OnHandle method uses the conversion function (Func<string,T>) to convert all the input string values into T output values.

 

Handler registration

The final step is to add an instance of this handler, into the request handlers collection, for each custom type T that we want to be converted. One way of achieving this is by extending  the new Preview 5 configuration model. The following code fragment shows its usage

var config = new HttpConfiguration()
                .UseParameterConverterFor<AComplexType>(
                    s => new AComplexType {Value = s.ToUpper()})
                .UseParameterConverterFor<AnotherComplexType>(
                    s => new AnotherComplexType {Value = Int32.Parse(s)+1});

using (var host = new HttpServiceHost(typeof(TheService), config, "http://localhost:8080/"))
{
    ...
}

 

The UseParameterConverterFor is a HttpConfiguration extension method, receiving the target type T and the conversion function,  which registers the required operation handler.

public static class ParameterConversionExtensions
{
    public static HttpConfiguration UseParameterConverterFor<T>(
	    this HttpConfiguration cfg, Func<string, T> conv)
    {
        cfg.AddRequestHandlers((coll, ep, desc) =>
            {
                if (desc.InputParameters.Any(p => p.ParameterType == typeof (T)))
                {
                    coll.Add(new ParameterConversionOperationHandler<T>(desc, conv));
                }
            });
        return cfg;
    }
}

Note the conditional addition to the handlers collections: the handler is only added if the operation has at least one parameter of type T.

This extension method uses another HttpConfiguration extension method: AddRequestHandlers.

public static class HttpConfigurationExtensions
{
    public static HttpConfiguration AddRequestHandlers(
	    this HttpConfiguration conf, 
        Action<Collection<HttpOperationHandler>, ServiceEndpoint, HttpOperationDescription> requestHandlerDelegate)
    {
        var old = conf.RequestHandlers;
        conf.RequestHandlers = old == null ? requestHandlerDelegate :
                                        (coll, ep, desc) =>
                                        {
                                            old(coll, ep, desc);
                                            requestHandlerDelegate(coll, ep, desc);
                                        };
        return conf;
    }
}

This new method adds the registration of a new configuration action without removing the ones that may already be defined, i.e., allows for the composition of multiple actions by independent extensions.

Concluding remarks

Some concluding remarks: