I have the Action method and the View for editing properties of some items.
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Admin")]
public async Task<ActionResult> Edit(Item item)
{
if (ModelState.IsValid)
{
db.Entry(item).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
ViewBag.CatagorieId = new SelectList(db.Catagories, "ID", "Name", item.CatagorieId);
return View(item);
}
and
#model OpenOrderFramework.Models.Item
#using OpenOrderFramework.Extensions
#{
ViewBag.Title = "edit";
}
<h2>Editing</h2>
#using (Html.BeginForm())
{
<div class="form-horizontal">
<h4>The car</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ID)
<-- etc -->
But when I submit the form I get an error
Store update, insert, or delete statement affected an unexpected number of rows (0).
I figured out that in action method ID of the item that was posted is always 0 even if real ID of the item is different.
Why does it happen?
GET Action method:
// GET: Items/Edit/5
[Authorize(Roles = "Admin")]
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Item item = await db.Items.FindAsync(id);
if (item == null)
{
return HttpNotFound();
}
ViewBag.CatagorieId = new SelectList(db.Catagories, "ID", "Name", item.CatagorieId);
return View(item);
}
When you post the form, the http call to your HttpPost action method is a totally separate Http request and Entity framework cannot track that entity.
As Darin mentioned in the comment, It is not a good idea to mix the entity classes in your UI layer. That makes it very tightly coupled.
What you should be using is creating and using a view model for your view. View model's are simply POCO classes, which is specific to the view.
public class ItemViewModel
{
public int Id {set;get;}
public string Name {set;get;}
public List<SelectListItem> Categories { set;get;}
public int SelectedCategory {set;get;}
}
And in your GET action, read the entity from your database,create an object of the view model and set the property values to that
public ActionResult Edit(int id)
{
var vm=new ItemViewModel { Id=id };
var item = db.Items.FirstOrDefault(s=>s.Id==id);
if(item!=null)
{
vm.Name = item.Name;
}
vm.Categories =db.Categories.Select(s=> new SelectListItem { Value=s.Id.ToString(),
Text=s.Name
}).ToList();
return View(vm);
}
And your view will be strongly typed to your view model
#model ItemViewModel
#using(Html.BeginForm())
{
#Html.DropdDownListFor(s=>s.SelectedCategory,Model.Categories,"Select")
#Html.HiddenFor(s=>s.Id)
#Html.TextBoxFor(s=>s.Name)
<input type="submit" />
}
And in your HttpPost action, read the existing entity from your db and update the property values you want to update.
[HttpPost]
public ActionResult Edit(ItemViewModel model)
{
if(ModelState.IsValid)
{
var item = d.Items.FirstOrDefault(s=>s.Id==model.Id);
item.Name = model.Name;
db.Entry(item).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
model.Categories =db.Categories.Select(s=>
new SelectListItem {
Value=s.Id.ToString(),
Text=s.Name }).ToList();
return View(model);
}
Make sure to add enough NULL checkings before accessing the entities/ objects in the code.
Related
I am trying to pull the data from a table stored in SQL 2008 into my MVC4
In My Controller :
public ActionResult Test()
{
SurveyEntities survey = new SurveyEntities();
var doctorList = survey.Doctors.ToList();
return View(doctorList);
}
and in my View:
#model IEnumerable<Survey.DataAccess.Doctor>
#{
ViewBag.Title = "Test";
}
<h2>Test</h2>
#using (Html.BeginForm("Test", "Home", FormMethod.Post))
{
#Html.DropDownListFor(m => m.)
}
But I am not able to access the field name in m, say for eg., the doctor's name, to bind it to the dropdownlist.
Where am i going wrong ?
If you dont need to bind to the result value you can also use Html.DropDownList('name', new SelectList(Model)) If you have to use DropDownListFor you would have to change your model and add a property to bind the select result like Html.DropDownListFor(m=>m.DoctorId, new SelectList(Model.Doctors).....
Normally, you want to use ViewModel, so that you can retrieve the selected doctorId when the form is posted back to server.
For example,
Model
public class SurveyModel
{
public string SelectedDoctorId { get; set; }
public IList<SelectListItem> AvailableDoctors { get; set; }
public SurveyModel()
{
AvailableDoctors = new List<SelectListItem>();
}
}
View
#model DemoMvc.Models.SurveyModel
#using (Html.BeginForm("Index", "Home"))
{
#Html.DropDownListFor(m => m.SelectedDoctorId, Model.AvailableDoctors)
<input type="submit" value="Submit" />
}
Controller
public ActionResult Index()
{
var model = new SurveyModel
{
AvailableDoctors = GetDoctorListItems()
};
return View(model);
}
[HttpPost]
public ActionResult Index(SurveyModel model)
{
if (ModelState.IsValid)
{
var doctorId = model.SelectedDoctorId;
// Do something
return View("Success");
}
// If we got this far, something failed, redisplay form
// Fill AvailableDoctors again; otherwise, DropDownList will be blank.
model.AvailableDoctors = GetDoctorListItems();
return View(model);
}
private IList<SelectListItem> GetDoctorListItems()
{
/*
SurveyEntities survey = new SurveyEntities();
return survey.Doctors
.Select(d => new SelectListItem {Text = d, Value = d.ToString()})
.ToList();
*/
// Simulate doctors return from database.
return new List<SelectListItem>
{
new SelectListItem {Text = "John Doe", Value = "1"},
new SelectListItem {Text = "Eric Newton", Value = "2"}
};
}
you can put this code in the Test method:
> ViewData["doctors"] = new SelectList(doctorList,"value", "text");
and then in a view:
#using (Html.BeginForm("Test", "Home", FormMethod.Post))
{
#Html.DropDownList("name", ViewData["doctors"] as SelectList)
input type="submit" value="Submit" />
}
I've got a view where I create new object and pass it to another view:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,carID,DateFrom,DateTo,UserID")] Reservation reservation)
{
if (ModelState.IsValid)
{
return RedirectToAction("ChooseCars",reservation);
}
return View(reservation);
}
in "ChooseCars" I have:
public ActionResult ChooseCars(Reservation reservation)
{
var reservations = db.Reservations.ToList();
var cars = db.Cars.ToList();
var reservationsResult = reservations.Where(res => res.DateFrom <= reservation.DateFrom && res.DateTo >= reservation.DateTo);
if (reservationsResult != null)
{
emptyCars = cars.Where(c => !reservationsResult.Any(y => y.carID == c.ID));
cars = emptyCars.ToList();
}
return View(cars);
}
and in that "ChooseCars" view I populate table with list of Cars that haven't been reserved.. Now I need to somehow pass that reservation and chosen car (by id) to some method where I create new "reservation" object containing carId.
What is the best way to do that?
I wrote a method, but I have no idea how to route those parameters to it:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddReservation([Bind(Include = "ID,carID,DateFrom,DateTo,UserID")] Reservation reservation, int? id)
{
Reservation finalReservation = new Reservation(id, reservation.DateFrom, reservation.DateTo, reservation.UserID);
db.Reservations.Add(finalReservation);
return View();
}
How it looks:
Your editing data, so the first thing is to create a view model(s) representing want you want to edit/display in the view. In your case you need the start and end dates, a collection of Car and the selected car.
public class ReservationVM // add validation and display attributes as appropriate
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int? SelectedCar { get; set; }
public IEnumerable<Car> AvailableCars { get; set; }
}
And your controller methods will be
[HttpGet]
public ActionResult Create()
{
ReservationVM model = new ReservationVM();
return View(model);
}
[HttpPost]
public ActionResult Create(ReservationVM model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var availableCars = ... // your query to load the collection of cars basd on the dates
if (!availableCars.Any())
{
ModelState.AddModelError("", "There are no cars available for these dates");
return View(model);
}
model.AvailableCars = availableCars;
return View("SelectCar", model);
}
[HttpPost]
public ActionResult Confirm(ReservationVM model)
{
// Initialize new Reservation, save and redirect
Reservation reservation = new Reservation()
{
StartDate = model.StartDate,
EndDate = model.EndDate,
CarId = model.SelectedCar.Value,
UserId = ... // current user?
};
db.Reservations.Add(reservation);
db.SaveChanges();
return RedirectToAction(....);`
}
And the views will be
Create.cshtml
#model ReservationVM
...
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.StartDate)
#Html.TextBoxFor(m => m.EndDate)
<input type="submit" value="Next" />
}
SelectCar.cshtml
#model ReservationVM
....
<table>
#foreach(var car in Model.AvailableCars)
{
<tr>
<td>#car.Make</td>
<td>#car.Model</td>
....
<td>
#using (Html.BeginForm("Confirm", "Home", new { StartDate = Model.StartDate, EndDate = Model.EndDate, SelectedCar = car.ID }))
{
<input type="submit" value="Reserve" />
}
</td>
</tr>
}
</table>
I have 2 methods(actions) to create new "racun". Difference is, 1st one has dropdown list to choos kupac_id(FK),and the 2nd already has kupac_id sent by GET method. First one looks like this:
// GET: racuns/Create
public ActionResult Create()
{
ViewBag.kupac_id = new SelectList(db.kupacs, "id_kupac", "naziv");
return View();
}
// POST: racuns/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id_racun,dv_racuna,status,dv_placanja,kupac_id")] racun racun)
{
if (ModelState.IsValid)
{
db.racuns.Add(racun);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.kupac_id = new SelectList(db.kupacs, "id_kupac", "naziv", racun.kupac_id);
return View(racun);
}
And the other one is like this:
// GET: racuns/CreateZaKupca
public ActionResult CreateZaKupca(int vid)
{
return View(new racun { kupac_id = vid });
}
// POST: racuns/CreateZaKupca
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateZaKupca([Bind(Include = "id_racun,dv_racuna,status,dv_placanja,kupac_id")] racun racun)
{
if (ModelState.IsValid)
{
db.racuns.Add(racun);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(racun);
}
So, when I run 1st action i got this -> http://prntscr.com/9k2cmc
And 2nd -> http://prntscr.com/9k2e56
Why is that, and can i get rid of those number and have empty field like in 1st action?
Ty for any help. EDIT:
<div class="form-group">
#Html.LabelFor(model => model.dv_racuna, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.dv_racuna, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.dv_racuna, "", new { #class = "text-danger" })
</div>
</div>
Part of code from view.
In your second action method, you are passing an object of your racun view model to the view and i am assuming that you have a property in this view model of type DateTime.
public class racun
{
public DateTime Datum_Racuna {set;get;}
}
And in your view, something like
#model racun
#using(Html.BeginForm())
{
#Html.EditorFor(s=>s.Datum_Racuna)
}
If you do not set the value of this property to a valid datetime, It will return you the DateTime.MinValue value(1/1/0001 12:00:00 AM), which is what you see in the form field.
If you wish to avoid this behaviour, you may change your property type from DateTime to Nullable DateTime(DateTime?).
public class racun
{
public DateTime? Datum_Racuna {set;get;}
}
Now since you changed the property type to nullable, you should do a null check before accessing the value to avoid Null Reference exceptions.
[HttpPost]
public ActionResult Create(racun model)
{
if(model.Datum_Racuna!=null)
{
var dtValue=model.Datum_Racuna.Value;
}
// to do :do something useful and return something useful.
}
I've just started a new MVC project and I'm having trouble getting the post result from a form.
This is my Model Class :
public class User
{
public int id { get; set; }
public string name { get; set; }
}
public class TestModel
{
public List<User> users { get; set; }
public User user { get; set; }
public SelectList listSelection { get; set; }
public TestModel()
{
users = new List<User>()
{
new User() {id = 0, name = "Steven"},
new User() {id = 1, name = "Ian"},
new User() {id = 2, name = "Rich"}
};
listSelection = new SelectList(users, "name", "name");
}
}
This is my view class
#model MvcTestApplicaiton.Models.TestModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.user, #Model.listSelection)
<p>
<input type="submit" value="Submit" />
</p>
}
#if (#Model.user != null)
{
<p>#Model.user.name</p>
}
And this is my controller :
public class TestModelController : Controller
{
public TestModel model;
//
// GET: /TestModel/
public ActionResult Index()
{
if(model ==null)
model = new TestModel();
return View(model);
}
[HttpPost]
public ActionResult Test(TestModel test)
{
model.user = test.user;
return RedirectToAction("index", "TestModel");
}
}
The drop down list appears just fine but I can't see to get the ActionResult Test function to run. I thought it would just bind itself with reflection but whatever is wrong, I can't see it.
You have two main errors in your code.
As Brett said you're posting to the Index method, but you don't have Index method that supports POST verb. The easiest way to fix is to change Html.BeginForm() with Html.BeginForm("Test", "TestModel")
You're using Html.DropDownListFor in a wrong way. You could pass only a value types there, because don't forget that the View will generate an HTML page. So instead of User in your Model you should have an UserID and in your View you should have #Html.DropDownListFor(x => x.UserID, #Model.listSelection). And finally in your Action you should query your data source to get the details for the user with this ID.
Hope this helps.
Looks like you're posting back to index. Either use a GET Test() action method, or specify the ACTION parameter in BeginForm().
For example,
#using (Html.BeginForm("Test", "TestModel"))
{
#Html.DropDownListFor(x => x.user, #Model.listSelection)
<p>
<input type="submit" value="Submit" />
</p>
}
Or use a view named Test (rename index.cshtml to test.cshtml):
public ActionResult Test()
{
if(model ==null)
model = new TestModel();
return View(model);
}
I have a DropDownListFor that is on my Index page and one in my Create page. Both dropdownlists serve the same purpose.
What I want is when the user selects an item in the Index dropdownlist in the index page, it saves that selected item's value which is a GUID to the session and when the Create page loads, I want the dropdownlist in there to select the item based on the GUID in the session.
At the moment when the user clicks on "Create" and goes to the create page, I am merely setting up an object and sending that object to the Create View.
Edit:
I am sending the user over to the Create page by doing this:
Html.ActionLink("Create New Listing", "Create", null, new { #class = "btn btn-primary" }))
How do I send the GUID of the selecteditem over to the view?
I guess you have a situation like this. Here is the Index view:
#model Models.IndexViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("SaveGuid", "Flow"))
{
Html.DropDownListFor(x => x.SelectedGuid, Model.Guids, new { onchange = "this.form.submit();" });
}
Here is the Index model:
public class IndexViewModel
{
public Guid SelectedGuid { get; set; }
public SelectList Guids { get; set; }
}
The Index and SaveGuid Action look like this:
private List<Guid> Guids = new List<Guid> { Guid.NewGuid(), Guid.NewGuid() }; // for testing only
public ActionResult Index()
{
var model = new IndexViewModel { Guids = new SelectList(Guids, Guids.First()) };
return View(model);
}
public ActionResult SaveGuid(IndexViewModel model)
{
Session["SelectedGuid"] = model.SelectedGuid;
return new RedirectResult("Create");
}
The Create View looks like this...
#model MvcBootStrapApp.Models.CreateViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm("SaveGuid", "Flow"))
{
#Html.DropDownListFor(x => x.SelectedGuid, Model.Guids, new { onchange = "this.form.submit();" });
}
#using (Html.BeginForm("SaveCreate", "Flow"))
{
// setup other controls
<input type="submit" value="Submit" />
}
Using a CreateViewModel like this...
public class CreateViewModel
{
public Guid SelectedGuid { get; set; }
public SelectList Guids { get; set; }
// include other model properties
}
The Create and CreateSave ActionResults look like this...
public ActionResult Create()
{
Guid selectedGuid = Guids.First();
if (Session["SelectedGuid"] != null)
selectedGuid = (Guid)Session["SelectedGuid"];
return View(new CreateViewModel
{
Guids = new SelectList(Guids, selectedGuid),
SelectedGuid = selectedGuid
});
}
public ActionResult SaveCreate(CreateViewModel model)
{
// save properties
return new RedirectResult("Index");
}
I used two forms to allow both the change of selected Guid and to postback all the Create properties.
If you want to use Session, what I think you need is to use a form to post to an ActionResult to save the dropdownlist's value and then redirect to the Create page.
public ActionResult SaveGuid(Guid value)
{
Session["SelectedGuid"] = value;
return new RedirectResult("Create");
}
Then in your Create ActionResult, pass the Session value to the Create View's Model.
public ActionResult Create()
{
var selectedGuid = (Guid)Session["SelectedGuid"];
return View(new CreateViewModel { SelectedGuid = selectedGuid, /* include other properties */ };
}
In your view you can set the selected option on the SelectList passed to your DropDownListFor...
#Html.DropDownListFor(
x => x.SelectedGuid,
new SelectList(Model.ListOfStuff, "Key", "Value", Model.SelectedGuid)
)