MVC UpdateModel is not updating some of the model properties - c#

In my MVC 5 EF Database First project I have been using UpdateModel method in my controller action successfully, but after including some additional fields (previously unused by application) I find that the new fields simply refuse to get values from the UpdateModel method call. The only thing of any significance I can identify is that the fields share part of their name.
Consider this class as an example:
public class Record
{
public int ID {get;set;}
public string Details {get;set;}
public string DetailsFile {get;set;}
...
}
The property/field DetailsFile was previously unused, but now is an optional field on the web form. It is stored in a <input type="hidden" name="DetailsFile" id="DetailsFile /> and is successfully posted to the controller action with the correct value (or empty string).
Within the controller action I handle the update like this:
[HttpPost]
public async Task<ActionResult> Edit(Record_EditView model, FormCollection collection)
{
if (ModelState.IsValid)
{
try
{
var record = await db.Record.FindAsync(model.ID);
UpdateModel(record, collection);
db.Entry(record).State = EntityState.Modified;
await db.SaveChangesAsync();
}
catch(Exception ex)
{
throw ex;
}
}
// do more stuff here
...
}
Which has been working fine, but with the additional field all fields except the DetailsFile get updated from the FormCollection that is passed in. I have inspected both the model and the collection and they have the correct value, but the record never gets the values until I do a second post of the same data. Then the values get pushed in to the fields as expected.
I don't get any errors thrown and am at a bit of a loss as to what is going on. For the time being I have resorted to modifying my controller action to this:
[HttpPost]
public async Task<ActionResult> Edit(Record_EditView model, FormCollection collection)
{
if (ModelState.IsValid)
{
try
{
var record = await db.Record.FindAsync(model.ID);
UpdateModel(record, collection);
record.DetailsFile = collection["DetailsFile"]; // <-- Manually insert DetailsFile value
db.Entry(record).State = EntityState.Modified;
await db.SaveChangesAsync();
}
catch(Exception ex)
{
throw ex;
}
}
// do more stuff here
...
}
And this works OK, however I'm sure that I shouldn't have to do this, and hope that someone out there can explain what I am overlooking!

Finally found the problem, it is not likely to benefit many people but just in case here is the answer.
More Information
In my project I started using a jquery plugin for styling file input elements in a more bootstrap fashion, namely Jasny Bootstrap.
The plugin works great, however as part of it's internal workings it takes steps to maintain state of existing data and avoid post conflict by using hidden input and renaming the file input like this:
this.$hidden.val('')
this.$hidden.attr('name', '')
this.$input.attr('name', this.name)
The Problem
Which ends up with element(s) that have an attribute name="" and that caused the page to post the element and the FormCollection to include an empty/"" item(s).
Despite the fact that no errors were thrown, nonetheless it seems to break the call to
UpdateModel(record,collection)
The Solution
To fix this and avoid posting the selected file (my project doesn't actually want the file to be posted, just the file path) I just intercept the form submit to remove the unwanted form elements prior to posting:
$('form').submit(function (e) {
e.preventDefault();
// get rid of any input elements that have name="" or file elemnts so will not mess with the posted form collection
$('input[name=""],input[type="file"]').remove();
this.submit();
})

Related

How to pass collection data through form?

I'm writing a simple application in .net core mvc.
The problem is with loading related data by view. The 1st time I load the view it gets through wonderfully, I attach data to the view by entity framework include extensions function etc.
This code is on GET, when just displaying the item to be updated, filling fields with current values.
public IActionResult Edit(long id)
{
ServiceResult<LectureDTO> result = _lectureService.GetById(id, new LectureIncludeOptions(true));
LectureEditViewModel model = new LectureEditViewModel
{
Lecture = new LectureEditDTO(result.Entity)
};
if (!result.Success)
{
model.HandleResult(result, ModelState);
}
return View(model);
}
2nd time around when submitting a POST, IF the model I've submitted is not valid or the result of the update is not okay, I'd like to just attach errors to the model and send it to the view (by the HandleResult method).
public IActionResult Edit(LectureEditViewModel model)
{
if (!ModelState.IsValid)
{
ServiceResult<LectureDTO> getResult = _lectureService.GetById(model.Lecture.Id, new LectureIncludeOptions(true));
if (!getResult.Success)
{
model.HandleResult(getResult, ModelState);
}
model.Lecture = new LectureEditDTO(getResult.Entity);
return View(model);
}
ServiceResult<LectureDTO> result = _lectureService.Update(model.Lecture, User.FindFirstValue(ClaimTypes.NameIdentifier));
if (result.Success)
{
model = new LectureEditViewModel()
{
Lecture = new LectureEditDTO(result.Entity)
};
}
model.HandleResult(result, ModelState);
return View(model);
}
This is how I'm currently loading relationship data on bad editing. I think it's wasteful if I already had it in the model before, just displaying the edit form. Is there a way to pass this data by model?
I've tried this in my form to pass the collection, but everytime I debug this controller action, the value for it is empty.
<input asp-for="Lecture.Lecturers" value="#Model.Lecture.Lecturers" class="form-control" type="hidden" />
Am I going about this all wrong? Is what I've been doing okay? What could I improve?
Thanks for taking the time to help me with this.
As Chris Pratt pointed out in comment to my thread, you should not post any data to the server, you don't want the user to be able to change. Therefore my case for using collections in this way is invalid. To post collections to the server, write them out in input fields and post them.

ConcurrencyException on HttpPost asp mvc

Hello I am trying to edit my table in db and is give me this error An update, insert, or delete instruction in the store has affected an unexpected number of rows (0). Entities may have been modified or deleted since loading.
trying many test and when i'm arrive on SaveChange is stop process
and incomplete operaion
This is the implementation
public ActionResult Edit(int? id)
{
db = new IdentityDBEntities();
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Article article = db.Articles.Find(id);
if (article == null)
{
return HttpNotFound();
}
ViewBag.Idc = new SelectList(db.Categories, "Id", "libelle", article.Idc);
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 https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Ida,description,UserId,Idc,titre,image")] Article article, HttpPostedFileBase image)
{
if (ModelState.IsValid)
{
if (image != null)
{
article.image = image.FileName;
}
db = new IdentityDBEntities();
article.UserId = System.Web.HttpContext.Current.User.Identity.GetUserId();
db.Entry(article).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Idc = new SelectList(db.Categories, "Id", "libelle", article.Idc);
return View(article);
}
Article was loaded in one instance of a DbContext, and you are attempting to save it with another instance. The entity would need to be associated to the new context before being saved. (I.e. Attach or Update) However, I do not recommend this approach.
As a general rule you should avoid passing entities to and from a web client. If you accept an entity to a web client, it is a simple matter to find the call passing the modified entity to the server, apply a breakpoint, and modify the contents using the debugger before resuming. If the server merely attaches the entity to a context and saves the changes, I can modify data in ways the application should not allow. It also involves sending more information to/from the client than is typically necessary.
You also run into the problem that the data supporting an entity loaded at one point in your current session has been modified by another session between the time you read the data, and the time you're prepared to update it. Systems should not merely attach and overwrite data without first verifying (via something like a Timestamp or last modified date/time) that the data hasn't been updated since the entity was loaded. From there the system can take an appropriate action. (log that data will be overwritten, overwrite, or merge & notify the user to review their changes.)
edit: To outline an example.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Ida,description,UserId,Idc,titre,image")] Article article, HttpPostedFileBase image)
{
if (!ModelState.IsValid)
{
ViewBag.Idc = new SelectList(db.Categories, "Id", "libelle", article.Idc);
return View(article);
}
using (var db = new IdentityDBEntities())
{
var existingArticle = db.Articles.Single(x => x.ArticleId == article.ArticleId);
if (existingArticle.RowVersion != article.RowVersion) // Or compare LastModifiedDateTime etc.
{
// Set a validation state to tell the user that the article had changed since they started editing. Perhaps merge values across, but send the article back.
return View(existingArticle);
}
if (image != null)
existingArticle.image = image.FileName;
existingArticle.UserId = System.Web.HttpContext.Current.User.Identity.GetUserId(); // can this be updated?
db.SaveChanges();
}
return RedirectToAction("Index");
}
The key differences:
DbContext's are disposable so should be wrapped in a using block.
We load the article from the context, check if it's been modified since we had originally loaded it. (other validation checks to ensure the user can modify this article would be prudent as well) if it isn't out of date and is valid, transfer the fields across and save the changes.

Best way to control unicity of an attribute using EF Code First

I've got a class User which has an attribute Name that must be unique.
So far I've investigated 3 ways of checking this:
Annotations
[StringLength(100)]
[Index(IsUnique = true)]
public string Name { get; set; }
Problem is, by trying to insert a user with a repeated name it throws this ex:
as you can see, I would have to navigate into the inner exceptions (which I don´t know if it is possible, but I assume it is) and the last inner exception´s message is not user friendly at all.
Fluent Api
https://stackoverflow.com/a/23155759/5750078
I haven´t tried it but I believe it is has the same problem that Annotations.
Check by hand
controller code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Password,Profile")] User user)
{
if (ModelState.IsValid)
{
lock (locker)
{
validateNameUnicity();
db.Users.Add(user);
db.SaveChanges();
}
return RedirectToAction("Index");
}
return View(user);
}
Problem: the check depends on my code, which may not be as accurate as date base checkings. Besides I will need to program more logic than the other two options. And last but no least if somehow I access the database directly there will be no checks at all.
I need to know which is the best practice to do this, because I'm trying to learn on my own, and I would like to do things as best as possible.
You should actually do both, a check in code and a uniqueness index as a final guard when concurrent users manage to insert identical records after all. (Because of the latency between the check and the actual insert).
This means that you always have to catch exceptions when you call SaveChanges, but that's not a bad idea anyway.
For the uniqueness check you could use the mechanism I described here, just change email into Name and you're good to go.
You could dig up the last exception message from a chain of inner exceptions by this extension method:
public static string GetDeepestExceptionMessage(this Exception exception)
{
string msg = string.Empty;
while (exception != null)
{
msg = exception.Message;
exception = exception.InnerException;
}
return msg;
}

Differentiate Create from Update in Content Part Driver in Orchard CMS

I want to serve different views which use different ViewModel objects depending on Actions. This can be achieved in a conventional ASP.NET MVC paradigm.
[HttpGet]
public ActionResult Create() {
return View(new CreateViewModel()); //this serves Create.cshtml View
}
[HttpGet, ActionName("Create")]
public ActionResult CreatePOST(CreateViewModel viewModel) {
if (!ModelState.IsValid) {
return View(viewModel); //if error, re-serve Create.cshtml View
}
// Create new model into database
return RedirectToAction("Index");
}
[HttpGet]
public ActionResult Edit(int Id) {
var model = RetriveModel(Id);
var viewModel = new EditViewModel { Id = model.Id, Name = model.Name };
return View(viewModel); //this serves Edit.cshtml
}
[HttpPost, ActionName("Edit")]
public ActionResult EditPOST(EditViewModel viewModel) {
if (!ModelState.IsValid) {
return View(viewModel); //if error, re-serve Edit.cshtml View
}
// Update model in database
return RedirectToAction("Index");
}
How do I do the same to Orchard Content Part? It seems that the overridable Editor method in a ContentPartDriver fused both Create and Update actions together. How do I tell if the method is creating or updating? Something like
// GET
protected override DriverResult Editor(CustomContentPart part, dynamic shapeHelper) {
if (IsNewRecord) {
return ContentShape("Parts_CustomContentPart_Create" () =>
shapeHelper.EditorTemplate(TemplateName: "Parts/CreateTemplate", Model: new CreateViewModel(), Prefix: Prefix)
);
} else {
return ContentShape("Parts_CustomContentPart_Edit" () =>
shapeHelper.EditorTemplate(TemplateName: "Parts/EditTemplate", Model: BuildEditViewModel(part), Prefix: Prefix)
);
}
}
// POST
protected override DriverResult Editor(CustomContentPart part, IUpdateModel updater, dynamic shapeHelper) {
object viewModel;
if (IsNewRecord) {
viewModel = new CreateViewModel();
} else {
viewModel = new EditViewModel();
}
update.TryUpdateModel(viewModel, Prefix, null, null);
return Editor(part, shapeHelper);
}
I'm a beginner in Orchard still learning the ropes on how Orchard does things. Pardon me if my questions are too trivial.
Check for a content item id, if it is null, or possibly 0, I forget, then you are in the process of creating a content item. If it does have a value then you are editing. You can also use this in your view, can be quite handy.
If you need custom functionality to be called on creation/updating then you could consider using handler methods?
So in your parts handler add something like
OnCreated<MyPart>((ctx, part) => CreateItems(part));
Where CreateItems is a method with your part as a parameter. There are a bunch of content item events you can hook into, there is a neat list in the docs: http://docs.orchardproject.net/Documentation/Understanding-content-handlers
As always, check the source code for good examples of their usage.
EDIT
Apparently checking for null id doesn't work, I checked in some of my modules were I used it and have used the following check:
Model.ContentItem.VersionRecord == null || !Model.ContentItem.VersionRecord.Published
Although this question has been asked and answered, just thought of posting my findings so I can find it later.
ContentItem.Id is indeed 0 when the content item isn't created yet. For example, when you're about to create a new Page, ContentItem.Id == 0. Now just click on the Save button without filling up the form and validation will fail since the required field Title wasn't provided and we're getting back the same view with an error. Since validation failed, technically we don't consider the content item to be created yet. However, at this point Orchard already treating it as an existing content item. Orchard even went as far as obtaining and increasing the Identity counter of the Content Item Record table (Orchard_Framework_ContentItemRecord) from the database and assigning it as an Id to the content item.
Orchard even wired up all the Version Records, making it pretty much a full-fledged content item. All these for a content item that failed validation during creation and facing possibility of being discarded altogether. The only thing Orchard hasn't done is inserting it into the database (it's only residing in memory at this point). Therefore there's really no other ways to tell if a content item is an existing one or one that was about to be created other than checking it against the database and see if the content item was really there.
var contentItemRepository = _workContext.Resolve<IRepository<ContentItemRecord>>();
var contentItemRecord = contentItemRepository.Get(Model.ContentItem.Id);
if (contentItemRecord == null) {
isNew = true;
}
or we could also use the IContentManager to do the same thing
var contentManager = Model.ContentItem.ContentManager;
var contentItem = contentManager.Get(Model.ContentItem.Id, VersionOptions.AllVersions);
if (contentItem == null) {
isNew = true;
}
Edit:
Apparently I spoke too soon. When I said above that Orchard hasn't inserted the content item into the database yet and it still resides in memory, it actually already in the database, in a yet to be committed Transaction. In the case above where validation fails, the transaction will be rolled back at the end. The correctness of the above code depends on when it was executed. If it was executed before the transaction was cancelled and rolled back, the content item is still in the database and won't yield an accurate result. If it was executed after transaction rollback (eg. in a View), then it'll behave as expected.
How Orchard handles content item creation can be seen in Orchard.Core.Contents.Controllers.AdminController.CreatePOST(string, string, Action<ContentItem>):
_contentManager.Create(contentItem, VersionOptions.Draft);
var model = _contentManager.UpdateEditor(contentItem, this);
if (!ModelState.IsValid) {
_transactionManager.Cancel();
return View(model);
}
The content item was being created first before it was being fed into IContentManager.UpdateEditor() to validate.
Update:
Filed a bug at
https://github.com/OrchardCMS/Orchard/issues/6534

Why does ModelState report errors when I update the model before the ModelState.IsValid check?

My controller code looks like:
[HttpPost]
public ActionResult Create(ExampleViewModel model)
{
model.User.RegistrationNumber = this.RegistrationNumber;
if (ModelState.IsValid)
{
}
return View("Create", model);
}
I keep getting a validation error message saying that "Registration Number cannot be blank" yet I am setting it explicitly.
Do I have to reset ModelState since I modified the model somehow?
What happenned basically I set the textbox to disabled, and then during the form post the data was lost so I have to explicitly set it again.
Because you set the text box to disabled, the data wasn't posted.
As you're setting the value yourself, you can just remove the errors on that field by doing the following:
ModelState.Remove("User.RegistrationNumber");
before calling ModelState.IsValid.
By the time the line of code in your controller which sets the registration number, the ModelState validation has already occurred. It occurs prior to invoking the Create() method on your controller. It's hard to know from your description what exactly you are trying to achieve, but if you don't want that field validated you could turn off validation for that field by commenting out the [required] attribute.
The validation happens before you call IsValid, and the error will key be present in the dictionary.
I'm not sure if this is the best way to handle it, but I've been doing something like this:
[HttpPost]
public ActionResult Create(ExampleViewModel model)
{
if (ModelState["User.RegistrationNumber"].Errors.Count == 1) {
model.User.RegistrationNumber = this.RegistrationNumber;
ModelState["User.RegistrationNumber"].Errors.Clear();
}
}
The other solutions here didn't work as expected in .NET Core 3.1 so I used the following alternative, which explicitly sets the Validation state of only the desired model property.
if (ModelState["Property"].Errors.Count > 0)
{
model.Property = someRequiredProperty; // db query using FirstOrDefault()
if (model.Property != null)
{
ModelState["Property"].ValidationState = ModelValidationState.Valid;
}
}
if (ModelState.IsValid)
{
// ...
}

Categories

Resources