Attribute values do not reflect changes made at runtime - c#

I have a problem and can't wrap my mind around it:
I made a mathode which should set propertie values of an specific attribute "MappingAttribute" and return the new object.
The problem:
The attribute values are always set to the default value "false".
Where am I wrong?
static public T MapToClass<T>(SqlDataReader reader) where T : class
{
T returnedObject = Activator.CreateInstance<T>();
PropertyInfo[] modelProperties = returnedObject.GetType().GetProperties();
for (int i = 0; i < modelProperties.Length; i++)
{
MappingAttribute[] attributes = modelProperties[i].GetCustomAttributes<MappingAttribute>(true).ToArray();
if (attributes.Length > 0) {
attributes[0].AutoIncrement = true;
attributes[0].Primekey = true;
}
}
return returnedObject;
}

Attributes are not stored anywhere except in the assembly metadata. They are only materialized into attribute instances when asked to do so by reflection - in your case via GetCustomAttributes<MappingAttribute>. But: you then discard these. The next time GetCustomAttributes<MappingAttribute> is called, fresh new instances will be handed out, with the values from the assembly metadata.
Basically: updating properties of attribute instances does not mean that other code will see those changes when that code asks about attribute metadata.
To illustrate this:
using System;
class FooAttribute : Attribute { public string Value { get; set; } }
[Foo(Value = "abc")]
class Bar
{
static void Main()
{
var attrib = (FooAttribute)typeof(Bar)
.GetCustomAttributes(typeof(FooAttribute), false)[0];
Console.WriteLine(attrib.Value); // "abc"
attrib.Value = "def";
Console.WriteLine(attrib.Value); // "def"
// now re-fetch
attrib = (FooAttribute)typeof(Bar)
.GetCustomAttributes(typeof(FooAttribute), false)[0];
Console.WriteLine(attrib.Value); // "abc"
}
}

Related

Cannot save custom property from a custom component into a DTSX file

I'm trying to create my first SSIS custom source component but I can't get it to save the custom properties into the .dtsx file.
According to https://learn.microsoft.com/en-us/sql/integration-services/extending-packages-custom-objects/persisting-custom-objects , all I needed is to implement the IDTSComponentPersist interface, but this doesn't work, the LoadFromXML and SaveToXML are never called. Neither when I save the file nor when I load the package.
However, if your object has properties that use complex data types, or
if you want to perform custom processing on property values as they
are loaded and saved, you can implement the IDTSComponentPersist
interface and its LoadFromXML and SaveToXML methods. In these methods
you load from (or save to) the XML definition of the package an XML
fragment that contains the properties of your object and their current
values. The format of this XML fragment is not defined; it must only
be well-formed XML.
When I save the SSIS package and look inside the XML, I get this, no data type defined and no values :
Did I miss to set something?
To simplify, I created a small test project. The original project try to save a list of struct with 2 string and 1 integer, but both has the same "incorrect" behavior, SaveToXML and LoadFromXML are never called.
Here's my code:
using System;
using System.Collections.Generic;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Pipeline;
using Microsoft.SqlServer.Dts.Runtime;
using System.Xml;
using System.ComponentModel;
using System.Globalization;
using System.Drawing.Design;
using System.Windows.Forms.Design;
using System.Windows.Forms;
namespace TestCase
{
public class MyConverter : TypeConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return false;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType.Name.ToUpper() == "STRING")
return string.Join(",", ((List<string>)value).ToArray());
else
return ((string)value).Split(',');
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value.GetType().Name.ToUpper() == "STRING")
return ((string)value).Split(',');
else
return string.Join(",", ((List<string>)value).ToArray());
}
}
class FancyStringEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
var svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
List<string> vals = (List<string>)value;
string valsStr = string.Join("\r\n", vals.ToArray());
if (svc != null)
{
using (var frm = new Form { Text = "Your editor here" })
using (var txt = new TextBox { Text = valsStr, Dock = DockStyle.Fill, Multiline = true })
using (var ok = new Button { Text = "OK", Dock = DockStyle.Bottom })
{
frm.Controls.Add(txt);
frm.Controls.Add(ok);
frm.AcceptButton = ok;
ok.DialogResult = DialogResult.OK;
if (svc.ShowDialog(frm) == DialogResult.OK)
{
vals = new List<string>();
vals.AddRange(txt.Text.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries));
value = vals;
}
}
}
return value;
}
}
[DtsPipelineComponent(ComponentType = ComponentType.SourceAdapter,
CurrentVersion = 0,
Description = "Test class for saving",
DisplayName = "Test class",
IconResource = "None",
NoEditor = false,
RequiredProductLevel = Microsoft.SqlServer.Dts.Runtime.Wrapper.DTSProductLevel.DTSPL_NONE,
SupportsBackPressure = false,
UITypeName = "None")]
public class TestSave : PipelineComponent, IDTSComponentPersist
{
private string _NbBadWordProperty = "NbBadWord";
private string _ListBadWordsProperty = "ListBadWords";
private List<string> _badWords;
public IDTSCustomProperty100 _nb;
public IDTSCustomProperty100 _list;
public TestSave()
{
_badWords = new List<string>();
_badWords.Add("Word1");
_badWords.Add("Word2");
_badWords.Add("Word3");
}
public void LoadFromXML(System.Xml.XmlElement node, IDTSInfoEvents infoEvents)
{
System.Windows.Forms.MessageBox.Show("Oh god! we're inside LoadFromXML!!");
}
public void SaveToXML(System.Xml.XmlDocument doc, IDTSInfoEvents infoEvents)
{
System.Windows.Forms.MessageBox.Show("Oh god! we're inside SaveToXML!!");
XmlElement elementRoot;
XmlNode propertyNode;
// Create a new node to persist the object and its properties.
elementRoot = doc.CreateElement(String.Empty, "NBElement", String.Empty);
XmlAttribute nbEl = doc.CreateAttribute("Nbelement");
nbEl.Value = _badWords.Count.ToString();
elementRoot.Attributes.Append(nbEl);
// Save the three properties of the object from variables into XML.
foreach (string s in _badWords)
{
propertyNode = doc.CreateNode(XmlNodeType.Element, "BadWord", String.Empty);
propertyNode.InnerText = s;
elementRoot.AppendChild(propertyNode);
}
doc.AppendChild(elementRoot);
}
private IDTSCustomProperty100 GetCustomPropertyByName(string name)
{
foreach (IDTSCustomProperty100 prop in this.ComponentMetaData.CustomPropertyCollection)
if (prop.Name.ToUpper() == name)
return prop;
return null;
}
public override DTSValidationStatus Validate()
{
return DTSValidationStatus.VS_ISVALID;
}
public override void ProvideComponentProperties()
{
try
{
base.ProvideComponentProperties();
// reset the component
this.ComponentMetaData.OutputCollection.RemoveAll();
this.ComponentMetaData.InputCollection.RemoveAll();
// Add custom properties
if (GetCustomPropertyByName(_NbBadWordProperty) == null)
{
_nb = this.ComponentMetaData.CustomPropertyCollection.New();
_nb.Name = _NbBadWordProperty;
_nb.Description = "Number of bad word to filter";
_nb.State = DTSPersistState.PS_DEFAULT;
_nb.Value = _badWords.Count;
_nb.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;
}
if (GetCustomPropertyByName(_ListBadWordsProperty) == null)
{
IDTSCustomProperty100 _list = this.ComponentMetaData.CustomPropertyCollection.New();
_list.Name = _ListBadWordsProperty;
_list.Description = "List of bad words";
_list.State = DTSPersistState.PS_DEFAULT;
_list.TypeConverter = typeof(MyConverter).AssemblyQualifiedName;
_list.Value = _badWords;
_list.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;
_list.UITypeEditor = typeof(FancyStringEditor).AssemblyQualifiedName;
}
// add input objects
// none
// add output objects
IDTSOutput100 o2 = this.ComponentMetaData.OutputCollection.New();
o2.Name = "Dummy output";
o2.IsSorted = false;
foreach (IDTSCustomProperty100 p in this.ComponentMetaData.CustomPropertyCollection)
{
if (p.Name == _ListBadWordsProperty)
{
MyConverter c = new MyConverter();
List<string> l = (List<string>)p.Value;
foreach (string s in l)
{
IDTSOutputColumn100 col1 = o2.OutputColumnCollection.New();
col1.Name = s.Trim();
col1.Description = "Bad word";
col1.SetDataTypeProperties(Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_WSTR, 500, 0, 0, 0);
}
}
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("Critical error: " + ex.Message);
}
}
}
}
Update1:
Add the TypeConverter and UITypeEditor. Still the same behavior (not saving the "complex" data type).
When I add the source component to a data flow, I got this, everything look fine:
I can edit the property, no problem
But when I save the SSIS package and look at the xml, the property still not saved and still have a datatype of System.NULL:
Thanks!
Important Note - based on Microsoft definition of IDTSComponentPersist Interface and code samples of SaveToXML found on Internet, I suspect that custom persistence can only be implemented on custom SSIS Tasks, Connection Managers and Enumerators.
Well, please choose for yourself whether do you really need to implement custom object persistence. Your custom properties seems to fit well into standard data types Int32 and String.
Important note from Microsoft -
When you implement custom persistence, you must persist all the properties of the object, including both inherited properties and custom properties that you have added.
So, you really have to do a lot of work to persist all properties of component including LocaleID from your sample - in case someone needs to alter it. I would probably do storing ListBadWords custom property as a string without custom XML persistence.
On your code -- the most possible cause of the System.Null data type problem is that ProvideComponentProperties() method is called on initialization of the component, when it is added on the Data Flow. Data type of the property is determined dynamically at this moment, the variable _badwords is not initialized yet and is a reference type, so it is defined as Null reference. The ProvideComponentProperties() method is used to define custom properties and set its default values, to solve your problem - set
if (GetCustomPropertyByName(_ListBadWordsProperty) == null)
{
IDTSCustomProperty100 _list = this.ComponentMetaData.CustomPropertyCollection.New();
_list.Name = _ListBadWordsProperty;
_list.Description = "List of bad words";
_list.State = DTSPersistState.PS_DEFAULT;
_list.TypeConverter = typeof(MyConverter).AssemblyQualifiedName;
// This is the change
_list.Value = String.Empty;
_list.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;
_list.UITypeEditor = typeof(FancyStringEditor).AssemblyQualifiedName;
}
If you set yourself up on implementing custom XML persistence - please study Microsoft code sample and other sources. Saving is done a little bit other way. The main difference is that inside elementRoot of the component properties, each property is created under its own XML Node. Node's InnerText is used to store property value, and optional Node's attributes can store additional information.

Design time serialization of custom type property

So basically I have a custom UserControl containing a private array of Label objects and I want to be able to access exclusively their Text properties from the outside.
I therefore added a property which type LabelTextCollection is an implementation of IEnumerable and has my Label array as its inner list. Furthermore, I added an implementation of UITypeEditor to allow editing from the windows forms designer.
To try it out, I added my control in a form and edited the property's value. All of that works fine until I close and reopen the designer and the labels take back their default values.
After looking around it seems I have to add an implementation of CodeDomSerializer to allow my type to succesfully serialize into the {Form}.Designer.cs file at design time. I tried serializing a comment line first to test it out but no code is generated.
My final goal would be to have a line like
this.{controlName}.Titles.FromArray(new string[] { "Whatever" } )
added at design time after the property was modified using my editor.
What am I misunderstanding and/or doing wrong ?
Custom Type
[DesignerSerializer(typeof(LabelTextCollectionSerializer), typeof(CodeDomSerializer))]
public class LabelTextCollection : IEnumerable<string>, IEnumerable
{
private Label[] labels;
public LabelTextCollection(Label[] labels)
{
this.labels = labels;
}
public void SetLabels(Label[] labels)
{
this.labels = labels;
}
public IEnumerator<string> GetEnumerator()
{
return new LabelTextEnum(labels);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new LabelTextEnum(labels);
}
public string this[int index]
{
get { return labels[index].Text; }
set { labels[index].Text = value; }
}
public override string ToString()
{
if (labels.Length == 0) return string.Empty;
else
{
StringBuilder sb = new StringBuilder("{ ");
foreach (string label in this)
{
sb.Append(label);
if (label == this.Last()) sb.Append(" }");
else sb.Append(", ");
}
return sb.ToString();
}
}
public string[] ToArray()
{
string[] arr = new string[labels.Length];
for (int i = 0; i < labels.Length; i++) arr[i] = labels[i].Text;
return arr;
}
public void FromArray(string[] arr)
{
for(int i = 0; i < arr.Length; i++)
{
if (i >= labels.Length) break;
else labels[i].Text = arr[i];
}
}
public class LabelTextEnum : IEnumerator<string>, IEnumerator
{
private readonly Label[] labels;
private int position = -1;
public LabelTextEnum(Label[] labels)
{
this.labels = labels;
}
public object Current
{
get
{
try
{
return labels[position].Text;
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
string IEnumerator<string>.Current { get { return (string)Current; } }
public void Dispose()
{
return;
}
public bool MoveNext()
{
return ++position < labels.Length;
}
public void Reset()
{
position = -1;
}
}
}
Type Editor
public class LabelTextCollectionEditor : UITypeEditor
{
IWindowsFormsEditorService _service;
IComponentChangeService _changeService;
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null)
{
_service = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
_changeService = (IComponentChangeService)provider.GetService(typeof(IComponentChangeService));
if (_service != null && _changeService != null && value is LabelTextCollection)
{
LabelTextCollection property = (LabelTextCollection)value;
LabelTextCollectionForm form = new LabelTextCollectionForm() { Items = property.ToArray() };
if (_service.ShowDialog(form) == DialogResult.OK)
{
property.FromArray(form.Items);
value = property;
_changeService.OnComponentChanged(value, null, null, null);
}
}
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
}
Serializer
public class LabelTextCollectionSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
var baseSerializer = (CodeDomSerializer)manager.GetSerializer( typeof(LabelTextCollection).BaseType, typeof(CodeDomSerializer));
object codeObject = baseSerializer.Serialize(manager, value);
if (codeObject is CodeStatementCollection && value is LabelTextCollection)
{
var col = value as LabelTextCollection;
var statements = (CodeStatementCollection)codeObject;
statements.Add(new CodeCommentStatement("LabelTextCollection : " + col.ToString()));
}
return codeObject;
}
}
Property of custom Type
[Category("Appearance")]
[Editor(typeof(LabelTextCollectionEditor), typeof(UITypeEditor))]
public LabelTextCollection Titles { get; }
EDIT :
I added a set to my Titles property and set up my project for design-time debugging, I then realized that an exception was thrown on the line
object codeObject = baseSerializer.Serialize(manager, value);
stating that the Label type isn't marked as [Serializable].
I'm assuming that the base serializer is trying to write a call to my LabelTextCollection constructor and to serialize the labels field as a parameter of it.
I tried replacing the line with
object codeObject = new CodeObject();
which got rid of the exception but didn't write anything in the designer.cs file.
I'm (once again) assuming that nothing is happening because there is no relation between the CodeObject I just created and the file (unless that relation is established after it's returned by the Serialize method ?).
As you can probably tell, I'm pretty new regarding the CodeDom stuff so how should I create this object properly ?
EDIT 2 :
I'm so dumb... I forgot the codeObject is CodeStatementCollection test...
So the comment line is writing fine, now all I need to do is to write the correct line with CodeDom and it should work fine.
If someone wants to help, I currently have added to the designer.cs file :
this.FromArray( new string[] { "TEST" } );
So I'm missing the control's and the property's names to get to my final goal.
I'll answer my own post to recapitulate what I did to fix it when that's done.
I managed to make the serialization work as I intended so I'm going to recap what I changed from the code I originally posted.
First my property of custom type needed a set to be able to be modified by the editor.
[Editor(typeof(LabelTextCollectionEditor), typeof(UITypeEditor))]
public LabelTextCollection Titles { get; set; }
I wrongly assumed that the property's value was changing because the label's texts were effectively changing in the designer after using the editor.
That was happening because the editor could access the reference to the inner label array through the use of the LabelTextCollection.FromArray method.
With the setter, the property is now properly edited at design-time.
The rest of the changes are all in the serializer so i'm posting the whole updated code :
public class LabelTextCollectionSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
CodeStatementCollection codeObject = new CodeStatementCollection();
if (value is LabelTextCollection)
{
LabelTextCollection col = value as LabelTextCollection;
// Building the new string[] {} statement with the labels' texts as parameters
CodeExpression[] strings = new CodeExpression[col.Count()];
for (int i = 0; i < col.Count(); i++) strings[i] = new CodePrimitiveExpression(col[i]);
CodeArrayCreateExpression arrayCreation = new CodeArrayCreateExpression(typeof(string[]), strings);
// Building the call to the FromArray method of the currently serializing LabelTextCollection instance
ExpressionContext context = manager.Context.Current as ExpressionContext;
CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(context.Expression, "FromArray", arrayCreation);
codeObject.Add(methodInvoke);
}
return codeObject;
}
}
To recap the changes I made in that class :
Removed the call to the baseSerializer.Serialize method to manage the whole serialization myself
Initializing the codeObject variable as a new CodeStatementCollection
Building my call to the LabelTextCollection.FromArray method using CodeDom
All of that now successfully writes the line I wanted in the Designer.cs file.
PS :
Thanks to #TnTinMn for the help and the push in the right direction.
EDIT :
After thorough testing of the serializer, I realized that the labels' texts went back to their default value when rebuilding the assembly containing the LabeltextCollection type while having a design view of a form containing my custom control opened.
The reason for that was that the property of LabeltextCollection type could not be serialized because the condition value is LabelTextCollection was false in that case as there was a discrepancy between two LabelTextCollection types from different assembly versions.
To fix that, I removed any direct reference to the type and accessed the method I needed to call through the Type class.
That got me the following serializer code :
public class LabelTextCollectionSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
CodeStatementCollection codeObject = new CodeStatementCollection();
// Building the new string[] {} statement with the labels' texts as parameters
string[] texts = value.GetType().GetMethod("ToArray").Invoke(value, null) as string[];
CodeExpression[] strings = new CodeExpression[texts.Length];
for (int i = 0; i < texts.Length; i++) strings[i] = new CodePrimitiveExpression(texts[i]);
CodeArrayCreateExpression arrayCreation = new CodeArrayCreateExpression(typeof(string[]), strings);
// Building the call to the FromArray method of the currently serializing LabelTextCollection instance
ExpressionContext context = manager.Context.Current as ExpressionContext;
CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(context.Expression, "FromArray", arrayCreation);
codeObject.Add(methodInvoke);
return codeObject;
}
}
You could still test the type of value using Type.Name but as my serializer only manages a single type, that wasn't needed in my case.

Cannot add an item to a List in a constructor of a user control's base class

I seem to be getting a null reference error when I try to add an item to a list in the constructor of my CustomCheckoutProcess class. This class is inherited by a user control's class, so its default constructor is called when the control's constructor is called (I believe that's how it works).
Here's the relevant parts of the class definition for the checkout process (error seems to be line 12 - marked with a comment on line 11):
public class CustomShoppingCart : ShoppingCart {
private List<CheckoutProcessArgument> checkoutProcessArguments = List<CheckoutProcessArgument>();
public CustomShoppingCart()
{
GetBaseCartSteps();
CheckoutProcessArgument stepTwoArg = new CheckoutProcessArgument("Step2", new Guid("12345678-1234-1234-1234-123456789012"));
// when I comment this line out, everything works fine
checkoutProcessArguments.Add(stepTwoArg);
BuildCheckoutProcess();
}
private void GetBaseCartSteps()
{
baseCartSteps = new Dictionary<string, int>();
// iterate through base cart's steps and grab name/index pairs
foreach (CheckoutProcessStepInfo step in this.CheckoutProcessSteps)
{
baseCartSteps.Add(step.Name, step.StepIndex);
}
}
private bool CartContains(Guid productGuid)
{
for (int i = 0; i < ShoppingCartInfoObj.CartItems.Count; i++)
{
if (ShoppingCartInfoObj.CartItems[i].SKU.SKUGUID == productGuid)
{
return true;
}
}
return false;
}
private void BuildCheckoutProcess()
{
// create a list of ints to dynamically add or remove the indexes of the base cart's steps
List<int> steps = new List<int>();
if (checkoutProcessArguments != null)
{
// add steps if they are specified in the arguments list
foreach (CheckoutProcessArgument argument in checkoutProcessArguments)
{
if (CartContains(argument.ProductGUID))
{
int stepIndexToAdd;
baseCartSteps.TryGetValue(argument.StepName, out stepIndexToAdd);
steps.Add(stepIndexToAdd);
}
}
}
// sort the steps so that they'll be in the correct order
steps.Sort();
customSteps = new int[steps.Count];
customSteps = steps.ToArray();
}
}
And here's the struct that I'm using to create arguments for a custom checkout process:
public struct CheckoutProcessArgument
{
private string stepName;
private Guid productGUID;
public string StepName { get { return stepName; } }
public Guid ProductGUID { get { return productGUID; } }
public CheckoutProcessArgument(string stepName, Guid productGuid)
{
this.stepName = stepName;
this.productGUID = productGuid;
}
}
Here's the stack trace for the error I recieve:
Message: Object reference not set to an instance of an object.
Stack Trace:
at CustomShoppingCart..ctor()
...
Problem is, I can't seem to see what could possibly be null. Any ideas? I'm guessing the problem isn't that I'm adding something to a List in a constructor, but that I've bungled something else somewhere, but for the life of me I can't figure out what.
Try instantianting the list in the constructor immediately before you attempt to access it. Also, if the list is to not be assigned to again you can use the readonly keyword. Further note that we can instantiate the list and add stepTwoArg to it in a single line of code.
private readonly List<CheckoutProcessArgument> checkoutProcessArguments;
public CustomShoppingCart()
{
GetBaseCartSteps();
CheckoutProcessArgument stepTwoArg = new CheckoutProcessArgument("Step2", new Guid("12345678-1234-1234-1234-123456789012"));
checkoutProcessArguments = new List<CheckoutProcessArgument> { stepTwoArg };
BuildCheckoutProcess();
}

Serialize a class that uses reflection to fill its properties

I have the following code:
[Serializable]
public class CustomClass
{
public CustomClass()
{
this.Init();
}
public void Init()
{
foreach (PropertyInfo p in this.GetType().GetProperties())
{
DescriptionAttribute da = null;
DefaultValueAttribute dv = null;
foreach (Attribute attr in p.GetCustomAttributes(true))
{
if (attr is DescriptionAttribute)
{
da = (DescriptionAttribute) attr;
}
if (attr is DefaultValueAttribute)
{
dv = (DefaultValueAttribute) attr;
}
}
UInt32 value = 0;
if (da != null && !String.IsNullOrEmpty(da.Description))
{
value = Factory.Instance.SelectByCode(da.Description, 3);
}
if (dv != null && value == 0)
{
value = (UInt32) dv.Value;
}
p.SetValue(this, value, null);
}
}
private UInt32 name;
[Description("name")]
[DefaultValue(41)]
public UInt32 Name
{
get { return this.name; }
set { this.name = value; }
}
(30 more properties)
}
Now the weird thing is: when I try to serialize this class I will get an empty node CustomClass!
<CustomClass />
And when I remove Init from the constructor it works as expected! I will get the full xml representation of the class but ofcourse without values (all with value 0).
<CustomClass>
<Name>0</Name>
...
</CustomClass>
Also, when I comment out the body of Init, I will get the same as above (the one with default values)
I've tried it with a public method, with a Helper class everything, but it does not work. That is, instead of the expected:
<CustomClass>
<Name>15</Name>
...
</CustomClass>
I will get
<CustomClass />
It seems when I use reflection in this class, serialization is not possible.
Or to summarize: when I call Init or when I fill my properties with reflection -> Serialization fails, when I remove this code part -> Serialization works but of course without my values.
Is this true? And does somebody know an alternative for my solution?
It should automatically get something from the database based on the Description and when this returns nothing it falls back to the DefaultValue...
PS1: I am using the XmlSerializer
PS2: When I set a breakpoint before the serialization, I can see that all the properties are filled with the good values (like 71, 72 etc).
Now the weird thing is: when I try to serialize this class I will get an empty node CustomClass!
XmlSerializer uses DefaultValue to decide which values to serialize - if it matches the default value, it doesn't store it. This approach is consistent with similar models such as data-binding / model-binding.
Frankly, I would say that in this case both DefaultValueAttribute and DescriptionAttribute are poor choices. Write your own - perhaps EavInitAttribute - then use something like:
[EavInit(41, "name")]
public uint Name {get;set;}
Note that there are other ways of controlling this conditional serialization - you could write a method like:
public bool ShouldSerializeName() { return true; }
which will also work to convince it to write the value (this is another pattern recognised by various serialization and data-binding APIs) - but frankly this is even more work (it is per-property, and needs to be public, so it makes a mess of the API).
Finally, I would say that hitting the database multiple times (once per property) for every new object construction is very expensive - especially since many of those values are likely to be assigned values in a moment anyway (so looking them up is wasted effort). I would put a lot of thought into making this both "lazy" and "cached" if it was me.
An example of a lazy and "sparse" implementation:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Xml.Serialization;
static class Program
{
static void Main()
{
var obj = new CustomClass();
Console.WriteLine(obj.Name);
// show it working via XmlSerializer
new XmlSerializer(obj.GetType()).Serialize(Console.Out, obj);
}
}
public class CustomClass : EavBase
{
[EavInit(42, "name")]
public uint Name
{
get { return GetEav(); }
set { SetEav(value); }
}
}
public abstract class EavBase
{
private Dictionary<string, uint> values;
protected uint GetEav([CallerMemberName] string propertyName = null)
{
if (values == null) values = new Dictionary<string, uint>();
uint value;
if (!values.TryGetValue(propertyName, out value))
{
value = 0;
var prop = GetType().GetProperty(propertyName);
if (prop != null)
{
var attrib = (EavInitAttribute)Attribute.GetCustomAttribute(
prop, typeof(EavInitAttribute));
if (attrib != null)
{
value = attrib.DefaultValue;
if (!string.IsNullOrEmpty(attrib.Key))
{
value = LookupDefaultValueFromDatabase(attrib.Key);
}
}
}
values.Add(propertyName, value);
}
return value;
}
protected void SetEav(uint value, [CallerMemberName] string propertyName = null)
{
(values ?? (values = new Dictionary<string, uint>()))[propertyName] = value;
}
private static uint LookupDefaultValueFromDatabase(string key)
{
// TODO: real code here
switch (key)
{
case "name":
return 7;
default:
return 234;
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
protected class EavInitAttribute : Attribute
{
public uint DefaultValue { get; private set; }
public string Key { get; private set; }
public EavInitAttribute(uint defaultValue) : this(defaultValue, "") { }
public EavInitAttribute(string key) : this(0, key) { }
public EavInitAttribute(uint defaultValue, string key)
{
DefaultValue = defaultValue;
Key = key ?? "";
}
}
}

How can I use c# attributes?

I have to create a pdf report from several classes that are going to contains several properties.
I need to display the value of the propertie and a label in front of it.
Something like :
detailsCalcul :
Numero client : valueOfMyProperty.
...
I was thinking of doing something like this :
[NomRapport("detailsCalcul")]
public class MyClass
{
[NomChamp("Numero client")]
public string NumeroClient { get; set; }
}
I sucessfully acessed to the value of my two attributes :
System.Reflection.MemberInfo[] proprietes = typeof(MyClass).GetMembers();
MyClass client = new MyClass();
client.NumeroClient = "1234";
foreach (var p in proprietes)
{
var aa = p.GetCustomAttributes(true);
for (int i = 0; i < aa.Length; i++)
{
var test = aa[i];
if (test.GetType() == typeof(NomChampAttribute))
{
var nomChamp = ((NomChampAttribute)attributes[i]).ToString());
}
}
}
i would like to know if it is possible access to the value of my property while I am acessing to the attribute ?
Thanks for your help,
Guillaume
An attribute does not know the context to which it is applied; you cannot even get to the property, let alone the instance. However, if you have a PropertyInfo and an instance, then:
object value = property.GetValue(instance, null);

Categories

Resources