Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In the WCF web programming model, how can one write an operation contract with an array of query string parameters (i.e. with the same name)?

Using the WCF web programming model one can specify an operation contract like so:

[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Xml, UriTemplate = "SomeRequest?qs1={qs1}&qs2={qs2}")]
XElement SomeRequest1(string qs1, string qs2);

Now if we had to make a contract that accepts an array of parameters with the same name (in this case qs1) contract like so...

[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Xml, UriTemplate = "SomeRequest?qs1={qs1}&qs1={qs2}")]
 XElement SomeRequest2(string qs1, string qs2);

We get the error message at run time when we make the invocation to the method:

the query string must have 'name=value' pairs with unique names. Note that the names are case-insensitive. See the documentation for UriTemplate for more details.

How does one define an HTTP service that exposes a resource with an array of parameters without resorting to a loosey-goosey interface?

like image 918
Dilip Krishnan Avatar asked Dec 10 '08 00:12

Dilip Krishnan


1 Answers

I've implemented a simple custom QueryStringConverter so that you can make qs1 an string[] then have the query string variable be comma delimited (e.g. http://server/service/SomeRequest?qs1=val1,val2,val3,val4)

[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Xml,
        UriTemplate = "SomeRequest?qs1={qs1}")]
XElement SomeRequest2(string[] qs1);

First you need a class that inherits from WebHttpBehavior so that we can inject our custom QueryStringConverter:

public class CustomHttpBehavior : System.ServiceModel.Description.WebHttpBehavior
{
    protected override System.ServiceModel.Dispatcher.QueryStringConverter GetQueryStringConverter(System.ServiceModel.Description.OperationDescription operationDescription)
    {
        return new CustomQueryStringConverter();
    }
}

Then our CustomQueryStringConverter that handles string[] parameters:

public class CustomQueryStringConverter : System.ServiceModel.Dispatcher.QueryStringConverter
{
    public override bool CanConvert(Type type)
    {
        if (type == typeof(string[]))
        {
            return true;
        }

        return base.CanConvert(type);
    }

    public override object ConvertStringToValue(string parameter, Type parameterType)
    {
        if (parameterType == typeof(string[]))
        {
            string[] parms = parameter.Split(',');
            return parms;
        }

        return base.ConvertStringToValue(parameter, parameterType);
    }

    public override string ConvertValueToString(object parameter, Type parameterType)
    {
        if (parameterType == typeof(string[]))
        {
            string valstring = string.Join(",", parameter as string[]);
            return valstring;
        }

        return base.ConvertValueToString(parameter, parameterType);
    }
}

The last thing you need to do is create a behavior configuration extension so that the runtime can get an instance of the CustomWebHttpBehavior:

public class CustomHttpBehaviorExtensionElement : System.ServiceModel.Configuration.BehaviorExtensionElement
{
    protected override object CreateBehavior()
    {
        return new CustomHttpBehavior();
    }

    public override Type BehaviorType
    {
        get { return typeof(CustomHttpBehavior); }
    }
}

Now we add the element to our configuration extensions so that our CustomWebHttpBehavior is used, we use the Name of that extension instead of <webHttp /> in our behavior:

 <system.serviceModel>
   <services>
     <service name="NameSpace.ServiceClass">
       <endpoint address="" behaviorConfiguration="MyServiceBehavior"
        binding="webHttpBinding" contract="NameSpace.ServiceClass" />
     </service>
   </services>
  <behaviors>
   <endpointBehaviors>
    <behavior name="MyServiceBehavior">
      <customWebHttp/>
    </behavior>
   </endpointBehaviors>
  </behaviors>
  <extensions>
    <behaviorExtensions>
      <add name="customWebHttp" type="NameSpace.CustomHttpBehaviorExtensionElement, MyAssemblyName" />
    </behaviorExtensions>
  </extensions>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
 </system.serviceModel>

You can now also extend your CustomQueryStringConverter to handle other types that the default one doesn't, such as nullable value types.

NOTE: There is a bug logged at microsoft connect that directly relates to this code. The code does not actually work in almost all circumstances where you attempt to Query Convert different types.

http://connect.microsoft.com/VisualStudio/feedback/details/616486/bug-with-getquerystringconverter-not-being-called-by-webservicehost#tabs

Please make sure you read this carefully before wasting hours of your time creating custom REST query string converters that cannot possibly work. (Applies to Framework 4.0 and below).

like image 50
joshperry Avatar answered Oct 14 '22 22:10

joshperry