More specific parameter type in the `View` method - asp.net-core

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?

Related

using a class method in MVC4

I just start do work on MVC4 Asp.Net
I have this class in my models
namespace PhoneBook.Models
{
public class User
{
public string Username { get; set; }
public string Password { get; set; }
public static String writeasd(){
return "asd";}
}
I have this method in my controller:
public ActionResult Main()
{
ViewBag.Username = Request.Form["username"];
ViewBag.Password = Request.Form["password"];
var user = new User()
return View(user);
}
However when I tried to call this method from my view like this:
#User.writeasd()
It gives error. What is the problem? Can you help me?
Note : I have #using PhoneBook.Models in the beginning of my view
When using a strongly typed view as you are there, you need two things.
One is a model directive
#model PhoneBook.Models.User
Then you can reference your model using the Model property of the view page.
So in your instance, you would use
#Model.writeasd()
HTH

Asp .Net MVC on action executing - Get the value of action parameter values of user defined types on action executing

I want to log the each action method parameter name and its
corresponding values in the database as key value pair. As part of
this, I am using OnActionExecuting ActionFilterAttribute, since it
will be the right place (OnActionExecuting method will get invoke for
all controller action methods call) to get Action Executing context.
I am getting the value for .Net types (string, int, bool). But I am
unable to get the value of the User defined types (custom types).
(ex: Login model). My model might have some other nested user
defined types as well.
I was trying to get the values of the user defined types but I am
getting the only class name as string. I hope we can do in
reflection.
Could you please anyone assist to resolve the issue. since I am new
to reflection. It will helpful to me. Thanks in Advance.
I need to get the name and value of these types in OnActionExecuting.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ActionParameter = new SerializableDictionary<string,string>();
if(filterContext.ActionParameter != null)
{
foreach(var paramter in filterContext.ActionParameter)
{
//able to get returnUrl value
//unable to get model values
ActionParameter.Add(paramter.Key, paramter.Value);
}
}
}
public ActionResult Login(LoginModel model, string returnUrl)
{
return View(model);
}
User defined type
public class LoginModel
{
public string UserName {get;set;}
public string Password {get;set;}
//User defined type
public UserRequestBase Request {get;set;}
}
//User defined type
public class UserRequestBase
{
public string ApplicationName {get;set;}
}
I am able to get the value of the returnUrl (login method param) in OnActionExecuting but not for model (login method param). I am able to see the values, but don't know how to access it, I used typeof even though I am unable to get it, but I need generic because i have 20 methods in controller so I could not only for LoginModel.
This answer isn't exactly what you want - based on your question - but I think it will work better for what want to accomplish. Quick aside...
Playing around with reflection and nested classes in this instance, lead to some SO (a propos?) errors for me...
So, a better path, maybe? Rather than trying to get/cast the property names, values (types?) from 'context.ActionParameters,` I found it was much easier to let a Json serialization do the work for me. You can then persist the Json object, then deserialize... pretty easy.
Anyway, here's the code:
using Newtonsoft.Json; // <-- or some other serialization entity
//...
public class LogActions : ActionFilterAttribute, IActionFilter
{
// Using the example -- LoginModel, UserRequestBase objects and Login controller...
void IActionFilter.OnActionExecuting(ActionExecutingContext context)
{
var param = (Dictionary<String, Object>)context.ActionParameters;
foreach (var item in param.Values)
{
string itemName = item.GetType().Name.ToString();
string itemToJson = JsonConvert.SerializeObject(item);
// Save JsonObject along with whatever other values you need (route, etc)
}
}
}
Then when you retrieve the Json object from the database you just have to deserialize / cast it.
LoginModel model = (LoginModel)JsonConvert.DeserializeObject(itemToJson, typeof(LoginModel));
From example:
public class LoginModel
{
public string UserName {get;set;}
public string Password {get;set;}
//User defined type
public UserRequestBase Request {get;set;}
}
//User defined type
public class UserRequestBase
{
public string ApplicationName {get;set;}
}
Controller used in example:
public ActionResult Login(LoginModel model, string returnUrl)
{
return View(model);
}
Hope this helps. If there are further issues with this please let me know and I will try to help.

MVC Get Method's Parameters Names

I'm working on MVC 4 Project. I have HomeController, and 3 Actions on it.
And I want to list parameters when I send controller and actionname.
When I figure my problem,
public class HomeController : Controller
{
public ActionResult GetItem(string barcode, string param2)
{
}
...
}
And I want to write this method.
public List<string> GetParametersName(string controller, string action)
{
//return - Method's Parameter(s) - **I DONT KNOW HOW CAN I WRITE THIS CODE**
}
When I call the method
Class.GetParametersName("Home","GetItem");
It must me return, "barcode, param2".
How can i write GetParametersName? Thanks.

Adding a dropdownlist in MVC

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.

Why does ASP.NET MVC assumes that view will have matching input and output types?

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?
}