Giter Site home page Giter Site logo

ardalis / smartenum Goto Github PK

View Code? Open in Web Editor NEW
2.1K 24.0 159.0 597 KB

A base class for quickly and easily creating strongly typed enum replacements in C#.

License: MIT License

C# 96.15% HTML 3.85%
clean csharp ddd design-patterns domain-driven-design dotnet dotnet-core enum hacktoberfest

smartenum's Introduction

NuGetNuGet Last Publish Ardalis.SmartEnum to NuGet

Table Of Contents

Sub-packages

SmartEnum.AutoFixture: NuGetNuGetpublish SmartEnum.AutoFixture to nuget

SmartEnum.Dapper: NuGetNuGetpublish SmartEnum.Dapper to nuget

SmartEnum.EFCore: NuGetNuGetpublish SmartEnum.EFCore to nuget

SmartEnum.JsonNet: NuGetNuGetpublish jsonnet to nuget

SmartEnum.MessagePack: NuGetNuGetpublish SmartEnum.MessagePack to nuget

SmartEnum.ProtoBufNet: NuGetNuGetpublish SmartEnum.ProtoBufNet to nuget

SmartEnum.SystemTextJson: NuGetNuGetpublish SmartEnum.SystemTextJson to nuget

SmartEnum.Utf8Json: NuGetNuGetpublish SmartEnum.Utf8Json to nuget

Give a Star! ⭐

If you like or are using this project please give it a star. Thanks!

Smart Enum

An implementation of a type-safe object-oriented alternative to C# enum.

Contributors

Thanks to Scott DePouw, Antão Almada, and Nagasudhir Pulla for help with this project!

Install

The framework is provided as a set of NuGet packages. In many cases you'll only need the base package, but if you need serialization and/or ORM support there are many implementation-specific packages available to assist.

To install the minimum requirements:

Install-Package Ardalis.SmartEnum

To install support for serialization, AutoFixture, EF Core, Model Binding, or Dapper select the lines that apply:

Install-Package Ardalis.SmartEnum.AutoFixture
Install-Package Ardalis.SmartEnum.JsonNet
Install-Package Ardalis.SmartEnum.Utf8Json
Install-Package Ardalis.SmartEnum.MessagePack
Install-Package Ardalis.SmartEnum.ProtoBufNet
Install-Package Ardalis.SmartEnum.EFCore
Install-Package Ardalis.SmartEnum.ModelBinding
Install-Package Ardalis.SmartEnum.Dapper

Version

The latest version of the package supports .NET 7. If you don't need or aren't yet ready to move to .NET 7 or later, you should install the previous stable version, Ardalis.SmartEnum 2.1.

Example package manager command:

Install-Package Ardalis.SmartEnum -Version 2.1.0

Usage

Define your smart enum by inheriting from SmartEnum<TEnum> where TEnum is the type you're declaring. For example:

using Ardalis.SmartEnum;

public sealed class TestEnum : SmartEnum<TestEnum>
{
    public static readonly TestEnum One = new TestEnum(nameof(One), 1);
    public static readonly TestEnum Two = new TestEnum(nameof(Two), 2);
    public static readonly TestEnum Three = new TestEnum(nameof(Three), 3);

    private TestEnum(string name, int value) : base(name, value)
    {
    }
}

The default value type is int but it can be set using the second generic argument TValue. The string alias can also be set explicitly, where spaces are allowed.

using Ardalis.SmartEnum;

public sealed class TestEnum : SmartEnum<TestEnum, ushort>
{
    public static readonly TestEnum One = new TestEnum("A string!", 1);
    public static readonly TestEnum Two = new TestEnum("Another string!", 2);
    public static readonly TestEnum Three = new TestEnum("Yet another string!", 3);

    private TestEnum(string name, ushort value) : base(name, value)
    {
    }
}

Just like regular enum, more than one string can be assigned to the same value but only one value can be assigned to a string:

using Ardalis.SmartEnum;

public sealed class TestEnum : SmartEnum<TestEnum>
{
    public static readonly TestEnum One = new TestEnum(nameof(One), 1);
    public static readonly TestEnum Two = new TestEnum(nameof(Two), 2);
    public static readonly TestEnum Three = new TestEnum(nameof(Three), 3);
    public static readonly TestEnum AnotherThree = new TestEnum(nameof(AnotherThree), 3);
    // public static TestEnum Three = new TestEnum(nameof(Three), 4); -> throws exception

    private TestEnum(string name, int value) : base(name, value)
    {
    }
}

In this case, TestEnum.FromValue(3) will return the first instance found, either TestEnum.Three or TestEnum.AnotherThree. No order should be assumed.

The Value content is used when comparing two smart enums, while Name is ignored:

TestEnum.One.Equals(TestEnum.One); // returns true
TestEnum.One.Equals(TestEnum.Three); // returns false
TestEnum.Three.Equals(TestEnum.AnotherThree); // returns true

Inheritance can be used to add "behavior" to a smart enum.

This example adds a BonusSize property, avoiding the use of the switch typically used with regular enums:

using Ardalis.SmartEnum;

public abstract class EmployeeType : SmartEnum<EmployeeType>
{
    public static readonly EmployeeType Manager = new ManagerType();
    public static readonly EmployeeType Assistant = new AssistantType();

    private EmployeeType(string name, int value) : base(name, value)
    {
    }

    public abstract decimal BonusSize { get; }

    private sealed class ManagerType : EmployeeType
    {
        public ManagerType() : base("Manager", 1) {}

        public override decimal BonusSize => 10_000m;
    }

    private sealed class AssistantType : EmployeeType
    {
        public AssistantType() : base("Assistant", 2) {}

        public override decimal BonusSize => 1_000m;
    }
}

You can take this a step further and use the ManagerType and associated BonusSize property in a parent class like so:

public class Manager
{
    private ManagerType _managerType { get; set; }
    public string Type
    {
        get => _managerType.Name;
        set
        {
            if (!ManagerType.TryFromName(value, true, out var parsed))
            {
                throw new Exception($"Invalid manage type of '{value}'");
            }
            _managerType = parsed;
        }
    }

    public string BonusSize
    {
        get => _managerType.BonusSize();
        set => _bonusSize_ = value;
    }
}

This other example implements a state machine. The method CanTransitionTo() returns true if it's allowed to transition from current state to next; otherwise returns false.

using Ardalis.SmartEnum;

public abstract class ReservationStatus : SmartEnum<ReservationStatus>
{
    public static readonly ReservationStatus New = new NewStatus();
    public static readonly ReservationStatus Accepted = new AcceptedStatus();
    public static readonly ReservationStatus Paid = new PaidStatus();
    public static readonly ReservationStatus Cancelled = new CancelledStatus();

    private ReservationStatus(string name, int value) : base(name, value)
    {
    }

    public abstract bool CanTransitionTo(ReservationStatus next);

    private sealed class NewStatus: ReservationStatus
    {
        public NewStatus() : base("New", 0)
        {
        }

        public override bool CanTransitionTo(ReservationStatus next) =>
            next == ReservationStatus.Accepted || next == ReservationStatus.Cancelled;
    }

    private sealed class AcceptedStatus: ReservationStatus
    {
        public AcceptedStatus() : base("Accepted", 1)
        {
        }

        public override bool CanTransitionTo(ReservationStatus next) =>
            next == ReservationStatus.Paid || next == ReservationStatus.Cancelled;
    }

    private sealed class PaidStatus: ReservationStatus
    {
        public PaidStatus() : base("Paid", 2)
        {
        }

        public override bool CanTransitionTo(ReservationStatus next) =>
            next == ReservationStatus.Cancelled;
    }

    private sealed class CancelledStatus: ReservationStatus
    {
        public CancelledStatus() : base("Cancelled", 3)
        {
        }

        public override bool CanTransitionTo(ReservationStatus next) =>
            false;
    }
}

List

You can list all of the available options using the enum's static List property:

foreach (var option in TestEnum.List)
    Console.WriteLine(option.Name);

List returns an IReadOnlyCollection so you can use the Count property to efficiently get the number of available options.

var count = TestEnum.List.Count;

FromName()

Access an instance of an enum by matching a string to its Name property:

var myEnum = TestEnum.FromName("One");

Exception SmartEnumNotFoundException is thrown when name is not found. Alternatively, you can use TryFromName that returns false when name is not found:

if (TestEnum.TryFromName("One", out var myEnum))
{
    // use myEnum here
}

Both methods have a ignoreCase parameter (the default is case sensitive).

FromValue()

Access an instance of an enum by matching its value:

var myEnum = TestEnum.FromValue(1);

Exception SmartEnumNotFoundException is thrown when value is not found. Alternatively, you can use TryFromValue that returns false when value is not found:

if (TestEnum.TryFromValue(1, out var myEnum))
{
    // use myEnum here
}

ToString()

Display an enum using the ToString() override:

Console.WriteLine(TestEnum.One); // One

Switch

Given an instance of a TestEnum, switch depending on value:

switch(testEnumVar.Name)
{
    case nameof(TestEnum.One):
        ...
        break;
    case nameof(TestEnum.Two):
        ...
        break;
    case nameof(TestEnum.Three):
        ...
        break;
    default:
        ...
        break;
}

Using pattern matching:

switch(testEnumVar)
{
    case null:
        ...
        break;
    case var e when e.Equals(TestEnum.One):
        ...
        break;
    case var e when e.Equals(TestEnum.Two):
        ...
        break;
    case var e when e.Equals(TestEnum.Three):
        ...
        break;
    default:
        ...
        break;
}

Because of the limitations of pattern matching SmartEnum also provides a fluent interface to help create clean code:

testEnumVar
    .When(TestEnum.One).Then(() => ... )
    .When(TestEnum.Two).Then(() => ... )
    .When(TestEnum.Three).Then(() => ... )
    .Default( ... );

N.B. For performance critical code the fluent interface carries some overhead that you may wish to avoid. See the available benchmarks code for your use case.

SmartFlagEnum

Support has been added for a Flag functionality. This feature is similar to the behaviour seen when applying the [Flag] attribute to Enums in the .NET Framework All methods available on the SmartFlagEnum class return an IEnumerable<SmartFlagEnum> with one or more values depending on the value provided/method called. Some Functionality is shared with the original SmartEnum class, listed below are the variations.

Setting SmartFlagEnum Values

When setting the values for a SmartFlagEnum It is imperative to provide values as powers of two. If at least one value is not set as power of two or two or more power of two values are provided inconsecutively (eg: 1, 2, no four!, 8) a SmartFlagEnumDoesNotContainPowerOfTwoValuesException will be thrown.

public class SmartFlagTestEnum : SmartFlagEnum<SmartFlagTestEnum>
    {
        public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0);
        public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1);
        public static readonly SmartFlagTestEnum Cash = new SmartFlagTestEnum(nameof(Cash), 2);
        public static readonly SmartFlagTestEnum Bpay = new SmartFlagTestEnum(nameof(Bpay), 4);
        public static readonly SmartFlagTestEnum Paypal = new SmartFlagTestEnum(nameof(Paypal), 8);
        public static readonly SmartFlagTestEnum BankTransfer = new SmartFlagTestEnum(nameof(BankTransfer), 16);

        public SmartFlagTestEnum(string name, int value) : base(name, value)
        {
        }
    }

This behaviour can be disabled by applying the AllowUnsafeFlagEnumValuesAttribute to the smart enum class. Note: If power of two values are not provided the SmarFlagEnum will not behave as expected!

[AllowUnsafeFlagEnumValues]
public class SmartFlagTestEnum : SmartFlagEnum<SmartFlagTestEnum>
    {
        public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0);
        public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1);
        public static readonly SmartFlagTestEnum Cash = new SmartFlagTestEnum(nameof(Cash), 2);
        public static readonly SmartFlagTestEnum Bpay = new SmartFlagTestEnum(nameof(Bpay), 4);
        public static readonly SmartFlagTestEnum Paypal = new SmartFlagTestEnum(nameof(Paypal), 8);
        public static readonly SmartFlagTestEnum BankTransfer = new SmartFlagTestEnum(nameof(BankTransfer), 16);

        public SmartFlagTestEnum(string name, int value) : base(name, value)
        {
        }
    }

Combination values can be provided explicitly and will be returned in place of the multiple flag values that would have been returned from the FromValue() method.

public class SmartFlagTestEnum : SmartFlagEnum<SmartFlagTestEnum>
    {
        public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0);
        public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1);
        public static readonly SmartFlagTestEnum Cash = new SmartFlagTestEnum(nameof(Cash), 2);
        public static readonly SmartFlagTestEnum CardAndCash = new SmartFlagTestEnum(nameof(CardAndCash), 3); -- Explicit `Combination` value
        public static readonly SmartFlagTestEnum Bpay = new SmartFlagTestEnum(nameof(Bpay), 4);
        public static readonly SmartFlagTestEnum Paypal = new SmartFlagTestEnum(nameof(Paypal), 8);
        public static readonly SmartFlagTestEnum BankTransfer = new SmartFlagTestEnum(nameof(BankTransfer), 16);

        public SmartFlagTestEnum(string name, int value) : base(name, value)
        {
        }
    }

These explicit values can be provided above the highest allowable flag value without consequence, however attempting to access a value that is higher than the maximum flag value that has not explicitly been provided (for example 4) will cause a SmartEnumNotFoundException to be thrown.

public class SmartFlagTestEnum : SmartFlagEnum<SmartFlagTestEnum>
    {
        public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0);
        public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1);
        public static readonly SmartFlagTestEnum Cash = new SmartFlagTestEnum(nameof(Cash), 2);
        public static readonly SmartFlagTestEnum AfterPay = new SmartFlagTestEnum(nameof(AfterPay), 5);

        public SmartFlagTestEnum(string name, int value) : base(name, value)
        {
        }
    }

    var myFlagEnums = FromValue(3) -- Works!
    -and-
    var myFlagEnums = FromValue(5) -- Works!
    -but-
    Var myFlagEnums = FromValue(4) -- will throw an exception :(

A Negative One (-1) value may be provided as an All value. When a value of -1 is passed into any of the FromValue() methods an IEnumerable containing all values (excluding 0) will be returned. If an explicit Combination value exists with a value of -1 this will be returned instead.

public class SmartFlagTestEnum : SmartFlagEnum<SmartFlagTestEnum>
    {
        public static readonly SmartFlagTestEnum All = new SmartFlagTestEnum(nameof(All), -1);
        public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0);
        public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1);
        public static readonly SmartFlagTestEnum Cash = new SmartFlagTestEnum(nameof(Cash), 2);
        public static readonly SmartFlagTestEnum Bpay = new SmartFlagTestEnum(nameof(Bpay), 4);
        public static readonly SmartFlagTestEnum Paypal = new SmartFlagTestEnum(nameof(Paypal), 8);
        public static readonly SmartFlagTestEnum BankTransfer = new SmartFlagTestEnum(nameof(BankTransfer), 16);

        public SmartFlagTestEnum(string name, int value) : base(name, value)
        {
        }
    }

Usage - (SmartFlagEnum)

public abstract class EmployeeType : SmartFlagEnum<EmployeeType>
    {
        public static readonly EmployeeType Director = new DirectorType();
        public static readonly EmployeeType Manager = new ManagerType();
        public static readonly EmployeeType Assistant = new AssistantType();

        private EmployeeType(string name, int value) : base(name, value)
        {
        }

        public abstract decimal BonusSize { get; }

        private sealed class DirectorType : EmployeeType
        {
            public DirectorType() : base("Director", 1) { }

            public override decimal BonusSize => 100_000m;
        }

        private sealed class ManagerType : EmployeeType
        {
            public ManagerType() : base("Manager", 2) { }

            public override decimal BonusSize => 10_000m;
        }

        private sealed class AssistantType : EmployeeType
        {
            public AssistantType() : base("Assistant", 4) { }

            public override decimal BonusSize => 1_000m;
        }
    }

    public class SmartFlagEnumUsageExample
    {
        public void UseSmartFlagEnumOne()
        {
            var result = EmployeeType.FromValue(3).ToList();

            var outputString = "";
            foreach (var employeeType in result)
            {
                outputString += $"{employeeType.Name} earns ${employeeType.BonusSize} bonus this year.\n";
            }

                => "Director earns $100000 bonus this year.\n"
                   "Manager earns $10000 bonus this year.\n"
        }

        public void UseSmartFlagEnumTwo()
        {
            EmployeeType.FromValueToString(-1)
                => "Director, Manager, Assistant"
        }

        public void UseSmartFlagEnumTwo()
        {
            EmployeeType.FromValueToString(EmployeeType.Assistant | EmployeeType.Director)
                => "Director, Assistant"
        }
    }

FromName()

Access an IEnumerable of enum instances by matching a string containing one or more enum names seperated by commas to its Names property:

var myFlagEnums = TestFlagEnum.FromName("One, Two");

Exception SmartEnumNotFoundException is thrown when no names are found. Alternatively, you can use TryFromName that returns false when no names are found:

if (TestFlagEnum.TryFromName("One, Two", out var myFlagEnums))
{
    // use myFlagEnums here
}

Both methods have a ignoreCase parameter (the default is case sensitive).

FromValue()

Access an IEnumerable of enum instances by matching a value:

var myFlagEnums = TestFlagEnum.FromValue(3);

Exception SmartEnumNotFoundException is thrown when no values are found. Alternatively, you can use TryFromValue that returns false when values are not found:

if (TestFlagEnum.TryFromValue(3, out var myFlagEnums))
{
    // use myFlagEnums here
}

Note: Negative values other than (-1) passed into this method will cause a NegativeValueArgumentException to be thrown, this behaviour can be disabled by applying the AllowNegativeInput attribute to the desired SmartFlagEnum class.

[AllowNegativeInput]
public class SmartFlagTestEnum : SmartFlagEnum<SmartFlagTestEnum>
    {
        public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0);
        public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1);

        public SmartFlagTestEnum(string name, int value) : base(name, value)
        {
        }
    }

Note: FromValue() will accept any input that can be succesfully parsed as an integer. If an invalid value is supplied it will throw an InvalidFlagEnumValueParseException.

FromValueToString()

Return a string representation of a series of enum instances name's:

var myFlagEnumString = TestFlagEnum.FromValueToString(3);

Exception SmartEnumNotFoundException is thrown when no values are found. Alternatively, you can use TryFromValueToString that returns false when values are not found:

if (TestFlagEnum.TryFromValueToString(3, out var myFlagEnumsAsString))
{
    // use myFlagEnumsAsString here
}

Note: Negative values other than (-1) passed into this method will cause a NegativeValueArgumentException to be thrown, this behaviour can be disabled by applying the AllowNegativeInput attribute to the desired SmartFlagEnum class.

BitWiseOrOperator

The FromValue() methods allow the Or ( | ) operator to be used to add enum values together and provide multiple values at once.

var myFlagEnums = TestFlagEnum.FromValue(TestFlagEnum.One | TestFlagEnum.Two);

This will only work where the type of the SmartFlagEnum has been specified as Int32 or else can be explicitly cast as an Int32.

var myFlagEnums = TestFlagEnumDecimal.FromValue((int)TestFlagEnum.One | (int)TestFlagEnum.Two);

Persisting with EF Core 2.1 or higher

EF Core 2.1 introduced value conversions which can be used to map SmartEnum types to simple database types. For example, given an entity named Policy with a property PolicyStatus that is a SmartEnum, you could use the following code to persist just the value to the database:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.Entity<Policy>()
        .Property(p => p.PolicyStatus)
        .HasConversion(
            p => p.Value,
            p => PolicyStatus.FromValue(p));
}

Remember, you need to implement your own parameterless constructor to make it works with db context. See #103 issue.

Using SmartEnum.EFCore

EF Core 6 introduced pre-convention model configuration which allows value conversions to be configured for specific types within a model. If you have installed Ardalis.SmartEnum.EFCore it is sufficient to add the following line at the beginning of the ConfigureConventions method:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.ConfigureSmartEnum();

    ...
}

For previous versions of EF Core, the following line can be added at the end of the OnModelCreating method:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    ...

    modelBuilder.ConfigureSmartEnum();
}

AutoFixture support

New instance of a SmartEnum should not be created. Instead, references to the existing ones should always be used. AutoFixture by default doesn't know how to do this. The Ardalis.SmartEnum.AutoFixture package includes a specimen builder for SmartEnum. Simply add the customization to the IFixture builder:

var fixture = new Fixture()
    .Customize(new SmartEnumCustomization());

var smartEnum = fixture.Create<TestEnum>();

Json.NET support

When serializing a SmartEnum to JSON, only one of the properties (Value or Name) should be used. Json.NET by default doesn't know how to do this. The Ardalis.SmartEnum.JsonNet package includes a couple of converters to achieve this. Simply use the attribute JsonConverterAttribute to assign one of the converters to the SmartEnum to be de/serialized:

public class TestClass
{
    [JsonConverter(typeof(SmartEnumNameConverter<TestEnum,int>))]
    public TestEnum Property { get; set; }
}

uses the Name:

{
  "Property": "One"
}

While this:

public class TestClass
{
    [JsonConverter(typeof(SmartEnumValueConverter<TestEnum,int>))]
    public TestEnum Property { get; set; }
}

uses the Value:

{
  "Property": 1
}

Note: The SmartFlagEnum works identically to the SmartEnum when being Serialized and Deserialized.

Dapper support

To enable Dapper support for SmartEnum values, add a SmartEnumTypeHandler to SqlMapper for the given SmartEnum type. There are two inheritors of SmartEnumTypeHandler: SmartEnumByNameTypeHandler, which maps the Name of a SmartEnum to a database column, and SmartEnumByValueTypeHandler, which maps the Value of a SmartEnum to a database column.

// Maps the name of TestEnum objects (e.g. "One", "Two", or "Three") to a database column.
SqlMapper.AddTypeHandler(typeof(TestEnum), new SmartEnumByNameTypeHandler<TestEnum>());
// Maps the value of TestEnum objects (e.g. 1, 2, or 3) to a database column.
SqlMapper.AddTypeHandler(typeof(TestEnum), new SmartEnumByValueTypeHandler<TestEnum>());

DapperSmartEnum

To avoid needing to explicitly register a SmartEnum type with Dapper, it can be done automatically by inheriting from DapperSmartEnumByName or DapperSmartEnumByValue instead of from SmartEnum.

public class TestEnumByName : DapperSmartEnumByName<TestEnumByName>
{
    public static readonly TestEnumByName One = new TestEnumByName(1);
    public static readonly TestEnumByName Two = new TestEnumByName(2);
    public static readonly TestEnumByName Three = new TestEnumByName(3);

    protected TestEnumByName(int value, [CallerMemberName] string name = null) : base(name, value)
    {
    }
}
public class TestEnumByValue : DapperSmartEnumByValue<TestEnumByValue>
{
    public static readonly TestEnumByValue One = new TestEnumByValue(1);
    public static readonly TestEnumByValue Two = new TestEnumByValue(2);
    public static readonly TestEnumByValue Three = new TestEnumByValue(3);

    protected TestEnumByValue(int value, [CallerMemberName] string name = null) : base(name, value)
    {
    }
}

Inheritors of DapperSmartEnum can be decorated with custom attributes in order to configure its type handler. Use DbTypeAttribute (e.g. [DbType(DbType.String)]) to specify that parameters should have their DbType property set to the specified value. Use DoNotSetDbTypeAttribute (e.g. [DoNotSetDbType]) to specify that parameters should not have their DbType property set. Use IgnoreCaseAttribute (e.g. [IgnoreCase]) when inheriting from DapperSmartEnumByName to specify that database values do not need to match the case of a SmartEnum Name.

Case Insensitive String Enum

When creating enums of strings, the default behaviour of SmartEnum is to compare the strings with a case sensitive comparer. It is possible to specify a different equality comparer for the enum values, for example a case insensitive one:

[SmartEnumStringComparer(StringComparison.InvariantCultureIgnoreCase)]
public class CaseInsensitiveEnum : SmartEnum<CaseInsensitiveEnum, string>
{
    protected CaseInsensitiveEnum(string name, string value) : base(name, value) { }

    public static CaseInsensitiveEnum One = new CaseInsensitiveEnum("One", "one");
    public static CaseInsensitiveEnum Two = new CaseInsensitiveEnum("Two", "two");
}

var e1 = CaseInsensitiveEnum.FromValue("ONE");
var e2 = CaseInsensitiveEnum.FromValue("one");

//e1 is equal to e2

Name Validation Attribute

The DataAnnotations ValidationAttribute SmartEnumNameAttribute allows you to validate your models, mandating that when provided a value it must be matching the name of a given SmartEnum. This attribute allows null values (use [Required] to disallow nulls).

In addition to specifying the SmartEnum to match, you may also pass additional parameters:

  • allowCaseInsensitiveMatch (default false)
  • errorMessage (default "{0} must be one of: {1}"): A format string to customize the error
    • {0} is the name of the property being validated
    • {1} is the comma-separated list of valid SmartEnum names

Example of Name Validation Attribute

public sealed class ExampleSmartEnum : SmartEnum<ExampleSmartEnum>
{
    public static readonly ExampleSmartEnum Foo = new ExampleSmartEnum(nameof(Foo), 1);
    public static readonly ExampleSmartEnum Bar = new ExampleSmartEnum(nameof(Bar), 2);
    
    private ExampleSmartEnum(string name, int value) : base(name, value) { }
}

public class ExampleModel
{
    [Required]
    [SmartEnumName(typeof(ExampleSmartEnum)]
    public string MyExample { get; set; } // must be "Foo" or "Bar"
    
    [SmartEnumName(typeof(ExampleSmartEnum), allowCaseInsensitiveMatch: true)]
    public string CaseInsensitiveExample { get; set; } // "Foo", "foo", etc. allowed; null also allowed here
}

Examples in the Real World

Search for more

References

smartenum's People

Contributors

aalmada avatar ardalis avatar arootbeer avatar azure-pipelines[bot] avatar bfriesen avatar cmenzi avatar codenjs avatar dependabot-preview[bot] avatar dependabot[bot] avatar imgbot[bot] avatar jafin avatar kylemcmaster avatar mgraf1 avatar mikaelweave avatar mxmissile avatar nagasudhirpulla avatar natastro avatar nemo-illusionist avatar nevind7 avatar pdevito3 avatar pedroduarte0 avatar rafek1241 avatar samuelwine avatar sdepouw avatar sgoodgrove avatar shadynagy avatar steve-oh avatar stevedunn avatar strandloper avatar yaevh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

smartenum's Issues

Not DataContract friendly

I tried to use SmartEnum in a DataContract for WCF communication. The class misses the DataContract attribute and the properties Name and Value are missing the DataMember attribute and setter.

missing nuget packages

Install-Package Ardalis.SmartEnum.JsonNet
Install-Package Ardalis.SmartEnum.Utf8Json

are missing

SmartEnumValueConverter doesn't work with a string value

If this is my "enum":

public sealed class TestEnum : SmartEnum<TestEnum, string>
{
    public static readonly TestEnum One = new TestEnum(nameof(One), "one");

    private TestEnum(string name, string value) : base(name, value)
    {
    }
}

This class doesn't work:

public class TestClass
{
    [JsonConverter(typeof(SmartEnumValueConverter<TestEnum, string>))]
    public TestEnum Property { get; set; }
}

The error thrown: The type 'string' must be a non-nullable value type in order to use it as parameter 'TValue' in the generic type or method 'SmartEnumValueConverter<TEnum, TValue>'.

Alternative implementation

I changed the "nameof(One)", from the main page example, by this:

  public class MessageTypeEnum : SmartEnum<MessageTypeEnum, int>
  {
    protected MessageTypeEnum(int value, [CallerMemberName] string name = null) : base(name, value)
    {
    }

    public static MessageTypeEnum None { get; } = new MessageTypeEnum(0);

    public static MessageTypeEnum Error { get; } = new MessageTypeEnum(1);

    public static MessageTypeEnum Question { get; } = new MessageTypeEnum(2);

    public static MessageTypeEnum Warning { get; } = new MessageTypeEnum(3);

    public static MessageTypeEnum Information { get; } = new MessageTypeEnum(4);
  }

The key here is the attribute CallerMemberName in the constructor fed at compile time.

I did this too:

    private static int _i = 0;
    public static MessageTypeEnum None { get; } = new MessageTypeEnum(_i++);
    public static MessageTypeEnum Error { get; } = new MessageTypeEnum(_i++);

It works, but I don't know if the values are guaranteed to be always the same, so I'm hesitant to do that. What do you say?

How about a HasFlag() extension method just like a regular enum?

I know that @AFM-Horizon is already working (#64) on a SmartFlagsEnum concept for this but I couldn't add an issue on his fork.

HasFlag() is one of the most useful extensions on good old enums. I suppose it wouldn't take much to add that in, given the well-laid out structure of SmartFlagsEnum by @AFM-Horizon.

I would go so far as to have AddFlag() and RemoveFlag() extension methods too, which I have so desperately wished regular enums had. I end-up writing my own AddFlag() and RemoveFlag() methods for most projects where I use enums.

Any thoughts?

Published NuGets are not strongly-named assemblies

The current NuGet package for v1.0.5* is not built as a strongly-named assembly. This is prevents using it in an assembly that is required to be strongly-named/signed. This impacts me since my current project is used in the GAC.

After reading around, it looks like the optimal solution may be for whoever is uploading the binary of this package to strongly sign it locally before uploading it:
http://disq.us/p/oog81l

Microsoft themselves currently recommend:

If you are an open-source developer and you want the identity benefits of a strong-named assembly, consider checking in the private key associated with an assembly into your source control system.

Either of these should work since strongly-naming assemblies is about identity, not security. There are some workaround hacks I could do such as assembling from source or disassembling/reassembling to add my own signature locally. But it'd be much more convenient for other users to just fix the Nugets.

* I'm targeting this version because my project is currently stuck in .NET 4.5.1 while further versions of SmartEnum are requiring .NET 4.6.1+

1.0.6 BC break?

Hi, seems 1.0.6 NuGet version introduced an additional constraint on TValue to be of struct type, but I can't really find when and where this happened here in the repo. Also can't really find the previous versions tags. Am I missing something? :)

Not a real issue

As the title says, it's not a real issue but more of a question.

I'd like to know why the decision was made to put the "name" property before the "value" in the constructor. Most of the time when working with key-value pairs or enums, it is logical to put the key first IMO and I'd like to know if there's any reason why it was made differently.

Thanks in advance!

Doesn't seem to have support for Flags Enums

My team really likes this project and would like to use it for our codebase! Unfortunately, we have several flags enums and they don't seem to be compatible with this project. :(

The FromValue method throws a SmartEnumNotFoundException when given a flags enum value as well as casting a flags value.

// Given SomeEnum.A is 1 and SomeEnum.B is 2, all three throw the exception
SomeEnum.FromValue(SomeEnum.A | SomeEnum.B);
SomeEnum.FromValue(3); 
(SomeEnum)3

Also, there's no way to get the Name of multiple values with the logical OR operator like we get when the Flags attribute is placed on an enum (i.e. SomeEnum.B | SomeEnum.C => B, C).

Will this project include this support in the near future?

Equals implementation

I was wondering if it would make sense to override and implement Equals() for the SmartEnum. Because now, if I'm checking for equality, I'm doing something like TestEnum.One.Value == myEnumVar.Value, which is not only a little more tedious, but you can also make mistakes.

If the answer is positive, I could try to do this myself and send a PR your way, if you like. :) In that case, I would have one question about what would be "the right way": two SmartEnum instances would be considered equal if their Name property is equal, right (and not necessarily their Value property, as in my example)?

Thanks by the way for providing this! I really like to use it. :)

Missing nuget packages

I've tried to install Ardalis.SmartEnum.AutoFixture, but it seems to be missing?

PM> Install-Package Ardalis.SmartEnum.AutoFixture
Install-Package : Unable to find package 'Ardalis.SmartEnum.AutoFixture'
At line:1 char:1
+ Install-Package Ardalis.SmartEnum.AutoFixture
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [Install-Package], Exception
+ FullyQualifiedErrorId :  NuGetCmdletUnhandledException,NuGet.PackageManagement.PowerShellCmdlets.InstallPackageCommand

FronValue not working when value is a custom object

Hello,
I have
LieuxDeStationnement : SmartEnum<LieuxDeStationnement, LieuDeStationnementKey>
so the "value" is of type LieuDeStationnementKey.

LieuDeStationnementKey implements IEquatable and IComparable with also operators (== != < >)

I tried this

    try
        {
            LieuDeStationnementKey ley1 = LieuxDeStationnement.Peseux.Value; // this is {"NE",6421}
            LieuxDeStationnement lieu1 = LieuxDeStationnement.FromValue(ley1); // this functions ok.
            LieuDeStationnementKey lieuKey = OtherObject.LieuDeStationnementKey; // this is {"NE",6421}
            LieuxDeStationnement lieu = LieuxDeStationnement.FromValue(lieuKey); // this give NotFound
        }
        catch (Exception e)
        {
            throw;
        }

I joind the zip with the two classes.
ALso I see that FromValue doesn't call any of the IEquatable, IComparable methods ... so I don't understand how it works.

LieuDeStationnementKey.zip

Using SmartEnum with EF

I tried to add a private default constructor to the class I derived from this SmartEnum class and VS2017 is telling me that this SmartEnum does not implement a default constructor. Do I have to "roll my own" SmartEnum class in order to use it with EF6 by adding a default constructor in it since this doesn't have one?

Derived List call only lists the base class items

I have a base class and two derived classes (code drastically simplified here). The base has no enum items but the two derived classes do.

I have a simple console app to test listing the enums. However, I the List property is not returning any items on the derived classes. Is this an issue or am I using this wrong? The intent is to have a base Error enum type that other services would derive from. Each service would have their own list of errors but I would like them to all be derived from the base Error type.

If I add enum items to the base Error class those items are listed multiple times.

public class Error : SmartEnum<Error>
{
    protected Error(string name, int value) : base(name, value)
    {
    }
    //Will have other properties here...
}

public class DerivedError1 : Error
{
    public static DerivedError1 Error1Four = new DerivedError1("Derived 1-1", 0);
    public static DerivedError1 Error1Five = new DerivedError1("Derived 1-2", 1);
    public static DerivedError1 Error1Six = new DerivedError1("Derived 1-3", 2);

    private DerivedError1(string name, int value) : base(name, value)
    {
    }
}

public class DerivedError2 : Error
{
    public static DerivedError2 ErrorFour = new DerivedError2("Derived 2-1", 0);
    public static DerivedError2 ErrorFive = new DerivedError2("Derived 2-2", 1);
    public static DerivedError2 ErrorSix = new DerivedError2("Derived 2-3", 2);

    private DerivedError2(string name, int value) : base(name, value)
    {
    }
}

class Program
{
    static void Main(string[] args)
    {

        //Nothing expected here
        foreach (var baseError in Error.List)
        {
            Console.WriteLine($"{baseError.Value} - {baseError.Name}");
        }

       //Expected to see 3 items here, get zero.
        foreach (var derivedError in DerivedError1.List)
        {
            Console.WriteLine($"{derivedError.Value} - {derivedError.Name}");
        }

       //Expected to see 3 items here, get zero.
        foreach (var derivedError in DerivedError2.List)
        {
            Console.WriteLine($"{derivedError.Value} - {derivedError.Name}");
        }

        Console.ReadLine();
    }
}

I'm seeing a similar issue with TestEnum.cs from the SmartEnum.UnitTests project if I add a second enum to the TestDerivedEnum class.

public abstract class TestBaseEnum : SmartEnum<TestBaseEnum>
{
    public static TestBaseEnum One;

    internal TestBaseEnum(string name, int value) : base(name, value)
    {
    }
}

public sealed class TestDerivedEnum : TestBaseEnum
{
    public static TestBaseEnum Two;

    private TestDerivedEnum(string name, int value) : base(name, value)
    {
    }

    static TestDerivedEnum()
    {
        One = new TestDerivedEnum(nameof(One), 1);
        Two = new TestDerivedEnum(nameof(Two), 2);
    }

    public static new TestBaseEnum FromValue(int value) =>
        TestBaseEnum.FromValue(value);

    public static new TestBaseEnum FromName(string name, bool ignoreCase = false) =>
        TestBaseEnum.FromName(name, ignoreCase);
}

Also, FromValue doesn't find enum 2.
var result2 = TestDerivedEnum.FromValue(2);

Thanks,
Brian

'No suitable constructor found for entity type' when doing EF Core migration

I am attempting to run update-database to migrate some changes that I made to my db.

It all goes well until I get the following error:

No suitable constructor found for entity type 'ReportType'. The
following constructors had parameters that could not be bound to
properties of the entity type: cannot bind 'id', 'name' in
'ReportType(string id, string name)'.

Here is the code for ReportType.cs:

public class ReportType : SmartEnum<ReportType, string>
    {
        public static readonly ReportType ReportType1 = new ReportType("Blah", "Blah");
        public static readonly ReportType ReportType2 = new ReportType("Blah", "Blah");
        public static readonly ReportType ReportType3 = new ReportType("Blah", "Blah");

        // required for EF, but breaking for SmartEnum
        // private ReportType() {}

        private ReportType(string id, string name) : base(name, id)
        {

        }
    }

As you can see in the commented section of that code, having a parameterless constructor would normally fix this issue for EF Core, but SmartEnum doesn't have a parameterless constructor base.

There was a commit to the SmartEnum library on Arpil 27th, 2018 that added a parameterless constructor so that this issue wouldn't exist, but that change was removed in a later commit and I am unsure how to proceed without it.

That commit can be found here: 870012d

And was removed in this commit:
1c9bf3e

Any help is greatly appreciated!

TryFrom* methods shouldn't throw ArgumentException

In current implementation TryFromName and TryFromValue throw an ArgumentException when value is an empty string or null. This behavior makes those methods pretty unusable as you have to write a lot of boilerplate code to handle all possible cases.
Also, this behavior differs from all other TryParse methods with a similar semantics

Static Property "List" always returns all derived SmartEnum of root class

Hello,
First Thanks for your work. Just a little issue with SmartEnum "inheritance".
If you add this test in SmartEnumList.cs, it won't pass :

        [Fact]
        public void ReturnsOnlyDerivedSmartEnums()
        {
            var result = DerivedTestEnumWithValues1.List;

            result.Should().BeEquivalentTo(DerivedTestEnumWithValues1.A, DerivedTestEnumWithValues1.B);
        }

Equality not useful when instantiated via EF6

I'm trying to store just the value of my SmartEnum in a database with EF6 using the technique in your great article here. It works fine and I was hoping to be able to use the handy new Equals implementation provided by @DeveTho in #22 on the resulting selected entities.

The trouble is that EF6 doesn't set the Name (only the Value) and Equals is testing both name and value resulting in always-false comparisons. Your EF Core configuration in the readme solves this, but I can't find a way to do a similar type conversion in EF6.

I can think of three potential solutions:

  1. Provide some magical type-converting EF6 configuration that I'm unaware of.
  2. Drop the Name equality test from Equals and only test Value. Of course, this would cause problems if you had more than one SmartEnum with the same value but I don't know why you'd want that anyway.
  3. Change the Value setter to also set the Name as suggested by commenter Naeem Sarfraz on your article I referenced above.
private int _value;
public int Value
{
    get { return _value; }
    private set
    {
        Name = FromValue(value).Name;
        _value = value;
    }
}

What do you think? Would you accept a PR for any of these?

SmartEnum comparison should only check the value

An 'enum' is a way to define an alias for a value. It allows multiples aliases for the same value but the aliases have to be unique. The following is perfectly valid code both for C# enums and SmartEnums:

public enum TestEnum
{ 
    First = 1,
    Second = 1,
    // Second = 2, // duplicate name not allowed
}

public sealed class TestSmartEnum : SmartEnum<TestSmartEnum, int>
{
    public static TestSmartEnum First = new TestSmartEnum(nameof(First), 1);
    public static TestSmartEnum Second = new TestSmartEnum(nameof(Second), 1);
    // public static TestSmartEnum Second = new TestSmartEnum(nameof(Second), 2); // duplicate name not allowed

    private TestSmartEnum(string name, int value) : base(name, value)
    {
    }
}

The issue with C# enums is that they allow values without aliases just by casting non-defined value to an enum. SmartEnums do not allow this because the constructor is private. Beside that, should all other behaviors be the same?

When comparing two enums with the same value

Console.WriteLine($"enum: {TestEnum.First == TestEnum.Second}");
Console.WriteLine($"SmartEnum: {TestSmartEnum.First == TestSmartEnum.Second}");

the results are different

enum: True
SmartEnum: False

This happens because SmartEnum.Equals() compares not only Value but also the Name.

Should they return the same?

This would also significantly improve performance as there won't be any more string comparisons.

public override int GetHashCode() => ReferenceEquals(null, Value) ? 0 : Value.GetHashCode(); 
public override bool Equals(object obj) => (obj is SmartEnum<TEnum, TValue> other) && Equals(other);

public bool Equals(SmartEnum<TEnum, TValue> other)
{
    if (other is null)
    {
        return false;
    }

    // If the objects share the same reference, return true
    if (ReferenceEquals(this, other))
    {
        return true;
    }

    // Return true if both name and value match
    return EqualityComparer<TValue>.Default.Equals(Value, other.Value);
}

The method SmartEnum.FromValue() should then change to allow the return of multiple names.

I'd like to know you opinion on this...

Can't use List or From methods before explicit use of enum value

Unless a call to one of the enum declarations (e.g. TestEnum.One) is made first, the List, FromName and FromValue methods do not always work correctly.

Having added the following tests to SmartEnumFromName

[Fact]
public void ReturnsEnumGivenNoExplicitPriorUse()
{
	string expected = "One";
	Assert.Equal(expected, TestEnum.FromName(expected).Name);
}

[Fact]
public void ReturnsEnumGivenExplicitPriorUse()
{
	string expected = TestEnum.One.Name;
	Assert.Equal(expected, TestEnum.FromName(expected).Name);
}

I found that they both pass when running all tests but when run individually, ReturnsEnumGivenNoExplicitPriorUse does not pass. Additionally I found that SmartEnumList.ReturnsAllDefinedSmartEnums also fails when run individually. Thus it seems that the forced invocation code is not working properly. I'll try to figure out why but in the meantime you may also want to take a look.

SmartEnumExtensions.IsSmartEnum returns false if abstract

Calling SmartEnumExtensions.IsSmartEnum for a SmartEnum<>-derived type returns false if the type is abstract. Is there a reason this method checks if the type is abstract and returns false? EmployeeType and ReservationStatus in the examples would return false for typeof(EmployeeType).IsSmartEnum() or typeof(ReservationStatus).IsSmartEnum() even though they are SmartEnums.

Am I misunderstanding the intended usage of the extension method?

Readme example doesn't compile.

dotnet new console -n SmartTest
cd SmartTest
dotnet add package Ardalis.SmartEnum --version 1.0.5
add TestEnum.cs pasted from readme.

public sealed class TestEnum : SmartEnum<TestEnum>
{
    public static readonly TestEnum One = new TestEnum(nameof(One), 1);
    public static readonly TestEnum Two = new TestEnum(nameof(Two), 2);
    public static readonly TestEnum Three = new TestEnum(nameof(Three), 3);

    private TestEnum(string name, int value) : base(name, value)
    {
    }
}

λ dotnet build
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

Restore completed in 56.65 ms for C:\git\temp\SmartTest\SmartTest.csproj.
TestEnum.cs(5,27): error CS0305: Using the generic type 'SmartEnum<TEnum, TValue>' requires 2 type arguments [C:\git\temp\SmartTest\SmartTest.csproj]

Build FAILED.

TestEnum.cs(5,27): error CS0305: Using the generic type 'SmartEnum<TEnum, TValue>' requires 2 type arguments [C:\git\temp\SmartTest\SmartTest.csproj]
0 Warning(s)
1 Error(s)


downloaded the repo and the tests run fine. So I am guessing what is in Nuget isn't what is in master??

SmartTest.zip

When methods do not appear in latest Nuget package

When referencing the Nuget package in .NET Framework 4.7.2 and .NET Core 3.1, the When methods of SmartEnum are not available. If I build from the repo and reference the dll directly, they are there.

SmartEnum with Swashbuckle doesn't show list of values like an enum

If you have a model on an API with a smart enum like this one and an enum

public TestEnum TestEnum { get; set; }
public TestSmartEnum SmartEnum { get; set; }
    public class TestSmartEnum: SmartEnum<TestSmartEnum, byte>
    {
        public static readonly TestSmartEnum One = new TestSmartEnum("One", 1);
        public static readonly TestSmartEnum Two = new TestSmartEnum("Two", 2);
        public static readonly TestSmartEnum Three = new TestSmartEnum("Three", 3);

        public TestSmartEnum(string name, byte value) : base(name, value)
        {
        }
    }

    public enum TestEnum
    {
        One,
        Two,
        Three
    }

// Configurates enums to show their names instead the values.
services.AddMvc()
.AddJsonOptions(options =>  options.SerializerSettings.Converters.Add(new StringEnumConverter()))

and you are using Swashbuckle to show your API, the smartEnum doesn´t show their values as if were an enum.

I've been able to create a schemaFilter and use it to show something thats shows information but It's not the same that with an enum.


    public class SmartEnumSchemaFilter : ISchemaFilter
    {
        public void Apply(OpenApiSchema schema, SchemaFilterContext context)
        {
            if (context.Type == typeof(TestSmartEnum))
            {
                var list = TestSmartEnum.List.Select(c => Tuple.Create<byte, string>(c.Value, c.Name)).ToList();

                schema.Example = new OpenApiString(string.Join(',', list));
                schema.Description = string.Join(',', list);              
            }
        }
    }
    services.AddSwaggerGen(swagger =>
    {
        swagger.SchemaFilter<SmartEnumSchemaFilter>();
    });

Behaviour without any schema filter:
imagen

imagen

imagen

Behaviour with schema filter:
imagen

imagen

imagen

SmartEnum Not Working with Entity Framework

Hello!

I am having an issue with SmartEnum and a class I'm trying to use EF Code First migrations with. My SmartEnum looks like this:

    public class DateTimeAccuracy : SmartEnum<DateTimeAccuracy, int>
    {
        public static DateTimeAccuracy DateTime = new DateTimeAccuracy(nameof(DateTime), 0);
        public static DateTimeAccuracy Date = new DateTimeAccuracy(nameof(Date), 1);
        etc...

        protected DateTimeAccuracy(string Name, int Value) : base(Name, Value)
        {
        }
    }

I'm using EF Core (2.1) and when attempting to add a migration, I get the following error:
System.InvalidOperationException: No suitable constructor found for entity type 'DateTimeAccuracy'. The following parameters could not be bound to properties of the entity: 'Name', 'Value'.

Ahhhh! There is no parameterless constructor for EF to work with! Looking at this page, I just need a blank constructor to make EF happy. I don't believe the SmartEnum library supports it "as is" but please correct me if I'm wrong. 😄

If it's not compatible, I believe I have a solution. If needed, should I do a pull request to master or bugfix1?
see here for my fix

Thanks!
Mikael

SmartEnum and Dapper

Hi,

I wrote a generic custom typehandler for SmartEnums and Dapper ORM. It will save the int value of the SmartEnum to the database and convert it back to the relevant SmartEnum when reading from the database.

using Ardalis.SmartEnum;
using Dapper;
using System;
using System.Data;

public class SmartEnumHandler<T> : SqlMapper.TypeHandler<T> where T : SmartEnum<T>
{
    public override T Parse(object value)
    {
        return SmartEnum<T, int>.FromValue(Convert.ToInt32(value));
    }

    public override void SetValue(IDbDataParameter parameter, T smartEnumeration)
    {
        parameter.Value = smartEnumeration.Value;
    }
}

I am using autofac to containerize my application, but cannot find the right syntax to register the SmartEnumHandler for all SmartEnums I created. Instead, in the container setup I register a handler for each SmartEnum individually, i.e:

        SqlMapper.AddTypeHandler(new SmartEnumHandler<Gender>());
        SqlMapper.AddTypeHandler(new SmartEnumHandler<YesNo>());

Any suggestion on how to register the TypeHandler for all SmartEnums in one line would be greatly appreciated.

Feel free to include the generic method in your nuget package if it is of any use to you.

Using VS2019 Community Edition, C#, .Net Core 3, Dapper -> latest NuGet.

Niels

MVC Dropdownlists and ViewModels

Hi All,

How would you use these in a view model to create a dropdown list for the possible choices in the enum?

Eg: If you take the readme example you would generate a dropdownlist of

  • One
  • Two
  • Three

which would bind to a viewmodel you then map back to your domain with the smart enum.

Cannot declare SmartEnum<T, string>

Prior to latest release I could declare a SmartEnum as the title says, w/o any errors.

I needed a mapping for some mimetypes, so I created the following enum:

public sealed class MimeType : SmartEnum<MimeType, string>
    {
        #region MyRegion

        public static readonly MimeType ai = new MimeType(nameof(ai), "application/postscript");
        public static readonly MimeType aif = new MimeType(nameof(aif), "audio/x-aiff");
        public static readonly MimeType aifc = new MimeType(nameof(aifc), "audio/x-aiff");
        public static readonly MimeType aiff = new MimeType(nameof(aiff), "audio/x-aiff");
        public static readonly MimeType asc = new MimeType(nameof(asc), "text/plain");
        public static readonly MimeType mid = new MimeType(nameof(mid), "audio/midi");
        public static readonly MimeType midi = new MimeType(nameof(midi), "audio/midi");
        public static readonly MimeType mif = new MimeType(nameof(mif), "application/vnd.mif");
        public static readonly MimeType mov = new MimeType(nameof(mov), "video/quicktime");
        public static readonly MimeType movie = new MimeType(nameof(movie), "video/x-sgi-movie");
        public static readonly MimeType mp2 = new MimeType(nameof(mp2), "audio/mpeg");
        public static readonly MimeType mp3 = new MimeType(nameof(mp3), "audio/mpeg");
        public static readonly MimeType mp4 = new MimeType(nameof(mp4), "video/mp4");
        public static readonly MimeType mpe = new MimeType(nameof(mpe), "video/mpeg");
        public static readonly MimeType mpeg = new MimeType(nameof(mpeg), "video/mpeg");
        public static readonly MimeType mpg = new MimeType(nameof(mpg), "video/mpeg");
        public static readonly MimeType mpga = new MimeType(nameof(mpga), "audio/mpeg");
        public static readonly MimeType ra = new MimeType(nameof(ra), "audio/x-pn-realaudio");
       
        #endregion

        private MimeType(string name, string value) : base(name, value)
        {

        }
    }

After the package update, I get the error:

Error CS0453
The type 'string' must be a non-nullable value type in order to use it as parameter 'TValue' in the generic type or method 'SmartEnum<TEnum, TValue>'

After searching by the error-code, apparently, there is a known and opened discussion about this issue here ?

Make Value updateable post initialization

I would like to do something like this:

    public class MessageHandler : SmartEnum<MessageHandler, Action<string>>
    {

        protected MessageHandler(string name, Action<string> value) : base(name, value)
        {
        }

        public void Update(Action<string> value)
        {
            this.Value = value;
        }

        public static MessageHandler ToBeDefinedLater =
            new MessageHandler(nameof(ToBeDefinedLater), _ => { });
    }

Which would be called like this:

MessageHandler.ToBeDefinedLater.Update(s => { "ugh" });

This would allow me to define MessageHandler in one project use it in another and inject the item I would like to work with from the parent project without having to share resources.

I'm not sure if this would cause any problems. But it would definitely make like simpler!

If you know of any workarounds that would be nice. I'm still working on figure it out.

I enjoy your podcast by-the-way. That is how I heard of your library!

Select Drop Downlist & Foreign key lookups

Hi, nice lib, can you tell me how to get a select list, 1) EnumToSelectDropDownList()

A function to populate dropdowns

public IEnumerable<SelectListItem> Creating_a_select_list(State selected)
{
    return State.GetAll().Select(
        x => new SelectListItem
        {
            Selected = x == selected,
            Text = x.Description,
            Value = x.Value.ToString()
        });
}

and 2) reverse lookup EF foreign-key fields in DB like here...

var enumToLookup = new EnumToLookup();
enumToLookup.NameFieldLength = 42; // optional, example of how to override default values
enumToLookup.Apply(context);

parameterless constructor

Commit #16's parameterless constructor is not in the source code anymore. Is there a reason for this?

In order to put the smartEnum into a EF Core Table, now I need to do the following for the constructor

// base("default", "default") should not be needed.  and it adds confusion for programmer I think.
protected BaseOptionType() : base("default", "default")  
     {
         IsRequired = false;
     }

EF Core Value Conversions does not work for me, because I do want to save both name and value into the database, so user can add record to it later as well.

related issues
#16
#15

Why not add to list within constructor?

Curious: Why was the type-member approach chosen over the simpler and more common approach of adding to the list during construction? This seems well researched, so I may be missing something, but the only benefit I can see is the ability to provide lazy initialization of the list; however this seems like a debatable optimization since any advanced usage (the reason for the approach) requires instantiation. It also would seem to have downsides, like relying on the implementer following a convention (fields vs properties, etc.). Thanks!

Use IDistributed Cache inside

If I wanted to use an IDistributedCache in a method how would I get this.
I just cant work out how to use this inside a method as I cant do a constructor injection of DI

Do SmartEnum classes work with AuthorizationFilterAttribute

I may be missing something but for some reason I was hoping that it would act like an enum or const string where
Permission.ManageRoles.Name ="ManageRoles" or ermission.ManageRoles.Value =4 but it does not. I get the following error when trying to add to a custom authorization attribute which works for our enums or const strings
[CustomAuthorize(Permission.ManageRoles.Name)]

An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

Any help would be much appreciated

Localization with SmartEnum

Hi Steve,

Thanks for such an amazing smart utility. I was wondering how can we use localization with SmartEnum? With a standard enum, you could add localization custom attribute and get the required information.

Thanks,
Attiqe

Value can not be string?

It's not possible to set string as value.
I think it is important to have that possibility.
@ardalis Your thoughts? Is there a plan to support that?
Thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.