Best Practice for fill dropdownlist in ASP.NET MVC - asp.net-mvc

I have a question. I fill dropdownlist models in a view model and send them to a view. This is better method rather than using ViewBag.
But when view model is sent to the Controller, this extra data will be sent to the Controller. Now if I use bind attribute for receiving some properties and ModelState is invalid, I should fill them and send to view again?? What is the best practice in these cases?

You can use ViewBag that will help you to avoid such kind of validation.
e.g. you have country data as a dropdown you can bind it to ViewBag and set it in Dropdown and for rest of the part you can supplied your own model which is required to supplied in view.
following is an example with ViewBag and Model.
public class HomeController : Controller
{
[HttpGet]
public ActionResult Home()
{
your Model
ABCModel model = new ABCModel();
// your ViewBag
ViewBag.Country = new SelectList(db.Contries.ToList(),"Id","Name");
return View(model)
}
[HttpPost]
public ActionResult Home(ABCModel model)
{
if(ModelState.IsValid)
{
// your action
}
return View(model);// you can redirect to other page as per your requirements
}
}
Index.cshtml
#model ABCModel // define your required model
bind dropdown like below
#Html.DropDownList("Country",ViewBag.Country as SelectList)

Related

Validation between controllers

I have a HomeController with an Index.cshtml Razor view that uses an InitialChoicesViewModel with validation attributes. The Index view contains the following form:
#using (Html.BeginForm("CreateCharacter", "DistributePoints", FormMethod.Get))
This goes to a different controller (which is what I want):
public class DistributePointsController : Controller
{
public ActionResult CreateCharacter(/* my form parameters */)
// ...
}
How do I perform server-side validation on the form (such as checking ModelState.IsValid), returning my original Index view with a correct ValidationSummary on error? (On success I want to return the CreateCharacter view of the other controller.)
Based on John H's answer, I resolved this as follows:
#using (Html.BeginForm("CreateCharacter", "Home"))
HomeController:
[HttpPost]
// Only some of the model fields are posted, along with an additional name field.
public ActionResult CreateCharacter(InitialChoicesViewModel model, string name)
{
if (ModelState.IsValid)
{
return RedirectToAction("CreateCharacter", "DistributePoints",
new {name, model.Level, model.UseAdvancedPointSystem});
}
// Unsure how to post a collection - easier to reload from repository.
model.ListOfStuff = _repository.GetAll().ToList();
return View("Index", model);
}
I had to add a parameterless constructor to my view model, too.
[HttpPost]
public ActionResult CreateCharacter(InitialChoicesViewModel model)
{
if (ModelState.IsValid)
return RedirectToAction("SomeSuccessfulaction");
return View("~/Views/Home/Index.cshtml", model);
}
The ~/ denotes the relative root of your site.
The code above complies with the Post-Redirect-Get pattern, in order to prevent some types of duplicate form submission problems. It does that by redirecting to a separate action when the form submission is successful, and by returning the current view, complete with ModelState information, on error.
By default, ASP.NET MVC checks first in \Views\[Controller_Dir]\, but after that, if it doesn't find the view, it checks in \Views\Shared.
If you do return View("~/Views/Wherever/SomeDir/MyView.aspx") You can return any View you'd like.
But for now in your case, try the following
public ActionResult CreateCharacter(SomeModel model)
{
if(!ModelState.IsValid){
return View("~/Views/Home/Index.cshtml", model )
}
return View();
}
To check your ModelState just use an if statement in Controller:
if(ModelState.IsValid)
{
...
}
If there is any error add you can add an error message to the ModelState Dictionary like this:
ModelState.AddModelError("Somethings failed", ErrorCodeToString(e.StatusCode));
After that return your same View and pass it to your model
return View(model);
If you add "#Html.ValidationSummary()" in your View, it will get the errors from the ModelState Dictionary and display them.But if you show values yourself maybe with different styles you can do it manually, take a look at this question
And if there is no error you can return your CreateCharacter View like this, just redirect user to the appropriate action:
return RedirectToAction("CreateCharacter","DistributePoints");

MVC3 (Razor) Passing Model Data from View to Controller

I need to know if there is a way to pass the Model (or a part of it, i.e. thereafter a search query) data of a View (Razor Engine) to a Controller.
To explain in a better way what I have to do, that's the interested code:
VIEW:
#model IEnumerable<MvcMovie.Models.Movie>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
...
#foreach (var item in Model) { ...}
...
#Html.ActionLink("Search", "SearchIndex")
#Html.ActionLink("Create Document", "createDoc"/*, new { id = Model.ToList() }*/)
I want to pass the
#model IEnumerable<MvcMovie.Models.Movie>
in the first line (= Model used in the foreach instruction) to the Controller "createDoc" to create a report document dynamically binded with the view.
I tried everything: I tried to use a ViewData (VIEW: ViewData["data"]=Model , CONTROLLER List movies= ViewData["data"]), I similarly tried a TempData, I tried to pass the Model as routeValues in the ActionLink (as you can see: new{ id= Model.toList() }), but nothing worked.
Is it even possible to do the thing I want to?
Can anyone help me?
Your model should not be IEnumerable<MvcMovie.Models.Movie> It should be a class, say SearchMovieModel, that has IEnumerable<MvcMovie.Models.Movie> Movies as one of its properties.
If you want a search model, something like this would be appropriate:
public class SearchMovieModel{
public IEnumerable<MvcMovie.Models.Movie> Movies {get;set;}
public string SearchString {get;set;}
}
you reference this model and its properties in your view and controller.
I guess I should add the method for parsing this in the controller.
On the first call to the view, the model does not exist. You need to create it in your controller:
public ActionResult Search(){
var model = new SearchMovieModel();
//you also need to instantiate the null objects unless you do that in the model's constructor
model.Movies = new List<Movie>();
return View(model);
}
To "reconvert" the POST data back to a model, you need to specify the model and method:
[HttpPost]
public ActionResult Search(SearchMovieModel model){
if (ModelState.IsValid){
//populate your IEnumerable<Movie> here.
return View(model);
}
// the complex collection will not be parsed back into the model. You will need to repopulate it.
model.Movies = new List<Movie>();
return View(model);
}
I think that know what you want... but whit this code
#Html.ActionLink("Create Document", "createDoc", new { id = Model.ToList() })
your html is..
Create Document
and that's because is render the type not the data
Solutions
define filter model to do the search again(the recommendation of jeremy-holovacs and mine)
why ask to the server the same data again? because if someone share that link... you can imagine whats it's the result even inject fake data that your app will generate
serialize data to json for example to forward it to the controller

Can i return different objects in views of same action method in Http get and Http Post?

I want to return an object on HTTPGet method and different object in HTTPPost method of the same action method in the controller, but i dont know what to write in the view, which model to get.
Here is the controller code , i have been trying
[HttpGet]
public ActionResult Create()
{
var intSrNo = Convert.ToInt64(TempData["sr_no"]);
MEntities obj_entity = new MEntities();
UDP_get_a_Result obj_proc = obj_entity.UDP_get_a(intSrNo).SingleOrDefault();
return View(obj_proc);
}
[HttpPost]
public ActionResult Create(Table_a obj_a)
{
if (ModelState.IsValid)
{
db.Table_a.AddObject(obj_a);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(obj_a);
}
i'm confused which model to write in view.(Table_a or UDP_get_a_Result) and i want both HttpGet to show values when the page is loaded and HttpPost when the submit is clicked.
View
#model ABC.models.Table_a
#{
ViewBag.Title = "Create";
}
A view can only be strongly typed to a single class. You cannot have different controller actions returning the same view and passing different models to this view. You could use view models: define a class which will hold all the information necessary for this view and then have your controller actions fill this view models and pass it to this view.
I think it would work to have the view typed to some base class (object) and then cast the model to whatever you needed it to be based on get/post. I wouldn't want to maintain it tho. :-D

ASP.NET MVC ViewModel with SelectList(s) best practice

I noticed that in the NerdDinner application that if the ModelState is invalid for a dinner, it merely returns the view for the model:
if (ModelState.IsValid) {
...
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
return View(dinner);
However, in my application the model (a view model in this situation) contains multiple SelectLists. These lists are not instantiated at this point because this view model was just populated from the form submission. What is the recommended way to repopulate this SelectLists before sending them back to the user?
This is what I want my controller to do:
public ActionResult Save(MyModel model)
{
if (ModelState.IsValid)
{
businessClass.Save(model);
return RedirectToAction("Index", "Home");
}
// This won't work because model has uninstantiated SelectLists
return View("MyView", model);
}
I don't want to send the model to my business logic if the ModelState is invalid, but it doesn't seem to make sense to put SelectList population code in my controller. Should I create a public method in my business logic solely for doing this kind of stuff on my view model(s)?
Personally I like to keep it simple:-
[HttpGet]
public Edit(int id) {
EditForm form = new EditForm();
// Populate from the db or whatever...
PopulateEditPageSelectLists(form);
return View(form);
}
[HttpPost]
public Edit(EditForm form) {
if (ModelState.IsValid) {
// Do stuff and redirect...
}
PopulateEditPageSelectLists(form);
return View(form);
}
public void PopulateEditPageSelectLists(form) {
// Get lookup data from the db or whatever.
}
If the logic to populate the select lists is all kinds crazy it might be worthwhile moving to a separate class or whatever it but as a first step this is the best place to start.
You dont say how much reusability would you like. But personally, i like things "clear" (dont invading controller) and reausable as possible, and that in MVC means - filters.
Look at this :
public class SupplyLanguagesAttribute : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
{
filterContext.Controller.ViewData["languagesList"] =
someService.LoadLanguagesAsDictionary();
base.OnActionExecuting(filterContext);
}
}
then you just use it with every action method where you "might" need languages :
[SupplyLanguages]
public ActionResult DoSomething()
{
...
}
And then in view, you can use the data directly for DropDownList from ViewData, or you can even "wrap" this too (and avoid "magic strings" in views), with custom reusable DropDown :
public static MvcHtmlString LanguageDropDown(this HtmlHelper html, string name, object selectValue, bool defaultOption = false)
{
var languages = html.ViewData["languagesList"] as IDictionary<string,string>;
if (languages == null || languages.Count() == 0)
throw new ArgumentNullException("LanguageDropDown cannot operate without list of languages loaded in ViewData. Use SupplyLanguages filter.");
var list = new SelectList(languages, "Key", "Value", selectValue);
return SelectExtensions.DropDownList(html, name, list);
}
My controllers populate the SelectLists on my Model if the ModelState is not valid.
Following Separation of Concerns, your business classes shouldn't know anything about the view model at all. If your view needs a list of employees your controller gets a list of employees from your business layer and creates the SelectList that your view needs.
Example
public ActionResult Save(MyModel model)
{
if (ModelState.IsValid)
{
businessClass.Save(model);
return RedirectToAction("Index", "Home");
}
model.PossibleEmployees
= _employeeRepository.All().Select(e =>
new SelectListItem{Text=e.Name,
Value=e.Id});
return View("MyView", model);
}
Update
If your select list population code is determining WHICH options to present I think you probably should move that to a service in your business layer. If reusability is the big concern, rouen's answer looks like it has the most possibility for reuse.
I use to fill lists even when the model is invalid. One other possible solution is to have an action returning the json information and build the select via ajax. SOmetimes I've also resorted to static properties / cached collections. I guess it's always depending on the particular case.
PS: You can use a local Model in each action, so I can leave initialization inside the Model constructor. (often I override a base model with [NonAction] utilities as well).
For example, I have an Employee list used widely in your application.
I've added some utility method in a base controller to build up SelectListItems and the likes. Since each and every model inherits from the base, I've got them almost everywhere in the app. Of course the Collection is filled via a dedicated business objec.
What I do is I have a static function in a class that returns a SelectList. The method accepts an Enum value which defines which SelectList to return. In the View the DropDownList or DropDownListFor functions call this function to get the SelectList.
The static function looks like this:
class HelperMethods
{
enum LookupType {Users, Companies, States};
public static SelectList CommonSelectList(LookupType type, int? filterValue = null)
//filterValue can be used if the results need to be filtered in some way
var db = new WhateverEntities();
switch (type)
{
case LookupType.Users:
var list = db.Users.OrderBy(u => u.LastName).ToList()
return new SelectList(list, "ID", "FullName")
break;
case LookupType.Companies
var list = db.Companies.OrderBy(u => u.Name).ToList()
return new SelectList(list, "ID", "Name")
break;
//and so on...
}
}
}
And the view contains this:
#Html.DropDownListFor(m => m.UserID, HelperMethods.CommonSelectList(LookupType.Users))
This way the Model and Controller does not need code to configure a SelectList to send over to the View. It makes it very easy to reuse a SelectList that has already been configured. Also, if a View needs to loop through a list of objects, then this same function can be used to get a list for that. This is the simplest and most convenient way I found of doing this.

Passing ViewModel Object From ActionMethod to Another

I have a registration form in the side bar of my web application. When the user submits the entered data, the user should be redirected to another page with a more complete registration form when he can fill the rest of the data. The data that was entered by the user in the first form should be already there in the second form, but that's not happening... I checked to see the value of the view model I'm passing to the second action method and it was null and in the browser's address bar I get:
http://localhost:2732/User/RegisterPage?model=Sharwe.MVC.ViewModels.RegisterPageViewModel
Here's the code:
public ActionResult Register()
{
return PartialView(new RegisterViewModel());
}
[HttpPost]
public ActionResult Register(RegisterViewModel dto)
{
var model = Mapper.Map<RegisterViewModel, RegisterPageViewModel>(dto);
return RedirectToAction("RegisterPage", "User", new { viewModel = model });
}
public ActionResult RegisterPage(RegisterPageViewModel viewModel)
{
return View(viewModel);
}
Isn't that the way to do this? Or am I missing something here...?
The Dictionary passed to RedirectToAction() is the Route Value not the View Model. And RedirectToAction() is basically telling the browser to go to a certain URL. Browser by default makes the GET request and obviously you lose your data.
For this, you need to use TempData dictionary. You can store view model in TempData and then RedirectToAction() to RegisterPage. TempData saves the data for only 1 request span and would delete it automatically. It's ideal for this scenario.
See this for more details > The value for a object inside a viewmodel lost on redirect to action in asp.net mvc 2.0?
In this particular case you don't need to use RedirectToAction, you can simply call the RegisterPage action directly:
[HttpPost]
public ActionResult Register(RegisterViewModel dto)
{
var model = Mapper.Map<RegisterViewModel, RegisterPageViewModel>(dto);
return RegisterPage(model);
}
public ActionResult RegisterPage(RegisterPageViewModel viewModel)
{
return View(viewModel);
}

Resources