How to get the form name for a property - forms

In Razor I know that if you write
#Html.HiddenFor(x => x.PropertyX.PropertyY)
it will generate HTML like:
<input type="hidden" name="PropertyX.PropertyY" value="...">
And (especially) if this was in an Editor Template it might generate this HTML:
<input type="hidden" name="ParentProperty[12].PropertyX.PropertyY" value="...">
How do I get a name for an arbitrary property? I'm assuming there must be some way to do this using MVC infrastructure (perhaps some method or class?)

You could write a custom helper for that:
public static class NameExtensions
{
public static string NameFor<TModel, TProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression
)
{
var partialName = ExpressionHelper.GetExpressionText(expression);
return html
.ViewContext
.ViewData
.TemplateInfo
// You could do the same with GetFullHtmlFieldId
// if you were interested in the id of the element
.GetFullHtmlFieldName(partialName);
}
}
and then:
#Html.NameFor(x => x.PropertyX.PropertyY)

Related

Aliased Form Names with HtmlHelpers

tl;dr
How do I modify form element names that are generated to use aliased names that come from ModelMetadata.AdditionalValues instead? Preferably with a custom (or not custom) html helper.
I have a search view model with some custom object properties. Each property goes two more properties deep in my view. I also use editor templates in my view. My form ends up with elements like the following:
HTML
<input name="Field1.Field.Id" id="..." type="..." />
<input name="Field1.Field.Label" id="..." type="..." />
<input name="Field2.Field.Id" id="..." type="..." />
<input name="Field2.Field.Label" id="..." type="..." />
Search.spark (.aspx)
${ Html.EditorFor(m => m.Field1) } // Field1 is of type SearchField
${ Html.EditorFor(m => m.Field2) } // Field2 is of type SearchField
etc...
SearchField.spark (.ascx)
<viewdata model="SearchField" />
// *snip*
${ Html.EditorFor(m => m.Field) } // Field is of type SpecialField
SpecialField.spark (.ascx)
<viewdata model="SpecialField" />
${ Html.HiddenFor(m => m.Id) } // Id is of type int?
${ Html.TextBoxFor(m => m.Label } // Label is of type string
Because the form uses GET, I end up with very long query strings. What I'd like to do is provide aliases for the properties. I already have an attribute, a model metadata provider to populate AdditionalValues and a custom model binder to take care of the aliases, but I'm stuck on generating the proper html.
I thought I'd just write a custom html helper, but got stuck pretty fast.
public static MvcHtmlString HiddenForA<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (metadata.AdditionalValues.ContainsKey("Alias"))
{
string alias = (string)metadata.AdditionalValues["Alias"];
// what to do now?
}
}
You have the alias that you want to use, the value is available from metadata.Model then all you need to do is generate your HTML. You could use a tag builder, string.format or what ever you prefer, for the sake of readability I'll use string.format and have the properties in ' rather than ".
public static MvcHtmlString HiddenForA<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (metadata.AdditionalValues.ContainsKey("Alias"))
{
string alias = (string)metadata.AdditionalValues["Alias"];
string value = Convert.ToString(value, metadata.Model);
string html = string.Format(#"<input type='hidden' name='{0}' value='{1}' >", alias, value);
return MvcHtmlString.Create(html);
}
}
However, this will only handle the issue of your long URLs, this will mean that your model binding will not properly bind these on your action. To do this you'll need to either create a custom model binder or have your action take a viewmodel with properties of these names then either create a new object of your desired model and fill in its properties or use the viewmodel as is.

Passing values from a form to a new action

Ok guys i have a question, if this is my Form , and is generated for every item in the DB, i want to send the item with the quantity specified.
to send the quantity , from this razor view
#using (Html.BeginForm("AddToCart", "Prices"))
{
string qtname = "qt" + #item.id;
<div>
<input id="#qtname" name="#qtname" class="quantity" type="text" value="0" readonly="readonly" />
</div>
<input type="submit" value="Adauga" class="addToCart" />`
}
i need just these?
[HttpPost]
public ActionResult AddToCart(ProductsModel Products, string qtname)
{ }
and do i need some html.hidden for passing along the item.id too?
In the form you are sending only the qtname parameter as input field, whereas your controller action also expects a ProductsModel parameter which is never sent. If you want to bind it you will have to create input fields for all properties of this view model.
But in your case a better solution would be to simply include the id of the product as hidden field and then fetch the corresponding product from your datastore given this id:
[HttpPost]
public ActionResult AddToCart(string id, string qtname)
{
ProductsModel products = _repository.GetProduct(id);
...
}

Where and how to load dropdownlists used in masterpage

I'm new to MVC!
I am trying to use two DropDownLists (Cities, Categories) in a PartialView that will be used in MasterPage, meaning they will be visble all the time.
I tried to load them in HomeCOntroller, but that didn't work. I got an Exception.
I read something about making a baseController that the other controllers will inherit from, I have tried that, kind of, but I guess i'm doing something wrong.
This is the only code I got today:
Masterpage
<% Html.RenderPartial("SearchForm"); %>
PartialView (SearchForm.ascx)
<% using (Html.BeginForm("Search", "Search")) { %>
<% } %> // dont know why I need two BeginForms, if I dont have this the other form won't trigger at all! Weird!
<% using (Html.BeginForm("Search", "Search", FormMethod.Get)) { %>
<%= Html.DropDownList("SearchForm.Category", new SelectList(ViewData["Categories"] as IEnumerable, "ID", "Name", "--All categories--")) %>
<%= Html.DropDownList("Search.City", Model.Cities, "--All cities--") %>
<input name="search" type="text" size="16" id="search" />
<input type="submit" id="test" title="Search" />
<% } %>
Two question:
Where and how to load the DropDownLists is the problem. I have tried to load it in the HomeController, but when go to another page then it says that the DDLs is empty and I get a Excecption.
Why do I have to use two forms for the ActionMethod to trigger ?
Hope anyone can help me out!
It sounds like you're only setting the property for a single action result. The Model.Cities data will have to be populated for every single view that needs to use it.
One solution would be to move the population of it to an ActionFilter
public class CityListAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext) {
var result = filterContext.Result as ViewResult;
result.ViewData.Model = //populate model
base.OnActionExecuted(filterContext);
}
}
and then add the filter to your controller
[CityList]
public class HomeController : Controller {
public ActionResult Index() {
return View();
}
}
As for the two forms issue, there should be no reason that i can think of that you need an empty form.
Take a look at the html that's being output and make sure it's ok. Also check the action is being generated correcly
Better way to do this, is to create something like MasterController and have action method on it like this:
[ChildActionOnly]
public ActionResult SearchForm()
{
//Get city data, category data etc., create SearchFormModel
return PartialView(model);
}
I recommend you create strongly typed view (SearchForms.ascx of type ViewUserControl<SearchFormModel>). Also it may be a good idea to have a model like this:
public class SearchViewModel
{
public IList<SelectListItem> Cities { get; set; }
public IList<SelectListItem> Categories { get; set; }
}
and use a helper like this: http://github.com/Necroskillz/NecroNetToolkit/blob/master/Source/NecroNet.Toolkit/Mvc/SelectHelper.cs to convert raw data to DDL friendly format beforehand.
In any case, you now use Html.RenderAction() instead of Html.RenderPartial() and specify you want "SearchForm" action from "MasterController".

What's the Struts 2 equivalent of ASP.NET's Request.Form (or FormCollection)?

I'm dynamically adding textboxes to a form on my jsp page using Javascript. When that form is submitted to an action, how does my action get the values of those textboxes? (I'm using Struts 2, btw.) In ASP.NET, I was able to find them in Form.Request/FormCollection. Is there a Struts 2 equivalent? Thanks a million.
In Struts2, you create beans in the form to do submit values. In order to create the input text-box, use the <s> tag. For example :
<s:textfield name="loginBean.userName" label="UserName" required="true" />
Here loginBean is the bean passed to the jsp page when.
Bean consists of variable declarations and getters-setters for the variable.
Then in the back-end Java where the form is submitted to, you can access the same bean.
Declare getter-setter in Java and then you can access the properties of the bean.
public LoginBean getLoginBean() {
return loginBean;
}
public void setLoginBean(LoginBean loginBean) {
this.loginBean = loginBean;
}
public String authenticate() {
String username = loginBean.getUserName();
I would recommend looking at source codes of open-source Struts projects.
It sounds like you're trying to populate a dynamic list. To do that, you just have to use the [n] index syntax at the end of your Action class property name:
HTML:
<input type="text" name="yourCollection[0]" value="first value" />
<input type="text" name="yourCollection[1]" value="second value" />
<input type="text" name="yourCollection[2]" value="third value" />
Action Class:
public class YourAction extends Action {
public List<String> yourCollection;
public List<String> getYourCollection(){
return yourCollection;
}
public void setYourCollection(List<String> aCollection){
this.yourCollection = aCollection;
}
}

ASP.Net MVC 2 - ModelBinding and Dictionary<int, int>

In my interface I have a list of text boxes, something like this :
http://screencast.com/t/YjIxNjUyNmU
The number of textboxes is unknown as each of them is associated with a Template.
In my page, the goal is to associate a number to some of those templates.
Here is a sample HTML code :
<% // loop on the templates
foreach(ITemplate template in templates)
{
// get the content from the input dictionary
int val;
content.TryGetValue(template.Id, out val);
// convert it as a string
string value = ((val > 0) ? val.ToString() : string.Empty);
// compute the element name/id (for dictionary binding)
string id = ??????????
string name = ??????????????
%>
<label for="<%= name %>"><%= template.Name %></label>
<input type="text" id="<%= id %>" name="<%= name %>" value="<%= value %>" />
<br />
<% }
%>
What I expect, in my controller, is to get a IDictionary where the first int is the template ID , and the other is the count given by the user.
Here is what I want :
public ActionResult Save(int? id, Dictionary<int, int> countByTemplate)
I tried a lot of things but nothing worked. I tried to read the sources but it's a maze, and I'm getting a headhache trying to get information about model binding.
Questions :
is there a good ressource on how the modelbinding works ?
I'd like someting exhaustive, I'm tired of the 84093043 blogs that talk about a given specific example.
how can I build my HTML, using to get a IDictionary (or even a IDictionary in my controller's action ?
Thanks a lot for your help
Information on how to write your input elements for binding to arrays, dictionaries, and other collections can be found at http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx.
Ok... Thanks to Levi I was able to get on a solution.
Not the cleaner one, but it works.
The HTML should be written this way:
<%
int counter = 0;
// loop on the templates
foreach(ITemplate template in templates)
{
// get the value as text
int val;
content.TryGetValue(template.Id, out val);
var value = ((val > 0) ? val.ToString() : string.Empty);
// compute the element name (for dictionary binding)
string id = "cbts_{0}".FormatMe(template.Id);
string dictKey = "cbts[{0}].Key".FormatMe(counter);
string dictValue = "cbts[{0}].Value".FormatMe(counter++);
%>
<input type="hidden" name="<%= dictKey %>" value="<%= template.Id %>" />
<input type="text" id="<%= id %>" name="<%= dictValue %>" value="<%= value %>" />
<label for="<%= id %>"><%= template.Name %></label>
<br />
<% }
%>
I had to add a hidden field to store the value.
I introduced a 'fake' counter just to loop over the dictionary the way ASP.Net MVC wants it.
As a result I got a dictionary filled with my values and '0' when the textbox is empty.
Another problem appeared: the ModelState was considered not valid because "a value is required". I don't want my values to be required, but looking at the modelbinder code, I did not found a way to tell the binder that a value is NOT required.
So I tricked the ModelState in my controller, removing all error, like this:
public ActionResult Save(int? id, Dictionary<int, int> cbts)
{
// clear all errors from the modelstate
foreach(var value in this.ModelState.Values)
value.Errors.Clear();
Well... I effectively got a solution, but the HTML is kind of ugly now, and counterintuitive (using an index to loop over a non-indexed collection ??).
And I need to trick each time I'll use this kind of binding to have it all work properly.
So I will now open a new post to make dictionary binder something better.
Here it is: ASP.Net MVC 2 - better ModelBinding for Dictionary<int, int>
EDIT - There is a cleaner solution, thanks to Pavel Chuchuva.
In the controller code, use a nullable int as value for the dictionary.
A bit more code to add, but much cleaner.
public ActionResult Save(int? id, Dictionary<int, int?> cbts)
{
// this is our final dictionary<int, int>
Dictionary<int, int> cbtsFinal = new Dictionary<int, int>();
// loop on the dicitonary with nullable values
foreach(var key in cbts.Keys)
{
// if we have a value
if(cbts[key].HasValue)
// then put it in the final dictionary
cbtsFinal.Add(key, cbts[key].Value);
}