2009-09-08 66 views
12

我有这样定义的合同:WCF服务合约可以有一个可为空的输入参数吗?

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

我得到一个异常: [出现InvalidOperationException:在承包经营“的getX“IMyGet”有类型的名为“MYX”查询变量“System.Nullable 1[System.Int32]', but type 'System.Nullable 1 [System.Int32]'不能被'QueryStringConverter'转换。为UriTemplate查询的值的变量必须具有可以通过“QueryStringConverter”]

转换找不到任何关于此错误的任何东西,除了下面的链接类型: http://blog.rolpdog.com/2007/07/webget-and-webinvoke-rock.html这是一老一少反正不是一个解决方案。

任何想法做什么除了摆脱可空参数?

感谢。

回答

1

是的,你可以有WCF的可为空的参数。我认为你的问题在于QueryStringConverter不能使用可为空的参数。

怎么办?你需要使用UriTemplate属性吗?如果您将其作为“经典网络服务”发布,那么您不会遇到此问题。

另一种选择是遵循您提供的链接中的建议 - 即接收myX参数作为字符串,然后将其转换为int ?,其中(比如说)“n”为空。不漂亮。

8

其实......你绝对可以有可空的参数,或任何其他类型的参数,QueryStringConverter开箱即不支持。所有你需要做的就是扩展QueryStringConverter以支持你需要的任何类型。请参阅这篇文章==>

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

+0

上面的代码引用中存在一个错误,它使得QueryStringConverter派生类在框架4中不可用。请确保在尝试此操作之前查看错误。在发现它在实践中不起作用之前,我浪费了很多时间。 – Jim 2011-05-27 10:55:41

32

接受的答案是有这个问题,不需要任何黑客的解决方案。它可能看起来像很多工作,但它不是真的,并且如果你仔细阅读它就会很有意义。问题的核心是确实有一个unresolved bug(从.NET 4起),这意味着WebServiceHost不使用自定义QueryStringConverters。因此,您需要做一些额外的工作并了解WebHttpEndpoints的WCF配置是如何工作的。以下为您找出解决方案。

首先,自定义QueryStringConverter,允许通过省略它们,或者提供一个空字符串中的查询字符串提供空值:

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); 
    } 
} 

现在定制WebHttpBehavior将设置自定义QueryStringConverter被用来代替标准的。需要注意的是,从WebHttpBehavior这种行为derivces让我们继承了一个REST端点所要求的行为,这是很重要的:即增加了自定义行为的WebHttpEndpoint

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

现在定制ServiceHost的,使其将使用自定义QueryStringConverter。此代码中要注意的重要一点是,它来自ServiceHost而不是WebServiceHost。这是很重要的,否则上面提到的bug会阻止自定义QueryStringConverter被使用:

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(); 
    } 
} 

,因为我们没有从获得WebServiceHost,我们需要做的工作,并确保我们的配置是正确的确保REST服务能够正常工作。像下面这样的东西就是你需要的。在这个配置中,我也有一个WS HTTP端点设置,因为我需要从C#(使用WS HTTP作为它的更好)和移动设备(使用REST)访问此服务。如果不需要,可以省略此端点的配置。需要注意的一点是,您不再需要自定义端点行为。这是因为我们现在添加了我们自己的自定义端点行为,该行为绑定了自定义的QueryStringConverter。它来源于WebHttpBehavior这是什么配置添加,使其现在是多余的。

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

最后要做的是创建一个自定义ServiceHostFactory并告诉SVC文件来使用它,这将导致使用的所有的自定义代码。当然,你也可以创建一个自定义元素,允许你在配置中添加行为,但我认为对于这种行为,基于代码的方法更好,因为你不太可能想要去除处理可空类型的能力,因为它会破坏你的服务:

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

更改您的Service.svc的标记文件到以下几点:

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

现在你可以使用你的服务接口可空类型没有任何问题,只要通过省略参数或将其设置为空字符串。以下资源可能更多的援助,以你:

希望这有助于!

+1

我喜欢这个解决方案。 – Sawyer 2013-08-07 14:06:12

+4

对于本应该成为微软实施的行为者来说,这是一个很多工作的地狱。 – crush 2014-02-06 14:53:46

+2

真是一个惊喜,微软让一些东西应该很容易成为痛苦的复杂的东西......虽然 – Jim 2014-07-23 19:48:21

1

哼,快速解决方案(不太漂亮)是接受可空参数作为WCF各自接口和服务代码中的字符串。

相关问题