Может ли контракт службы WCF иметь входной параметр, допускающий значение NULL?

У меня есть контракт, определенный следующим образом:

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

Я получаю исключение: [InvalidOperationException: операция «GetX» в контракте «IMyGet» имеет переменную запроса с именем «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, который устарел и в любом случае не является решением.

есть идеи, что делать, кроме как избавиться от параметра, допускающего значение NULL?

благодаря.


person Ami    schedule 08.09.2009    source источник


Ответы (4)


Да, вы можете иметь параметры, допускающие значение NULL, с помощью WCF. Я думаю, ваша проблема в том, что QueryStringConverter не работает с параметрами, допускающими значение NULL.

Что делать? Вам нужно использовать атрибут UriTemplate? Если бы вы опубликовали это как «классический веб-сервис», у вас не было бы этой проблемы.

Другой вариант - следовать совету в предоставленной вами ссылке, то есть получить параметр myX в виде строки, а затем преобразовать его в int ?, где (скажем) «n» имеет значение null. Не очень.

person Kirk Broadhurst    schedule 08.09.2009

Есть решение этой проблемы, не требующее никаких взломов. Это может показаться большим трудом, но на самом деле это не так и имеет большой смысл, если вы его прочитаете. Суть проблемы в том, что действительно существует неразрешенная ошибка (начиная с .NET 4), которая означает, что WebServiceHost не использует настраиваемые QueryStringConverters. Итак, вам нужно немного поработать и понять, как работает конфигурация WCF для WebHttpEndpoints. Ниже представлено решение для вас.

Во-первых, настраиваемый 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-файлу использовать его, что приведет к использованию всего настраиваемого кода. Конечно, вы также можете создать настраиваемый элемент, который позволил бы вам добавить поведение в конфигурацию, но я думаю, что для этого поведения лучше использовать подход на основе кода, поскольку маловероятно, что вы захотите удалить возможность обработки типов, допускающих значение NULL, так как это нарушит вашу службу:

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

Теперь вы можете без проблем использовать типы, допускающие значение NULL, в интерфейсе службы, просто опуская параметр или задав для него пустую строку. Следующие ресурсы могут быть вам более полезны:

Надеюсь это поможет!

person Xcalibur    schedule 19.01.2012
comment
Мне нравится это решение. - person Sawyer; 07.08.2013
comment
Это чертовски много работы для чего-то, что Microsoft не стоило бы придумать. - person crush; 06.02.2014
comment
Какой сюрприз, Microsoft превращает то, что должно быть легко, во что-то мучительно сложное ... Хороший ответ. - person Jim; 23.07.2014
comment
Я создал одну библиотеку и добавил эти 4 файла классов и сослался на все соответствующие пространства имен, а также дал ссылку на библиотеку dll на приложение wcfservice, но все равно отображается та же ошибка ... чего мне не хватает, пожалуйста, помогите мне .. - person Suganth G; 19.11.2015
comment
Ух ты. Большое спасибо за это решение. Разве это не так странно? - person Oswald; 23.10.2017

На самом деле ... вы абсолютно можете иметь параметры, допускающие значение NULL, или любой другой тип параметра, который не поддерживается QueryStringConverter из коробки. Все, что вам нужно сделать, это расширить QueryStringConverter для поддержки любого типа, который вам может понадобиться. См. Принятый ответ в этом сообщении ==>

В Модель веб-программирования WCF, как можно написать контракт операции с массивом параметров строки запроса (то есть с тем же именем)?

person WayneC    schedule 27.02.2010
comment
В приведенной выше ссылке на код есть ошибка, которая делает производные классы QueryStringConverter непригодными для использования в платформе 4. Прежде чем пытаться это сделать, убедитесь, что вы ознакомились с ошибкой. Я потратил много времени, прежде чем обнаружил, что на практике это не работает. - person Jim; 27.05.2011

Хм, быстрое решение (не очень красивое) - принять параметр, допускающий значение NULL, как строку в соответствующих кодах интерфейса и службы WCF.

person wolf354    schedule 30.11.2017