Why does ASP.NET MVC assumes that view will have matching input and output types? - asp.net-mvc-4

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

Related

ASP.NET Core 6 Write value to Page without iteration?

I am trying to write a specific value to a page in ASP.NET Core 6. I found multiple solutions with iterators but I am not able to write a single value from non-iteratable models / instances (no enumerators & lists) to a page.
public class UserViewModel
{
public string Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
This models purpose is to get filled with values from the Identity Framework.
public UserViewModel umodel { get; set; }
Said model gets instanced, filled without any problems - it holds values (checked via console) in page.cshtml.cs:
var user = await _userManager.FindByIdAsync(id);
UserViewModel UserModel = new UserViewModel();
UserModel.UserName = user.UserName;
UserModel.Email = user.Email;
UserModel.Id = user.Id;
Console.WriteLine(UserModel.UserName);
Console.WriteLine(UserModel.Id);
Console.WriteLine(UserModel.Email);
If I try to access it on the corresponding page (page.cshtml) I can only access its name without any problems:
#Html.DisplayNameFor(model => model.umodel.Email)
When I want to access its content there is no value on the page.
#Html.DisplayFor(model => model.umodel.Email)
How can I access the values in this model on a razor page? All the solutions I found based on some kind of iterator and therefore models that had some kind of enumerator or where instanced and filled as a list.
From the code you posted, you aren't populating the page's UserViewModel property. You instantiated a different UserViewModel instance. You wrote the values of that to the Console, but the actual model property (umodel) has not been populated.
Try this in the OnGet method:
var user = await _userManager.FindByIdAsync(id);
umodel.UserName = user.UserName;
umodel.Email = user.Email;
umodel.Id = user.Id;
When rendering property values, you don't need the DisplayFor helper (unless you are using display templates). You just need to prefix the property with #:
#Model.umodel.UserName
I figured it out. There hast to be an instance of the model / class but in a specific way and naming. See following example:
Model:
public class IdentityUserModel
{
public string Id { get; set; }
public string ?UserName { get; set; }
public string ?Email { get; set; }
}
Reference to model in the Main class of the Page (page.cshtml.cs):
public IdentityUserModel IUserModel { get; set; }
Then the important part in the OnGet/OnGetAsync function(?) (page.cshtml.cs):
IUserModel = new(); // <-- instance
var user = [whatever...]
IUserModel.UserName = user.UserName;
IUserModel.Email = user.Email;
IUserModel.Id = user.Id;
Then to write on the page.cshtml:
#Model.IUserModel.Id
My understanding is that there has to be an instance of the class in die page context with exactly the same name (therefore = new() without instance name). I may have been blind but reading throug microsofts learn pages again this is was not clear at all to me.
Thanks to Mike Brind for sending me in the right direction with his input.

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

Pass Url Parameters to Action by Model in ASP.NET MVC 4

I want to assign my url parameters to Model properties, passed as a parameter to the associated Action. For example;
Say, my url is http://www.example.com/Item/Index?color=red&size=50
My action inside the controller is like below:
public class ItemController : Controller
{
public ActionResult Index(MyModel myModel)
{
//
return View(myModel);
}
}
I want to configure the model or whatever necessary so that my model takes the color and size as field values. The following didn't work:
public class MyModel
{
[Display(Name = "color")]
public string Color{ get; set; }
[Display(Name = "size")]
public string Size{ get; set; }
}
What would be the correct way to solve the problem?
Thanks for any suggestion.
Update
Well, yes! The code above would work correctly, because Url parameter names are the same as model property names. I should explain my problem exactly as I encounter for the next time, sorry.
I must correct a part of my question to make it clear. The url should have been: http://www.example.com/Item/Index?c=red&s=50 to detect the problem.
If the url is like that, the code would not work. Because Url parameters don't have the same name as Model properties.
Updated model is below:
public class MyModel
{
[Display(Name = "c")]
public string Color{ get; set; }
[Display(Name = "s")]
public string Size{ get; set; }
}
Try adding [FromUri] in front of the parameter.
public class ItemController : Controller
{
public ActionResult Index([FromUri] MyModel myModel)
{
// do something
return View();
}
}
debugging the issue
Here are some suggestions in debugging the issue, as it should work out of the box.
try binding to primitive types
public class ItemController : Controller
{
public ActionResult Index(string color, string size)
{
// do something
return View();
}
}
Try reading out of the request object directly
var size = this.Request["size"];
If either of those work there is an issue with your model binding.
Update
If you want to have the query string parameters different to the model in MVC you'll need to have a custom model binder. Take a look at Asp.Net MVC 2 - Bind a model's property to a different named value and http://ole.michelsen.dk/blog/bind-a-model-property-to-a-different-named-query-string-field.html which extends the answer a little.
https://github.com/yusufuzun/so-view-model-bind-20869735 has an example with some html helpers that could be useful.

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.

NHibernate, AutoMapper and ASP.NET MVC

I'm wondering about a "best practice" using NHibernate, AutoMapper and ASP.NET MVC. Currently, i'm using :
class Entity
{
public int Id { get; set; }
public string Label { get; set; }
}
class Model
{
public int Id { get; set; }
public string Label { get; set; }
}
Entity and model are mapped like this :
Mapper.CreateMap<Entity,Model>();
Mapper.CreateMap<Model,Entity>()
.ConstructUsing( m => m.Id == 0 ? new Entity() : Repository.Get( m.Id ) );
And in the controller :
public ActionResult Update( Model mdl )
{
// IMappingEngine is injected into the controller
var entity = this.mappingEngine.Map<Model,Entity>( mdl );
Repository.Save( entity );
return View(mdl);
}
Is this correct, or can it be improved ?
that's how I was doing in a project:
public interface IBuilder<TEntity, TInput>
{
TInput BuildInput(TEntity entity);
TEntity BuildEntity(TInput input);
TInput RebuildInput(TInput input);
}
implement this interface for each entity or/and for some group of entities you could do a generic one and use it in each controller; use IoC;
you put your mapping code in the first 2 methods (doesn't matter the mapping technology, you could even do it by hand)
and the RebuildInput is for when you get the ModelState.IsValid == false, just call BuildEntity and BuildInput again.
and the usage in the controller:
public ActionResult Create()
{
return View(builder.BuildInput(new TEntity()));
}
[HttpPost]
public ActionResult Create(TInput o)
{
if (!ModelState.IsValid)
return View(builder.RebuildInput(o));
repo.Insert(builder.BuilEntity(o));
return RedirectToAction("index");
}
I actually do sometimes generic controller that is used for more entities
like here: asp.net mvc generic controller
EDIT:
you can see this technique in an asp.net mvc sample application here:
http://prodinner.codeplex.com
I would inject the IMappingEngine into the controller, instead of the using the static Mapper class. You then get all the benefits of being able to mock this in your tests.
Take a look at this link by AutoMapper's creator,
http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/05/11/automapper-and-ioc.aspx