I have a ASP.NET MVC wrapper class that wraps a List containing ViewModels:
public class ScheduleContainerViewModel
{
public List<SchedulingViewModel> Items { get; set; }
public ScheduleContainerViewModel(List<SchedulingViewModel> items)
{
Items = items;
}
SchedulingViewModel contains a Schedule object and a Users object.
ScheduleContainerViewModel is eventually passed to a PartialView.
This PartialView is called via Ajax to populate a div in a View. The model for the PartialView is ScheduleContainerViewModel and the model for the View is a Job.
The Ajax GET works great. When I try to POST it, however, the Controller is never called and I get a 500 server error. Here's some of the XHR data being POSTed:
It seems to me that List Items is being passed, containing SchedulingViewModels and their associated Schedules, but maybe not?
There are three relevant methods in the Controller. Here are their signatures:
public ActionResult ScheduleJob(int? id)
public ActionResult Table(int? id)
[HttpPost]
public ActionResult Table(ScheduleContainerViewModel cvm)
ScheduleJob loads the initial View, which calls [HttpGet] Table to load the PartialView. Finally, [HttpPost] Table is the one being POSTed to. Why can't the model binder see the data being passed to it in the XHR?
My only guess it that it's because the model used by the View called by ScheduleJob is different than the one being POSTed to [HttpPost] Table.
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
}
My application has a view that is linked from various different other views located in different controllers. I would like to have a 'Back' button in this view that will send the user back to the previous view, which ever that may be.
To this end I have added a string attribute to the viewmodel which I would like to use to reference the originating view /MyController/MyAction in the #Html.ActionLink parameters.
Some of the views linking to this view belongs to current controller, some belong to other controllers. This means I have to pass the controller as well as the action to the ActionLink.
As it stands, my code looks something like this:
ViewModel:
public class MyViewModel
{
public int MyData { get; set; }
[HiddenInput]
public string ReturnUrl { get; set; }
}
View:
#Html.ActionLink("Back", Model.ReturnUrl)
This produces the undesirable result of localhost:####/CurrentController/MyController/MyAction
Of course I could always save two strings on the ViewModel (one for the controller and one for the action) and pass them to the ActionLink seperately, but if possible I would like to avoid that. Is there an overload of ActionLink that allows me to use a single return url string, without making implications about the controller?
Also, is it possible to achieve the same thing on the controller side, f.ex. like this:
[HttpPost]
public ActionResult DoStuff(MyViewModel model)
{
// do stuff
return RedirectToAction(model.ReturnUrl);
}
You shouldn't use ActionLink. Just use an anchor tag, like this:
<a href='#Url.Content(Model.ReturnUrl)'>Back</a>
And, in your Action, you can do it like below:
[HttpPost]
public ActionResult DoStuff(MyViewModel model)
{
// do stuff
return Redirect(model.ReturnUrl);
}
Also, another solution would be to have two properties (ReturnController and ReturnAction) in your model.
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.
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.
This question is related to another I ask recently, it can be found here for some background information.
Here is the code in the Edit ActionResult:
public virtual ActionResult Edit(int id)
{
///Set data for DropDownLists.
ViewData["MethodList"] = tr.ListMethods();
ViewData["GenderList"] = tr.ListGenders();
ViewData["FocusAreaList"] = tr.ListFocusAreas();
ViewData["SiteList"] = tr.ListSites();
ViewData["TypeList"] = tr.ListTalkbackTypes();
ViewData["CategoryList"] = tr.ListCategories();
return View(tr.GetTalkback(id));
}
I add lists to the ViewData to use in the dropdownlists, these are all IEnumerable and are all returning values.
GetTalkback() returns an Entity framework object of type Talkback which is generated from the Talkback table.
The DropDownListFor code is:
<%: Html.DropDownListFor(model=>model.method_id,new SelectList(ViewData["MethodList"] as IEnumerable<SelectListItem>,"Value","Text",Model.method_id)) %>
The record I am viewing has values in all fields. When I click submit on the View, I get an Object reference not set to an instance of an object. error on the above line.
There are a number of standard fields in the form prior to this, so the error is only occurring on dropdown lists, and it is occurring on all of them.
Any ideas? This is my first foray in to MVC, C#, and Entity so I am completely lost!
If you have [HttpPost] method like that
[HttpPost]
public ActionResult Edit(Talkback model)
{
//Do something with model
return View(model);
}
You have to fill ViewData again. If you don't do it, you'll have Object reference not set to an instance of an object errors.
The best thing to do would be to follow POST-REDIRECT-GET patter and do it like that:
[HttpPost]
public ActionResult Edit(Talkback model)
{
//Do something with model
return RedirectToAction("Edit", new { id = model.id });
}
You'll have ViewData filled again by [HttpGet] method.