Set properties values with violating open-close principle validation - c#

I have a ServiceField class which contains FieldId and Value as two properties .
public class ServiceField
{
public int FieldId { get; set; }
public string Value { get; set; }
}
/// <summary>
/// This method is used to assign value to HajjPackageFields class
/// </summary>
/// <param name="hajPackageObj">HajjPackageFields whose value needs to be updated</param>
private void UpdateDetailFieldsValue(HajjPackageFields hajPackageObj)
{
foreach (ServiceField field in GetPackageDetails(hajPackageObj.PackageId))
{
if (field.FieldId == (int)HajjServiceFieldsEnum.AccomodationView)
{
hajPackageObj.AccomodationView = field.Value == "1";
}
else if (field.FieldId == (int)HajjServiceFieldsEnum.AirTicket)
{
hajPackageObj.AirTicket = field.Value == "1";
}
}
}
Problem is if any new property add in HajjPackageField class than i have to modify my UpdateDetailFieldsValue method which is against the open close principle. Is there any other proper way to achieve this task ?

If I clearly understood you, reflection will help to solve problem:
private void UpdateDetailFieldsValue(HajjPackageFields hajPackageObj)
{
var pairs = new Dictionary<int, string>();
foreach (var enumVal in typeof(HajjServiceFieldsEnum).GetEnumValues())
pairs[(int)(HajjServiceFieldsEnum)enumVal] = typeof(HajjServiceFieldsEnum).GetEnumName(enumVal);
foreach (ServiceField field in GetPackageDetails(hajPackageObj.PackageId))
typeof(HajjPackageFields).GetProperty(pairs[field.FieldId])
?.SetValue(hajPackageObj, field.Value == "1");
}

Related

Xml List Serialization and Node Type Names

Ive come across multiple questions and answers on here but none specific to my situation.
I have a class 'Entity' with multiple classes that extend off of it. I want the serialization to hit the list and understand and use the type of each item for the node name.
Now, I can use what is commented out (define each array item in the main class and define the name of such by using [XmlArrayItem("Subclass1", typeof(subclass1)] but I want to keep all definitions in their subclass and I will be having too many subclasses to define everything in the main entity class...Is there anyway to achieve this?
I have tried using [XmlType(TypeName="...")] for the subclasses and so on but that did not work.
[Serializable]
[XmlInclude(typeof(Subclass1))]
[XmlRoot("Entity")]
public class Entity{
[XmlArray("CausedBy")]
//[XmlArrayItem("Subclass1", typeof(subclass1))]
//[XmlArrayItem("Sublcass2", typeof(Subclass2))]
public List<Entity> CausedBy { get; set; }
}
[Serializable]
[XmlRoot("Subclass1")]
[XmlInclude(typeof(Subclass2))]
public class Subclass1:Entity{
//Code...
}
[Serializable]
[XmlRoot("Subclass2")]
public class Subclass2:Subclass1{
//Code...
}
Serializing the above code after creating an entity and adding a Subclass1 and Subclass2 to the list 'CausedBy' class results in the following:
<Entity>
<CausedBy>
<Entity ... xsi:type="SubClass1" />
<Entity ... xsi:type="SubClass2" />
</CausedBy>
<Entity>
I would like the output to show:
<Entity>
<CausedBy>
<SubClass1 .../>
<SubClass2 .../>
</CausedBy>
<Entity>
Since I totally failed to read the question to begin with, here's a new answer (it's a bit of a tl;dr, so you can always skip to the end and follow the link):
It isn't possible to get the built in serializer class to work because you don't wish to add the attributes that it needs to be able to operate. Your only option is to seralize the class yourself, however, this need not be as tedious as it sounds; I had a similar issue a few years ago with DataGridView in virtual mode and produced a generic virtualizer that could be used to virtualize the data for display; it used a custom attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class showColumnAttribute : System.Attribute
{
///<summary>Optional display format for column</summary>
public string Format;
///<summary>Optional Header string for column<para>Defaults to propety name</para></summary>
public string Title;
///<summary>Optional column edit flag - defaults to false</summary>
public bool ReadOnly;
///<summary>Optional column width</summary>
public int Width;
///<summary>
///Marks public properties that are to be displayed in columns
///</summary>
public showColumnAttribute()
{
Format = String.Empty;
Title = String.Empty;
ReadOnly = false;
Width = 0;
}
}
And a constructor:
///<summary>
///Extracts the properties of the supplied type that are to be displayed
///<para>The type must be a class or an InvalidOperationException will be thrown</para>
///</summary>
public Virtualiser(Type t)
{
if (!t.IsClass)
throw new InvalidOperationException("Supplied type is not a class");
List<VirtualColumnInfo> definedColumns = new List<VirtualColumnInfo>();
PropertyInfo[] ps = t.GetProperties();
MethodInfo mg, ms;
for (int i = 0; i < ps.Length; i++)
{
Object[] attr = ps[i].GetCustomAttributes(true);
if (attr.Length > 0)
{
foreach (var a in attr)
{
showColumnAttribute ca = a as showColumnAttribute;
if (ca != null)
{
mg = ps[i].GetGetMethod();
if (mg != null)
{
ms = ps[i].GetSetMethod();
definedColumns.Add
(
new VirtualColumnInfo
(
ps[i].Name, ca.Width, ca.ReadOnly, ca.Title == String.Empty ? ps[i].Name : ca.Title,
ca.Format, mg, ms
)
);
}
break;
}
}
}
}
if (definedColumns.Count > 0)
columns = definedColumns.ToArray();
}
This extracts the public properties of the class and supplies marked items to the DataGridView as columns together with a header, format, etc.
The effect of all of this (and the rest of the missing code) was that any type could be virtualized in a dataGridView simply by tagging public properties and calling the virtualizer once for a given type:
#region Virtualisation
static readonly Virtualiser Virtual = new Virtualiser(typeof(UserRecord));
[XmlIgnore] // just in case!
public static int ColumnCount { get { return Virtual.ColumnCount; } }
public static VirtualColumnInfo ColumnInfo(int column)
{
return Virtual.ColumnInfo(column);
}
public Object GetItem(int column)
{
return Virtual.GetItem(column, this);
}
/*
** The supplied item should be a string - it is up to this method to supply a valid value to the property
** setter (this is the simplest place to determine what this is and how it can be derived from a string).
*/
public void SetItem(int column, Object item)
{
String v = item as String;
int t = 0;
if (v == null)
return;
switch (Virtual.GetColumnPropertyName(column))
{
case "DisplayNumber":
if (!int.TryParse(v, out t))
t = 0;
item = t;
break;
}
try
{
Virtual.SetItem(column, this, item);
}
catch { }
}
#endregion
The number of columns, their properties and order can be specified automatically by creating a number of public properties derived from the class data:
#region Display columns
[showColumn(ReadOnly = true, Width = 100, Title = "Identification")]
public String DisplayIdent
{
get
{
return ident;
}
set
{
ident = value;
}
}
[showColumn(Width = 70, Title = "Number on Roll")]
public int DisplayNumber
{
get
{
return number;
}
set
{
number = value;
}
}
[showColumn(Width = -100, Title = "Name")]
public string DisplayName
{
get
{
return name == String.Empty ? "??" : name;
}
set
{
name = value;
}
}
#endregion
This would virtualize any class for dataGridView to display and edit data and I used it many times over the years and the extraction of properties to display is exactly what is required for XML serialization, indeed, it has a lot of the same characteristics.
I was going to adapt this method to do the same job for XML serialization but someone has already done it at https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=474453, I hope you can make use of this method to solve your problem.
This works for me:
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Entity entity = new Entity();
entity.CausedBy = new List<Entity>();
entity.CausedBy.Add(new Subclass1());
entity.CausedBy.Add(new Subclass2());
entity.CausedBy.Add(new Subclass2());
entity.CausedBy.Add(new Subclass1());
entity.CausedBy.Add(new Subclass1());
entity.Save(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Test.txt"));
}
}
[Serializable]
[XmlRoot("Entity")]
public class Entity
{
[XmlArray("CausedBy")]
[XmlArrayItem("SubClass1", typeof(Subclass1))]
[XmlArrayItem("SubClass2", typeof(Subclass2))]
public List<Entity> CausedBy { get; set; }
}
[Serializable]
[XmlRoot("Subclass1")]
public class Subclass1 : Entity
{
[XmlIgnore]
String t = DateTime.Now.ToShortDateString();
public String SubClass1Item { get { return "Test1 " + t; } set { } }
}
[Serializable]
[XmlRoot("Subclass2")]
public class Subclass2 : Entity
{
[XmlIgnore]
String t = DateTime.Now.ToString();
public String SubClass2Item { get { return "Test2 " + t; } set { } }
}
It produces:
<Entity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CausedBy>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
<SubClass2>
<SubClass2Item>Test2 20/09/2017 01:06:55</SubClass2Item>
</SubClass2>
<SubClass2>
<SubClass2Item>Test2 20/09/2017 01:06:55</SubClass2Item>
</SubClass2>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
</CausedBy>
</Entity>

Mapping a SharePoint list item to a C# class

Say I have:
public class SPListItem
{
public override object this[string fieldName]
{
get
{
return this.GetValue(fieldName);
}
set
{
this.SetValue(fieldName, value, !this.HasExternalDataSource);
}
}
}
public class Bar
{
public int Prop1 { get; set; }
public int Prop2 { get; set; }
public int Prop3 { get; set; }
}
is there any way I can do:
var fooInst = new SPListItem();
Bar barInst = (Bar)fooInst // or maybe Bar.FromFoo(Foo f) if handling the cast is not possible
and then have:
barInst.Prop1 give me the equivalent of:
fooInst["Prop"];
Without implementing the getters and setters for every property in Bar?
Aaaaaand, here we go. This class generates entities from your lists.
From: https://justsharepointthings.wordpress.com/2015/09/10/sharepoint-generate-c-poco-classes-from-an-existing-definition/
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.SharePoint;
namespace Code_Generation {
/// <summary>
/// Generates List object entities from a site connection.
/// </summary>
public class SPListPocoGenerator {
string parentDir = "GeneratedListPOCO/";
string hiddenDir = "GeneratedListPOCO/HiddenLists/";
private class PropertyString {
private string _propStr;
public PropertyString(string propStr) {
_propStr = propStr;
_properties = new Dictionary < string, string > ();
}
private Dictionary < string, string > _properties;
public string this[string key] {
get {
return _properties.ContainsKey(key) ? _properties[key] : string.Empty;
}
set {
if (_properties.ContainsKey(key)) {
_properties[key] = value;
} else {
_properties.Add(key, value);
}
}
}
/// <summary>
/// Replaces properties in the format {{propertyName}} in the source string with values from KeyValuePairPropertiesDictionarysupplied dictionary.nce you've set a property it's replaced in the string and you
/// </summary>
/// <param name="originalStr"></param>
/// <param name="keyValuePairPropertiesDictionary"></param>
/// <returns></returns>
public override string ToString() {
string modifiedStr = _propStr;
foreach(var keyvaluePair in _properties) {
modifiedStr = modifiedStr.Replace("{{" + keyvaluePair.Key + "}}", keyvaluePair.Value);
}
return modifiedStr;
}
}
public string _classDefinitionStr = #
"
using System;
using Microsoft.SharePoint;
public class {{EntityName}}
{
private SPListItem listItem;
public {{EntityName}}_InternalProperties InternalProperties
{
get; private set;
}
public {{EntityName}}(SPListItem li)
{
this.listItem = li;
this.InternalProperties = new {{EntityName}}_InternalProperties(this.listItem);
}
{{PropertySections}}
public class {{EntityName}}_InternalProperties
{
private SPListItem listItem;
public {{EntityName}}_InternalProperties(SPListItem li)
{
this.listItem = li;
}
{{HiddenPropertySections}}
{{InternalPropertySections}}
}
}";
private const string _propertySectionStr = "\n\n\t" + #
"public {{PropertyType}} {{PropertyName}}
{ get { return listItem[Guid.Parse("
"{{PropertyId}}"
")] as {{PropertyType}}; }
set { listItem[Guid.Parse("
"{{PropertyId}}"
")] = value; }}";
/// <summary>
/// Gets string identifying the field type
/// </summary>
/// <param name="field"></param>
/// <returns></returns>
private string GetSafeTypeName(SPField field) {
if (field.FieldValueType == null) {
return "object"; //Not going to try to parse it further, this is enough.
}
var type = field.FieldValueType;
if (type.IsValueType) {
return type.FullName + "?";
}
return type.FullName;
}
public void GenerateForWeb(SPWeb web) {
var blackList = new[] {
"Documents", "Form Templates", "Site Assets", "Site Pages", "Style Library"
};
Directory.CreateDirectory(parentDir);
Directory.CreateDirectory(hiddenDir);
foreach(SPList list in web.Lists) {
PropertyString _classDefinition = new PropertyString(_classDefinitionStr);
string entityName = "SPL_" + list.Title.Replace(" ", "");
_classDefinition["EntityName"] = entityName;
foreach(SPField field in list.Fields) {
PropertyString propertySection = new PropertyString(_propertySectionStr);
propertySection["PropertyType"] = GetSafeTypeName(field); //field.FieldValueType.FullName; -> Returning Null often. Thanks, SharePoint!
propertySection["PropertyName"] = field.EntityPropertyName.Replace("_x0020_", "_");
propertySection["PropertyId"] = field.Id.ToString();
if (SPBuiltInFieldId.Contains(field.Id)) _classDefinition["InternalPropertySections"] += propertySection;
else if (field.Hidden) _classDefinition["HiddenPropertySections"] += propertySection;
else _classDefinition["PropertySections"] += propertySection;
}
if (list.Hidden || blackList.Contains(list.Title)) {
File.WriteAllText(hiddenDir + entityName + ".cs", _classDefinition.ToString());
} else {
File.WriteAllText(parentDir + entityName + ".cs", _classDefinition.ToString());
}
}
}
}
}
For my former employer I implemented a DAO pattern for SharePoint. Unfortunately I was not allowed to take the code with me or publish it... I used annotations together with reflection to solve the issues with different names, optional fields, type casting etc. I also wrote a generator for the DTO-objects. But to be honest, it was a quite big effort for something that LINQ might solve in your case. Or writing your classes by hand, or writing a code generator for the getters and setters - it all depends on the size of your project(s).
Before implementing my own DAO I had a quite bad experience with LINQ to SQL, especially when columns were renamed, added or removed I didn't like the behaviour, I also had performance issues using it. That's why I prefered my own DAO pattern, but for easy tasks LINQ might be enough. My experience with LINQ also might be outdated, it was about 7 years ago ;)
You could use an ExpandoObject, which is in the System.Dynamic namespace. Try something like this (untested):
public class SPListItemPropertyMapper
{
private dynamic _expandoObject;
public SPListItemPropertyMapper(SPListItem listItem)
{
_expandoObject = new ExpandoObject();
foreach (SPField field in listItem.Fields)
{
_expandoObject.Add(field.InternalName, listItem.GetFormattedValue(field.InternalName));
}
}
public dynamic FieldValues
{
get { return _expandoObject; }
}
}
Usage:
SPListItem listItem; //your list item here
var propertyMapper = new SPListItemPropertyMapper(listItem);
var title = propertyMapper.FieldValues.Title;
var editor = propertyMapper.FieldValues.Editor;
var created = propertyMapper.FieldValues.Created;
etc. You should consider extending the foreach loop by more logic, to return values based on the field type instead of just using GetFormattedValue.

Force binary deserialization to fail when type modified

I'm looking for a non-intrusive way to enforce deserialization to fail under the following circumstances:
The type is not defined in a strongly named assembly.
BinaryFormatter is used.
Since serialized, the type has been modified (e.g. a property has been added).
Below is an illustration/repro of the problem in form of a failing NUnit test. I'm looking for a generic way to make this pass without modifying the Data class, preferably by just setting up the BinaryFormatter during serialization and/or deserialization. I also don't want to involve serialization surrogates, as this is likely to require specific knowledge for each affected type.
Can't find anything in the MSDN docs that helps me though.
[Serializable]
public class Data
{
public string S { get; set; }
}
public class DataSerializationTests
{
/// <summary>
/// This string contains a Base64 encoded serialized instance of the
/// original version of the Data class with no members:
/// [Serializable]
/// public class Data
/// { }
/// </summary>
private const string Base64EncodedEmptyDataVersion =
"AAEAAAD/////AQAAAAAAAAAMAgAAAEtTc2MuU3Rvcm0uRGF0YS5UZXN0cywgV"+
"mVyc2lvbj0xLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2"+
"VuPW51bGwFAQAAABlTc2MuU3Rvcm0uRGF0YS5UZXN0cy5EYXRhAAAAAAIAAAAL";
[Test]
public void Deserialize_FromOriginalEmptyVersionFails()
{
var binaryFormatter = new BinaryFormatter();
var memoryStream = new MemoryStream(Convert.FromBase64String(Base64EncodedEmptyDataVersion));
memoryStream.Seek(0L, SeekOrigin.Begin);
Assert.That(
() => binaryFormatter.Deserialize(memoryStream),
Throws.Exception
);
}
}
I'd recommend a "Java" way here - declare int field in every single serializable class like private int _Serializable = 0; and check that your current version & serialized version match; manually increase when you change properties. If you insist on automated way you'll have to store a lot of metadata and check if current metadata & persisted metadata matches (extra burden on performance/size of serialized data).
Here is the automatic descriptor. Basically you'll have to store TypeDescriptor instance as a part of your binary data & on retrieve check if persisted TypeDescriptor is valid for serialization (IsValidForSerialization) against current TypeDescriptor.
var persistedDescriptor = ...;
var currentDescriptor = Describe(typeof(Foo));
bool isValid = persistedDescriptor.IsValidForSerialization(currentDescriptor);
[Serializable]
[DataContract]
public class TypeDescriptor
{
[DataMember]
public string TypeName { get; set; }
[DataMember]
public IList<FieldDescriptor> Fields { get; set; }
public TypeDescriptor()
{
Fields = new List<FieldDescriptor>();
}
public bool IsValidForSerialization(TypeDescriptor currentType)
{
if (!string.Equals(TypeName, currentType.TypeName, StringComparison.Ordinal))
{
return false;
}
foreach(var field in Fields)
{
var mirrorField = currentType.Fields.FirstOrDefault(f => string.Equals(f.FieldName, field.FieldName, StringComparison.Ordinal));
if (mirrorField == null)
{
return false;
}
if (!field.Type.IsValidForSerialization(mirrorField.Type))
{
return false;
}
}
return true;
}
}
[Serializable]
[DataContract]
public class FieldDescriptor
{
[DataMember]
public TypeDescriptor Type { get; set; }
[DataMember]
public string FieldName { get; set; }
}
private static TypeDescriptor Describe(Type type, IDictionary<Type, TypeDescriptor> knownTypes)
{
if (knownTypes.ContainsKey(type))
{
return knownTypes[type];
}
var descriptor = new TypeDescriptor { TypeName = type.FullName, Fields = new List<FieldDescriptor>() };
knownTypes.Add(type, descriptor);
if (!type.IsPrimitive && type != typeof(string))
{
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).OrderBy(f => f.Name))
{
var attributes = field.GetCustomAttributes(typeof(NonSerializedAttribute), false);
if (attributes != null && attributes.Length > 0)
{
continue;
}
descriptor.Fields.Add(new FieldDescriptor { FieldName = field.Name, Type = Describe(field.FieldType, knownTypes) });
}
}
return descriptor;
}
public static TypeDescriptor Describe(Type type)
{
return Describe(type, new Dictionary<Type, TypeDescriptor>());
}
I also though about some mechanism of shortening size of persisted metadata - like calculating MD5 from xml-serialized or json-serialized TypeDescriptor; but in that case new property/field will mark your object as incompatible for serialization.

Get Object nested structure as string path

Consider the following set of classes. There are two things I would like to achieve.
Get the string representation of the path of the current property. For example totalAsset.BuildingAsset.HistoricalBuildingAsset.Path should return "TotalAsset.BuildingAsset.HistoricalBuildingAsset"
Given a path "TotalAsset.BuildingAsset.HistoricalBuildingAsset" and a value "100", I want to use the path to retrieve the property and change its value.
Code Example:
public abstract class Field
{
private string _path = string.Empty;
public double Value {get;set;}
public string Path
{
get
{
//Code probably goes here
throw new NotImplementedException();
}
protected set { _path = value; }
}
}
public sealed class TotalAsset : Field
{
public TotalAsset(BuildingAsset buildingAsset)
{
Path = "TotalAsset";
BuildingAsset = buildingAsset;
}
public BuildingAsset BuildingAsset { get; private set; }
}
public sealed class BuildingAsset : Field
{
public HistoricalBuildingAsset HistoricalBuildingAsset { get; private set; }
public BuildingAsset(HistoricalBuildingAsset historicalBuildingAsset)
{
Path = "BuildingAsset";
this.HistoricalBuildingAsset = historicalBuildingAsset;
}
}
public sealed class HistoricalBuildingAsset : Field
{
public HistoricalBuildingAsset()
{
Path = "HistoricalBuildingAsset";
}
}
[TestClass]
public class TestPath
{
[TestMethod]
public void MethodTestPath()
{
var historicalBuildingAsset = new HistoricalBuildingAsset();
var buildingAsset = new BuildingAsset(historicalBuildingAsset);
var totalAsset = new TotalAsset(buildingAsset);
Assert.AreEqual("TotalAsset.BuildingAsset.HistoricalBuildingAsset", totalAsset.BuildingAsset.HistoricalBuildingAsset.Path);
}
}
Wouldn't this be easily solved using polymorphism?
Based on your question, it seems like your Path property has an inmutable value, thus you should be able to solve your issue like the following code:
public class A
{
public virtual string Path
{
get { return "A"; }
}
}
public class B : A
{
public override string Path
{
get { return base.Path + ".B"; }
}
}
public class C : B
{
public override string Path
{
get { return base.Path + ".C"; }
}
}
A a = new A();
Console.WriteLine(a.Path); // Prints "A"
B b = new B();
Console.WriteLine(b.Path); // Prints "A.B"
C c = new C();
Console.WriteLine(c.Path); // Prints "A.B.C"
Update v1.1: Recursive approach (now includes getting a property value and setting a property value by a given object path)
Because you want to leave your model as is and go with the composition way, this is the piece of "magic" to dynamically get the whole path. Note that I've required a new FullPath property in order to avoid an infinite loop during path calculation (you can also try it in a DotNetFiddle):
using System;
using System.Linq;
using System.Reflection;
public abstract class Field
{
public double Value
{
get;
set;
}
public string Path
{
get;
protected set;
}
public string FullPath
{
get
{
return BuildPath(this);
}
}
/// <summary>
/// Recursively-builds a dot-separated full path of associated fields
/// </summary>
/// <param name="field">Optional, it's a reference to current associated field </param>
/// <param name="path">Optional, provided when this method enters to the first associated </param>
/// <returns>The whole dot-separated full path of associations to Field</returns>
private string BuildPath(Field field, string path = "")
{
// Top-level path won't start with dot
if (path != string.Empty)
{
path += '.';
}
path += field.Path;
// This will look for a property which is of type Field
PropertyInfo fieldProperty = field.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
.SingleOrDefault(prop => prop.PropertyType.IsSubclassOf(typeof(Field)));
// If current field has a property of type Field...
if (fieldProperty != null)
{
// ...we'll get its value and we'll start a recursion to find the next Field.Path
path = BuildPath((Field)fieldProperty.GetValue(field, null), path);
}
return path;
}
/// <summary>
/// Recursively sets a value to an associated field property
/// </summary>
/// <param name="path">The whole path to the property</param>
/// <param name="value">The value to set</param>
/// <param name="associatedField">Optional, it's a reference to current associated field</param>
public void SetByPath(string path, object value, Field associatedField = null)
{
if (string.IsNullOrEmpty(path.Trim()))
{
throw new ArgumentException("Path cannot be null or empty");
}
string[] pathParts = path.Split('.');
if (associatedField == null)
{
associatedField = this;
}
// This will look for a property which is of type Field
PropertyInfo property = associatedField.GetType().GetProperty(pathParts[0], BindingFlags.Public | BindingFlags.Instance);
if (property == null)
{
throw new ArgumentException("A property in the path wasn't found", "path");
}
object propertyValue = property.GetValue(associatedField, null);
// If property value isn't a Field, then it's the last part in the path
// and it's the property to set
if (!propertyValue.GetType().IsSubclassOf(typeof(Field)))
{
property.SetValue(associatedField, value);
}
else
{
// ... otherwise, we navigate to the next associated field, removing the first
// part in the path, so the next call will look for the next property...
SetByPath(string.Join(".", pathParts.Skip(1)), value, (Field)propertyValue);
}
}
/// <summary>
/// Recursively gets a value from an associated field property
/// </summary>
/// <param name="path">The whole path to the property</param>
/// <param name="associatedField">Optional, it's a reference to current associated field</param>
/// <typeparam name="T">The type of the property from which the value is going to be obtained</typeparam>
public T GetByPath<T>(string path, Field associatedField = null)
{
if (string.IsNullOrEmpty(path.Trim()))
{
throw new ArgumentException("Path cannot be null or empty");
}
string[] pathParts = path.Split('.');
if (associatedField == null)
{
associatedField = this;
}
// This will look for a property which is of type Field
PropertyInfo property = associatedField.GetType().GetProperty(pathParts[0], BindingFlags.Public | BindingFlags.Instance);
if (property == null)
{
throw new ArgumentException("A property in the path wasn't found", "path");
}
object propertyValue = property.GetValue(associatedField, null);
// If property value isn't a Field, then it's the last part in the path
// and it's the property to set
if (!propertyValue.GetType().IsSubclassOf(typeof(Field)))
{
return (T)property.GetValue(associatedField, null);
}
else
{
// ... otherwise, we navigate to the next associated field, removing the first
// part in the path, so the next call will look for the next property...
return GetByPath<T>(string.Join(".", pathParts.Skip(1)), (Field)propertyValue);
}
}
}
public sealed class TotalAsset : Field
{
public TotalAsset(BuildingAsset buildingAsset)
{
Path = "TotalAsset";
BuildingAsset = buildingAsset;
}
public BuildingAsset BuildingAsset
{
get;
private set;
}
}
public sealed class BuildingAsset : Field
{
public HistoricalBuildingAsset HistoricalBuildingAsset
{
get;
private set;
}
public BuildingAsset(HistoricalBuildingAsset historicalBuildingAsset)
{
Path = "BuildingAsset";
this.HistoricalBuildingAsset = historicalBuildingAsset;
}
}
public sealed class HistoricalBuildingAsset : Field
{
public HistoricalBuildingAsset()
{
Path = "HistoricalBuildingAsset";
}
public int Age
{
get;
set;
}
}
public class Program
{
public static void Main()
{
TotalAsset total = new TotalAsset(new BuildingAsset(new HistoricalBuildingAsset()));
// Prints "TotalAsset.BuildingAsset.HistoricalBuildingAsset"
Console.WriteLine(total.FullPath);
total.SetByPath("BuildingAsset.HistoricalBuildingAsset.Age", 300);
// Prints "300" as expected!
Console.WriteLine(total.GetByPath<int>("BuildingAsset.HistoricalBuildingAsset.Age"));
}
}
You can re-use the existing .net framework Binding pattern and codebase. Your description of what you want to do sounds mightily like MVVM binding to me. The use of Binding in WPF is explained here http://msdn.microsoft.com/en-us/library/ms752347(v=vs.110).aspx.
Using System.Windows.Data.Binding gives you an extensible framework for getting data into and out of object graphs using relative and absolute string paths to nominate the class members and collection indexes.

Dictionary Contains QuestionMaks

I have a simple question. During i create a dictionary in c#, half of the dictionary contains question marks. Here is my situration:
SourceCode:
/// <summary>
/// Get Member
/// </summary>
/// <param name="Binder"></param>
/// <param name="Result"></param>
/// <returns></returns>
public override bool TryGetMember(GetMemberBinder Binder, out object Result)
{
if (Binder.Name == "AsDictionary")
{
IDictionary<string, string> cValues = new Dictionary<string, string>();
foreach (var cValue in myValues)
{
string cVal = "";
if (cValue.Value == null)
{
cVal = "";
}
else
{
cVal = cValue.Value.ToString();
}
cValues.Add(cValue.Key, cVal);
}
Result = cValues;
return true;
}
int cCount = myValues.Where(Item => Item.Key.ToLower() == Binder.Name.ToLower()).ToList().Count;
if (cCount == 0)
{
Result = null;
return false;
}
else
{
Result = myValues.Where(Item => Item.Key.ToLower() == Binder.Name.ToLower()).First().Value;
}
return true;
}
myValues is also a ObservableCollection:
private ObservableCollection<DynamicSqlValue> myValues;
DynamicSqlValue is a very simple class:
public class DynamicSqlValue
{
public string Key
{
get;
set;
}
public object Value
{
get;
set;
}
}
Thank you for your help!
Must be some kind of visual studio issue or maybe you're running out of memory. Have you tried logging the values?
I would go a step farther than Patryk and ask why you aren't doing myValues.ToDictionary. Also, the .Where, .ToList, and .Count should be as simple as:
myValues.Any(kvp => kvp.Key.Equals(Binder.Name, StringComparison.InvariantCultureIgnoreCase))
That function should really only be like 4 or 5 lines of code.
It's not answer for Your question (sorry for that) but i have some refactor tip of Your code:
Make Your foreach like that:
foreach (var cValue in myValues)
{
cValues.Add(cValue.Key, cValue.Value != null ? cValue.Value.ToString() : string.Empty);
}
And create local variable from linq query:
var binderMyValues = myValues.Where(Item => Item.Key.ToLower() == Binder.Name.ToLower());
to use it like e.g.
int cCount = binderMyValues.ToList().Count;
or...
Result = binderMyValues.First().Value;

Categories

Resources