Подтвердить что ты не робот

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'.
4b9b3361

Ответ 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