I'm just trying to get back into .NET MVC with the new release and I can't get my head round the way may view is binding to the DataModel.
I have a model with a property "first_name" and within an HTML form I have the following
<%= Html.TextBox("first_name", Model.first_name)%>
<%= Html.TextBoxFor(model => model.first_name) %>
<input type="text" name="first_name" id="first_name" class="myLabel"
value="<%=Model.first_name %>" />
In an action on a controller if I set the first_name property on my model and do
mymodelObject.first_name = "Test";
return View(mymodelObject);
What is the reason only the third textbox picks up on this first_name value and the other two don't?
Edit:
I probably haven't explained this well enough, sorry. Imagine I have 2 controller methods -
public ActionResult Register()
{
Registration model = new Registration();
model.first_name = "test";
return View(model);
}
With this one either binding works.
After this has been displayed I then click a button on the form and try and run this:
[HttpPost]
public ActionResult Register(Registration_ViewData model)
{
model.first_name = "Steve";
return View(model);
}
What I'm asking is why does the 3rd but not the first 2 bind to "Steve" as the new name.
You need to clear your model state so your code would look something like:
[HttpPost]
public ActionResult Register(Registration model)
{
ModelState.Clear();
model.first_name = "Steve";
return View(model);
}
Because HTML helpers read the value from the ModelState and not from the model. In order the change that behavior you'll need to work with the ModelState as well.
(see: Changing model’s properties on postback)
This should work for the first two:
<%= Html.TextBox("first_name", x => x.first_name)%>
<%= Html.TextBoxFor(model => model.first_name) %>
Related
I have a strongly typed view inheriting from a POCO class. I want to initialize the property of a model with a Querystring value at the time when view loads.
On the View Load I am using ViewData to the save the code :
public ActionResult Data() {
ViewData["QueryStringValue"] = this.Request.QueryString["Param1"]
return View();
}
In the HTML markup, I am using this code to initialize the model property in a hidden variable
<%:Html.HiddenFor(m=>m.Param,
Convert.ToInt32(Html.Encode(ViewData["QueryStringValue"]))) %>
m.param is a byte type.
URL of the request is somewhat like this : http://TestApp/Data/AddData?Param1=One
On View Save event, I am using model binding but issue is that I don't get to see the value of param initialized in the controller. It is always NULL.
My Save Event maps to a controller :
[HttpPost]
public ActionResult SaveData(MyData d)
{
string paramValue = d.Param; //this always returns null
BO.Save(d);
}
I inspected the HTML source and saw that the value of the hidden field itself is blank. Not sure why this is happening since the below code works and shows the param value in a heading element
<h2> <%=Html.Encode(ViewData["QueryStringValue"]) %> </h2>
I have no idea where I am going wrong on this.
I think, Instead of Passing the Querystring value in ViewData, You should set it as the Property value of your ViewModel/ Model and pass that to your View.
public ActionResult Data()
{
YourViewModel objVm=new YourViewModel();
objVm.Param=Request.QueryString["Param1"];
return View(objVm);
}
Now in your Strongly typed View, use it like this
#model YourViewModel
#using(Html.BeginForm())
{
#html.HiddenFor(#m=>m.Param);
<input type="submit" value="Save" />
}
Now Param value will be available in yout HttpPost action method
[HttpPost]
public ActionResult Data(YourViewModel objVm)
{
string param=objVm.Param;
//Do whatever you want with param
}
Just made this work, Issue is with this line:
<%:Html.HiddenFor(m=>m.Param,
Convert.ToInt32(Html.Encode(ViewData["QueryStringValue"]))) %>. I stated in the question that m.Param is of type byte. I figured out that issue was with casting.
I tried this code and it worked
<%:Html.HiddenFor(m => m.Param, (byte)Convert.ToInt16(this.Request.QueryString["Param1"].ToString()))%>
I am starting a new project in Asp.net MVC 2.
I have been mostly a webforms developer and have limited exposure to Asp.Net MVC and hence this is probably a noob question.
My situation is as follows:
I have a create page for saving some data to the DB.
The view for this page is not strongly bound / typed - so the way I am extracting the data from the view is by looking at the POST parameters.
Incase there is an error (data validation, etc), I need to send the user back to the previous page with everything filled in the way it was and displaying the message.
On webforms, this got handled automatically due to the view state - but how can I go about doing the same here?
A code example can be as follows:
View:
<% using (Html.BeginForm("Create", "Question", FormMethod.Post)) { %>
<div>
Title: <%: Html.TextBox("Title", "", new { #style="width:700px" })%>
</div>
<div>
Question: <%: Html.TextBox("Question", "", new { #style="width:700px" })%>
</div>
<input type="submit" value="Submit" />
<% } %>
Controller:
[HttpPost]
[ValidateInput(false)]
public ActionResult Create() {
Question q = new Question();
q.Title = Request.Form["Title"];
q.Text = Request.Form["Question"];
if(q.Save()) {
return RedirectToAction("Details", new { id = q.Id });
}
else {
// Need to send back to Create page with data filled in
// Help needed here
}
}
Thanks.
You could simply return the View in case of error. This will preserve the context.
[HttpPost]
[ValidateInput(false)]
public ActionResult Create(Question q) {
if(q.Save()) {
return RedirectToAction("Details", new { id = q.Id });
}
else {
// Need to send back to Create page with data filled in
// Help needed here
return View();
// If the view is located on some other controller you could
// specify its location:
// return View("~/Views/Question/Create.aspx");
}
}
Also I would recommend you to use strongly typed views along with the strongly typed helpers. Notice how I used a Question object directly as action parameter. This is equivalent to the code you have written in which you were manually extracting and building this object. The model binder does this job automatically for you.
My model is correctly validated. If I take a peak in the validation results during debug, I will see that everything is correct. However, all my validation results will show, even if only one is invalid. Again, during debug, only one field is correctly showing up in the validation results, but when my view is rendered all our displayed:
[HttpPost]
public ActionResult Create(Widget widget)
{
if (widge.Valid)
{
// Save to db
}
retun View(widget);
}
My view:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" Inherits="System.Web.Mvc.ViewPage<Widget>" %>
// took out a lot of html here
<form action="Create" method="post">
<input name="Widget.City" value="<%= Model.City == null ? "" : Model.City%>" />
<%= Html.ValidationMessage("Widget.City")%>
<input name="Widget.Department" value="<%= Model.Department == null ? "" : Model.Department %>" />
<%= Html.ValidationMessage("Widget.Department")%>
<button type="submit">Save</button>
</form>
Let us say City and Department are set to NotNull in my model and I correctly put in a City, but leave Department blank. Again, it will show it is invalid on the controller, with the property Department having a problem, yet in my view I'll get "may not be null" messages for both properties. If I have 4 properties and 4 ValidationMessage tags in my view, even if one property is not valid ... all 4 will show. What's the deal?
If I'm not mistaken, I think you want to be using
Html.ValidationMessageFor(model => model.City)
Not what you're using currently in your view.
Also... Since you're using a strongly typed view, you should be checking ModelState.IsValid to determine whether or not you should save your Widget. That is if you're using data annotations on your view model.
What are you using as your input to the [HttpGet] action of the view?
Since you're using the format Widget.Property the view expects the model to have a Widget property containing the data for the widget.. i.e it is looking for Model.Widget.Property but your model only contains Model.Property. Based on the code posted here, you're only passing the widget back to the view and it will interpret that as having a NULL Widget property, thus triggering all NotNull validation.
A solution here is to assign the Widget post model (what you're accepting in the [HttpPost] action) to a Widget property of the of the model you're passing back to the view.
public class CreateViewModel
{
public Widget Widget { get; set; }
}
[HttpPost]
public ActionResult Create(Widget widget)
{
if (widge.Valid)
{
// Save to db
}
var viewModel = new CreateModel() { Widget = widget };
retun View( viewModel );
}
Hopefully I articulated that correctly.. Its been a long day = )
Here is my situation -
I have two nested view models:
<%=Html.EditorFor(x => x.DisplayEntitiesWithRadioboxesViewModel)%><br />
Which sit within their parent (StructureViewModel), I can populate the nested ViewModels easily and pass it through to the main View:
Within the Controller - Example
var moveDepartment = new StructureViewModel();
moveDepartment.DisplayEntitiesWithRadioboxesViewModel = fullDepartmentList.Select(x => new DisplayEntityViewModel
{
Id = x.Id,
Path = x.Path,
PathLevel = x.PathLevel,
Description = x.Description,
});
return View(moveDepartment);
EditorTemplete - Example
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Site.Areas.Administration.ViewModel.DisplayEntityViewModel>>" %>
<table class="aligncenter"><%
if (Model != null)
{
foreach (var entity in Model)
{%>
<tr class="tRow">
<td style="text-align:left; text-indent:<%=Html.Encode(entity.PathLevel)%>em">
<%=Html.Encode(entity.Description)%>
<%=Html.RadioButton("radiobutton",entity.Id)%>
</td>
</tr><%
}
}%>
</table>
namespace Site.Areas.Administration.ViewModel
{
public class DisplayEntityViewModel
{
public int Id { get; set; }
public string Path { get; set; }
public string PathLevel { get; set; }
public string Description { get; set; }
}
}
However when I try to pull back this information the nested ViewModels are null:
[HttpPost]
public ActionResult Move(StructureViewModel StructureViewModel)
When I hover over StructureViewModel it only contains data set at the parent ViewModel. For example: a hidden value can been seen but DisplayEntitiesWithRadioboxesViewModel = null.
The only way I know how to access the DisplayEntitiesWithRadioboxesViewModel is to use FormCollection and iterate throught the FormCollection and pull out the information I need from the nested ViewModels.
This however just doesn't seem right, as I have found at I then have to re-populate the DisplayEntitiesWithRadioboxesViewModel with the values from the FormCollection, if for example an error has occured and the user needs to be sent back to the same View.
I have tried searching the web/books but cannot find a solution.
Is there a better way?
Thanks in advance for any help.
And why did you use an EditorFor for a
simple dropdown, which is easily to
use with DropDownFor
This has now been altered to use the DropDownFor.
what is the Key of the
DisplayEntitiesWithRadioboxesViewModel
value in FormCollection
{string[3]}
[0] = "DisplayEntitiesWithRadioboxesViewModel.radiobutton"
[1] = "Action"
[2] = "OldParentId"
Clare :-)
Your problem is pretty common and somewhat easy to fix once you understand how it works.
Right now you have a view model that has a property which is an IEnumerable<T> (doesn't matter what the generic parameter is). You are trying to pass the items to the view and populate the IEnumerable<T> with the same values when the response comes back, using the values originally written to the page, and augmented with the selected item (at least from the code you have posted anyway, it would help for you to state your exact intention in the question). The problem you have here is that you must send those values to the page in a way in which they can be returned.
Let me just say now that you probably should NOT be using this technique. It is typically a much better idea to return the selection only and generate the list again if you need to server side.
From the looks of things, you want to return the whole list and then look for the item that is selected, which is after all the point of a drop down or radio button group. In order to get the selection back, the parameter to your controller action must have properties which match the variables passed back in. In this case, it looks like you are using the parameter name radiobutton for all of your radio buttons (the same hold true for drop down list, only it uses the name of the list). Which ever one is selected, the value associated with it is returned with that name. The MVC framework takes care of trying to find the appropriate action which has as many names specified as possible.
What you need to use for your action parameter is a new class that contains a property for all of the field names being submitted back to the server! Or of course you could simply add the radiobutton property to your StructureViewModel too. In fact, you'll notice that it is trying to set that value already, only it doesn't currently exist on your view model. You still will not receive the original list back however, but thats okay, because even if you did receive the original list back, you have no identifier on it to let you know which item was selected!
Hopefully this helps you understand what is going on, if you have more questions, please ask.
I would recommend you using strongly typed helpers everywhere so that you don't have to worry about naming your controls. Here's how to proceed:
Models:
public class DisplayEntityViewModel
{
public int Id { get; set; }
public string Path { get; set; }
public string PathLevel { get; set; }
public string Description { get; set; }
}
public class StructureViewModel
{
public IEnumerable<DisplayEntityViewModel> DisplayEntitiesWithRadioboxesViewModel { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var moveDepartment = new StructureViewModel();
moveDepartment.DisplayEntitiesWithRadioboxesViewModel = new[]
{
new DisplayEntityViewModel
{
Id = 1,
Path = "some path 1",
PathLevel = "some path level 1",
Description = "some description 1"
},
new DisplayEntityViewModel
{
Id = 2,
Path = "some path 2",
PathLevel = "some path level 2",
Description = "some description 2"
},
};
return View(moveDepartment);
}
[HttpPost]
public ActionResult Index(StructureViewModel StructureViewModel)
{
return View(StructureViewModel);
}
}
Main View (~/Views/Home/Index.aspx):
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<SomeNs.Models.StructureViewModel>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) { %>
<table class="aligncenter">
<%= Html.EditorFor(x => x.DisplayEntitiesWithRadioboxesViewModel) %>
</table>
<input type="submit" value="Go" />
<% } %>
</asp:Content>
Editor Template (~/Views/Home/EditorTemplates/DisplayEntityViewModel.ascx)
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ToDD.Models.DisplayEntityViewModel>" %>
<tr class="tRow">
<td style="text-align:left; text-indent:<%=Html.Encode(Model.PathLevel)%>em">
<%= Html.Encode(Model.Description) %>
<!-- Remember that you need to place input fields for each property
that you expect to get back in the submit action
-->
<%= Html.HiddenFor(x => x.Description) %>
<%= Html.TextBoxFor(x => x.Path) %>
</td>
</tr>
Now submit the form and everything should be bound correctly. An important thing to note is that the editor template is strongly typed to DisplayEntityViewModel and not IEnumerable<DisplayEntityViewModel> as in your case. When in your main view you write:
<%= Html.EditorFor(x => x.DisplayEntitiesWithRadioboxesViewModel) %>
the framework automatically detects that the property is a collection and will call the editor template for each item of this collection so you no longer need to loop through the elements which makes your code more elegant.
UPDATE:
Using dropdown lists is also very easy: checkout this answer.
Can you tell me how the EditorFor looks exactly? And why did you use an EditorFor for a simple dropdown, which is easily to use with DropDownFor.
what is the Key of the DisplayEntitiesWithRadioboxesViewModel value in FormCollection
If I understand it correctly, you have a View, with some parent-info, and at the same time multiple iterations of these 2 fields in the same view. Is that right?
Then I know how to fix this.
I have following problem. In my view model I defined some list properties as follows:
public class BasketAndOrderSearchCriteriaViewModel
{
List<KeyValuePair> currencies;
public ICollection<KeyValuePair> Currencies
{
get
{
if (this.currencies == null)
this.currencies = new List<KeyValuePair>();
return this.currencies;
}
}
List<KeyValuePair> deliverMethods;
public ICollection<KeyValuePair> DeliveryMethods
{
get
{
if (this.deliverMethods == null)
this.deliverMethods = new List<KeyValuePair>();
return this.deliverMethods;
}
}
}
This view model is embedded in another view model:
public class BasketAndOrderSearchViewModel
{
public BasketAndOrderSearchCriteriaViewModel Criteria
{
[System.Diagnostics.DebuggerStepThrough]
get { return this.criteria; }
}
}
I use 2 action methods; one is for the GET and the other for POST:
[HttpGet]
public ActionResult Search(BasketAndOrderSearchViewModel model){...}
[HttpPost]
public ActionResult SubmitSearch(BasketAndOrderSearchViewModel model){...}
In the view I implement the whole view model by using the EditorFor-Html Helper which does not want to automatically display DropDownLists for List properties!
1. Question: How can you let EditorFor display DropDownLists?
Since I could not figure out how to display DropDownLists by using EditorFor, I used the DropDownList Html helper and filled it through the view model as follows:
public IEnumerable<SelectListItem> DeliveryMethodAsSelectListItem()
{
List<SelectListItem> list = new List<SelectListItem>();
list.Add(new SelectListItem()
{
Selected = true,
Text = "<Choose Delivery method>",
Value = "0"
});
foreach (var item in this.DeliveryMethods)
{
list.Add(new SelectListItem()
{
Selected = false,
Text = item.Value,
Value = item.Key
});
}
return list;
}
My 2. question: As you can see I pass my view model to the action metho with POST attribute! Is there a way to get the selected value of a DropDownList get binded to the passed view model? At the moment all the DropDownList are empty and the selected value can only be fetched by the Request.Form which I definitely want to avoid!
I would greatly appreciate some ideas or tips on this!
For those like me that got to this post these days I'd recommend you to fully download the tutorial from http://www.asp.net/mvc/tutorials/mvc-music-store-part-1 which covers this and most of the common techniques related with .NET MVC applications.
Anyway Really usefull your post and answers man (If I could vote you I would :)
Let's try to take on this one:
Answer to Question 1: How can you let EditorFor display DropDownLists?
When you call Html.EditorFor() you can pass extra ViewData values to the EdiorTemplate View:
<%: Html.EditorFor(model => Model.Criteria, new { DeliveryMethods = Model.DeliveryMethods, Currencies = Model.Currencies}) %>
Now you have ViewData["DeliveryMethods"] and ViewData["Currencies"] initialized and available inside your EditorTemplate.
In your EditorTemplate you somehow need to call and convert those entries into DropDowns / SelectLists.
Assuming you've got an ascx file of type System.Web.Mvc.ViewUserControl<BasketAndOrderSearchCriteriaViewModel> you could do the following:
<%: Html.LabelFor(model => model.DeliveryMethods) %>
<%: Html.DropDownList("SelectedDeliveryMethod", new SelectList(ViewData["DeliveryMethods"] as IEnumerable, "SelectedDeliveryMethod", "Key", "value", Model.SelectedDeliveryMethod)) %>
Same goes for the Currencies.
<%: Html.LabelFor(model => model.Currencies) %>
<%: Html.DropDownList("SelectedCurrency", new SelectList(ViewData["Currencies"] as IEnumerable, "SelectedDeliveryMethod", "Key", "value", Model.SelectedCurrency)) %>
This setup will make your DeliveryMethodAsSelectListItem() obsolete and you can use any kind of list. Means you are not bound to KeyValuePairs. You'll just need to adjust your call on Html.DropDownList() from now on.
As you can see, I have introduced some new properties to your BasketAndOrderSearchCriteriaViewModel:
Model.SelectedDeliveryMethod
Model.SelectedCurrency
They are used to store the currently selected value.
Answer to Question 2: Is there a way to get the selected value of a DropDownList get binded to the passed view model?
In the EditorFor template we are passing the newly created Model.SelectedDeliveryMethod and Model.SelectedCurrency properties as the SelectedValue Parameter (See 4th Overload of the DropDownList Extension Method).
Now that we have the View doing it's job: How can we get the currently selected value inside the POST Action?
This is really easy now:
[HttpPost]
public ActionResult SubmitSearch(BasketAndOrderSearchViewModel model)
{
...
var selectedDeliveryMethod = model.Criteria.SelectedDeliveryMethod;
var selectedCurrency model.Criteria.SelectedDeliveryMethod;
...
}
Note: I don't have an IDE to test it right now, but it should do the trick or at least show you in which direction to go.