Im having issue geting Complex JSON working as a parameter in my WCF Service.
Using, Microsoft.Net 3.5 SP1 in Visual Studio 2008 SP1
With the following Contract:
[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; }
}
}
Using the following URL:
http://localhost:1122/Service1.svc/GetDataUsingDataContract?composite={"BoolValue":"True","StringValue":"Hello"}
With the Enpoint Configuration:
<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>
I Get the follwoing Error:
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'.
[NEW ANSWER (2019]
In case somebody is still searching for this (I just did, and I figured out a good solution).
In contract file:
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, UriTemplate = "TestBlockHeight?blockHeight={blockHeight}")]
Task<string> TestBlockHeight(BlockHeight blockHeight);
In service file:
public async Task<string> TestBlockHeight(BlockHeight blockHeight)
{
return await Task.FromResult($"Called TestBlockHeight with parameter {blockHeight}");
}
My custom BlockHeight
type class:
[TypeConverter(typeof(MyBlockHeightConverter))]
public class BlockHeight : IComparable<ulong>
{
private ulong value;
public BlockHeight(ulong blockHeight)
{
value = blockHeight;
}
}
And the custom TypeConverter
class that I needed to create:
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);
}
}
This works because WCF uses a QueryStringConverter
to serialize url parameters, and only specific types are allowed. One of these types is an arbitrary type which is decorated with TypeConverterAttribute
. More info here:
https://docs.microsoft.com/en-us/dotnet/api/system.servicemodel.dispatcher.querystringconverter?view=netframework-4.8
I don't believe you're allowed to pass complex types on the query string using WCF this way. See this response from a Microsoft tech in the ASP.NET forums - it's similar to your situation.
Based on how you've stubbed out your service method, it seems like a more appropriate verb to use would be POST or PUT, and you could put your CompositeType
payload in the request body. But I'm guessing as to your intention.
I was able to make your code work and still use a GET by changing the query string type from CompositeType
to String
and then deserializing the JSON string into a CompositeType
by using the ASP.NET JavaScriptSerializer
class. (You can use your favorite JSON helper class here -- I'm partial to JSON.NET, but I also hear FlexJson is very good, too.)
I didn't touch your web.config (except to make it work in my local test app). My only change was in the service method signature and the implementation of the service method.
[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;
}
}
I hope this helps. Let me know if you have other questions with this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With