ASP.NET MVC Persisting mdoel's ID value when Editing - asp.net-mvc

public Edit(int? id){ /* Codes */ }
[HttpPost]
public Edit(Item model){ /* Codes */ }
I retrieve a copy of Item in the first Edit method, which would contain a value for ItemID. But when it gets to the HttpPost method, the id value's lost.
If switched to
public Edit(int? ItemID){ /* Codes */ }
[HttpPost]
public Edit(Item model){ /* Codes */ }
this way ItemID can be persisted in the Item model.
But is this a good way to handle it? Will ASP.NET MVC always be able to know that it needs to plug "ItemID" into Item?
and are there other ways to persist the ID value? Thanks.

I cannot understand how do you lose id at the HttpPost handling. Maybe you should check your binder and possibly write one for yourself? In my experience default binders are a little cumbersome. You could start from here although I don't pretend it's the best solution. In case you need to write many binders by hand take a look at some tools that could help you make conversion in declarative way like AutoMapper .

Have you tried adding the id as a parameter to the Post action?
public Edit(int? id){ /* Codes */ }
[HttpPost]
public Edit(int id, Item model){ /* Codes */ }
This way, when the form is posted back, the id will be populated from the URL.

Is the property on your Item model called ItemID? If so, then the default model binder won't populate it if you're passing around a field called ID. If you change your method signatures so that the parameter names match up with your Item property names it should work as expected.

Phil Haack had a post that may or may not be related to what you're doing.
Also, if you're not sending the ID out to the client as part of a form (hidden field or whatnot) and it isn't part of the POST URL then it would only make sense that you wouldn't have the ID field populated properly on POST.

The MVC 2 way of solving the ID issue is Optional URL Parameters.
If you're still on MVC 1 then use a binding attribute on the method argument:
[HttpPost]
public ActionResult Edit([Bind(Exclude = "ItemID")] Item model)
{
// ...
}

Little late answer but when using razor its common to use a hidden field in order to bind the Id to the model.
#Html.HiddenFor(model => model.Id)
A complete form post could look like this:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Address</h4>
<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.StreetLine1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.StreetLine1, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.StreetLine1, "", 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>
}

Related

Edit user removes password - ASP.NET MVC

I am trying to edit the data for one of my users. However, whenever I edit something, the passwords which are hidden, are also being changed and apparently set to null, which render the user unable to log in next time he wants to login. I know that I might have been able to solve the issue by using ViewModels, but im trying to do it without.
Model
public class User : IdentityUser
{
[Display(Name = "First name")]
public String FirstName { get; set; }
[Display(Name = "Last name")]
public string LastName { get; set; }
public string Email{ get; set; }
}
Please notice that the User-class extends from IdentityUser which holds password variables.
Edit in Controller
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "FirstName,LastName,Email,PhoneNumber")] User user)
{
if (ModelState.IsValid)
{
db.Entry(user).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
View for Edit
<div class="form-horizontal">
<h4>User</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.FirstName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LastName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.LastName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.LastName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Email, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Email, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Email, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PhoneNumber, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PhoneNumber, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PhoneNumber, "", 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>
}
It is my understand that the Bind-parameter in the Edit-method either whitelist or blacklist the variables for editing. So for that reason i removed all of the values that shouldnt be edited by the user in Bind.
There are a couple of problems here. First, don't present these fields to the user in the first place. I can't imagine a reason why a user should be able to edit their "locked out" status, or their hashed password. Only include in the UI the fields which the user should actually be modifying. Hell, even this has horrible idea written all over it:
#Html.HiddenFor(model => model.Id)
You're not only allowing the user to edit everything about their user record, but you're also allowing them to specify another user record to edit. So any user in the system can completely edit any other user in the system.
Hopefully you see how this is a bad thing :)
Now, you can (and often must) include the identifier in a hidden field. The above problem is mainly bad because of what else you're doing:
db.Entry(user).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
You are completely and implicitly trusting whatever the user sends you to be a whole, correct, and complete record for the database. And replacing whatever existing record is there with whatever the user sends you. That's... not good.
This approach can work for some models, but certainly not sensitive ones like user security data.
Instead, fetch the existing record and only edit the necessary fields. Something like this:
var existingUser = db.Users.Single(u => u.Id == currentUserId);
existingUser.FirstName = user.FirstName;
existingUser.LastName = user.LastName;
// etc.
db.SaveChanges();
Notice that I used an otherwise undefined variable called currentUserId. Do not use model.Id, because again that's allowing the user to specify which other user they want to edit. Determing the current user ID by their current logged in session, not by what they send in the form. However you currently identify your users. (User.Identity?)
In short...
Only let the user see/edit what they're allowed to
Validate in the save action that the user is allowed to edit that data (never assume that they must be allowed to simply because they previously opened the page)
Only update the values meant to be updated in that operation, don't just wholesale replace an entire record of sensitive data

Troubleshooting simple create-from

I am trying to read a value from a view in aps.net mvc: I am aware that this seems like a very basic issue, however, i could not find any solution for this, so i am turning to you: In my case, it seems as if the parameter playlistModel.Model.Name is never sent, or at least is null.
My controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(PlaylistViewModelDetails playlistModel)
{
if (!String.IsNullOrEmpty(playlistModel.Model.Name))
{
//this is never called due to playlistModel.Model.Name being null.
return RedirectToAction("Index");
}
return View(playlistModel);
}
#model Orpheus.Models.ViewModels.PlaylistViewModelDetails
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Erstellen" class="btn btn-default" />
</div>
</div>
</div>
}
public class PlaylistViewModelDetails
{
public PlaylistModel Model = new PlaylistModel(); //a seperate class containing a string value, which must be read from the form
}
Thank you for helping me to solve this issue!
Your PlaylistViewModelDetails contains only a field for Model. The DefaultModelBinder only binds properties, not fields.
Change your model to
public class PlaylistViewModelDetails
{
public PlaylistModel Model { get; set; }
}
and add a parameter-less constructor if you want to initialize PlaylistModel
public PlaylistViewModelDetails()
{
Model = new PlaylistModel();
}
Note also Name in PlaylistModel also need to be a property.

Input field always sending null value

I have 3 input fields(User name, email & password) in my registration form. All of them are mandatory. But it looks like User Name field is always sending null value to controller and I'm getting unexpected validation message. Any help would be appreciated. P.S. rest 2 fields are just fine
HTML:
#using (Html.BeginForm("Create", "Registration", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Registration Form</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.UserName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UserName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UserName, "", new { #class = "text-danger" })
</div>
</div>
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Email,Password")] UserRegistration userRegistration)
{
if (ModelState.IsValid)
{
db.UserSet.Add(userRegistration);
db.SaveChanges();
return View("Index");
}
return View(userRegistration);
}
I suppose that you need to change the Bind annotation to include a UserName and not a Name property
public ActionResult Create([Bind(Include = "UserName,Email,Password")] UserRegistration userRegistration)
When you use the Bind Attribute you define a list of property names for which the mvc engine should allow (include) the binding. The UserRegistration model has clearly a property named UserName and this property is not allowed in the binding list of names

MVC validation with only HTML

I am working on a contact us form and I was able to do a successful validation but i have a small problem,
when I submit and miss one of the required values, the form comes back with the error message and does not keep the value and I have noticed that only happens if I use HTML only, it will work fine with HTML helpers.
Here is a snippet of my code:
<div class="form-group">
#Html.LabelFor(model => model.FirstName, "First Name")
<div>
<input class="form-control" name="FirstName" id="FirstName" data-val="true" type="text" value="" />
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
But it will work for the last name field:
<div class="form-group">
#Html.LabelFor(model => model.LastName, "Last Name")
<div>
#Html.EditorFor(model => model.LastName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.LastName, "", new { #class = "text-danger" })
</div>
</div>
Filling the info:
Notice after submitting the last name stays there but the first name disappears:
Any help will be appreciated :)
Because your input tag's value property value is set to an empty string.
You should use the Html.TextBoxFor helper method to generate the inuput field and it will show you the value you posted when your model validation fails. The helper method will also generate the relevant html5 attribute needed for client side unobtrusive validation.
#model ContactViewModel
#using(Html.BeginForm())
{
#Html.TextBoxFor(s=>s.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
<!-- Other fields -->
<input type="submit" />
}
Assumin your HttpPost action method is returning the posted model back to the view which Model validation fails
[HttpPost]
public ActionResult Contact(ContactViewModel model)
{
if(ModelState.IsValid)
{
// to do : return something
}
return View(model);
}

Does the name of parameter have to be model?

Hit a strange issue where my model is not binding and shows up on the controller as null.
I have a form doing a httppost. My breakpoint in the controller is hit and the parameter I expect to be my model is null.
Looking at some example code on another page that works, I copied and pasted it and the only difference was the name of the parameter was 'model' instead of message.
View
#model Site.Models.ContactMessage
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>ContactMessage</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Message, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Message, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Message, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.To, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.To, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.To, "", 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>
Controller
public ActionResult Contact()
{
return View();
}
[HttpPost]
public ActionResult Contact(ContactMessage message)
{
var m = message;
return View();
}
and it worked. I thought I must have entirely missed something about naming convention. Found you can use Bind, from reading a heap of other posts, to change the prefix like;
public ActionResult Contact([Bind(Prefix = "model")] ContactMessage message)
but that didn't work, still null. Going to rename it to model so it works and I can move on but would like to know why it's not binding if not called model.
public ActionResult Contact(ContactMessage message)
Changed back to this as above but still returns a null.
Interestingly, if I open up another MVC app, that one has whatever parameter names I want and works fine. It's using an older version of MVC 5 (not updated it yet but I will do that and see if anything happens. I don't expect it will.)
Your problem is that you model contains a property named Message and you also have a parameter named message The DefaultModelBinder reads the form values which will include message = "someTextValue" and searches for model properties that have the name message. It finds the one in you model and sets it value (all OK so far) but then it finds another one (your parameter) and tries to set the value of a complex object string value (in effect ContactMessage message = "someTextValue";) which fails so the model becomes null

Resources