MVC Model Binding Post Values Best Practice - asp.net-mvc

I'm trying to figure out if what I'm doing is flawed or acceptable. Specifically, I'm questioning the NULL value I'm getting back in the POST to Controller in 'Timeframes' property. The 'Timeframe' (singular) property DOES contain the value so all is good. However, is this just how model binding works and the property (Timeframes) that is used to populate the DDL comes back as null? Is this best practice and what I'm doing is fine? Is this a concern of sending values around that are not needed...performance concern?
Timeframe = used to return value back to Controller on Post
Timeframes = used to populate DDL values
Drop Down List Box on View:
#Html.DropDownListFor(m => m.Timeframe, Model.Timeframes)
Model:
public class ABCModel
{
public List<SelectListItem> Timeframes { get; set; }
public string Timeframe { get; set; }
}
Controller:
[HttpPost]
public void TestControllerMethod(ABCModel model)
{
//this value is null.
var timeFrames = model.Timeframes;
//this value is populated correctly
var timeFrame = model.Timeframe;
}

A form only posts back the name/value pairs of its successful controls. You have created a form control for property Timeframe, so you get the value of the selected option in the POST method.
You have not (and should not), created form controls for each property of each SelectListItem in your Timeframes property, so nothing relating to it is send in the request when the form is submitted, hence the value of Timeframes is null.
If you need to return the view because ModelState is invalid, then you need to re-populate the TimeFrames property as you did in the GET method (otherwise your DropDownListFor() will throw an exception). A typical implementation migh look like
public ActionResult Create()
{
ABCModel model = new ABCModel();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(ABCModel model)
{
if (!modelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// Save and redirect
}
private void ConfigureViewModel(ABCModel model)
{
model.TimeFrames = ....; // your code to populate the SelectList
}

Related

MVC: post, catching form values into property of page model

Alright...this may be a bit backwards but, I only need to do it in one spot.
I have a Model
public class LoginModel : xxx.Models.PageVars
{
public Item.LoginAttempt LoginAttempt { get; set; }
public LoginModel()
{
// does a bunch of stuff here...mainly to set the layout properties from PageVar
this.LoginAttempt = new Item.LoginAttempt();
}
}
Login Attempt is a simple obj (for now)
// login attempt
public class LoginAttempt
{
public string Email { get; set; }
public string Password { get; set; }
}
My controller
public ActionResult Login()
{
return View("Login", new Models.LoginModel());
}
[HttpPost]
public ActionResult LoginAttempt(LoginAttempt model)
{
return View("Login", model);
}
In my view
#model xxx.Models.LoginModel
Is there a way to use the property of the obj/model from LoginModel for the #model.
I can get the values from FormCollection or request but...that's not optimal.
thoughts???
tnx
The model for your GET should match the model for your POST. Otherwise, you're not playing on the same field. In order to allow the binding of data from a POST to a model, the HTML Helpers will generate a name that matches the access path of the property in the view's model. In other words, in your form, based on the model being LoginModel, your field names will be LoginAttempt.Email and LoginAttempt.Password. But, in the POST action, you're accepting just LoginAttempt, so the modelbinder is expecting to see data for Email and Password, which it won't find.
There's actually not even any need for this nested class. Just put your Email and Password fields directly on LoginModel and use that for both your view and your POST parameter. Then, you won't have any issues because everything will match up.
Why don't you have the form post controller action accept the parent model LoginModel instead of LoginAttempt? That way, the default MVC model binding should automatically parse the submitted values into the LoginModel and you'll have acces to LoginAttempt.
If it isn't then your form needs to use the prefix values in the names of the properties on the form. This is done automatically when you use TextboxFor, DropdownListFor etc.
In your example, the names of the form fields should start with LoginAttempt.Email etc
I've seen it work 2 ways. First way would be to rename your LoginAttempt model parameter to be
[HttpPost]
public ActionResult LoginAttempt(LoginAttempt loginModel)
{
return View("Login", model);
}
But i would use the Bind(Prefix) option
[HttpPost]
public ActionResult LoginAttempt([Bind(Prefix="LoginModel")] LoginAttempt model)
{
return View("Login", model);
}
you can't really return model of type LoginAttempt to the view though so you'd have to do even more work to get it to work if you're set on doing it this way. You should probably be redirecting to a different page instead of returning the Login view if it succeeds. Other wise return new LoginModel() {LoginAttempt = model}

In a post method, how to get compact parameters instead of all the object properties

Maybe the title is not so explicitly. Let me explain you my situation
I've got a get and post method in my controller. In the GET method, gets the entities from the database context
[HttpGet]
public ActionResult RecheckAssignment(short id)
{
var assignment = db.Assignments.Find(id);
Session["QuestionList"] = QuestionRepositoryManager.GetAllPossibleQuestionsFromJson(assignment.Content); // it's a list!
return View(Session["QuestionList"]);
}
Assignment entity contains as 10 properties. When I show this entities in the model, it shows uses all the properties, but when the user does post should get only two properties from it (Id string, Changed bool) in the POST METHOD.
I do not what to put inside of the method parameters.
[HttpPost]
public ActionResult RecheckAssignment(...)
{
return View();
}
I put everything in a session variable because later I must have to get the entities again, I guess this is a good option using Session but I'm not sure.
So, what should I have to write inside of the method to get only the Id and Changed properties to updated the entities.
When ASP.NET MVC maps a <form> back to the Action during a POST it will fill in what it can. Consider a class like this:
public class Car
{
public string Make { get; set; }
public string Model { get; set; }
public int Year { get; set; }
}
and now consider this form:
#using (Html.BeginForm("ActionName", "ControllerName", FormMethod.Post))
{
Html.TextBoxFor(m => m.Make)
}
and now consider this Action:
public ActionResult ActionName(Car model)
{
// the values of Car will look like this
model.Make // this will be what was in the text box
model.Model // this will be null
model.Year // this will be 0
}
and take note that null and 0 are the default values for those types. So, if I wanted to POST the property Model I need to get it in the form. I can do that with #Html.TextBoxFor, but what if I don't want the user to see it? Well, I can do that too:
Html.HiddenFor(m => m.Model);
and so now when the form is POSTed it will populate the Model with the value it was downloaded with. So, just make sure that all the properties you need are in the form in some way.

ASP.NET MVC ViewModel update

I have a details page which lets me edit the information associated with a particular item.
public ActionResult Details(int id)
{
Call call = db.Calls.Find(id);
return View(new CallFormViewModel(call));
}
I use a view model --
public class CallFormViewModel
{
public Call Call { get; private set; }
public CallFormViewModel()
{
Call = new Call();
}
public CallFormViewModel(Call call)
{
Call = call;
}
}
When I submit, I want to only allow certain properties of the 'call' object to be updated. My Post handling method looks like this -
[HttpPost]
public ActionResult Details(CallFormViewModel callForm)
{
(some code removed for clarity)
UpdateModel(callForm.Call ,new string[] {
"Contact",
"Summary",
"Description",
}
}
The problem is that callForm has already been updated with all of the input from the form submit before I even call UpdateModel.
How can I change this and use UpdateModel to selectively update fields?
Thanks
Edit:
I think i've been looking at this the wrong way. What I should be doing is this:
[HttpPost]
public ActionResult Details(int id, CallFormViewModel callForm)
{
var call = db.Calls.Find(id);
(some code removed for clarity)
UpdateModel(call, "Call", new string[] {
"Contact",
"Summary",
"Description",
}
}
This way it's taking the incomplete data [and only the fields i want] and applying it to the actual model. I had been confusing the callForm.Call with the actual model object, when infact it's only a representation of it.
Have to wait till I get to work to test this theory.
When you write your ViewModel in the Action parameters, The Model Binder Bind the ViewModel properties to the "incoming" data. This should work:
[HttpPost]
public ActionResult Details()
{
CallFormViewModel callForm = new CallFormViewModel();
UpdateModel(callForm.Call ,new string[] {
"Contact",
"Summary",
"Description",
}
}
Update:
The Bind attribute option:
[Bind(Include = "Contact,Summary,Description")]
public class CallFormViewModel
{
// As before...
}
You don't need to call UpdateModel because you will get an updated model passed as the parameter to your method. If you'd prefer to use UpdateModel, you should remove the parameter from the action method.
To then update just the fields you are interested in, you should either:
Have the method take a different view model that only includes the fields that should be updated.
Change the form so that only the fields you want updated are in text boxes (or other updatable controls) created by the HTML.TextBoxFor() methods.

How to keep dropdownlist selected value after postback

In asp.net mvc3 how to keep dropdown list selected item after postback.
Do something Like this :
[HttpPost]
public ActionResult Create(FormCollection collection)
{ if (TryUpdateModel(yourmodel))
{ //your logic
return RedirectToAction("Index");
}
int selectedvalue = Convert.ToInt32(collection["selectedValue"]);
ViewData["dropdownlist"] = new SelectList(getAllEvents.ToList(), "EventID", "Name", selectedvalue);// your dropdownlist
return View();
}
And in the View:
<%: Html.DropDownListFor(model => model.ProductID, (SelectList)ViewData["dropdownlist"])%>
Even easier, you can include the name(s) of your dropdowns in your ActionResult input parameters. Your dropdowns should be in form tags. When the ActionResult is posted to, ASP.Net will iterate through querystrings, form values and cookies. As long as you include your dropdown names, the selected values will be preserved.
Here I have a form with 3 dropdowns that posts to an ActionResult. The dropdown names are (non-case sensitive): ReportName, Year, and Month.
//MAKE SURE TO ACCEPT THE VALUES FOR REPORTNAME, YEAR, AND MONTH SO THAT THEY PERSIST IN THE DROPDOWNS EVEN AFTER POST!!!!
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ReportSelection(string reportName, string year, string month)
{
PopulateFilterDrowdowns();
return View("NameOfMyView");
}
MVC does not use ViewState, which means you will need to manage the value persistence yourself. Typically this is done through your model. So, given that you have a view model, e.g.:
public class MyViewModel { }
And your controller:
public class MyController : Controller
{
public ActionResult Something()
{
return View(new MyViewModel());
}
public ActionResult Something(MyViewModel model)
{
if (!ModelState.IsValid)
return View(model);
return RedirectToAction("Index");
}
}
Now, when you pass the model back to the view with data (probably incorrect - failed validation), when you use your DropDownListFor method, just pass in the value:
#Model.DropDownListFor(m => m.Whatever, new SelectList(...))
... etc.
MVC's model binding will take care of the reading of the data into your model, you just need to ensure you pass that back to the view to show the same value again.
Assuming the selected item is part of the post, the controller now knows what it is. Simply have an entry in the ViewData dictionary indicating which item should be selected (null on get or if nothing was selected). In the view, check the value and if it's not null, select the appropriate option.
Use HttpRequestBase object.
In the view, this should work:
#Html.DropDownList("mydropdown", ViewBag.Itens as IEnumerable<SelectListItem>, new { value = Request["mydropdown"] })
If you are building the drop down list data source in the controller Action Method you can send the selected value to it
Controller:
public ActionResult Index( int serviceid=0)
{
// build the drop down list data source
List<Service> services = db.Service.ToList();
services.Insert(0, new Service() { ServiceID = 0, ServiceName = "All" });
// serviceid is the selected value you want to maintain
ViewBag.ServicesList = new SelectList(services, "ServiceID", "ServiceName",serviceid);
if (serviceid == 0)
{
//do something
}
else
{
// do another thing
}
return View();
}
View:
//ServiceList is coming from ViewBag
#Html.DropDownList("ServicesList", null, htmlAttributes: new { #class = "form-control" })

How to round-trip view-only data between views and controllers

I'm passing data between my controller and my view using a ViewModel class. When there are validation errors, I return the ViewModel back to the view so the user can see the errors.
I'm having trouble figuring out the best way to deal with the data that is only passed from the controller to the view, which isn't passed back to the controller, such as the contents of dropdown lists.
Here's a simplified example from the project I'm working on:
I have a Widget object in my domain model that has an Employee property. I have a view that allows the user to edit this employee property by selecting their name from a drop down list.
public class WidgetFormViewModel {
// Used for a drop down list in the view
public SelectList EmployeeList { get; set; }
// This will contain the employee the user selected from the list
public int EmployeeID { get; set; }
public Widget Widget { get; set; }
}
And the controller:
// GET: /Widget/Edit/1
public ActionResult Edit(int id) {
var widget = _widgetService.GetWidgetByID(id);
var employees = _widgetService.GetAllEmployees();
var viewModel = new WidgetFormViewModel()
{
EmployeeList =
new SelectList(employees, "ID", "Name", widget.Employee),
Widget = widget,
WidgetID = widget.ID
};
return View("Edit", viewModel);
}
// POST: /Widget/Edit
public ActionResult Edit(WidgetFormViewModel viewModel) {
var existingWidget = _widgetService.GetWidgetByWidgetID(viewModel.WidgetID);
existingWidget.Employee = _widgetService.GetEmployeeByID(viewModel.EmployeeID);
// try { /* Save widget to DB */ } catch { /* Validation errors */ }
return ModelState.IsValid
// Update was successful
? (ActionResult) RedirectToAction("List")
// Model state is invalid, send the viewModel back to the view
: View("Edit", viewModel)
}
Now, here's the problem: When the ModelState is invalid and viewModel gets passed back to the view, its EmployeeList property is blank. What is the best way to deal with this?
Should I just repopulate it before returning to the view? This method seems difficult to maintain. (What if I add PageTitle and HeaderText to the view model as well? Suddenly there are more things to keep track of.) Is there another approach?
Inside of the catch block of the controller action handling the post, extract your error messages and add it to this.ModelState, then have it return this.Edit(viewModel.widgetID);.
You already have all the logic you need built up to display the view appropriately, you just want to use ModelState to make sure that errors make it back to the view.

Resources