Can a WCF service contract have a nullable input p

2019-03-11 05:46发布

I have a contract defined like this:

[OperationContract]
[WebGet(UriTemplate = "/GetX?myStr={myStr}&myX={myX}", BodyStyle = WebMessageBodyStyle.Wrapped)]
string GetX(string myStr, int? myX);

I get an exception: [InvalidOperationException: Operation 'GetX' in contract 'IMyGet' has a query variable named 'myX' of type 'System.Nullable1[System.Int32]', but type 'System.Nullable1[System.Int32]' is not convertible by 'QueryStringConverter'. Variables for UriTemplate query values must have types that can be converted by 'QueryStringConverter'.]

could not find anything about this error except the following link: http://blog.rolpdog.com/2007/07/webget-and-webinvoke-rock.html which is a little old and not a solution anyway.

any ideas what to do except get rid of the nullable parameter?

thanks.

4条回答
Viruses.
2楼-- · 2019-03-11 06:09

Hum, the quick solution (not pretty) is to accept the nullable parameter as a string in the WCF respective Interface and Service codes.

查看更多
我想做一个坏孩纸
3楼-- · 2019-03-11 06:19

There is a solution to this problem that does not require any hacks. It might look like a lot of work but it's not really and makes a lot of sense if you read through it. The core of the problem is that there is indeed an unresolved bug (as of .NET 4) that means the WebServiceHost does not use custom QueryStringConverters. So you need to a little extra work and understand how the WCF configuration for WebHttpEndpoints works. The below lays out the solution for you.

First, a custom QueryStringConverter that allows nulls to be provided in the query string by omitting them, or providing a blank string:

public class NullableQueryStringConverter : QueryStringConverter
{
    public override bool CanConvert(Type type)
    {
        var underlyingType = Nullable.GetUnderlyingType(type);

        return (underlyingType != null && base.CanConvert(underlyingType)) || base.CanConvert(type);
    }

    public override object ConvertStringToValue(string parameter, Type parameterType)
    {
        var underlyingType = Nullable.GetUnderlyingType(parameterType);

        // Handle nullable types
        if (underlyingType != null)
        {
            // Define a null value as being an empty or missing (null) string passed as the query parameter value
            return String.IsNullOrEmpty(parameter) ? null : base.ConvertStringToValue(parameter, underlyingType);
        }

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

Now a custom WebHttpBehavior that will set the custom QueryStringConverter to be used in place of the standard one. Note that this behavior derivces from WebHttpBehavior which is important so that we inherit the behavior required for a REST endpoint:

public class NullableWebHttpBehavior : WebHttpBehavior
{
    protected override QueryStringConverter GetQueryStringConverter(OperationDescription operationDescription)
    {
        return new NullableQueryStringConverter();
    }
}

Now a custom ServiceHost that adds the custom behavior to the WebHttpEndpoint so that it will use the custom QueryStringConverter. The important thing to note in this code, is that it derives from ServiceHost and NOT WebServiceHost. This is important because otherwise the bug mentioned above will prevent the custom QueryStringConverter from being used:

public sealed class NullableWebServiceHost : ServiceHost
{
    public NullableWebServiceHost()
    {
    }

    public NullableWebServiceHost(object singletonInstance, params Uri[] baseAddresses) : base(singletonInstance, baseAddresses)
    {
    }

    public NullableWebServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses)
    {
    }

    protected override void OnOpening()
    {
        if (this.Description != null)
        {
            foreach (var endpoint in this.Description.Endpoints)
            {
                if (endpoint.Binding != null)
                {
                    var webHttpBinding = endpoint.Binding as WebHttpBinding;

                    if (webHttpBinding != null)
                    {
                        endpoint.Behaviors.Add(new NullableWebHttpBehavior());
                    }
                }
            }
        }

        base.OnOpening();
    }
}

Because we are not deriving from WebServiceHost we need to do it's work and make sure our configuration is correct to ensure the REST service will work. Something like the following is all you need. In this configuration, i also have a WS HTTP endpoint setup because i needed to access this service from both C# (using WS HTTP as its nicer) and mobile devices (using REST). You can omit the configuration for this endpoint if you don't need it. One important thing to note is that you DO NOT need the custom endpoint behavior anymore. This is because we are now adding our own custom endpoint behavior that binds the custom QueryStringConverter. It derives from WebHttpBehavior which is what the configuration added, making it now redundant.

<system.serviceModel>
  <services>
    <service behaviorConfiguration="ServiceBehavior" name="MyNamespace.Service1">
      <endpoint binding="webHttpBinding" bindingConfiguration="WebHttpBinding" contract="MyNamespace.IService1" />
      <endpoint address="ws" binding="wsHttpBinding" bindingConfiguration="WsHttpBinding" contract="MyNamespace.IService1" />
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    </service>
  </services>

  <bindings>
    <webHttpBinding>
      <binding name="WebHttpBinding">
        <security mode="Transport">
          <transport clientCredentialType="None" />
        </security>
      </binding>
    </webHttpBinding>

    <wsHttpBinding>
      <binding name="WsHttpBinding">
        <security mode="Transport">
          <transport clientCredentialType="None" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>

  <behaviors>
    <serviceBehaviors>
      <behavior name="ServiceBehavior">
        <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true" httpHelpPageEnabled="false" httpsHelpPageEnabled="true" />
        <dataContractSerializer maxItemsInObjectGraph="2147483647" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

The last thing to do is create a custom ServiceHostFactory and tell the svc file to use it, which will cause all the custom code to be used. Of course you could also create a custom element that would allow you to add the behavior in configuration, but i think for this behavior a code-based approach is better, since it is unlikely you will want to remove the ability to process nullable types, as it will break your service:

public sealed class NullableWebServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        return new NullableWebServiceHost(serviceType, baseAddresses);
    }
}

Change the markup of your Service.svc file to the following:

<%@ ServiceHost Service="MyNamespace..Service1" CodeBehind="Service1.svc.cs" Factory="MyNamespace.NullableWebServiceHostFactory" %>

Now you can use nullable types in your service interface without any issues, simply by omitting the parameter or setting it to an empty string. The following resources may be of more assistance to you:

Hope this helps!

查看更多
在下西门庆
4楼-- · 2019-03-11 06:22

Actually...you absolutely can have nullable parameters, or any other type of parameter that isn't supported by QueryStringConverter out of the box. All you need to do is extend QueryStringConverter to support any type you would need. See the accepted answer in this post ==>

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)?

查看更多
SAY GOODBYE
5楼-- · 2019-03-11 06:23

Yes, you can have nullable parameters with WCF. I think your problem here is that QueryStringConverter does not work with nullable parameters.

What to do? Do you need to use the UriTemplate attribute? If you published this as a 'classic web service' then you wouldn't have this issue.

The other option is to follow the advise in the link you provided - i.e. receive the myX parameter as string and then cast it to an int?, where (say) "n" is null. Not pretty.

查看更多
登录 后发表回答