This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page.
Coming back to “proper” WCF extensibility, this week’s post is about the QueryStringConverter. This is actually a simple topic to be covered, as its purpose is quite specific (unlike other extensibility points seen before, which could be used for a wide variety of cases) – within WCF the QueryStringConverter is only used on endpoints which have the WebHttpBehavior applied to them. And even in those, only on operations which have parameters passed via the query strings (either operations with parameters marked with [WebGet] or a [WebInvoke] operation with a UriTemplate that explicitly binds some parameters to the query string). A QueryStringConverter is the piece which can convert between operation parameters and their representation in a query string. For example, for the operation below:
On the server side (the most common usage) the QueryStringConverter will convert the strings in the query string into the actual parameters which will be passed to the operation – in the case of the “genre” parameter it will be the same value; for the “year” parameter the value will be parsed as an integer and passed to the operation. On the client side (i.e., when using a WebHttpBinding/WebHttpBehavior-based client endpoint), the converter is responsible for converting the operation parameters into strings which will be passed in the query string when the call is being made.
The default QueryStringConverter used by the WebHttpBehavior supports natively several types, including all simple numeric types (Byte, SByte, Int16, Int32, Int64, UInt16, UInt32, UInt64, Single, Double, Decimal), Boolean, Char, Object, String, DateTime, DateTimeOffset, TimeSpan, Guid, Uri, and arrays of Byte (essentially, all the types which the DataContractSerializer considers to be “primitives”, with the exception of XmlQualifiedName). Enumeration types are also supported by default (the string representation of the enum values are used). Finally, there is also another set of types which are supported by the default QueryStringConverter – any one which declares a [TypeConverter] attribute with a type converter which can convert the type to and from strings (more on that below).
Public implementations in WCF
The QueryStringConverter class is concrete, so it can be used directly. There’s also a JsonQueryStringConverter class, which uses the DataContractJsonSerializer to convert between instances of non-primitive types and (its JSON-equivalent) strings.
The QueryStringConverter class has three public (and virtual) methods. CanConvert is used when the runtime is being set up (called by WebHttpBehavior.ApplyDispatchBehavior for the server side or ApplyClientBehavior for the client side) for all parameter types in the operations, and if one of them return false, the service (or the channel factory) will throw when being opened. At the server side, when an incoming request arrives with query parameters, ConvertStringToValue will be called to convert it to the appropriate CLR type. At the client side, ConvertValueToString will be called to do the equivalent translation. Notice that it entails that if you’re writing a custom QueryStringConverter to be used on the server side only (most common scenario), you don’t need to override ConvertValueToString as it will never be called.
How to set the query string converter
Since the QueryStringConverter is only honored by the WebHttpBehavior, we need a class derived from that behavior to replace the QueryStringConverter used in the operation. WebHttpBehavior defines a virtual method, GetQueryStringConverter, which can be overridden to return a type derived from the default QueryStringConverter.
Notice that this method is called for every operation in the contract, so you can potentially have different converters per each operation.
Supporting additional types in query string parameters
Before going in the details, one question needs to be asked: why does one need to support additional types in query string parameters anyway? Per the REST principles, we should not pass complex types over GET requests, and if you are trying to do it you’re likely doing it wrong (or at least not in the spirit of REST). The URI (universal resource identifier) for a resource shouldn’t be identified by the resource itself, which is what most complex types represent. There may be some good reasons for that, though. A point can potentially be used as an identifier of a place in a grid (or in a plane in general), and being able to pass it in the query string is a nice option to have. Also, I’ve seen some non-idempotent operations are mapped as GET operations to work around cross-domain limitations which could benefit from an extended set of types (although I really, really dislike this, as it goes against all good that HTTP can give us, but I digress). And it’s possible, for performance reasons (less HTTP traffic), that we may want to use a GET operation to retrieve a collection of resources, instead of a single resource, and supporting things such as arrays of identifiers in an operation may make sense in some scenarios (even though it may go against the strict idea of resource). And with many questions in forums about supporting different types in query strings that, even if it may not be the most elegant solution, some people need it, and WCF offers a way to solve this problem.
So we can use the query string converter to support additional parameter types. So how to do about it? There are essentially two ways to go about this issue: create a type converter for your type, and decorate it with a [TypeConverter] attribute referencing the converter you created. Or create a subclass of QueryStringConverter and override the appropriate methods depending on where the converter will be used.
The first case isn’t specific to WCF, it comes from the component model namespace and was primarily intended for usage in XAML-based projects to convert a type to and from a text representation, but the converter is generic enough that it supports converting between any arbitrary pair of types. For the Point scenario listed above, a simple converter is shown below. By decorating the class Point with the [TypeConverter] attribute (and creating the converter itself), this type can now be used in query parameters for WCF endpoints which use the WebHttpBehavior.
The type converter way works fine, but it’s not feasible in some cases. First, you need to decorate the type with an attribute, and this is often not possible – if you want to support arrays, dictionaries, or any built-in types which can’t be modified. Also, you need to create essentially a converter class for every type you want to support (since TypeConverter.CanConvertFrom does not specify which type it needs to convert to), so if you need to support multiple types the number of converters will increase. For these scenarios the alternative is to replace the query string converter itself with a subclass, which can handle the conversion. The main drawback of this approach is that you’ll now need also a subclass of WebHttpBehavior to replace the query string converter used by the behavior. Below is the same scenario for the Point class solved with the new approach.
Which approach to use will really depend on the scenario. The second approach works for all types, but if you already have a converter used elsewhere (such as for XAML binding), then you can reuse the same converter for WCF HTTP endpoints as well. And if you like to define the endpoint in configuration (instead of via code), to use the second option you’ll need to create a behavior configuration extension for the new behavior as well.
Real world scenario: retrieving multiple resources in a single GET request
In the questions I’ve seen people asking about enabling new types for query string parameters, some of them were simply trying to pass array values in the query string parameters. As I mentioned before, one valid reason to do that is to selectively retrieve some resources from a service in a single request (to avoid multiple HTTP round-trips), and this would hopefully work in a simple way. This is a fairly simple scenario to implement, so I decided to go with it.
To start things off, here’s a simple service for which one can retrieve products. With the optimization option, besides the “canonical” GetProduct operation, we also have a GetProducts one. Their implementation are trivial.
Trying to use this service with the default WebHttpBehavior will cause this validation error to be thrown when the service is being opened:
System.InvalidOperationException: Operation 'GetProducts' in contract 'Service' has a query variable named 'ids' of type 'System.Int32', but type 'System.Int32' is not convertible by 'QueryStringConverter'. Variables for UriTemplate query values must have types that can be converted by 'QueryStringConverter'.
Since we cannot update the int class to use the type converter, we need to go with the new QueryStringConverter implementation. But
And before I go any further, here goes the usual disclaimer – this is a sample for illustrating the topic of this post, this is not production-ready code. I tested it for a few contracts and it worked, but I cannot guarantee that it will work for all scenarios (please let me know if you find a bug or something missing). Some known issues with the code are that for strings, any values in the array which contain commas will be split (it should be implemented in a CSV-style, where if you have commas in your data you can wrap the strings in quotes). For simplicity sake it also doesn’t have a lot of error handling which a production-level code would.
The new converter will use a simple algorithm to split the input from the query string at the ‘,’ character, and then use the original logic from the base QueryStringConverter class to convert the array elements. Notice that since this is to be used on the server only we don’t need to override ConvertValueToString.
And that’s pretty much it. We can now test the service and it should work just fine.
One final notice about WCF and REST endpoints: the new WCF Web APIs should make this scenario simpler by using formatters.