ASP.Net Edit Losing Values - c#

For an example, I am creating an ASP.Net MVC controller for the following model:
public class MyItem
{
public int ID { get; set; }
public int ItemTypeID { get; set; }
public string Description { get; set; }
public DateTime CreateDate { get; set; }
public DateTime? ModifyDate { get; set; }
}
For this example, Description is the only field the user will be able to see or modify.
I set ItemTypeID and CreateDate when the record is first created.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(
Include = "ID,ItemTypeID,Description,CreateDate,ModifyDate")] MyItem myItem)
{
if (ModelState.IsValid)
{
// Set values for behind-the-scenes columns.
myItem.ItemTypeID = 1;
myItem.CreateDate = DateTime.Now;
db.MyItems.Add(myItem);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myItem);
}
My Edit view only has a control for Description, since that's the only one I'll need on the form.
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>MyItem</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ID)
<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>
<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>
}
My HttPost Edit function sets the ModifyDate.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(
Include = "ID,ItemTypeID,Description,CreateDate,ModifyDate")] MyItem myItem)
{
if (ModelState.IsValid)
{
// Set modified date.
myItem.ModifyDate = DateTime.Now;
db.Entry(myItem).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myItem);
}
My problem is when it executes the HttpPost Edit, the ItemTypeID and CreateDate are cleared out on the object it sends, since I don't include them on the view. Will my Edit forms need hidden fields for every unused column, or is there a better way to carry over those values?

Will my Edit forms need hidden fields for every unused column
No, but it will need fields for any used value. This would include any value you need to be included in the model when it's sent back to the server. (It's also of course worth noting that any value sent to the server can be modified by the user, and user input shouldn't be implicitly trusted for any security or authorization purposes.)
Essentially you have two options:
Include in the form POST any values needed to re-construct your object.
Include in the form POST an identifier to refer to your object, and use that identifier to fetch the object from the data and re-construct it from that.
The latter approach may indeed work for you, since you already include the identifier in the form:
#Html.HiddenFor(model => model.ID)
You can use that to fetch the actual model instance from the database, then use the subset of form data you do receive to change relevant values on that model instance.

Related

MVC Model data is not binding

I have created form to add customer. I rendered customer page with Viewmodel. View Model class as follows,
public class CustomerViewModel
{
public IEnumerable<MemberShipType> MemberShipTypes { get; set; }
public Customer Customers { get; set; }
}
public class Customer
{
[Display(Name ="Customer ID")]
public int CustomerId { get; set; }
[Required(ErrorMessage = "Please enter customer name")]
[StringLength(255)]
[Display(Name ="Customer Name")]
public string CustomerName { get; set; }
public MemberShipType MemberShipType { get; set; }
[Required(ErrorMessage = "Please select membership type")]
[Display(Name = "Membership Type")]
public byte MembershipTypeId { get; set; }
}
public class MemberShipType
{
[Display(Name ="Membership Id")]
public byte Id { get; set; }
[Required]
[Display(Name = "Subscription Plan")]
public string Name { get; set; }
}
After adding that class, we have created Action to save customer form data using a single model class(Not viewModel)
I have created Customer form using Viewmodel to display with Membership type data.
UI is rendering fine with the below code. But, I am not able to get the model data in the action method.
If I directly use the viewmodel in the action data is coming fine. The problem needs to map all the view model property to a particular model.
It's required more time to map model property each time.
Can any know how to directly use entity framework add method with customer Model(Not View model)
#using (Html.BeginForm("Save", "Customer", FormMethod.Post))
{
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.Customers.CustomerName, htmlAttributes:
new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(m => m.Customers.CustomerName,
new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(m => m.Customers.CustomerName, "",
new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Customers.MembershipTypeId, htmlAttributes:
new { #class = "control-label col-md-2" })
<div class="col-lg-10">
#Html.DropDownListFor(m => m.Customers.MembershipTypeId,
new SelectList(Model.MemberShipTypes, "Id", "Name"),
"Please Select", new {#class = "form-control"})
#Html.ValidationMessageFor(m => m.Customers.MembershipTypeId,
"",
new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<input type="reset" value="Reset" class="btn btn-default" />
<button type="submit" class="btn btn-primary">Save</button>
</div>
</div>
</div>
}
The below action model always return null.
[System.Web.Mvc.HttpPost]
public ActionResult Save(Customer customer)
{
if (customer.CustomerId == 0)
{
_context.Customer.Add(customer);
_context.SaveChanges();
}
}
I am getting a Customer model is null. If I pass customerViewModel data is coming. Can anyone know the answer on how to directly get the data in the model class?
Since you're binding the view to a model of CustomerViewModel and you're using the HTML helpers EditorFor (lambda overload), you should expect that same model in return on your POST. When you use LabelFor and EditorFor, the automatic naming will probably give you something like "Customers_CustomerName" so it can put your view model back together again.
One solution is to change your expected model on your save method to be a 'CustomerViewModel' and just use the .Customer property to get the data.
[System.Web.Mvc.HttpPost]
public ActionResult Save(CustomerViewModel model)
{
if (model.Customer.CustomerId == 0)
{
_context.Customer.Add(model.Customer);
_context.SaveChanges();
}
}
Another option is to name your input fields manually to reflect properties of the 'Customer' model directly and it will map them into a "Customer" model for you on POST. eg Instead of #Html.LabelFor(m => m.Customers.CustomerName you'd just use #Html.EditorFor("CustomerName", Model.Customers.CustomerName)
<div class="form-group">
#Html.LabelFor(m => m.Customers.CustomerName, htmlAttributes:
new { #class = "control-label col-md-2" })
<div class="col-md-10">
*********EDIT HERE --> #Html.TextBox("CustomerName", Model.Customers.CustomerName
new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(m => m.Customers.CustomerName, "",
new { #class = "text-danger" })
</div>
</div>
I got the solution for this issue. The reason for the problem is, I've created controller and Model in the same name. So, I've changed Model name with different alias in Viewmodel like below,
public class CustomerViewModel
{
public IEnumerable<MemberShipType> MemberShipTypes
{ get; set; }
public ProductionCustomer productionCustomers
{ get; set; }
}
If we use model object in controller to Get/POST it will work even if we rendered the form with ViewModel(Multiple Model). By default mvc will identify the model to post in the form.

How to pass dynamically created textbox values from view to controller in MVC

I want to pass all values from dynamically generated text-boxes from view to controller.
My model:
public class QuestionModel
{
[Required(ErrorMessage = "{0} is required")]
[Display(Name = "Question here")]
public string Question { get; set; }
}
My view:
#using (Html.BeginForm("Add_Question", "Home", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
<div class="form-group">
//here I'm generating dynamic textboxes
#for (int i = 1; i <= numberOfQuestions; i++)
{
<div class="col-md-12">
#Html.LabelFor(model => model.Question, new { })
#Html.TextBoxFor(model => model.Question, "", new { #required = "required", #class = "form-control" })
#Html.ValidationMessageFor(model => model.Question, "", new { #class = "text-danger" })
</div>
}
</div>
<div class="form-group">
<div class="col-md-12">
<input type="submit" value="Done" class="btn-success form-control" />
</div>
</div>
}
My controller:
public ActionResult Add_Question()
{
return View();
}
[HttpPost]
public ActionResult Add_Question(QuestionModel model)
{
//Get all textbox values here
return RedirectToAction("Home", "Home");
}
Should I create a list of strings for this? If yes then how?
Please help.
You can slightly modify the viewmodel property and loop inside view to contain every element from List<string> like this:
Model
[Display(Name = "Question here")]
public List<string> Question { get; set; }
View
#for (int i = 0; i < numberOfQuestions; i++)
{
<div class="col-md-12">
#Html.LabelFor(model => model.Question)
#Html.TextBoxFor(model => model.Question[i], "", new { #required = "required", #class = "form-control" })
</div>
}
Note that collection index starts from zero, hence the first question should have index of 0.
Additional note:
You may need to create custom validation attribute for List<string> as provided in this reference because default RequiredAttribute only checks for null and not total count of entire collection items (empty collection with Count = 0 is not null).
Related issue:
Asp.net razor textbox array for list items
Return the view with the model:
[HttpPost]
public ActionResult Add_Question(QuestionModel model)
{
return View(model);
}
you can retrieve the values using the Formcollection object, but your dynamically created text boxes should have unique id for eg:- Question1, Question2 etc.
And then you can loop through Formcollection object.
below code is just for single textbox you need to create loop and retrieve
public ActionResult AddQuestion(FormCollection form)
{
string question1 = form["Question1"].ToString();
return View();
}

C# razorview DropDownListFor 'Value cannot be null'

I am new to ASP.NET MVC and I'm working on my first project just for fun.
I get this ArgumentNullException and I cannot figure out what's wrong.
This is my model:
public class SpeciesLabel
{
[Key]
[Required]
public string Name { get; set; }
[Required]
public CustomGroup CustomGroup { get; set; }
[Required]
public Family Family { get; set; }
[Required]
public Genus Genus { get; set; }
[Required]
public Species Species { get; set; }
}
public class SpeciesLabelDbContext : DbContext
{
public SpeciesLabelDbContext()
: base("DefaultConnection")
{
}
public DbSet<SpeciesLabel> SpeciesLabel { get; set; }
}
This is the controller:
public ActionResult Create()
{
List<SelectListItem> customGroups = new List<SelectListItem>();
IQueryable<string> customGroupsQuery = from g in customGroupsDb.CustomGroup
select g.Name;
foreach (var element in customGroupsQuery)
{
customGroups.Add(new SelectListItem()
{
Value = element,
Text = element
});
}
ViewBag.CustomGroup = customGroups;
This is the controller POST request:
public ActionResult Create([Bind(Include = "CustomGroup,Family,Genus,Species")] SpeciesLabel speciesLabel)
{
if (ModelState.IsValid)
{
db.SpeciesLabel.Add(speciesLabel);
db.SaveChanges();
return RedirectToAction("Create");
}
return View();
}
And this is the view:
<pre>
#model PlantM.Models.PlantModels.SpeciesLabel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Species label</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.CustomGroup, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.CustomGroup, new SelectList(ViewBag.CustomGroupList, "Value", "Text"), "Please select...", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CustomGroup, "", new { #class = "text-danger" })
</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>
}
</pre>
I have inputs for all properties in the view but I cut them as they are similar to this one and the exception would be the same. Only property Name is not returned from the view as it will be designed in the controller (concatenation of the other properties).
This is the exception I get when I submit the form:
ArgumentNullException
Edit:
After adding the ViewBag initialization in the POST Create method the problem with the ArgumentNullException is resolved but I still receive Null value arguments and the object cannot be created due to this and the Create view is recalled again and again!? Can anyone advise why these #Html.DropDownListFor do not post any value to the controller?
From the comment, it sound like you see the view on first visit, but a null exception happen after you post.
If above assumption is correct, then I think your problem is because when you post back, your model did not pass the validation (for example, maybe a required input field did not post back value), which means ModelState.IsValid is false, so return View() was called
Here is the problem, you are not setting the ViewBag.CustomGroup = customGroups; in before return, hence ViewBag.CustomGroup is null, that is why you are seeing the exception.
init the ViewBag like how you did it on get then you should be able to see the page.

Entity A depends on Entity B, how to send all entity's B info to POST Create method of Entity A using MVC Code First

I've got a User class which has a Name (unique and required) a password (required) and a Profile (required). The Profile class has a Name which is unique and required too. Both classes have an Id that acts as primary Key while generating data base using code first.
I want to allow the creation of new users in my page, here is some code to do this:
Controller
// GET: Users/Create
public ActionResult Create()
{
populateViewBagWithProfilesAsSelectListItem();
return View();
}
private void populateViewBagWithProfilesAsSelectListItem()
{
IEnumerable<SelectListItem> profiles = db.Profiles.ToList().
Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.Name
});
ViewBag.Profiles = profiles;
}
// POST: Users/Create
// 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.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Password,Profile")] User user)
{
user.Profile = db.Profiles.Find(user.Profile.Id);
ModelState.Clear();
TryValidateModel(user);
lock (UsersLocker)
{
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
return RedirectToAction("Index");
}
}
populateViewBagWithProfilesAsSelectListItem();
return View(user);
}
Create View
#model ProceduresRecord.Web.MVC.Models.User
#{
ViewBag.Title = "Crear";
}
<h2>#ViewBag.Title</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Usuario</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.Password, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Password, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Password, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Profile, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Profile.Id, (IEnumerable<SelectListItem>)ViewBag.Profiles, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Profile.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Crear" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Volver a la Lista", "Index")
</div>
this results in this page:
As you can see I populate the profiles from data base, I show the profiles names to the user, once he picks one and clicks on create, an Id of that profile is sent to the POST method Create in the controller. Then I struggle to get the complete Profile from database using the provided Id, I assign the complete profile to the user and re validate the ModelState (so that it changes to true... it was false because the profile didn't have a name until now).
It all seems to work, but I was wondering...
Isn't there a better way to do this? I mean, I already got the profiles from data base when populating the html select, wouldn't it be awesome to be able to send the complete profile to the POST in one shot and avoid this code:
user.Profile = db.Profiles.Find(user.Profile.Id);
ModelState.Clear();
TryValidateModel(user);
If there is some way, please tell me!
P.D: I'm trying to learn MVC and Code First on my own, any advice will be appreciated.
When you are creating your user, you do not need the complete Profile but only the id of the Profile. Therefore, you only need to post the user information and the Profile Id. When you create a new user and set the Profile Id property, EF will figure it out for you: If the profile exists with that key it will use it, otherwise it will complain that it cannot find an item with the provided foreign key.
Also I would create a model for the view which will look like this:
public class UserModel
{
public string UserName { get; set; }
public string Password { get; set; }
// This will be the selected profile id
public string SelectedProfileId { get; set; }
// fill this with all the profiles
public IEnumerable<SelectListItem> AvailableProfiles { get; set; }
}
Each SelectListItem can be created like this:
new SelectListItem
{
Value = ProfileId, // Whatever the property is
Text = ProfileName, // this will be displayed in dropdown
});
In your controller create an instance of that and send it to your view.
In your view create the dropdown like this:
#Html.DropDownListFor(m => m.SelectedProfileId,
new SelectList(m.AvailableProfiles)),
"Select Profile")
Then in the post get the selected profile id from the SelectedProfileId property.
Not sure what your ModelState errors are. But you should probably have a property on your User enitity called ProfileId. If not you should probably add one. Then you can change your Views profile dropdown to
#Html.DropDownListFor(model => model.ProfileId, (IEnumerable<SelectListItem>)ViewBag.Profiles, htmlAttributes: new { #class = "form-control" })
Then change Profile in your Bind(Include) of the Create action to ProfileId and your Create action can just be
// POST: Users/Create
// 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.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Name,Password,ProfileId")] User user)
{
lock (UsersLocker)
{
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
return RedirectToAction("Index");
}
}
populateViewBagWithProfilesAsSelectListItem();
return View(user);
}
If it were me, I'd create a new view model class UserViewModel with the Id, Name, Password and ProfileId properties and pass that back and forth to the View.

.NET MVC 5 Check Box list from database using View Model [duplicate]

This question already has answers here:
Pass List of Checkboxes into View and Pull out IEnumerable [duplicate]
(2 answers)
Closed 6 years ago.
I need to populate a checkbox list of equipment on a form for users to request equipment. The data for the list is stored in a table named 'Equipment'. I am working with EF 6 database first. The view is strongly typed and will write to an 'Orders' table. I am stuck on how to use a View Model and not ViewBag to populate the check box list for the form. I have looked at MikesDotNetting, the Rachel Lappel post about view models and several others and it's not making sense to me.
Code below:
public class Equipment
{
public int Id { get; set; }
public string Description { get; set; }
public string Method { get; set; }
public bool Checked { get; set; }
}
public class Order
{
public int id{ get; set; }
public string Contact_Name { get; set; }
public List<Equipment>Equipments { get; set; }
public string Notes { get; set; }
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Contact_Name,Equipment,Notes")] Order order)
{
if (ModelState.IsValid)
{
db.Orders.Add(order);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(order);
}
View
#model CheckBoxList.Models.Order
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Order</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Contact_Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Contact_Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Contact_Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
//checkbox list populated here
</div>
<div class="form-group">
#Html.LabelFor(model => model.Notes, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Notes, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Notes, "", new { #class = "text-danger" })
</div>
</div>
See this answer for how to do it How to bind checkbox values to a list of ints?
That example uses Guid's as PK's but that can be easily replaced with int's.
I'm going to assume your Equipment class is your EF entity.
So you are creating your order page so let's start with the CreateOrderViewModel
public class CreateOrderViewModel
{
public string Contact_Name { get; set; }
public Dictionary<int, string> AllEquipment{ get; set; }
public int[] SelectedEquipment { get;set; }
public string Notes { get; set; }
}
Populate AllEquipment with just the id and the name of the piece of equipment. This is the complete list of equipment that will be needed to show all the equipment checkboxes with the value of the id of the equipment.
Something like
var viewModel = new CreateOrderViewModel {
AllEquipment = context.Equipment.ToDictionary(e => e.Id, e.Description);
}
SelectedEquipment is the list of equipment with checkboxes checked. So when you post this information back, the SelectedEquipment property will have a list of all the id's that need to be attached to the order.
When you create the order just iterate through the list and add them to the Equipment list in your Order entity.
Make a for loop in your list and generate a checkbox for every item in it.
<div class="form-group">
#for (int i = 0; i < Model.Equipments.Count(); i++)
{
#Html.CheckBoxFor(x => x.Equipments[i].Checked)
#Model.Equipments[i].Description
//If you need to hide any values and get them in your post
#Html.HiddenFor(x => x.Equipments[i].Id)
#Html.HiddenFor(x => x.Equipments[i].Method)
}
</div>

Categories