I am using boilerplate CRUD methods, which include BIND operations on the methods that catch the form submissions. I have discovered that if I have my fields as nullable both in the model as well as in the DB, and I do not include those fields in a CRUD operation (also no presence in BIND), these fields end up null when before they were filled. If I flag these fields as not nullable in the DB, I cannot complete the CRUD operation without including these fields in hidden form fields because they are not nullable.
How do I make a CRUD operation ignore these fields without adding them as hidden fields in the forms?? As in, do not null them, do not change their data.
For example, if I have my POST method as such:
public async Task<ActionResult> Edit([Bind(Include = "CompanyId,CompanyName,CompanyAddress,CompanyCity")] Company company) {
And there is a field both in the model as well as in the DB such as CompanyCity, if it is nullable in model and db it gets nulled with the update. If it is not nullable in the model and db, the update fails because the field is not nullable but the update wants to null it because it didn't exist in the bind.
I am also using only the base models, such as Company, for this example. However when I try to make another base model, such as EditCompanyViewModel, I am unable to pull data out of the database to put into that view model. The entire await command gets flagged as being not of the correct model/type.
Essentially, I need to know how to edit only part of a table, without messing/mucking/deleting the rest of the table entries and without creating a metric arseload of hidden form fields that exist purely to hold the data I don't want to edit.
I have a conceptual gap here, and I am metaphorically chasing my tail. I can't seem to bridge the gap to a solution.
EDIT:
My modified view model:
public class EditMarketingViewModel {
[Key]
public Guid CompanyId { get; set; }
[DisplayName("How did you hear of us")]
public Guid? HowHeardId { get; set; }
[DisplayName("eNewsletter")]
public bool eNewsletter { get; set; }
[DisplayName("Event Code")]
public Guid? EventCodeId { get; set; }
[DisplayName("Notes")]
public string MarketingNotes { get; set; }
#region Essentials
[HiddenInput, Timestamp, ConcurrencyCheck]
public byte[] RowVersion { get; set; }
[HiddenInput]
public DateTime Modified { get; set; }
[HiddenInput]
public string TouchedBy { get; set; }
#endregion
}
My view:
@model CCS.Models.EditMarketingViewModel
@{
ViewBag.Title = "Edit Marketing Info.";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>@ViewBag.Title</h2>
@using(Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<fieldset>
@Html.HiddenFor(model => model.CompanyId)
@Html.HiddenFor(model => model.RowVersion)
@Html.LabelFor(model => model.HowHeardId, htmlAttributes: new { @class = "control-label" })@Html.DropDownList("HowHeardId", null, " « ‹ Select a How Heard Type › » ", htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.HowHeardId, "", new { @class = "text-danger" })
@Html.LabelFor(model => model.eNewsletter, htmlAttributes: new { @class = "control-label" })@Html.EditorFor(model => model.eNewsletter, new { htmlAttributes = new { @data_on_text = "Yes", @data_off_text = "No" } })
@Html.ValidationMessageFor(model => model.eNewsletter, "", new { @class = "text-danger" })
@Html.LabelFor(model => model.EventCodeId, htmlAttributes: new { @class = "control-label" })@Html.DropDownList("EventCodeId", null, " « ‹ Select an Event Code › » ", htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.EventCodeId, "", new { @class = "text-danger" })
@Html.LabelFor(model => model.MarketingNotes, htmlAttributes: new { @class = "control-label" })@Html.EditorFor(model => model.MarketingNotes, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.MarketingNotes, "", new { @class = "text-danger" })
<input type="submit" value="Save" class="btn btn-default" />
</fieldset>
}
<p>[ @Html.ActionLink("Back to List", "Index", "Company") ]</p>
Now how do I modify my controller to work with it:
// GET: Company/EditMarketing
public async Task<ActionResult> EditMarketing() {
var id = new Guid(User.GetClaimValue("CWD-Company"));
Company company = await db.Company.FindAsync(id);
if(company == null) {
return HttpNotFound();
}
ViewBag.HowHeardId = new SelectList(db.HowHeard.Where(x => x.Active == true).OrderBy(x => x.SortOrder), "HowHeardId", "HowHeardType", company.HowHeardId);
ViewBag.EventCodeId = new SelectList(db.EventCode.Where(x => x.Active == true).OrderBy(x => x.EventCodeDate), "EventCodeId", "EventCodeName", company.EventCodeId);
return View(company);
}
EDIT 2:
My POST:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditMarketing([Bind(Include = "CompanyId,HowHeardId,eNewsletter,EventCodeId,MarketingNotes,RowVersion")] Company company) {
try {
if(ModelState.IsValid) {
TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
company.Modified = DateTime.UtcNow;
company.TouchedBy = User.Identity.GetFullNameLF();
db.Entry(company).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index", "Company");
}
} catch(DbUpdateConcurrencyException ex) {
var entry = ex.Entries.Single();
var companyValues = (Company)entry.Entity;
var databaseValues = (Company)entry.GetDatabaseValues().ToObject();
if(databaseValues.MarketingNotes != companyValues.MarketingNotes) { ModelState.AddModelError("MarketingNotes", "Current Value: " + databaseValues.MarketingNotes); }
if(databaseValues.eNewsletter != companyValues.eNewsletter) { ModelState.AddModelError("eNewsletter", "Current Value: " + databaseValues.eNewsletter); }
if(databaseValues.HowHeardId != companyValues.HowHeardId) { ModelState.AddModelError("HowHeardId", "Current Value: " + db.HowHeard.Find(databaseValues.HowHeardId).HowHeardType); }
if(databaseValues.EventCodeId != companyValues.EventCodeId) { ModelState.AddModelError("EventCodeId", "Current Value: " + db.EventCode.Find(databaseValues.EventCodeId).EventCodeName); }
ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");
company.RowVersion = databaseValues.RowVersion;
} catch(DataException dex) {
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists please inform your Manager, who will inform the developers." + dex);
}
ViewBag.HowHeardId = new SelectList(db.HowHeard.Where(x => x.Active == true).OrderBy(x => x.SortOrder), "HowHeardId", "HowHeardType", company.HowHeardId);
ViewBag.EventCodeId = new SelectList(db.EventCode.Where(x => x.Active == true).OrderBy(x => x.EventCodeDate), "EventCodeId", "EventCodeName", company.EventCodeId);
return View(company);
}
Please note that I am making use of concurrency to avoid data collisions. Hence the RowVersion column.