C# XML Serializable Error - c#

i am trying to XML serialize a class object but having the following problem:
the code compiles fine and the messagebox displays all the correct data but when i view the XML it does not seem to include the data for the actual transfer i.e. FireGridLocation data is missing from the XML.
XmlSerializer s;
StringWriter w;
FireGridUnit fireGridUnit = new FireGridUnit();
fireGridUnit.FireGridLocation = new GridUnit(GridLock.getColumn, GridLock.getRow);
MessageBox.Show("gridlock col " + GridLock.getColumn);
MessageBox.Show("column fire " + fireGridUnit.FireGridLocation.getColumn);
MessageBox.Show("row fire " + fireGridUnit.FireGridLocation.getRow);
s = new XmlSerializer(typeof(FireGridUnit));
w = new StringWriter();
s.Serialize(w, fireGridUnit);
MessageBox.Show(w.ToString());
alt text http://img52.imageshack.us/img52/220/errorce.jpg
here is the FireGridUnit:
[Serializable]
public class FireGridUnit
{
/// <summary>
/// Location storage
/// </summary>
//public GridUnit FireGridLocation { get; set; }
public GridUnit FireGridLocation;
}
and here is the GridUnit class:
public class GridUnit
{
/// <summary>
/// Default initialization
/// </summary>
public GridUnit()
{
Column = -1;
Row = -1;
}
/// <summary>
/// Initialize to supplied coordinate
/// </summary>
/// <param name="column"></param>
/// <param name="row"></param>
public GridUnit(int column, int row)
{
Column = column;
Row = row;
}
/// <summary>
/// Set/Return Column
/// </summary>
//public int Column { get; set; }
private int Column;
public int getColumn
{
get { return Column; }
}
/// <summary>
/// Set/Return Row
/// </summary>
//public int Row { get; set; }
private int Row;
public int getRow
{
get { return Row; }
}
}
if you can assist with this issue, your input is very welcome.

XML serialization can only serialize read/write properties. Your getColumn and getRow properties are read-only, so they can't be serialized.
BTW, the Serializable attribute is not necessary for XML serialization

Related

C# - After executing the remove statement , list stays the same

Everyone , I tried to remove item based on condition but it seems that after executing the remove statement my list stays the same .
class Menu
public class Menu : Entity
{
/// <summary>
/// Tạo mới guid khi gọi tới menu
/// </summary>
public Menu()
{
Random rnd = new Random();
Id = rnd.Next(1,999999) * DateTime.Now.Millisecond;
Guid = Guid.NewGuid();
}
/// <summary>
/// Danh sách Role
/// </summary>
[Required(ErrorMessage = nameof(EnumManageMenu.ENM01))]
public ICollection<string> Roles { get; set; }
/// <summary>
/// Danh sách Permisions
/// </summary>
[Required(ErrorMessage = nameof(EnumManageMenu.ENM02))]
public ICollection<string> Permisions { get; set; }
/// <summary>
/// Tên của từng menu
/// </summary>
[Required(ErrorMessage = nameof(EnumManageMenu.ENM03))]
[MaxLength(200, ErrorMessage = nameof(EnumManageMenu.ENM07))]
public string Name { get; set; }
/// <summary>
/// Đường dẫn
/// </summary>
[Required(ErrorMessage = nameof(EnumManageMenu.ENM09))]
[MaxLength(1000, ErrorMessage = nameof(EnumManageMenu.ENM08))]
public string Link { get; set; }
/// <summary>
/// Mỗi menu sẽ chứa một loại menu
/// </summary>
public ICollection<Menu> Items { get; set; }
/// <summary>
/// Mô tả thêm của từng menu
/// </summary>
[MaxLength(2000, ErrorMessage = nameof(EnumManageMenu.ENM11))]
public string Description { get; set; }
/// <summary>
/// Loại menu
/// </summary>
[Required(ErrorMessage = nameof(EnumManageMenu.ENM03))]
[MaxLength(1000, ErrorMessage = nameof(EnumManageMenu.ENM013))]
public string EnumCode { get; set; }
/// <summary>
/// Icon của menu
/// </summary>
[Required(ErrorMessage = nameof(EnumManageMenu.ENM016))]
[MaxLength(1000, ErrorMessage = nameof(EnumManageMenu.ENM017))]
public string Icon { get; set; }
/// <summary>
/// Trạng thái của menu
/// </summary>
public bool Active { get; set; }
/// <summary>
/// Sắp xếp danh sách menu
/// </summary>
public int SortOrder { get; set; }
}
below is my code
public async Task<MethodResult<List<MenuModel>>> GetAllMenuAsync()
{
MethodResult<List<MenuModel>> methodResult = new MethodResult<List<MenuModel>>();
List<Menu> empty = new List<Menu>();
List<Menu> menus = await _dbCollection.Find(FilterDefinition<Menu>.Empty).ToListAsync();
empty = menus;
int countList = menus.Count;
for (int i = 0; i < countList; i++)
{
foreach (var item in GetListNoDel(menus[i]))
{
if (item.IsDeleted)
{
empty.Remove(item);
}
}
}
List<MenuModel> menuModels = _mapper.Map<List<MenuModel>>(empty);
methodResult.Result = menuModels;
return methodResult;
}
private IEnumerable<Menu> GetListNoDel(Menu menus)
{
yield return menus;
foreach (var item in menus.Items)
{
foreach (var items in GetListNoDel(item))
{
yield return items;
}
}
}
You cannot "foreach" over a list and immediately remove items (as you undoubtedly know). So you will have to first find the items to remove, and then remove them. A twist in your case is that those "items to remove" can be at any depth in a menu tree.
So when you find an item to remove, also remember which list it appears in. Then you can directly remove it from that list.
void MainMethod()
{
var menulist = new List<Menu>(); // omitted: fill list
// get the *full* list of items to remove,
// so you don't change a list that is still used in a foreach
var todelete = ToDelete(menulist).ToList();
// and remove them from their parent list
foreach (var todo in todelete)
{
todo.list.Remove(todo.child);
}
}
private IEnumerable<(List<Menu> list, Menu child)> ToDelete(List<Menu> menuitems)
{
foreach (var item in menuitems)
{
if (item.IsDeleted)
{
// now we know that "item" must be removed from "menuitems"
yield return (menuitems, item);
}
else
{
// if the parent is deleted, then all children will be gone as well
// - so only recursively check non-deleted items
// pass the results of a recursive call up the call stack
foreach (var pairs in ToDelete(item.Items))
{
yield return pairs;
}
}
}
}

"About the "code" tag in the RichText field of "Contentful dotnet SDK

RichText in Contentful is deserialized to Document type, and Document is converted to MarkupString type for use. (I created an extension method).
When using the "code" tag in Contentful's RichTextEditor, the "pre" tag does not exist in the parent element, so line breaks and indentation are ignored by the browser.
Is there a way to add a parent element to any HTML tag?
public static MarkupString ToHtml(this Document doc)
{
var renderer = new HtmlRenderer();
var html = renderer.ToHtml(doc).GetAwaiter().GetResult();
return (MarkupString)html;
}
using Blazor ServerSide.
<div>
#entry.Content.ToHtml()
</div>
Model
public class ContentfulEntry
{
public SystemProperties Sys { get; set; }
public string Title { get; set; }
public Document Content { get; set; }
public string Description { get; set; }
public Asset Cover { get; set; }
}
Implement a custom renderer for Text:
public class CustomTextRenderer : IContentRenderer
{
/// <summary>
/// The order of this renderer in the collection.
/// </summary>
public int Order { get; set; } = 90;
/// <summary>
/// Whether or not this renderer supports the provided content.
/// </summary>
/// <param name="content">The content to evaluate.</param>
/// <returns>Returns true if the content is a textual node, otherwise false.</returns>
public bool SupportsContent(IContent content)
{
return content is Text;
}
/// <summary>
/// Renders the content to a string.
/// </summary>
/// <param name="content">The content to render.</param>
/// <returns>The content as a string.</returns>
public string Render(IContent content)
{
var text = content as Text;
var sb = new StringBuilder();
if (text.Marks != null)
{
foreach (var mark in text.Marks)
{
if(mark == "code">) {
sb.Append("<pre>");
}
sb.Append($"<{MarkToHtmlTag(mark)}>");
}
}
sb.Append(text.Value);
if (text.Marks != null)
{
foreach (var mark in text.Marks)
{
sb.Append($"</{MarkToHtmlTag(mark)}>");
if(mark == "code">) {
sb.Append("</pre>");
}
}
}
return sb.ToString();
}
private string MarkToHtmlTag(Mark mark)
{
switch (mark.Type)
{
case "bold":
return "strong";
case "underline":
return "u";
case "italic":
return "em";
case "code":
return "code";
}
return "span";
}
/// <summary>
/// Renders the content asynchronously.
/// </summary>
/// <param name="content">The content to render.</param>
/// <returns>The rendered string.</returns>
public Task<string> RenderAsync(IContent content)
{
return Task.FromResult(Render(content));
}
}
Then add it to your HTML renderers collection of renderers:
public static MarkupString ToHtml(this Document doc)
{
var renderer = new HtmlRenderer();
renderer.AddRenderer(new CustomTextRenderer());
var html = renderer.ToHtml(doc).GetAwaiter().GetResult();
return (MarkupString)html;
}
Note that the Order property controls the order in which renderers are evaluated. This means this custom renderer will be evaluated before the default ones.

Array types with <example> tag not working with Swagger (swashbuckle.aspnetcore)

I am using the summary and example tags for the swagger documentation.
I have a problem with the tag, it's not recognized by swagger when I use array :
I use swashbuckle.aspnetcore package Nuget.
Example :
[DataContract]
public class HeaderResponse
{
/// <summary>
/// Statut code
/// </summary>
///<example>400</example>
[DataMember]
public int StatusCode { get; set; }
/// <summary>
/// Title
/// </summary>
/// <example>Erreur sur methode1</example>
[DataMember]
public string Title { get; set; }
/// <summary>
/// List of errors
/// </summary>
///<example>["entry1", "entry2", "entry3"]</example>
[DataMember]
public List<string> ErrorList { get; } = new List<string>();
}
On swagger documentation, array is not interpreted :
I found others solutions by using ISchemaFilter like this :
public class SwaggerExcludeFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
switch (context.Type.Name)
{
case "HeaderResponse":
foreach (var property in schema.Properties)
{
if (property.Value.Type == "array")
{
var array = new OpenApiArray();
array.Add(new OpenApiString("item1"));
array.Add(new OpenApiString("item2"));
array.Add(new OpenApiString("item3"));
property.Value.Example = array;
}
}
break;
}
}
}
Is there no other way than to use ISchemaFilter to handle tags of type array?
You can try to do like this:
public List<string> ErrorList { get; } = new List<string>{"entry1", "entry2", "entry3"};
or:
[DataContract]
public class HeaderResponse
{
public HeaderResponse()
{
ErrorList = new List<string> {"entry1", "entry2", "entry3" };
}
/// <summary>
/// Statut code
/// </summary>
///<example>400</example>
[DataMember]
public int StatusCode { get; set; }
/// <summary>
/// Title
/// </summary>
/// <example>Erreur sur methode1</example>
[DataMember]
public string Title { get; set; }
/// <summary>
/// List of errors
/// </summary>
[DataMember]
public List<string> ErrorList { get; set; }
}
Here is a demo:
[HttpPost("TestPar")]
public IActionResult TestPar(HeaderResponse h)
{
return Json(h);
}
result:
The quotes must be escaped using "
/// <summary>
/// List of errors
/// </summary>
///<example>["entry1","entry2","entry3"]</example>
[DataMember]
public List<string> ErrorList { get; } = new List<string>();
I have the same problem and I try this. you can add a condition on the name of the property and add the null return in order not to lose the example on the other properties. But the best solution would be to be able to find a generic solution. How is it possible to get the example tag and the property name with ISchemaFilter ? This is my solution :
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
schema.Example = GetExampleOrNullFor(schema, context);
}
private IOpenApiAny GetExampleOrNullFor(OpenApiSchema schema, SchemaFilterContext context)
{
switch (context.Type.Name)
{
case "MyClass":
foreach (var property in schema.Properties)
{
//Array property, which wraps its elements
if (property.Value.Type == "array")
{
if (property.Key == "MyProperty1")
{
var array = new OpenApiArray();
array.Add(new OpenApiString("item1"));
array.Add(new OpenApiString("item2"));
property.Value.Example = array;
}
else if (property.Key == "MyProperty2")
{
var array = new OpenApiArray();
array.Add(new OpenApiString("item1"));
property.Value.Example = array;
}
}
}
return null;
default:
return null;
}
}
Support for examples using XML comments on arrays was added in Swashbuckle.AspNetCore version 6.0.0.
It's mentioned in this GitHub issue and their documentation shows the following example:
/// <summary>
/// The sizes the product is available in
/// </summary>
/// <example>["Small", "Medium", "Large"]</example>
public List<string> Sizes { get; set; }

How would I set a model property to the result of an extension's method?

I have a data model consisting of a couple of properties and methods, this data model inherits from an interface. However, I've now created a static extension and would like to set one of my model properties to the result of that extension method.
These are my models and extensions:
PublishedContentModel:
namespace CMS.Core.Models
{
public class PublishedContentModel : IPublishedContent
{
/// <summary>
/// The id of this node.
/// </summary>
public Int32 Id { get; set; }
/// <summary>
/// The name of this node.
/// </summary>
public String Name { get; set; }
/// <summary>
/// The url path.
/// </summary>
public String Path { get; set; }
/// <summary>
/// This nodes document id.
/// </summary>
public Int32 DocumentId { get; set; }
/// <summary>
/// The parent node's id.
/// </summary>
public IPublishedContent Parent { get; set; }
/// <summary>
/// The collection of this node's children.
/// </summary>
public IEnumerable<IPublishedContent> Children { get; set; }
/// <summary>
/// The collection of ancestors.
/// </summary>
public IEnumerable<IPublishedContent> Ancestors ()
{
IEnumerable<IPublishedContent> colContentNodes = ContentService.GetAllContentNodes();
List<IPublishedContent> colAncestors = new List<IPublishedContent>();
foreach (IPublishedContent objContentNode in colContentNodes.Where(x => x.Descendants().Any(y => y.Id == Id)))
{
colAncestors.Add(objContentNode);
}
return colAncestors;
}
/// <summary>
/// The collection of all nodes under this.
/// </summary>
public IEnumerable<IPublishedContent> Descendants ()
{
// - Get the children and create a collection for descendants.
IEnumerable<IPublishedContent> colChildren = Children;
List<IPublishedContent> colDescendants = new List<IPublishedContent>();
if (colChildren.Any())
{
colDescendants = colChildren.ToList();
foreach (IPublishedContent objChild in colChildren)
{
IEnumerable<IPublishedContent> colChildsChildren = objChild.Children;
// - Check if this node has children.
if (colChildsChildren.Any())
{
colDescendants.AddRange(colChildsChildren);
}
}
}
return colDescendants;
}
}
}
ContentModelExtensions:
namespace CMS.Core.Models
{
public static class ContentModelExtensions
{
public static IEnumerable<IPublishedContent> Children (this IPublishedContent Content)
{
IEnumerable<IPublishedContent> colContentNodes = ContentService.GetAllContentNodes();
List<IPublishedContent> colChildren = new List<IPublishedContent>();
foreach (IPublishedContent objContentNode in colContentNodes.Where(x => x.Parent != null))
{
colChildren.Add(objContentNode);
}
return colChildren;
}
public static IEnumerable<T> Children<T> (this IPublishedContent Content) where T : class, IPublishedContent
{
IEnumerable<IPublishedContent> colContentNodes = ContentService.GetAllContentNodes();
List<T> colChildren = new List<T>();
foreach (T objContentNode in colContentNodes.Where(x => x.Parent != null))
{
colChildren.Add(objContentNode);
}
return colChildren;
}
}
}
The issue with this code is that I can't do Children.Any() seeing as the IEnumerable for this is null. I tried fixing this by setting the Children property when it gets sent to the view, like this:
objContentNode = new PublishedContentModel ()
{
Id = Convert.ToInt32(objReader["Id"]),
Name = Convert.ToString(objReader["Name"]),
Parent = GetContentNodeById(Convert.ToInt32(objReader["ParentId"])),
Path = Convert.ToString(objReader["Path"]),
DocumentId = Convert.ToInt32(objReader["DocumentId"]),
Children = ContentModelExtensions.Children(objContentNode)
};
Put that just makes the connection timeout, any help would be appreciated. Let me know if I haven't explained my issue well enough.
TLDR:
I want to set public IEnumerable<IPublishedContent> Children { get; set; } to the result of ContentModelExtensions.Children()

Protobuf-net object graph reference with surrogate

As far as I know, protobuf-net starting from v2 supports references but they cannot be used in conjunction with surrogate (exception "A reference-tracked object changed reference during deserialization" is thrown in this case)
I'm wondering if there is some workaround that I didn't consider to make it works.
Here following the code of my test case that reproduces the above exception.
Classes
public class Person
{
public Person(string name, GenderType gender)
{
Name = name;
Gender = gender;
}
public string Name { get; set; }
public GenderType Gender { get; set; }
}
[Flags]
public enum GenderType : byte
{
Male = 1,
Female = 2,
Both = Male | Female
}
public class Family
{
public Family(List<Person> people, Person familyHead = null)
{
People = people;
FamilyHead = familyHead;
}
public List<Person> People { get; set; }
public Person FamilyHead { get; set; }
}
public class PersonSurrogate
{
public string Name { get; set; }
public byte Gender { get; set; }
public PersonSurrogate(string name, byte gender)
{
Name = name;
Gender = gender;
}
#region Static Methods
public static implicit operator Person(PersonSurrogate surrogate)
{
if (surrogate == null) return null;
return new Person(surrogate.Name, (GenderType)surrogate.Gender);
}
public static implicit operator PersonSurrogate(Person source)
{
return source == null ? null : new PersonSurrogate(source.Name, (byte)source.Gender);
}
#endregion
}
public class FamilySurrogate
{
public FamilySurrogate(List<Person> people, Person familyHead)
{
People = people;
FamilyHead = familyHead;
}
public List<Person> People { get; set; }
public Person FamilyHead { get; set; }
#region Static Methods
public static implicit operator Family(FamilySurrogate surrogate)
{
if (surrogate == null) return null;
return new Family(surrogate.People, surrogate.FamilyHead);
}
public static implicit operator FamilySurrogate(Family source)
{
return source == null ? null : new FamilySurrogate(source.People, source.FamilyHead);
}
#endregion
}
Serializer
/// <summary>
/// Class with model for protobuf serialization
/// </summary>
public class FamilySerializer
{
public GenderType GenderToInclude;
public FamilySerializer(Family family, GenderType genderToInclude = GenderType.Both)
{
GenderToInclude = genderToInclude;
Family = family;
Init();
}
private void Init()
{
Model = RuntimeTypeModel.Create();
FillModel();
Model.CompileInPlace();
}
public FamilySerializer()
{
Init();
}
public Family Family { get; set; }
public RuntimeTypeModel Model { get; protected set; }
protected virtual void FillModel()
{
Model = RuntimeTypeModel.Create();
Model.Add(typeof(Family), false)
.SetSurrogate(typeof(FamilySurrogate));
MetaType mt = Model[typeof(FamilySurrogate)];
mt.Add(1, "People");
mt.AddField(2, "FamilyHead").AsReference = true; // Exception "A reference-tracked object changed reference during deserialization" - because using surrogate.
mt.UseConstructor = false;
Model.Add(typeof(Person), false)
.SetSurrogate(typeof(PersonSurrogate));
mt = Model[typeof(PersonSurrogate)]
.Add(1, "Name")
.Add(2, "Gender");
mt.UseConstructor = false; // Avoids to use the parameterless constructor.
}
public void Save(string fileName)
{
using (Stream s = File.Open(fileName, FileMode.Create, FileAccess.Write))
{
Model.Serialize(s, Family, new ProtoBuf.SerializationContext(){Context = this});
}
}
public void Open(string fileName)
{
using (Stream s = File.Open(fileName, FileMode.Open, FileAccess.Read))
{
Family = (Family)Model.Deserialize(s, null, typeof(Family), new ProtoBuf.SerializationContext(){Context = this});
}
}
}
Test Case
private Family FamilyTestCase(string fileName, bool save)
{
if (save)
{
var people = new List<Person>()
{
new Person("Angus", GenderType.Male),
new Person("John", GenderType.Male),
new Person("Katrina", GenderType.Female),
};
var fam = new Family(people, people[0]);
var famSer = new FamilySerializer(fam);
famSer.Save(fileName);
return fam;
}
else
{
var famSer = new FamilySerializer();
famSer.Open(fileName);
if (Object.ReferenceEquals(fam.People[0], fam.FamilyHead))
{
// I'd like this condition would be satisfied
}
return famSer.Family;
}
}
I think for now this is just an unsupported scenario and I'm unaware of a way to make it magically work; it may be something that I can get back to at some point, but there are many much higher priority things that would take precedence.
My usual advice here - and this applies to any serializer, not just protobuf-net: any time you find yourself hitting a limitation of the serializer, or even just something that is awkward to configure in the serializer: stop fighting the serializer. This kind of problem almost always arises when people try to serialize their regular domain model, and something in the domain model isn't a perfect fit for their chosen serializer. Instead of trying arcane magic: split your model - have your domain model be a good fit for what you want your application to see, and create a separate model that is a great fit for your serializer. Then you don't need concepts like "surrogates". If you're using multiple serialization formats, or have multiple different "versions" of layout in the same serialization format: have multiple serialization models.
It really isn't worth the headache of trying to make on model serve multiple masters.
Since I understand that will be not a supported scenario, I found a way to handle this and I'd like to share my complete solution, in case someone else needs this (or if someone else wanted to share a better solution or improve my approach)
Classes
public class Person
{
public Person(string name, GenderType gender)
{
Name = name;
Gender = gender;
}
public string Name { get; set; }
public GenderType Gender { get; set; }
}
[Flags]
public enum GenderType : byte
{
Male = 1,
Female = 2,
Both = Male | Female
}
public class Family
{
public Family(List<Person> people, Person familyHead = null)
{
People = people;
FamilyHead = familyHead;
}
public List<Person> People { get; set; }
public Person FamilyHead { get; set; }
}
#region Interfaces
/// <summary>
/// Interface for objects supporting the object graph reference.
/// </summary>
public interface ISurrogateWithReferenceId
{
/// <summary>
/// Gets or sets the id for the object referenced more than once during the process of serialization/deserialization.
/// </summary>
/// <remarks>Default value is -1.</remarks>
int ReferenceId { get; set; }
}
#endregion
public class PersonSurrogate : ISurrogateWithReferenceId
{
/// <summary>
/// Standard constructor.
/// </summary>
public PersonSurrogate(string name, byte gender)
{
Name = name;
Gender = gender;
ReferenceId = -1;
}
/// <summary>
/// Private constructor for object graph reference handling.
/// </summary>
private PersonSurrogate(int referenceId)
{
ReferenceId = referenceId;
}
public string Name { get; set; }
public byte Gender { get; set; }
#region object graph reference
/// <summary>
/// Gets the unique id assigned to the surrogate during the process of serialization/deserialization to handle object graph reference.
/// </summary>
/// <remarks>Default value is -1.</remarks>
public int ReferenceId { get; set; }
public override bool Equals(object obj)
{
return base.Equals(obj) || (ReferenceId > 0 && obj is ISurrogateWithReferenceId oi && oi.ReferenceId == ReferenceId);
}
public override int GetHashCode()
{
if (ReferenceId > 0)
return ReferenceId;
return base.GetHashCode();
}
#endregion object graph reference
protected virtual bool CheckSurrogateData(GenderType gender)
{
return gender == GenderType.Both || (GenderType)Gender == gender;
}
#region Static Methods
/// <summary>
/// Converts the surrogate to the related object during the deserialization process.
/// </summary>
public static implicit operator Person(PersonSurrogate surrogate)
{
if (surrogate == null) return null;
if (FamilySerializer.GetCachedObject(surrogate) is Person obj)
return obj;
obj = new Person(surrogate.Name, (GenderType)surrogate.Gender);
FamilySerializer.AddToCache(surrogate, obj);
return obj;
}
/// <summary>
/// Converts the object to the related surrogate during the serialization process.
/// </summary>
public static implicit operator PersonSurrogate(Person source)
{
if (source == null) return null;
if (FamilySerializer.GetCachedObjectWithReferenceId(source) is PersonSurrogate surrogate)
{
surrogate = new PersonSurrogate(surrogate.ReferenceId);
}
else
{
surrogate = new PersonSurrogate(source.Name, (byte)source.Gender);
FamilySerializer.AddToCache(source, surrogate);
}
return surrogate;
}
#endregion
}
public class FamilySurrogate
{
public FamilySurrogate(List<Person> people, Person familyHead)
{
People = people;
FamilyHead = familyHead;
}
public List<Person> People { get; set; }
public Person FamilyHead { get; set; }
#region Static Methods
public static implicit operator Family(FamilySurrogate surrogate)
{
if (surrogate == null) return null;
return new Family(surrogate.People, surrogate.FamilyHead);
}
public static implicit operator FamilySurrogate(Family source)
{
return source == null ? null : new FamilySurrogate(source.People, source.FamilyHead);
}
#endregion
}
Serializer
/// <summary>
/// Class with model for protobuf serialization
/// </summary>
public class FamilySerializer
{
public GenderType GenderToInclude;
public FamilySerializer(Family family, GenderType genderToInclude = GenderType.Both)
{
GenderToInclude = genderToInclude;
Family = family;
Init();
}
private void Init()
{
Model = RuntimeTypeModel.Create();
FillModel();
Model.CompileInPlace();
}
public FamilySerializer()
{
Init();
}
public Family Family { get; set; }
public RuntimeTypeModel Model { get; protected set; }
protected virtual void FillModel()
{
Model = RuntimeTypeModel.Create();
Model.Add(typeof(Family), false)
.SetSurrogate(typeof(FamilySurrogate));
MetaType mt = Model[typeof(FamilySurrogate)];
mt.Add(1, "People"); // This is a list of Person of course
//mt.AddField(2, "FamilyHead").AsReference = true; // Exception "A reference-tracked object changed reference during deserialization" - because using surrogate.
mt.Add(2, "FamilyHead");
mt.UseConstructor = false;
Model.Add(typeof(Person), false)
.SetSurrogate(typeof(PersonSurrogate));
mt = Model[typeof(PersonSurrogate)]
.Add(1, "Name")
.Add(2, "Gender")
.Add(3, "ReferenceId");
mt.UseConstructor = false; // Avoids to use the parameter-less constructor.
}
#region Cache
static FamilySerializer()
{
ResizeCache();
}
/// <summary>
/// Resizes the cache for object graph reference handling.
/// </summary>
/// <param name="size"></param>
public static void ResizeCache(int size = 500)
{
if (_cache != null)
{
foreach (var pair in _cache)
{
pair.Value.ResetCache();
}
}
_cache = new ConcurrentDictionary<int, FamilySerializerCache>();
for (var i = 0; i < size; i++)
_cache.TryAdd(i, new FamilySerializerCache());
}
private static ConcurrentDictionary<int, FamilySerializerCache> _cache;
/// <summary>
/// For internal use only. Adds the specified key and value to the serializer cache for the current thread during the serialization process.
/// </summary>
/// <param name="objKey">The the element to add as key.</param>
/// <param name="objValue">The value of the element to add.</param>
/// <remarks>The <see cref="ISurrogateWithReferenceId.ReferenceId"/> is updated for <see cref="objValue"/></remarks>
public static void AddToCache(object objKey, ISurrogateWithReferenceId objValue)
{
_cache[Thread.CurrentThread.ManagedThreadId].AddToCache(objKey, objValue);
}
/// <summary>
/// For internal use only. Adds the specified key and value to the serializer cache for the current thread during the serialization process.
/// </summary>
/// <param name="objKey">The the element to add as key.</param>
/// <param name="objValue">The value of the element to add.</param>
/// <remarks>The <see cref="ISurrogateWithReferenceId.ReferenceId"/> is updated for <see cref="objKey"/></remarks>
public static void AddToCache(ISurrogateWithReferenceId objKey, object objValue)
{
_cache[Thread.CurrentThread.ManagedThreadId].AddToCache(objKey, objValue);
}
/// <summary>
/// For internal use only. Resets the cache for the current thread.
/// </summary>
public static void ResetCache()
{
_cache[Thread.CurrentThread.ManagedThreadId].ResetCache();
}
/// <summary>
/// For internal use only. Gets the <see cref="ISurrogateWithReferenceId"/> associated with the specified object for the current thread.
/// </summary>
/// <param name="obj">The object corresponding to the value to get.</param>
/// <returns>The related ISurrogateWithReferenceId if presents, otherwise null.</returns>
public static ISurrogateWithReferenceId GetCachedObjectWithReferenceId(object obj)
{
return _cache[Thread.CurrentThread.ManagedThreadId].GetCachedObjectWithReferenceId(obj);
}
/// <summary>
/// For internal use only. Gets the object associated with the specified <see cref="ISurrogateWithReferenceId"/>.
/// </summary>
/// <param name="surrogateWithReferenceId">The <see cref="ISurrogateWithReferenceId"/> corresponding to the object to get.</param>
/// <returns>The related object if presents, otherwise null.</returns>
public static object GetCachedObject(ISurrogateWithReferenceId surrogateWithReferenceId)
{
return _cache[Thread.CurrentThread.ManagedThreadId].GetCachedObject(surrogateWithReferenceId);
}
#endregion Cache
public void Save(string fileName)
{
using (Stream s = File.Open(fileName, FileMode.Create, FileAccess.Write))
{
Model.Serialize(s, Family, new ProtoBuf.SerializationContext(){Context = this});
}
}
public void Open(string fileName)
{
using (Stream s = File.Open(fileName, FileMode.Open, FileAccess.Read))
{
Family = (Family)Model.Deserialize(s, null, typeof(Family), new ProtoBuf.SerializationContext(){Context = this});
}
}
}
Serializer cache
/// <summary>
/// Helper class to support object graph reference
/// </summary>
internal class FamilySerializerCache
{
// weak table for serialization
// ConditionalWeakTable uses ReferenceEquals() rather than GetHashCode() and Equals() methods to do equality checks, so I can use it as a cache during the writing process to overcome the issue with objects that have overridden the GetHashCode() and Equals() methods.
private ConditionalWeakTable<object, ISurrogateWithReferenceId> _writingTable = new ConditionalWeakTable<object, ISurrogateWithReferenceId>();
// dictionary for deserialization
private readonly Dictionary<ISurrogateWithReferenceId, object> _readingDictionary = new Dictionary<ISurrogateWithReferenceId, object>();
private int _referenceIdCounter = 1;
/// <summary>
/// Gets the value associated with the specified key during serialization process.
/// </summary>
/// <param name="key">The key of the value to get.</param>
/// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the <paramref name="value" /> parameter. This parameter is passed uninitialized.</param>
/// <returns>True if the internal dictionary contains an element with the specified key, otherwise False.</returns>
private bool TryGetCachedObject(object key, out ISurrogateWithReferenceId value)
{
return _writingTable.TryGetValue(key, out value);
}
/// <summary>
/// Gets the value associated with the specified key during deserialization process.
/// </summary>
/// <param name="key">The key of the value to get.</param>
/// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the <paramref name="value" /> parameter. This parameter is passed uninitialized.</param>
/// <returns>True if the internal dictionary contains an element with the specified key, otherwise False.</returns>
private bool TryGetCachedObject(ISurrogateWithReferenceId key, out object value)
{
return _readingDictionary.TryGetValue(key, out value);
}
/// <summary>
/// Resets the internal dictionaries and the counter;
/// </summary>
public void ResetCache()
{
_referenceIdCounter = 1;
_readingDictionary.Clear();
// ConditionalWeakTable automatically removes the key/value entry as soon as no other references to a key exist outside the table, but I want to clean it as well.
_writingTable = new ConditionalWeakTable<object, ISurrogateWithReferenceId>();
}
/// <summary>
/// Adds the specified key and value to the internal dictionary during serialization process.
/// </summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value of the element to add.</param>
/// <remarks>If the object implements <see cref="ISurrogateWithReferenceId"/> interface then <see cref="ISurrogateWithReferenceId.ReferenceId"/> is updated.</remarks>
public void AddToCache(object key, ISurrogateWithReferenceId value)
{
if (value.ReferenceId == -1)
value.ReferenceId = _referenceIdCounter++;
_writingTable.Add(key, value);
}
/// <summary>
/// Adds the specified key and value to the internal dictionary during deserialization process.
/// </summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value of the element to add.</param>
/// <remarks>If the object implements <see cref="ISurrogateWithReferenceId"/> interface then <see cref="ISurrogateWithReferenceId.ReferenceId"/> is updated.</remarks>
public void AddToCache(ISurrogateWithReferenceId key, object value)
{
if (key.ReferenceId == -1)
key.ReferenceId = _referenceIdCounter++;
_readingDictionary.Add(key, value);
}
/// <summary>
/// Gets the <see cref="ISurrogateWithReferenceId"/> associated with the specified object.
/// </summary>
/// <param name="obj">The object corresponding to the value to get.</param>
/// <returns>The related ISurrogateWithReferenceId if presents, otherwise null.</returns>
public ISurrogateWithReferenceId GetCachedObjectWithReferenceId(object obj)
{
if (TryGetCachedObject(obj, out ISurrogateWithReferenceId value))
return value;
return null;
}
/// <summary>
/// Gets the object associated with the specified <see cref="ISurrogateWithReferenceId"/>.
/// </summary>
/// <param name="surrogateWithReferenceId">The <see cref="ISurrogateWithReferenceId"/> corresponding to the object to get.</param>
/// <returns>The related object if presents, otherwise null.</returns>
public object GetCachedObject(ISurrogateWithReferenceId surrogateWithReferenceId)
{
if (TryGetCachedObject(surrogateWithReferenceId, out object value))
return value;
return null;
}
}
Test Case
private Family FamilyTestCase(string fileName, bool save)
{
if (save)
{
var people = new List<Person>()
{
new Person("Angus", GenderType.Male),
new Person("John", GenderType.Male),
new Person("Katrina", GenderType.Female),
};
var fam = new Family(people, people[0]);
var famSer = new FamilySerializer(fam);
famSer.Save(fileName);
return fam;
}
else
{
var famSer = new FamilySerializer();
famSer.Open(fileName);
if (Object.ReferenceEquals(fam.People[0], fam.FamilyHead))
{
Console.WriteLine("Family head is the same than People[0]!");
}
return famSer.Family;
}
}

Categories

Resources