"Display" or "Description" attribute for enum values allowing multiple usage - c#

I’d like to use an attribute for parsing strings to enum values (something like in this question), but I’d like to use multiple strings for each enum value, e.g.:
enum Foo
{
[SomeAttribute("A BAR")]
[SomeAttribute("The BAR")]
Bar,
[SomeAttribute("A BUZZ")]
[SomeAttribute("The BUZZ")]
Buzz
}
That means that I cannot use Description nor DisplayName because they have AllowMultiple=false.
I know that it is simple to create my own attribute, but:
Does the .NET framework already contain a suitable attribute that I can use for this?

No it doesn't. Go with creating your own attribute.

Related

Add custom value in HTML.EnumDropDownListFor

So far I have soemthing like this:
public enum MyEnum
{
[Display(Name="FirstElement")]
element1 = 1,
[Display(Name="SecondElement")]
element2 = 2
}
And in the view, if I use:
Html.EnumDropDownListFor(x=>x.EnumProperty)
While having:
public MyEnum EnumProperty {get;set;}
On the view model, the dropdown works as expected(I see FirstElement, and the value attached is element1)
The problem is that I want to have a dash in the value, like this:
public enum MyEnum
{
[Display(Name="FirstElement")]
element1 = 1,
[Display(Name="SecondElement")]
element2 = 2,
[Display(Name="Third")]
element-dashed = 3
}
It won't work, as element-dashed is not a valid entry.
This won't work either:
[Value("element-dashed")]
[Display(Name="Third")]
elementDashed = 3
The answer to your question is simple. But it warrants an explanation, to explain why this is the answer.
There is no functional reason to want a dash in an enum.
Let's say I have a working application, which contains your enum:
public enum MyEnum
{
[Display(Name="FirstElement")]
element1 = 1,
[Display(Name="SecondElement")]
element2 = 2
}
Suppose I change the enumerator names:
public enum MyEnum
{
[Display(Name="FirstElement")]
BatmanIsBruceWayne = 1,
[Display(Name="SecondElement")]
SupermanIsClarkKent = 2
}
I am assuming that every usage of the enum in code is also adjusted accordingly. If you use the Rename method in VS (F2), this is automatically the case.
Nothing has changed about how the application works. Functionally speaking, the name of an enum does not matter.
public enum CardSuit { Hearts, Spades, Diamonds, Clubs }
public enum CardSuit { TheRedOne, TheBlackOne, TheOtherRedOne, TheOtherBlackOne }
Of course, the first one is much more readable for developers than the second one. However, a compiler does not care about the meaning of the enum names. It only cares that the appropriate value is used consistently.
This is the same principle as variable names. While we should of course take care to make variable names readable by developers; there is no technical reason (i.e. to the functionality of the application) why we should use int myDescriptiveInteger over int x.
We only choose a descriptive name for the purposes of readability for humans who maintain the code.
There is a single exception to this: If you are calling .ToString() on an enum, in order to have a string representation of the field you are using.
Note: While this is technically possible and has been done before by others; I do wonder if that's really what you should be doing here. You're already assigning explicit int values to the enum values. Those int values are much easier for saving/loading an enum instead of trying to work with its string equivalent. While both are technically possible, using the int is far better due to a smaller footprint and a lack of the naming problem that you are wrongly trying to fix here.
It is not clear from your question whether this is the case for you. Maybe it's done somewhere in your application, maybe it's not. I can't know that without you telling me.
If it is not done, then your problem is functionally irrelevant. The name does not matter except for readability purposes, and I'm quite convinced that your developers won't suddenly stop understanding the intent of the code because there's a dash missing.
If it is done, then you actually have already provided the answer to your problem. Instead of calling .ToString() on the enum; instead you must use the value of [Display(Name="FirstElement")] as the correct "string name" for your enum.
public static string GetName(this myEnum enumValue)
{
return enumValue.GetType()
.GetMember(enumValue.ToString())
.GetCustomAttribute<DisplayAttribute>()
.GetName();
}
And then you can change the following code:
myEnumValue.ToString();
To the following code, which correctly uses the attribute's value instead of the enum field name:
myEnumValue.GetName();
In both cases, the core answer remains the same: You do not need to worry about the specific name of your enum fields because it is functionally irrelevant.
Edit If you want your enum value to be displayed differently in the combobox than you want it to be when you save the value, then use a second attribute to differentiate between the two. But the principle still stands: do not base yourself off of the field names than an enum uses.
And I would like to stress again that you should be using the integer values of your enum. I see no reason why you would explicitly assign int values to your enum and then refuse to use them.

How to Intercept EF operations?

I am currently using Entity Framework for a project and one of my classes have an Enum representing some values.
So far EF is saving the Enums as numbers in the database, but I wanted to save them as their actual string names. For example, the Enum NY is saved as 1, instead of "NY".
I have already seen some ways to make this work, like having a string property with a hidden Enum private field, but I wanted to know if there is a way I can just Intercept EF when it's doing the CRUD operations and then I can change the Enum to a String in the Data Context class.
No, you cannot do that directly: when you map your class with an Enum property, that property is mapped to a database int column, and you cannot change that in any way. I mean that, as you cannot change the model, there is no way to intercept and convert the Enum property value into an string, because the model stubbornly wants an int.
That said, there are several ways to make it work:
having an string property for the key and a [NotMapped] Enum property that updates that key. SEE THE NOTE: But the key must be public, and thus accesible through the application code.
using a class that have only the enum property and is used in your application domain, and a different class which is used for your EF model, and map the values, for example using ValueInjecter or Automapper
I usually take the first path and use an attribute that allows me to define the string key for each Enum value, so you can reuse this pattern in all the cases in which need to do this.
NOTE: this part of the answer was wrong: you can map any property regardles of the modifier (public, protected, private, internal...). EF conventions only include the public properties, and there are no data annotations that can overcome this limitation. But you can use it with the Fluent API. However, as the property is private,you cannot access it directly using the Fluent API. There are several solutions to do it described here: Code First Data Annotations on non-public properties
If you follow this path, you can have a class like this:
public class MyEntity
{
// ...
[NotMapped]
public EnumType Value
{
get { /* return KeyForEnum converted to EnumType value */ }
set { /* set KeyForEnum value from the received EnumType value*/}
}
// Use some mechanism to map this private property
private string KeyForEnum { get; set; }
// ...
}
As you can see, if you use a class like this, in the app the entity will have a property of EnumType type, but in the database it will be an string.
One of the tricks to be able to map it through Fluent API is this:
1) Add an static property that returns an expression able to select the property from an object of this class, i.e.
public static readonly Expression<Func<MyEntity,string>> KeyForEnumExpression
= me => me.KeyForEnum;
2) Use it in the fluent API to get the property mapped, like so:
modelBuilder
.Entity()
.Property(MyEntity.KeyForEnumExpression)
LAST NOTE: This will modify the POCO class by adding the static readonly property. You can use Reflection instead to build an expression to access the private property, like you can see here: EF 4.1 Code First, ¿map private members?. It's in Spanish, but you can look directly at the code

XmlSerializer and acceptable values

Hello I am working on an project in which I should serialize and deserialize my objects to Xml and back to objects. I use the XmlSerializer class in order to achieve this. So my problem is that I can't figure out how to prevent the serialization if the attribute value of an element is invalid. For example I have an element with name person which contain 1 attribute (name)
I would like to prevent the user to put other names than (Alex, Nick,..) in this attribute I need something like xsd restriction (pattern) in this case but for my model. How can I solve this problem?
If you just want conditional serialisation, you can do this with the ShouldSerialize* pattern. So if you have a property Name (for example), you can add:
public bool ShouldSerializeName() {
/* validate; return true to serialize, false to skip */
}
The method needs to be public for XmlSerializer, although the same pattern works in other places (System.ComponentModel, for example) even if no-public.
I'm not sure weather it is a good idea to ignore some data in certain circumstances, but if you really wanna do this, take a look at the IXmlSerializable Interface. I think implementing this interface manually will be the only way to fulfill your requirements.

Mapping database int to enum flags in model

My application has a model which currently uses an integer in the SQL database to store the value of a [Flags]Enum. The Enum looks something like this:
namespace MyProject.Models
{
[Flags]
public enum MyEnum : int
{
FirstThing = 1,
SomethingElse = 2,
YetAnotherOne = 4
}
}
So if a particular row had this field set to 3, it means flags FirstThing and SomethingElse are both set. Right now I'm using a helper class to convert and check MyEnum values to/from/against the integer, which does work, but I think there's gotta be a way to map the SQL INT field directly to the enum.
Basically the end goal is to have a list of checkboxes, one for each possible flag that will eventually be saved in the database as an INT.
Is this a good idea? If so, how do I go about this? If not, should I just suck it up and write out all that code myself (instead of using some nifty tricks)?
You will need that helper class, neither OR mapper fully supports mapping int to enum. There are ways around it, but that's more of a replication of the target behaviour with gapping holes in it than anything near the wanted effect.

How can I internationalize strings representing C# enum values?

I've seen many questions and answers about mapping strings to enums and vice-versa, but how can I map a series of localized strings to enums?
Should I just create an extension method like this that returns the proper string from a resource file? Is there a way to localize attributes (like "Description") that are used in solutions like this?
Which is the preferred solution - extension method or attributes. It seems to me that this isn't the intended purpose of attributes. In fact, now that I think about it, if I were to use an extension method an attribute seems like something I'd use to specify a key in a resource file for the localized string I want to use in place of the enum value.
EDIT - example:
Given the following enum,
public enum TransactionTypes {
Cheque = 1,
BankTransfer = 2,
CreditCard = 3
}
I would like a way to map each type to a localized string. I started off with an extension method for the enum that uses a switch statement and strongly typed references to the resource file.
However, an extension method for every enum doesn't seem like a great solution. I've started following this to create a custom attribute for each enumerated value. The attribute has a base name and key for the resource file containing localized strings. In the above enum, for example, I have this:
...
[EnumResourceAttribute("FinancialTransaction", "Cheque")]
Cheque = 1,
...
Where "FinanacialTransaction" is the resx file and "Cheque" is the string key. I'm trying to create a utility method to which I could pass any value from any enumeration and have it return the localized string representation of that value, assuming the custom attribute is specified. I just can't figure out how to dynamically access a resource file and a key within it.
I would definitely suggest using a resource file, probably with a method (extension or otherwise) to make it simple to get hold of the relevant resource. As the number of languages you support grows, you don't really want the code to be full of text, distracting you from the values themselves.
Likewise translation companies are likely to be geared up to handle resx files - they're not going to want to mess around in your source code, and you shouldn't let them do so anyway :)
Just use resources which are keyed on the name of the enum and the value within it. Straightforward, scales to multiple enums and multiple languages, doesn't clutter up your source code, works well with translation tools, and is basically going along with the flow of i18n within .NET.
EDIT: For mapping the enum values to the resource names, I'd just do something like:
public static string ToResourceName<T>(this T value) where T : struct
{
return typeof(T).Name + "." + value;
}
Then you could do:
string resource = MyEnum.SomeValue.ToResourceName();
Obviously that's performing string concatenation every time - you could cache that if you wanted to, but I wouldn't bother unless you had some indication that it was actually a problem.
That doesn't stop you using the extension method for non-enums, of course. If you want to do that, you need something like Unconstrained Melody.
I continued with the custom attributes and created this utility method:
public static string getEnumResourceString(Enum value)
{
System.Reflection.FieldInfo fi = value.GetType().GetField(value.ToString());
EnumResourceAttribute attr = (EnumResourceAttribute)System.Attribute.GetCustomAttribute(fi, typeof(EnumResourceAttribute));
return (string)HttpContext.GetGlobalResourceObject(attr.BaseName, attr.Key);
}

Categories