WCF Complex JSON INPUT Ошибка (не конвертируется QueryStringConverter)

У меня возникла проблема с запуском комплекса JSON, работающего в качестве параметра в моей службе WCF.

Использование Microsoft.Net 3.5 SP1 в Visual Studio 2008 с пакетом обновления 1 (SP1)

Со следующим контрактом:

[ServiceContract]
public interface IService1
{

    [OperationContract]
    [WebGet(UriTemplate="GetDataUsingDataContract?composite={composite}", 
        BodyStyle=WebMessageBodyStyle.Wrapped, 
        RequestFormat=WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    CompositeType GetDataUsingDataContract(CompositeType composite);

    // TODO: Add your service operations here
}

// Use a data contract as illustrated in the sample below to add composite types to service operations
[DataContract]
public class CompositeType
{
    string boolValue = "true";
    string stringValue = "Hello ";

    [DataMember]
    public string BoolValue
    {
        get { return boolValue; }
        set { boolValue = value; }
    }

    [DataMember]
    public string StringValue
    {
        get { return stringValue; }
        set { stringValue = value; }
    }
}

Используя следующий URL-адрес:

http://localhost:1122/Service1.svc/GetDataUsingDataContract?composite={"BoolValue":"True","StringValue":"Hello"}

С конфигурацией Enpoint:

<system.serviceModel>
    <services>
        <service name="WebHTTPBindingExample.Service1" behaviorConfiguration="WebHTTPBindingExample.Service1Behavior">
            <host>
                <baseAddresses>
                    <add baseAddress="http://localhost:8731/Design_Time_Addresses/WebHTTPBindingExample/Service1/"/>
                </baseAddresses>
            </host>
            <!-- Service Endpoints -->
            <!-- Unless fully qualified, address is relative to base address supplied above -->
            <!--<endpoint address="" binding="wsHttpBinding" contract="WebHTTPBindingExample.IService1">
                --><!-- 
      Upon deployment, the following identity element should be removed or replaced to reflect the 
      identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity 
      automatically.
  --><!--
                <identity>
                    <dns value="localhost"/>
                </identity>
            </endpoint>-->
            <endpoint address="Web" behaviorConfiguration="ChatAspNetAjaxBehavior" binding="webHttpBinding" name="ajaxEndpoint" contract="WebHTTPBindingExample.IService1"/>
            <!-- Metadata Endpoints -->
            <!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
            <!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        </service>
    </services>
    <behaviors>
        <serviceBehaviors>
            <behavior name="WebHTTPBindingExample.Service1Behavior">
                <!-- To avoid disclosing metadata information, 
  set the value below to false and remove the metadata endpoint above before deployment -->
                <serviceMetadata httpGetEnabled="True"/>
                <!-- To receive exception details in faults for debugging purposes, 
  set the value below to true.  Set to false before deployment 
  to avoid disclosing exception information -->
                <serviceDebug includeExceptionDetailInFaults="False"/>
            </behavior>
        </serviceBehaviors>
        <endpointBehaviors>
            <behavior name="ChatAspNetAjaxBehavior">
                <webHttp/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
</system.serviceModel>

Получаем следующую ошибку:

Operation 'GetDataUsingDataContract' in contract 'IService1' has a query variable named 'composite' of type 'WebHTTPBindingExample.CompositeType', but type 'WebHTTPBindingExample.CompositeType' is not convertible by 'QueryStringConverter'.  Variables for UriTemplate query values must have types that can be converted by 'QueryStringConverter'.

Ответ 1

Я не считаю, что вам разрешено передавать сложные типы в строке запроса, используя WCF таким образом. См. этот ответ от технологии Microsoft на форумах ASP.NET - это похоже на вашу ситуацию.

Основываясь на том, как вы удалили свой метод обслуживания, кажется, что более подходящий глагол для использования будет POST или PUT, и вы можете поместить свою полезную нагрузку CompositeType в тело запроса. Но я догадываюсь о ваших намерениях.

Мне удалось заставить ваш код работать и использовать GET, изменив тип строки запроса от CompositeType до String, а затем десериализуя строку JSON в CompositeType с помощью ASP.NET JavaScriptSerializer класс. (Здесь вы можете использовать свой любимый класс помощника JSON - я неполный к JSON.NET, но я также слышу FlexJson тоже очень хорошо.)

Я не касался вашего web.config(кроме того, чтобы он работал в моем локальном тестовом приложении). Мое единственное изменение было в сигнатуре метода службы и реализации метода службы.

[ServiceContract]
public interface IService1
{

    [OperationContract]
    [WebGet(UriTemplate = "GetDataUsingDataContract?composite={composite}",
        BodyStyle = WebMessageBodyStyle.Wrapped,
        RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    CompositeType GetDataUsingDataContract(String composite);

    // TODO: Add your service operations here
}


public class Service1 : IService1
{
    public CompositeType GetDataUsingDataContract(String composite)
    {
        //use the JavaScriptSerializer to convert the string to a CompositeType instance
        JavaScriptSerializer jscript = new JavaScriptSerializer();
        CompositeType newComp = jscript.Deserialize<CompositeType>(composite);
        newComp.StringValue += " NEW!";
        return newComp;
    }

}

Надеюсь, это поможет. Дайте мне знать, если у вас есть другие вопросы.

Ответ 2

Дэвид Хёрстер - ваша ссылка с 2009 года, которая сегодня недействительна.

Если вы посмотрите на следующую документацию: http://msdn.microsoft.com/en-us/library/bb412179(v=vs.90).aspx

Вы можете видеть, что для вызова

 MyOperation(int number,Person p)

вы можете сделать следующее:

http://example.com/myservice.svc/MyOperation?number=7&p={"name":"John","age":42}

Предложения Дэвидса, возможно, упрощают многое на стороне программирования, но это противоречит всем правилам REST, которые относятся к сигнатурам методов, которые сами документируют.

Кроме того, убедитесь, что ваша привязка - webHttpBinding для преобразования из SOAP в REST.

Ответ 3

Просто используйте атрибут WebGet без параметров:

[ServiceContract]
public interface IService1
{
    [OperationContract]
    [WebGet]
    CompositeType GetDataUsingDataContract(CompositeType composite);
}

В Web.config используйте enableWebScript и webHttpBinding:

<configuration>
  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="jsonBehavior">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <services>
      <service name="Application.Service1">
        <endpoint address="json" behaviorConfiguration="jsonBehavior" binding="webHttpBinding" contract="Application.IService1" />
        <endpoint address="soap" binding="basicHttpBinding" contract="Application.IService1" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Затем вы можете вызвать свой JSON REST следующим образом:

http://localhost:29075/Service1.svc/json/GetDatausingDataContract?composite={ "BoolValue": true, "StringValue":  "Akira"}

Примечание: в этой конфигурации вы можете использовать как JSON REST, так и SOAP.

Ответ 4

[НОВЫЙ ОТВЕТ (2019]

В случае, если кто-то все еще ищет это (я только что сделал, и я нашел хорошее решение).

В файле контракта:

[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, UriTemplate = "TestBlockHeight?blockHeight={blockHeight}")]
Task<string> TestBlockHeight(BlockHeight blockHeight);

В служебном файле:

public async Task<string> TestBlockHeight(BlockHeight blockHeight)
{
    return await Task.FromResult($"Called TestBlockHeight with parameter {blockHeight}");
}

Мой пользовательский класс BlockHeight типа:

[TypeConverter(typeof(MyBlockHeightConverter))]
public class BlockHeight : IComparable<ulong>
{
    private ulong value;

    public BlockHeight(ulong blockHeight)
    {
        value = blockHeight;
    }
}

И пользовательский класс TypeConverter, который мне нужно было создать:

public class MyBlockHeightConverter : TypeConverter
{
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if(destinationType == typeof(string) && value is BlockHeight blockHeight)
        {
            return blockHeight.ToString();
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if(value is string str)
        {
            return new BlockHeight(ulong.Parse(str));
        }
        return base.ConvertFrom(context, culture, value);
    }

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if(sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            return true;
        }
        return base.CanConvertTo(context, destinationType);
    }
}

Это работает, потому что WCF использует QueryStringConverter для сериализации параметров URL, и разрешены только определенные типы. Одним из этих типов является произвольный тип, который украшен TypeConverterAttribute. Больше информации здесь:https://docs.microsoft.com/en-us/dotnet/api/system.servicemodel.dispatcher.querystringconverter?view=netframework-4.8