WCF Complex JSON INPUT Error (not convertible by QueryStringConverter)
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.
[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