In MVC4:
I have the following property in my model used for a dropdown list:
public SelectList Subjects { get; set; }
I set the Subjects property in my Index() Action on page load and return the model.
The dropdown gets populated just fine with the SelectListItems.
#Html.DropDownListFor(x => x.Subject, new SelectList(Model.Subjects, "Text", "Text", "Other"))
When I submit the form the Subjects SelectList in the model has changed to null. There has to be a simple way to persist this on HttpPost. I assume I want to submit and post this SelectList as well, along with all the form fields? How would I do this?
It is commonly accepted that you re-populate a SelectList after the Post action. Just extract it inside a method and call it in the Get and Post action.
Posting it back again to the controller is not the way to go. You can cache the items in the SelectList so you won't have to make a query to the data store twice.
Example:
public ActionResult Create()
{
var model = new SubjectModel();
PopulateSubjectList(model);
return View(model);
}
[HttpPost]
public ActionResult Create(SubjectModel model)
{
if (ModelState.IsValid)
{
// Save item..
}
// Something went wrong.
PopulateSubjectList(model);
return View(model);
}
private void PopulateSubjectList(SubjectModel model)
{
if (MemoryCache.Default.Contains("SubjectList"))
{
// The SubjectList already exists in the cache,
model.Subjects = (List<Subject>)MemoryCache.Default.Get("SubjectList");
}
else
{
// The select list does not yet exists in the cache, fetch items from the data store.
List<Subject> selectList = _db.Subjects.ToList();
// Cache the list in memory for 15 minutes.
MemoryCache.Default.Add("SubjectList", selectList, DateTime.Now.AddMinutes(15));
model.Subjects = selectList;
}
}
Note: MemoryCache uses the System.Runtime.Caching namespace. See: System.Runtime.Caching namespace.
Also, caching should be in a seperate layer between your controller (or business layer) and the data access layer, this is just for clarity.
Browsers only post back the selected values on the form elements. Also, its not a good idea to post back the values which can be retrieved from the data store. You would have to pull the items in the list just like you did while populating the list.
Also, MVC does not maintain the state of the page like .NET webpages as it does not have a view state. Developers are fully responsible for managing states of pages between the post backs, which is the essence of MVC design pattern.
Related
In my MVC controller I have two action methods.
The first one is Index method:
public IActionResult Index()
{
return PopulateViewModel();
}
The "PopulateViewModel" Action Method is used for updating of the view model and then showing these updated values on the Index view.
public IActionResult PopulateViewModel()
{
ViewModel viewModel = new ViewModel()
{
//updating values in the view model
//the values are received when the form in the view is submitted
};
return View("Index", viewModel);
}
The problem that I have is that on my Index view the updated values are not shown immediately after submitting the form in the view. When I submit the form I must then once again refresh the page to see the updated values.
What could be the reason for such behavior and how can I correct that?
You misunderstand the conceptual notion. The index is supposed to represent the initial page state. Other actions within the controller will modify the output by rendering the page with the adjusted model. Or handling server side model binding, but the concept is fundamentally achieving the same result.
Your controller logic should be within the following constraints.
public class SampleController : Controller
{
public IActionResult Index() => new View("...", ...);
public IActionResult SubmitSample(string location)
{
var model = service.GetLabLocations(location);
return View("...", model);
}
}
The index is simulating a GET request, returning the initial page in the required state. The form portion, should POST data, outlined in the SubmitSample portion of the code. This will change the state of the page, but the server will need to render with those changes. So the page will load with the attached model, for you to display.
This would represent Razor more than likely on the server side.
#if(Model != null)
foreach(var sample in Model)
{
// Markup, with the data
}
I'm developing a Web Application by using ASP.Net MVC 5. My model is something similar to:
Person
- int ID
- string FullName
- int PersonTypeId
PersonType
- Id
- Name
- Description
I'm working on the "create new Person" page. I have created a ViewModel with the following structure:
public class SampleAddViewModel
{
public Person person;
public SelectList personTypes; // Used to populate the DropDown element.
}
My controller's GET method (to simply display the page):
// GET: Add new person
public ActionResult Add()
{
SampleAddViewModel savm = new SampleAddViewModel();
AddPersonViewModel.personTypes = new SelectList(PersonTypesEntity.GetAll(), "Id", "Name");
return View(savm);
}
In my controller's POST method (to store the created person) I would expect to just receive the Person model, and not the entire ViewModel. But on the View page I think it is only possible to declare an #model razon line, which I think it must be #model SampleAddViewModel ...
Would it be possible to, in the POST Add entry, have something similar to the following:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Add([Bind(Include = "ID, Name, PersonTypeId")] Person person)
{
if (ModelState.IsValid)
{
db.Add(person);
db.SaveChanges();
return RedirectToAction("Index");
}
x <- //Should I re-create the ViewModel in here?
return View(x);
}
Which would be the best way to address the problem? I'm also trying to avoid using ViewBag. Maybe the best way in fact is to re-send the entire ViewModel.
If you have any errors on the server side on the POST method then yes, you'd want to send the ViewModel back to the view.
Since you are only sending a Person instance into the controller action and your View is expecting an instance of SampleAddViewModel, you should create an instance of one of these and pass it to the View; After all, you're going to need to repopulate the personTypes dropdown with data again.
I'm having some issues with my DropDownLists, because when I post the information and my Model is not valid it comes back "empty" to the page triggering an error exactly like this question.
I've used the solution proposed there and it fixed my problem. Anyway, I wanted to avoid querying the database every time my ModelState is not valid and I came with this approach. I would like to know if it is valid or if there are better ways to do it now, considering that instead of MVC2 (which was the MVC version from the question) I'm now using MVC 5, maybe they added something new to tackle this.
What I've done was to use the TempData to persist the information when my model is not valid.
public class ViewModel
{
[DisplayName("Project")]
public int ProjectID { get; set; }
public List<SelectListItem> Projects { get; set; }
//Other fields
}
Now my Create() Action (that populates Projects)
[HttpGet]
public ActionResult Create()
{
ViewModel vmodel = new ViewModel();
vmodel.Projects = db.GetProjects(User.Identity.Name).Select(x => new SelectListItem { Text = x.Description, Value = x.Id }).ToList();
TempData["Projects"] = vmodel.Projects;
return View(vmodel);
}
And my post would be like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ViewModel vmodel)
{
//Clear TempData (in theory will clear my tempdata when read, so if this controller redirects to another action my tempdata will be clear)
List<SelectListItem> projects = (TempData["Projects"] as List<SelectListItem>);
if (ModelState.IsValid)
{
//...
}
//If it got here it's going back to the screen.
//Repopulate the TempData (allowing it to exist one more trip)
TempData["Projects"] = projects;
vmodel.Projects = projects
return View(atendimento);
}
Is this approach a good one? Is there a better way to achieve that without querying the database every single time?
Thanks a lot!
You don't need to use TempData at all as you have a property in your view model to hold the dropdown items.
public ActionResult Create()
{
ViewModel vmodel = new ViewModel();
vmodel.Projects = GetProjects();
return View(vmodel);
}
private List<SelectListItem> GetProjects()
{
return db.GetProjects(User.Identity.Name)
.Select(x => new SelectListItem { Text = x.Description,
Value = x.Id }).ToList();
}
And in the view
#Html.DropDownListFor(s=>s.ProjectID,Model.Projects)
And in your HttpPost action, If ModelState is not valid, Reload the Projects collection again (because http is stateless)
if(ModelState.IsValid)
{
// to do :Save and redirect
}
model.Projects = GetProjects();
return View(model);
You may cache the Projects so that you do not need to hit the database every time, if you are too much worried about performance.
Personally, I wouldn't worry about querying the database each time for this kind of operation.
What if projects are added/deleted? This could be the reason the save failed (selected project deleted) and the user would never realise it.
I usually write a method to populate all of my view model's SelectListItems and then use this in my Get and in my Post if the validation fails.
I know it was silly to ask this question but I'm not able to figure it out and I need your help guys.
First of all I am new to MVC. In my Project I am using a dropdownlistFor helper for displaying a list of names available for a particular Id. I did that and it is displaying names for an id.
Now while posting the form I am getting a Null Reference Exception for property used in the dropdownlist.
Here is my property in model which is a list of names.
In my controller in the [HttpGet] I did this which calls a function and returns a list of names for that Id.
Now the list of names is being displyed while form loading. And my view is as
When I am submitting the form I am getting a Null Reference Exception because in new SelectList(Model.InterviewerName) the Model is NULL.
Is there anyway to get me out of this issue.
I think you should update your viewmodel like this:
public class InterviewViewModel
{
public List<SelectListItem> Interviewers { set;get;}
public int SelectedInterviewerID { set;get;}
//Other properties relevant to the view as needed
}
And in your GET action set the Interviewers collection property:
public ActionResult Interview()
{
var vm=new InterviewViewModel();
vm.Interviewers =GetInterViewrsFromSomewhere();
return View(vm);
}
public List<SelectListItem> GetInterViewrsFromSomewhere()
{
var list=new List<SelectListItem>();
//Items hard coded for demo. you can read from your db and fill here
list.Add(new SelectListItem { Value="1", Text="AA"});
list.Add(new SelectListItem { Value="2", Text="BB"});
return list;
}
And in your view which is strongly typed to InterviewViewModel
#model InterviewViewModel
#using(Html.Beginform())
{
<p>Select interviewer :
#Html.DropdownlistFor(x=>x.SelectedInterviewerID,Model.Interviewers,"Select")
<input type="submit" />
}
So when the form is posted, The selected interviewers id will be available in the SelectedInterviewerID property:
[HttpPost]
public ActionResult Interview(InterviewViewModel model)
{
if(ModelState.IsValid)
{
//check for model.SelectedIntervieweID value
//to do :Save and redirect
}
//Reload the dropdown data again before returning to view
vm.Interviewers=GetInterViewrsFromSomewhere();
return View(vm);
}
In the HttpPost action method, if you are returning the viewmodel back to the view, you need to refill the dropdown content because HTTP is stateless and it won't keep the dropdown content between the requests (Webforms does this using Viewstate and here in MVC we don't have that).
I think i spotted the problem, you need to do the following two things to resolve,
1) change the model property public IList<string> InterviewerName to public string InterviewerName
2) use ViewBag to take the selectlist values to the View.
Let me know if it helps.
I was wondering what the best way to approach this problem in ASP.NET MVC would be. The following is a trivial example of what I'd like to be able to do:
I have a webpage with textbox and a submit button. When the submit button is pressed the I would like the contents to be displayed on the same webpage. When it is pressed again I would like what was already displayed from the first submission to be displayed as well as the new data that was just submitted.
I have tried saving this data to a model, but the model is wiped clean every time the form posts. How could I do this and keep the data from the post before the last one (and the post before that)?
If you want data to persist between requests, as a starting point I would use 'TempData'. The TempData property value is stored in session state and exists until it is read or until the Session expires.
Example ViewModel:
public class SomeClass
{
public string Something { get; set; }
public List<string> RetainedValues { get; set; }
}
Example Controller:
[HttpGet]
public ActionResult Index()
{
return View("Index");
}
[HttpPost]
public ActionResult Index(SomeClass postedValues)
{
// retrieve retained values
var retained = (List<string>) TempData["RetainedValues"] ?? new List<string>();
retained.Add(postedValues.Something);
// save for next post
TempData["RetainedValues"] = retained;
// setup viewmodel
var model = new SomeClass
{
RetainedValues = retained
};
return View("Index", model);
}
Example View (strongly typed):
<div>
#foreach(var item in Model.RetainedValues)
{
<div>#item</div>
}
</div>
#using(Html.BeginForm())
{
#Html.EditorFor(m=>m.Something)
<input type="submit"/>
}
Just put an hidden field for your model property then your previews value will be loaded on it and passed it back to the next post.
Ex.: #Html.HiddenFor(model => model.YourProperty)
So knowing that you could have two properties ex.: one named newValue and other called allValues.
the allValues you use it with an hidden field and your newValue you use to insert the new ones. So on post you just add the newValue to the allValues.
Something like that:
model.allValues += newValue;
--UPDATE
Or you can use session or tempdata as mentioned by #Jesse
For this case I would prefer to use hidden fields as it has a lower complexity and its data didnt need be secure as it will be shown to the user anyway.