The goal
Change the model depending on what it is expecting.
The problem
I have a method in my ProductsControllercalled Category. When someone request this method, two things can happen: if the parameter passed to the method is different than Daily-Offers, then one type of list is passed to View. If the parameter passed to the method is equal to Daily-Offers, then another type of list is passed to View.
To better comprehension, see:
[HttpGet]
public ActionResult Category(string categoryName = null)
{
int? categoryId = categoryName != "Daily-Offers" ?
Convert.ToInt32(Regex.Match(categoryName, #"\d+").Value) :
(int?)null;
if (categoryName == "Daily-Offers")
{
var productsList = Products.BuildOffersList();
ViewBag.Title = String.Format("Today's deal: ({0})", DateTime.Now);
ViewBag.CategoryProductsQuantity = productsList.Count;
ViewBag.CurrentCategory = "Daily-Offers";
return View(productsList);
}
else if (Regex.Match(categoryName, #"\d+").Success &&
String.Format("{0}-{1}",
categoryId,
CommodityHelpers.UppercaseFirst
(CommodityHelpers.GenerateSlug
(Categories.GetDetails((sbyte)categoryId).Category_Name)))
== categoryName)
{
ViewBag.Title = Categories.GetDetails((sbyte)categoryId).Category_Name;
ViewBag.CategoryProductsQuantity =
Categories.GetDetails((sbyte)categoryId).Category_Products_Quantity;
ViewBag.CurrentCategory =
CommodityHelpers.UppercaseFirst(CommodityHelpers.GenerateSlug
(Categories.GetDetails((sbyte)categoryId).Category_Name));
return View(Products.BuildListForHome(categoryId, null));
}
else
{
return View("404");
}
}
As you can see, the view should be prepared to receive IList<Offers> or/and IList<NormalProducts> — and I do not know how to do this.
What I have already tried
Something like this:
#model if(IEnumerable<BluMercados.Models.Data.getProductsListForHome_Result>)
?: (IEnumerable<BluMercados.Models.Data.getProductsInOfferList_Result>);
But, of course, no success. Just to illustrate.
A view should really only have one model, so trying to make a view consume two different models, while doable, shouldn't be done.
Instead, you can create different views. Say you create a new view for DailyOffers, which takes IEnumerable<Offers> as its model.
You can then user an overload of View() to specify which view to return:
return View("DailyOffers", productsList);
However, instead of doing this, would it make more sense to redirect to a different action in the case of DailyOffers? So, you have a new action:
public ActionResult DailyOffers(...)
and instead of return View(productsList) you do:
return RedirectToAction("DailyOffers");
This all assumes that the models are sufficiently different from one another. If they are similar, using the Interface solution as suggested by p.s.w.g. would make more sense.
I don't think you can do that, and even if you could, it wouldn't be good practice. If the view for each model is very similar, I'd recommend creating an common interface for Offers and NormalProducts
public interface IProduct
{
// common properties ...
}
public class Offer : IProduct
{
// ...
}
public class NormalProduct : IProduct
{
// ...
}
And then in your view use
#model IEnumerable<IProduct>
If your view for each model is significantly different, I'd strongly recommend splitting these into different views, and deciding between them in controller:
if (categoryName == "Daily-Offers")
{
...
return View("Category_Offers", productsList);
}
else if (...)
{
...
return View("Category_NormalProducts", Products.BuildListForHome(categoryId, null));
}
And then in your view use
// Category_Offers.cshtml
#model IEnumerable<Offer>
// Category_NormalProducts.cshtml
#model IEnumerable<NormalProduct>
Related
Something strange is happening in my umbraco project where I have a repository set up like so;
public class HireItemsRepo:BaseGenericRepository<YouHireItContext,HireItem>
{
public List<HireItemViewModel> PopulateHireItemViewModel(RenderModel model)
{ List<HireItemViewModel> HireItems = new List<HireItemViewModel>();
foreach (var Hireitem in base.GetAll())
{
HireItems.Add(
new HireItemViewModel(model.Content)
{
Title = Hireitem.Title,
Price = Hireitem.Price
}
);
}
return HireItems;
}
}
which I'm using in my controller like this
public class HiresController : RenderMvcController
{
// GET: Hire
public override ActionResult Index(RenderModel model)
{
HireItemsRepo repo = new HireItemsRepo();
var VM = repo.PopulateHireItemViewModel(model);
return View("Hires",VM.ToList());
}
}
And using that model in the view like this;
#model List<You_Hire_It.Models.HireItemViewModel>
/*HTML starts here*/
It's strange because if I try to use that model as a List, Umbraco will blow up with the following error;
Cannot bind source type System.Collections.Generic.List`1[[You_Hire_It.Models.HireItemViewModel, You_Hire_It, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] to model type Umbraco.Web.Models.RenderModel.
However, if I refactor all the code to use the model on it's own as if I only have one set of values to use, it has no problem with it!
Could anybody point me in the right direction with this please?
Many thanks in advance!
You can inherit from RenderModel as DZL suggests. However, I generally prefer to use route hijacking which would enable me to keep my models simple.
Instead of the Index method in your RenderMvcController, you can create a method with the same name as your view. I note your view is called Hires. So change your controller code to this:
public class HiresController : RenderMvcController
{
// GET: Hire
public ActionResult Hires(RenderModel model)
{
HireItemsRepo repo = new HireItemsRepo();
var VM = repo.PopulateHireItemViewModel(model);
return CurrentTemplate(VM)
}
}
You now need to have your view inherit from UmbracoViewPage. So at the top of your view replace the #model line with the following:
#inherits UmbracoViewPage<List<HireItemViewModel>>
Your model in the view is now of type List<HireItemViewModel> which I think is what you want.
So to iterate the items you would use:
#foreach(var item in Model){
{
// etc
}
Additionally, as this view now inherits from UmbracoViewPage, you have access to the UmbracoContext - just use #Umbraco
For example:
#Umbraco.TypedContentAtRoot().Where(x=>x.DocumentTypeAlias == "HomePage")
or
#Umbraco.AssignedContentItem etc
That is because the model you return from the action need to be of type RenderModel or inherit from it and in your case you are returning a List.
So your model should look something like this:
public class ViewModel : RenderModel
{
public ViewModel(IPublishedContent content) : base(content) { }
public List<HireItem> HireItems { get; set; }
}
public override ActionResult Index(RenderModel model)
{
var vm = new ViewModel(model);
vm.HireItems = new HireItemsRepo().GetHireItems();
return View("Hires", vm);
}
I've got a page in an app I'm building. The page contains a few bits and pieces, then a partial view that loads a different view depending on what's selected from a dropdown. Each of the options from the dropdown has a different view associated with it, and each view has its own fields and model.
Whatever the view is that loads, I'm performing the same action - I'm serializing the model and storing the XML in a database. This is always the case, and there is no unique processing based on the views/models (other than the fact that the fields are different). All models inherit from the same base class for serialization purposes.
I wanted to be able to do something like:
public ActionResult SubmitPartialView<T>(T model)
{
BaseClass baseClassModel = (BaseClass)(object)model;
// serialize and save to database
}
But MVC doesn't allow this - "cannot call action on controller because the action is a generic method".
If I try passing the BaseClass in as a parameter itself, it only contains the properties of the base class and therefore none of the model's properties.
Is there no other option other than to create a separate action for every single view that can submit, and make each one call a separate method that handles the logic?
I see this question is a little old, but if it helps anyone - I was doing some reading with dynamic models and MVC, saw this and it led me to think of a possible solution. Not sure why you would want to have dynamic models. But the great thing with MVC is, you can!
So;
[HttpPost]
public ActionResult SubmitPartial([DynamicModelBinder] dynamic model)
{
// Our model.ToString() serialises it from the baseModel class
var serialisedString = model.ToString();
// do something .. echo it back for demo
return Content(serialisedString);
}
And the model binder is something like this;
public class DynamicModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var currentModel = controllerContext.HttpContext.Request.Form["CurrentModel"];
if (currentModel == "CompanyModel")
{
Type customModel = typeof(CompanyModel);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, customModel);
}
if (currentModel == "UserModel")
{
Type customModel = typeof(UserModel);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, customModel);
}
return base.BindModel(controllerContext, bindingContext);
}
}
hth
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.
In an MVC application, I have some DropDownLists. In my controller I create the IEnumerable<SelectListItem>s and transfer them to my View. Unfortunately, if there is a validation error, I need to recreate those lists, otherwise the view rendering fails.
In the controller action method I'm doing:
var possibilities = _repo.Objects.Select(o=>new SelectListItem(...));
viewmodel.Possibilities = possibilities;
return View(viewmodel);
The view-model has the Possibilities property defined like this:
IEnumerable<SelectListItem> Possibilities { get; set; }
And in my view I access it:
#Html.DropDownListFor(vm=>vm.ThePossibility, vm.Possibilities)
The problem is that when the form post action method is called, the view model passed to it has a null for Possibilities, so when I call:
if(!ModelState.IsValid)
return View(model);
The view doesn't get rendered.
I understand why the propery is null on the post action method, but what's the best way of correcting this? I'd rather not reinitialize those lists.
Thanks.
If you don't want to re-initialize the lists, you will have to cache them somewhere, such as the session or somewhere else.
Frankly, in most cases, it's just simpler to rebuild them. You will have to re-assign them every time.
You should look into using the Post-Redirect-Get pattern; There is a nice pair of attributes described in this blog post that make this very easy to do in ASP.Net MVC.
I usually cache these somewhere or provide a static class for getting common lists. You can then provide access to these in your model with a getter.
For example:
IEnumerable<SelectListItem> _possibilities
IEnumerable<SelectListItem> Possibilities
{
get
{
if (_possibilities == null)
_possibilities = CommonLists.Possibilities();
return possibilities;
}
}
Accessors and JSON (NetwonSoft) are your friends.
In a nutshell
When you set the IEnumerable<SelectListItem> property of your model, serialize it to a public string property.
When your public string property is being set, and the IEnumerable<SelectListItem> is not defined (e.g. null), deserialize the string property to the IEnumerable<SelectListItem> property of your model.
In your view, embed the serialized string so that it is persisted between posts to the server. (Eg. #Html.HiddenFor(...))
Model
public class MyViewModel
{
public IEnumerable<SelectListItem> Selections
{
get
{
return selections;
}
set
{
selections= value;
// serialize SelectListItems to a json string
SerializedSelections = Newtonsoft.Json.JsonConvert.SerializeObject(value);
}
}
IEnumerable<SelectListItem> selections;
public string SerializedSelections
{
get
{
return serializedSelections;
}
set
{
serializedSelections = value;
if(Selections == null)
{
// SelectListItems aren't defined. Deserialize the string to the list
Selections = Newtonsoft.Json.JsonConvert.DeserializeObject<IEnumerable<SelectListItem>>(value);
}
}
}
string serializedSelections;
}
Razor View
<form>
...
#Html.HiddenFor(m => m.SerializedSelections)
...
#Html.DropDownListFor(m => m.SomeProperty, Model.Selections)
</form>
Say I am implementing a view for Food. (ASP.NET MVC2)
Then depending on the type (say fruit or vegetable for example) I will change the view.
Can I do this without creating a seperate view for fruit and vegetable?
I.e. Say we have url structure like http://localhost:xxxx/Food/{foodID}
and don't want
http://localhost:xxxx/Veg/{foodID}
http://localhost:xxxx/Fruit/{foodID}
So I want to be able to change the view depending on the type. I'm using a tabstrip control from telerik, to give you an idea of the difference in the views - it'd just be say - not displaying one particular tab for Veg, and displaying it if fruit, for example.
Can a view accept two different view models ? so when we hit http://localhost:xxxx/Food/{foodID} the code determines what type the object is (Fruit or Vegetable) then sends either FruitViewModel or VegetableViewModel ? If I just send one viewmodel how can I control the logic to display or not display certain things in the view?
If you define FoodViewModel as a base class and have FruitViewModel and VegetableViewModel extend it, you could have ViewPage<FoodViewModel> and pass in either. Then, your view can check for which specific subclass it got and render the appropriate output.
Alternatively, if the only difference between FruitViewModel and VegetableViewModel is that one is Fruit and one is Vegetable (but all other properties are shared, i.e., Name, Calories, Color, Cost), have a FoodType property on the shared FoodViewModel and use it to conditionally render the appropriate output.
What alternative is the best depends on how much the Fruit and Veg Views differ:
Alternative 1:
You can create two different Views (you can pass the view name to the View method):
public ViewResult Food(int id)
{
var food = ...
if (/* determine if food is Veg or Fruit */)
return View("Fruit", new FruitViewModel { ... });
else
return View("Veg", new VegViewModel { ... });
}
By returning a different View the url doesn't change (as it does when using return RedirectToAction("Action", "Controller", ...) which implies a HTTP redirect.
Alternative 2:
You can have a common FoodViewModel extended by both the FruitViewModel and the VegViewModel. Then your FoodView can be of type FoodViewModel and decide what to show in your View code.
If the only thing you need to change is the tabstrip setting. You can provide a property called "ShowTabItem" on your ViewModel, then bind that property to your TabStripItem in your view.
public class FoodViewModel
{
Food _food;
public FoodViewModel(Food food)
{
}
public bool ShowTabItem
{
get
{
return _food.Type == FoodType.Vegetable;
}
}
}
Bind your ShowTabItem property to the Visibility or Enabled property of the tabstrip item. (whatever fits)
then your controller will simply be
public ActionResult Food(int id)
{
Food food = getFood(id);
return View(new FoodViewModel(food));
}
Let your ViewModel decide what needs to be displayed.
Hope this helps!