Entity set to null after selected item from SelectList - c#

When creating Employee entity you are supposed to select MeetingCenterfrom DropDownList. All MeetingCenters show just fine in DropDownList with their Names, but when some of them is selected and Employee is created Meeting Center is null. Im using NoSQL DocumentDB database.
Controller:
[ActionName("Create")]
public async Task<IActionResult> CreateAsync()
{
ViewBag.MeetingCentersList = await _meetingCenterReposiotry.GetItemsAsync();
return View();
}
Create View:
#model Employee
#using (Html.BeginForm("Create", "Home", FormMethod.Post, new { #class = "form-horizontal" }))
{
...
<div class="form-group">
#Html.LabelFor(MeetingCenter => Model.MeetingCenter, new { #class = "control-label" })
#Html.DropDownListFor(MeetingCenter => Model.MeetingCenter, new SelectList(ViewBag.MeetingCentersList, "MeetingCenterId", "Name"), new { #class = "form-control" })
</div>
...
}
Piece of Employee Model
public class Employee
{
...
[JsonProperty(PropertyName = "id")]
public string EmployeeId { get; set; }
[JsonProperty(PropertyName = "meetingCenter")]
public MeetingCenter MeetingCenter { get; set; }
...
}
Piece of MeetingCenter Model
public class MeetingCenter
{
...
[JsonProperty(PropertyName = "id")]
public string MeetingCenterId { get; set; }
...
}

With your current code, the DropDownListFor helper will render a SELECT element with options, which has the MeetingCenterId as the value attribute and the Name as the Text. The SELECT element's name attribute value will be MeetingCenter. So when the form is submitted the form data will look like this
MeetingCenter: 2
Assuming user selected the option with value "2".
But the MeetingCenter property of your view model(Employee) is not a numeric type, it is a complex type(MeetingCenter). So model binder cannot map this value to MeetingCenter property of your view model.
You can render the SELECT element with the name MeetingCenter.MeetingCenterId and then model binder will be able to map the posted form data as the input element name matches with the naming-structure of your view model.
So you should render something like this inside your form.
<select name="MeetingCenter.MeetingCenterId">
</select>
You can generate the above markup by using the SELECT tag helper and specifying MeetingCenter.MeetingCenterId as the asp-for property.
<select asp-for="MeetingCenter.MeetingCenterId"
asp-items="#(new SelectList(ViewBag.MeetingCentersList,
"MeetingCenterId", "Title"))">
</select>
Now when the form is submitted, it will populate the MeetingCenter property of your view model and it's MeetingCenterId property.
If you want the full MeetingCenter property to be populated (properties other than MeetingCenterId, get the full object by querying the data provided by _meetingCenterReposiotry.GetItemsAsync() using the MeetingCenterId available to you in the HttpPost action. Something like this
var id = viewModel.MeetingCenter.MeetingCenterId;
var items = await _meetingCenterReposiotry.GetItemsAsync();
var item = items.FirstOrDefault(a=>a.MeetingCenterId==id);
// User item now
// May be do somethig like : viewModel.MeetingCenter = item;
I also suggest you to use the correct types. If MeetingCenterId is numeric value, use int as type instead of string

Related

ASP drop down select showing up weird

I've been trying to create a drop down list with asp tag helpers and for some reason they always look strange. I'll include my code for Controller, View, and view model. Also the picture of how it looks.
Controller:
public async Task<IActionResult> Create()
{
var dalias = await _context.DeptAliases
.Select(m => new SelectListItem { Text = m.Alias, Value = m.DeptAliasPkey.ToString() }).Distinct()
.ToListAsync();
var vm = new CopyCenterCreateViewModel()
{
DAlias = dalias,
};
return View(vm);
}
View:
#model PrintShop.ViewModels.CopyCenterCreateViewModel
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label asp-for="DAlias" class="control-label"></label>
<select asp-for="DAlias" asp-items="Model.DAlias" class="form-control"></select>
<span asp-validation-for="DAlias" class="text-danger"></span>
</form>
ViewModel:
public class CopyCenterCreateViewModel
{
public List<SelectListItem> DAlias { get; set; }
}
Result:
It looks like the select control is rendering in multi-selection mode. If the tag helper asp-for is binding to an IEnumerable then the select element is rendered in multiple mode.
From tag-helpers multiple select section of the docs:
The Select Tag Helper will automatically generate the multiple = "multiple" attribute if the property specified in the asp-for
attribute is an IEnumerable.
You only have a single property in the ViewModel which is a List, and you're binding the items to the same property as the selected value.
You could rename the list of items to something else and add a property to bind to the value of the selected item:
public class CopyCenterCreateViewModel
{
public string DAlias { get; set; }
public List<SelectListItem> Items { get; set; }
}
In the controller you'd populate the Items property of the CopyCenterCreateViewModel.
In the view bind asp-items to the view model Items property:
<select asp-for="DAlias" asp-items="Model.Items" class="form-control"></select>

Razor bind radiobox to list of SelectListItem

I have a basic model
public class SpeakerConsent
{
public string FieldLabel { get; set; }
public List<SelectListItem> OptionValues { get; set; }
}
My razor page currently looks like the following
#for (var i = 0; i < Model.OptionValues.Count(); i++)
{
#Html.RadioButtonFor(x => Model.OptionValues[i].Selected, Model.OptionValues[i].Value )
}
I might have 4 or 5 items in my OptionValues.
Question : How can i bind to the Selected property of the SelectListItem so when the model is posted i can identify which on of the radio buttons has been selected?
With your current code, the HTML markup generated for the input element will be like below
<input data-val="true" name="OptionValues[0].Selected" type="radio" value="201">
the name is Selected and the value is a number. The Selected property of the SelectListItem is bool type. Model binding will not work as expected when you post this form data.
IMHO, The easiest way to handle a scenario like this is to use editor templates.
I will first create a class which represents the data I want to use with the radio buttons.
public class MyOption
{
public bool IsSelected { set; get; }
public int Id { set; get; }
public string Text { set; get; }
}
Now in my main view model, I will add a collection type property
public class SpeakerConsent
{
public List<MyOption> Options { set; get; }
}
Now in your ~/Views/Shared/ directory, create a folder called EditorTemplates and then create a view called MyOption.cshtml in that. You can have the HTML markup you want to use to render the radio button in that. Here I am keeping the Id property in a hidden field along with the radio button and label.
#model YourNamespace.MyOption
<div>
<span>#Model.Text</span>
#Html.HiddenFor(g=>g.Id)
#Html.RadioButtonFor(b => b.IsSelected, true)
</div>
Now in your GET action, you can populate this Options property of yout SpeakerConsent view model object and send to the view.
public ActionResult Create()
{
var vm = new SpeakerConsent();
vm.Options = new List<MyOption>
{
new MyOption { Id=1, Text="Seattle"},
new MyOption { Id=2, Text="Detroit"},
new MyOption { Id=31, Text="Kerala"},
};
return View(vm);
}
Now in your main view, you can simply call the EditorFor helper method.
#model YourNamespace.SpeakerConsent
#using (Html.BeginForm("Index", "Home"))
{
#Html.EditorFor(a=>a.Options)
<button type="submit">Save</button>
}
Make sure to check the HTML markup generated by this code. notice the
name attribute value of the inputs.
When the form is submitted, you can read the Options property value, loop through them and read the Id and IsSelected property value and use them as needed
[HttpPost]
public ActionResult Create(SpeakerConsent model)
{
// check model.Options
// to do : return something
}

Does model maintains its structure when data is received in controller?

I am not sure whether I've framed the question properly above in subject but I will try to explain to my best about the question I have.
I have below ContactUsModel which is a part of HomeViewModel, better say Nested Model Class in a single model
public class ContactUsDataModel
{
public string ContactName { get; set; }
public string ContactEmail { get; set; }
public string ContactMessage { get; set; }
public string ContactPhone { get; set; }
}
and I am getting this Model referred in HomeViewModel as below:
public class HomeViewModel
{
/*My other models goes here*/
public ContactUsDataModel CUDModel { get; set; }
}
Now in Index.cshtml view I strongly create a form view as below:
#model ProjectName.Models.HomeViewModel
<!--I have other views for other models-->
#using (Html.BeginForm("ContactPost", "Home", FormMethod.Post, new { id = "contactform" }))
{
#Html.TextBoxFor(m => m.CUDModel.ContactName, new { #class="contact col-md-6 col-xs-12", placeholder="Your Name *" })
#Html.TextBoxFor(m => m.CUDModel.ContactEmail, new { #class = "contact noMarr col-md-6 col-xs-12", placeholder = "E-mail address *" })
#Html.TextBoxFor(m => m.CUDModel.ContactPhone, new { #class = "contact col-md-12 col-xs-12", placeholder = "Contact Number (optional)" })
#Html.TextAreaFor(m=>m.CUDModel.ContactMessage, new { #class = "contact col-md-12 col-xs-12", placeholder = "Message *" })
<input type="submit" id="submit" class="contact submit" value="Send message">
}
I do ajax Post as below:
$('#contactform').on('submit', function (e) {
e.preventDefault();
var formdata = new FormData($('.contact form').get(0));
$.ajax({
url: $("#contactform").attr('action'),
type: 'POST',
data: formdata,
processData: false,
contentType: false,
//success
success: function (result) {
//Code here
},
error: function (xhr,responseText,status) {
//Code here
}
});
});
and in Controller I tried to receive it as below:
public JsonResult ContactPost(ContactUsDataModel model)
{
var name=model.ContactName; //null
/*Fetch the data and save it and return Json*/
//model is always null
}
For some reason the above model is always null. But this works if I refer the model as HomeViewModel model instead of ContactUsDataModel model in controller parameter like below:
public JsonResult ContactPost(HomeViewModel model)
{
var name=model.CUDModel.ContactName; //gets value
/*Fetch the data and save it and return Json*/
//Model is filled.
}
My question here is even though I fill model of type
ContactUsDataModel in the view I am getting it as null if I refer
directly, but ContactUsModel which is inside HomeViewModel gets
filled. Doesn't type of model matter here. Is the hierarchy its
referred is necessary while fetching in controller?
Well, if your generated <input> name is CUDModel.ContactName instead of simply ContactName, the default Model-Binder wouldn't be able to bind it.
Fortunately, you can use the [Bind] attribute with prefix:
public JsonResult ContactPost([Bind(Prefix="CUDModel")]ContactUsDataModel model)
{
// ...
}
See MSDN
Your view posts the Type you have referenced in the view - #model ProjectName.Models.HomeViewModel - CUDModel is simply a property of HomeViewModel.
Using your web browser, inspect each DOM input element "name" property. MVC automatically maps properties from your inputs to the class using the input's "name" property.
To solve this you can create a custom model binder or create the inputs by hand, specifying the name property in such a way that the automatic model binder can match them to properties of your class.
However, there isn't anything wrong with your controller action taking HomeViewModel as an argument.
More information, found here.

MVC5 Html.DropDownListFor Does not select correct value

I'm doing something horribly simple and it isn't working.
#Html.DropDownListFor(m => m.ContentDefinitionID, Model.ContentBoxesSelectList, new {#class = "form-control"})
The ContentDefinitionID is a UInt64 (although I've tried an int)
I use this same select list for 4 different controls on the page.
If my Model.ContentDefinition is set to 4, (which would be test4 in the drop down) then it SHOULD pull that selected value from the Model, NOT from the selectList right? Someone else said that it ignores the value on the SelectList when you use the m=>m.X syntax - which makes sense.
But it always selects the first one. I tried adding another parameter for what it should select, but it wants the text, not the value and I hate to have to lookup the text. (And I'm not sure this will post back correctly if I do that anyway)
I'm about to go create some JQuery code to set the default values for everything -but that is crazy. This is the obvious behavior of the DropDownListFor() method, kind of a 'duh' thing - why doesn't it work? Do I need to create a custom SelectList for every control that exists on the page?
--- Update, the model etc:
class PageModel
{
Int64 ContentDefinitionID {get;set;}
SelectList ContentBoxesSelectList {get;set;}
}
Controller init for model:
model.ContentDefinitionID = 4; // In real situation, this is an
array of N model.ContentBoxesSelectList = new SelectList( from
ContentDefinitionDocument doc in allContentBoxes
where doc.Size == size
select new {Name = doc.Name, id = doc.DefinitionID}, "Id", "Name");
Rendered Output (from it as an array):
selected value should be: 1
<select class="form-control" data-val="true" data-val-number="The field ContentDefinitionID must be a number." data-val-required="The ContentDefinitionID field is required." id="ContentBoxes_0__ContentDefinitionID" name="ContentBoxes[0].ContentDefinitionID" style="width:25%;float:left"><option value="1">Test1</option>
<option value="2">Test 2</option>
<option value="4">Test 4</option>
<option value="0">Test 0</option>
</select>
And None of them are selected.
From the html your generating (name="ContentBoxes[0].ContentDefinitionID"), you are using this in a for loop. Unfortunately DropDownListFor() does not work as expected in a for loop and you need to use a custom EditorTemplate for your model (its been reported as a bug but not yet fixed).
You have not posted your models, but assuming
public class ContentBox
{
public int ContentDefinitionID { get; set; }
....
}
public class ContentBoxesVM
{
public IEnumerable<ContentBox> ContentBoxes { get; set; }
public SelectList ContentBoxesSelectList { get; set; }
}
Side note: Its only necessary to generate one SelectList (rather that one for each object in the collection)
Controller
public ActionResult Edit()
{
ContentBoxesVM model = new ContentBoxesVM();
// populate model, for example
model.ContentBoxes = new List<ContentBox>()
{
new ContentBox { ContentDefinitionID = 4 }
};
model.ContentBoxesSelectList = new SelectList(...);
return View(model);
}
Editor Template (/Views/Shared/EditorTemplates/ContentBox.cshtml)
#model ContentBox
....
#Html.DropDownListFor(m => m.ContentDefinitionID, (SelectList)ViewData["contentBoxesList"])
...
Main view
#model ContentBoxesVM
#using(Html.BeginForm())
{
....
// pass the select list as additional view data to the editor template
#Html.EditorFor(m => m.ContentBoxes, new { contentBoxesList = Model.ContentBoxesSelectList })
....
<input type="submit" />
}
You can achieve what you want by changing your model:
class PageModel
{
Int64 ContentDefinitionID {get;set;}
List<ContentDefinitionDocument> ContentBoxesList {get;set;}
}
and in controller:
array of N model.ContentBoxesList = allContentBoxes.Where(doc => doc.Size == size).ToList();
and in View create SelectList this way:
#Html.DropDownListFor(m => m.ContentDefinitionID,
new SelectList(Model.ContentBoxesSelectList,
"DefinitionID",
"Name",
Model.ContentDefintionID),
new {#class = "form-control"})

ASP MVC - Get data from partial view on Create

Im using ASP.Net MVC 5.
I have two simple classes; Student and Course, like this;
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
I want to create a new Course with optional many Students.
The student(s) form/view will be rendered as a partail view (insde the Course-view).
Right now I have a Create-View that is strongly type to Course.
This view only have 1 textbox - name of the Course.
I render the partial view that is strongly typed to Student.
To simplify i just want to add 1 student to the List.
I would like pass the student data to the course object and then "move on" to the controller.
Can anyone help me with this approach of passing data from a partitial view, or give me a hint of how its done in MVC? ;)
Ok found out what I was doing wrong. First off I donwloaded the Html helper, BeginCollectionItem. Real nice if you want to dynamically add and remove fields/textboxes that will be added to your model.
First off send an empty object to to your view to work with (from your controller).
I just passed in a new Course object. Ctor of Course creates a new List with 1 student object.
Then i used RenderPartial to display a partailview + the student item.
#foreach(var student in Model.Students)
{
RenderPartial("_Student", student);
}
This view looks like this:
#model Project.Data.Entities.Student
<div class="AddStudent form-group">
#using (Html.BeginCollectionItem("students"))
{
#Html.Label("Name:", new { #class = "control-label col-md-2" })
<div class="col-md-8">
#Html.TextBoxFor(x => x.Name)
<button type="button" class="deletButton btn btn-default">Remove</button>
#Html.ValidationMessageFor(model => model.Name)
</div>
}
</div>
I render a button that is hooked up to delete (jquery) the student field.
When i want to add more students to my Course i just use an ajax call to add more partial "_Student" views.
<div>
#Ajax.ActionLink("Add more...", "NewStudentRow", "Course", new AjaxOptions()
{
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "students"
}, new { #class = "btn btn-default" })
</div>
The NewStudentRow method in my controller looks like this:
public PartialViewResult NewStudentRow ()
{
return PartialView("_Student", new Student());
}
Pretty simple if you just use the http://www.nuget.org/packages/BeginCollectionItem/
You can solve this by having more than one partial view..
Pseudo-code:
CourseView:
<TextBox>#Model.Name</TextBox>
#foreach(var student in Model.Students)
{
RenderPartial("ShowStudent");
}
RenderPartial("AddStudent");
The AddStudentView conains all fields you need to provide to save a student to database. In the action you take the input parameters, save the new student and redirect ( something like return RedirectToAction("Course", new { id = student.CourseId }) ) to the Course view. The course view will then be loaded including the new student.
You could also do all of this with ajax to prevent postback, but as you haven't specified any desire to prevent postback I think this would be a good solution.

Categories