This is what I came up with, but it feels bloated and well, untidy. And I don't like that I have created an instance of each class just to get the right one.
class FileHasher
{
private readonly List<IHasher> _list;
public FileHasher()
{
_list = new List<IHasher>();
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
if(typeof(IHasher).IsAssignableFrom(type) && type.IsClass)
_list.Add((IHasher) Activator.CreateInstance(type));
}
}
public HashReturn GetHashFromFile(string file, HashFileType hashType)
{
var hashReturn = new HashReturn();
IHasher iHasher = _list.Find(hasher => hasher.HashType == hashType);
hashReturn.Hash = iHasher.FileToHash(file);
return hashReturn;
}
public HashReturn GetHashFromString(string str, HashFileType hashType)
{
var hashReturn = new HashReturn();
IHasher iHasher = _list.Find(hasher => hasher.HashType == hashType);
hashReturn.Hash = iHasher.StringToHash(str);
return hashReturn;
}
}
internal class HashReturn
{
public Exception Error { get; set; }
public string Hash { get; set; }
public bool Success { get; set; }
}
enum HashFileType
{
CRC32,
MD5
}
internal interface IHasher
{
HashFileType HashType { get; }
string FileToHash(string file);
string StringToHash(string str);
}
class MD5Hasher : IHasher
{
public HashFileType HashType { get { return HashFileType.MD5; } }
public string FileToHash(string file)
{
return "";
}
public string StringToHash(string str)
{
return "";
}
}
class CRC32Hasher : IHasher
{
public HashFileType HashType { get { return HashFileType.CRC32; } }
public string FileToHash(string file)
{
return "";
}
public string StringToHash(string str)
{
return "";
}
}
MEF solves this nicely for you.
http://mef.codeplex.com/
It is included in .NET 4.
Related
I'm working on a project where I have some recursive data structure and I want to create a fixture for it.
The data structure is XmlCommandElement, it has a single method ToCommand that converts XmlCommandElement to Command.
Each node on the tree can be a XmlCommandElement and/or XmlCommandPropertyElement.
Now, in order to test the behaviour of the method ToCommand I want to fetch XmlCommandElement with some arbitrary data.
I want to control the depth of the tree and the amount of instances of XmlCommandElement and/or XmlCommandPropertyElement per node.
So here is the code I'm using for the fixture:
public class XmlCommandElementFixture : ICustomization
{
private static readonly Fixture _fixture = new Fixture();
private XmlCommandElement _xmlCommandElement;
public int MaxCommandsPerDepth { get; set; }
public int MaxDepth { get; set; }
public int MaxPropertiesPerCommand { get; set; }
public XmlCommandElementFixture BuildCommandTree()
{
_xmlCommandElement = new XmlCommandElement();
var tree = new Stack<XmlCommandElementNode>();
tree.Push(new XmlCommandElementNode(0, _xmlCommandElement));
while (tree.Count > 0) {
var node = tree.Pop();
node.Command.Key = CreateRandomString();
node.Command.Properties = CreateProperties();
if (MaxDepth > node.Depth) {
var commands = new List<XmlCommandElement>();
for (var i = 0; i < MaxCommandsPerDepth; i++) {
var command = new XmlCommandElement();
tree.Push(new XmlCommandElementNode(node.Depth + 1, command));
commands.Add(command);
}
node.Command.Commands = commands.ToArray();
}
}
return this;
}
public void Customize(IFixture fixture)
{
fixture.Customize<XmlCommandElement>(c => c.FromFactory(() => _xmlCommandElement)
.OmitAutoProperties());
}
private static string CreateRandomString()
{
return _fixture.Create<Generator<string>>().First();
}
private XmlCommandPropertyElement[] CreateProperties()
{
var properties = new List<XmlCommandPropertyElement>();
for (var i = 0; i < MaxPropertiesPerCommand; i++) {
properties.Add(new XmlCommandPropertyElement {
Key = CreateRandomString(),
Value = CreateRandomString()
});
}
return properties.ToArray();
}
private struct XmlCommandElementNode
{
public XmlCommandElementNode(int depth, XmlCommandElement xmlCommandElement)
{
Depth = depth;
Command = xmlCommandElement;
}
public XmlCommandElement Command { get; }
public int Depth { get; }
}
}
And this is how I'm using it:
xmlCommandElement = new Fixture().Customize(new XmlCommandElementFixture {
MaxDepth = 2,
MaxCommandsPerDepth = 3,
MaxPropertiesPerCommand = 4
}.BuildCommandTree()).Create<XmlCommandElement>();
This works perfectly fine! but the issue I have with it is it isn't generic, the whole point of AutoFixture at least as far as I know is to avoid making specific fixtures.
So what I would really like to do is something like this (found it here but it doesn't work for me.):
var fixture = new Fixture();
fixture.Behaviors.OfType<ThrowingRecursionBehavior>()
.ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new DepthThrowingRecursionBehavior(2));
fixture.Behaviors.Add(new OmitOnRecursionForRequestBehavior(typeof(XmlCommandElement), 3));
fixture.Behaviors.Add(new OmitOnRecursionForRequestBehavior(typeof(XmlCommandPropertyElement), 4));
xmlCommandElement = fixture.Create<XmlCommandElement>();
Here is all the code for reference:
Interfaces:
public interface ICommandCollection : IEnumerable<ICommand>
{
ICommand this[string commandName] { get; }
void Add(ICommand command);
}
public interface ICommandPropertyCollection : IEnumerable<ICommandProperty>
{
string this[string key] { get; }
void Add(ICommandProperty property);
}
public interface ICommandProperty
{
string Key { get; }
string Value { get; }
}
public interface ICommand
{
ICommandCollection Children { get; set; }
string Key { get; }
ICommandPropertyCollection Properties { get; }
}
public interface ICommandConvertible
{
ICommand ToCommand();
}
Classes:
public sealed class CommandPropertyCollection : ICommandPropertyCollection
{
private readonly IDictionary<string, ICommandProperty> _properties;
public CommandPropertyCollection()
{
_properties = new ConcurrentDictionary<string, ICommandProperty>();
}
public string this[string key]
{
get
{
ICommandProperty property = null;
_properties.TryGetValue(key, out property);
return property.Value;
}
}
public void Add(ICommandProperty property)
{
_properties.Add(property.Key, property);
}
public IEnumerator<ICommandProperty> GetEnumerator()
{
return _properties.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public sealed class CommandProperty : ICommandProperty
{
public CommandProperty(string key, string value)
{
Key = key;
Value = value;
}
public string Key { get; }
public string Value { get; }
}
public sealed class Command : ICommand
{
public Command(string key, ICommandPropertyCollection properties)
{
Key = key;
Properties = properties;
}
public ICommandCollection Children { get; set; }
public string Key { get; }
public ICommandPropertyCollection Properties { get; }
}
public class XmlCommandPropertyElement : ICommandPropertyConvertible
{
[XmlAttribute("key")]
public string Key { get; set; }
[XmlAttribute("value")]
public string Value { get; set; }
public ICommandProperty ToCommandProperty()
{
return new CommandProperty(Key, Value);
}
}
Finally, the class I'm trying to test is as follow:
public class XmlCommandElement : ICommandConvertible
{
[XmlArray]
[XmlArrayItem("Command", typeof(XmlCommandElement))]
public XmlCommandElement[] Commands { get; set; }
[XmlAttribute("key")]
public string Key { get; set; }
[XmlArray]
[XmlArrayItem("Property", typeof(XmlCommandPropertyElement))]
public XmlCommandPropertyElement[] Properties { get; set; }
public ICommand ToCommand()
{
ICommandPropertyCollection properties = new CommandPropertyCollection();
foreach (var property in Properties) {
properties.Add(property.ToCommandProperty());
}
ICommand command = new Command(Key, properties);
return command;
}
}
The test itself looks like this:
namespace Yalla.Tests.Commands
{
using Fixtures;
using FluentAssertions;
using Ploeh.AutoFixture;
using Xbehave;
using Yalla.Commands;
using Yalla.Commands.Xml;
public class XmlCommandElementTests
{
[Scenario]
public void ConvertToCommand(XmlCommandElement xmlCommandElement, ICommand command)
{
$"Given an {nameof(XmlCommandElement)}"
.x(() =>
{
xmlCommandElement = new Fixture().Customize(new XmlCommandElementFixture {
MaxDepth = 2,
MaxCommandsPerDepth = 3,
MaxPropertiesPerCommand = 4
}.BuildCommandTree()).Create<XmlCommandElement>();
});
$"When the object is converted into {nameof(ICommand)}"
.x(() => command = xmlCommandElement.ToCommand());
"Then we need to have a root object with a key"
.x(() => command.Key.Should().NotBeNullOrEmpty());
"And 4 properties as its children"
.x(() => command.Properties.Should().HaveCount(4));
}
}
}
Thanks to Mark Seemann! the final solution looks like this:
public class RecursiveCustomization : ICustomization
{
public int MaxDepth { get; set; }
public int MaxElements { get; set; }
public void Customize(IFixture fixture)
{
fixture.Behaviors
.OfType<ThrowingRecursionBehavior>()
.ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior(MaxDepth));
fixture.RepeatCount = MaxElements;
}
}
And can be used like this:
xmlCommandElement = new Fixture().Customize(new RecursiveCustomization {
MaxDepth = 2,
MaxElements = 3
}).Create<XmlCommandElement>();
You can fairly easily create a small tree by changing the Fixture's recursion behaviour:
[Fact]
public void CreateSmallTree()
{
var fixture = new Fixture();
fixture.Behaviors
.OfType<ThrowingRecursionBehavior>()
.ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior(recursionDepth: 2));
var xce = fixture.Create<XmlCommandElement>();
Assert.NotEmpty(xce.Commands);
}
The above test passes.
I try to create a program that consumes plugins with COM-interface.
I implement IClassFactory interface to create instances of plugin. The CreateInstance-Method is implemented this way:
public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
{
MessageBox.Show("TestClassFactory CreateInstance");
ppvObject = IntPtr.Zero;
if (pUnkOuter != IntPtr.Zero)
{
// The pUnkOuter parameter was non-NULL and the object does
// not support aggregation.
Marshal.ThrowExceptionForHR(ComNative.CLASS_E_NOAGGREGATION);
}
if (riid == new Guid(ManagerGuids.ClassId)
|| riid == new Guid(ComNative.IID_IDispatch)
|| riid == new Guid(ComNative.IID_IUnknown))
{
MessageBox.Show("Pre GetComInterfaceForObject");
// Create the instance of the .NET object
// The container has to be passed as property and not by constructor injection.
// This is because COM (de-)registration needs a constructor without parameters.
ppvObject = Marshal.GetComInterfaceForObject(new NetPlugin(), typeof(INetPlugin));
MessageBox.Show("Post GetComInterfaceForObject");
}
else
{
// The object that ppvObject points to does not support the
// interface identified by riid.
Marshal.ThrowExceptionForHR(ComNative.E_NOINTERFACE);
}
return WinError.S_OK;
}
The call of Marshal.GetComInterfaceForObject(new NetPlugin(), typeof(INetPlugin)); returns invalid argument error (E_INVALIDARG).
The arguments of the method:
NetPlugin:
[ClassInterface(ClassInterfaceType.None)]
[Guid(ManagerGuids.ClassId)]
[ComVisible(true)]
public class NetPlugin : INetPlugin
{
public NetPlugin()
{
}
[ComVisible(true)]
[DispIdAttribute(0x1)]
public String LastParameters
{
get { return ""; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0x2)]
public String Vendor
{
get { return "Company"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0x3)]
public String Description
{
get { return "Test plugin written in .NET"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0x4)]
public String Version
{
get { return "1.0"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0x5)]
public String Category
{
get { return "Void"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0x6)]
public String MenuEntry
{
get { return "NetPlugin"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0x7)]
public String MessageString
{
get { return "NetPlugin"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0x8)]
public String TooltipText
{
get { return "Tooltip NetPlugin"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0x9)]
public String FriendlyName
{
get { return "NetPlugin FFFFF Friendly"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0xa)]
public String BitmapResourceSmall
{
get { return "BitmapResourceSmall.bmp"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0xb)]
public String BitmapResourceLarge
{
get { return "BitmapResourceLarge.bmp"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0xc)]
public String Key
{
get { return ""; } // must be empty
set { }
}
[ComVisible(true)]
[DispIdAttribute(0xd)]
public String Reserved
{
get { return "Void"; }
set { }
}
[ComVisible(true)]
[DispIdAttribute(0xe)]
public Int32 Run()
{
//-- Return scode
return 0;
}
}
INetPlugin:
[Guid(ManagerGuids.InterfaceId), ComVisible(true)]
interface INetPlugin
{
[DispId(0x1)]
String LastParameters { get; set; }
[DispId(0x2)]
String Vendor { get; set; }
[DispId(0x3)]
String Description {get; set; }
[DispId(0x4)]
String Version { get; set; }
[DispId(0x5)]
String Category { get; set; }
[DispId(0x6)]
String MenuEntry { get; set; }
[DispId(0x7)]
String MessageString { get; set; }
[DispId(0x8)]
String TooltipText { get; set; }
[DispId(0x9)]
String FriendlyName { get; set; }
[DispId(0xa)]
String BitmapResourceSmall { get; set; }
[DispId(0xb)]
String BitmapResourceLarge { get; set; }
[DispId(0xc)]
String Key { get; set; }
[DispId(0xd)]
String Reserved { get; set; }
[DispId(0xe)]
Int32 Run();
}
The NetPlugin is registred this way:
RegistrationServices rs = new RegistrationServices();
bool b = rs.RegisterAssembly(System.Reflection.Assembly.GetExecutingAssembly(), AssemblyRegistrationFlags.SetCodeBase);
if (!b)
return 998;
String exePath = new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath;
Registry.SetValue(#"HKEY_CLASSES_ROOT\CLSID\{" + ManagerGuids.ClassId + #"}\LocalServer32", "", exePath);
The INetPlugin is registred this way:
[HKEY_CLASSES_ROOT\Interface\InterfaceGUID]
[HKEY_CLASSES_ROOT\Interface\InterfaceGUID\ProxyStubClsid32]
#="{00020420-0000-0000-C000-000000000046}"
[HKEY_CLASSES_ROOT\Interface\InterfaceGUID\TypeLib]
#="{228a33a4-193a-4d6e-93ee-6aba49be9488}"
"Version"="1.0"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\InterfaceGUID]
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\InterfaceGUID\ProxyStubClsid32]
#="{00020420-0000-0000-C000-000000000046}"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\InterfaceGUID\TypeLib]
#="{228a33a4-193a-4d6e-93ee-6aba49be9488}"
"Version"="1.0"
I'll be very thankful for any help!
I have this:
public class Blah
{
public int id { get; set; }
public string blahh { get; set; }
}
public class Doh
{
public int id { get; set; }
public string dohh { get; set; }
public string mahh { get; set; }
}
public List<???prpClass???> Whatever(string prpClass)
where string prpClass can be "Blah" or "Doh".
I would like the List type to be class Blah or Doh based on what the string prpClass holds.
How can I achieve this?
EDIT:
public List<prpClass??> Whatever(string prpClass)
{
using (var ctx = new ApplicationDbContext())
{
if (prpClass == "Blah")
{
string queryBlah = #"SELECT ... ";
var result = ctx.Database.SqlQuery<Blah>(queryBlah).ToList();
return result;
}
if (prpClass == "Doh")
{
string queryDoh = #"SELECT ... ";
var result = ctx.Database.SqlQuery<Doh>(queryDoh).ToList();
return result;
}
return null
}
}
you have to have a common supertype:
public interface IHaveAnId
{
int id { get;set; }
}
public class Blah : IHaveAnId
{
public int id { get; set; }
public string blahh { get; set; }
}
public class Doh : IHaveAnId
{
public int id {get;set;}
public string dohh { get; set; }
public string mahh { get; set; }
}
then you can do:
public List<IHaveAnId> TheList = new List<IHaveAnId>();
and in some method:
TheList.Add(new Blah{id=1,blahh = "someValue"});
TheList.Add(new Doh{id =2, dohh = "someValue", mahh = "someotherValue"});
to iterate through the list:
foreach(IHaveAnId item in TheList)
{
Console.WriteLine("TheList contains an item with id {0}", item.id);
//item.id is allowed since you access the property of the class over the interface
}
or to iterate through all Blahs:
foreach(Blah item in TheList.OfType<Blah>())
{
Console.WriteLine("TheList contains a Blah with id {0} and blahh ='{1}'", item.id, item.blahh);
}
Edit:
the 2 methods and a int field holding the autovalue:
private int autoValue = 0;
public void AddBlah(string blahh)
{
TheList.Add(new Blah{id = autovalue++, blahh = blahh});
}
public void AddDoh(string dohh, string mahh)
{
TheList.Add(new Doh{id = autovalue++, dohh = dohh, mahh = mahh});
}
Another Edit
public List<object> Whatever(string prpClass)
{
using (var ctx = new ApplicationDbContext())
{
if (prpClass == "Blah")
{
string queryBlah = #"SELECT ... ";
var result = ctx.Database.SqlQuery<Blah>(queryBlah).ToList();
return result.Cast<object>().ToList();
}
if (prpClass == "Doh")
{
string queryDoh = #"SELECT ... ";
var result = ctx.Database.SqlQuery<Doh>(queryDoh).ToList();
return result.Cast<object>.ToList();
}
return null;
}
}
in the view you then have to decide what type it is. In asp.net MVC you can use a display template and use reflection to get a good design. But then i still don't know what technology you are using.
Yet another Edit
TestClass:
public class SomeClass
{
public string Property { get; set; }
}
Repository:
public static class Repository
{
public static List<object> Whatever(string prpClass)
{
switch (prpClass)
{
case "SomeClass":
return new List<SomeClass>()
{
new SomeClass{Property = "somestring"},
new SomeClass{Property = "someOtherString"}
}.Cast<object>().ToList();
default:
return null;
}
}
}
And a controller action in mvc:
public JsonResult Test(string className)
{
return Json(Repository.Whatever("SomeClass"),JsonRequestBehavior.AllowGet);
}
then i called it with: http://localhost:56619/Home/Test?className=SomeClass
And got the result:
[{"Property":"somestring"},{"Property":"someOtherString"}]
Is this what you are trying to do?
public class Blah
{
public int id { get; set; }
public string blahh { get; set; }
}
public class Doh
{
public int id { get; set; }
public string dohh { get; set; }
public string mahh { get; set; }
}
class Program
{
public static List<T> Whatever<T>(int count) where T: new()
{
return Enumerable.Range(0, count).Select((i) => new T()).ToList();
}
static void Main(string[] args)
{
var list=Whatever<Doh>(100);
// list containts 100 of "Doh"
}
}
How to mock this class in nUnit Tests?
public class OpenDataQuery: PagedQuery, IOpenDataQuery
{
private static Dictionary<string, SortItem> m_sortModes;
protected override Dictionary<string, SortItem> SortModes
{
get
{
if (m_sortModes == null)
{
m_sortModes = new Dictionary<string, SortItem>();
AddSortMode(m_sortModes, new SortItem(ObjectExtensions.GetNameFromExpression<OpenDataCategoriesModel, string>(m => m.Name), "Наименование ↑", true) { IsDefault = true });
AddSortMode(m_sortModes, new SortItem(ObjectExtensions.GetNameFromExpression<OpenDataCategoriesModel, string>(m => m.Name), "Наименование ↓"));
}
return m_sortModes;
}
}
public IEnumerable<OpenDataCategoriesModel> OpenDataCategories { get; set; }
public string OpenDataTags { get; set; }
}
and
public abstract class PagedQuery : IPagedQuery
{
private const int DEFAULT_PAGE = 1;
private const int DEFAULT_COUNT = 5;
private int? m_page;
private int? m_count;
private int? m_total;
private string m_sort;
public int? Page
{
get
{
if (m_page == null || m_page <= 0)
{
return DEFAULT_PAGE;
}
return m_page;
}
set { m_page = value; }
}
public int? Count
{
get
{
if (m_count == null || m_count <= 0)
{
return DEFAULT_COUNT;
}
return m_count;
}
set { m_count = value; }
}
public int? Total
{
get
{
if (m_total == null || m_total <= 0)
{
return 0;
}
return m_total;
}
set { m_total = value; }
}
public string SearchQuery { get; set; }
protected virtual Dictionary<string, SortItem> SortModes
{
get { return null; }
}
public string Sort
{
get
{
var sortMode = GetSortMode(m_sort);
if (sortMode == null)
{
var defaultSort = (from i in SortModes where i.Value.IsDefault select i).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(defaultSort.Key))
{
return defaultSort.Key;
}
return (from i in SortModes select i.Key).First();
}
return m_sort;
}
set
{
m_sort = value;
}
}
protected void AddSortMode(Dictionary<string, SortItem> sortModes, SortItem sortItem)
{
sortModes.Add(
String.Format(
"{0}{1}",
sortItem.FieldName.ToLower(),
sortItem.Asc ? "asc" : "desc"
),
sortItem
);
}
private SortItem GetSortMode(string sort)
{
if (SortModes == null || string.IsNullOrWhiteSpace(sort) ||
!SortModes.ContainsKey(sort.ToLower()))
{
return null;
}
return SortModes[sort.ToLower()];
}
public IOrderBy GetOrderBy()
{
var item = GetCurrentSortItem();
if (item == null)
{
return null;
}
if (item.Asc)
{
return new OrderBy(item.FieldName);
}
return new OrderByDesc(item.FieldName);
}
public SortItem GetCurrentSortItem()
{
return GetSortMode(Sort);
}
public Dictionary<string, SortItem> GetSortItems()
{
return SortModes;
}
}
and
public interface IOpenDataQuery : IPagedQuery
{
string OpenDataTags { get; set; }
}
I have some service method, that used openDataQuery class in parameters and in unit test i am trying mock this class, but this doesn't work:
public partial class OpenDataQueryRepository : Mock<OpenDataQuery>
{
public OpenDataQueryRepository(MockBehavior mockBehavior = MockBehavior.Strict)
: base(mockBehavior)
{
var opendataQuery = new Mock<IOpenDataQuery>();
var pagedQuery = opendataQuery.As<IPagedQuery>();
this.Setup(p=>p.GetOpenDataCategoriesMain(pagedQuery.Object,outttl)).Returns(OpenDataCategories);
}
}
I know that i should use Moq.Protected() for protected methods, but i don't know how use it correctly in this case. Please help me.
UPDATE:
I am testing this controller:
public class ODOpenDataController : ODBaseController
{
private readonly IOpenDataProvider m_openDataProvider;
public ODOpenDataController(IOpenDataProvider openDataProvider)
{
m_openDataProvider = openDataProvider;
}
public ActionResult Index(OpenDataQuery query)
{
int total;
query.OpenDataCategories = m_openDataProvider.GetOpenDataCategoriesMain(query, out total)
query.Total = total;
return View(query);
}
}
Test:
[Test]
public void Index_Test()
{
var opendataController = new ODOpenDataController(new OpenDataRepository().Object);
var result = opendataController.Index(new OpenDataQuery()) as ViewResult;
var model = result.Model as OpenDataQuery;
Assert.IsTrue(model.OpenDataCategories.Count() == 1);
}
I'm trying to serialize/deserialize collection of interfaces, which basically is unsupported. I found a question on SO where a proposition is stated to provide a serializable wrapper properties for those properties which depends on interfaces. So that's what I have:
public class Serialize {
public Serialize() {
this.SCollection = new SerializableInterfacesCollection<ObservableCollection<A>>(new ObservableCollection<A>());
}
[XmlIgnore()]
public ObservableCollection<A> Collection {
get {
return this.SCollection.Value;
}
set {
SCollection.Value = value;
}
}
public SerializableInterfacesCollection<ObservableCollection<A>> SCollection { get; set; }
}
public interface A {
string Str { get; set; }
int Int { get; set; }
// bad properties... very bad:
B B { get; set; }
ObservableCollection<B> Collection {get;set;}
}
public interface B {
string Label { get; set; }
string Value { get; set; }
}
public class AImpl: A {
public AImpl() {
SB = new SerializableInterface<B>();
SCollection = new SerializableInterfacesCollection<ObservableCollection<B>>(new ObservableCollection<B>());
}
[XmlAttribute()]
public string Type = typeof(AImpl).FullName;
public string Str {get;set;}
public int Int {get;set;}
[XmlIgnore()]
public B B {
get {
return SB.Value;
}
set {
SB.Value = value;
}
}
public SerializableInterface<B> SB;
[XmlIgnore()]
public ObservableCollection<B> Collection {
get {
return SCollection.Value;
}
set {
SCollection.Value = value;
}
}
public SerializableInterfacesCollection<ObservableCollection<B>> SCollection { get; set; }
}
public class BImpl01: B {
[XmlAttribute()]
public string Type = typeof(BImpl01).FullName;
public string Label {get;set;}
public string Value {get;set;}
}
public class BImpl02: B {
[XmlAttribute()]
public string Type = typeof(BImpl02).FullName;
public string Label {get;set;}
public string Value {get;set;}
}
Everything works fine if the A interface does not include "bad properties" (and of course they do not appear in the AImpl class). If it does than the collection's element does not deserialize and stops after deserializing first property of interface B.
Here are the wrappers:
public class SerializableInterface<T>: IXmlSerializable {
public SerializableInterface(T value) {
Value = value;
}
public SerializableInterface() { }
public T Value { get; set; }
public const string TypeAttr = "Type";
public const string NullAttrValue = "null";
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema() {
return null;
}
public virtual void ReadXml(System.Xml.XmlReader reader) {
if (!reader.HasAttributes)
throw new FormatException("expected a type attribute!");
string type = reader.GetAttribute(TypeAttr);
reader.Read(); // consume the value
if (type == NullAttrValue)
return;// leave T at default value
XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
this.Value = (T)serializer.Deserialize(reader);
}
public virtual void WriteXml(System.Xml.XmlWriter writer) {
if (Value == null) {
writer.WriteAttributeString(TypeAttr, NullAttrValue);
return;
}
try {
var type = Value.GetType();
var ser = new XmlSerializer(type);
writer.WriteAttributeString(TypeAttr, type.FullName);
ser.Serialize(writer, Value);
}
catch (Exception e) {
// some logging
throw e;
}
}
#endregion
}
public class SerializableInterfacesCollection<T>: SerializableInterface<T> where T: IList, new() {
public SerializableInterfacesCollection() { }
public SerializableInterfacesCollection(T value)
: base(value) {}
#region IXmlSerializable Members
public override void ReadXml(System.Xml.XmlReader reader) {
try {
if (!reader.HasAttributes)
throw new FormatException("No " + TypeAttr + " in element");
string type = reader.GetAttribute(TypeAttr);
if (string.IsNullOrEmpty(type) || type == NullAttrValue || type != typeof(T).FullName)
return;
reader.Read();
Value = new T();
while (reader.NodeType != XmlNodeType.EndElement) {
string typename = reader.GetAttribute(TypeAttr);
if (string.IsNullOrEmpty(typename)) {
throw new FormatException("Collection element has to have " + TypeAttr + " attribute specifing its type");
}
Type t = Type.GetType(typename);
if (null == t.GetInterface(typeof(T).GetProperty("Item").PropertyType.FullName))
break;
XmlSerializer xs = new XmlSerializer(t);
var o = xs.Deserialize(reader);
Value.Add(o);
}
}
catch (Exception e) {
// some logging
throw e;
}
}
public override void WriteXml(System.Xml.XmlWriter writer) {
if (Value == null) {
writer.WriteAttributeString(TypeAttr, NullAttrValue);
return;
}
try {
writer.WriteAttributeString(TypeAttr, Value.GetType().FullName);
foreach (var el in Value) {
var ser = new XmlSerializer(el.GetType());
ser.Serialize(writer, el);
}
}
catch (Exception e) {
// some logging
throw e;
}
}
#endregion
}
and here is a method generating data for tests:
private Serialize makeA() {
Serialize result = new Serialize();
for (int i = 0; i < 10; ++i) {
A a = new AImpl() { Str = "str " + i, Int = i, B = makeB(i) };
for (int j = 0; j < 10; ++j) {
a.Collection.Add(makeB(j));
}
result.Collection.Add(a);
}
return result;
}
B makeB(int i) {
if (i % 2 == 0) {
return new BImpl01(){Label= "Blabel " + i, Value="value b"+i};
}
else {
return new BImpl02(){Label= "B2label " + i, Value="value b2"+i};
}
}