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;
}
}
}
Related
Here, the question was posed how to validate non-nullable required types.
The provided solution to make the field nullable like the following is not desirable in my case.
[Required]
public int? Data { get; set; }
How can you change the behavior to instead make the following fail validation in the cases where the field is omitted from the request.
[Required]
public int Data { get; set; }
I have tried a custom validator, but these do not have information about the raw value and only see the default 0 value. I have also tried a custom model binder but it seems to work at the level of the entire request model instead of the integer fields which a want. My binder experiment looks like this:
public class RequiredIntBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(int))
throw new InvalidOperationException($"{nameof(RequiredIntBinder)} can only be applied to integer properties");
var value = bindingContext.ValueProvider.GetValue(bindingContext.BinderModelName);
if (value == ValueProviderResult.None)
{
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
return new SimpleTypeModelBinder(bindingContext.ModelType).BindModelAsync(bindingContext);
}
}
public class RequiredIntBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(int))
{
return new BinderTypeModelBinder(typeof(RequiredIntBinder));
}
return null;
}
}
and is registered with mvc like this
options.ModelBinderProviders.Insert(0, new RequiredIntBinderProvider());
but the model binder is never used. I feel like I might be close but cannot connect the last dots.
Solution working with json requests
You cannot validate an already created model instance, because a non-nullable property has always a value (no matter whether it was assigned from json or is a default value). The solution is to report the missing value already during deserialization.
Create a contract resolver
public class RequiredPropertiesContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
foreach (var contractProperty in contract.Properties)
{
if (contractProperty.PropertyType.IsValueType
&& contractProperty.AttributeProvider.GetAttributes(typeof(RequiredAttribute), inherit: true).Any())
{
contractProperty.Required = Required.Always;
}
}
return contract;
}
}
and then assign it to SerializerSettings:
services.AddMvc()
.AddJsonOptions(jsonOptions =>
{
jsonOptions.SerializerSettings.ContractResolver = new RequiredPropertiesContractResolver();
});
The ModelState is then invalid for non-nullable properties with the [Required] attribute if the value is missing from json.
Example
Json body
var jsonBody = #"{ Data2=123 }"
is invalid for model
class Model
{
[Required]
public int Data { get; set; }
public int Data2 { get; set; }
}
Everything from the request is just a string. The modelbinder matches up keys in the request body with property names, and then attempts to coerce them to the appropriate type. If the property is not posted or is posted with an empty string, that will obviously fail when trying to convert to an int. As a result, you end up with the default value for the type. In the case of an int that's 0, while the default value of int? is null.
Only after this binding process is complete is the model then validated. Remember you're validating the model not the post body. There's no reasonable way to validate the post body, since again, it's just a a bunch of key-value pair strings. Therefore, in the case of an int property that's required, but not posted, the value is 0, which is a perfectly valid value for an int, and the validation is satisfied. In the case of int?, the value is null, which is not a valid int, and thus fails validation. That is why the nullable is required, if you want to require a non-nullable type have a value. It's the only way that an empty value can be differentiated from simply a "default" value.
If you are using view models, as you should be, this should not be an issue. You can bind to a nullable int with a required attribute, and you will be assured that it will have a value, despite being nullable, if your model state is valid. Then, you can map that over to a straight int on your entity. That is the correct way to handle things.
non-nullable required types.
You do not. It is either required - then there is no sense in it being nullable - or it is not required, then you nullable makes sense, but it makes no sense to require it.
Attributes are always for the whole request. You are in a logical problem because you try to use them not as intended.
If it is optional, the user should actually submit a patch, not a put/post.
There was the way to do that, at least it works for me, try [BindRequired] for non-nullable types.
I have an enum on helper library in my solution.
For example
public enum MyEnum
{
First,
Second
}
I want to use MyEnum in a few another project. I want to decorate this enum in each project with own attribute like this:
public enum MyEnum
{
[MyAttribute(param)]
First,
[MyAttribute(param2)]
Second
}
How to decorate enum from another library with own local attribute?
You can't do what you've described - the best you can do is to create a new Enum that uses the same set of values. You will then need to cast to the "real" enum whenever you use it.
You could use T4 templates or similar to generate the attributed enum for you - it would be much safer that way as it would be very easy to map the wrong values, making for some very subtle bugs!
Linqpad Query
enum PrimaryColor
{
Red,
Blue,
Green
}
enum AttributedPrimaryColor
{
[MyAttribute]
Red = PrimaryColor.Red,
[MyAttribute]
Blue = PrimaryColor.Blue,
[MyAttribute]
Green = PrimaryColor.Green
}
static void PrintColor(PrimaryColor color)
{
Console.WriteLine(color);
}
void Main()
{
// We have to perform a cast to PrimaryColor here.
// As they both have the same base type (int in this case)
// this cast will be fine.
PrintColor((PrimaryColor)AttributedPrimaryColor.Red);
}
Attributes are compile-time additions (metadata) to code. You can not modify them when using the compiled code assembly.
(Or perhaps you could if you are a diehard low-level IL wizard, but I certainly am not...)
If your enum values require modification or parameters at various places, then you should consider other solutions, e.g. a Dictionary or even a Database Table.
E.g. using a Dictionary:
var values = new Dictionary<MyEnum, int>()
{
{ MyEnum.First, 25 },
{ MyEnum.Second, 42 }
};
var valueForSecond = values[MyEnum.Second]; // returns 42
You can do something like this, but it will be tedious.
The idea is to use your project settings to allow the change when you import the enum in a new project.
First, you will need 2 attributes:
// This one is to indicate the format of the keys in your settings
public class EnumAttribute : Attribute
{
public EnumAttribute(string key)
{
Key = key;
}
public string Key { get; }
}
// This one is to give an id to your enum field
[AttributeUsage(AttributeTargets.Field)]
public class EnumValueAttribute : Attribute
{
public EnumValueAttribute(int id)
{
Id = id;
}
public int Id { get; }
}
Then, this method:
// This method will get your attribute value from your enum value
public object GetEnumAttributeValue<TEnum>(TEnum value)
{
var enumAttribute = (EnumAttribute)typeof(TEnum)
.GetCustomAttributes(typeof(EnumAttribute), false)
.First();
var valueAttribute = (EnumValueAttribute)typeof(TEnum).GetMember(value.ToString())
.First()
.GetCustomAttributes(typeof(EnumValueAttribute), false)
.First();
return Settings.Default[String.Format(enumAttribute.Key, valueAttribute.Id)];
}
I did not check if the type is correct, not even if it finds any attributes. You will have to do it, otherwise you will get an exception if you don't provide the right type.
Now, your enum will look like that:
[Enum("Key{0}")]
public enum MyEnum
{
[EnumValue(0)] First,
[EnumValue(1)] Second
}
Finally, in your project settings, you will have to add as many lines as the number of members in your enum.
You will have to name each line with the same pattern as the parameter given to EnumAttribute. Here, it's "Key{0}", so:
Key0: Your first value
Key1: Your second value
etc...
Like this, you only have to change your settings values (NOT THE KEY) to import your enum and change your attributes to a project to another.
Usage:
/*Wherever you put your method*/.GetEnumAttributeValue(MyEnum.First);
It will return you "Your first value".
Is there any way that I can change enum values at run-time?
e.g I have following type
enum MyType
{
TypeOne, //=5 at runtime
TypeTwo //=3 at runtime
}
I want at runtime set 5 to TypeOne and 3 to TypeTwo.
As others have pointed out, the answer is no.
You could however probably refactor your code to use a class instead:
public sealed class MyType
{
public int TypeOne { get; set; }
public int TypeTwo { get; set; }
}
...
var myType = new MyType { TypeOne = 5, TypeTwo = 3 };
or variations on that theme.
Just refer to MSDN help HERE
An enumeration type (also named an enumeration or an enum) provides an efficient way to define a set of named integral constants that may be assigned to a variable.
Also HERE
In the Robust Programming Section - Just as with any constant, all references to the individual values of an enum are converted to numeric literals at compile time.
So you need to realign your idea of Enum and use it accordingly.
To answer your question - No it is not possible.
Enums are compiled as constant static fields, their values are compiled into you assembly, so no, it's not possible to change them. (Their constant values may even be compiled into places where you reference them.)
Eg take this enum:
enum foo
{
Value = 3
}
Then you can get the field and its information like this:
var field = typeof(foo).GetField("Value", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
Console.WriteLine(field.GetValue(null));
Console.WriteLine(field.Attributes);
I am building an application that makes heavy use of Enums for custom data. Essentially, an object is stored in the database with about 28 separate attributes. Each attribute is a two-character field that's translated from the SQL straight over to an Enum.
Unfortunately, I need to also translate these values into two different human-readable values. One for a legend on a data table, and one for a CSS class to style an image on the web application front-end.
To do this, I've set up two custom attributes and applied them to the Enum where necessary. For example:
Custom Attribute Interface
public interface IAttribute<T>
{
T Value { get; }
}
Example Custom Attribute
public sealed class AbbreviationAttribute: Attribute, IAttribute<string>
{
private readonly string value;
public AbbreviationAttribute(string value)
{
this.value = value;
}
public string Value
{
get { return this.value; }
}
}
Method to Retrieve Custom Attribute from Enum
public static R GetAttributeValue<T, R>(IConvertible #enum)
{
R attributeValue = default(R);
if (#enum != null)
{
FieldInfo fi = #enum.GetType().GetField(#enum.ToString());
if (fi != null)
{
T[] attributes = fi.GetCustomAttributes(typeof(T), false) as T[];
if (attributes != null && attributes.Length > 0)
{
IAttribute<R> attribute = attributes[0] as IAttribute<R>;
if (attribute != null)
{
attributeValue = attribute.Value;
}
}
}
}
return attributeValue;
}
Example Enum Using This Pattern
public enum Download
{
[Abbreviation("check")]
[Description("Certified")]
C = 1,
[Abbreviation("no-formal")]
[Description("No formal certification")]
NF = 2,
[Abbreviation("cert-prob")]
[Description("Certified with potential problems")]
CP = 3
}
Both Abbreviation and Description are custom attributes that implement IAttribute<T>. My actual Enum has 11 possible values, and as I mentioned before it's used in 28 separate properties in my custom object. Using custom attributes seemed the best way to map this information back and forth.
Now for the question, is this the best way to accomplish this? I store the Enum value ("C", "NF", or "CP" in the snippet above) in the database, but I need the values of the Abbreviation and Description in my code. Also, I doubt this will be the final set of custom attributes I'll need.
Before I keep moving forward with this pattern ... is it the right way to do things? I'd rather fix potential problems with this approach now than have to track back and refactor later.
This is the same method I use. The one downside is serialization. The custom attributes values do not serialize.
I like the custom attribute method over the database method because it ties the attribute data right to the enum instead of having to use a lookup table or class, etc.
I'd probably build a hash table and a special type for something like this. You may have already discarded the idea for some reason or another, but here's what I would do not knowing the specifics of your application.
class SpecialType {
// include the fields and all attributes that you need to reference, ToString method for debugging, and any serialization you need
public string foo { get; set; }
public string bar { get; set; }
public ToString() { return "SpecialType with foo '" + foo + "' and bar '" + bar + "'"; }
}
Dictionary<int, SpecialType> myDict = new Dictionary<int, SpecialType> {
{ 1, new SpecialType { foo = "XA1B2", bar = "XC3D4" } },
{ 2, new SpecialType { foo = "ZA1B2", bar = "ZC3D4" } },
{ 3, new SpecialType { foo = "YA1B2", bar = "YC3D4" } },
}
Then I could easily keep ints in my other classes to save memory, find out if a particular value was valid by checking for existence in the Keys of the Dictionary, all that jazz. It would probably be a lot easier to do databinding if you're eventually going to use WPF or read/write to disk, too.
Can you alter the database? I think the best option would be to make a table (or tables) to house the possible values of the enums and foreign key the main objects over to it (instead of using char codes - this makes pulling it in easier and normalizes your DB). Give the table an Abbreviation and Description column, then pull those in and reference them by their key, and cache them if lookups are slow.
One thing that's dangerous about the attributes is that if any of those strings ever have to change, it's a complete redeploy of the app. If you make them database values, you can change them with a simple UPDATE.
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.