my class property has default value which will be serialize.
public class DeclaredValue
{
[XmlElement(ElementName = "Amount", DataType = "double", IsNullable = false), DefaultValue(999)]
public double Amount { get; set; }
[XmlElement(ElementName = "Reference2", DataType = "string", IsNullable = false), DefaultValue("")]
public string Reference2 { get; set; }
}
so we create instance of DeclaredValue class and provide value for Reference2 property and do not assign anything for Amount. so when we serialize the class DeclaredValue then no tag found for amount in my xml. i mention default value for amount "999" then why it does not work in serialization. i want that if do not assign anything for amount then amoun tag should be there in my xml with default value.
to do this what way i need to decorate the amount property that it always comes with default value in xml after serialization if user do not assign anything to this property.
please guide me what i need to change in the code to get my desired output.
Per the note on MSDN:
A DefaultValueAttribute will not cause
a member to be automatically
initialized with the attribute's
value. You must set the initial value
in your code.
Somewhat surprisingly the DefaultValue only regulates the writing of an object, members that are equal to their DefaultValue will not be written out.
You must still initialize members before or after loading yourself, for example in the constructor.
Let me thoroughly describe what is happening.
When XmlSerializer Deserialize() method is called, it creates a new object using a default constructor. It doesn't apply any DefaultValueAttributes to this object, I beleave, because of assumption that default ctor should "know better" how to initialize values by default. From this point of view - it is logical.
XmlSerializer doesn't serialize members which values are the same as marked by DefaultValue attribute. From some point of view such behavior is driven by logic too.
But when you do not initialize members in ctor and call deserialize method, XmlSerializer see no corresponding xml field, but it see that the field/property has DefaultValueAttribute, serializer just leave such value (according to the assumption that the default constructor knows better how to initialize a class "by defaults"). And you've got your zeros.
Solution
To initialize a class members by these DefaultValueAttributes (sometimes it is very handy to have this initialization values just in place) you can use such simple method:
public YourConstructor()
{
LoadDefaults();
}
public void LoadDefaults()
{
//Iterate through properties
foreach (var property in GetType().GetProperties())
{
//Iterate through attributes of this property
foreach (Attribute attr in property.GetCustomAttributes(true))
{
//does this property have [DefaultValueAttribute]?
if (attr is DefaultValueAttribute)
{
//So lets try to load default value to the property
DefaultValueAttribute dv = (DefaultValueAttribute)attr;
try
{
//Is it an array?
if (property.PropertyType.IsArray)
{
//Use set value for arrays
property.SetValue(this, null, (object[])dv.Value);
}
else
{
//Use set value for.. not arrays
property.SetValue(this, dv.Value, null);
}
}
catch (Exception ex)
{
//eat it... Or maybe Debug.Writeline(ex);
}
}
}
}
}
This "public void LoadDefaults()", can be decorated as an Extension to object or use as some static method of a helper class.
As Henk Holterman mentionned, this attribut doesn't set the default value automatically. Its purpose is mostly to be used by visual designers to reset a property to its default value.
As others mentioned, the DefaultValue attribute doesn't initialize the property. You could use a simple loop to set all properties:
foreach (var property in GetType().GetProperties())
property.SetValue(this, ((DefaultValueAttribute)Attribute.GetCustomAttribute(
property, typeof(DefaultValueAttribute)))?.Value, null);
Even though ?.Value could return null, it works with non-nullable types, I tested this.
If only few of your properties have a default value, you should maybe only set the value if it is there.
If all properties should have a default value, remove the ? to get an error if you forgot one.
Most likely, arrays won't work, see MajesticRa's solution how to handle that.
Related
I always add an Uninitialized value to all my enums and set it to 0 to handle cases where I deserialize an object that has an enum property value that was never set.
enum MyEnum
{
Uninitialized = 0,
MyEnumValue1 = 1,
MyEnumValue2 = 2,
MyEnumValue3 = 3,
}
However, I don't want the Uninitialized value to show up in my Swagger documentation.
I've tried adding the [JsonIgnore] attribute to that value, but that didn't work.
Anyone know how to accomplish this?
Just in case anyone else struggles with this. You can create a custom SchemaFilter and populate the Enum property filtering on those enum values with a custom attribute (in this example: OpenApiIgnoreEnumAttribute).
public class OpenApiIgnoreEnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
var enumOpenApiStrings = new List<IOpenApiAny>();
foreach (var enumValue in Enum.GetValues(context.Type))
{
var member = context.Type.GetMember(enumValue.ToString())[0];
if (!member.GetCustomAttributes<OpenApiIgnoreEnumAttribute>().Any())
{
enumOpenApiStrings.Add(new OpenApiString(enumValue.ToString()));
}
}
schema.Enum = enumOpenApiStrings;
}
}
}
public class OpenApiIgnoreEnumAttribute : Attribute
{
}
public enum ApplicationRole
{
[OpenApiIgnoreEnum]
DoNotExpose = 1,
ValueA = 2,
ValueB = 3,
}
You can use an IDocumentFilter to remove anything you want from the specs.
It might not be intuitive at first, look at some of the examples they have:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/search?q=IDocumentFilter
With that you are able to change the swagger json spec to remove or inject anything you want.
Now be careful you could end up with a non-compliant spec, always check it against the validator: https://validator.swagger.io/validator/debug?url=http://swagger-net-test.azurewebsites.net/swagger/docs/V1
You can simply omit your Uninitialized enum value to solve this.
Enums can actually contain values other than the ones you explicitly define. I can do var myEnumValue = (MyEnum)12345; and it won't break or throw an exception, but it won't match any of the explicitly defined enum values either.
As long as the defined values do not equal default(int), or the default of whatever you chose your enum type to be, you can still work with the enum as expected, and catch unitialized values with a switch default case.
This has the added benefit of catching all unlisted enum values, not just the one you explicitly declared.
Royston46's answer was excellent and helped build a foundation, but I had to make a couple of modifications to get it to fully work for my scenario.
The first issue was handling nullable types. If the underlying property associated with the enum is nullable, context.Type.IsEnum will return false.
The second issue is that finding the member by value to examine the custom attributes won't work correctly when you have multiple enums with the same value. In our case, we deprecated some old enum names, but left them in the enum for compatibility with existing API consumers. However, we needed the documentation to only show the new names.
Here is the revised implementation that we built from Royston46's excellent answer:
public class OpenApiIgnoreEnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type.IsEnum || (Nullable.GetUnderlyingType(context.Type)?.IsEnum ?? false))
{
var type = context.Type.IsEnum ? context.Type : Nullable.GetUnderlyingType(context.Type);
var enumOpenApiStrings = new List<IOpenApiAny>();
foreach (var enumName in Enum.GetNames(type))
{
var member = type.GetMember(enumName)[0];
if (!member.GetCustomAttributes<OpenApiIgnoreEnumAttribute>().Any())
{
enumOpenApiStrings.Add(new OpenApiString(enumName));
}
}
schema.Enum = enumOpenApiStrings;
}
}
}
I am confused on how XmlSerializer works behind the scenes. I have a class that deserializes XML into an object. What I am seeing is for the following two elements that are NOT part of the Xml being deserialized.
[XmlRootAttribute("MyClass", Namespace = "", IsNullable = false)]
public class MyClass
{
private string comments;
public string Comments
{
set { comments = value; }
get { return comments; }
}
private System.Collections.Generic.List<string> tests = null;
public System.Collections.Generic.List<string> Tests
{
get { return tests; }
set { tests = value; }
}
}
Let's take the following XML as an example:
<MyClass>
<SomeNode>value</SomeNode>
</MyClass>
You notice that Tests and Comments are NOT part of the XML.
When this XML gets deserialized Comments is null(which is expected) and Tests is an empty list with a count of 0.
If someone could explain this to me it would be much appreciated. What I would prefer is that if <Tests> is missing from the XML then the list should remain null, but if a (possibly empty) node <Tests /> is present then the list should get allocated.
What you are observing is that members referring to modifiable collections such as List<T> are automatically pre-allocated by XmlSerializer at the beginning of deserialization. I am not aware of any place where this behavior is documented. It may be related to the behavior described in this answer to XML Deserialization of collection property with code defaults, which explains that, since XmlSerializer supports adding to get-only and pre-allocated collections, if a pre-allocated collection contains default items then the deserialized items will be appended to it - possibly duplicating the contents. Microsoft may simply have chosen to pre-allocate all modifiable collections at the beginning of deserialization as the simplest way of implementing this.
The workaround from that answer, namely to use a surrogate array property, works here as well. Since an array cannot be appended to, XmlSerializer must accumulate all the values and set them back when deserialization is finished. But if the relevant tag is never encountered, XmlSerializer apparently does not begin accumulating values and so does not call the array setter. This seems to prevent the default pre-allocation of collections that you don't want:
[XmlRootAttribute("MyClass", Namespace = "", IsNullable = false)]
public class MyClass
{
private string comments;
public string Comments
{
set { comments = value; }
get { return comments; }
}
private System.Collections.Generic.List<string> tests = null;
[XmlIgnore]
public System.Collections.Generic.List<string> Tests
{
get { return tests; }
set { tests = value; }
}
[XmlArray("Tests")]
public string[] TestsArray
{
get
{
return (Tests == null ? null : Tests.ToArray());
}
set
{
if (value == null)
return;
(Tests = Tests ?? new List<string>(value.Length)).AddRange(value);
}
}
}
Sample .Net fiddle showing that Tests is allocated only when appropriate.
When you apply [System.Xml.Serialization.XmlElement(IsNullable = true)] to the property, the List will be null after deserialization.
Another possibility is to use the "magic" "Specified" suffix:
public bool TestsSpecified {get;set;}
If you have a serialized field/property XXX and a boolean property XXXSpecified, then the bool property is set according to whether or not the main field/property was set.
We wound up here after a google search for the same issue.
What we ended up doing was checking for Count == 0, after deserialization, and manually setting the property to null;
...
var varMyDeserializedClass = MyXmlSerializer.Deserialize(new StringReader(myInput));
if (varMyDeserializedClass.ListProperty.Count == 0)
{
varMyDeserializedClass.ListProperty = null;
}
...
It's a cheap workaround, but provides the expected result and is useful to avoid refactoring or redesign.
I want to option to set a property with multiple types and am struggling to find a solution.
public static PropertyType Property
{
get { return Property;}
set {
if (value.GetType() == typeof(PropertyType))
{
Property = value;
}
//Or any other type
if (value.GetType() == typeof(string))
{
Property = FunctionThatReturnsPropertyType(value);
}
}
}
I hope that makes sense, I am only ever setting the Property as one type but I would like to be able to assign to it with other types and then transform them within the setter - is this possible?
What you want looks like design error.
In C# property's setter and getter have always the same type. So you have basically next choices:
Make your property type object (or dynamic if you want to get even worse design) and transform values within the setter as you stated in the question - i strongly recommend to avoid this approach.
Get away from property concept and create separate methods to get value of the field and assign from different types. This approach will allow you to assign value if you dont know the type at compile-time while getter-method will be typed still correctly. But generally it still looks like bad design.
Make all the transformations outside the property, This solution is preferred. You should know which type you will use in every separate case.
Try Property type as object.
public static Object PropertyName
{
get { return PropertyName; }
set { PropertyName = value; }
}
In a base class I have the following method for derived classes:
protected virtual void SetValue<T>(ref T field, string propertyName, T value)
{
//assign value to the field and do some other staff
...
}
Is there any way to check if fieldVar has an attribute applied (for example DataMemberAttribute)?
No, there is no way to do that, except that it looks like you're also told the property name.
If you can find the FieldInfo of the field, then you can find any attributes, but not through the ref-parameter alone.
Reading between the lines you have a set of private fields which back the values of public properties. On some or all of these properties you some data attributes attached that you want to discover.
PropertyInfo pi = this.GetType().GetProperty(propertyName);
object[] dataMemberAttributes = pi.GetCustomAttributes(typeof(DataMemberAttribute, true);
if (dataMemberAttributes.Length > 0)
{
DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)dataMemberAttributes[0];
// Do stuff with the attribute.
}
Taking a look at the following question, Real world use of custom .NET attributes how would you implement the solution proposed by #Esteban?
I'm a little confused as to when and where the code would get executed I think. Could you please supply a good sample of code.
I've asked this question before but didn't properly phrase it I think so...
With respect to the question/answer that you reference, I assume that there would be some code that runs either in the data layer or in the class itself that does validation. That code would use Reflection on the object being validated to find the properties with different attributes and run the specific validation logic associated with that attribute on that property.
It might look something like the following:
public void Validate( object obj )
{
foreach (var property in obj.GetType().GetProperties())
{
var attribute = property.GetCustomAttributes(typeof(ValidationAttribute), false);
var validator = ValidationFactory.GetValidator( attribute );
validator.Validate( property.GetValue( obj, null ) );
}
}
On submit(save) of the html form(win form) you get back changed Customer class. For each property you check if it has custom attribute(inherited from ValidationAttribute or implementing IValiador interface or something like this) associated with it. For each such property you call the validate method of attribute(create appropriate validation class and call validate method) on the property value.
You would use reflection:
public class MyClass
{
[Description("I'm an attribute!")]
public int MyField;
public Attribute GetAttribute(string fieldName)
{
FieldInfo field = typeof(MyClass).GetField("MyField");
Attribute[] attributes = (Attribute[])field.GetCustomAttributes(typeof(Attribute), false);
DescriptionAttribute desc = (DescriptionAttribute)attributes[0];
return desc;
}
}
If the attributed member is a field, you would use FieldInfo, as used in the example. If it's a property, you would use PropertyInfo, the members of FieldInfo and PropertyInfo are pretty much the same.