WYSIWYG Text Editor SummerNote Entered data is not persisting - asp.net

Above is a picture of my edit page. The issue I have is that this editor (summernote) does not persist it's data when I save
**I have however, narrowed down the problem. **
Fields on the form persist the data in the value field on the HTML elements
SummerNote does not store the text I've written into the editor in the value element
If I use the default editor and put a value in (which is stored in the value element), then use summernote, the value in the value field will be there, but not displayed.
Editing this value field in the inspector will then cause the value to be persisted on save.
The line containing the value field:
<input class="form-control text-box single-line" id="Content_Content" name="Content.Content" type="text" value="I am the text that is not shown but would be persisted!" style="display: none;">
The note-editing-area div:
<div class="note-editing-area"><div class="note-handle"><div class="note-control-selection" style="display: none;"><div class="note-control-selection-bg"></div><div class="note-control-holder note-control-nw"></div><div class="note-control-holder note-control-ne"></div><div class="note-control-holder note-control-sw"></div><div class="note-control-sizing note-control-se"></div><div class="note-control-selection-info"></div></div></div><textarea class="note-codable"></textarea><div class="note-editable panel-body" contenteditable="true" style="height: 320px;"><h1>Help!</h1><p>What I type here will not be persisted when i click the <b>save </b>button.</p></div></div>
My question therefore is this:
How can I Either
Have the html between the ::before and ::after of the summernote editor (The value that the user types into the control) be put into the value tag aswell so that it is persisted
Do something else to make it work.
Additional Information:
Here is My Model:
Article.cs:
public class Article
{
#region Constructors
public Article(string title, string subTitle = "")
{
InitializeCollections();
Title = title;
SubTitle = subTitle;
}
public Article(string title, Article parentArticle, string subTitle = "")
{
InitializeCollections();
Title = title;
SubTitle = subTitle;
ParentArticles.Add(parentArticle);
}
public Article()
{
InitializeCollections();
}
void InitializeCollections()
{
ParentArticles = new List<Article>();
ChildArticles = new List<Article>();
}
#endregion
[Key]
public int ArticleId { get; set; }
public virtual ArticleContent Content { get; set; }
[StringLength(GUIConstants.MaxCharactersInMenuItemText)]
public string Title { get; set; }
[StringLength(100)]
public string SubTitle { get; set; }
public int? Sequence { get; set; }
public bool Published { get; set; }
public DateTime? PublishedDate { get; set; }
public virtual ICollection<Article> ParentArticles { get; set; }
public virtual ICollection<Article> ChildArticles { get; set; }
public string Notes { get; set; }
Article Content (It's the Content's Content property that has the WYSIWYG Editor, however if i use the WYSIWYG for a property dirrectly on the article model (like Title) I get the same issue.
public class ArticleContent
{
[Key, ForeignKey("Article")]
public int ArticleId { get; set; }
public virtual Article Article { get; set; }
[AllowHtml]
public string Content { get; set; }
}
}
Controller:
// GET: Articles/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var article = db.Article.Include(a => a.Content).SingleOrDefault(a => a.ArticleId == id);
if (article == null)
{
return HttpNotFound();
}
return View(article);
}
// POST: Articles/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[ValidateInput(false)]
[HttpPost, ActionName("Edit")]
public ActionResult EditPost(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var articleToUpdate = db.Article.Include(a => a.Content).SingleOrDefault(a => a.ArticleId == id);
if (TryUpdateModel(articleToUpdate, "",
new string[] { "Title", "SubTitle", "Sequence", "Content" }))
{
try
{
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
return View(articleToUpdate);
}
View:
**#model Catalyst.Models.Article
#{
ViewBag.Title = "Edit Article";
}
#section styles{
#Styles.Render("~/Content/summernote")
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ArticleId)
<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" } })
#Html.ValidationMessageFor(model => model.Title, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SubTitle, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SubTitle, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SubTitle, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Sequence, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Sequence, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Sequence, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Content.Content, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Content.Content, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Content.Content, "", 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", "Articles", new { #class = "btn btn-default" })
</div>
#section scripts {
#Scripts.Render("~/bundles/SummerNote", "~/bundles/SummerNoteArticleContentEditor")
}**
The Javascript turning the content textbox into summernote
(function ($) {
function HomeIndex() {
var $this = this;
function initialize() {
$('#Content_Content').summernote({
focus: true,
height: 320,
codemirror: {
theme: 'united'
}
});
}
$this.init = function () {
initialize();
}
}
$(function () {
var self = new HomeIndex();
self.init();
})
}(jQuery))
I followed this guide:
http://www.c-sharpcorner.com/UploadFile/3d39b4/bootstrap-wysiwyg-editor-in-Asp-Net-mvc/

Related

Subclass is not returned to controller from view

The structure is:
public class SchoolsInformationFooViewModel
{
[Display(Name = "Параметры отчета")]
public Options options { get; set; }
[Display(Name = "Секции строк")]
public IEnumerable<box> boxes { get; set; }
}
public class Options
{
[Display(Name = "Период")]
public DateTime date { get; set; }
}
Controller - send:
// GET: SchoolsInformation
[HttpGet]
public ActionResult Index()
{
Options options = new Options { date = DateTime.Today };
SchoolsInformationFooViewModel model = new SchoolsInformationFooViewModel { options = options };
return View(model);
}
Controller-receipt:
// POST: SchoolsInformation
[HttpPost]
public ActionResult Index([Bind(Include = "date")] Options options)
{
// Here options.date == null
}
View:
<div class="form-group">
#Html.LabelFor(model => model.options.date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.options.date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.options.date, "", new { #class = "text-danger" })
</div>
</div>
Html:
<input class="form-control text-box single-line" data-val="true" data-val-date="Поле Период должно содержать дату." data-val-required="Требуется поле Период." id="options_date" name="options.date" type="datetime" value="23.08.2021 0:00:00">
The date is passed to the view successfully. But an empty class is returned - all properties are empty (null). Why this is so - I cannot understand. I tried adding prefixes to the bind, but in this case, instead of an empty class, the variable contains null
Didn't work because I inserted my control:
using (Html.BeginForm("index", "SchoolsInformation", FormMethod.Post))
{...}

ViewModel array shows empty value (ASP.Net MVC 5)

I am using ASP.Net MVC, I am trying to get values using a ViewModel.
FAQ class have (Name,Description)
FAQ_Detail class is List of Question and Answer which I am passing as PartialView
I have attached front end page and controller page, in controller I can get the Name,Description but always return NULL FAQ_Detail property
View Model
public class FAQViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IList<Details> FAQ_Details { get; set; }
}
public class Details
{
public int Id { get; set; }
public int FAQId { get; set; }
public string Question { get; set; }
public string Answer { get; set; }
}
View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>FAQ</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<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.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
<br />
<br />
<br />
#for (int i = 0; i <= 2; i++)
{
var m = new FAQ.Models.ViewModels.Details()
{
Id = i,
FAQId = 11,
Question = string.Format("Quesiton {0}",i),
Answer = string.Format("Ans. {0}",i)
};
#Html.Partial("_AddFAQ",m)
}
<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>
}
Controller
[HttpPost]
public ActionResult Create(Models.ViewModels.FAQViewModel model) //The model.FAQ_Details array always empty
{
try
{
//
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The problem is you are just displaying the question/answer pair as form fields and there is nothing to bind them to the array in the model you expect. Usually, the name HTML attribute maps the client-side field to the property name of the model on the server.
The better thing you can do is to have some JavaScript, collect the data and pass that through an AJAX call to the server.
I found a solution, I pass master model in controller and use Detail model as collection
public ActionResult Create(string name,string description, ICollection<Models.ViewModels.Details> data)
{
try
{
//
return RedirectToAction("Index");
}
catch
{
return View();
}
}

Attaching an entity of type 'X' failed because another entity of the same type already has the same primary key value. error

Hello I am quite new to MVC programming and have come across a rather strange error I cannot seem to wrap my head around. I am creating an MVC 5 application, and When I try and use the edit option from my controller, the parent entity and child entities all seem to load correctly, however when I try and save the changes back I receive the error "Attaching an entity of type 'X.Models.ApplicationUser' failed because another entity of the same type already has the same primary key value". I have the below ViewModel/Models:
public class EditMatchesViewModel
{
public int ID { get; set; }
[Required]
public DateTime Date { get; set; }
[Required]
public string Division { get; set; }
[Required]
public Team HomeTeam { get; set; }
[Required]
public Team AwayTeam { get; set; }
public List<Game> Games { get; set; }
public MatchStatus Status { get; set; }
}
public class Game
{
public int ID { get; set; }
public GameType GameType { get; set; }
public virtual ApplicationUser AwayPlayer1 { get; set; }
public virtual ApplicationUser AwayPlayer2 { get; set; }
public virtual ApplicationUser HomePlayer1 { get; set; }
public virtual ApplicationUser HomePlayer2 { get; set; }
public int AwayScore1 { get; set; }
public int AwayScore2 { get; set; }
public int HomeScore1 { get; set; }
public int HomeScore2 { get; set; }
}
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public string UserRole { get; set; }
public bool Active { get; set; }
public Team Team { get; set; }
}
public class Team
{
public int ID { get; set; }
public string TeamName { get; set; }
public bool Active { get; set; }
public virtual ICollection<ApplicationUser> Players { get; set; }
}
And the following controller:
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Match matchData = await db.Matches.
Where(m => m.ID == id).
Include(t => t.AwayTeam).
Include(t2 => t2.HomeTeam).
Include(x => x.Games.Select(g => g.GameType)).
FirstOrDefaultAsync();
EditMatchesViewModel model = new EditMatchesViewModel
{
Date = matchData.Date,
Division = matchData.Division,
AwayTeam = matchData.AwayTeam,
HomeTeam = matchData.HomeTeam,
ID = matchData.ID,
Status = matchData.Status,
Games = matchData.Games.ToList()
};
ViewBag.teams = new SelectList(db.Teams.Where(a => a.Active == true).ToList(), "ID", "TeamName");
ViewBag.players = db.Users.AsNoTracking().Where(a => a.Active == true).ToList();
ViewBag.gametypelist = db.GameType.Where(a => a.Active == true).AsNoTracking().ToList();
if (model == null)
{
return HttpNotFound();
}
return View(model);
}
// POST: Matches/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "ID,Date,Division,HomeTeam,AwayTeam,Games")] Match match)
{
if (ModelState.IsValid)
{
db.Entry(match).State = EntityState.Modified;
//db.Set<Match>().AddOrUpdate(match);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(match);
}
And finally the following view:
#model TennisClub.Models.EditMatchesViewModel
#{
ViewBag.Title = "Update match and game information.";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Match</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ID)
<div class="form-group">
#Html.LabelFor(model => model.Date, htmlAttributes: new { #class =
"control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Division, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Division, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Division, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.HomeTeam, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.HomeTeam.ID, (IEnumerable<SelectListItem>)ViewBag.Teams, "Please Select Home Team", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.HomeTeam, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.AwayTeam, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.AwayTeam.ID, (IEnumerable<SelectListItem>)ViewBag.Teams, "Please Select Away Team", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.AwayTeam, "", new { #class = "text-danger" })
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>Game Type</th>
<th>Away Team</th>
<th>Home Team</th>
<th>Away Score</th>
<th>Home Score</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.Games.Count; i++)
{
<tr>
#Html.HiddenFor(model => Model.Games[i].ID)
<td>#Html.LabelFor(model =>
Model.Games[i].GameType.Name, htmlAttributes: new { #class = "control-label
col-md-2" })<br />#Html.DropDownListFor(model => Model.Games[i].GameType.ID,
new SelectList(ViewBag.gametypelist, "ID", "Name",
Model.Games[i].GameType.ID), null, new { #class = "form-control" })</td>
<td>
#Html.LabelFor(model =>
Model.Games[i].AwayPlayer1.UserName, htmlAttributes: new { #class =
"control-label col-md-2" })<br />#Html.DropDownListFor(model =>
Model.Games[i].AwayPlayer1.Id,
Model.Games[i].AwayPlayer1 != null ? new SelectList(ViewBag.players, "Id",
"UserName", Model.Games[i].AwayPlayer1.Id) :
new SelectList(ViewBag.players, "Id", "UserName"), "Please select away
player 1", new { #class = "form-control" })
<br />
#Html.LabelFor(model =>
Model.Games[i].AwayPlayer2.UserName, htmlAttributes: new { #class =
"control-label col-md-2" })<br />#Html.DropDownListFor(model =>
Model.Games[i].AwayPlayer2.Id,
Model.Games[i].AwayPlayer2 != null ? new SelectList(ViewBag.players, "Id",
"UserName", Model.Games[i].AwayPlayer2.Id) :
new SelectList(ViewBag.players, "Id", "UserName"), "Please select away
player 2", new { #class = "form-control" })
</td>
<td>
#Html.LabelFor(model =>
Model.Games[i].HomePlayer1.UserName, htmlAttributes: new { #class =
"control-label col-md-2" })<br />#Html.DropDownListFor(model =>
Model.Games[i].HomePlayer1.Id,
Model.Games[i].HomePlayer1 != null ? new SelectList(ViewBag.players, "Id",
"UserName", Model.Games[i].HomePlayer1.Id) :
new SelectList(ViewBag.players, "Id", "UserName"), "Please select home
player 1", new { #class = "form-control" })
<br />
#Html.LabelFor(model =>
Model.Games[i].HomePlayer2.UserName, htmlAttributes: new { #class =
"control-label col-md-2" })<br />#Html.DropDownListFor(model =>
Model.Games[i].HomePlayer2.Id,
Model.Games[i].HomePlayer2 != null ? new SelectList(ViewBag.players, "Id",
"UserName", Model.Games[i].HomePlayer2.Id) :
new SelectList(ViewBag.players, "Id", "UserName"), "Please select home
player 2", new { #class = "form-control" })
</td>
<td>
#Html.LabelFor(model => Model.Games[i].AwayScore1,
htmlAttributes: new { #class = "control-label col-md-2" })<br
/>#Html.EditorFor(model => Model.Games[i].AwayScore1, new { htmlAttributes =
new { #class = "form-control" } })<br />
#Html.LabelFor(model => Model.Games[i].AwayScore2,
htmlAttributes: new { #class = "control-label col-md-2" })<br />#Html.EditorFor(model => Model.Games[i].AwayScore2, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.LabelFor(model => Model.Games[i].HomeScore1, htmlAttributes: new { #class = "control-label col-md-2" })<br />#Html.EditorFor(model => Model.Games[i].HomeScore1, new { htmlAttributes = new { #class = "form-control" } })<br />
#Html.LabelFor(model => Model.Games[i].HomeScore2, htmlAttributes: new { #class = "control-label col-md-2" })<br />#Html.EditorFor(model => Model.Games[i].HomeScore2, new { htmlAttributes = new { #class = "form-control" } })
</td>
</tr>
}
</tbody>
</table>
<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")
}
I have tried some of the things from some of the other posts with no luck, it seems that the ApplicationUser is always being loaded regardless of whether I say to include it or not. It also seems like it always includes the team Id which then includes the players again and on and on. Any help or direction would be greatly appreciated.
The problem occurs in this line because another Match entity set still loaded somewhere in memory with exactly same primary key as Match viewmodel in POST method has, and you can't have more than one entity with same primary key in memory when saving changes:
db.Entry(match).State = EntityState.Modified;
Instead of directly setting Match entity state like that, try using Attach() method before doing SaveChangesAsync():
db.Matches.Attach(match);
await db.SaveChangesAsync();
Or use AsNoTracking() to disable entity tracking for Match in GET action method which just used to retrieve data:
Match matchData = await db.Matches.AsNoTracking()
.Where(m => m.ID == id)
.Include(t => t.AwayTeam)
.Include(t2 => t2.HomeTeam)
.Include(x => x.Games.Select(g => g.GameType))
.FirstOrDefaultAsync();
If both possible solutions above doesn't work, set Match entity state to EntityState.Detached after retrieved query results in GET action method:
if (matchData != null)
{
context.Entry(matchData).State = EntityState.Detached;
}
As a side note, better to load Match entity by using its primary key in POST action method (with Find() method) or update property values based from existing entity loaded in memory rather than detaching and reattaching it again.
Similar issues:
ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value
Error attaching entity because of same primary key when trying to save an update
The first three solutions did not work. What I was ultimately forced to do to get a solution was follow along with your side note (at least I think that is what you were getting at). The issue was arising because the includes of the GameType and AppplicationUsers' were trying to create new entities of their respective objects instead of finding the information in the database and setting it to modified. Below is the updated controller code that has gotten it working:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(EditMatchesViewModel match)
{
if (ModelState.IsValid)
{
var updatedMatch = db.Matches.Find(match.ID);
updatedMatch.Date = match.Date;
updatedMatch.AwayTeam = match.AwayTeam;
updatedMatch.HomeTeam = match.HomeTeam;
updatedMatch.Division = match.Division;
updatedMatch.Games = new List<Game>();
foreach (var game in match.Games)
{
if (game.ID > 0)
{
var updatedGame = db.Games.Find(game.ID);
updatedGame.GameType = db.GameType.Find(game.GameType.ID);
updatedGame.AwayPlayer1 = db.Users.Find(game.AwayPlayer1.Id);
updatedGame.AwayPlayer2 = db.Users.Find(game.AwayPlayer2.Id);
updatedGame.HomePlayer1 = db.Users.Find(game.HomePlayer1.Id);
updatedGame.HomePlayer2 = db.Users.Find(game.HomePlayer2.Id);
updatedGame.AwayScore1 = game.AwayScore1;
updatedGame.AwayScore2 = game.AwayScore2;
updatedGame.HomeScore1 = game.HomeScore1;
updatedGame.HomeScore2 = game.HomeScore2;
updatedMatch.Games.Add(updatedGame);
}
}
db.Matches.Attach(updatedMatch);
db.Entry(updatedMatch).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(match);
}

Updating Master-Detail / Parent-Child data in MVC

Have a view with a master-detail style view for an invoice. Master is invoice, detail is invoice lines. I'm trying to get detail data items to save on upon an edit post but the detail data is lost upon reaching the post Edit on the controller. So the master data saves fine but the detail is obviously not saved.
Invoice Class:
public class Invoice
{
public Invoice()
{
}
[Required]
[Key]
public int InvoiceID { get; set; }
[Required]
[StringLength(30)]
[DisplayName("Invoice Number")]
public string InvoiceNumber { get; set; }
[Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[DataType(DataType.Date)]
[Column(TypeName = "Date")]
[DisplayName("Invoice Date")]
public DateTime InvoiceDate { get; set; }
public List<InvoiceLine> InvoiceLines { get; set; }
[ForeignKey("Client")]
public int OwnerClientIDFK { get; set; }
[DisplayName("Client")]
public Client Client { get; set; }
}
Invoice Line class:
public class InvoiceLine
{
public InvoiceLine()
{
}
[Key]
[Required]
public int InvoiceLineId { get; set; }
[Required]
[StringLength(255)]
[DisplayName("Item")]
public string ItemName { get; set; }
[DisplayName("Description")]
public string ItemDescription { get; set; }
[Required]
public int Quantity { get; set; }
[Required]
[DisplayFormat(DataFormatString = "{0:C}", ApplyFormatInEditMode = true)]
public decimal Value { get; set; }
[ForeignKey("ParentInvoice")]
public int InvoiceID { get; set; }
public Invoice ParentInvoice { get; set; }
}
Controller Edit (get):
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
// Invoice invoice = db.Invoices.Find(id);
Invoice invoice = db.Invoices.Include(i => i.InvoiceLines)
.Include(i => i.Client)
.Where(c => c.InvoiceID == id).FirstOrDefault();
if (invoice == null)
{
return HttpNotFound();
}
ViewBag.OwnerClientIDFK = new SelectList(db.Clients, "ClientId", "CompanyName", invoice.OwnerClientIDFK);
return View(invoice);
}
Controller Edit (post):
public ActionResult Edit([Bind(Include = "InvoiceID,InvoiceNumber,InvoiceDate,OwnerClientIDFK")] Invoice invoice)
{
if (ModelState.IsValid)
{
db.Entry(invoice).State = EntityState.Modified;
foreach (var invLine in invoice.InvoiceLines)
{
db.Entry(invLine).State = EntityState.Modified;
}
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.OwnerClientIDFK = new SelectList(db.Clients, "ClientId", "CompanyName", invoice.OwnerClientIDFK);
return View(invoice);
}
So in the above, when it reaches the foreach, it throws an exception because InvoiceLines is null.
View:
#model DemoApp.Entities.Invoice
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Invoice</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.InvoiceID)
<div class="form-group">
#Html.LabelFor(model => model.InvoiceNumber, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.InvoiceNumber, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.InvoiceNumber, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.InvoiceDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.InvoiceDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.InvoiceDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.OwnerClientIDFK, "OwnerClientIDFK", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("OwnerClientIDFK", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.OwnerClientIDFK, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<h2>Invoice Lines</h2>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="row">
<div class="col-md-8">
<table class="table">
<thead>
<tr>
<th>Item</th>
<th>Description</th>
<th>Qty</th>
<th>Unit Value</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.InvoiceLines.Count; i++)
{
<tr>
<td>#Html.EditorFor(x => x.InvoiceLines[i].ItemName, new { htmlAttributes = new { #class = "form-control" } })</td>
</tr>
}
</tbody>
</table>
</div>
</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")
}
How do I get it to update the detail / child data?
Any help appreciated. Thanks in advance.
You are not binding InvoiceLines on edit post action method
public ActionResult Edit([Bind(Include = "InvoiceID,InvoiceNumber,InvoiceDate,OwnerClientIDFK,InvoiceLines")] Invoice invoice)
{
What you probably need is to load the Invoice Lines when you get the parent entity.
db.Entry(invoice).Collection(i=>i.InvoiceLines).Load() should do that.
First off, this is kind of a tricky scenario. There are a lot of moving parts. I got really interested in this and wrote a sample application. :)
Here's the link to the repo on GitHub:
https://github.com/benday/asp-mvc-invoice-sample
There are a handful of things going on that are conspiring to cause problems in your code.
1) ironically, the Bind[] that you have in your Edit(Invoice model) method is getting in your way. If you remove it entirely, ASP MVC will attempt to bind everything. Right now, it's only binding what you tell it to bind and since you don't include the InvoiceLines collection, then it's coming up null.
2) if you want ASP MVC to attempt to bind data into your model, you need to POST that data back to the server. If those invoice lines aren't in an html form and aren't represented as form fields, then that data is just going to be missing.
The code you have to draw your invoice lines is missing most of the fields that would be required to populate your InvoiceLine objects.
#Html.EditorFor(x => x.InvoiceLines[i].ItemName, new {
htmlAttributes = new { #class = "form-control" } })
Editor Template for the InvoiceLine class
In my sample code, I created an editor template for the InvoiceLine class. This allows me to easily create bindable html for the InvoiceLines by calling #Html.EditorFor(model => model.Invoice.InvoiceLines) asdf
I know this isn't the simplest answer ever but I hope this helps you over the hump.

DropDownList has duplicate values

My dropdownlist has duplicate values being the item_brand. How can remove the duplicates? Have tried using distinct. Basically the user must select the item brand then a list of items will populate based on that brand. But if I have two products with the same brand that brand name will populate twice in the brandList
My view
#using (Html.BeginForm(new { OrderID = Model.OrderID }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Item Information</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<section class="panel">
<div class="panel-body">
<div class="form-group">
#Html.LabelFor(m => m.SelectedBrand, "Brand", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.SelectedBrand,Model.BrandList, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(m=>m.SelectedBrand, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.SelectedItem, "Description", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.SelectedItem, Model.ItemList, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.SelectedItem, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.item_order_quantity, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.item_order_quantity, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.item_order_quantity, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.HiddenFor(model => model.OrderID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.HiddenFor(model => Model.OrderID, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OrderID, "", new { #class = "text-danger" })
</div>
</div>
</div>
</section>
</div>
<section class="panel">
<div class="panel-body">
<input type="submit" value="Add Item" class="btn btn-default" style="float:right;" />
Back
</div>
</section>
<!-- JS includes -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="//ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.min.js"></script>
<script src="//ajax.aspnetcdn.com/ajax/mvc/4.0/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript">
var itemUrl = '#Url.Action("FetchItems")';
var items = $('#SelectedItem');
$('#SelectedBrand').change(function() {
items.empty();
$.getJSON(itemUrl, { brand: $(this).val()},function(data) {
if (!data) {
return ;
}
items.append($('<option></option>').val('').text('Please select'));
$.each(data, function(index, item) {
items.append($('<option></option>').val(item.Value).text(item.Text));
});
});
})
</script>
}
</body>
</html>
Controller
// GET: ItemOrder/Create
public ActionResult Create(int ID)
{
ORDER order = db.Order.Find(ID);
ItemOrderVM model = new ItemOrderVM() {
OrderID = order.OrderID
};
ConfigureViewModel(model);
return View(model);
}
[HttpGet]
public JsonResult FetchItems(int brand)
{
var data = db.Item.Where(l => l.ItemID == (brand))
.Select(l => new { Value = l.ItemID, Text = l.item_description });
return Json(data, JsonRequestBehavior.AllowGet);
}
private void ConfigureViewModel(ItemOrderVM model)
{
var brand = (from m in db.Item
select m);
model.BrandList = new SelectList(db.Item, "ItemID", "item_brand");
if (model.SelectedBrand.HasValue)
{
IEnumerable<ITEM> items = db.Item.Where(l => l.item_brand.Equals(model.SelectedBrand));
model.ItemList = new SelectList(items, "ItemID", "item_description");
}
else
{
model.ItemList = new SelectList(Enumerable.Empty<SelectListItem>());
}
}
ItemOrder View Model
public class ItemOrderVM
{
public int? ID { get; set; }
public int OrderID { get; set; }
public int ItemID { get; set; }
[DisplayName("Quantity")]
[Range(1, int.MaxValue, ErrorMessage = "Quantity must be greater than 0")]
public int item_order_quantity { get; set; }
[Display(Name = "Brand")]
public int ? SelectedBrand { get; set; }
[Display(Name = "Description")]
public int SelectedItem { get; set; }
public SelectList BrandList { get; set; }
public SelectList ItemList { get; set; }
public List<OrderVM> Orders { get; set; }
}
Your problem is in model.BrandList since you are selecting ItemId which I assume is unique so you have the same item_brand that appears multiple times
So all you need is to get a list of distinct brands.
In your ConfigureViewModel method:
model.BrandList = db.Item.Select(i => new SelectListItem{Text = i.item_brand, Value = i.item_brand}).Distrinct().ToList();
(If you have an item_brand_id property that is unique for each brend you should use it as value)
And then in your Fetch action:
[HttpGet]
public JsonResult FetchItems(string brand)
{
var data = db.Item.Where(l => l.item_brand == brand)
.Select(l => new { Value = l.ItemID, Text = l.item_description });
return Json(data, JsonRequestBehavior.AllowGet);
}

Resources