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.
Related
I know this question has been asked several times and answered, but none of the solutions are working for me.
This is my ViewModel:
public class FlightSearchResults
{
public FlightSearch SearchModel { get; set; }
public List<vwLowFareSearchResults> SearchResults { get; set; }
public string TestString { get; set; }
public DateTime TestDate { get; set; }
}
I am using a RedirectToAction like this:
FlightSearchResults flightSearchResults = new FlightSearchResults();
flightSearchResults.SearchModel = model;
flightSearchResults.SearchResults = flights;
flightSearchResults.TestDate = DateTime.Now.AddDays(-2);
flightSearchResults.TestString = "Just Testing . . .";
return RedirectToAction("index", "flights", flightSearchResults);
I am only getting this List<vwLowFareSearchResults> SearchResults property in my flights index, none of the others are having values assigned. I have tried several variations from some threads on StackOverFlow like:
return RedirectToAction("index", "flights", new { SearchResults = flights, SearchModel = model });
return RedirectToAction("Result", "Dialog", new RouteValueDictionary(flightSearchResults));
I can return the view like this:
return View("~/Views/Flights/Index.cshtml", flightSearchResults);
but this is not a good solution as the url is not updated. I am modifying some older projects and it's mess of using Session and Viewbag.
I need to simplify and the pattern of view and controller communication of data in the previous code is a mess. Is it possible to do this without using the ViewData or Viewbag in a simple RedirectToAction.
Any kind of help in achieving this will be great as I am new to MVC.
Here is an approach I used recently. Try:-
... Previous code omitted.
//In your controller action, save the data to TempData...
TempData["FlightSearchResults"] = FlightSearchResults;
return RedirectToAction("flights");
}
public ActionResult flights()
{
FlightSearchResults flightResults = TempData["FlightSearchResults"];
return View(flightResults);
}
I actually used NewtonSoft to serialise the objects to a string, and then back again in the target action. So you might want to try something like ...
using Newtonsoft.Json;
...
...
... Previous code omitted.
//In your controller action, save the data to TempData...
TempData["FlightSearchResults"] = JsonConvert.SerializeObject(FlightSearchResults);
return RedirectToAction("flights");
}
public ActionResult flights()
{
string storedResults = TempData["FlightSearchResults"].ToString();
FlightSearchResults flightResults = JsonConvert.DeserializeObject<FlightSearchResults>(storedResults);
return View(flightResults);
}
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);
}
I am having previously defined action which return the List<T>() int Json Format using JsonResult.
I want to use the same action's JSON result into another action.
For better understanding,
My Common Action is::
public JsonResult GetReferences(long CustomerID)
{
var CustomerReference = new List<ReferenceViewModel>();
//Code to push data into CustomerReference
return Json(CustomerReference.OrderBy(x=>x.ReferenceName));
}
I want to use the above JsonResult into my other Action as::
public ActionResult MassUpdate(List<long> CustomerIDs, List<long> DocumentIDs)
{
List<ReferenceViewModel> JsonCustomerReference = GetReferences(CustomerID);
}
But I am not able to access the JsonResult here. How can I do that?
GetReferences is returning a JSONREsult which is not a List. why dont you try abscracting this a bit:
private IEnumerable<ReferenceViewModel> getReferences(long CustomerID)
{
var customerReferences = new List<ReferenceViewModel>();
//Code to push data into CustomerReference
return customerReferences;
}
public JsonResult GetReferences(long CustomerID)
{
return Json(customerReference(CustomerID).OrderBy(x=>x.ReferenceName));
}
public JsonResult MassUpdate(List<long> CustomerIDs, List<long> DocumentIDs)
{
var allData = CustomerIDs.SelectMany(x => customerReference(x)).OrderBy(x=>x.ReferenceName);
return Json(alldata);
}
I am making some assumptions here. But i think this is what you are kind of going for. you could also change the return type of getReferences to an IEnumerable and do the sorting in each Action if that is what you are going for. I updated my answer to reflect that change.
I hope that you want something like this.
JsonResult hold data in Data Property. Which is object so you need to cast it back.
List JsonCustomerReference = (List)((JsonResult)GetReferences(CustomerID)).Data;
Is it possible for an action controller to accept a literal object. For example, I have several views in which I would like to post various models from to a single controller that can then determine the incoming model object for further processing.
Model sample:
public class Model1
{
// properties, etc.
}
public class Model2
{
// properties, etc.
}
public class Model3
{
// properties, etc.
}
controller sample:
[HttpPost]
public ActionResult ProcessModel(Object anyModel)
{
// determine the model
if((anyModel as Model1) != null)
{
var model1 = anyModel as Model1;
// continue with code
}
else if((anyModel as Model2) != null)
{
var model2 = anyModel as Model2;
// continue with code
}
// continue with model check, etc.
}
I've tried, but my controller does not appear to be picking up the model as my object parameter remains empty. Is this possible?
Have a quick read about how model binding works... The model binder (which takes whatever is posted to your Action and turns it into the anyModel parameter uses the type of the parameter to determine what to do.
Since the type is Object it can't do anything.
My guess (depending on what you're trying to achieve) is that you can have several Action overloads each with a different type of Model as the parameter which then call common code.
[HttpPost]
public ActionResult ProcessModel(Model1 anyModel){}
[HttpPost]
public ActionResult ProcessModel(Model2 anyModel){}
[HttpPost]
public ActionResult ProcessModel(Model3 anyModel){}
That said it's a bit odd to have one action which takes lots of different models. There's a good chance you're better off doing something else.
Your question might gather a better answer if you say what you're trying to achieve
The Default Asp.NET ModelBinder cannot bind generic Objects this way. You should take a look here to understand how the model will be build back in the server by the DefaultModelBinder: Understanding ASP.NET MVC Model Binding.
Given that your form has many Models, you should encapsulate them into a ViewModel to do this kind of operation.
The ViewModel should looks like this:
public class MyViewModel
{
public Model1 Model1 {get; set;}
public Model1 Model2 {get; set;}
public Model1 Model3 {get; set;}
}
And the controller:
[HttpPost]
public ActionResult ProcessModel(MyViewModel myViewModel)
{
// determine the model
if(myViewModel.Model1 != null)
{
// continue with code
}
else if(myViewModel.Model2 != null)
{
// continue with code
}
// continue with model check, etc.
}
Recently I faced the same issue and resolved it as below:
Step 1: From javascript pass 2 parameter :
First, pass model name as String for identification which model is coming
Second, Pass data from javascript using JSON.stringify(data). where your data can be from Model1, Model2 , Model3 etc.
Step2: In your controller:
[HttpPost]
public ActionResult ProcessModel(string modelName, string anyModel)
{
switch(modelName) {
case "Model1":
var modelValue= JsonDeserialize<Model1>(anyModel);
// do something
break;
case "Model2":
var modelValue= JsonDeserialize<Model2>(anyModel);
// do something
break;
}
}
You Need One method like below:
public T JsonDeserialize<T>(string jsonModel){
return JsonConvert.DeserializeObject<T>(jsonModel, jsonSettings);
}
JsonConvert need namespace "Newtonsoft.Json".
You also need to declare jsonSettings as below
JsonSerializerSettings jsonSettings= new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
DefaultValueHandling = DefaultValueHandling.Ignore
};
This solution is kind of workaround. There is one more solution. you can check that also:
How can I make a Controller Action take a dynamic parameter?
Hope this helps.
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.