I have a Wcf service that works as RestApi
[KnownType(typeof(myClass1))]
[KnownType(typeof(myClass2))]
[KnownType(typeof(myClassAndOther23typesOmmited))]
[DataContract]
public class ApiResult
{
[DataMember]
public bool Success { get; private set; }
[DataMember]
public object Result { get; private set; }
}
Field Result is problematic part, it cannot be serialized as it's an object type. So the question is, how to return proper ApiResult object.
Note
Althought there is KnownTypeAttribute, service throws SerializationException when i try to assign string[] to ApiResult Result field and return to client.
Upd
After trying ApiResult<T>
Service compiled successfully, and intellisense gives this
After few investigations, gathered that those weird type names are made to avoid collisions in service (it's simple hashcode of type which were achieved by GetHash() of literally object),
This is responce to nvoigt solution as couldnt insert image to comment
Why don't you create a generic ApiResult:
[DataContract]
public class ApiResult<T>
{
[DataMember]
public bool Success { get; internal set; }
[DataMember]
public T Result { get; internal set; }
}
That way, your method can actually return a typed value like ApiResult<myClass1> and you don't need any KnownTypes at all.
Related
I'm writing a console app to retrieve JSON data from a 3rd party API. I have no control over the API data structures or functionality.
Several of the calls I make will return multiple 'pages' of data. The data is a collection of objects of a certain type e.g. User.
I have created classes in my app to match the various data types from the API.
public class User
{
[JsonProperty("id")]
public int ID { get; set; }
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
}
public class FooBar
{
[JsonProperty("foo")]
public string Foo { get; set; }
[JsonProperty("bar")]
public string Bar { get; set; }
}
The API response is always in the same format for these calls. While the actual object types in the "data" array will differ depending on what call has been made.
{
"paging":{"page":1},
"data":[{<object>}, {<object>}, {<object>},...]
}
I have created a class to try to deserialize these. The dynamic[] type for the Data property is for illustrative purposes and I am happy to change it if there is a better approach.
public class ApiResponseObject
{
[JsonProperty("paging")]
public Paging PagingInfo { get; set; }
[JsonProperty("data")]
public dynamic[] Data { get; set; }
}
And I would like to have the Data collection resolve to the appropriate type for the objects it contains. e.g.
string userJson = "{\"paging\":{\"page\":1},\"data\":[{\"id\":1,\"first_name\":\"Joe\",\"last_name\":\"Bloggs\"},{\"id\":2,\"first_name\":\"Jane\",\"last_name\":\"Doe\"}]}"; // json string would come from API
string foobarJson = "{\"paging\":{\"page\":1},\"data\":[{\"foo\":\"Lorem\",\"bar\":\"Ipsum\"},{\"foo\":\"Dolor\",\"bar\":\"Amet\"}]}";
var userResponse = JsonConvert.DeserializeObject<ApiResponseObject>(userJson);
var foobarResponse = JsonConvert.DeserializeObject<ApiResponseObject>(foobarJson);
The deserialization succeeds but the Data collection is of type JObject and cannot be cast into the correct type (User, FooBar).
I am trying to avoid having to write specific response object classes for each request if possible.
I will know what type of object I am expecting in the collection when I am requesting it so I could pass that type to the deserializer but I'm not clear on how to achieve that in this particular scenario.
Something like the below psuedo code would be ideal.
var userResponse = JsonConvert.DeserializeObject<ApiResponseObject<User>>(userJson);
Thanks for your help!
You can use the generic type T, like this :
public class ApiResponseObject<T>
{
[JsonProperty("paging")]
public Paging PagingInfo { get; set; }
[JsonProperty("data")]
public T[] Data { get; set; }
}
I have classes that look like this, based on the json being returned by Slack's api:
public class Response<T>
{
public bool ok { get; set; }
public string error { get; set; }
}
public class PostMessage : Response<PostMessage>
{
public string ts { get; set; }
public string channel { get; set; }
public Message message { get; set; }
}
public class ChannelsHistory : Response<ChannelsHistory>
{
public string latest { get; set; }
public List<Message> messages { get; set; }
public bool has_more { get; set; }
}
And I want to write a single method that can bottleneck the call to JsonConvert.DeserializeObject<T>. I don't know too much about the details of the implementation behind that method, but I thought that this would work:
internal static Response<T> GetSlackResponse<T>(List<KeyValuePair<string, string>> parameters = null)
{
Uri slackUri = BuidSlackUri(typeof(T), parameters);
String jsonResponse = GetJson(slackUri);
Response<T> response = JsonConvert.DeserializeObject<Response<T>>(jsonResponse);
if (!response.ok)
{
Aesthetic.Catch("The Slack API failed to respond successfully. " + response.error);
}
return response;
}
Nothing is failing, but not all of the properties I need are being deserialized. For example, a call to GetSlackResponse() will return a Response that has ok set to true, but I won't have access to the Message property of the PostMessage class. I've tried casting (both explicitly and with as), to no avail.
I'm sure I'm missing something simple here, can someone point it out?
Having my various Response class extend a generically typed class was a red herring; it introduced nothing but making the problem more confusing. I only needed the method to be generic, not the type itself.
Changing the relevant line in GetSlackResponse<T>() method from
Response<T> response = JsonConvert.DeserializeObject<Response<T>>(jsonResponse);
to
T response = JsonConvert.DeserializeObject<T>(jsonResponse);
fixed everything. Now my various response types only need to extend my base Response class, and will be properly deserialized by JSON.NET. There is no need for the response to be a generically typed object.
I've created a WCF Data Service with a base class of my EF model.
I wanted to return a custom type (one that isn't in my EF model), but I get the error:
The server encountered an error processing the request. Please see the service help
page for constructing valid requests to the service.
My custom class looks like:
public class MyCustomClass
{
public string CustomProp { get; set; }
public List<int> Ids { get; set; }
}
How can I make this work?
You need to set up your return object as a data contract:
[DataContract]
public class MyCustomClass
{
[DataMember]
public string CustomProp { get; set; }
[DataMember]
public List<int> Ids { get; set; }
}
See also: How to accept JSON in a WCF DataService?
Linked is how to set up a receiving service, returning the values you just change the return types on your methods.
The only way I have found to do this with WCF Data Services is to pass a json string as a parameter and then deserialize it into the custom class.
Like so:
[WebGet(ResponseFormat = WebMessageFormat.Json)]
public bool ConfigurationChanged(string jsonStr)
{
try
{
MyObject obj = new JavaScriptSerializer().Deserialize<MyObject>(jsonStr);
// ... do something with MyObject
}
catch (Exception)
{
throw;
}
}
I have DataContract class which has property of type List<AnotherObject>. AnotherObject is also marked with DataContract. For some reason this property comes from wcf service as null, althought I fill it at the server. Is that by design?
Here you go. Class definitions:
[DataContract]
public class UserInfo
{
[DataMember]
public decimal UserID
{
get;
protected internal set;
}
[DataMember]
public string UserName
{
get;
protected internal set;
}
[DataMember]
public string Pswd
{
get;
protected internal set;
}
[DataMember]
public List<decimal> RoleID
{
get;
protected internal set;
}
List<UserRole> userRolesTable = new List<UserRole>();
[DataMember]
public List<UserRole> UserRoles
{
get
{
return userRolesTable;
}
protected internal set { }
}
}
[DataContract]
public class UserRole
{
[DataMember]
public decimal ROLEID { get; internal set; }
[DataMember]
public string ROLE_CODE { get; internal set; }
[DataMember]
public string ROLE_DESCRIPTION { get; internal set; }
[DataMember]
public decimal FORMID { get; internal set; }
[DataMember]
public string FORMCODE { get; internal set; }
[DataMember]
public string FORMNAME { get; internal set; }
}
UserRoles property comes as null.
Why are you letting the RoleId property be auto-implemented but not UserRoles? The code as-is won't work because you have an empty setter. You should probably just use an auto-property for it:
[DataMember]
public List<UserRole> UserRoles
{
get; set;
}
Or at least provide a meaningful setter. You setter does nothing, hence the de-serializer can't populate the value.
List<UserRole> userRolesTable = new List<UserRole>();
[DataMember]
public List<UserRole> UserRoles
{
get
{
return userRolesTable;
}
protected internal set { }
}
Your setter is empty. Put some
userRolesTable = value;
Another thing, your DataContract properties should have public setters.
Your Setter on the UserRoles property is set to internal. Because the WCF framework will be setting the property, it gives up assigning the value because it is listed as internal.
http://connect.microsoft.com/data/feedback/details/625985/wcf-client-entities-with-internal-setters-and-internalsvisibletoattribute-on-asmbly-fail
You can do what this link suggests, using the InternalsVisibleToAttribute attribute on that property, but I have never used it.
update
What I am trying to say is that I bet the Serialization works fine, the WCF framework is unable to insert the deserialized value into the host code because based upon the data contract, the internal Setter section of the property is inaccessible. use the InternalVisibleTo attribute to inform the WCF serialization framework access to the setter of the client version of your data contract object.
You need to Implement the setter...
protected internal set { userRolesTable = value; }
Basically, its a serialization problem. I had this problem in my code in the past, but it has been a while, so bear with me.
First, we need to find out if the object relations are null before the WCF call, so put a debug before and after.
If the object is being returned as null before the call, you have a few options:
You can explicitly use .Include("AnotherObject") on your DbContext to get the object. I used this by having my Read method take an array of strings which I used to include all the necessary objects. This is more ideal than automatically taking all objects because during serialization, if you take everything, you could fairly easily end up with your entire database being serialized, which introduces performance and security issues, among other things.
Another option is to use a dynamic proxy by adding the keyword virtual in front of your list. The DataContractSerializer, though, has a problem serializing dynamic proxies, so you will need to implement an attribute that uses the ProxyDataContractResolver instead of DataContractResolver. This attribute needs to be applied on all OperationContracts that can pass a dynamic proxy. This will automatically take ALL object references, which is probably bad coding practice, so I do recommend the above method.
public class ApplyDataContractResolverAttribute : Attribute, IOperationBehavior
{
public ApplyDataContractResolverAttribute() { }
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters) { }
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
}
public void Validate(OperationDescription description) { }
}
Edit: Also I think you can have setters in Data Contracts not be public, because I do it and it works fine :). But I would try making your setter public first, then solving the problem, then reverting to a protected setter, just so that you are dealing with as few variables at a time as possible.
I have created two WCF Services (Shipping & PDFGenerator). They both, along with my ClientApp, share an assembly named Kyle.Common.Contracts. Within this assembly, I have three classes:
namespace Kyle.Common.Contracts
{
[MessageContract]
public class PDFResponse
{
[MessageHeader]
public string fileName { get; set; }
[MessageBodyMember]
public System.IO.Stream fileStream { get; set; }
}
[MessageContract]
public class PDFRequest
{
[MessageHeader]
public Enums.PDFDocumentNameEnum docType { get; set; }
[MessageHeader]
public int? pk { get; set; }
[MessageHeader]
public string[] emailAddress { get; set; }
[MessageBodyMember]
public Kyle.Common.Contracts.TrackItResult[] trackItResults { get; set; }
}
[DataContract(Name = "TrackResult", Namespace = "http://kyle")]
public class TrackResult
{
[DataMember]
public int SeqNum { get; set; }
[DataMember]
public int ShipmentID { get; set; }
[DataMember]
public string StoreNum { get; set; }
}
}
My PDFGenerator ServiceContract looks like:
namespace Kyle.WCF.PDFDocs
{
[ServiceContract(Namespace="http://kyle")]
public interface IPDFDocsService
{
[OperationContract]
PDFResponse GeneratePDF(PDFRequest request);
[OperationContract]
void GeneratePDFAsync(Kyle.Common.Contracts.Enums.PDFDocumentNameEnum docType, int? pk, string[] emailAddress);
[OperationContract]
Kyle.Common.Contracts.TrackResult[] Test();
}
}
If I comment out the GeneratePDF stub, the proxy generated by VS2010 realizes that Test returns an array of Kyle.Common.Contracts.TrackResult. However, if I leave GeneratePDF there, the proxy refuses to use Kyle.Common.Contracts.TrackResult, and instead creates a new class, ClientApp.PDFDocServices.TrackResult, and uses that as the return type of Test.
Is there a way to force the proxy generator to use Kyle.Common.Contracts.TrackResult whenever I use a MessageContract? Perhaps there's a better method for using a Stream and File Name as return types?
I just don't want to have to create a Copy method to copy from ClientApp.PDFDocServices.TrackResult to Kyle.Common.Contracts.TrackResult, since they should be the exact same class.
After a lot of extra digging, I realize that it was actually the Enum that "broke" it. It has do with the way DataContractSerializer works vs. XmlSerializer. Long story short, the solution was to turn the Enum into a nullable.
[MessageContract]
public class PDFRequest
{
[MessageHeader]
public Enums.PDFDocumentNameEnum? docType { get; set; }
[MessageHeader]
public int? pk { get; set; }
[MessageHeader]
public string[] emailAddress { get; set; }
[MessageBodyMember]
public Kyle.Common.Contracts.TrackItResult[] trackItResults { get; set; }
}
I ran into the same problem (MessageContract+enums) and your post helped me. Indeed if you explicitly set the enum fields to nullable it works. The issue is that when enums are used, WCF uses the XML serializer which cannot tell null from empty string.
There is a detailed explanation of this behaviour here by one of the actual WCF team members.
In the case of document/literal when using bare messages, WCF falls back to XmlSerializer when handling enum types. ... XmlSerializer treats null as missing by default ... we encounter a schema without nillable="true" ... The detection logic for value types currently only handles primitive value types, it does not check for enums.
In other words WCF does not like enums... But hey, it works, you just need to be aware of it!
You can instruct Visual Studio to re-use classes from referenced assemblies. So if your test project has an assembly reference to the Kyle.Common.Contracts assembly, it should re-use those types defined in there rather than adding new client-side proxy classes.
The switch to enable this is on the Advanced page in the Add Service Reference dialog window - it should be on by default:
Make sure that your project
has an assembly reference to the common data contract assembly
that this setting is really ON when you add the service reference