I know the following is not possible because the Enumeration's type has to be an int
enum GroupTypes
{
TheGroup = "OEM",
TheOtherGroup = "CMB"
}
From my database I get a field with incomprehensive codes (the OEM and CMBs). I would want to make this field into an enum or something else understandable. Because if the target is readability, the solution should be terse.
What other options do I have?
I like to use properties in a class instead of methods, since they look more enum-like.
Here's an example for a Logger:
public class LogCategory
{
private LogCategory(string value) { Value = value; }
public string Value { get; private set; }
public static LogCategory Trace { get { return new LogCategory("Trace"); } }
public static LogCategory Debug { get { return new LogCategory("Debug"); } }
public static LogCategory Info { get { return new LogCategory("Info"); } }
public static LogCategory Warning { get { return new LogCategory("Warning"); } }
public static LogCategory Error { get { return new LogCategory("Error"); } }
public override string ToString()
{
return Value;
}
}
Pass in type-safe string values as a parameter:
public static void Write(string message, LogCategory logCategory)
{
var log = new LogEntry { Message = message };
Logger.Write(log, logCategory.Value);
}
Usage:
Logger.Write("This is almost like an enum.", LogCategory.Info);
You could also use the extension model:
public enum MyEnum
{
[Description("String 1")]
V1= 1,
[Description("String 2")]
V2= 2
}
Your Extension Class
public static class MyEnumExtensions
{
public static string ToDescriptionString(this MyEnum val)
{
DescriptionAttribute[] attributes = (DescriptionAttribute[])val
.GetType()
.GetField(val.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : string.Empty;
}
}
usage:
MyEnum myLocal = MyEnum.V1;
print(myLocal.ToDescriptionString());
How about using a static class with constants?
static class GroupTypes
{
public const string TheGroup = "OEM";
public const string TheOtherGroup = "CMB";
}
void DoSomething(string groupType)
{
if(groupType == GroupTypes.TheGroup)
{
// Be nice
}
else if (groupType == GroupTypes.TheOtherGroup)
{
// Continue to be nice
}
else
{
// unexpected, throw exception?
}
}
Try adding constants to a static class. You don't end up with a Type, but you will have readable, organised constants:
public static class GroupTypes {
public const string TheGroup = "OEM";
public const string TheOtherGroup = "CMB";
}
I used a structure as alluded to in a previous answer, but did away with any complexity. To me, this was most like creating an enumeration of strings. It is used in the same manner that an enumeration is used.
struct ViewTypes
{
public const string View1 = "Whatever string you like";
public const string View2 = "another string";
}
Example use:
switch( some_string_variable )
{
case ViewTypes.View1: /* do something */ break;
case ViewTypes.View2: /* do something else */ break;
}
You can do it very easily actually. Use the following code.
enum GroupTypes
{
OEM,
CMB
};
Then when you want to get the string value of each enum element just use the following line of code.
String oemString = Enum.GetName(typeof(GroupTypes), GroupTypes.OEM);
I've used this method successfully in the past, and I've also used a constants class to hold string constants, both work out pretty well, but I tend to prefer this.
You can add attributes to the items in the enumeration and then use reflection to get the values from the attributes.
You would have to use the "field" specifier to apply the attributes, like so:
enum GroupTypes
{
[field:Description("OEM")]
TheGroup,
[field:Description("CMB")]
TheOtherGroup
}
You would then reflect on the static fields of the type of the enum (in this case GroupTypes) and get the DescriptionAttribute for the value you were looking for using reflection:
public static DescriptionAttribute GetEnumDescriptionAttribute<T>(
this T value) where T : struct
{
// The type of the enum, it will be reused.
Type type = typeof(T);
// If T is not an enum, get out.
if (!type.IsEnum)
throw new InvalidOperationException(
"The type parameter T must be an enum type.");
// If the value isn't defined throw an exception.
if (!Enum.IsDefined(type, value))
throw new InvalidEnumArgumentException(
"value", Convert.ToInt32(value), type);
// Get the static field for the value.
FieldInfo fi = type.GetField(value.ToString(),
BindingFlags.Static | BindingFlags.Public);
// Get the description attribute, if there is one.
return fi.GetCustomAttributes(typeof(DescriptionAttribute), true).
Cast<DescriptionAttribute>().SingleOrDefault();
}
I opted to return the DescriptionAttribute itself above, in the event that you want to be able to determine whether or not the attribute is even applied.
Use a class.
Edit: Better example
class StarshipType
{
private string _Name;
private static List<StarshipType> _StarshipTypes = new List<StarshipType>();
public static readonly StarshipType Ultralight = new StarshipType("Ultralight");
public static readonly StarshipType Light = new StarshipType("Light");
public static readonly StarshipType Mediumweight = new StarshipType("Mediumweight");
public static readonly StarshipType Heavy = new StarshipType("Heavy");
public static readonly StarshipType Superheavy = new StarshipType("Superheavy");
public string Name
{
get { return _Name; }
private set { _Name = value; }
}
public static IList<StarshipType> StarshipTypes
{
get { return _StarshipTypes; }
}
private StarshipType(string name, int systemRatio)
{
Name = name;
_StarshipTypes.Add(this);
}
public static StarshipType Parse(string toParse)
{
foreach (StarshipType s in StarshipTypes)
{
if (toParse == s.Name)
return s;
}
throw new FormatException("Could not parse string.");
}
}
Create a second enum, for your DB containing the following:
enum DBGroupTypes
{
OEM = 0,
CMB = 1
}
Now, you can use Enum.Parse to retrieve the correct DBGroupTypes value from the strings "OEM" and "CMB". You can then convert those to int and retrieve the correct values from the right enumeration you want to use further in your model.
Another way to deal with the problem, is to have a enum and a array of strings that will map the enum values with the list of strings:
public enum GroupTypes
{
TheGroup = 0,
TheOtherGroup
}
string[] GroupTypesStr = {
"OEM",
"CMB"
};
you may use it something like this:
Log.Write(GroupTypesStr[(int)GroupTypes.TheOtherGroup]);
It will prompt CMB
PROS:
Easy and clean code.
High Performance (specially in comparison with those approaches that
uses classes)
CONS:
Prone to mess up the list when editing it, but it will be okay for a
short list.
Here is the extension method that I used to get the enum value as string. First here is the enum.
public enum DatabaseEnvironment
{
[Description("AzamSharpBlogDevDatabase")]
Development = 1,
[Description("AzamSharpBlogQADatabase")]
QualityAssurance = 2,
[Description("AzamSharpBlogTestDatabase")]
Test = 3
}
The Description attribute came from System.ComponentModel.
And here is my extension method:
public static string GetValueAsString(this DatabaseEnvironment environment)
{
// get the field
var field = environment.GetType().GetField(environment.ToString());
var customAttributes = field.GetCustomAttributes(typeof (DescriptionAttribute), false);
if(customAttributes.Length > 0)
{
return (customAttributes[0] as DescriptionAttribute).Description;
}
else
{
return environment.ToString();
}
}
Now, you can access the enum as string value using the following code:
[TestFixture]
public class when_getting_value_of_enum
{
[Test]
public void should_get_the_value_as_string()
{
Assert.AreEqual("AzamSharpBlogTestDatabase",DatabaseEnvironment.Test.GetValueAsString());
}
}
New in .Net Core 3.0/C# 8.0 (if your work environment allows you to upgrade your project) is a short-hand switch statement that looks somewhat enum-ish. At the end of the day it's the same old boring switch statement we've been using for years.
Only real difference here is that the switch statement got a new suit.
public static RGBColor FromRainbow(Rainbow colorBand) =>
colorBand switch
{
Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00),
Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF),
Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
_ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
};
You'll notice that the code above which I copied from here, is actually using an enum as a param.
It's not exactly what you want (and trust me, I've wanted something of similar to what the OP is requesting for a long time), but I actually feel like this is somewhat of an olive branch from MS. JMO.
Hope it helps someone!
Why not just use the same enum, but just call .ToString()?
using System;
public class EnumSample
{
enum Holidays
{
Christmas = 1,
Easter = 2
};
public static void Main()
{
Enum myHolidays = Holidays.Christmas;
Console.WriteLine("The value of this instance is '{0}'", myHolidays.ToString());
}
}
Taken from #EvenMien and added in some of the comments. (Also for my own use case)
public struct AgentAction
{
private AgentAction(string value) { Value = value; }
public string Value { get; private set; }
public override string ToString() { return this.Value; }
public static AgentAction Login = new AgentAction("Login");
public static AgentAction Logout = new AgentAction("Logout");
public static implicit operator string(AgentAction action) { return action.ToString(); }
}
Have you considered a lookup table using a Dictionary?
enum GroupTypes
{
TheGroup,
TheOtherGroup
}
Dictionary<string, GroupTypes> GroupTypeLookup = new Dictionary<string, GroupTypes>();
// initialize lookup table:
GroupTypeLookup.Add("OEM", TheGroup);
GroupTypeLookup.Add("CMB", TheOtherGroup);
You can then use GroupTypeLookup.TryGetValue() to look up a string when you read it.
I would just create a dictionary and use the code as the key.
Edit: To address the comment about doing a reverse lookup (finding the key), this would not be terribly efficient. If this is necessary, I would write a new class to handle it.
public class DataType
{
private readonly string value;
private static readonly Dictionary<string, DataType> predefinedValues;
public static readonly DataType Json = new DataType("json");
public static readonly DataType Xml = new DataType("xml");
public static readonly DataType Text = new DataType("text");
public static readonly DataType Html = new DataType("html");
public static readonly DataType Binary = new DataType("binary");
static DataType()
{
predefinedValues = new Dictionary<string, DataType>();
predefinedValues.Add(Json.Value, Json);
predefinedValues.Add(Xml.Value, Xml);
predefinedValues.Add(Text.Value, Text);
predefinedValues.Add(Html.Value, Html);
predefinedValues.Add(Binary.Value, Binary);
}
private DataType(string value)
{
this.value = value;
}
public static DataType Parse(string value)
{
var exception = new FormatException($"Invalid value for type {nameof(DataType)}");
if (string.IsNullOrEmpty(value))
throw exception;
string key = value.ToLower();
if (!predefinedValues.ContainsKey(key))
throw exception;
return predefinedValues[key];
}
public string Value
{
get { return value; }
}
}
Here is my take on this, using C# 9.0 syntax to keep it clean. I define a base class for the enums:
public class StringEnum
{
protected StringEnum(string value) { Value = value; }
public string Value { get; }
public override string ToString() => Value;
}
Creating new enum style types is then easy and compact:
public class GroupTypes : StringEnum
{
private GroupTypes(string value) : base(value) {}
public static readonly GroupTypes TheGroup = new("OEM");
public static readonly GroupTypes TheOtherGroup = new("CMB");
}
Use it like this:
void Example(GroupTypes groupType)
{
Console.WriteLine(groupType); // Will print "OEM" or "CMB"
if (groupType == GroupTypes.TheGroup) { ... }
}
You can also add more functionality to StringEnum, which will then be available for all your subclasses (e. g., implementing IComparable and overriding Equals and GetHashCode)
My first question - Do you have access to the Database itself? This should be normalized in the database, ideally, otherwise, any solution is going to be prone to error. In my experience, data fields full of "OEM" and "CMB" tend to wind up having things like "oem " and other 'crap data' mixed in over time.... If you can normalize it, you could use the key in the table containing the elements as your Enum, and you're done, with a much cleaner structure.
If that's not available, I'd make your Enum, and make a class to parse your string into the Enum for you. This would at least give you some flexibility in handling non-standard entries and much more flexibility for trapping or handling errors than doing any of the workarounds using Enum.Parse/Reflection/etc. A dictionary would work, but could break down if you ever have case issues, etc.
I'd recommend writing a class so you can do:
// I renamed this to GroupType, since it sounds like each element has a single type...
GroupType theType = GroupTypeParser.GetGroupType(theDBString);
This preserves most of your readability without having to change the DB.
C# doesn't support enumerated strings, but for most situations you can use a List or Dictionary to get the desired effect.
E.g. To print pass/fail results:
List<string> PassFail = new List<string> { "FAIL", "PASS" };
bool result = true;
Console.WriteLine("Test1: " + PassFail[result.GetHashCode()]);
This is a way to use it as a strongly typed parameter or as a string :
public class ClassLikeEnum
{
public string Value
{
get;
private set;
}
ClassLikeEnum(string value)
{
Value = value;
}
public static implicit operator string(ClassLikeEnum c)
{
return c.Value;
}
public static readonly ClassLikeEnum C1 = new ClassLikeEnum("RandomString1");
public static readonly ClassLikeEnum C2 = new ClassLikeEnum("RandomString2");
}
A small tweak to Glennular Extension method, so you could use the extension on other things than just ENUM's;
using System;
using System.ComponentModel;
namespace Extensions {
public static class T_Extensions {
/// <summary>
/// Gets the Description Attribute Value
/// </summary>
/// <typeparam name="T">Entity Type</typeparam>
/// <param name="val">Variable</param>
/// <returns>The value of the Description Attribute or an Empty String</returns>
public static string Description<T>(this T t) {
DescriptionAttribute[] attributes = (DescriptionAttribute[])t.GetType().GetField(t.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : string.Empty;
}
}
}
Or Using Linq
using System;
using System.ComponentModel;
using System.Linq;
namespace Extensions {
public static class T_Extensions {
public static string Description<T>(this T t) =>
((DescriptionAttribute[])t
?.GetType()
?.GetField(t?.ToString())
?.GetCustomAttributes(typeof(DescriptionAttribute), false))
?.Select(a => a?.Description)
?.FirstOrDefault()
?? string.Empty;
}
}
Following the answer of #Even Mien I have tried to go a bit further and make it Generic, I seem to be almost there but one case still resist and I probably can simplify my code a bit.
I post it here if anyone see how I could improve and especially make it works as I can't assign it from a string
So Far I have the following results:
Console.WriteLine(TestEnum.Test1);//displays "TEST1"
bool test = "TEST1" == TestEnum.Test1; //true
var test2 = TestEnum.Test1; //is TestEnum and has value
string test3 = TestEnum.Test1; //test3 = "TEST1"
var test4 = TestEnum.Test1 == TestEnum.Test2; //false
EnumType<TestEnum> test5 = "TEST1"; //works fine
//TestEnum test5 = "string"; DOESN'T compile .... :(:(
Where the magics happens :
public abstract class EnumType<T> where T : EnumType<T>
{
public string Value { get; set; }
protected EnumType(string value)
{
Value = value;
}
public static implicit operator EnumType<T>(string s)
{
if (All.Any(dt => dt.Value == s))
{
Type t = typeof(T);
ConstructorInfo ci = t.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,null, new Type[] { typeof(string) }, null);
return (T)ci.Invoke(new object[] {s});
}
else
{
return null;
}
}
public static implicit operator string(EnumType<T> dt)
{
return dt?.Value;
}
public static bool operator ==(EnumType<T> ct1, EnumType<T> ct2)
{
return (string)ct1 == (string)ct2;
}
public static bool operator !=(EnumType<T> ct1, EnumType<T> ct2)
{
return !(ct1 == ct2);
}
public override bool Equals(object obj)
{
try
{
return (string)obj == Value;
}
catch
{
return false;
}
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public static IEnumerable<T> All
=> typeof(T).GetProperties()
.Where(p => p.PropertyType == typeof(T))
.Select(x => (T)x.GetValue(null, null));
}
I only then have to declare this for my enums:
public class TestEnum : EnumType<TestEnum>
{
private TestEnum(string value) : base(value)
{}
public static TestEnum Test1 { get { return new TestEnum("TEST1"); } }
public static TestEnum Test2 { get { return new TestEnum("TEST2"); } }
}
I would make it into a class an avoid an enum altogether. And then with the usage of a typehandler you could create the object when you grab it from the db.
IE:
public class Group
{
public string Value{ get; set; }
public Group( string value ){ Value = value; }
public static Group TheGroup() { return new Group("OEM"); }
public static Group OtherGroup() { return new Group("CMB"); }
}
If I understand correctly, you need a conversion from string to enum:
enum GroupTypes {
Unknown = 0,
OEM = 1,
CMB = 2
}
static GroupTypes StrToEnum(string str){
GroupTypes g = GroupTypes.Unknown;
try {
object o = Enum.Parse(typeof(GroupTypes), str, true);
g = (GroupTypes)(o ?? 0);
} catch {
}
return g;
}
// then use it like this
GroupTypes g1 = StrToEnum("OEM");
GroupTypes g2 = StrToEnum("bad value");
You can make it more fancy with generics for the enum type if you wish.
In VS 2015, you can use nameof
public class LogCategory
{
public static string Trace;
public static string Debug;
public static string Info;
public static string Warning;
public static string Error;
}
Usage:
Logger.Write("This is almost like an enum.", nameof(LogCategory.Info));
I wanted to avoid using string literals completely, and also I didn't need to have space in item descriptions. More importantly, I wanted to have a mechanism to check if the provided string is a valid item, so I came up with this solution:
public class Seasons
{
public static string Spring { get; }
public static string Summer { get; }
public static string Fall { get; }
public static string Winter { get; }
public static bool IsValid(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
return false;
}
try
{
return typeof(Seasons).GetProperty(propertyName) != null;
}
catch
{
return false;
}
}
}
And here is how it works:
void Main()
{
string s = nameof(Seasons.Fall);
Console.WriteLine($"Fall is valid: {Seasons.IsValid(s)}"); // true
s = "WrongSeason";
Console.WriteLine($"WrongSeason is valid: {Seasons.IsValid(s)}"); // false
}
I tried to refactor IsValid() into a base class and use reflection to read the type (MethodBase.GetCurrentMethod().DeclaringType), but since I wanted to have it static, it returns the base class type, not the inherited type. Your remedy to this will be very welcomed! Here is what I was trying to achieve:
public class Seasons : ConstantStringsBase
{
// ... same
}
public class ConstantStringsBase
{
public static bool IsValid(string propertyName)
{
return MethodBase.GetCurrentMethod().DeclaringType.GetProperty(propertyName) != null;
}
}
Based in other opinions, this is what I come up with. This approach avoids having to type .Value where you want to get the constant value.
I have a base class for all string enums like this:
using System;
using Newtonsoft.Json;
[JsonConverter(typeof(ConstantConverter))]
public class StringEnum: IConvertible
{
public string Value { get; set; }
protected StringEnum(string value)
{
Value = value;
}
public static implicit operator string(StringEnum c)
{
return c.Value;
}
public string ToString(IFormatProvider provider)
{
return Value;
}
public TypeCode GetTypeCode()
{
throw new NotImplementedException();
}
public bool ToBoolean(IFormatProvider provider)
{
throw new NotImplementedException();
}
//The same for all the rest of IConvertible methods
}
The JsonConverter is like this:
using System;
using Newtonsoft.Json;
class ConstantConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
serializer.Serialize(writer, null);
}
else
{
serializer.Serialize(writer, value.ToString());
}
}
}
And an actual string enum will be something like this:
public sealed class Colors : StringEnum
{
public static Colors Red { get { return new Catalog("Red"); } }
public static Colors Yellow { get { return new Catalog("Yellow"); } }
public static Colors White { get { return new Catalog("White"); } }
private Colors(string value) : base(value) { }
}
And with this, you can just use Color.Red to even serialize to json without using the Value property
I even implemented a few enums as suggested by #Even (via class X and public static X members), just to find out later that these days, starting .Net 4.5, there's the right ToString() method.
Now I'm reimplementing everything back to enums.
You can use two enums. One for the database and the other for readability.
You just need to make sure they stay in sync, which seems like a small cost.
You don't have to set the values, just set the positions the same, but setting the values makes it very clear the two enums are related and prevents errors from rearranging the enum members. And a comment lets the maintenance crew know these are related and must be kept in sync.
// keep in sync with GroupTypes
public enum GroupTypeCodes
{
OEM,
CMB
}
// keep in sync with GroupTypesCodes
public enum GroupTypes
{
TheGroup = GroupTypeCodes.OEM,
TheOtherGroup = GroupTypeCodes.CMB
}
To use it you just convert to the code first:
GroupTypes myGroupType = GroupTypes.TheGroup;
string valueToSaveIntoDatabase = ((GroupTypeCodes)myGroupType).ToString();
Then if you want to make it even more convenient you can add an extension function that only works for this type of enum:
public static string ToString(this GroupTypes source)
{
return ((GroupTypeCodes)source).ToString();
}
and you can then just do:
GroupTypes myGroupType = GroupTypes.TheGroup;
string valueToSaveIntoDatabase = myGroupType.ToString();
Related
The main purpose is to show intellisense when setting the property. It would be great if I could do it via an attribute like the image below.
The property should remain a string(not enum or struct) so that Mongo's BsonSerializer can serialize it properly. Here is an example of what it might look like:
To help other developers on the team know possible (but not exlusive) values they can use for the Type field Code Completion should display values that can be used as shown below:
(Edited) I was able to solve this by creating my own type
public class SkinType:StringType<SkinType>
{
public SkinType(string value)
{
Value = value;
}
public SkinType()
{
}
public static implicit operator string(SkinType d)
{
return d.Value;
}
public static implicit operator SkinType(string d)
{
return new SkinType(d);
}
public const string StringValue = nameof(StringValue);
public const string Color = nameof(Color);
}
Now I get intellisense for my Type property and Mongo knows how to serialize it.
Here is how I use it:
public class Skin : ServiceMongoIdentity
{
//removed some properties for brevity.
[BsonIgnoreIfDefault]
[BsonDefaultValue(SkinType.StringValue)]
public SkinType Type { get; set; } = SkinType.StringValue;
}
Here is how the StringType base class is defined. I had to make Value public because Generics cannot have constructors with parameters
public abstract class StringType<T> where T :StringType<T>,new()
{
[ReadOnly(true)]
public string Value;
public T FromString(string d)
{
return new T
{
Value = d
};
}
public override bool Equals(object obj)
{
return obj?.ToString() == Value;
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public override string ToString()
{
return Value;
}
}
I used this accepted answer to create an Enum of Guids.
public enum AccessRoles
{
[EnumGuid("2ED3164-BB48-499B-86C4-A2B1114BF1")]
SysAdmin =1,
[EnumGuid("A5690E7-1111-4AFB-B44D-1DF3AD66D435")]
Admin = 2,
[EnumGuid("30558C7-66D9-4189-9BD9-2B87D11190")]
OrgAdmin = 3,
}
class EnumGuid : Attribute
{
public Guid Guid;
public EnumGuid(string guid)
{
Guid = new Guid(guid);
}
}
I try check if a Guid is part of an enum, it throws an exception System.InvalidOperationException even though userId = 2ED3164-BB48-499B-86C4-A2B1114BF1 is a valid guid.
if(Enum.IsDefined(typeof(AccessRoles), userId))
{
}
I tried converting it to string and checking, but that time it does not throw an error but does not go inside the if loop.
if(Enum.IsDefined(typeof(AccessRoles), userId.ToString().ToUpper()))
{
}
So how do I fix it? Or is there a better way? I want to avoid the multiple if statements or a case statement and so what to use it as enums so they are reusable.
I would replace your enum with an immutable struct, and add a static class to hold all possible roles in the application:
public struct AccessRole
{
public AccessRole(Guid guid, int number, string name) : this()
{
Uuid = guid;
Number = number;
Name = name;
}
public Guid Uuid {get;}
public int Number {get;}
public string Name {get;}
}
Then you can add a static class for AccessRoles:
public static class AccessRoles
{
private static List<AccessRole> _roles;
static AccessRoles()
{
_roles = new List<AccessRole>();
// Here I populate it hard coded for the sample,
// but it should be populated from your database or config file
_roles.Add(new AccessRole(new Guid("2ED3164-BB48-499B-86C4-A2B1114BF1"), 1, "SysAdmin"));
_roles.Add(new AccessRole(new Guid("A5690E7-1111-4AFB-B44D-1DF3AD66D435"), 2, "Admin"));
_roles.Add(new AccessRole(new Guid("30558C7-66D9-4189-9BD9-2B87D11190"), 3, "OrgAdmin"));
}
public static AccessRole GetRole(Guid uuid)
{
return _roles.Find(r => r.Uuid == uuid);
}
public static AccessRole GetRole(int number)
{
return _roles.Find(r => r.Number == number);
}
public static AccessRole GetRole(string name)
{
return _roles.Find(r => r.Name == name);
}
}
Now all you have to do is change the way the _roles list is populated in the static constructor to either a database of a configuration file, and you're good to go.
Note that the AccessRoles provides static methods to get a search for a role by either property. It can be replaced with a single method that will get a predicate, but I think that this way it's more readable.
I would suggest a complete different approach, when working with fixed user roles.
Using an Enumeration you can achieve same and much more:
public abstract class UserRoleType : Enumeration<UserRoleType>
{
protected UserRoleType(int value, string displayName)
: base(value, displayName)
{}
public static readonly UserRoleType Unknown = new UnknownRoleType();
public static readonly UserRoleType Administrator = new AdministratorRoleType();
public static readonly UserRoleType System = new SystemRoleType();
public static readonly UserRoleType Moderator = new ModeratorRoleType();
public virtual bool CanCreateUser => false;
public virtual bool CanBlockUser => false;
public virtual bool CanResetUserPassword => false;
}
public sealed class UnknownRoleType : UserRoleType
{
public UnknownRoleType()
: base(0, "Unknown")
{ }
}
public sealed class AdministratorRoleType : UserRoleType
{
public AdministratorRoleType()
: base(10, "Administrator")
{}
public override bool CanCreateUser => true;
public override bool CanBlockUser => true;
public override bool CanResetUserPassword => true;
}
public sealed class SystemRoleType : UserRoleType
{
public SystemRoleType()
: base(20, "System")
{ }
public override bool CanBlockUser => true;
public override bool CanResetUserPassword => true;
}
public sealed class ModeratorRoleType : UserRoleType
{
public ModeratorRoleType()
: base(40, "Moderator")
{ }
public override bool CanBlockUser => true;
}
By setting abstract/virtual properties on the abstract UserRoleType, you system only have operate on the abstract class.
When your user context is being initialized (on login), you simply find the user role by
var roleTypeValueFromDatabase = 10;
var roleType = UserRoleType.FromValueOrDefault(roleTypeValueFromDatabase, UserRoleType.Unknown);
if (roleType.CanCreateUser)
{
// create user..
}
// Find roles with specific rights
var rolesThatCanResetPassword = UserRoleType.GetAll().Where(urt => urt.CanResetUserPassword);
About the Enumeration class, there are several implementation of them on github/nuget.
Mine is for .Net core v2 - https://github.com/microknights/Collections
with Nuget: Install-Package MicroKnights.Collections
Headspring - https://github.com/HeadspringLabs/Enumeration
source files only.
public enum AccessRoles
{
SysAdmin = 1,
Admin = 2,
OrgAdmin = 3
}
public class Attributes
{
public static Dictionary<int, Guid> Attribute = new Dictionary<int, Guid>()
{
{(int)AccessRoles.SysAdmin, Guid.Parse("6D18698C-04EC-4E50-84DB-BE513D5875AC")},
{(int)AccessRoles.Admin, Guid.Parse("32E86718-7034-4640-9076-A60B9B6CA51A")},
{(int)AccessRoles.OrgAdmin, Guid.Parse("2CA39E37-8AEA-463F-AE14-E9D92AC5FB5E")}
};
}
Console.WriteLine(Attributes.Attribute[(int)AccessRoles.SysAdmin]);
Console.WriteLine(Attributes.Attribute[(int)AccessRoles.Admin]);
Console.WriteLine(Attributes.Attribute[(int)AccessRoles.OrgAdmin]);
Maybe passing the Guid values with System.ComponentModel.AmbientValueAttribute like so :
using System.ComponentModel;
public enum AccessRoles
{
[AmbientValue(typeof(Guid), "749e73c0-ba25-4f69-9f81-ec21d9942e52")]
SysAdmin = 1,
[AmbientValue(typeof(Guid), "39cc7e3d-db5f-4619-a577-e24cb89de5a7")]
Admin = 2,
[AmbientValue(typeof(Guid), "93902f8d-46d3-4b43-b684-b0ee66bbf7de")]
OrgAdmin = 3,
}
With an extension to obtain the AmbientValue:
using System.Reflection;
public static class EnumExtensions
{
public static object GetAmbientValue(this Enum enumVal)
{
Type type = enumVal.GetType();
MemberInfo[] memInfo = type.GetMember(enumVal.ToString());
object[] attributes = memInfo[0].GetCustomAttributes(typeof(AmbientValueAttribute), false);
if (attributes == null || attributes.Length == 0)
return default;
return ((AmbientValueAttribute)attributes[0]).Value;
}
}
And finally obtaining the Guid value like so :
var valGuid = (Guid)AccessRoles.SysAdmin.GetAmbientValue();
I have a class that is used for storing user data to a file. It works well, but can't really be placed into a PCL library easily. Outside of the PCL, it's all fine.
The class looks like this
public static class UserData
{
public static object GetPropertyValue(this object data, string propertyName)
{
return data.GetType().GetProperties().Single(pi => pi.Name == propertyName).GetValue(data, null);
}
public static object SetPropertyValue<T>(this object data, string propertyName, T value)
{
data.GetType().GetProperties().Single(pi => pi.Name == propertyName).SetValue(data, value);
return new object();
}
private static string pUserSettingsFile;
private static UserSettings userSetting;
public static bool AccountEnabled
{
get
{
return UserSettings.account_enabled;
}
set
{
UserSettings settings = UserSettings;
settings.account_enabled = value;
UserSettings = settings;
}
}
public static UserSettings UserSettings
{
get
{
if (userSetting == null)
{
if (File.Exists(UserSettingsFile))
{
userSetting = Serializer.XmlDeserializeObject<UserSettings>(UserSettingsFile);
}
else
{
userSetting = new UserSettings();
Serializer.XmlSerializeObject(userSetting, UserSettingsFile);
}
}
return userSetting;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value is null!");
}
userSetting = value;
if (File.Exists(UserSettingsFile))
{
File.Delete(UserSettingsFile);
}
Serializer.XmlSerializeObject(userSetting, UserSettingsFile);
}
}
public static string UserSettingsFile
{
get
{
if (string.IsNullOrEmpty(pUserSettingsFile))
{
pUserSettingsFile = Path.Combine(GroupShootDroid.Singleton.ContentDirectory, "UserSettings.xml");
}
return pUserSettingsFile;
}
}
#endregion
}
public class UserSettings
{
public bool account_enabled { get; set; }
public string address { get; set; }
public string country { get; set; }
}
It's not rocket science, but does what I need it to do.
What I'm trying to do is use the Get/SetPropertyValue methods to return or set any of the properties within the class.
Currently, to access the Get/SetPropertyValue methods I'm using this
public string GetStringValue(string valToGet)
{
string rv = (string)UserData.GetPropertyValue(valToGet);
return rv;
}
public void SetStringValue(string name, string val)
{
UserData.SetPropertyValue(name, val);
}
On compiling though, the GetPropertyValue method is giving an error that No overload for method GetPropertyValue takes 1 argument with the SetPropertyValue complaining that there isn't an overload that takes 2
I'm not sure that the code I'm using will do what I need it to do (from what I've read on here it should be), but I'm more perplexed as to why the errors are showing.
Is there a better way to do what I'm trying to do? The application is a Xam.Forms app, so the PCL accesses the class through an interface using injection.
You are defining extension method, you need an instance of the class to call them:
var o = new Object();
string rv = (string)o.GetPropertyValue(valToGet);
// or, but no sure
string rv = (string)UserData.GetPropertyValue(o, valToGet);
or more probably in your case:
public string GetStringValue(string valToGet)
{
string rv = (string)this.GetPropertyValue(this, valToGet);
//or
//string rv = (string)UserData.GetPropertyValue(this, valToGet);
return rv;
}
I think you're getting confused between the UserData class and the object class. Your extension methods extend object.
I'm not sure of all of the correct terminology for what I am trying to do, so I will just dive in with some code.
Current Setup:
public enum NavigationLinks
{
[FriendlyName("System Dashboard")]
SystemDashboard,
[FriendlyName("Trading Dashboard")]
TradingDashboard,
}
public class UINameAttribute : Attribute
{
public string Value { get; private set; }
public UINameAttribute(string Value)
{
this.Value = Value;
}
}
What I would like:
public enum NavigationLinks
{
[FriendlyName]
SystemDashboard,
[FriendlyName]
TradingDashboard,
}
public class UINameAttribute : Attribute
{
public string Value { get; private set; }
public UINameAttribute(string Value)
{
this.Value = Value;
}
public UINameAttribute()
{
string AttributedValue = this.AttributedObject.ToString();
// Take the value of the attribute and add a space in between the camel case.
}
}
Can I access the underlying 'thing' that the attribute is on from within the constructor of the attribute?
No, you can't access attributed member from within the attribute's constructor.
But why do that anyway, if you already have a logic how to resolve friendly name from enum value.
public enum NavigationLinks
{
SystemDashboard,
TradingDashboard,
}
public static class Program
{
private static string ToFriendlyName(string defaultName)
{
var sb = new StringBuilder(defaultName);
for (int i = 1; i < sb.Length; ++i)
if (char.IsUpper(sb[i]))
{
sb.Insert(i, ' ');
++i;
}
return sb.ToString();
}
public static void Main(string[] args)
{
var value = NavigationLinks.SystemDashboard;
var friendlyName = ToFriendlyName(value.ToString());
}
}
In addition to Stipo's approach you can also write an extension method to get the name, something like this:
public static class NavigationLinksExtension
{
public static string GetFriendlyName(this NavigationLinks navLink)
{
string tmpName = navLink.ToString();
tmpName = Regex.Replace(tmpName, "(?<=[a-z])([A-Z])", " $1"); // insert space
return tmpName;
}
}
Then you can simply access the value:
NavigationLinks nl = NavigationLinks.TradingDashboard;
string nlFriendlyName = nl.GetFriendlyName();
An attribute can't (directly) get access to the thing it is describing. If you want ToString() to be the default and only override it occasionally, you would be better off with a helper function (e.g. GetFriendlyName) that defaults to ToString(), but replaces it with the value in a FriendlyName attribute should one exist.
Please try the following:
var inputString = NavigationLinks.SystemDashboard;
Regex.Replace(inputString, "([A-Z][a-z0-9]+)+", "$1$2");
Use the DescriptionAttribute (or create a custom attribute), then, using Reflection to get the value:
Create the class EnumDescriptions (using System.ComponentModel and System.Reflection):
public class EnumDescriptions
{
public static string StringValueOf(Enum value)
{
FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes[0].Description;
}
And put Description in the Enum:
public enum Animals
{
[Description("System Dashboard")]
SystemDashboard,
[Description("Trading Dashboard")]
TradingDashboard,
}
To get the values:
static void Main(string[] args)
{
Console.WriteLine(EnumDescriptions.StringValueOf(Animals.SystemDashboard));
Console.WriteLine();
Console.WriteLine(EnumDescriptions.StringValueOf(Animals.TradingDashboard));
Console.Read();
}
is there a way to make an enum value not browsable to combo box
or just, not to come back from Enum.GetValues() ??
public enum DomainTypes
{
[Browsable(true)]
Client = 1,
[Browsable(false)]
SecretClient = 2,
}
This is a generic method (based on another SO answer which I can't find) which you can call on any enum.
By the way, the Browsable attribute is already defined in System.ComponentModel.
For example:
ComboBox.DataSource = EnumList.Of<DomainTypes>();
...
public class EnumList
{
public static List<T> Of<T>()
{
return Enum.GetValues(typeof(T))
.Cast<T>()
.Where(x =>
{
BrowsableAttribute attribute = typeof(T)
.GetField(Enum.GetName(typeof(T), x))
.GetCustomAttributes(typeof(BrowsableAttribute),false)
.FirstOrDefault() as BrowsableAttribute;
return attribute == null || attribute.Browsable == true;
}
)
.ToList();
}
}
There is nothing already in place to do this for you with the Enum.GetValues() method. If you want to use attributes, you can create your own custom attribute and use it via reflection:
public class BrowsableAttribute : Attribute
{
public bool IsBrowsable { get; protected set; }
public BrowsableAttribute(bool isBrowsable)
{
this.IsBrowsable = isBrowsable;
}
}
public enum DomainTypes
{
[Browsable(true)]
Client = 1,
[Browsable(false)]
SecretClient = 2,
}
And then you can use reflection to check for custom attributes and generate a list of Enums based on the Browsable attribute.
It really can't be done in C# - a public enumeration exposes all members. Instead, consider using a wrapper class to hide/expose the items selectively. Maybe something like this:
public sealed class EnumWrapper
{
private int _value;
private string _name;
private EnumWrapper(int value, string name)
{
_value = value;
_name = name;
}
public override string ToString()
{
return _name;
}
// Allow visibility to only the items you want to
public static EnumWrapper Client = new EnumWrapper(0, "Client");
public static EnumWrapper AnotherClient= new EnumWrapper(1, "AnotherClient");
// The internal keyword makes it only visible internally
internal static readonly EnumWrapper SecretClient= new EnumWrapper(-1, "SecretClient");
}
Hope this helps. Good luck!