I am submiting a blank form and all of the other fields display their correct validation error message stating that the field cannot be empty, except for one.
Here is the model:
public enum Medium
{
[Description("Teleconference & Report")]
Teleconference_Report,
[Description("Email & Telephone")]
Email_Telephone
}
[Required]
[Display(Name = "Medium")]
public Medium Medium { get; set; }
Here is the field in the form:
<div class="form-group">
#Html.LabelFor(model => model.Medium, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.DropDownList("MediumID", null, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Medium, "", new { #class = "text-danger" })
</div>
</div>
The "MediumID" DropDownList is populated using a viewbag which is set to whatever the following returns:
// Puts all of the mediums of communication into a user friendly dropdownlist.
public List<SelectListItem> GetMediumList()
{
List<SelectListItem> mediumList = new List<SelectListItem>();
foreach (Medium state in EnumToList<Medium>())
{
mediumList.Add(new SelectListItem
{
Text = GetEnumDescription(state),
Value = state.ToString(),
});
}
return mediumList;
}
The issue is, is that when the default display of the drop down list of 'please select...' is left, this is accepted and the form gets submitted, which doesn't happen for any of my other fields. When looking at the new entry in the database, the 'Medium' field gets set to the first thing in the DropDownList which is 'Teleconference & Report'.
Any help is greatly appreciated.
EDIT:
Below shows the form section for another enum called 'Frequency', but these are not changed to user friendly strings.
<div class="form-group">
#Html.LabelFor(model => model.Frequency, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.EnumDropDownListFor(model => model.Frequency, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Frequency, "", new { #class = "text-danger" })
</div>
</div>
Below here, shows the two methods which turn the enums into user friendly strings:
// Returns a 'user friendly', readable version of the enum.
public static string GetEnumDescription(Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
// Puts all of the same enums into a list.
public static IEnumerable<T> EnumToList<T>()
{
Type enumType = typeof(T);
// Can't use generic type constraints on value types,
// so have to do check like this.
if (enumType.BaseType != typeof(Enum))
throw new ArgumentException("T must be of type System.Enum");
Array enumValArray = Enum.GetValues(enumType);
List<T> enumValList = new List<T>(enumValArray.Length);
foreach (int val in enumValArray)
{
enumValList.Add((T)Enum.Parse(enumType, val.ToString()));
}
return enumValList;
}
Add the EnumDataType attribute to the property on your view model:
[EnumDataType(typeof(Medium), ErrorMessage = "Submitted value is not valid for the enum")]
public Medium Medium { get; set; }
You should add this anyway, because an Enum can represent any numeric value and won't be validated automatically by the MVC framework.
I hope this helps!
Related
How can I remove a comma from the value when posting back to SQL? My SQL field is decimal but sometimes a value in the user will enter 10,000 and not 10000 so the post fails. So the strange this is I get an error message:
System.InvalidOperationException: 'The ViewData item that has the key 'TypeOf' is of type 'System.String' but must be of type 'IEnumerable'.'
Which happens with a comma is put in Amount - without a comma in amount all is fine?
Thanks,
EB
<div class="form-group">
#Html.LabelFor(model => model.TypeOf, htmlAttributes: new { #class = "required control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.TypeOf, (IEnumerable<SelectListItem>)ViewBag.TypesOfLoan, null, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TypeOf, "", new { #class = "text-danger" })
</div>
</div>
View:
#Html.(model => model.Amount, new { htmlAttributes = new { #class = "form-control", #style = "width:110px" } })
CS:
public Nullable<decimal> Amount { get; set; }
Controller:
public ActionResult CreateOrder(Order newOrder)
{
if (ModelState.IsValid)
{
db.Orders.Add(newOrder);
db.SaveChanges();
return RedirectToAction("Orders");
}
else
{
return View(newOrder);
}
}
If the user can add commas then it is no longer a decimal, which means your field is a text or varchar.
NOTE You can stop the user from entering the comma or any other funny character at all
#Html.TextBoxFor(model => model.Amount, new {#type="number"})
One of the things you can do is to use string replace and the parse to double
To replace the comma use
var amount = amount_input.Replace(',', '');
to parse to double use
var amount_as_double = double.Parse(amount, CultureInfo.InvariantCulture)
EDIT
looking at your question, I would not know if you are using #Html.TextBoxFor or and also no know how you are positing the form to the controller.
if your controller method looks like this
[HttpPost]
public ActionResult Index(Model model)
{
//let's say no1 is decimal, model.no1 is going to be 0 if the user enters a comma and string.Replace is not going to work
var amount = model.no1;
...
}
but of you are using something like
[HttpPost]
public ActionResult Index(FormCollection collection)
{
var amount = collection["Amount"].Replace(',', '')
...
}
I have the following in my controller:
public ActionResult Create()
{
ViewBag.PlayerId = new SelectList(db.Players, "Id", "Name");
return View();
}
This is in the view:
<div class="form-group">
#Html.LabelFor(model => model.PlayerId, "PlayerId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("PlayerId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.PlayerId, "", new { #class = "text-danger" })
</div>
</div>
But when I submit the form, it gives me the below error:
System.InvalidOperationException: 'The ViewData item that has the key 'PlayerId' is of type 'System.Int32' but must be of type 'IEnumerable'.'
I googled a lot but were not able to find the solution. Your help is highly appreciated.
Write your #Html.DropDownList as follows:
#Html.DropDownList("PlayerId", ViewBag.PlayerId as SelectList,"Select Player", htmlAttributes: new { #class = "form-control" })
Now it will work!
You have to pass the SelectList into dropdown but actually the model binder is confused between PlayerId as viewmodel property and PlayerId as ViewBag property, hence causing the error.
Better to create a viewmodel property which will store option list with different name:
public class ViewModel
{
public int PlayerId { get; set; }
// other properties
// option list here
public List<SelectListItem> PlayerList { get; set; }
}
Then add the option lists from database into controller action:
public ActionResult Create()
{
var model = new ViewModel();
model.PlayerList = db.Players.Select(x => new SelectListItem { Text = x.Name, Value = x.Id }).ToList();
return View(model);
}
And use strongly-typed helper to bind it afterwards:
#Html.DropDownListFor(model => model.PlayerId, Model.PlayerList, "Select", new { #class = "form-control" })
Related issue:
The ViewData item that has the key is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
I have been trying to find a solution to a situation that I'm busy designing, however I have not managed to get to it.
Imagine having the following model
public enum InputType
{
TextInput,
LookupInput
}
public struct AdditionalProperty
{
public string Key {get;set;}
public string Value {get;set;}
public InputType Type {get;set;}
}
public class Person
{
public string FirstName {get;set;}
public List<AdditionalProperty> AdditionalProperties {get;set;}
}
Then, having the following controller
public class HomeController
{
public ActionResult Index()
{
var model = new Person { FirstName = "MyName" };
model.AdditionalProperties = new List<AdditionalProperty>();
var listItem = new AdditionalProperty
{
Key = "Surname",
Value = "MySurname"
};
model.AdditionalProperties.Add(listItem);
return View(model)
}
}
What I'm looking for is the Razor view code on how to "dynamically" create the properties with the correct input type, bound to something in order for me to be able to still use the model when the form gets posted back to the controller for a Save function.
So the property that is known, would be something like this:
<div class="form-group">
<div class="form-row">
<div class="col-md-6">
#Html.LabelFor(model => model.FirstName, new { #class = "control-label" })
<div>
#Html.TextBoxFor(model => model.FirstName, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
</div>
</div>
The Idea would then be to have the following. Obviously the below isn't sufficient, and this is where I need the help.
I would like to show the additional properties, one below the other, each on a separate line (using bootstrap row) based on the property.InputType
#foreach (var property in Model.Properties)
{
#Html.LabelFor(model => property.Key, new { #class = "control-label" })
<div>
#if (property.InputType == TextInput)
{
#Html.TextBoxFor(model => property.Value, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
}
#Html.ValidationMessageFor(model => property.Key, "", new { #class = "text-danger" })
</div>
}
Thus, I would like to see my view as:
| <label> | <input>
Known Property | FirstName | MyFirstName
Unknown Property | Surname | MySurname
In terms of completeness, I am posting the following answer.
I am going to post the Model, View (Index & EditorTemplates) & Controller to show the complete working solution that I used to test the answer that was given to me.
My Model class for this test
Person.cs
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<AdditionalProperty> AdditionalProperties { get; set; }
}
AdditionalProperty.cs
public struct AdditionalProperty
{
public string Key { get; set; }
public object Value { get; set; }
public DateTime? DateValue
{
get
{
DateTime dateValue;
if (DateTime.TryParse(Value?.ToString(), out dateValue))
{
return dateValue;
}
return null;
}
set => Value = value;
}
public InputType InputType { get; set; }
public List<SelectListItem> ValueLookupItems { get; set; }
}
The reason I have a separate DateValue property here is to assist the browser when doing DateTime binding otherwise the DateTimePicker doesn't show.
I used an enum to determine what type of input type this specific property should make use of.
InputType.cs
public enum InputType
{
TextBox,
DropdownBox,
TextArea,
DateSelection,
}
In order to keep the views as simple as possible, Stephen provided me with a sample for the Index View as well as an EditorTemplate for the AdditionalProperty object. The EditorTemplate is used for separation of concerns and to ensure that all the logic behind what input type is being used is in one place.
I have found that the DateTime property doesn't work well, so an additional EditorTemplate was required. I got this from this post.
DateTime.cshtml
Note: Location of template -> /Views/Shared/EditorTemplates
#model DateTime
#{
IDictionary<string, object> htmlAttributes;
object objAttributes;
if (ViewData.TryGetValue("htmlAttributes", out objAttributes))
{
htmlAttributes = objAttributes as IDictionary<string, object> ?? HtmlHelper.AnonymousObjectToHtmlAttributes(objAttributes);
}
else
{
htmlAttributes = new RouteValueDictionary();
}
htmlAttributes.Add("type", "date");
String format = (Request.UserAgent != null && Request.UserAgent.Contains("Chrome")) ? "{0:yyyy-MM-dd}" : "{0:d}";
#Html.TextBox("", Model, format, htmlAttributes)
}
AdditionalProperty.cshtml
Note: Location of template -> /Views/Shared/EditorTemplates
Note: The location of my AdditionalProperty formed part of the DynamicViewExample.Models namespace
#model DynamicViewExample.Models.AdditionalProperty
<div>
#Html.HiddenFor(m => m.Key)
#Html.LabelFor(m => m.Key, Model.Key, new {#class = "control-label"})
#if (Model.InputType == DynamicViewExample.Models.InputType.TextBox)
{
#Html.TextBoxFor(m => m.Value, new {#class = "form-control"})
}
else if (Model.InputType == DynamicViewExample.Models.InputType.TextArea)
{
#Html.TextAreaFor(m => m.Value, new {#class = "form-control"})
}
else if (Model.InputType == DynamicViewExample.Models.InputType.DropdownBox)
{
#Html.DropDownListFor(m => m.Value, Model.ValueLookupItems, new {#class = "form-control"})
}
else if (Model.InputType == DynamicViewExample.Models.InputType.DateSelection)
{
#Html.EditorFor(m => m.DateValue, new {#class = "form-control"})
}
else
{
#Html.HiddenFor(m => m.Value) // we need this just in case
}
</div
This would be how the Index.cshtml file would look
#model DynamicViewExample.Models.Person
#{
ViewBag.Title = "Home Page";
}
#using (Html.BeginForm())
{
<div class="row">
#Html.LabelFor(model => model.FirstName, new { #class = "control-label" })
#Html.TextBoxFor(model => model.FirstName, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
<div class="row">
#Html.LabelFor(model => model.LastName, new { #class = "control-label" })
#Html.TextBoxFor(model => model.LastName, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
#Html.ValidationMessageFor(model => model.LastName, "", new { #class = "text-danger" })
</div>
<div class="row">
#Html.EditorFor(m => m.AdditionalProperties, new { htmlAttributes = new { #class = "form-control"}})
</div>
<input type="submit" class="btn btn-primary" />
}
And then finally, the HomeController.cs file contains a Get and Post that allows the ability to manipulate the data as you please. What is missing here is the "dynamic" way of populating the model, but that will naturally happen once a DB has been introduced into the mix.
[HttpGet]
public ActionResult Index()
{
var model = new Person
{
FirstName = "Gawie",
LastName = "Schneider",
AdditionalProperties = new List<AdditionalProperty>
{
new AdditionalProperty {Key = "Identification Number", Value = "1234567890123456", InputType = InputType.TextBox},
new AdditionalProperty {Key = "Date Of Birth", Value = DateTime.Today, InputType = InputType.DateSelection},
new AdditionalProperty {Key = "Age", Value = "31", InputType = InputType.TextBox},
new AdditionalProperty {Key = "Gender", Value = "Male", InputType = InputType.DropdownBox,
ValueLookupItems = new List<SelectListItem>
{
new SelectListItem{Text = "Male", Value = "Male"},
new SelectListItem{Text = "Female", Value = "Female"}
}},
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(Person model)
{
//Do some stuff here with the model like writing it to a DB perhaps
return RedirectToAction("Index");
}
So if I would have to sum up what I was trying to do here.
The goal I wanted to achieve was to be able to make use of Strongly Typed / Known Properties in conjunction with Dynamic / Unknown Properties to create a system that would allow the user to create new inputs on the fly without the need for a developer to be involved.
I honestly hope that this might help someone else as well some day.
Enjoy the coding experience
Gawie
I've been reading several articles about how to present choices to users. Some use ListBoxFor, some use CheckBoxFor, and then there is this thing called MultiSelectList.
What I am (was) confused about was that each example seemed to have done it a totally different way, and none of them actually used the built in "SelectListItem" class but instead always created their own.
So I originally was going to post a question asking for general clarification, but I thought it would just be representative of all the other various post and repetitive.
So let me re-phrase: How do you use a "List" or a "MultiSelectList" to present a user a list of choices, including the option for them to be displayed as a list of Check boxes?
In other words, if I have the following 2 items in my Model, how would I display each of them as a typical choice list box, or a typical Check List box?
public List<SelectListItem> Widgets1 { get; set; }
public MultiSelectList Widgets2 { get; set; }
Warning... just wanted to point out that the "CheckBox" option basically hangs once you get too many choices. (e.g. changed my loop to 500) and it basically won't submit.
the problem is traced back to the validation of the CheckBoxFor line. This can be fixed by changing the one line to...
#Html.CheckBoxFor(cc => cc.WidgetsAsCheckList[myIndex].Selected, new { data_val = "false", htmlAttributes = new { #class = "form-control" } })
If I do this, I can have 1,500 items in the check list and the submit occurs in under 3 seconds
So let's start with the Model. It is really basic and you can see that I'm just creating 3 Lists that will store the same data.
The primary difference here is that all of the examples I read, people where creating their own Item classes, where I wanted to simply use the built in "SelectListItem" class
public class FooModel
{
[Display(Name = "WidgetCheckList")]
public List<SelectListItem> WidgetsAsCheckList { get; set; }
[Display(Name = "WidgetListBox")]
public List<SelectListItem> WidgetsAsListBox { get; set; }
[Display(Name = "WidgetMultiSelectList")]
public MultiSelectList WidgetMultiSelectList { get; set; }
//We have to create a bucket that not only some how
//auto-magically knowns what has been pre-selected in the
//original list, but provides the view something to store
//the new selections in when returning to the controller.
//I have to admit, I have no idea how this knows what was
//pre-selected, but being new at MVC, there are things I
//just have to leave it as a mystery becuase it just works.
[HiddenInput(DisplayValue = false)]
public List<string> userSelectionsAsListBox { get; set; }
[HiddenInput(DisplayValue = false)]
public List<string> userSelectionsAsMultiSelectList { get; set; }
public FooModel()
{
this.WidgetsAsCheckList = new List<SelectListItem>();
this.WidgetsAsListBox = new List<SelectListItem>();
this.WidgetMultiSelectList = new MultiSelectList(new List<SelectListItem>());
}
}
For the Controller, because this was a learning test, I just made up the data. The key here is that I build a List of SelectListItems and then used that same list to populate all 3 demo fields of the Model to show 3 different ways to work with the same data.
// ------------------------------------------------------------------------
[HttpGet] //Display the Edit view
public ActionResult Edit()
{
FooModel myModel = new FooModel;
//For testing, here I'm going to Inject some Choices
//So first we build a list of them
List<SelectListItem> myChoices = new List<SelectListItem>();
for (Int32 myIndex = 1; myIndex < 15; myIndex++)
{
SelectListItem myChoice = new SelectListItem();
myChoice.Value = myIndex.ToString();
myChoice.Text = "Choice " + myIndex.ToString();
if ((myIndex % 2) == 0)
{
myChoice.Selected = true;
}
else
{
myChoice.Selected = false;
}
myChoices.Add(myChoice);
}
String[] mySelections = myChoices.Where(x => x.Selected == true).ToArray().Select(x => x.Value).ToArray();
//Now we use that same list to populate all 3 variations in our model
myModel.WidgetsAsCheckList.AddRange(myChoices);
myModel.WidgetsAsListBox.AddRange(myChoices);
myModel.WidgetMultiSelectList = new MultiSelectList(myChoices, "Value", "Text", mySelections);
return View(myModel);
}
Now for the view, I display each list. The first of course is a check box and the second and third are list boxes but use different underlying objects...
#* This displays the "list" of SelectListItems as Checkboxes but we have to do alot more work *#
<div class="form-group">
#Html.LabelFor(model => model.WidgetsAsCheckList, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="form-control" style="overflow-y: scroll; height: 25em; width:280px;">
#for (var myIndex = 0; myIndex < Model.WidgetsAsCheckList.Count; myIndex++)
{
#Html.CheckBoxFor(cc => cc.WidgetsAsCheckList[myIndex].Selected, new { htmlAttributes = new { #class = "form-control" } })
#Html.HiddenFor(cc => cc.WidgetsAsCheckList[myIndex].Value, new { htmlAttributes = new { #class = "form-control" } })
#Html.DisplayFor(cc => cc.WidgetsAsCheckList[myIndex].Text, new { htmlAttributes = new { #class = "form-control" } })
<br />
}
</div>
</div>
</div>
#* This displays the "list" of SelectListItems as list box that does all the work for us *#
<div class="form-group">
#Html.LabelFor(model => model.WidgetsAsListBox, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBoxFor(model => model.userSelectionsAsListBox, Model.WidgetsAsListBox, new { #class = "form-control", size = 25 })
</div>
</div>
#* This displays the "MultiSelectList" as list box that does all the work for us *#
<div class="form-group">
#Html.LabelFor(model => model.WidgetMultiSelectList, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBoxFor(model => model.userSelectionsAsMultiSelectList, Model.WidgetMultiSelectList, new { #class = "form-control", size = 25 })
</div>
</div>
And finally, when the user makes their own selections (or takes the pre-selected ones) and hits submit, we can get the results in the the controller simply by...
// ---------------------------------------------------------------------
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FooModel myFooModel)
{
List<string> SelectedItemsFromListBox = myFooModel.userSelectionsAsListBox;
List<string> SelectedItemsFromMultiSelectList = myFooModel.userSelectionsAsMultiSelectList;
List<string> SelectedItemsFromCheckList = myFooModel.WidgetsAsCheckList.Where(x => x.Selected == true).ToList().Select(x => x.Value).ToList();
}
I am filling out a form, however when selecting an option from the drop down list and click submit, no matter what option I select, it always parses the top one through. The displayed value never changes, so it you leave it as the default option 'please select...' and click submit, this stays as 'please select...' but the entry in the database is always the one that appears at the top of the drop down.
Here is the model:
public enum Medium
{
[Description("Teleconference & Report")]
Teleconference_Report,
[Description("Email & Telephone")]
Email_Telephone
}
[Required]
[Display(Name = "Medium")]
public Medium Medium { get; set; }
Here is the field in the form:
<div class="form-group">
#Html.LabelFor(model => model.Medium, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.DropDownList("MediumID", null, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Medium, "", new { #class = "text-danger" })
</div>
</div>
The "MediumID" DropDownList is populated using a viewbag which is set to whatever the following returns:
// Puts all of the mediums of communication into a user friendly dropdownlist.
public List<SelectListItem> GetMediumList()
{
List<SelectListItem> mediumList = new List<SelectListItem>();
foreach (Medium state in EnumToList<Medium>())
{
mediumList.Add(new SelectListItem
{
Text = GetEnumDescription(state),
Value = state.ToString(),
});
}
return mediumList;
}
Below shows the form section for another enum called 'Frequency', but these are not changed to user friendly strings (and is working fine).
<div class="form-group">
#Html.LabelFor(model => model.Frequency, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.EnumDropDownListFor(model => model.Frequency, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Frequency, "", new { #class = "text-danger" })
</div>
</div>
Below here, shows the two methods which turn the enums into user friendly strings:
// Returns a 'user friendly', readable version of the enum.
public static string GetEnumDescription(Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
// Puts all of the same enums into a list.
public static IEnumerable<T> EnumToList<T>()
{
Type enumType = typeof(T);
// Can't use generic type constraints on value types,
// so have to do check like this.
if (enumType.BaseType != typeof(Enum))
throw new ArgumentException("T must be of type System.Enum");
Array enumValArray = Enum.GetValues(enumType);
List<T> enumValList = new List<T>(enumValArray.Length);
foreach (int val in enumValArray)
{
enumValList.Add((T)Enum.Parse(enumType, val.ToString()));
}
return enumValList;
}
Finally, here is the method signature where the fields are binded/bound:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Point,ApplicationID,MediumID,Frequency,StartDate,EndDate")] TouchPoint touchPoint)
Within this method, the dropdown is passed to the view using the following:
ViewBag.MediumID = GetMediumList();
Any help is greatly appreciated.
Your model has a property named Medium but your view does not bind to that property. The name of the <select> your generating is MediumID which does not exist in your model, so the default value for Medium when you submit will Teleconference_Report (the first enum value).
Change the view to
#Html.DropDownListFor(m => m.Medium, (IEnumerable<SelectListItem>)ViewBag.MediumID, "Please select...", new { #class = "form-control" })
although I would recommend changing the ViewBag property name to say MediumList to make it more obvious that its a collection. And even better, use a view model with a property public IEnumerable<SelectListItem> MediumList { get; set; } so that the viewcan be #Html.DropDownListFor(m => m.Medium, Model.MediumList, .... ).
You also need to change the [Bind] attribute to include "Medium" (and remove "MediumID") although using a view model means the [Bind] attribute is not required.
Side note: You do not need the [Required] attribute unless you want to add a specific error message using the ErrorMessage = "..." property (an enum is always required by default unless you make the property nullable).