Serializing data using XmlArrayItemAttribute not working well - wcf

I have this DataContract, which is an array of strings:
[System.Xml.Serialization.XmlArrayAttribute(Order = 19)]
[System.Xml.Serialization.XmlArrayItemAttribute("CardNumber", typeof(string), IsNullable = false)]
[DataMember]
public string[] Cards {get; set; }
As I read, it should be serialized like this:
<Cards>
<CardNumber>123123</CardNumber>
</Cards>
but I'm still getting:
<Cards>
<string>123123</string>
</Cards>
What's wrong with it?

You can use CollectionDataContract instead.
First, create a class:
[CollectionDataContract(ItemName="CardNumber")]
public class CardsList : List<string> { }
And then, replace this line:
[DataMember]
public string[] Cards {get; set; }
By this:
[DataMember(Name="Cards")]
public CardsList Cards {get; set; };
Hope it helps.

Related

WCF with abstract base class with implemented interfaces does not serialize properly?

On the service side I have an abstract base class like so:
[DataContract]
public abstract class EntityBase : IObjectState, IDatabaseMetaData
{
[NotMapped]
[DataMember]
public ObjectState ObjectState { get; set; }
#region IDatabaseMetaData Members
[DataMember] public DateTime InsertDatetime { get; set; }
[DataMember] public int InsertSystemUserId { get; set; }
[DataMember] public DateTime? UpdateDatetime { get; set; }
[DataMember] public int? UpdateSystemUserId { get; set; }
public virtual SystemUser InsertSystemUser { get; set; }
public virtual SystemUser UpdateSystemUser { get; set; }
#endregion
}
Here is an implementing class (data contract):
[DataContract(Namespace = Constants.MyNamespace)]
public class AccountClass : EntityBase
{
[DataMember] public int AccountClassId { get; set; }
[DataMember] public string AccountClassCode { get; set; }
[DataMember] public string AccountClassDesc { get; set; }
}
On the client side I have essentially duplicated contracts. Here is the Client.AccountClass:
public class AccountClass : ObjectBase
{
private int _accountClassId;
private string _accountClassCode;
private string _accountClassDesc;
public int AccountClassId
{
get { return _accountClassId;}
set
{
if (_accountClassId == value) return;
_accountClassId = value;
OnPropertyChanged(() => AccountClassId);
}
}
public string AccountClassCode
{
get { return _accountClassCode; }
set
{
if (_accountClassCode == value) return;
_accountClassCode = value;
OnPropertyChanged(() => AccountClassCode);
}
}
public string AccountClassDesc
{
get { return _accountClassDesc; }
set
{
if (_accountClassDesc == value) return;
_accountClassDesc = value;
OnPropertyChanged(() => AccountClassDesc);
}
}
}
..and here is the parts of ObjectBase that matter:
public abstract class ObjectBase : IObjectState, IDatabaseMetaData
{
public ObjectState ObjectState { get; set; }
#region IDatabaseMetaData Members
public DateTime InsertDatetime { get; set; }
public int InsertSystemUserId { get; set; }
public DateTime? UpdateDatetime { get; set; }
public int? UpdateSystemUserId { get; set; }
#endregion
}
When I debug the service in my WcfMessageInspector.BeforeSendReply, I can see the message correctly sending the IObjectState and IDatabaseMetaData values. However, on the client side, they are always null (or default values). I have tried using KnownTypes, applying the namespace to the abstract class. The only way I can serialize everything correctly is to get rid of the interfaces and base classes all together and put the properties directly on the Client/Server AccountClass object. What am I missing here? Thanks.
Update 1
This seems to be a namespace thing. If I move my EntityBase and ObjectBase into the same CLR Namespace, everything works (with no KnownType attributes). In my client contract's AssemblyInfo.cs file I have this:
[assembly: ContractNamespace(Constants.MyNamespace, ClrNamespace = "Project.Name.Client.Entities")]
I tried adding ContractNamespaces here to no avail. Like I said, unless the EntityBase and ObjectBase are in the same namespace, it won't work. However, this is a problem for me because it creates a circular reference, unless I move a lot of stuff around.
Any idea how I can see what the full data contract (namespaces, DataMembers, etc) looks like just before/after serialization on the client/server? I tried intercepting the OnSerializing event without much luck. Thanks again.
This was a namespace issue.
I explicitly add the correct namespace to all parties involved and everything works great. One thing I notice is that the ContractNamespace's ClrNamespace in your AssemblyInfo.cs file should match the AssemblyTitle. Also, putting more than one ContractNamespace in the AssemblyInfo.cs does nothing. For example, I was doing this:
[assembly: ContractNamespace(Constants.MyNamespace, ClrNamespace = "Company.Project.Client.Entities")]
[assembly: ContractNamespace(Constants.MyNamespace, ClrNamespace = "Company.Project.Client.Entities.Core")]
Any POCO in the Company.Project.Client.Entities.Core would not serialize correctly until I explicitly put the DataContract namespace on it like so
[DataContract(Namespace = Constants.MyNamespace)]
public class SomeObject
{
[DataMember] public string SomeProperty { get; set; }
//..etc
}
Alternatively, I could have restructured the project so SomeObject was in the Company.Project.Client.Entities namespace and that would have worked.
Finally, the most helpful thing to debugging this was looking at the WSDL, and then using a custom IDispatchMessageInspector to see the actual messages AfterReceiveRequest and BeforeSendReply. Hopefully this helps someone.

Sitecore IndexField: resulting IEnumerable is empty

I have the following fields in my model:
public virtual IEnumerable<Person> Authors { get; set; }
public virtual IEnumerable<ExternalContributor> External_Contributors { get; set; }
[IndexField("Authors")]
[TypeConverter(typeof(IndexFieldGuidValueConverter))]
public virtual IEnumerable<Guid> AuthorIds { get; set; }
[IndexField("External Contributors")]
[TypeConverter(typeof(IndexFieldGuidValueConverter))]
public virtual IEnumerable<Guid> ExternalContributorIds { get; set; }
and I have a MultiList of GuiD in the fields "Authors" and "External Contributors". When I try to access those fields, "Authors" is populated with a list of objects, while External_Contributors is always empty.
Is there something obvious I am missing here?
EDIT:
Here are the definitions for Person and ExternalContributor:
[SitecoreType(TemplateId = "{2CD821FC-A334-49F4-93B9-CB0D8E7D71FF}", AutoMap = true)]
public class Person : ImageTemplate, ITagged, IViewImage, IViewCover, ISectors, ISpecialisms, IEquatable<Person>
{
public static string ParentPath = "/sitecore/content/Data/People";
public static Guid Template = new Guid("{2CD821FC-A334-49F4-93B9-CB0D8E7D71FF}");
[...various fields...]
}
}
[SitecoreType(TemplateId = "{7C35993C-140B-43FE-A00A-7ADA00A2A488}", AutoMap = true)]
public class ExternalContributor : ImageTemplate, ITagged, IViewImage, IEquatable<ExternalContributor>
{
public static string ParentPath = "/sitecore/content/Blue Rubicon Data/external-contributors";
public static Guid Template = new Guid("{7C35993C-140B-43FE-A00A-7ADA00A2A488}");
[...various fields...]
}
}
What about something like this:
[IndexField("External_Contributors")]
I'm not sure, but I've never seen a index field with spaces, I don't know if the fieldname translator (if it still exists) would fix it.
You should re-index your items after applying your change before it may work.

Unable to sending complex object through WCF

I have a ServiceContract as such :
bool CreateSlideshow(Slideshow current, string path, string name);
When I run my program calling the service, I get the following exception :
There was an error while trying to serialize parameter
http://tempura.org/:current. The InnerException message was 'Type
'System.DelegateSerializationHolder+DelegateEntry' with data contract
name
'DelegateSerializationHolder.DelegateEntry:http://schemas.datacontract.org/2004/07/System'
is not expected. Consider using a DataContractResolver or add any
types not known statically to the list of known types.....
Apparently the problem lies in Slideshow. My class Slideshow has the following members :
private String name;
private String path;
private List<Slide> slides;
and the class Slide has the following members :
private String title;
private ContentTypeEnum contentType;
private String textContent;
private String pictureContextPath;
where ContentTypeEnum is an enumeration.
Any idea how to resolve the exception? Hope for some advice/suggestions. Thanks in advance.
Your used types have to be marked with the [DataContract] attribute. The properties have to be marked with the [DataMember] attribute.
Furthermore your fields should be public properties, because a datacontract doesn't make any sense with private fields only.
[DataContract]
public class Slideshow
{
[DataMember]
public String Name { get; set; }
[DataMember]
public String Path { get; set; }
[DataMember]
public List<Slide> Slides { get; set; }
}
[DataContract]
public class Slide
{
[DataMember]
public String Title { get; set; }
[DataMember]
public ContentTypeEnum ContentType { get; set; }
[DataMember]
public String TextContent { get; set; }
[DataMember]
public String PictureContextPath { get; set; }
}

Returning IEnumerable<> from WCF service causes exception: The underlying connection was closed: The connection was closed unexpectedly

When I return IEnumerable<ProgramRange> an exception is thrown:
The underlying connection was closed: The connection was closed unexpectedly.
The class ProgramRange looks like this:
[DataContract]
public partial class ProgramRange
{
public ProgramRange()
{
this.GradeVariants = new HashSet<GradeVariant>();
}
[DataMember]
public int ID { get; set; }
[DataMember]
public int Range { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public virtual ICollection<GradeVariant> GradeVariants { get; set; }
}
The collection only contains eight items, so I don't think it's the <dataContractSerializer maxItemsInObjectGraph="2147483647" /> which is often suggested.
When I fetch the data from the context I do .ToList() so it can't be that:
public IEnumerable<ProgramRange> GetAll()
{
using (Entities dbContext = new Entities())
{
return dbContext.ProgramRanges.ToList();
}
}
I've tried to add the ProgramRange class to the known types of your service in the implementation:
[ServiceBehavior]
[ServiceKnownType(typeof(ProgramRange))]
public class ValidationService : IValidationService
I've tried returning several other things just to test: ProgramRange (works), IEnumerable<string> (works), List<ProgramRange> (does't work)
I know there are several question on the subject here on stackowerflow and I tried a lot of suggestions, but I can't get it to work.
Edit:
Here's the GradeVariant class:
[DataContract]
public partial class GradeVariant
{
public GradeVariant()
{
this.GradeVariantRules = new HashSet<GradeVariantRule>();
}
[DataMember]
public int ID { get; set; }
[DataMember]
public int GradeTypeID { get; set; }
[DataMember]
public int ProgramRangeID { get; set; }
[DataMember]
public Nullable<int> ProgramID { get; set; }
[DataMember]
public Nullable<int> ApprenticeID { get; set; }
[DataMember]
public Nullable<int> Prefix { get; set; }
[DataMember]
public Nullable<bool> IV { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public virtual GradeType GradeType { get; set; }
[DataMember]
public virtual ProgramRange ProgramRange { get; set; }
[DataMember]
public virtual ICollection<GradeVariantRule> GradeVariantRules { get; set; }
}
Edit :
You are using nullable objects, this is not allowed in a wcf service.
Add [IgnoreDataMember] in front of these members or make them not nullable to fix your issue.
Could you post the class GradeVariant as well? It's possible there are certain unparsable elements in that class which throw the connection was closed unexpectedly.
A few posibilities are a dictionary or nullable item.
Your test with a single ProgramRange might work because the list of GradeVariants is empty or null.
A good tip to try out is to check the inner exception of the inner exception of the inner exception....
At the end of the line it usually says something like can't parse Dictionary.
If it's the serialization (which it probably is), try this more directly to see if you can find the issue:
try
{
MemoryStream tempWrite = new MemoryStream();
DataContractSerializer ds = new DataContractSerializer(typeof(ProgramRange));
ds.WriteObject(myProgramRangeInstance, tempWrite);
MemoryStream tempRead = new MemoryStream(tempWrite.GetBuffer());
ProgramRange newInstance = (ProgramRange)ds.ReadObject(tempRead);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
Try that with myProgramRangeInstance starting out as a valid instance of your class. That'll tell you if it's a serialization issue, and what exactly is causing it.
Right now, I'm betting on the fact that you're using a HashSet, but I really don't know. The exception information from the above should give more information.
I found the answer to my own question and here goes:
The problem was that WCF was unable to serialize the circular reference that Entity Framework created when loading the referenced entities.
To resolve this issue, simply put
[DataContract(IsReference = true)]
over the model class.
It's explained in detail here, http://www.binaryforge-software.com/wpblog/?p=129

How to get json response of a custom object using wcf REST services?

How can I serialize an object to return a custom type?
//The response is null.
http://localhost:50604/GameService/Getbyid?id=1
[WebGet(ResponseFormat = WebMessageFormat.Json)]
public MyClass GetById(int id)
[DataContract]
[KnownType(typeof(User))]
public partial class MyClass
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public int? CreatedBy { get; set; }
[DataMember]
public virtual User CreatedByUser { get; set; } //How will I serialize this?
}
You are missing UriTemplate for your operation so your Id is probably never passed in and your method works with default value = 0.
Try this:
[WebGet(UriTemplate="Getbyid?id={id}", ResponseFormat = WebMessageFormat.Json)]
public MyClass GetById(int id)
CreatedByUser will be serialized automatically if filled and if User is data contract as well.