persisting dropdownlist between mvc post if the ModelState is Invalid - c#

I have a simple form which saves the following entity
public class TravelInfo{
public int ID {get;set;}
public string CentreCode {get;set;}
public DateTime TravelDate {get;set;}
}
I have the standard 2 create methods in my controller - 1 get 1 post and am using this viewmodel to get stuff into the view.
public class TravelInfoVM{
public TravelInfo TravelInfo{get;set;}
public IEnumerable<SelectListItem> Centres {get;set;}
}
Controller methods...
public ActionResult Create(){
var CentresList = db.Centres.Select(c=> new SelectListItem {Text = c.Name, Value = c.Code}).ToList();
TravelInfoVM = new TravelInfoVM(){Centres = CentresList};
return View(TravelInfoVM);
}
[HttpPost]
public ActionResult Create(TravelInfoVM model){
//the Centres part of the model at this point is empty
if(ModelState.IsValid){
//save
//redirect
}
//do i **REALLY** have to get it again as below, or can I hold on to it somehow?
model.Centres = db.Centres.Select(c=> new SelectListItem {Text = c.Name, Value = c.Code}).ToList();
return View(model);
}
the question is, do I really need to do a second round trip to the DB to get the list of Centres if the ModelState comes back as invalid? or is there a better/different way to persist this list across posts, until the user correctly enters the details required to save..? or do i have completely the wrong end of the stick..

Not without adding it to the session, which is an inappropriate use of the session. Otherwise, each request is a unique snowflake, even if it seems like it's the same because you're returning the same form with errors.
I wouldn't worry about this. It's just one query, and since it's the same query already issued previously, it will very likely (or at least could be) cached, so it may not actually hit the database, anyways.
One thing I would recommend though is abstracting the query so that your GET and POST actions will simply call a function that won't change, and if you need to make a change to how the select list is created, you just do it in one place:
internal void PopulateCentreChoices(TravelInfoVM model)
{
model.Centres = db.Centres.Select(c=> new SelectListItem {Text = c.Name, Value = c.Code}).ToList();
}
...
public ActionResult Create(){
var model = new TravelInfoVM();
PopulateCentreChoices(model);
return View(model);
}
[HttpPost]
public ActionResult Create(TravelInfoVM model){
if(ModelState.IsValid){
//save
//redirect
}
PopulateCentreChoices(model);
return View(model);
}

Related

Model is not updating and rendering on the 'results' page

I'm trying to update my model from an [HttpPost] The user enters their job title and I want to update the model and render it on a results page. However, its not updating the model based on the scope. Default is "Student" and I want it to display whatever the user inputs.
Model
public class InternshipModel {
public string Employer { get; set; }
public string Major { get; set; }
public string Title { get; set; }
}
Controller
Models.InternshipModel mod = new Models.InternshipModel() { Major = "Computer Science", Employer = "Random", Title = "Student" };
//After Post method
[HttpPost]
public ActionResult Index(string major, string employer, string title) {
mod.Title = title;
UpdateModel(mod.Title);
return RedirectToAction("Results", "Home");
}
public ActionResult Results() {
ViewBag.Message = "This is the results page.";
ViewBag.Changes = mod.Title; //Should expect user input, not default 'Title'
return View();
}
Another way is to declare your model as static like
Public static Models.InternshipModel mod = new Models.InternshipModel() { Major = "Computer Science", Employer = "Random", Title = "Student" };
In static only one copy of your model will be created when the application starts and will reserve memory until the application stops thats why its not suggested .You can try the solution of Stephen since its a better way to achieve this.
The mod variable that you have created (scope for the controller), resets each time you move out of the controller, or as #stephen in his comment mentions, it creates a new instance each time you make a request.
Here, you move out of the controller to the routing table when you return RedirectToAction("Results","home");
So, the best way to deal with this issue is to save the model in a database and retrieve wherever you need it.
Since you have mentioned, you would query the database with this details, It would be better to send this object as a whole or only the title to the Action you are passing it to. Like
return RedirectToAction("Results", "Home", new { InternshipModel = mod });
or
return RedirectToAction("Results", "Home", new { title = mod.Title });
and you would recieve them in the Action as parameters.
like :
public ActionResult Results(Models.InternshipModel mod)
{
ViewBag.Message = "This is the results page.";
ViewBag.Changes = mod.Title; //User input, not default 'Title'
return View();
}
Hope this helps.
Another simple change you can do is adding static keyword before the object declaration. This will help you to initialise only one instance and your latest values will be reflected when object is invoked
Public static Models.InternshipModel mod = new Models.InternshipModel() { Major = "Computer Science", Employer = "Random", Title = "Student" };

ASP.NET MVC dropdown-list from database

Ok, so I'm new to this whole MVC-world, but it seems to be a pretty good way of getting things done and I'm trying to make it work here.
The problem is:
I can't get data from my table in my SQL-database to a simple drop-down form on my registration page.
I have just no idea where to put the stuff, where to code to open the table, select the ids, where to put the response.write and how do I send it to the view?
My Model is this:
public class users
{
public string name {get; set;}
public int user_id {get; set;}
}
My Controller is this:
[HttpGet]
public ActionResult ListUser()
{
return View();
}
And my View is this:
#model Community.Models.users
I have googled for 2 days now and watched several videos on youtube but of no use, I can't find it. Please, anyone with some knowledge here? And please point me to some good tutorials and/or forums where I can browse for more questions I might have
Still no luck on this project..
I'm creating a form and within that form, i want a db-loop (IEnumerable).. But the current model is not a IEnumerable. I'm pretty much stuck, watched a bunch of tutorials and they all just list ONE connection, what if I want two models?
Here is my Controller, I get that you must pass a list to the view, right?
public ActionResult Registration()
{
return View(db.users.ToList());
}
How do i get hold of that list in my view witout an IEnumerable model?
#neoistheone, your example didnt help me much, my DB opens like this:
private DataBaseContext db = new DataBaseContext();
and i don't know how, but it opens the connection.
I've tried for so many hours now, its just silly, haven't slept for soo long!
I'm used to programming ASP-Classic fyi, and this is my first serious try to upgrade my knowledge about programing an up-to-date language and OOP.
Add the SelectList to your model:
public SelectList DropDownList { get; set; }
build the class for that collection:
public class MyListTable
{
public string Key { get; set; }
public string Display { get; set; }
}
and then in your controller, load the data for the MyListTable class from the database:
var list = new List<MyListTable>();
using (SqlConnection c = new SqlConnection(cString))
using (SqlCommand cmd = new SqlCommand("SELECT KeyField, DisplayField FROM Table", c))
{
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
list.Add(new MyListTable
{
Key = rdr.GetString(0),
Display = rdr.GetString(1)
});
}
}
}
var model = new users();
model.DropDownList = new SelectList(list, "Key", "Display");
and then finally, you need to send your model to the view:
return View(model);
Now in the Razor you can display this:
#Html.DropDownListFor(m => Model.DropDownList);
You of course can name these things better names, but you get the idea.
There a great answers already but Here is another approach.
You will use user as a model, ListUserViewModel as view-model and UserController as the contoller. The work of view-model is to carry all info needed to be displayed on the page from the controller without adding unwanted properties into the model class. In your case list of users from database into the drop down list.
Model:
public class User //By the way use singular when naming a class
{
public string name {get; set;}
public int user_id {get; set;}
}
View-model
public class ListUserViewModel
{
public list<User> Users{get; set;}
}
Controller
public class UserController : Controller
{
private DataBaseContext db = new DataBaseContext();
[HttpGet]
public ActionResult ListUser()
{
var users = db.Users.ToList();
var viewModel = new ListUserViewModel { Users = users };
return View(viewModel);
}
}
Now use ListUserViewModel instead of User in your view as a model
#model Community.Models.ListUserViewModel
and the drop down
#Html.DropDownListFor(m => m.Users, new SelectList(Model.Users, "user_id", "name"), " ")
Explanation:
You are creating drop down list for Users with Model.Users as select list data source. "user_id" as a value of the selected user and "name" as display label. the last argument( i put empty string " ") is a default value that the drop down will display before selection.
I hope this will help you or someone else.
Try this,
model
public string CoutryID { get; set; }
public List<SelectListItem> CountryList { get; set; }
Controller method which fill the list
public List<Country> getCountryList()
{
using (QRMG_VendorPortalDataContext _context = new QRMG_VendorPortalDataContext())
{
return (from c in _context.Countries
where c.IsDeleted == false
select c).ToList();
}
}
Drop down list in View
#Html.DropDownListFor(m => m.CoutryID,
new SelectList(Model.CountryList,
"CoutryID", "Value"))
I find this system works (and avoids using ViewBag):
View Model:
public class YourViewModel
{
// This could be string, int or Guid depending on what you need as the value
public int YourDropdownSelectedValue { get; set; }
public IEnumerable<SelectListItem> YourDropdownList { get; set; }
}
Controller:
// Get database values (by whatever selection method is appropriate)
var dbValues = db.YourEntity.ToList();
// Make Selectlist, which is IEnumerable<SelectListItem>
var yourDropdownList = new SelectList(dbValues.Select(item => new SelectListItem
{
Text = item.YourSelectedDbText,
Value = item.YourSelectedDbValue
}).ToList(), "Value", "Text");
// Assign the Selectlist to the View Model
var viewModel = new YourViewModel(){
// Optional: if you want a pre-selected value - remove this for no pre-selected value
YourDropdownSelectedValue = dbValues.FirstOrDefault(),
// The Dropdownlist values
YourDropdownList = yourDropdownList
};
// return View with View Model
return View(viewModel);
and in the View:
#Html.DropDownListFor(a => a.YourDropdownSelectedValue, Model.YourDropdownList, "select this text - change this to null to exclude", new { #class = "your-class" })
If you are really new to ASP.Net MVC, this is a quite good Tutorial that shows you how the MVC-Pattern works.
MVC3: http://www.asp.net/mvc/tutorials/getting-started-with-aspnet-mvc3/cs/intro-to-aspnet-mvc-3
MVC4: http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/intro-to-aspnet-mvc-4
Here is the sample-code to download: http://code.msdn.microsoft.com/Introduction-to-MVC-3-10d1b098
this is an helpful video: http://www.asp.net/mvc/videos/mvc-1/conference-presentations/creating-nerddinnercom-with-microsoft-aspnet-model-view-controller-mvc
this is my table in the database
take look it my Action controller
// GET: Letters
public ActionResult Index()
{
ViewBag.LetterStatus = new SelectList(LetterStatusService.GetAllLettersStatus(), "Id", (CultureHelper.GetCurrentCulture() == "ar") ? "NameArabic" : "Name", Request.QueryString["LetterStatus"]);
return View();
}
and in the view
#Html.DropDownList("LetterStatus")
the constructor I used is
new SelectList(
list<Objlect> myListFromDatabase,
string PropertyNameOfValueInHtml,
string PropertyNameOfDesplayInHtml,
string SelectedItemValue
);
this line Request.QueryString["LetterStatus"] because I send the Selected Items within QuerySrting
and based on CurrentCulture I chose what column to display
and the result are
but I think the best way to do this,,,, is to get or create the Items then Iterate throw them to generate the select tag manually. I described this approach well in this answer
hope this helps you
I had to put Everything together from about 5 different Stack Overflow entries. I'm a newbie that's not in love with EF. I prefer doing things in SQL. Mike Perrenoud got me started, but I had trouble getting his solution to compile properly in the view.
First, I declared my dropdown Id/Name inside my model and then declared a numeric selector plus a SelectList
public class BusinessType
{
public int Id { get; set; }
public string Name { get; set; }
}
public int SelectedBusinessId { get; set; }
public SelectList BusinessTypeddList { get; set; }
In my [HttpGet] (the one that does not pass in a model), I ALWAYS populate my dropdowns. I'm doing things in SQL Server because I find it easier than the EF abstraction and syntax (which baffles me). This code declares a List of business types and populates it directly from the database. Feel free to use a sproc or whatever. IMPORTANT: Don't forget to return the View(model) at the end or your View will get an object missing reference error.
var list = new List<MerchantDetail.BusinessType>();
using (var con = new SqlConnection(Common.DB_CONNECTION_STRING_BOARDING))
{
con.Open();
using (var command = new SqlCommand("SELECT Id, BusinessTypeDesc as Name FROM BusinessType order by Id", con))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
list.Add(new MerchantDetail.BusinessType
{
Id = reader.GetInt32(0),
Name = reader.GetString(1)
});
}
}
}
}
var model = new MerchantDetail();
model.BusinessTypeddList = new SelectList(list, "Id", "Name");
model.SelectedBusinessId = 0;
return View(model);
The view is simple. Mine looks like this.
#Html.DropDownListFor(m => m.SelectedBusinessId, Model.BusinessTypeddList, "Select One:", new { #class = "custom-select" });
NOTE: Setting the SelectedBusinessId to 0 only works if there's nothing in the model. It will be set to one of the dropdown values otherwise. That will happen in a different HttpGet that passes the model in.

How to properly call JsonResult method in controller from view model?

From the oddity in my question, I suspect I'm not going in the right direction.
Supposed I have a view that has a paginated list among several other items. On first load, the list is loaded with the first page of the list (this is where I'm attempting to call my JsonResult method in the controller from the model).
public class FooListViewModel
{
public FooListViewModel()
{
DateTime today = DateTime.Today;
DateTime later = DateTime.Today.AddDays(5);
// Here I need to make call to my JsonResult method
// in the controller to populate fooItems
}
public IEnumerable<FooItem> fooItems { get; private set; }
public IEnumerable<DateTime> dates { get; private set; }
}
In controller
[HttpGet]
public JsonResult GetItems(DateTime start, DateTime end)
{
var fooItems = domainServices.Foo.GetAllFooItems();
// Add predicates to filter between start and end dates.
return Json(fooItems, JsonRequestBehavior.AllowGet);
}
On each page button click, it will reload ONLY the list with another call to the JsonResult method in the controller via AJAX, but this has already been done.
It's easier to just simulate the button click on the client when the page just loads, then you only have one routine to do all the getting data, inserting into page and correctly formatting it.
Otherwise, create a GetData function that your controller routine (Index?) calls and your GetJSON routine calls to get whatever data you need. The index method will stick this in the model to pass to the view, and the GetJSON routine just returns the result as json.
Sample code:
public ActionResult Index()
{
MyViewModel model = new MyViewModel();
model.data = GetData();
return View(model);
}
public JsonResult GetJson(DateTime startDate,DateTime endDate)
{
var result=GetData(startDate,endDate);
return Json(result);
}
private IEnumerable<MyData> GetData(DateTime startDate=null,DateTime endDate=null)
{
return something;
}
You really shouldn't be calling action methods in any way other than via a HTTP request (since what you get back should he a HTTP response). This is, in a sense, like asking your server-side code to send a request to a page, which is all backwards.
If you have logic that you need in both in your controller and in your model constructor, you should probably be abstracting this logic from your presentation layer, exposing it in your business layer and just consuming it in both places:
public class FooProvider
{
public List<Foo> GetFilteredFoos (/* whatever you need */)
{
// filter and return foos
}
}
Controller:
public JsonResult GetItems(DateTime start, DateTime end)
{
var fooItems = domainServices.Foo.GetFilteredFoos(/* some params */);
return Json(fooItems);
}
Model:
public FooListViewModel()
{
DateTime today = DateTime.Today;
DateTime later = DateTime.Today.AddDays(5);
var ds = DomainServices();
fooItems = ds.Foo.GetFilteredFoos(/* some params */);
}
Here, I assume that DomainServices.Foo is an instance of a class called FooProvider.
Having said this, however, I would avoid having this kind of login in your view model at all, where possible. Why not simply make the call in the controller when you first initialize the model?
public ActionResult Index()
{
var model = FooListViewModel();
model.fooItems = ds.Foo.GetFilteredFoos(/* things */);
return View(model);
}
Then, update via AJAX as normal.

using ViewBag to pass a model object

I'm trying to sort my result page (which is in another view than the filtration page). I have faced this weird issue I do not understand why keeps happening to me.
All the codes provided in very short form, please ask me if you need any other parts of my code for more information.
My Index view(where user filters results):
#model IEnumerable<Cars.Models.CarSearch>
#using (Html.BeginForm("SearchResult", "Home", FormMethod.Post,
new
{
id = "CategoryFormID",
data_modelListAction = #Url.Action("ModelList"),
data_makeListAction = #Url.Action("MakeList"),
data_editionListAction = #Url.Action("EditionList")
}))
{
<label>Make</label>
<select id="MakeID" name="carMake">
<option>All Makes</option>
</select>
}
My SearchResult view:
#model IEnumerable<Cars.Models.Car>
Make
My model:
public class Car
{
public String Make { get; set; } //is my table model
}
public class CarFilter {
public String carMake { get; set; }
}
public class CarSearch {
public CarFilter CarFilter { get; set; }
public byte PageSize { get; set; }
public short PageNumber { get; set; }
public int TotalRows { get; set; }
}
My Controller:
public ActionResult SearchResult(String sortOrder, CarFilter filters)
{
ViewBag.CurrentFilters = filters;
return View();
}
All I'm trying to do is to get carMake from Index post it to controller in CarFilter form (since in my code there are LOTS of fields in the form and I don't want to write them all down) and when user clicks on sort by Make it GET the SearchResult method and it's supposed to set filters = ViewBag.CurrentFilters which is the value user inputted from beginning.
Now the funny part is, when I replace CarFilter filters with String carMake and other places respectively. It works like a charm.
My question:
Why?
How can I do this with CarFilter filters?
UPDATE:
Problem is that filters = ViewBag.CurrentFilters in my SearchResult view does not work with the type CarFilter, because it keeps giving me NULL value when user clicked on the sort by Make.
Second UPDATE:
I tried changing filters = ViewBag.CurrentFilters with CarFilter = ViewBag.CurrentFilters. Now CarFilter filters in my SearchResult(...)method in my controller is not and null object, but ALL the values of the objects in the model class is null (which shouldn't be). I mean the filters object exists but it seems like the values of CarFilter class in my model haven't been passed by ViewBag.CurrentFilters to the view.
when you canged the name it worked because framework found property name and the bind it to what you have within action parameters doesnt work so nicely with objects. My advice is to stick with simple types
Here is similiar case:
How to send model object in Html.RenderAction (MVC3)
Its not a ViewBag problem thants how it works in general. Its the prime reason for using flatted models :/

How do I transfer ViewModel data between POST requests in ASP.NET MVC?

I have a ViewModel like so:
public class ProductEditModel
{
public string Name { get; set; }
public int CategoryId { get; set; }
public SelectList Categories { get; set; }
public ProductEditModel()
{
var categories = Database.GetCategories(); // made-up method
Categories = new SelectList(categories, "Key", "Value");
}
}
Then I have two controller methods that uses this model:
public ActionResult Create()
{
var model = new ProductEditModel();
return View(model);
}
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
return View(model); // this is where it fails
}
}
The first time the user goes to the Create view, they are presented with a list of categories. However, if they fail validation, the View is sent back to them, except this time the Categories property is null. This is understandable because the ModelBinder does not persist Categories if it wasn't in the POST request. My question is, what's the best way of keeping Categories persisted? I can do something like this:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
// manually populate Categories again if validation failed
model.Categories = new SelectList(categories, "Key", "Value");
return View(model); // this is where it fails
}
}
But this is an ugly solution. How else can I persist it? I can't use a hidden field because it's a collection.
I would use the repository to fetch whatever data is needed and don't think it's an ugly solution:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (!ModelState.IsValid)
{
// manually populate Categories again if validation failed
model.Categories = Repository.GetCategories();
return View(model);
}
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
// I would recommend you to redirect here
return RedirectToAction("Success");
}
To further refactor this I would recommend you watching the excellent Putting Your Controllers on a Diet video presentation by Jimmy Bogard.
I typically implement my lists (for drop downs) as a readonly property. When the View gets the value the property is self contained on what it needs to return the values.
public SelectList Categories
{
get
{
var categories = Database.GetCategories(); // made-up method
return new SelectList(categories, "Key", "Value");
}
}
If necessary you can grab the currently selected item (i.e. validation failed) from the property containing the id that was posted and bound to the instance of your class.
In my case I have a BaseModel class where I keep all those property list as class attributes.
Like in the following sample:
public IEnumerable<SelectListItem> CountryList
{
get
{
return GetCountryList().Select(
t => new SelectListItem { Text = t.Name, Value = Convert.ToString(t.CountryID) });
}
}
GetCountryList() is a function that ask a Singleton for data. This would only happen once in the app lifecycle
Another way for doing this, and if those lists are pretty big, would be to have a static utility class with the lookup table that returns the SelectListItem.
If you need to access a list that change from time to time then simply dont use a Singleton class.

Categories

Resources