Model usage within View - asp.net-mvc

I think I'm not clear with the #model that can be part of the view
For example
#model MyModel
Is it the input argument that I can populate and call the view with?
return View("MyView", MyModel);
Is it the output variable I can populate during the post of the view (for next control action)
[HttpPost]
public ActionResult SomePostAction(MyModel myModel) //(and in post action)
Is it both ??

3, it's both!
It is called model binding. A feature of ASP.NET which makes it trivial to bind a mode to a view. Hence the name 'view model', which those models are usually called.
Assigning a model to your view gives you a so-called strongly typed view, which fully exposes the power over the Razor syntax.
The model binder is capable of binding the values of every input field back to the model when posting the form, as long as the name attribute of the form matches the name of the property on the view model. Html helpers such as Html.EditorFor(m => m.SomeProperty) makes this a trivial task.
As Mystere Man mentions, it's also possible to do this without an actual model in your view. For instance, this works:
Html (I omitted the form tag and submit button):
<input type="text" name="SomeString" />
with this method in your controller:
[HttpPost]
public ActionResult SomeAction(string someString)
{
// ...
}

The #model declaration at the top of your view is related to the model object you passed to the View() method in your controller (option 1 in your question). The #model declaration is your way of telling the Razor view engine that the view is strongly typed. That means the C# compiler can double check any properties of your view accesses.
Suppose you had the following class
public class MyModel
{
public string Name { get; set; }
}
Without a strongly typed view you might have something like this in your view
<div>
Hello, #Model.Nmae
</div>
Notice that there's a typo in Name. ASP.Net has no idea what your model is so it has to use a dynamic object. You won't find that error until runtime. If you had delcared #model MyModel you would have an error at build time because MyModel doesn't have a Nmae property.
However, it's not uncommon to use the same model type as a parameter of the action. Imagine your page is an HTML form. In that case the model that your view is strongly typed to and the model that's passed to an MVC action could be the same.

Related

How does Controller get the Model from the View FormMethod.Post?

In my ASP.NET MVC 5.2 application running .NET Framework v4.5.2, my AdminController has an InventoryQueryList method that accepts the model:
[HandleError]
[HttpPost]
public ActionResult InventoryQueryList(CheckInventoryQueryModel model)
{
// ...
}
My view contains the model and calls the InventoryQueryList method on POST:
#model CCNRebuild.Models.CheckInventoryQueryModel
#{
ViewBag.Title = "InventoryQuery";
Layout = "~/Views/Shared/_ConsoleLayout.cshtml";
}
#using (Html.BeginForm("InventoryQueryList", "Admin", FormMethod.Post))
{
#Html.AntiForgeryToken();
<label>
Dealer:#Html.DropDownListFor(m => m.DealerID, Model.Dealerships)
</label>
...
<input type="submit" value="Submit" />
}
But, whenever I click the submit button, I get an error:
MissingMethodException: No parameterless constructor defined for this object.
Why is the view not passing my model parameter to the controller?
The controller has never had a parameterless constructor in the 3 months that I have been working here.
NOTE: I tried to only show relevant code, and I left out anything unnecessary. If there is anything missing that needs to be seen, please comment and I'll try to oblige.
The error is telling you that CheckInventoryQueryModel doesn't have a parameterless constructor, and it needs one. So you would either:
Remove whatever non-parameterless constructor(s) it does have (and update related code accordingly), or
Add a parameterless constructor.
The model binder needs a parameterless constructor in order to construct an instance of the model. (Unless you write a custom model binder for this type. Which probably isn't the road you want to take, but is an option.) This is a fairly common pattern in frameworks that automate model instance creation. Entity Framework, for example.
As for the actual questions being asked...
How does Controller get the Model from the View FormMethod.Post?
and
Why is the view not passing my model parameter to the controller?
It just sounds like you were misinterpreting the error. I see no reason to suggest that the page isn't passing the form value(s) to the server. Though you can always confirm this in your browser's debugging tools by observing the HTTP request being made when posting the form.
The request sends the data to the server, and can do so in a variety of ways. Form posts are generally key/value pairs. The ASP.NET MVC framework's model binder uses those key/value pairs to populate properties on the model. It just couldn't do that in this case because it couldn't create an instance of the model to be populated.

Using ViewModel with a PartialView in ASP.NET MVC

I'm attempting to use a PartialView with a ViewModel but I am getting the error
The model item passed into the dictionary is of type 'Regression', but this dictionary requires a model item of type 'RegressionVM'.
Controller:
public ActionResult _Regression(Regression regression)
{
var model = new ViewModels.RegressionVM(regression);
return PartialView(model);
}
Partial View
#model ViewModels.RegressionVM
<div>
<p>Correlation Coefficient : #Model.Regression.CorrelationCoefficient</p>
</div>
Main View (relevant part)
#Html.Partial("_Regression", SectorAnalysis.evReg)
I've checked that the object passed to the partial controller is not null and is of the correct type.
If in the controller I simply take in a type Regression and pass it to the PartialView that works fine but I get errors whenever I use a view model pattern.
Interestingly if I omit the viewmodel from the partial controller as below the error goes away (obviously I change the partial view to accept #model Regression) :
public ActionResult _Regression(Regression regression)
{
return PartialView(regression);
}
I'm using ASP.NET MVC 4
You need to change this
#Html.Partial("_Regression", SectorAnalysis.evReg)
to this
#Html.Action("_Regression", "ControllerName", SectorAnalysis.evReg)
Rationale:
Html.Partial does not call the controller action, it simply attempts to render the partial view with the model that you sent it. In your case, you are sending a model of type Regression to a partial view that is expecting a model type of ViewModels.RegressionVM. By calling Html.Action(), you are instructing the razor view engine to execute the action in your controller that takes a Regression type object and returns a ViewModels.RegressionVM to the partial view.

How does #Html.BeginForm() works?

I'm very new to ASP.NET, just started the MVC tutorial today on asp.net. I got here http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/examining-the-edit-methods-and-edit-view
So far so good, the problem:
In my View I have the following code
(Model is set to the view with #model MyFirstMVC4.Models.Movie)
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
//... bla bla html input
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
My MovieController
// Shows the view
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
//
// POST: /Movie/Edit/5
[HttpPost] // Handles the view above
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
And here is the question - How the heck does it pass the Movie object to the POST method above?! When I observe the client side there is
<form action = "/Movie/Edit/1" ... />
Here I don't understand why action = url of the very same view page?!1
Also on the server side there is just Html.BeginForm() :(
How does it realize to what action method to post and what route parameters to pass?
It works, I just don't know why
The version of BeginForm in the code,
with no parameters, sends an HTTP POST to the current URL, so if the view is a response to
/Movie/Edit/5, the opening form tag will look like the following:
< form action="/Movie/Edit/5" method="post">
The BeginForm HTML helper asks the routing engine how to reach the Edit action of the
MovieController. Behind the scenes it uses the method named GetVirtualPath on the Routes
property exposed by RouteTable — that’s where your web application registered all its routes in
global.asax. If you did all this without an HTML helper, you’d have to write all the following
code:
#{
var context = this.ViewContext.RequestContext;
var values = new RouteValueDictionary{
{ "controller", "movie" }, { "action", "edit" }
};
var path = RouteTable.Routes.GetVirtualPath(context, values);
}
<form action="#path.VirtualPath" method="get">
...
</form>
You asked how is movie object is passed. That is called model binding.
When you have an action with a parameter, the MVC runtime uses a model binder to build the
parameter. You can have multiple model binders registered in the MVC runtime for different types
of models, but the workhorse by default will be the DefaultModelBinder.
In the case of an Movie
object, the default model binder inspects the Movie and finds all the movie properties available
for binding. Following the naming convention you examined earlier, the default model binder can automatically convert and move values from the request into an movie object (the model binder can
also create an instance of the object to populate).
In other words, when the model binder sees an Movie has a Title property, it looks for a value
named “Title” in the request. Notice the model binder looks “in the request” and not “in the form
collection.” The model binder uses components known as value providers to search for values in
different areas of a request.
The model binder can look at route data, the query string, and the form
collection, and you can add custom value providers if you so desire.
When you call BeginForm() without any parameters it default to using the same controller/action used to render the current page. It assumes you'll have an action with the correct name on your controller that will accept postbacks (which you do). It uses the RouteValues to do this.
It automatically binds each input control (by name) to the parameters of the action accepting the postback - or in your case, the properties of the object parameter for the action accepting the postback.
[HttpPost] attribute is given to the action that you want to be called on the POST submit of the form.
to understand how #using (Html.BeginForm()) works , you need to know what page it is already on . using #using (Html.BeginForm()) in 2 different views will come back to two different controllers
We can create forms by typing simple html or by html helpers.
One of them Html.BeginForm(); it is a little bit odd because you actually can wrap it in a using statement because this particular helper returns an object that implements IDisposable in C#. First it writes out with opening tag. And at the bottom when the generated code calls dispose on that object, that’s when it will write out closing form tag . So BeginForm gives me an object that will write out my opening form tag and my closing from tag. After that you don't worry about anything you can just focus on labels and inputs

ASP.NET MVC Binding to View Model as well as routed values

I know similar questions have been asked regarding complex model binding in ASP.NET MVC, but I am having a problem binding because of a lack of a sufficient prefix coming back on the POST and wondered if there were an easy solution.
I have a view Model that looks something like this:
public class ViewModel<Survey, Contact>
{
public Survey Model { get; set; }
public Contact Model2 { get; set; }
}
I then have an action method like this that accepts the POSTed
public ActionResult Survey(
string id, string id2, SurveyViewModel<Survey, Contact> model)
{
// code goes here...
}
In my form, the first two id's are from the URL route and I then have form code (using #Html.EditorFor(x => x.Model.SurveyName) or similar), generated with names like this:
<input class="text-box single-line" id="Model_Email"
name="Model.Email" type="text" value="" />
A post works if I change the name from Model.Email to model.Model.Email, but I am trying to avoid having to create a custom model binder.
Is there
A setting I can make in the view to change the name for all fields rendered in a view using the #Html.EditorFor typed view helpers?
Something I can change using the Bind attribute on the action that would allow it to default binding to that object?
The answer may be "build a custom binder", but I just wanted to pose the question before biting that off.
Thanks for the help. Best Regards,
Hal
You can pass custom viewdata with custom HtmlFieldPrefix to view. Every control rendered with helper will have that prefix.
ViewData.TemplateInfo.HtmlFieldPrefix = "prefix here";
Take a look at this: Forcing EditorFor to prefix input items on view with Class Name?

MVC2 Post using sub-classes

I'm building one page to edit various product types. Each product type has a view model (TentProductVM, BootProductVM) that inherits from ProductVM. My MVC2 View checks the model type and adds fields as appropriate. For example, if the model is of type BootProductVM I call Html.TextBoxFor to add a field for the boot's foot size. Page displays fine.
The problem is the post. I've declared a function (in VB) as follows:
<HttpPost()>Function Edit(byval prod as ProductVM) As ActionResult
Of course, this function only receives the form data from the base class ProductVM. So instead I added a function for each product type:
<HttpPost()>Function EditTent(byval prod as TentProductVM) As ActionResult
<HttpPost()>Function EditBoot(byval prod as BootProductVM) As ActionResult
and point the View to the appropriate post function:
Using Html.BeginForm("Edit" & Model.ObjectTypeName, "Catalog")
However, when EditTent or EditBoot gets called, the prod parameter only contains data from the base class. All the fields declared in the subclass view models are left at default values.
What am I doing wrong? Or is there a better approach? (The obvious solution is multiple pages but, since ProductVM has many fields relative to the subclasses, I'd rather not.)
After much experimentation, I've decided not to use this approach. First, I couldn't get it to work without resorting to having an Action parameter of type FormCollection. Second, the obvious solution I discarded is appealing if I use a partial view. The partial view has all the fields associated with the base class (ProductVM), leaving only the fields associated with the derived classes (TentProductVM, BootProductVM) in the regular views.
Felt like I was fighting against the MVC auto-magic, which is never the right approach.
The thing to remember about MVC is that it's based on the "Convention over Configuration" mindset. So if you're passing a strongly typed class class instance to your action method, it expects it to be named "model".
Try changing your declarations to look like this:
<HttpPost()> Function EditTent(byval model as TentProductVM) As ActionResult
<HttpPost()> Function EditBoot(byval model as BootProductVM) As ActionResult
The other (less ideal) option would be to expect a FormCollection object in your action method.
<HttpPost()> Function EditTent(byval form as FormCollection) as ActionResult
Update
Just updating to include some of the discussion points below... In order to post a strongly typed object to a controller action method, the types need to match up.
Assuming your controller's action method looks like this:
<HttpPost()> Function EditTent(byval model as ProductVM) As ActionResult
Your view should be typed accordingly:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Your.Namespace.ProductVM>" %>

Resources