Model not binding correctly from a modal rendered partial - c#

I am having a really werid issue my model is not binding to form correctly
#using (Html.BeginForm("SaveCaseNotes", "MISObjects", FormMethod.Post, new { #id = "myForm", #name = "myForm" })) {
<form id="myForm" asp-action="">
<div asp-validation-summary="All" class="text-danger"></div>
#Html.HiddenFor(m => m.Id)
#Html.LabelFor(model => model.Title)
#Html.TextBoxFor(model => model.Title, new { #class = "form-control", #placeholder = "Title" })
#Html.ValidationMessageFor(m => m.Title, string.Empty, new { #style = "color:red;" })
#Html.LabelFor(model => model.Summary)
#Html.TextBoxFor(model => model.Summary, new { #class = "form-control"})
#Html.ValidationMessageFor(m => m.Summary, string.Empty, new { #style = "color:red;" })
#Html.LabelFor(model => model.Notes)
#Html.EditorFor(model => model.Notes, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Notes)
#Html.LabelFor(model => model.DateReported)
#Html.EditorFor(model => model.DateReported, new { htmlAttributes = new { #class = "form-control datetimepicker" } })
#Html.ValidationMessageFor(model => model.DateReported)
<input name="IsValid" type="hidden" value="#ViewData.ModelState.IsValid.ToString()" />
<div class="modal-footer">
<button type="submit" id="btnSave" class="btn btn-primary">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</form>
}
This is my model
public class MISObjectNotes {
public int Id { get; set; }
public int MISObjectId { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public string Notes { get; set; }
public string LastModifiedBy { get; set; }
public DateTime LastModifedDate { get; set; }
public DateTime DateReported { get; set; }
public DateTime DateActioned { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public string CreatedBy { get; set; }
public bool isAcitve { get; set; }
public bool isDeleteted { get; set; }
}
This is my method but its not binding correctly from the above form btw the above form is behind1 render a partial inside a modal popup.
[HttpPost]
public async Task<IActionResult> SaveCaseNotes(MISObjectNotes notes) {
MISObject tempObject = new MISObject();
Int32.TryParse(HttpContext.Session.GetString("CaseId"),out int resultCaseId);
if (ModelState.IsValid) {
notes.MISObjectId = resultCaseId;
notes.isAcitve = true;
notes.LastModifedDate = DateTime.Now;
var test = notes.Notes;
notes.LastModifiedBy = HttpContext.Session.GetString("Intitals");
_context.Add(notes);
await _context.SaveChangesAsync();
tempObject = await _context.MISobject.Where(w=>w.Id== resultCaseId).FirstOrDefaultAsync();
}
return View("Details",tempObject);
}

You added your model to _context:
_context.Add(notes);
so you should take it from _context, and not from _context.MISobject.
tempObject = await _context.Where(w=>w.Id== resultCaseId).FirstOrDefaultAsync();
or did I missed something?

This is my method but its not binding correctly from the above form
[HttpPost]
public async Task<IActionResult> SaveCaseNotes(MISObjectNotes notes)
Based on the code you shared, we can find that you named action parameter with "notes", and you defined a property public string Notes { get; set; } in model class MISObjectNotes, which cause model binding not working as expected.
To fix it, you can try to rename your action parameter, such as "mISObjectNotes" etc.

Related

SaveChanges() duplicates records. one null record and the other one is fine

I'm using ASP.NET MVC 5 and Entity Framework.
I have these three model classes:
public class SubCat
{
public int Id { get; set; }
public string Description { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class Item
{
public Item()
{
Images = new HashSet<Image>();
}
public int Id { get; set; }
public string Description { get; set; }
[ForeignKey("SubCat")]
public int? SubCatId { get; set; }
public SubCat SubCat { get; set; }
//public byte? Image { get; set; }
[ForeignKey("Images")]
public int? ImageId { get; set; }
public ICollection<Image> Images { get; set; }
public int ItemPoints { get; set; }
public int? PointsValue { get; set; }
public string Status { get; set; }
[Display(Name = "Delivery")]
public string DeliveryChoice { get; set; }
public bool? Approved { get; set; }
public bool? Bartered { get; set; }
}
public class Image
{
public int Id { get; set; }
[DisplayName("Upload File")]
public string Picture { get; set; }
[NotMapped]
public HttpPostedFileBase ImageFile { get; set; }
[ForeignKey("Item")]
public int? ItemId { get; set; }
public Item Item { get; set; }
}
When I add a new item, I need to give it a subcategory id in order to show it in the view and here is how I add a new item:
The form:
#using (Html.BeginForm("ItemSubmit", "Orders", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.LabelFor(m => m.Item.Description, new { #class = "col-md-2 control-label" })
#Html.TextBoxFor(m => m.Item.Description, new { #class = "form-control" })
</div>
<br />
<div class="form-group">
<div class="col-md-6">
#Html.DropDownListFor(m => m.Category.Id, new SelectList(Model.Categories, "Id", "Description"), "Select Category", new { #class = "form-control", #id = "cats" })
</div>
<div class="col-md-6">
<select id="subs" disabled class="dropdown form-control" name="subCategory">
<option>Select Category First</option>
</select>
</div>
<br />
<br />
<br />
<div class="form-group">
#Html.LabelFor(m => m.Item.Status, new { #class = "col-md-2 control-label" })
#Html.DropDownListFor(m => m.Item.Status, new SelectList(Model.Items, "Status", "Status"), "", new { #class = "form-control" })
</div>
<br />
<div class="form-group">
#Html.LabelFor(m => m.Item.DeliveryChoice, new { #class = "col-md-2 control-label" })
#Html.DropDownListFor(m => m.Item.DeliveryChoice, new SelectList(Model.Items, "DeliveryChoice", "DeliveryChoice"), "", new { #class = "form-control" })
</div>
<div class="form-group">
<div class="col-md-1"></div>
<h3><span class="label label-primary">How many points would you suggest for your Item?</span></h3>
<div class="col-md-3"></div>
#Html.TextBoxFor(m => m.Item.ItemPoints, new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Image.Picture, htmlAttributes: new { #class = "control-label col-md-2" })
<input type="file" name="ImageFile" required>
</div>
<br />
<br />
<div class="row">
<button type="submit" class="btn btn-success">Submit</button>
</div>
</div>
}
and the controller:
[HttpPost]
public ActionResult ItemSubmit(Item item, int subCategory, Image imageModel, Order order, ApplicationUser user)
{
if (ModelState.IsValid)
{
var sub = _context.SubCats.SingleOrDefault(m => m.Id == subCategory);
//Item
item.SubCatId = sub.Id;
//Image
string fileName = Path.GetFileNameWithoutExtension(imageModel.ImageFile.FileName);
string extension = Path.GetExtension(imageModel.ImageFile.FileName);
fileName = fileName + DateTime.Now.ToString("yymmssfff") + extension;
imageModel.Picture = "~/Image/" + fileName;
fileName = Path.Combine(Server.MapPath("~/Image/"), fileName);
imageModel.ImageFile.SaveAs(fileName);
//Order
order.OrderDate = DateTime.Now;
order.TransactionType = "Item Submit";
order.UserId = User.Identity.GetUserId();
order.Approved = false;
order.ItemId = item.Id;
_context.Items.Add(item);
_context.Images.Add(imageModel);
_context.Orders.Add(order);
_context.SaveChanges();
}
return RedirectToAction("Index","Home");
}
but when _context.SaveChanges() is called, ef tend to save two copies of the item! one with a subcatid and one wihout a subcatid, and the funny thing is that the image takes the itemid of the item without the subcatid, so when i try to show the item on the view by the image it doesnt show because the item shown must be related to the subcat chosen.
here is the jquery i use in the view:
$(document).ready(function () {
$(".mybtn").on('click', function () {
var id = $(this).attr('data-id');
$("#images").html('');
$.get("/Items/GetSubById", { ID: id }, function (data) {
$("#sub-categories-btns").html(``);
for (var i = 0; i < data.length; i++) {
$("#sub-categories-btns").append(`
<button id="button" type="button" data-id="`+ data[i].Id + `" class="btn btn-success my-subcategory-btn">
`+ data[i].Description +`
</button>
`);
}
});
});
$("#sub-categories-btns").delegate(".my-subcategory-btn", 'click', function () {
var subId = $(this).attr('data-id');
$("#images").html('');
$.get("/Items/GetImgaesBySubCategoryId", { ID: subId }, function (data) {
for (var i = 0; i < data.length; i++) {
$("#images").append(`
<div class="form-group col-md-4">
<button data-id="`+ data[i].ItemId +`" name="item" class="ItemInfo img-btn"><img src="`+ data[i].Picture +`" width="200" height="200" /></button>
</div>
`);
}
});
});
$("#images").delegate('.img-btn', 'click', function () {
var imgId = $(this).attr('data-id');
window.location.href = "/Items/Buy?item=" + imgId;
});
});
This is the json i send to the view:
public JsonResult GetSubById(int? ID)
{
return Json(_context.SubCats.Where(p => p.CategoryId == ID), JsonRequestBehavior.AllowGet);
}
public JsonResult GetImgaesBySubCategoryId(int? ID)
{
var data = _context.Images.Where(m => m.Item.SubCatId == ID).ToList();
data.ForEach(img => img.Picture = img.Picture.Replace("~", ""));
return Json(data, JsonRequestBehavior.AllowGet);
}
How do i stop it from duplicating??
I actually found the solution a while ago but i forgot to post here!
i dont know how to explaing it but when the image is being created it filled the item navigation property with a new item with the same information of the item sent by the form!
So i just added these two lines before the uploading image code in the ItemSubmit Controller
imageModel.Item = null;
imageModel.ItemId = item.Id;
i also had a problem in the model that is sent from the view model so i commented the if(ModelState.IsValid)
hope this would help others

ASP.NET MVC ViewModels to combine many-to-many model relationship to View

I am building the classic movie catalog project.
I have a many-to-many with movies and genres so that I can list the genres in movies and vice-versa, etc.
I am having trouble doing that with my models. I can use models to relate one to view, but need ViewBag to display the other.
I am a beginner and any help conquering advanced ViewModels would be greatly appreciated.
Ill use my create() movie method as an example:
My MovieController:
[Authorize(Roles = "Admin")]
[HttpGet]
public ActionResult Create()
{
ViewBag.Genres = db.Genres.Select(
g => new GenreToMovie
{
id = g.Id,
Name = g.Name,
Checked = false
}).ToList();
return View();
}
...
//List Genre Movies
private void MovieLister(Genre genre)
{
//Assign Movies to Genre List
ViewBag.Movies = db.Movies.Select(
m => new MovieToGenre
{
id = m.Id,
Title = m.Title,
Year = m.Year,
Checked = db.Genres.FirstOrDefault(g => g.Id == genre.Id).Movies.Any(movie => movie.Id == m.Id)
}).ToList();
My Movie Model:
namespace MovieSite.Models
{
public class Movie
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public int Year { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
public Movie() { }
public Movie(string title, int year)
{
Title = title;
Year = year;
Genres = new List<Genre>();
}
}
}
My MovieViewModels (A bad name I'm sure, its where I keep my GenreTomMovie ViewModel:
namespace MovieSite.Models
{
//Assign Genres to Movies
public class GenreToMovie
{
public int id { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
//Assign Movies to Genres
public class MovieToGenre
{
public int id { get; set; }
public string Title { get; set; }
public int Year { get; set; }
public bool Checked { get; set; }
}
public class MovieViewModel
{
public int MovieID;
public string Title { get; set; }
public int Year { get; set; }
public MovieViewModel() { }
public MovieViewModel(int id, string title, int year)
{
MovieID = id;
Title = title;
Year = year;
}
}
}
My Create View:
#model MovieSite.Models.Movie
#{
ViewBag.Title = "Create Movie";
}
<h2>Add Movie</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<label style="color:red" id="errormsg"></label>
<div class="form-group">
#Html.LabelFor(model => model.Title, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Title, new { htmlAttributes = new { #class = "form-control", id = "movieTitle" } })
#Html.ValidationMessageFor(model => model.Title, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Year, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Year, new { htmlAttributes = new { #class = "form-control", id = "movieYear" } })
#Html.ValidationMessageFor(model => model.Year, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Genres, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10" style="height:250px; width:400px; overflow-y:scroll">
<br />
#{
List<MovieSite.Models.GenreToMovie> moviegenres = ViewBag.Genres;
foreach (var genre in moviegenres.OrderBy(m => m.Name))
{
<ul style="padding:0px">
<input type="checkbox" name="SelectedGenres" value="#genre.id" #Html.Raw(genre.Checked ? "checked=\"checked\"" : "") />
#genre.Name
</ul>
}
}
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default"/>
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Manage")
</div>
You can see just below halfway on my views I have a viewbag:
#{
List<MovieSite.Models.GenreToMovie> moviegenres = ViewBag.Genres;
foreach (var genre in moviegenres.OrderBy(m => m.Name))
{
<ul style="padding:0px">
<input type="checkbox" name="SelectedGenres" value="#genre.id" #Html.Raw(genre.Checked ? "checked=\"checked\"" : "") />
#genre.Name
</ul>
}
}
To display my genres, and I don't want that. I have tried to make a number of viewmodels to combine data, but I get nowhere. I've been banging my head against the table for a couple days now and can't find a clear example online. Please Obi-Wan, you're my only hope.
I just need one clean example and I can figure this out. I just can't unlock it right now. Thanks for your time and help.
EDIT:
With the suggestion in the comments I made a viewmodel to pass through:
public class MovieAddVM
{
public int Id { get; set; }
public string Name { get; set; }
public int Year { get; set; }
public string Title { get; set; }
public bool Checked { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
public MovieAddVM()
{
GenreList = new List<GenreToMovie>();
}
public List<GenreToMovie> GenreList { get; set; }
}
It registers with my views, but does not populate a list of genres. How do I query my genres and attribute that list to the viewmodel? I'm sorry I am very confused about all this.

Editing only part of object in view but pass whole obj to ActionListener

I have a form with a submit button that should pass through the item to the actionlistener. I thought it might be similar to the question in #Html.HiddenFor does not work on Lists in ASP.NET MVC but none of the answers seem to work. You can even see my for-loop taken from one of the answers in there.
[
EDIT: I have gotten rid of the mass of hidden loops and replaced with #Html.EditorFor so that you can see, even if not hidden, the flags list does not get to the actionlistener. This is a problem because when someone edits the flags, there is no way to update the db as I cannot get the ID of the flag updated.
]
The ModelState in the controller is never valid, regardless whether I keep the "[Bind(Include =" there or not. That's just there because of the tutorial for
ASP.NET MVC Tutorial: Web application development with Azure Cosmos DB.
ItemController.cs:
[HttpPost]
[ActionName("ProductEdit")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditProductAsync( [Bind(Include = "Id, Name, Flags")] Item model)
{
Item product = await DocDBRepo<Item>.GetItem(model.Id);
model.Organisations = product.Organisations;
if (ModelState.IsValid) //Checks item validation via "required" set on properties
{
await DocDBRepo<Item>.UpdateItemAsync(model.Id, model);
return RedirectToAction("Index");
}
return View(model);
}
[HttpGet]
[ActionName("ProductEdit")]
public async Task<ActionResult> EditProductAsync(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Item item = await DocDBRepo<Item>.GetItem(id);
if (item == null)
{
return HttpNotFound();
}
return View(item);
}
ProductEdit.cs:
#model RRPortal.Models.Item
#{
ViewBag.Title = "ProductEdit";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>ProductEdit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Flags, htmlAttributes: new { #class = "control-label col-md-2 " })
</div>
#*Flags list*#
#for (int i = 0; i < Model.Flags.Count; i++) //foreach (var flag in Model.Flags)
{
<div class="form-group">
//#Html.HiddenFor(modelItem => Model.Flags[i].Id)
#Html.Label(Model.Flags[i].Name, htmlAttributes: new { #class = "control-label col-md-3" })
#Html.LabelFor(modelItem => Model.Flags[i].Enabled, htmlAttributes: new { #class = "control-label col-md-1" })
<div class="col-md-8">
#Html.EditorFor(modelItem => Model.Flags[i].Enabled, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(modelItem => Model.Flags[i].Enabled, "", new { #class = "text-danger" })
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Item.cs:
public class Item
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[Required]
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "flags")]
public List<Flag> Flags { get; set; }
[JsonProperty(PropertyName = "organisations")]
public List<Organisation> Organisations { get; set; }
}
public class Flag
{
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
[Required]
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[Required]
[JsonProperty(PropertyName = "enabled")]
public bool Enabled { get; set; }
}
public class Organisation
{
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "users")]
[Display(Name ="Users")]
public List<User> UserStore { get; set; }
}
public class User
{
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
[Required]
[JsonProperty(PropertyName = "fname")]
public string FName { get; set; }
[Required]
[JsonProperty(PropertyName = "lname")]
public string LName { get; set; }
[Required]
[Display(Name = "Admin?")]
[JsonProperty(PropertyName = "isadmin")]
public bool IsAdmin { get; set; }
}
The Item's Id and Name comes through and is not null when I debug the controller, but the Flags List is always empty. The ModelState shows the following exception: {"The parameter conversion from type 'System.String' to type 'RRPortal.Models.Flag' failed because no type converter can convert between these types."}
I have also been asked where the ModelState is showing the exception so below is a screenshot:
I will gladly update the question if anyone has any questions. I have been tweaking the view for 2 days now and still can't get the item to contain anything. The rendered HTML appears to contain the organisation and inner objects perfectly fine.
Any help is appreciated!
My guess is that in your HttpGet view you have something along the lines of:
[HttpGet]
public ActionResult EditProductAsync()
{
var model = new ProductViewModel()
{
Flags = _uow.Products.GetFlags(),
Organisations = _uow.Products.GetOrganisations()
};
return View(model);
}
Because these objects are not also returned as part of your form, they are returning to the server as empty which is throwing an error for you, thus invalidating the model. Before you check if the model is valid, you should first do something like this:
[HttpPost]
[ActionName("ProductEdit")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditProductAsync( [Bind(Include = "Id, Name, Flags, Organisations")] Item model)
{
model.Organisations = _uow.Products.GetOrganisations();
model.Flags = _uow.Products.GetFlags();
if (ModelState.IsValid)
{
await DocDBRepo<Item>.UpdateItemAsync(model.Id, model);
return RedirectToAction("Index");
}
return View(model);
}
By populating those fields, any model errors you have are strictly your client's errors on submitting the form.

MVC Post with complex types, model is empty

I have the following code to fill my model before posting it to my controller.
The list I'm iterating through is a list.
<div class="tab-content">
#*#foreach (var description in Model.Category.C_CategoryDescription)*#
#for (var i = 0; i < Model.Category.C_CategoryDescription.Count; i++)
{
<div id="#("tab" + #Model.Category.C_CategoryDescription.ToList()[i].ProductTypeId)" class="#(Model.Category.C_CategoryDescription.ToList()[i] == #Model.Category.C_CategoryDescription.First() ? "tab-active" : "tab")">
<div class="form-group ">
#Html.LabelFor(model => model.Category.C_CategoryDescription.ToList()[i].DescriptionTop, "Beskrivelse - Top", htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.TextAreaFor(model => model.Category.C_CategoryDescription.ToList()[i].DescriptionTop, new {#class = "richText"})
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Category.C_CategoryDescription.ToList()[i].DescriptionBottom, "Beskrivelse - Bund", htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.TextAreaFor(model => model.Category.C_CategoryDescription.ToList()[i].DescriptionBottom, new {#class = "richText"})
</div>
</div>
</div>
}
</div>
The HTML comes out fine. But as soon as I catch the post in my controller, the model is empty. Not NULL, but empty.
I read numerous articles saying that it points to a problem with the model binding.
I changed my code to reflect what's described: here
Still no dice.
Any help is appreciated.
EDIT: I changed my code according to this post.
My view now looks like this:
<div class="tab-content">
#Html.Partial("_Edit", Model.Category.C_CategoryDescription.ToList())
</div>
With a partial view looking like this:
#model IList<DataAccess.Plusbog.C_CategoryDescription>
#{
var productType = Model;
}
#for (var i = 0; i < productType.Count; i++)
{
<div id="#("tab" + #Model[i].ProductTypeId)" class="#(Model[i] == #Model.First() ? "tab-active" : "tab")">
<div class="form-group ">
#Html.LabelFor(model => productType[i].DescriptionTop, "Beskrivelse - Top", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(model => productType[i].DescriptionTop, new { #class = "richText" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => productType[i].DescriptionBottom, "Beskrivelse - Bund", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(model => productType[i].DescriptionBottom, new { #class = "richText" })
</div>
</div>
</div>
}
Same result, sadly.
EDIT:
Here's the models:
public class CategoryModel
{
public C_Category Category { get; set; }
public SelectList Categories { get; set; }
public SelectList ProductTypes { get; set; }
public String ISBNListToAddManually { get; set; }
public string Response { get; set; }
}
And the C_Category class:
public partial class C_Category
{
public C_Category()
{
this.C_CategoryDescription = new HashSet<C_CategoryDescription>();
this.Books = new HashSet<Books>();
this.ChildCategories = new HashSet<C_Category>();
this.Campaign = new HashSet<Campaign>();
this.Group = new HashSet<Group>();
}
public int Id { get; set; }
public Nullable<int> ParentCategoryId { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public string Slug { get; set; }
public string Keywords { get; set; }
public virtual ICollection<C_CategoryDescription> C_CategoryDescription { get; set; }
public virtual ICollection<Books> Books { get; set; }
public virtual ICollection<C_Category> ChildCategories { get; set; }
public virtual C_Category ParentCategory { get; set; }
public virtual ICollection<Campaign> Campaign { get; set; }
public virtual ICollection<Group> Group { get; set; }
}
And lastly, the C_CategoryDescription:
public partial class C_CategoryDescription
{
public int CategoryId { get; set; }
public int ProductTypeId { get; set; }
public string DescriptionTop { get; set; }
public string DescriptionBottom { get; set; }
public string MetaDescription { get; set; }
public string MetaKeywords { get; set; }
public string AlternativeTitle { get; set; }
public virtual C_Category C_Category { get; set; }
public virtual C_ProductType C_ProductType { get; set; }
}
Your code for generating the elements in the collection needs to be
#Html.TextAreaFor(m => m.Category.C_CategoryDescription[i].DescriptionTop, new {#class = "richText"})
which will generate the correct name attributes
<textarea name="Category.C_CategoryDescription[0].DescriptionTo" ... />
<textarea name="Category.C_CategoryDescription[1].DescriptionTo" ... />
Your current use a .ToList() is generating incorrect name attributes (not tested, but I assume its name="[0].DescriptionTo")
Alternatively you can use a custom EditorTemplate for the C_CategoryDescription model if you cannot change the collection to implement IList
In Views/Shared/EditorTemplates/C_CategoryDescription.cshtml (note the name of the file must match the name of the class)
#model yourAssembly.C_CategoryDescription
<div class="form-group ">
#Html.LabelFor(m => m.DescriptionTop, "Beskrivelse - Top", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(m => m.DescriptionTop, new { #class = "richText" })
<div>
</div>
....
and then in the main view, to generate the correct html for each item in the collection
#Html.EditorFor(m => m.Category.C_CategoryDescription)

Two equal pieces of code work differently

Working on my project I faced a problem: EF didn't load the data from connected table, but in other practically equal piece of code all is perfect. Can somebody explain it?
Here is the situation:
All is good, type.name is loaded:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult pacientEdit([Bind(Include = "ID,comments")] Anamnesis anamnesis)
{
if (ModelState.IsValid)
{
db.Entry(anamnesis).State = EntityState.Modified;
db.SaveChanges();
return pacientDetails(anamnesis.ID);
}
return PartialView(anamnesis);
}
public ActionResult pacientDetails(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Anamnesis anamnesis = db.anamneses.Include(p => p.type).Where(p => p.ID == id).First();
if (anamnesis == null)
{
return HttpNotFound();
}
return PartialView("~/views/Anamnesis/pacientDetails.cshtml", anamnesis);
}
Here I run PacientEdit, then it flows to PacientDetails and gives me full anamnesis, including full type with it's name.
All is bad, type.name is null:
In the second case the name of type is null despite all my tries to load it from db.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult pacientEdit(Assigment assigment)
{
if (ModelState.IsValid)
{
db.Entry(assigment).State = EntityState.Modified;
db.SaveChanges();
return pacientDetails(assigment.ID);
//dirty fix, but works: return (new AssigmentsController()).pacientDetails(assigment.ID);
}
return PartialView(assigment);
}
public ActionResult pacientDetails(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Assigment assigment = db.assigments.Include(p => p.type).Where(p => p.ID == id).First();
if (assigment == null)
{
return HttpNotFound();
}
return PartialView("~/views/Assigments/pacientDetails.cshtml", assigment);
}
In this case all data in pacientDetails, including type_ID is loading, but type.name is null
Can somebody explain such behavior?
Used models:
public class Anamnesis
{
public int ID { get; set; }
public AnamnesisEventType type { get; set; }
public String comments { get; set; }
}
public class Assigment
{
public int ID { get; set; }
public AssigmentType type { get; set; }
public decimal? weight { get; set; }
public decimal? dose { get; set; }
public decimal? inADay { get; set; }
[DataType(DataType.MultilineText)]
public String comments { get; set; }
public String medicine { get; set; }
[DefaultValue(1)]
public int actual { get; set; }
public DateTime cancelDate { get; set; }
}
public class AnamnesisEventType
{
public int ID { get; set; }
public String name { get; set; }
}
public class AssigmentType
{
public int ID { get; set; }
public String name { get; set; }
public String description { get; set; }
}
Here is the view for pacientEdit for AssigmentsController:
#model WebApplication2.Models.Assigment
<form id="#String.Format("AssigmentsEdit{0}", Model.ID)">
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.ID)
#Html.HiddenFor(model => model.type.ID)
#Html.HiddenFor(model => model.cancelDate)
#Html.HiddenFor(model => model.actual)
<div class="row">
<div class="col-md-4">
<strong>
#Html.DisplayFor(model => model.type.name)
</strong>
</div>
<div class="col-md-6">
<p>
#Html.EditorFor(model => model.medicine, new { htmlAttributes = new { #class = "form-control", #placeholder = Html.DisplayNameFor(model => model.medicine) } })
</p>
</div>
<div class="col-md-2">
<a onclick="CancelEdit('Assigments', #Model.ID);" class="btn btn-warning btn-sm"><span class="glyphicon glyphicon-backward" aria-hidden="true"></span></a>
<a class="btn btn-sm btn-primary" onclick="PostEditForm('Assigments', #Model.ID);">
<span class="glyphicon glyphicon-save" aria-hidden="true"></span>
</a>
</div>
</div>
<div class="row alert alert-info" style="margin-top:10px">
<div class="col-md-4">
<h4><span class="glyphicon glyphicon-tint" aria-hidden="true"></span> Назначение:</h4>
#Html.EditorFor(model => model.weight, new { htmlAttributes = new { #class = "form-control", #placeholder = Html.DisplayNameFor(model => model.weight) } })
#Html.EditorFor(model => model.dose, new { htmlAttributes = new { #class = "form-control", #placeholder = Html.DisplayNameFor(model => model.dose) } })
#Html.EditorFor(model => model.inADay, new { htmlAttributes = new { #class = "form-control", #placeholder = Html.DisplayNameFor(model => model.inADay) } })
</div>
<div class="col-md-8">
<h4><span class="glyphicon glyphicon-comment" aria-hidden="true"></span> #Html.DisplayNameFor(model => model.comments):</h4>
<p>#Html.EditorFor(model => model.comments, new { htmlAttributes = new { #class = "form-control", #placeholder = Html.DisplayNameFor(model => model.comments) } })</p>
</div>
</div>
</form>
<hr />
I guess the problem is in the fact that you're trying to bind Assignment ID and AssignmentType ID using the same name.
Create an AssignmentViewModel class (which is anyway a good practice), where you'll have only the ID of member "type" like this:
public class AssigmentViewModel
{
public int ID { get; set; }
public int TypeId { get; set; }
public decimal? weight { get; set; }
public decimal? dose { get; set; }
public decimal? inADay { get; set; }
[DataType(DataType.MultilineText)]
public String comments { get; set; }
public String medicine { get; set; }
public int actual { get; set; }
public DateTime cancelDate { get; set; }
}
You could also try to modify the hiddenFor type like this:
#Html.HiddenFor(model => model.type)
Hope this help

Categories