ASP.NET应用程序中的WCF Web服务 - 如何支持可为空的参数?

时间:2011-12-22 03:26:47

标签: asp.net wcf asp.net-4.0

我正在尝试在我的WCF服务中允许灵活的方法参数,但我遇到了一些问题。

首先,我将解释目标。我希望能够声明一个Web方法,例如:

    [OperationContract, WebGet(ResponseFormat = WebMessageFormat.Json, RequestFormat=WebMessageFormat.Json)]
    public StatisticEventArgs GetStatistic(DateTime? startDate = null, DateTime? endDate = null)
    {
        return new StatisticEventArgs() { Count = 50; }
    }

并且能够通过jquery $ .ajax方法调用它:

$.ajax({
        type: 'GET',
        url: 'service/MyService.svc,
        data: '', // or '?startDate=12/1/2011&endDate=12/10/2011','?startDate=12/1/2011', etc. Any combination of startDate or endDate parameters
        contentType: "application/json",
        dataType: "json",
        processdata: true,
        success: endCallback,
        error: errorCallback 
    });

目标是创建所有参数都是可选的方法,对于未指定的参数,将使用其默认值。在这个例子中,我希望能够调用我可以提供startDate的方法 或endDate参数,两者或两者都没有。

我很快发现默认的 QueryStringParameter 类(如本问题所示: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)?)不支持可空类型,但 JsonQueryStringConverter 支持。此外,如上面的问题所述,.NET 4.0及更低版本中存在与此解决方案相关的错误,该错误会阻止 JsonQueryStringConverter 类在自定义行为中实例化(从不调用GetQueryStringConverter)。 / p>

经过更多研究后,我发现this Microsoft bug提供了一种解决方法,但仅限于通过代码实例化服务。

我的问题是,如何在ASP.NET WCF Web服务中应用该变通方法?我相信我需要在web.config中进行设置,但我不知道如何实现这一目标。这是我到目前为止的相关示例代码(我修改了名称,因此它可能包含一些拼写错误):

我的ASP.NET Web应用程序项目中包含的WCF类:

namespace Namespace.Services
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class MyService
    {

    [OperationContract, WebGet(ResponseFormat = WebMessageFormat.Json, RequestFormat=WebMessageFormat.Json)]
    public StatisticEventArgs GetStatistic(DateTime? startDate = null, DateTime? endDate = null)
    {
        return new StatisticEventArgs() { Count = 50; }
    }

    public class NullableTypeBehaviorBehaviorExtension :  BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get { return typeof(NullableTypeBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new NullableTypeBehavior();
        }
    }

    public class NullableTypeBehavior : WebHttpBehavior
    {

        protected override System.ServiceModel.Dispatcher.QueryStringConverter GetQueryStringConverter(OperationDescription operationDescription)
        {
            return new JsonQueryStringConverter();
        }
    }
}

使用Javascript:

$.ajax({
        type: 'GET',
        url: 'service/MyService.svc/GetStatistic',
        data: '', // or '?startDate=12/1/2011&endDate=12/10/2011','?startDate=12/1/2011', etc. Any combination of startDate or endDate parameters
        contentType: "application/json",
        dataType: "json",
        processdata: true,
        success: endCallback,
        error: errorCallback 
    });

的web.config:

<system.serviceModel>
<extensions>
  <behaviorExtensions>
    <add name="nullableTypeBehavior" type="Namespace.Services.NullableTypeBehaviorBehaviorExtension, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  </behaviorExtensions>
</extensions>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"></serviceHostingEnvironment>
<services>
  <service name="MyWcfService">
    <endpoint address="" behaviorConfiguration="MyBehaviorConfig"
      binding="webHttpBinding"  contract="Namespace.Services.MyService" />
  </service>
</services>
<behaviors>
  <serviceBehaviors>
    <behavior name="web">
      <serviceMetadata httpGetEnabled="true"/>
      <serviceDebug includeExceptionDetailInFaults="true"/>
    </behavior>
  </serviceBehaviors>
  <endpointBehaviors>
    <behavior name="MyBehaviorConfig">
      <nullableTypeBehavior />
    </behavior>
  </endpointBehaviors>
</behaviors>
</client>

更新1: 解决方案

以下是我最终得到的答案(由VinayC提供):

public class CustomFactory : System.ServiceModel.Activation.ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type t, Uri[] baseAddresses)
   {
       return new CustomHost(t, baseAddresses);
   }
}

public class CustomHost : ServiceHost
{
    public CustomHost(Type t, params Uri[] baseAddresses) : base(t, baseAddresses) { }

   protected override void OnOpening()
   {
       var nullableTypeBehavior = new NullableTypeBehavior();
      foreach(var ep in this.Description.Endpoints)
      {
         ep.Behaviors.Add(nullableTypeBehavior);
      }
      base.OnOpening();
   }
}

并位于MyService.svc文件的顶部:

<%@ ServiceHost Language="C#" Debug="true" Service="Namespace.Services.MyService" CodeBehind="MyService.svc.cs" Factory="Namespace.Services.CustomFactory" %>

更新2:传递此解决方案的日期时出现问题

目前通过日期不起作用:

http://localhost/Services/MyService.svc?startDate=11/10/2011&endDate=11/11/2011

但这些请求有效:

http://localhost/Services/MyService.svc?startDate=null&endDate=null
http://localhost/Services/MyService.svc

看起来像是特定的JsonQueryStringConverter,所以我会调查它并在找到解决方案时回发。似乎它应该足够简单,可以调试。

更新3:传递日期的解决方案:

问题是我传递的日期不是.NET JSON的预期格式。我正在传递类似的东西:

"12/01/2011"

和.NET JSON(包括JsonQueryStringConverter)需要这种格式:

"\/Date(1283219926108)\/"

该日期格式不是javascript原生的。为了检索这种格式的日期,我找到了这篇文章。添加此脚本后,我可以在jQuery中执行此操作,并且将正确解析日期:

 $.ajax({
        type: 'GET',
        url: 'service/MyService.svc/GetStatistic',
        data: '?startDate=' + JSON.stringifyWCF(new Date('12/1/2011'))
        contentType: "application/json",
        dataType: "json",
        processdata: true,
        success: endCallback,
        error: errorCallback 
    });

一切似乎都按预期工作。我可以忽略任何可选参数,将它们设置为null,或者给它们一个值,所有请求都可以工作。

使用变通方法更新了Microsoft错误。解决方法比这里提供的解决方案更优雅。

1 个答案:

答案 0 :(得分:1)

我不确定你是否需要可空类型。如果要传递多个日期/时间值 - 为什么不使用签名,例如

public StatisticEventArgs GetStatistic(DateTime[] startDates = null, DateTime[] endDates = null)

CustomQueryStringConverter中,您需要检查参数是否为空/空字符串,然后您的数组将为空。开始日期和结束日期将按索引相关联。只有您可能无法通过的可能组合才会有一对缺少开始日期,另一对缺少结束日期。作为解决方法,您始终可以使用一些纪元开始日期。

修改
如果您尝试在WCF服务中应用MS Connect的解决方法,则需要创建自定义服务主机工厂。例如,

public class CustomFactory : ServiceHostFactory
{
   public override ServiceHost CreateServiceHost( Type t, Uri[] baseAddresses )
   {
      return new CustomHost( t, baseAddresses )
   }
}

public class CustomHost : ServiceHost
{
   public DerivedHost( Type t, params Uri baseAddresses ) : base( t, baseAddresses ) {}

   public override void OnOpening()
   {
      var nullableTypeBehavior = new NullableTypeBehavior();
      foreach(var ep in this.Description.EndPoints)
      {
         ep.Behaviors.Add(nullableTypeBehavior);
      }
   }
}

最后在svc文件中使用@ServiceHost directive来插入工厂 - 例如:

<% @ServiceHost Factory=”CustomFactory” Service=”MyService” %>