Pro Asp.Net MVC 4 (pg 535):
The string argument is used to search the view data, ViewBag and view
model to find a corresponding data item that can be used as the basic
for the input element. So, for example, if you call
#Html.TextBox("DataValue"), the MVC Framework tries to find some
item of data that corresponds with the key DataValue. The following
locations are checked: • ViewBag.DataValue • #Model.DataValue
Book states that an overload of a Html.TextBox which takes a single string argument checks both ViewData and view model to obtain the value for display. But after experimenting a little, it seems it only checks ViewData for a value, but not also a view model.
Thus, in the output generated from Html.TextBox the value attribute is set to an empty string:
public class Person
{
public string FirstName = "Leila";
}
*.cshtml:
#model HelperMethods.Models.Person
...
#Html.TextBox("FirstName")
Output:
...
<input id="FirstName" name="FirstName" type="text" value="" />
So is the book wrong and TextBox(HtmlHelper, String) checks only ViewData or does it also check a view model?
UPDATE:
public class Person
{
public string FirstName = "Leila";
}
public class HomeController : Controller
{
public ActionResult Index()
{
return View((object)new Person());
}
}
Index.cshtml:
#model Mvc4.Controllers.Person
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
#Html.TextBox("FirstName")
</body>
</html>
Much appreciated
Just did a quick test and it seems that it has to be a property and not a field.
So this should work:
public class Person
{
public string FirstName { get; set; }
}
Related
I am brand spanking new to ASP.NET MVC and I'm working through a tutorial to get the basics. The tutorial is in C# and I'm converting it to VB to stay consistent with some related projects. I have to assume that I'm missing something in that conversion but can't figure out what it could be.
I have the following class:
Public Class GuestResponse
Public Name As String
Public Email As String
Public Phone As String
Public WillAttend As Boolean?
End Class
and a strongly-typed view related to that class:
#ModelType PartyInvites.GuestResponse
#Code
Layout = Nothing
End Code
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
</head>
<body>
#Using Html.BeginForm
#<p>Your name: #Html.TextBoxFor(Of String)(Function(x) x.Name)</p>
#<p>Your email: #Html.TextBoxFor(Of String)(Function(x) x.Email)</p>
#<p>Your phone: #Html.TextBoxFor(Of String)(Function(x) x.Phone)</p>
#<p>
Will you attend?
#Html.DropDownListFor(Of Boolean?)(Function(x) x.WillAttend,
New SelectListItem() {New SelectListItem With {.Text = "Yes, I'll be there", .Value = Boolean.TrueString},
New SelectListItem With {.Text = "No, I can't come", .Value = Boolean.FalseString}}, "Choose an option")
</p>
#<input type="submit" value="Submit RSVP" />
End Using
</body>
</html>
My controller has a simple action method that takes that class and responds with another view:
<HttpPost>
Function RsvpForm(g As GuestResponse) As ViewResult
'TODO: Email response to the party organizer
Return View("Thanks", g)
End Function
Unfortunately, the form data from the post request isn't being populated in my GuestResponse class. If I insert a breakpoint in that action method, all the GuestResponse properties are empty. At the same time, the Request.Form values look as expected - they're just not filling in the GuestResponse object.
I'm sure it's something small I'm missing. Any help is appreciated.
Try adding a <FromForm> attribute to the g parameter.
I've figured it out. Apparently, ASP.NET will only populate properties of an object - my object only had public fields.
So this:
Public Class GuestResponse
Public Name As String
Public Email As String
Public Phone As String
Public WillAttend As Boolean?
End Class
should be:
Public Class GuestResponse
Public Property Name As String
Public Property Email As String
Public Property Phone As String
Public Property WillAttend As Boolean?
End Class
Thanks to those of you who participated.
This is my form in AlertSelect.cshtml:
#model edxl_cap_v1_2.Models.ContentViewModels.AlertViewModel
#{
ViewData["Title"] = "AlertSelect";
Layout = "~/Views/Shared/_CapCategoryLayout.cshtml";
}
<head>
<meta name="viewport" content="width=device-width" />
<title>#ViewBag.Title</title>
</head>
#{
<h4>#Model.Alerts.Count Alerts</h4>
<form>
<select asp-for="SelectedAlertIndex" asp-items="#Model.Alert_Identifiers">
<option>Select one</option>
</select>
<asp-controller ="Alerts" asp-action="LoadAlert" method="post">
<br />
<input type="submit" name="LoadAlert" value="LoadAlert" />
</form>
}
And this is my LoadAlert() controller action:
[HttpPost]
public IActionResult LoadAlert(Alert obj, string LoadAlert)
{
if (!string.IsNullOrEmpty(LoadAlert))
{
ViewBag.Message = "Alert loaded successfully";
}
return View("/Views/Alerts/Index", obj);
}
AlertSelect.cshtml displays the standard header and left column and the drop down list and when selected and submitted, the page resets instead of displaying /Views/Alerts/Index.cshtml with the data filled in correctly. However the url reflects the selected item: http://localhost:61453/alerts/AlertSelect?SelectedAlertIndex=2&LoadAlert=LoadAlert.
I'm close but clearly missing something and any help would very welcome. I'd also like to display the selected item on the index.cshtml page to reinforce for the user the alert message they're working on, but the viewmodel (with two properties) for the Select Tag Helper is different from the model (with 16 properties) for the index page.
Made the change to my form per both comments and answer below, and instead of returning the view specified in LoadAlert() I have a mismatch of models: "InvalidOperationException:
The model item passed into the ViewDataDictionary is of type
'edxl_cap_v1_2.Models.Alert', but this ViewDataDictionary instance requires
a model item of type 'System.Collections.Generic.IEnumerable`1[edxl_cap_v1_2.Models.Alert]'.
AlertSelect.cshtml specifies:
#model edxl_cap_v1_2.Models.ContentViewModels.AlertViewModel
Alert.cs specifies:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace edxl_cap_v1_2.Models
{
public class Alert
{
[Key]
public int AlertIndex { get; set; }
[MaxLength(150)]
public string Alert_Identifier { get; set; }
public string Sender { get; set; }
public DateTime Sent { get; set; }
...
/Views/Alerts/Index.cshtml specifies:
#model IEnumerable<edxl_cap_v1_2.Models.Alert>
#using edxl_cap_v1_2.Models
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_CapCategoryLayout.cshtml";
}
and _CapCategoryLayout specifies:
#model edxl_cap_v1_2.Models.ContentViewModels.AlertViewModel
<!DOCTYPE html>
I'm still not seeing where
The model item passed into the ViewDataDictionary is of type
'edxl_cap_v1_2.Models.Alert'
comes into play except as the namespace of Alert.cs. What do I need to change to keep out of these type mismatches?
The correct way to define form tag helpers in Core MVC should be like this (see documentation here):
<form asp-controller="Alerts" asp-action="LoadAlert" method="post">
<select asp-for="SelectedAlertIndex" asp-items="#Model.Alert_Identifiers">
<option>Select one</option>
</select>
<br />
<input type="submit" name="LoadAlert" value="LoadAlert" />
</form>
Note that default form submit method is GET if unspecified, as described in form element definition:
method = get|post [CI]
This attribute specifies which HTTP method will
be used to submit the form data set. Possible (case-insensitive)
values are "get" (the default) and "post".
The GET method will pass form values through URL with query string, hence you get the URL like following:
http://localhost:61453/alerts/AlertSelect?SelectedAlertIndex=2&LoadAlert=LoadAlert
Since no HttpGetAttribute action method found with those 2 query string parameters, the page will "reset" and displaying the same form again instead returning another view.
I have the Registration controller:
public class RegistrationController : Controller
{
private RegistrationVmBuilder _registrationVmBuilder = new RegistrationVmBuilder();
public ActionResult Index()
{
return View(_registrationVmBuilder.BuildRegistrationVm());
}
}
and the RegistrationBuilder class:
public class RegistrationVmBuilder
{
public RegistrationVm BuildRegistrationVm()
{
RegistrationVm registrationVm = new RegistrationVm
{
Courses = GetSerializedCourseVms(),
Instructors = GetSerializedInstructors()
};
return registrationVm;
}
}
RegistrationVm class:
public class RegistrationVm
{
public string Courses { get; set; }
public string Instructors { get; set; }
}
_Layout.cshtml:
#model string
<html ng-app="registrationModule">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script src="~/Scripts/jquery-2.1.4.min.js"></script>
<script src="~/Scripts/bootstrap.js"></script>
<script src="~/Scripts/registration-module.js"></script>
#RenderSection("JavascriptInHead")
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />
<link href="~/Content/bootstrap-state.min.css" rel="stylesheet" />
<title>College Registration</title>
</head>
<body>
#RenderBody()
</body>
</html>
And in index view:
#model TestApp.Models.Registration.RegistrationVm
The problem is when i run the app I got this error:
The model item passed into the dictionary is of type
'TestApp.Models.Registration.RegistrationVm', but this
dictionary requires a model item of type 'System.String'.
The shared _Layout.cshtml has a model declared (#model string), which is inherited by any view that uses it. So essentially your Index view's model (#model TestApp.Models.Registration.RegistrationVm) is ignored.
I don't know why you have #model string in your _Layout view (the model doesn't seem to be used in there), but if you want to use inheritance see Pass data to layout that are common to all pages.
Simply remove the model declaration from the shared layout.
I have an action on my controller that takes two parameters that should be captured when a form is posted:
[HttpPost]
public ActionResult Index(MyModel model, FormAction action)
The idea is that the model data should be captured in MyModel and the button that the user pressed should be captured in FormAction:
public class MyModel
{
public string MyValue { get; set; }
}
public class FormAction
{
public string Command { get; set; }
}
Here is my view:
#model TestApp.Models.MyModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
#using (Html.BeginForm("Index", "Home"))
{
#Html.TextBoxFor(x => x.MyValue)
<input type="submit" value="OK" name="command" />
<input type="submit" value="Cancel" name="command" />
}
</div>
</body>
</html>
If I add another string parameter to the action called 'command' then the value of the button comes through but it doesn't get bound to the Command property on the FormAction parameter - the parameter is always null.
If I add a Command property to MyModel then the button value does come through.
Is there something in MVC model binding that prevents more than one complex model to be bound in one action method?
The metter is that Html.BeginForm sends only model from top statenent: #model TestApp.Models.MyModel. If I clearly understand what you whant to do, the better solution will be to create ViewModel:
public class ViewModel
{
public MyModel myModel {get; set;}
public FormAction formAction {get; set;}
}
Change View as follows:
#model TestApp.Models.ViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
#using (Html.BeginForm("Index", "Home"))
{
#Html.TextBoxFor(model=> model.myModel.MyValue)
#Html.TextBoxFor(model=> model.formAction.Command)
<input type="submit" value="OK" name="command" />
<input type="submit" value="Cancel" name="command" />
}
</div>
</body>
</html>
And change your Action to:
[HttpPost]
public ActionResult Index(ViewModel model)
I've got to the bottom of this and the reason it wasn't working was simply because the parameter was named action. This is pretty much the equivalent of a keyword in MVC and it is used by the MVC framework to identify the action to be executed.
Changing the parameter name to something else means that the Command property comes through as expected!
I have setup a little test project that I hope will explain my problem.
I have a parent class that has a property containing all its children of type Child. In the MVC view I list all the children in a table and to have the form post back my children and map them to the parent property automatically I render them like:
#Html.TextBox(string.Format("Children[{0}].ChildName", childIndex), child.ChildName)
In the controller I have marked property ChildName to be required. The problem I have is that jquery unobtrusive validation does not emit any data validation attributes. Everything validates nice on the server but not on the client (obviously because there are no attributes for jquery validation to find on these inputs).
Please have a look at the code:
VIEW
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
<form action="#Url.Action("Save")" method="post">
#Html.TextBox("Name", (string)Model.Name)
<table>
<tr><td>Child name</td></tr>
#{var childIndex = 0;}
#foreach (var child in (List<GridValidationTest.Controllers.Child>)Model.Children)
{
<tr><td>#Html.TextBox(string.Format("Children[{0}].ChildName", childIndex), child.ChildName)</td></tr>
childIndex++;
}
</table>
<br /><button>Submit</button>
</form>
</div>
</body>
</html>
CONTROLLER
namespace GridValidationTest.Controllers
{
public class Parent
{
[Required]
public string Name { get; set; }
public IList<Child> Children { get; set; }
}
public class Child
{
[Required]
public string ChildName { get; set; }
}
public class MyController : Controller
{
//
// GET: /My/
public ActionResult Index()
{
var parent = new Parent { Name = "Parent name" };
parent.Children = new List<Child>
{
new Child {ChildName = "First child"},
new Child {ChildName = "Second child"}
};
return View("Index", parent);
}
public ActionResult Save(Parent parent)
{
return View("Index", parent);
}
}
}
The client validation on property Name of Parent class works fine as expected.
How should I render the children to have client-side validation to work as I expect?
You need to bind the fields in your view to your model. Instead of using:
#Html.TextBox(string.Format("Children[{0}].ChildName", childIndex), child.ChildName)
Use
#Html.TextBoxFor(model => model.Children[childIndex])
#Html.ValidationMessageFor(model => model.Children[childIndex])
Also make sure you are including the right jquery javascript files on your page. Something like:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
Also make sure that in your web config you have ClientValidationEnabled and UnobtrusiveJavaScriptEnabled turned on like so:
<appSettings>
....
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
....
</appSettings>