views:

3554

answers:

4

Using 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.

My question is how does one define a Http service that exposes a resource with an array of parameters without resorting to a loosey-goosey interface.

+11  A: 

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.

joshperry
Dilip Krishnan
Excellent! I ran into an issue where my behavior extension wouldn't load unless I fully qualified the type name with assembly version, key, etc. See http://nayyeri.net/configuration-error-for-custom-behavior-extensions-in-wcf
Sebastian Good
+1  A: 

To respond to your comment on my other answer:

You can do a wildcard parameter at the end of the querystring like

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

This way the qs1 string parameter will be the whole raw querystring after the qs1=, you could then parse that manually in your code.

The QueryStringConverter relies on the formatting of the querystring so doing something exactly how you want is not possible without possibly rewriting QueryStringConverter instead of the little overrides we did in the other answer.

From MSDN:

Wildcard segments must follow the following rules:

  • There can be at most one named wildcard segment for each template string.
  • A named wildcard segment must appear at the right-most segment in the path.
  • A named wildcard segment cannot coexist with an anonymous wildcard segment within the same template string.
  • The name of a named wildcard segment must be unique.
  • Named wildcard segments cannot have default values.
  • Named wildcard segments cannot end with “/”.
joshperry
A: 

I tried to implement Uritemplate querstring with wildcard but i was getting exception that {*} should be variable or literal value. Can you explain me how should I implement.

lalita
+1  A: 

be aware that in WCF 3.5 you must specify the full qualified assembly name in

   <extensions>
    <behaviorExtensions>
      <add name="customWebHttp" type="NameSpace.CustomHttpBehaviorExtensionElement, MyAssemblyName, NOT SUFFICIENT HERE" />
    </behaviorExtensions>
  </extensions>

just like this: SampleService.CustomBehavior, SampleService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

otherwise you will get exception:

Configuration Error Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

Parser Error Message: Invalid element in configuration. The extension name 'CustomWebHttp' is not registered in the collection at system.serviceModel/extensions/behaviorExtensions.

pootow