71

In my view I have a enumdropdownlist (a new feature in Asp.Net MVC 5.1).

@Html.EnumDropDownListFor(m => m.SelectedLicense,new { @class="form-control"})

If I execute the above code I get dropdownlist for my following enum.

public enum LicenseTypes
{
    Trial = 0,
    Paid = 1
}

but by default I want my dropdownlist to have a value(custom text) and this is what I tried

@Html.EnumDropDownListFor(m => m.SelectedLicense,"Select a license" ,new { @class="form-control"})

but now the problem is when i run it, my dropdownlist looks like this enter image description here So, the default text I want to show doesn't appear by default. If a user selects "select a license" and tries to submit the form, it does show an error saying "select a license" but it doesn't show as default text. Something i need to change?

Ps: The image is the screenshot of the page when it loads. By default it'll show Trial as selected option.

RaelB
  • 3,301
  • 5
  • 35
  • 55
Cybercop
  • 8,475
  • 21
  • 75
  • 135

5 Answers5

90

Try to change the Index of LicenseTypes start from 1 not 0 like below:

public enum LicenseTypes
{
    Trial = 1,
    Paid = 2
}

Then you can use Range attribute to validate the selected license type like below:

public class YourViewModel
{
     //Other properties
     [Range(1,int.MaxValue,ErrorMessage = "Select a correct license")]
     public LicenseTypes LicenseTypes { get; set; }
}

Finally, in your view:

   @Html.EnumDropDownListFor(m => m.LicenseTypes,"Select a license",new { @class = "form-control"})
   @Html.ValidationMessageFor(m => m.LicenseTypes)
Lin
  • 15,078
  • 4
  • 47
  • 49
  • that works but, then it will add `Select a license` as one of the enum value with id 0. I want to be able to show error message when user doesn't select one of the values from enum. – Cybercop Feb 19 '14 at 13:55
  • could do that but i'll leave it unaccepted for some time and see if someone can come with better solution, till then +1 for ur idea. – Cybercop Feb 19 '14 at 14:07
  • 3
    @probackpacker has the better answer, this option will not get caught in the validation handling – Dan Aug 14 '14 at 18:48
75

By the time your EnumDropDownListFor is rendered SelectedLicense already has the default value for the type, which is 0.

Just change the type of your SelectedLicense property to a nullable enum, like so:

public LicenseTypes? SelectedLicense { get; set; }

This also allows you to continue using the Required attribute, which I think is significantly cleaner. The Required attribute will not allow a null response, so even though your model allows nulls, the form will not.

Brad Lawrence
  • 1,483
  • 1
  • 11
  • 11
Michael Richardson
  • 4,213
  • 2
  • 31
  • 48
  • 1
    I think this is the best answer for today, but is seems like a hack. The real fix should be within MVC to handle non-nullable Enums properly. – jhilden Jul 28 '14 at 15:45
  • 4
    @jhilden, I think that MVC _is_ handling the non-nullable Enum properly by doing _exactly_ what you are telling it to do. In .NET [enum variables are always initialized to the value 0](http://stackoverflow.com/a/529937/1812515). Let's suppose that you left the property type as a non-nullable Enum (`LicenseTypes`) and the form is posted with "Select a License" (the list item with no value) selected. Your `SelectedLicense` property would end up with the default value (`Trial`). If you just examined the model it would be indistinguishable from the user actually selecting the `Trial` value. – Michael Richardson Jul 28 '14 at 16:06
  • you are correct. I looked back at my other code and if we want an int to be required but have no default value then we do: [required] public int? NullableIntHere {get;set;} – jhilden Jul 28 '14 at 18:20
16

I have an enum:

public enum Sex
{
    Male,
    Female
}

In my model I have:

    [DisplayName("Sex")]
    [Required]
    public Sex? Sex { get; set; }

An in the view:

    @Html.EnumDropDownListFor(model => model.Sex, "Select sex", new { @class = "form-control", type = "text"})

By this I have a dropdown with default option "Select sex", but validation accepts only options provided by enum ("Male" and "Female").

In MVC3 (without EnumDropDownListFor) I used in model:

    [DisplayName("Sex")]
    [Required(AllowEmptyStrings=false)]
    public Sex? Sex { get; set; }

    Sex = null;

    Sexes = Repository.GetAutoSelectList<Sex>("");

In view:

    @Html.DropDownListFor(model => model.Sex, Model.Sexes, new { @class = "form-control", type = "text" })
rsobon
  • 1,012
  • 2
  • 12
  • 26
11

The ViewModel class needs to have the default value set on the enum property for it to be the default selected public

public class Test
    {
        public Cars MyCars { get; set; }
        public enum Cars
        {
            [Display(Name = @"Car #1")]
            Car1 = 1,
            [Display(Name = @"Car #2")]
            Car2 = 2,
            [Display(Name = @"Car #3")]
            Car3 = 3
        }

    }

Controller:

 public class EnumController : Controller
    {
        // GET: Enum
        public ActionResult Index()
        {
            var model = new Test {MyCars = Test.Cars.Car3}; // set default value
            return View(model);
        }
        [HttpPost]
        public ActionResult Index(Test model)
        {
            .....
        }
    }

View:

@Html.BeginForm()
{
<div class="panel bg-white">
    <div class="panel-header fg-white">
        Enums
    </div>
    <div class="panel-content">
        <div class="input-control select size3">
            @Html.EnumDropDownListFor(model => model.MyCars)

        </div>
    </div>
    <input type="submit" class="button success large" />
</div>
}
Haroon
  • 1,052
  • 13
  • 28
0

Am I a bit late ?

Changing the values of the enum type is not very satisfying.

Neither is changing the model property to render it nullable and then add a [Required] attribute to prevent it to be nullable.

I propose to use the ViewBag to set the default selected value of the dropdown. The line 4 of the controller just bellow is the only important one.

EDIT : Ah... newbies... My first idea was to use ModelState.SetModelValue because my newbie instinct prevented me to simply try to set the desired value in the ViewBag since the dropdown was binded to the model. I was sure to have a problem: it would bind to the model's property, not to the ViewBag's property. I was all wrong: ViewBag is OK. I corrected the code.

Here is an example.

Model:

namespace WebApplication1.Models {

    public enum GoodMusic {
        Metal,
        HeavyMetal,
        PowerMetal,
        BlackMetal,
        ThashMetal,
        DeathMetal // . . .
    }

    public class Fan {
        [Required(ErrorMessage = "Don't be shy!")]
        public String Name { get; set; }
        [Required(ErrorMessage = "There's enough good music here for you to chose!")]
        public GoodMusic FavouriteMusic { get; set; }
    }
}

Controller:

namespace WebApplication1.Controllers {
    public class FanController : Controller {
        public ActionResult Index() {
            ViewBag.FavouriteMusic = string.Empty;
            //ModelState.SetModelValue( "FavouriteMusic", new ValueProviderResult( string.Empty, string.Empty, System.Globalization.CultureInfo.InvariantCulture ) );
            return View( "Index" );
        }
        [HttpPost, ActionName( "Index" )]
        public ActionResult Register( Models.Fan newFan ) {
            if( !ModelState.IsValid )
                return View( "Index" );
            ModelState.Clear();
            ViewBag.Message = "OK - You may register another fan";
            return Index();
        }
    }
}

View:

@model WebApplication1.Models.Fan
<h2>Hello, fan</h2>
@using( Html.BeginForm() ) {
    <p>@Html.LabelFor( m => m.Name )</p>
    <p>@Html.EditorFor( m => m.Name ) @Html.ValidationMessageFor( m => m.Name )</p>
    <p>@Html.LabelFor( m => m.FavouriteMusic )</p>
    <p>@Html.EnumDropDownListFor( m => m.FavouriteMusic, "Chose your favorite music from here..." ) @Html.ValidationMessageFor( m => m.FavouriteMusic )</p>
    <input type="submit" value="Register" />
    @ViewBag.Message
}

Without the "ModelState.SetModelValue or ViewBag.FavouriteMusic = string.Empty" line in the model Index action the default selected value would be "Metal" and not "Select your music..."

Rikou
  • 281
  • 3
  • 9
  • @shim: It works with partial view called from the main view, but not in partial view called in AJAX. Partial views get a copy of the main view ViewBag. You do execute the Index action when a partial view is called by the main view. You do not when an AJAX request invokes a partial view. Index.cshtml: – Rikou Feb 13 '17 at 17:21