Handling postbacks in MVC properly - c#

In my update actions I am doing this:
[HttpPost]
public ActionResult Update()
{
if(Request.Form["..."])
{
}
..
}
So I am grabbing the ID of the entity, then loading it, then updating the properties based on the posted form values.
I'm using MVC2.
How should I be doing this as I read about model state but not sure how to start?
I want to refactor to make this right.
Update
My viewmodel looks like:
public class SomeViewModel
{
public User User {get; set;}
}

You should define a view model containing all the necessary information that the view will send:
public class MyViewModel
{
public int Foo { get; set; }
[Required]
public string Bar { get; set; }
[Required]
public DateTime? Baz { get; set; }
}
and then have your controller action take this view model as parameter:
[HttpPost]
public ActionResult Update(MyViewModel model)
{
if (!ModelState.IsValid)
{
// validation failed (the user left the Bar field empty) =>
// we redisplay the view so that he can fix the errors
return View(model);
}
// at this stage we know that the model is valid =>
// we could do some processing. The model.Foo and model.Bar
// properties will contain the values entered by the user in the
// corresponding form fields so that you don't need to fetch them
// manually from the Request. The default model binder will take
// care of this
...
}

Related

There is no ViewData item of type IEnumerable

I have this ViewModel :
public class FoundViewModel
{
public string Title { get; set; }
public string Description { get; set; }
public int City { get; set; }
public int SubGroup { get; set; }
public string Address { get; set; }
public DateTime FindDate { get; set; }
}
and in View I add two Dropdown and Fill it in actionresult when call get:
var Provinces = _provinces.SelectAll();
ViewBag.Provinces = new SelectList(Provinces,"Id","Title",Provinces.FirstOrDefault().Id);
and in view :
#Html.DropDownList("drpProvince", (SelectList)ViewBag.Provinces, new { #onchange="javascript:getcity(this.value);"})
But when Click on the button and post it , I getting this ERROR:
There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'drpProvince'
I found some similar question on stackover flow but can't know .
What's a Problem ?
thanks.
What happens is that, at the time of the post and reload the page to show the cities according to the selected province, are not generating ViewBag again.
The solution is to initialize the ViewBag again since this is lost after PostBack each call.
Example.
public ActionResult loadCities () {
    /* Logic .... */
    // Here initialize ViewBag again to view the know.
    return View ();
}
This Error Occured when you fill viewbag in Get View and when it post and Model is not Valid , You Returned model to the View And viewBag is null, You should be Fill all Viewbag if Model is not Valid befor return view.
[Httppost]
public ActionResult YouAction(myVm mymodel)
{
if (!ModelState.IsValid)
{
// fill your Viewbags
return View(mymodel);
}
}

Submitting form doesn't bring over all the data

I'm trying to add an object using a PartialView inside a popup. It's a simple Rental application for which the data model was generated Model First through Entity Framework. The Controllers and Views have mostly been Scaffolded by EF. The relationship between RentalApplication and RentalObject is 1 to many, meaning a RentalObject always has to have 1 RentalApplication.
My controller looks like this:
// GET: /Calendar/Add/1
// Create a PartialView using a RentalObject as the model.
// Use the provided ID to lock in the RentalApplication.
[HttpGet]
public PartialViewResult Add(int id)
{
return PartialView(
new RentalObject(db.RentalApplicationSet.FirstOrDefault(x => x.Id == id)));
}
// POST: /Calendar/Add
// Save the submitted RentalObject to the db
[HttpPost]
public ActionResult Add(RentalObject rentalobject)
{
if (ModelState.IsValid)
{
try
{
db.RentalObjectSet.Add(rentalobject);
db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
return View();
}
My object looks like this:
public partial class RentalObject
{
public RentalObject()
{
this.Lease = new HashSet<Lease>();
}
public RentalObject(RentalApplication rentapp)
{
this.Lease = new HashSet<Lease>();
RentalApplication = rentapp;
PricePerHour = RentalApplication.DefaultPricePerHour;
Currency = RentalApplication.DefaultCurrency;
}
public int Id { get; set; }
public string Name { get; set; }
public bool IsAvailable { get; set; }
public string Illustration { get; set; }
public string Description { get; set; }
public decimal PricePerHour { get; set; }
public string Currency { get; set; }
public virtual ICollection<Lease> Lease { get; set; }
public virtual RentalApplication RentalApplication { get; set; }
}
So when I'm opening the popup (using #Ajax.ActionLink to GET the first controller Add action) I'm creating a RentalObject WITH a RentalApplication (2nd constructor) to use as the model. This works so far, the popup dialog shows the values PricePerHour and Currency from the RentalApplication.
However, when I submit the form in my PartialView popup everything gets copied over BUT the RentalApplication object. It somehow ends up creating a new RentalObject object using the PricePerHour and Currency from the original RentalApplication, but doesn't include the object itself under the RentalApplication property. My debugger even goes to the first constructor for RentalObject.
So I guess it's having trouble keeping a complex object inside another object when submitted from controller to view (GET) and back to controller (POST). Is this just poor practice on my part? Should I be using a ViewModel?
In the past I've had to use #Html.HiddenFor(m=>m.yourObjectHere) on objects that were not changed in the form to keep them from getting new-ed up again. I did this for every object I didn't use in the form (about 2 or 3).
Hope this helps.

Passing ViewModel - View to Controller When JavaScript disabled

It's difficult for me for developping an functionality without JavaScript..
I have a ViewModel :
public class AccountRegisterViewModel
{
#region Properties
public User User { get; set; }
public ExternalAccounts ExtAccounts { get; set; }
public LocalPassword Password { get; set; }
public Company CompanyARegister { get; set; }
public Company CompanyBRegister { get; set; }
public bool SameCompanies { get; set; }
public int NbCompanies { get; set; }
...
}
In view, i have a link with checkbox for copying the first company with the second
But I don't know how pass this viewModel (View to Controller) for keeping my data and return the same View with the copie of company..
i try this, in view :
#Html.ActionLink("Click", "CopyCompanies","Account", new { model = Model })
#Html.CheckBoxFor(model => model.SameCompanies)
In Controller :
[AllowAnonymous]
public ActionResult CopyCompanies(AccountRegisterViewModel model)
{
...
if (model.SameCompanies)
{
// copie
}else //clear
...
return View("Step2Register", model);
}
Any idea ?? Thank you for your help
If the CopyCompanies action method requires the AccountRegisterViewModel object, then you will need to provide it. Unfortunately, you will not be able to provide the value using the approach you are following when creating the link.
Your two options would be to have a hidden field for each property in AccountRegisterViewModel and then let the model binding create the object, but even this would not be ideal, since the viewModel is composed of complex objects, so you would have way too many hidden fields.
Your second option, which I think is a better approach, would be to pass in some kind of Id that corresponds to the AccountRegisterViewModel that CopyCompany can use to look up the values it would need.

Returning different types of ViewModel back to the Controller

I have an ASP.NET MVC4 application in which at a certain point there is a page (Index) where a user can select an item from a DropDownList. After submitting this, the controller will return a different PartialView-name to another View (Create), based on the selected item from the list. Each partial has its own ViewModel and when the PartialView is sent from the Controller to Create-View it is rendered correctly. To achieve this I made a general ViewModel and several other viewmodels which derive from this general ViewModel. The Create-view has the general ViewModel as Model and the Partials, which will be rendered in the Create-view, have the matching derived types as Model.
But here's the problem, when I submit the form on the PartialView I have to retrieve the right ViewModel back in the Controller. Accepting the general ViewModel as a parameter won't work as I won't be able to down-cast it to the right ViewModel then. Here's some example code that I have:
ViewModels:
public class PropertyViewModel
{
public string ViewName { get; set; }
public String Name { get; set; }
public String Description { get; set; }
}
public class IntegerViewModel : PropertyViewModel
{
public int MinValue { get; set; }
public int MaxValue { get; set; }
}
public class TextViewModel : PropertyViewModel
{
public int MaxLength { get; set; }
}
Controller:
public ActionResult Create(String partialName)
{
var model = GetViewModelFromName(partialName);
return View(model);
}
[HttpPost]
public ActionResult Create(???)
{
//What to do here and what kind of parameter should I expect?
}
Is there a 'clean' way to do this? And does anyone know how to achieve this?
Update:
I have a solution that seems to work. In the PartialView I set the actionName and the controllerName of the form, like this:
#using (Html.BeginForm("CreateIntegerProperty", "Property")) {
//Formstuff...
}
#using (Html.BeginForm("CreateTextProperty", "Property")) {
//Formstuff...
}
And in my Controller I have all the different Actions (one for each of the PartialViews). This seems to work. Now is this a clean way to do this? If anyone comes up with a better idea, please do let me know!
If your solution works then go with it. It seems fine to me. The only issue would arise if you were bothered about having the same URL for each action.
You could enhance it a tiny bit by adding the Action and Controller names to the base ViewModel if you wanted to, like this:
public class PropertyViewModel
{
public string ViewName { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public String Controller { get; set; }
public String Action { get; set; }
}
And then doing this:
#using (Html.BeginForm(Model.Action, Model.Controller)) {
//Formstuff...
}
This would be worth doing if it meant you could now use the same View (or partial view, or whatever) for the form.
If you DO want the same URL for each action, then one way is to override OnModelBinding, but I probably wouldn't bother personally.

View and ICollection Question

I am coverting my app from webforms to mvc, at the moment i am at a design issue (well i just dont know how to do it in mvc).
Basically my model would be something like this:
public class DamagedItem
{
public Int32 LoanId {get;set;}
public String IdentityCode {get;set;}
public virtual ICollection<DamagedItems> DamagedItems {get;set;}
}
In my controller i would like to do:
public ActionResult Add(DamagedItem damagedItem)
{
//Do update logic here
}
Then in my view i can add to the ICollection as needed.
But, i can't do this because if i try and access the ICollection from my controller it is null.
Here is an image of when i want to do:
I just dont know how to lay it out in my view, how to i add such items to my ICollection, update the view then when i need to save i have access to what i have added from my controller?
Thanks,
Nick
Edit:
I was thinking of using a partial in the view and doing all the logic for the bottom half using ajax and storing it in a session variable, but i would prefer NOT to make it reliant on ajax.
It is better to separate: you shoud have 2 actions, which produce 2 view.
You should have LoadInformationModel classe:
public class LoadInformationModel
{
public string StudentCode { get; set; }
public string FirstName { get; set; }
// etc..
public ICollection<Damage> Type { get; set; }
}
corresponding action
[HttpGet]
public ActionResult LoanInformation(int id)
{
var loanInfo = // get data by given id..
var model = new LoadInformationModel {
StudentCode = loanInfo.StudentCode,
// etc
Type = new List<Damage> { new Damage { Value = "Damaged"}, new Damage { Value = "Damaged Again" }
}
return View(model);
}
As well as RepairDataModel class
public class RepairDataModel
{
public bool CoveredByWarranty { get; set; }
public ICollection Status { get; set; }
}
And corresponding action
[HttpGet]
public ActionResult Repair(int id)
{
// logic
return View(model);
}
Your task is to create Post handler, that would save data to DB then form submitted
[HttpPost]
public ActionResult(RepairDataModel model)
{
// save to db
return View();
}
The view returned by Index() method, could be created like
#Html.RenderAction("LoanInformation")
#Html.RenderAction("Repair")
The rest depends on your desing and imagination. I hope that would give you direction.
What I can see is only the DamagedItem lacks a contructor with values for Collection;
public class DamagedItem
{
public DamagedItem()
{
DamagedItems = new List<DamagedItems>();
DamagedItems.Add(new DamagedItem { Description = "Damaged" } );
}
public Int32 LoanId {get;set;}
public String IdentityCode {get;set;}
public virtual ICollection<DamagedItems> DamagedItems {get;set;}
}

Categories

Resources