【问题标题】:Can a WCF service contract have a nullable input parameter?WCF 服务合同可以有一个可为空的输入参数吗?
【发布时间】:2023-03-31 17:09:01
【问题描述】:

我有这样定义的合同:

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

我得到一个例外: [InvalidOperationException:合约“IMyGet”中的操作“GetX”有一个名为“myX”的查询变量,类型为“System.Nullable1[System.Int32]', but type 'System.Nullable1[System.Int32]”,不能由“QueryStringConverter”转换。 UriTemplate 查询值的变量必须具有可由 'QueryStringConverter' 转换的类型。]

除了以下链接外,找不到有关此错误的任何信息: http://blog.rolpdog.com/2007/07/webget-and-webinvoke-rock.html 这有点老了,无论如何都不是解决方案。

除了去掉可以为空的参数之外,还有什么想法吗?

谢谢。

【问题讨论】:

    标签: c# rest wcf nullable


    【解决方案1】:

    这个问题有一个不需要任何技巧的解决方案。它可能看起来需要做很多工作,但实际上并非如此,如果你通读它,它就会很有意义。问题的核心是确实有一个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,这很重要,因此我们继承了 REST 端点所需的行为:

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

    现在是一个自定义 ServiceHost,它将自定义行为添加到 WebHttpEndpoint,以便它使用自定义 QueryStringConverter。在这段代码中需要注意的重要一点是,它派生自 ServiceHost 而不是 WebServiceHost。这很重要,否则上面提到的错误将阻止自定义 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" %>
    

    现在您可以在服务接口中使用可空类型而不会出现任何问题,只需省略参数或将其设置为空字符串即可。以下资源可能对您有更多帮助:

    希望这会有所帮助!

    【讨论】:

    • 我喜欢这个解决方案。
    • 对于本应由 Microsoft 实施的事情来说,工作量太大了。
    • 令人惊讶的是,微软将原本应该简单的东西变成了痛苦的复杂的东西......不过答案很好
    • 我创建了一个库并添加了这 4 个类文件,并引用了所有各自的命名空间,并将 lib dll 引用提供给 wcfservice 应用程序,但仍然显示相同的错误.. 我是什么缺少请帮助我..
    • 哇。非常感谢这个解决方案。是不是觉得这很奇怪
    【解决方案2】:

    实际上...您绝对可以拥有可为空的参数,或任何其他类型的参数,这些参数不被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)?

    【讨论】:

    • 上面的代码参考中有一个错误导致 QueryStringConverter 派生类在框架 4 中无法使用。在尝试此操作之前,请确保查看该错误。我浪费了很多时间才发现它在实践中不起作用。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      是的,您可以在 WCF 中使用可为空的参数。我认为您的问题是 QueryStringConverter 不适用于可为空的参数。

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

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

      【讨论】:

        猜你喜欢
        • 2010-12-12
        • 1970-01-01
        • 2023-04-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-27
        • 2014-05-25
        • 2011-01-06
        • 1970-01-01
        相关资源
        最近更新 更多