Dynamically creating hidden form fields for various models (MVC 4) - javascript

I'm trying to dynamically create hidden fields for a set of properties, but I'm getting a 500 server error when I submit the form. I confirmed the following:
The properties I'm iterating over in the foreach statement are correct.
property.Name is a valid property name for the type retrieved by NewItem.GetType()
Here's what I have:
View
#model PaneViewModel
using (Ajax.BeginForm("AddItem", "Action", new AjaxOptions
{
UpdateTargetId = "tool-wrapper",
HttpMethod = "POST",
}))
{
// Some standard input fields here (these are working properly).
[...]
// Here's what's broken:
#foreach (var property in Model.NewItem.GetType().GetProperties().Where(<criteria here>))
{
#Html.HiddenFor(m => m.NewItem.GetType().GetProperty(property.Name), column.GetValue(Model.NewItem, null))
}
<button type="submit">Add</button>
}
ItemViewModel
public class ItemViewModel
{
public int SomeField { get; set; }
public int AnotherField { get; set; }
}
PaneViewModel
public class PaneViewModel
{
public ItemViewModel NewItem { get; set; }
}
Controller
[HttpPost]
public ActionResult AddItem([Bind(Prefix = "NewItem")] ItemViewModel model)
{
// Stuff here.
}
It's worth noting that the following generates the hidden fields with the correct names and values in the generated HTML, but the values of the hidden field aren't posted to the controller action:
#foreach (var property in Model.NewItem.GetType().GetProperties().Where(<criteria here>))
{
#Html.Hidden(property.Name, column.GetValue(Model.NewItem, null))
}
So it seems the problem is with the m => m.NewItem.GetType().GetProperty(property.Name) component

This type of logic does not belong in a view
Html.HiddenFor() expects an expression (Expression<Func<TModel,
TProperty>>) as the first parameter, but .GetProperty() returns
typeof PropertyInfo
You should not be generating multiple hidden inputs for properties
of your model, but rather use a view model to represent only what
you need to edit (it degrades performance by sending extra data to
the client and then posting it back again unchanged, and anyone
could use FireBug or similar tools to change the values and you
might be none the wiser.
However, if you do want to do this, the you could create a html helper that generates hidden inputs for all properties marked with the [HiddenInput] attribute (or modify this example to pass in some condition that filters the required properties)
public static MvcHtmlString HiddenForModel<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
StringBuilder html = new StringBuilder();
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
var properties = metaData.Properties.Where(p => p.TemplateHint == "HiddenInput");
foreach(var property in properties)
{
html.Append(helper.Hidden(property.PropertyName));
}
return MvcHtmlString.Create(html.ToString());
}
Note this will also generate the id and data-val-* attributes which are probably unnecessary, so you could minimize the generated html by using
foreach(var property in properties)
{
TagBuilder input = new TagBuilder("input");
input.MergeAttribute("type", "hidden");
input.MergeAttribute("name", property.PropertyName);
input.MergeAttribute("value", string.Format("{0}", property.Model));
html.Append(input.ToString());
}

Related

How to access object properties in javascript?

I am passing an object(model) in View where I have javascript code written. The object has certain properties that I want to access in javascript in order to create a drop down list from the values of those properties.
Here is my object:
public class TestObject
{
public BuildData ExteriorColor { get; set; }
public BuildData InteriorColor { get; set; }
}
and
public class BuildData
{
public List<ExteriorInteriorData> Data { get; set; }
public bool isInstalled { get; set; }
public BuildData()
{
Data = new List<ExteriorInteriorData>();
}
}
Now in the View I have an object of TestObject through ViewData and I want to populate the values present in List<ExteriorInteriorData> in a select list.
Basically I want to do something like this:
for (var i = 0; i < data.ExteriorColor.Data.length; i++) {
$("#Exterior_Color").append($("<option " + (i == 0 ? "selected" : "") + "></option>").val(data.ExteriorColor.Data[i].ColorName + ", " + data.ExteriorColor.Data[i].RgbValue).html(data.ExteriorColor.Data[i].ColorName));
}
So, How do I access the object TestObject present in Viewdata inside of Javascript?
if you are writing JavaScript in same view then you just need to convert your model object in js object using this code.
var jsModel = #Html.Raw(Json.Encode(Model))
if you want in external file then create an html element and set this model in data- field and get this model in js like this
View
<div data-JsObject="#Html.Raw(Json.Encode(Model))" id="JSOBJ"> </div>
JS External file
var list = JSON.parse($("#JSOBJ").data("JsObject"))
I hope it'll work for you.
except of javascript you can use defualt razor helper for create dropdownlist :
#Html.DropDownListFor(model => model.ExteriorColor.Data, new SelectList(Model.ExteriorColor.Data, "Value", "Text"))
repale Value and Text By properties in ExteriorInteriorData
Try this. Use for loop within the tag.
#model [yourproject].Models.TestObject
<select id="Exterior_Color" name="Exterior_Color">
#foreach (var item in this.Model.ExteriorColor)
{
<option value="#item.RgbValue">#item.ColorName</option>
}
</select>
You can simply get selected item from javasrcipt
$("#Exterior_Color").val();

How to reuse form on the same MVC page with the same model?

I have a built a view with a simple contact form with Name, Email and Phone. I re-use this form two or three times on the same parent page. When the user submits one of the forms, I detect the form submitted and post contents via javascript/jQuery to a database using Dapper.
The problem is when I re-use the contact form on any page, the input fields will not be generated with unique IDs. This causes w3 validation to fail saying Duplicate ID. In this particular case, I need the page to pass w3 validation.
How would I go about solving this? I tried with ViewData.TemplateInfo.HtmlFieldPrefix to prefix the input fields but does not really solve the problem as the prefix value is static. If I do a random prefix value, then how can I capture that in HttpPost controller?
Here is my code.
Index.cshtml
- with several references to ContactForm.cshtml:
<html>
<head>...</head>
<body>
....
#{Html.RenderAction("ContactForm")}
....
#{Html.RenderAction("ContactForm")}
....
</body>
</html>
ContactForm.cshtml
#model ProjectX.Models.CaseModel
#using (Html.BeginForm("SendMessage", "Home", FormMethod.Post))
{
#Html.TextInputFor(model => model.Name, Res.Name, new { placeholder = Res.Name } )
#Html.TextInputFor(model => model.Phone, Res.Phone, new { placeholder = Res.Phone, type = "tel" })
#Html.TextInputFor(model => model.Email, Res.Email, new { placeholder = Res.Email, type = "email" })
<input type="submit" value="Send" onclick="jsSaveForm(event);" />
}
// #Html.TextInputFor MvcHtmlString helper method
public static MvcHtmlString TextInputFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string title, Object htmlAttributes = null)
{
var req = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).IsRequired;
var name = helper.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression));
if (req)
title += " *";
string html = String.Format("<div class=\"inp\"><label for=\"{2}\">{0}</label>{1}</div>", helper.ValidationMessageFor(expression), helper.TextBoxFor(expression, htmlAttributes), name);
return new MvcHtmlString(html);
}
CaseModel.cs
public class CaseModel
{
[Required(ErrorMessageResourceType = typeof(Res), ErrorMessageResourceName = "ValidationRequired")]
public string Name { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
}
HomeController
// GET
[ChildActionOnly]
[Route("~/Home/ContactForm")]
public ActionResult ContactForm()
{
// tried this but this is a static value.
// ViewData.TemplateInfo.HtmlFieldPrefix = "SomePrefix";
return PartialView(new CaseModel());
}
// POST
[HttpPost]
[Route("~/Home/SendMessage")]
public async Task<PartialViewResult> SendMessage(CaseModel model)
{
... brevity...
await SaveData(model);
}
You can just remove the id attribute by setting it to an empty string in the htmlAttributes argument, for example
#Html.TextInputFor(model => model.Name, Res.Name, new { id = "", placeholder = Res.Name } )
although you might want to consider doing this in your TextInputFor() extension method.
As a side note, you extension method does not take into account the display name for your properties (when using the [Display(Name = "...")] attribute, and the title parameter is unnecessary. I suggest your code should be
public static MvcHtmlString TextInputFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
{
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
string displayName = metaData.DisplayName;
if (metaData.IsRequired)
{
displayName += " *";
}
StringBuilder html = new StringBuilder();
html.Append(helper.LabelFor(expression, displayName).ToString());
html.Append(helper.TextBoxFor(expression, htmlAttributes).ToString());
html.Append(helper.ValidationMessageFor(expression).ToString());
TagBuilder container = new TagBuilder("div");
container.AddCssClass("inp");
container.InnerHtml = html.ToString();
return new MvcHtmlString(container.ToString());
}

Get correct checkbox value when using razor asp.net mvc

I am rendering my form using razor. I iterate over class .control_group that's inside a form and create objects that I need to send back to controller. My form has checkboxes and hidden input values. Problem I am facing now is this. Checkbox elements rendered by razor have two inputs, one is hidden and other one is shown. When I collect form data I am always getting last input value (hidden one, and it's always false) How can I get the true value?
Current data sent to controller (everything is false):
{"ajaxData":[{"Id":"1","RoleId":"1","R":"false","W":"false","E":"false","D":"false"},{"Id":"2","RoleId":"2","R":"false","W":"false","E":"false","D":"false"}]}
Collecting data like this (found similar problem here on SO):
var ajaxData = $('.control_group').map(function (i, group) {
var data = {};
$(group).find(':input').each(function () {
data[this.name] = this.value;
});
return data;
}).get();
ajaxData = JSON.stringify({ 'ajaxData': ajaxData });
console.log(ajaxData);
Controller looks like this:
public void SendData(List<SomeClass> ajaxData)
{
var data = ajaxData;
}
public class SomeClass
{
public int Id { get; set; }
public int RoleId { get; set; }
public bool R { get; set; }
public bool W { get; set; }
public bool E { get; set; }
public bool D { get; set; }
}
It is by design, you can read about this here: asp.net mvc: why is Html.CheckBox generating an additional hidden input
I can suggest you while iterating the elements do the following
if the form has another element with the same name, and it is not check box, skip it.
this way you can just collect the correct fields.
I am most certainly sure that you can handle this with JQUERY, if not, post a JSFIDDLE so we can help you.
Razor syntax always creates a hidden field for radio button & checkbox. You can change your :input selector to :input:checkbox to do your task.
var ajaxData = $('.control_group').map(function (i, group) {
var data = {};
$(group).find(':input:checkbox').each(function () {
data[this.name] = this.value;
});
return data;
}).get();
ajaxData = JSON.stringify({ 'ajaxData': ajaxData });
console.log(ajaxData);

ASP.NET MVC - How to "reverse" model binding to convert a C# model back to a query string representation

I have a custom javascript on the client side that I use to build up a querystring and pass over to my asp.net-mvc controller
var templateQueryString = BuildTemplate();
$.ajax({
url: '/MyController/Save?' + templateQueryString,
type: 'post',
dataType: 'json',
success: function (data) {
}
}
and on my controller all of the properties leverage the model binding so it comes in as a single object on the server side. NOTE: that this is a pretty complex object with arrays and arrays of sub objects:
public ActionResult Save(MyTemplate template)
{
}
the issue now is that I need to be able to convert from my C# object back to a string that represents "myTemplateQueryString" on the client side.
Is there any recommended way to take an object and do the "reverse" model binding. They key here is that it generates a string that I could use as a query string again in the future to pass into another asp.ent-mvc controller action.
Here is an example of the querystring that I am storing locally:
<input type="hidden" value="showIds=false&showRisks=false&
amp;statusIds=2&statusIds=1&statusIds=6&statusIds=8&
amp;statusIds=3&statusIds=9&showCompleted=0"
name="filterQueryString" id="filterQueryString">
As #haim770 said it would be easier if you used JSON in the request payload, and not the query string to pass your complex object to the server.
Regarding creating the query string from a model there is not a built-in method that does something like that or any recommended approach as far as i know. An obvious solution is to use reflection and build the query string from your properties.
Assuming your BuildTemplate class looks something like:
public class BuildTemplate
{
public bool ShowIds { get; set; }
public bool ShowRisks { get; set; }
public bool ShowCompleted { get; set; }
public int[] StatusIds { get; set; }
}
You can develop an extension method to convert any object to a QueryString. Here is some initial code you can start with:
public static class ObjectExtensions
{
public static string ToQueryString(this Object obj)
{
var keyPairs = obj.GetType().GetProperties().Select(p =>
new KeyValuePair<string, object>(p.Name.ToLower(), p.GetValue(obj, null)));
var arr = new List<string>();
foreach (var item in keyPairs)
{
if (item.Value is IEnumerable && !(item.Value is String))
{
foreach (var arrayItem in (item.Value as IEnumerable))
{
arr.Add(String.Format("{0}={1}", item.Key, arrayItem.ToString().ToLower()));
}
}
else
arr.Add(String.Format("{0}={1}", item.Key, item.Value.ToString().ToLower()));
}
return "?" + String.Join("&", arr);
}
}
Then you can easily invoke this code on any object to generate a query string:
var person = new BuildTemplate() { StatusIds = new []{ 1, 5, 8, 9 }, ShowRisks = true };
var queryString = person.ToQueryString();
This would generate a query string like:
"?showids=false&showrisks=true&showcompleted=false&statusids=1&statusids=5&statusids=8&statusids=9"
This query string should work just fine with the default model binder for the BuildTemplate class.

MVC , JS change value textboxfor not posting

I have this modelView
public class Ue
{ public class uEkranModel
{
public List<Grup> Grup = new List<Grup>();
private List<Secim> _secim;
public List<Secim> secim
{
get
{
if (_secim == null)
_secim = new List<Secim>();
return _secim;
}
set { _secim = value; }
}
}
public class Secim
{
public Guid uGuid { get; set; }
public Guid fGuid { get; set; }
}
}
I need to fell the List secims items with JS and post it back to controller.
I have tried to :
1)initialize the list in the controller :
Controller :
gidecek.Data = new Models.Ucak.UcakDeneme.uEkranModel();
gidecek.Data.secim.Add(new Models.Ue.Secim { uGuid = new Guid() });
gidecek.Data.secim.Add(new Models.Ue.Secim { uGuid = new Guid() });
View :
#using (Html.BeginForm("deneme", "U", FormMethod.Post, new { id = "secimTamam", style = "display:none" }))
{
#Html.EditorFor(m => m.Data.secim[0])
#Html.TextBoxFor(m => m.Data.secim[0].uGuid, new {id="gidis" })
}
JS :
$("#Data_secim_0__ucusGuid").attr("value", index);
This way , when the code is executed the value field of the textboxfor is changed(when the JS fired) but when I check the post data in controller , it is NULL.
also tried :
$("#Data_secim_0__ucusGuid").val(index);
which doesnt cahnge the value of teh textbox.
What I need is to fill the model values with js and post the form with js as well.(The data user is selecting is different, I am just posting back the GUID of the items within a form.)
2 possible issues. Your getter is initializing a new List<Secim>. Try initializing it in the constructor
public class uEkranModel
{
public uEkranModel()
{
secim = new List<Secim>();
}
public List<Secim> secim { get; set;}
....
}
Also I have seen other posts on SO indicating problems posting back GUID's (and one solution that was accepted was to use a view model with the GUID's converted to strings)

Categories

Resources