Is it possible to modify the attribute of a property at runtime?
let's say I have some class:
public class TheClass
{
[TheAttribute]
public int TheProperty { get; set; }
}
Is there a way to do this?
if (someCondition)
{
// disable attribute. Is this possible and how can this be done?
}
No this is not possible. You cannot modify attribute values from metadata, or metadata in general, at runtime
Strictly speaking the above is not true. There are certain APIs which do allow allow for some metadata generation and modification. But they are very scenario specific, (ENC, profiling, debugging) and should not be used in general purpose programs.
It depends; from a reflection perspective: no. You can't. But if you are talking about attributes used by System.ComponentModel in things like data-binding, they you can use TypeDescriptor.AddAttributes to append extra attributes. Or other customer models involving custom descriptors. So it depends on the use-case.
In the case of xml serialization, it gets more interesting. Firstly, we can use fun object models:
using System;
using System.Xml.Serialization;
public class MyData
{
[XmlAttribute]
public int Id { get; set; }
[XmlAttribute]
public string Name { get; set; }
[XmlIgnore]
public bool NameSpecified { get; set; }
static void Main()
{
var ser = new XmlSerializer(typeof(MyData));
var obj1 = new MyData { Id = 1, Name = "Fred", NameSpecified = true };
ser.Serialize(Console.Out, obj1);
Console.WriteLine();
Console.WriteLine();
var obj2 = new MyData { Id = 2, Name = "Fred", NameSpecified = false };
ser.Serialize(Console.Out, obj2);
}
}
The bool {name}Specified {get;set;} pattern (along with bool ShouldSerialize{name}()) is recognised and used to control which elements to include.
Another alternative is to use the non-default ctor:
using System;
using System.Xml.Serialization;
public class MyData
{
[XmlAttribute]
public int Id { get; set; }
public string Name { get; set; }
static void Main()
{
var obj = new MyData { Id = 1, Name = "Fred" };
XmlAttributeOverrides config1 = new XmlAttributeOverrides();
config1.Add(typeof(MyData),"Name",
new XmlAttributes { XmlIgnore = true});
var ser1 = new XmlSerializer(typeof(MyData),config1);
ser1.Serialize(Console.Out, obj);
Console.WriteLine();
Console.WriteLine();
XmlAttributeOverrides config2 = new XmlAttributeOverrides();
config2.Add(typeof(MyData), "Name",
new XmlAttributes { XmlIgnore = false });
var ser2 = new XmlSerializer(typeof(MyData), config2);
ser2.Serialize(Console.Out, obj);
}
}
Note though that if you use this second approach you need to cache the serializer instance, as it emits an assembly every time you do this. I find the first approach simpler...
Attributes are baked into code at compilation time. The only way you can define new attributes at run time is to generate new code at runtime (using Reflection.Emit, for example). But you cannot change the attributes of existing code.
You can put Boolean variable in the class to disable/enable the property instead of disabling it at run time.
You might want to look at this http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/5b0d356d-d006-43ff-bfcd-aa90dd8de6db
And Dave Morton's explanation on this blog http://blog.codinglight.com/2008/10/changing-attribute-parameters-at.html
Sounds like you want to consider implementing IXmlSerializable
You can implement IDataErrorInfo, then check range in Validate method.
public string this[string property] {
get { return Validate(property); }
}
public string Error { get; }
protected virtual string Validate(string property) {
var propertyInfo = this.GetType().GetProperty(property);
var results = new List<ValidationResult>();
var result = Validator.TryValidateProperty(
propertyInfo.GetValue(this, null),
new ValidationContext(this, null, null) {
MemberName = property
},
results);
if (!result) {
var validationResult = results.First();
return validationResult.ErrorMessage;
}
return string.Empty;
}
In sub class
protected override string Validate(string property) {
Debug.WriteLine(property);
if (property == nameof(YourProperty)) {
if (_property > 5) {
return "_property out of range";
}
}
return base.Validate(property);
}
Related
I have below method on service layer which will be called from different controller with different actionType(parameter to my method).
public async Task<OutPutList[]> GetMyDataAsync(string actionType)
{
var request = new getMyDataRequest
{
getMyDatas = new getMyDatas
{
AFlag= false,
AFlagSpecified = false,
BFlag=false,
BFlagSpecified=false,
cFlag=false,
cFlagSpecified=false,
commonproperty= ""
}
};
return await _myService.method(request);
}
If the actionType is specified as "A" , the AFlag and AFlagSpecified properties value to be set as "True" . And if i specify actionType as B then BFlag and BFlagSpecified values to be set as true and AFlag will be false.how this can be done dynamically or in any smpler way? I am having around 12 actiontypes and if i create different request object then i need to write 12 if else condition. Can this be simplified with minimum code.
You can use Reflection to make your code dynamic.
To make you understand more, You can look below code
getMyDatas.cs
public class getMyDatas
{
public bool AFlag { get; set; }
public bool BFlag { get; set; }
public bool CFlag { get; set; }
}
Program.cs
using StackOverFlowTest;
using System;
using System.Reflection;
//input
string actionType = "A";
//Business
var Data = new getMyDatas();
PropertyInfo[] properties = typeof(getMyDatas).GetProperties();
foreach (PropertyInfo property in properties)
{
property.SetValue(Data, Convert.ChangeType(property.Name.StartsWith(actionType), property.PropertyType), null);
}
//Testing
Console.WriteLine(Data.AFlag);
Console.WriteLine(Data.BFlag);
Console.WriteLine(Data.CFlag);
Shortly, I check if property name starts with input
I have a console aplication where I am trying to obtain the values of the properties of an object dynamically:
class Program
{
static void Main(string[] args)
{
DtoCartaCompromiso test = new DtoCartaCompromiso() { CodProducto = 1,
DescProducto = "aaa",
CodProveedor = 2,
DescProveedor = "bbb",
FechaExpiracion = DateTime.Now,
FechaMaxEntrega = DateTime.Now,
NumLote = "22" };
var testlist = new List<DtoCartaCompromiso>();
testlist.Add(test);
List<Header> columns = new List<Header>() { new Header{Name= "CodProducto"},new Header{Name= "NumLote"},new Header{Name= "DescProducto"},new Header{Name= "CodProveedor"},new Header{Name= "DescProveedor"},new Header{Name= "FechaExpiracion"},new Header{Name= "FechaExpiracion"},new Header{Name= "FechaMaxEntrega"} };
foreach (var d in testlist)
{
foreach (var col in columns)
{
string value = ((d.GetType().GetProperty(col.Name).GetValue(d, null)) ?? "").ToString();
Console.WriteLine(value);
}
}
Console.Read();
}
}
public class DtoCartaCompromiso
{
public int CodProducto;
public string NumLote;
public string DescProducto;
public int CodProveedor;
public string DescProveedor;
public Nullable<DateTime> FechaExpiracion;
public Nullable<DateTime> FechaMaxEntrega;
}
public class Header
{
public string Name;
}
i am getting the error "Object reference not set to an instance of an object" when I get to the line:
string value = ((d.GetType().GetProperty(col.Name).GetValue(d, null)) ?? "").ToString();
the error seems to occur when I get to the GetProperty() method, but I dont understand why
The problem is that you don't have properties there in your classes, they are public fields really. A public property looks like
public string PropertyName { get; set; }
but in your case there is lack of both getters and setters.
Change GetProperty() to GetField() and it will work. Or make your fields properties. Personally, I would go with the second option since it is a better idea to use properties instead of public fields.
Best guess without knowing more about your application, is the following sub-exression results in null:
d.GetType().GetProperty(col.Name)
At that point, the subsequent .GetValue() call will fail with your reported error.
Your property is probably not public. Make it public or pass System.Reflection.BindingFlags.NonPublic to your GetProperty call.
More info: http://msdn.microsoft.com/en-us/library/zy0d4103(v=vs.110).aspx
There seems to be a bug / inconsistency in the Microsoft XmlSerializer: If you have a property marked with a System.ComponentModel.DefaultValue attribute, this does not get serialized. Fair enough - this could be seen as an expected behavior.
The problem is the same attribute is not respected when deserializing. The code below illustrates the issue.
Question is how could I bypass this? I have potentially hundreds of business classes with default values used in the UI tier (Views), so default value initialization in constructor is not an option. It has to be something generic. I could create a completely new default attribute, but it seems like duplicate work. Do you see a way to override the XmlSerializer behavior or should I use just another serializer that does the job better?
The example code:
public class DefaultValueTestClass
{
[System.ComponentModel.DefaultValue(10000)]
public int Foo { get; set; }
}
[TestMethod]
public void SimpleDefaultValueTest()
{
// Create object and set the property value TO THE DEFAULT
var before = new DefaultValueTestClass();
before.Foo = 10000;
// Serialize => xml
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(DefaultValueTestClass));
string xml;
using (var stream = new System.IO.StringWriter())
{
serializer.Serialize(stream, before);
xml = stream.ToString();
}
// Deserialize the same object
DefaultValueTestClass after;
using (var reader = new System.IO.StringReader(xml))
{
after = (DefaultValueTestClass)serializer.Deserialize(reader);
}
// before.Foo = 10000
// after.Foo = 0
Assert.AreEqual(before.Foo, after.Foo);
}
It is your job to implement the defaults; [DefaultValue] merely says "this is the default, you don't need to worry about this" - it doesn't apply it. This applies not just to XmlSerializer, but to the core System.ComponentModel API to which [DefaultValue] belongs (which drives things like the bold / not-bold in PropertyGrid, etc)
Basically:
public class DefaultValueTestClass
{
public DefaultValueTestClass()
{
Foo = 10000;
}
[DefaultValue(10000)]
public int Foo { get; set; }
}
will work in the way you expect. If you want it to serialize whether or not it is that particular value, then the correct implementations is:
public class DefaultValueTestClass
{
public DefaultValueTestClass()
{
Foo = 10000;
}
public int Foo { get; set; }
}
If you want to preserve the [DefaultValue], but want it to always serialize, then:
public class DefaultValueTestClass
{
[DefaultValue(10000)]
public int Foo { get; set; }
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public bool ShouldSerializeFoo() { return true; }
}
Where ShouldSerialize* is another pattern from System.ComponentModel that is recognised by several serializers.
And here's some UI code to show that XmlSerializer is actually doing exactly the same things that the UI code (built on System.ComponentModel) has always done:
using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
[System.STAThread]
static void Main()
{
Application.EnableVisualStyles();
using (var form = new Form())
using (var grid = new PropertyGrid())
{
grid.Dock = DockStyle.Fill;
var obj = new DefaultValueTestClass
{ // TODO - try with other numbers to
// see bold / not bold
Foo = 10000
};
// note in the grid the value is shown not-bold; that is
// because System.ComponentModel is saying
// "this property doesn't need to be serialized"
// - or to show it more explicitly:
var prop = TypeDescriptor.GetProperties(obj)["Foo"];
bool shouldSerialize = prop.ShouldSerializeValue(obj);
// ^^^ false, because of the DefaultValueAttribute
form.Text = shouldSerialize.ToString(); // win title
grid.SelectedObject = obj;
form.Controls.Add(grid);
Application.Run(form);
}
}
}
public class DefaultValueTestClass
{
[System.ComponentModel.DefaultValue(10000)]
public int Foo { get; set; }
}
I dont know how to name the question properly, so fell free to change it. My question is, I have around 10 methods that look like:
[WebMethod(EnableSession = true)]
public string ReadUserAdditional()
{
EUser user = (EUser)Session["user"];
var json = new { result = true, user.Image, user.Biography };
return new JavaScriptSerializer().Serialize(json);
}
[WebMethod(EnableSession = true)]
public string ReadUserBasicInformation()
{
EUser user = (EUser)Session["user"];
var json = new { result = true, user.Name, user.Username};
return new JavaScriptSerializer().Serialize(json);
}
The methods are very similar, but they return different fields. Im thinking about refactoring all methods into one, receveing the fields to return as parameters. Is it a good idea? How can I do that? Reflection?
First of all you need to know that object and dictionary are presented in json simmilar.
[WebMethod(EnableSession = true)]
public string ReadUserAdditional()
{
return GetUserInfo(new []
{
new FieldInfo {Name = "Image", u => u.Image},
new FieldInfo {Name = "Biography", u => u.Biography}
});
}
private string GetUserInfo(FieldInfo[] infos)
{
EUser user = (EUser)Session["user"];
var dict = new Dictionary<string, object>{ { "result", true } };
foreach(var info in infos)
{
dictionary.Add(info.Name, info.Accessor(user));
}
return new JavaScriptSerializer().Serialize(dict );
}
public class FieldInfo
{
public Func<EUser, object> Accessor { get; set; }
public string Name { get; set;}
}
I don't think it's a terrible idea, especially if you have tons of these methods and want to simplify your API.
A few downsides:
1) Reflection comes at a perf cost. This probably doesn't matter a whole lot unless you're the size of Twitter.
2) There would potentially be security concerns if the data had any properties you do NOT wanting users getting access to, such as some sort of internal database keys or what not. Make sure every property on your class is one you're totally okay becoming public information.
You can use a lambda to refactor away the duplication:. This would reduce all your methods to a single line of code:
[WebMethod(EnableSession = true)]
public string ReadUserAdditional()
{
return GetUserJSON(x => new { result = true, x.Image, x.Biography });
}
[WebMethod(EnableSession = true]
public string ReadUserBasicInformation()
{
return GetUserJSON(x => new { result = true, x.Name, x.UserName });
}
private string GetUserJSON(Func<EUser, string> jsonFields)
{
EUser user = (EUser)Session["user"];
var json = jsonFields(user);
return new JavaScriptSerializer().Serialize(json);
}
Another approach is to use Automapper or similar library to project your data.
[WebMethod(EnableSession = true)]
public string ReadUserAdditional()
{
return GetUserInfo<UserAdditionalDto>();
}
private string GetUserInfo<TDto>(FieldInfo[] infos)
{
EUser user = (EUser)Session["user"];
var dto = Mapper.Map<TDto>(user); // Mapper is Automapper entry class.
return new JavaScriptSerializer().Serialize(dto );
}
public class UserAdditionalDto
{
public string Image { get; set; }
public string Biography { get; set;}
}
I have created a custom Property Selector to accept an array in the constructor to say which properties should be included in the search. The approach works well as long as there are no component types, but how do I deal with those? Here is an example:
public class Customer
{
public virtual int Id { get; private set; }
public virtual Name Name { get; set; }
public virtual bool isPreferred { get; set; }
//...etc
}
public class Name
{
public string Title { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Fullname { get; }
}
public class CustomerPropertySelector : Example.IPropertySelector
{
private string[] _propertiesToInclude = { };
public CustomerPropertySelector(string[] propertiesToInclude)
{
this._propertiesToInclude = propertiesToInclude;
}
public bool Include(object propertyValue, String propertyName, NHibernate.Type.IType type)
{
//...Checking for null and zeros etc, excluded for brevity
if (!_propertiesToInclude.Contains(propertyName))
return false;
return true;
}
}
I would like to be able to search by first name, but not necessarily last. The property name is Name however, so both first and last names seem to be part of the same property, and something like Name.Firstname, which would normally work as a criterion, doesn't seem to work here. What would be the best way around that?
EXAMPLE:
Customer exampleCust = new Customer(FirstName: "Owen");
IList<Customer> matchedCustomers = _custRepo.GetByExample(exampleCust, new string[] { "Name.FirstName" });
Given that there are 2 customers in db, only one named "Owen", but both have isPreferred = false, I would like my query to only return the first one. Standard QBE will return both based on isPreferred property.
SOLUTION:
Thank you for the answers, the solution is mostly based on answer by therealmitchconnors, however I couldn't have done it without Mark Perry's answer either.
The trick was to realise that rather than including Name.FirstName property, I actually want to exclude Name.LastName, since QBE only allows us to exclude properties. I used a method adapted from therealmitchconnors's answer to help me determine fully qualified names of properties. Here is the working code:
public IList<T> GetByExample(T exampleInstance, params string[] propertiesToInclude)
{
ICriteria criteria = _session.CreateCriteria(typeof(T));
Example example = Example.Create(exampleInstance);
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var childProperties = GetChildProperties(prop);
foreach (var c in childProperties)
{
if (!propertiesToInclude.Contains(c))
example.ExcludeProperty(c);
}
}
criteria.Add(example);
return criteria.List<T>();
}
private IEnumerable<string> GetChildProperties(System.Reflection.PropertyInfo property)
{
var builtInTypes = new List<Type> { typeof(bool), typeof(byte), typeof(sbyte), typeof(char),
typeof(decimal), typeof(double), typeof(float), typeof(int), typeof(uint), typeof(long),
typeof(ulong), typeof(object), typeof(short), typeof(ushort), typeof(string), typeof(DateTime) };
List<string> propertyNames = new List<string>();
if (!builtInTypes.Contains(property.PropertyType) && !property.PropertyType.IsGenericType)
{
foreach (var subprop in property.PropertyType.GetProperties())
{
var childNames = GetChildProperties(subprop);
propertyNames = propertyNames.Union(childNames.Select(r => property.Name + "." + r)).ToList();
}
}
else
propertyNames.Add(property.Name);
return propertyNames;
}
I wasn't sure of the best way to determine whether a property was a component class or not, any suggestions on how to improve the code are very welcome.
The following code would replace the logic you are using to populate propertiesToInclude. I changed it from an array to a list so I could use the Add method because I am lazy, but I think you get the picture. This does only work for one sub-level of properties. For n levels you would need to recurse.
List<string> _propertiesToInclude = new List<string>();
Type t;
var props = t.GetProperties();
foreach (var prop in props)
{
if (prop.PropertyType.IsClass)
foreach (var subprop in prop.PropertyType.GetProperties())
_propertiesToInclude.Add(string.Format("{0}.{1}", prop.Name, subprop.Name));
else
_propertiesToInclude.Add(prop.Name);
}
I thought I had something but reading your question again you want to know why the QBE NHibernate code doesn't work with component properties.
I think you need to create a sub-criteria query for the Name part.
Perhaps something like this:
public IList<Customer> GetByExample(Customer customer, string[] propertiesToExclude){
Example customerQuery = Example.Create(customer);
Criteria nameCriteria = customerQuery.CreateCriteria<Name>();
nameCriteria.Add(Example.create(customer.Name));
propertiesToExclude.ForEach(x=> customerQuery.ExcludeProperty(x));
propertiesToExclude.ForEach(x=> nameCriteria.ExcludeProperty(x));
return customerQuery.list();
}
This is an example from the NHibernate Test Project, it shows how to exclude Component properties.
[Test]
public void TestExcludingQBE()
{
using (ISession s = OpenSession())
using (ITransaction t = s.BeginTransaction())
{
Componentizable master = GetMaster("hibernate", null, "ope%");
ICriteria crit = s.CreateCriteria(typeof(Componentizable));
Example ex = Example.Create(master).EnableLike()
.ExcludeProperty("Component.SubComponent");
crit.Add(ex);
IList result = crit.List();
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Count);
master = GetMaster("hibernate", "ORM tool", "fake stuff");
crit = s.CreateCriteria(typeof(Componentizable));
ex = Example.Create(master).EnableLike()
.ExcludeProperty("Component.SubComponent.SubName1");
crit.Add(ex);
result = crit.List();
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Count);
t.Commit();
}
}
Source code link