If MVC only allows you to have one ViewModel per View, how does one incorporate a dropdownlist (need to have a separate ViewModel for this) into an existing View which is already used by another ViewModel (ie an entity which has a column for this dropdownlist)?
This Question in addition, I guess, Got everything you are looking for:
How to write a simple Html.DropDownListFor()?
As a beginner, I did a very basic implementation of dropDownlist using the NorthWind Database only.
I had imported the Product & Suppliers table from Northwind database.
In the ProductController.cs file, which is the controller file for my Product table, add method: GetAllSuppliers to get all SuppliersID which we will display in a dropdown.
public IEnumerable<int> GetAllSuppliers()
{
NorthwindEntities db = new NorthwindEntities();
return db.Suppliers.Select(e => e.SupplierID);
}
Now, in the Create action method in ProductController.cs, pass all the values of SupplierID in ViewData as seen below:
public ActionResult Create()
{
ViewData["Suppliers"] = new SelectList(GetAllSuppliers());
return View(new Product());
}
In your corresponding Create.aspx View, use this:
<%: Html.DropDownListFor(model => model.SupplierID, ViewData["Suppliers"] as SelectList) %>
Below is a snapshot of the Result:
Let me know if you need any explanation.
You can make a property inside your main ViewModel which contains ViewModel for dropdownlist and use it with dropdown.
Assume you have controller.
public class HomeController
{
public ActionResult Index()
{
var viewModel = new MainViewModel
{
SomeProperty = "SomeValue",
DropDownData = new DropDownDataViewModel() // Initialize it with appropriate data here.
};
return this.View(viewModel);
}
}
And MainViewModel
public class MainViewModel
{
public string SomeProperty {get; set;}
public DropDownDataViewModel DropDownData { get; set; }
}
So, inside your view you can call #Model.DropDownData to get access to this viewmmodel.
Related
One of the most popular books on ASP.NET Core is "Pro ASP.NET Core 3" by Adam Freeman.
In chapters 7-11, he builds an example application, SportsStore.
The Index method of the HomeController shows a list of products:
Here's the Index method:
public ViewResult Index(int productPage = 1)
=> View(new ProductsListViewModel {
Products = repository.Products
.OrderBy(p => p.ProductID)
.Skip((productPage - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo {
CurrentPage = productPage,
ItemsPerPage = PageSize,
TotalItems = repository.Products.Count()
}
});
The view file that corresponds to this method is Views\Home\Index.cshtml. This file has the following line at the top:
#model ProductsListViewModel
So the view is expecting an object of type ProductsListViewModel. However, in Visual Studio, IntelliSense shows View as expecting an argument of type object:
I'm surprised that View here isn't shown to expect an object of type ProductsListViewModel. Since it's setup to accept an argument of type object, we can actually pass in some nonsensical value:
public ViewResult Index(string category, int productPage = 1) =>
View(10);
and the project will still compile!
Is there a way to set things up so that View actually only accepts the model type specified in the view file? I.e. in this case, set things up so that View only accepts ProductsListViewModel?
Thanks!
By using generic, I did the following.
//This is the Base class for every model must inherit.
public class EntityModel
{
public int ID { get; set; }
}
//Model / view model must inherit from EntityModel
public class EmployeeModel : EntityModel
{
public string Name { get; set; }
}
Create a new ViewController which accepts only the EntityModel or its derived class.
Mark the View and its overloaded method obsolete so that only the entity Model calls as a parameter can only be used. Also if anyone uses it show restrict them for using it. Therefore throw error.
public class ViewController<T> : Controller where T: EntityModel
{
[Obsolete]
public override ViewResult View()
{
return base.View();
}
[Obsolete]
public override ViewResult View(object model)
{
throw new Exception("Use view method which accepts EntityModel");
}
[Obsolete]
public override ViewResult View(string viewName)
{
throw new Exception("Use view method which accepts EntityModel");
}
[Obsolete]
public override ViewResult View(string viewName, object model)
{
throw new Exception("Use view method which accepts EntityModel");
}
public new ViewResult View(T model)
{
return base.View(model);
}
}
Use the newly created ViewController in your Home Controller.
public class HomeController : ViewController<EmployeeModel>
{
public IActionResult Index()
{
EmployeeModel emp = new EmployeeModel();
emp.ID = 1;
emp.Name = "Satish Pai";
return View(emp);
}
}
I don't think there is a way to catch any wrong view models got passed into views at compile time!
The problem
The view is strongly typed with the view model you declare on the top so it knows what model is coming in, but the controller doesn't know which view you want it to return to...
By default, yes the controller is going to return to a view that has the same name as the method, but you can change that default, and you can even pass the name of the view as string parameter to one of the View() overloads:
public IActionResult Index(string category, int page = 1)
{
...
return View("OutOfStock", vm);
}
Now Visual Studio doesn't know which view model you want the controller to build and pass to the view. In fact, Visual Studio doesn't even know what view I want to return. Even after I put "OutOfStock" as the view name, Visual Studio doesn't know whether the view even exists or not...
Using Generic
#Satish's solution is indeed interesting but it assumes you are only working with 1 single view model for a single controller, which normally isn't the case. Usually you will have different view models for different actions.
If Generic were the way to go, I would suggest to put it on the action, rather on the controller:
public abstract class BaseController : Controller
{
public ViewResult View<T>(T viewModel) where T : new()
{
return View(viewModel);
}
}
Then you can use it like this in the controller:
public class ProductController : BaseController
{
public IActionResult Index(string category, int page = 1)
{
var vm = new ProductListViewModel
{
...
};
return View<ProductListViewModel>(vm);
// This would give you the compile time error!
// return View<ProductListViewModel>(10);
}
}
But why? What's the point of doing this? You, as the developer, have to know ProductListViewModel is the right view model to pass anyway. Putting something like this in place would be only helpful if there is a junior or new hire who's working on your code and doesn't bother to check the view model the returned view is asking for?
Maybe?
Now I know a tool like Resharper might be able to help and catch the mismatch at compile time.
Also writing unit tests on what the methods in the controller return might be helpful?
I have a view-model like this:
public class U1MyProfile1ViewModel : U1Profile
{
public List<SelectListItem> CountryList { get; set; }
}
Thinking that I want the model accessible to the view, plus a some extra fields that aren't really part of the model, such as a drop down list of countries.
Then in the controller I try to "pass the model over to the view-model"
var myProfile = await _mainDbContext.U1Profiles
.AsNoTracking()
.FirstOrDefaultAsync(i => i.SiteUserId == mySiteUserId);
U1MyProfile1ViewModel myProfileViewModel = (U1MyProfile1ViewModel)myProfile;
this compiles, but I get a runtime error of:
InvalidCastException: Unable to cast object of type 'WebApp.Models.U1Profile' to type 'WebApp.ViewModels.U1MyProfile1ViewModel'.
Any ideas on how to do this easily?
Something simpler than assigning the model to the view-model field by field.
Set your View model like follow:
View modal
public class U1MyProfile1ViewModel
{
public List<SelectListItem> CountryList { get; set; }
public U1Profile U1Profile{get;set;}
public string othervariable{get;set;}
}
Controller
var myProfile = await _mainDbContext.U1Profiles
.AsNoTracking()
.FirstOrDefaultAsync(i => i.SiteUserId == mySiteUserId);
U1MyProfile1ViewModel myProfileViewModel = new U1MyProfile1ViewModel;
U1MyProfile1ViewModel.U1Profile=myProfile;
U1MyProfile1ViewModel.CountryList=yourcountrylist;
And finally just passed your viewmodal to View and you get your result.
For better understanding just see below link:
Link1
Link2
I was watching a tutorial about HtmlHelper for DropDown https://www.youtube.com/watch?v=79aYSOcmpV8
Around min 8, he is reading the db to replace some hardcode values.
To pass list of Departments from the controller, store them in "ViewBag"
public ActionResult Index()
{
// Connect to the database
SampleDBContext db = new SampleDBContext();
// Retrieve departments, and build SelectList
ViewBag.Departments = new SelectList(db.Departments, "Id", "Name");
return View();
}
Last step.
Now in the "Index" view, access Departments list from "ViewBag"
#Html.DropDownList("Departments", "Select Department")
I dont see anything like strong type model on the view.
So how the Helper know Departments refers to a value in the ViewBag?
When you add a value to ViewBag, it is also added to the ViewData property of ViewContext when the view is generated. The DropDownList() overload that your using in equivalent to passing a null SelectList in
#Html.DropDownList("Departments", null, "Select Department")
in which case, internally, the helper searches the ViewData property to find a matching key which is an IEnumerable<SelectListItem> (which "Departments" is). You can see the relevant code in the private static MvcHtmlString SelectInternal() method of the source code
// If we got a null selectList, try to use ViewData to get the list of items.
if (selectList == null)
{
selectList = htmlHelper.GetSelectData(name);
....
}
Note that the example in the tutorial is a poor approach, using 'magic' strings and requiring you to access the value in the POST method using Request.Form["Departments"]. A far better approach is to use a view model and strongly bind to your view model, for example
public class MyViewModel
{
public int SelectedDepartment { get; set; }
public IEnumerable<SelectListItem> DepartmentList { get; set; }
...
}
and the GET method would be
public ActionResult Create()
{
MyViewModel model = new MyViewModel
{
DepartmentList = new SelectList(db.Departments, "Id", "Name");
};
return View(model);
}
and in the view
#model MyViewModel
....
#Html.DropDownListFor(m => m.SelectedDepartment, Model.DepartmentList, "Select Department")
and post the form back to
[HttpPost]
public ActionResult Create(MyViewModel model)
I want to know,What is a best way to create dropdownlists in MVC 4?
With ViewBag or another approach?
I would argue that since the items are variable values within your view that they belong in the View Model. The View Model is not necessarily just for items coming back out of the View.
Model:
public class SomethingModel
{
public IEnumerable<SelectListItem> DropDownItems { get; set; }
public String MySelection { get; set; }
public SomethingModel()
{
DropDownItems = new List<SelectListItem>();
}
}
Controller:
public ActionResult DoSomething()
{
var model = new SomethingModel();
model.DropDownItems.Add(new SelectListItem { Text = "MyText", Value = "1" });
return View(model)
}
View:
#Html.DropDownListFor(m => m.MySelection, Model.DropDownItems)
Populate this in the controller or wherever else is appropriate for the scenario.
Alternatively, for more flexibility, switch public IEnumerable<SelectListItem> for public IEnumerable<MyCustomClass> and then do:
#Html.DropDownFor(m => m.MySelection,
new SelectList(Model.DropDownItems, "KeyProperty", "ValueProperty")
In this case, you will also, of course, have to modify your controller action to populate model.DropDownItems with instances of MyCustomClass instead.
ASP.NET MVC (or rather Html.Helpers and base page implementation) assumes that there will be one type for both rendering and posting (namely Model).
This is a violation of ISP, isn't it?
I am tempted to derive my Edit views (those that have different render-data, and post-data) from a custom EditPageBaseView<TViewModel, TFormData>.
The problem is I want my validation and post work against FormData instance (stored inside ViewModel), but MVC assumes that entire ViewModel will be POSTed back.
Is there an OOB way to facilitate that? (I didn't find one if there is).
Is it a bad idea (in concept) to have separate data types for different operations exposed by a service (a view in this case).
I tend to follow the CQRS model when constructing my view models. All rendering is done with ViewModel classes and all posting back is done with Command classes. Here's a contrived example. Let's say we have a View with a small form for creating users.
The ViewModel and Command classes looks like this:
public abstract class ViewModel {}
public abstract class Command: ViewModel
public class CreateUserViewModel : ViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public string PasswordConfirm { get; set; }
}
public class CreateUserCommand : Command
{
public string Username { get; set; }
public string Password { get; set; }
public string PasswordConfirm { get; set; }
}
The UserController creates a CreateUserViewModel as the model for the Get request and expects a CreateUserCommand for the Post request:
public ActionResult CreateUser()
{
// this should be created by a factory of some sort that is injected in
var model = new CreateUserViewModel();
return View(model);
}
[HttpPost]
public ActionResult CreateUser(CreateUserCommand command)
{
// validate and then save the user, create new CreateUserViewModel and re-display the view if validation fails
}
Model binding takes care of ensuring that the properties of the Posted CreateUserCommand are populated properly, even though the Get View is bound to a CreateUserViewModel.
They don't have to match, but they do match by default.
If you don't want them to match, you can specify a different model in your Form or ActionLink:
Example of a Mismatch using Razor and C#:
Index.chtml:
#model FirstModel
<div>
#using (Html.BeginForm("Action", "ControllerName", new { ParameterName = new SecondModel { First = "First", Second = "Second" } }, FormMethod.Post)) {
<input type="submit" value="Submit Button" />
}
</div>
The Controller:
public class ControllerName : Controller {
public ActionResult Index() {
return View(new FirstModel());
}
public ActionResult Action(SecondModel ParameterName) {
return View() // Where to now?
}