Auto generate hidden inputs for all viewModel fields - asp.net-mvc

Is it real to auto generate inputs for all hidden fields. I want something like this extestion method Html.AutoGenerateHiddenFor(viewmodel)
And output:
<input type="hidden" name="field1" value="123" />
<input type="hidden" name="field2" value="1234" />
<input type="hidden" name="field3" value="1235" />

You could use the MvcContrib's Html.Serialize method:
#using (Html.BeginForm())
{
#Html.Serialize(Model)
<button type="submit">OK</button>
}
and then inside your controller action that is receiving the postback:
[HttpPost]
public ActionResult SomeAction([Deserialize] MyViewModel model)
{
...
}
It uses classic WebForms's ViewState to serialize the model and emits a single hidden input field which will contain the serialized model. It kinda emulates the legacy ViewState.
An alternative solution would be to persist your model to your backend and then simply have a single hidden input field inside your form containing an unique id that will allow to retrieve the model back from this backend.

public class ModelToPersistBetweenFormSubmits()
{
public string field1 { get; set;}
public string field2 { get; set;}
public string field3 { get; set;}
public string field4 { get; set;}
public string GetHiddenFields(string excludeFields = "")
{
string[] excludeFieldList = excludeFields.Split(',');
string val = string.Empty;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (System.Reflection.PropertyInfo property in this.GetType().GetProperties())
{
if (excludeFieldList.Contains(property.Name))
{
continue;
}
else if (property.GetIndexParameters().Length > 0)
{
val = string.Empty; //sb.Append("Indexed Property cannot be used");
}
else
{
val = (property.GetValue(this, null) ?? "").ToString();
}
sb.Append(string.Format("<input type='hidden' id='{0}' name='{0}' value='{1}'/>", property.Name, val));
sb.Append(System.Environment.NewLine);
}
return sb.ToString();
}
}
//render hidden fields except for current inputs
#Html.Raw(Model.GetHiddenFields("field4,field3"))

Related

How to Submit Value, but Receive Key from Input field ASP.NET Core MVC

I have an id received in my action parameter. From where I got my scheme details. By this details I found my desired scheme name. And now I want to show this name in my .cshtml as an input field where I want to show the value in "read-only" format (not changeable). So in my controller I sent it inside a ViewData instead of SelectList as shown below:
ViewData["SchemeNum"] = schemeInfo.SchemeNum;
instead of
ViewData["SchemeInfoId"] = new SelectList(_context.PackagewiseSchemeInfo.Where(p => p.Id == id), "Id", "SchemeNum", schemeInfo.SchemeNum);
I know if I use a selectlist, it would have been easier for me to catch the Key after form submitting like mentioned above -- "Id", "SchemeNum" ...
HTML:
<input class="form-control" value="#ViewBag.SchemeNum" readonly />
Now, I'm getting SchemeNum instead of Id after form submission. I want to know how to catch the KEY instead of value by an input tag? Please help.
You probably wanna disable your input add a hidden field for the id.
<input class="form-control" value="#ViewBag.SchemeNum" disabled />
<input type="hidden" value="#ViewBag.SchemeId" />
Besides,you could custom Model Binder to get the matched key of the value.
Here is a working demo like below:
Model:
public class PackagewiseSchemeInfo
{
public int Id { get; set; }
public string SchemeNum { get; set; }
}
View:
#model PackagewiseSchemeInfo
<form asp-action="Test">
<input asp-for="SchemeNum" class="form-control" value="#ViewBag.SchemeNum" readonly />
<input type="submit" value="create" />
</form>
Custom Model Binder:
public class CustomModelBinder : IModelBinder
{
private readonly YourDbContext _context;
public CustomModelBinder(YourDbContext context)
{
_context = context;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var data = bindingContext.ActionContext.HttpContext.Request.Form["SchemeNum"];
var model = _context.PackagewiseSchemeInfo.Where(a => a.SchemeNum == data.ToString())
.Select(a=>a.Id).FirstOrDefault();
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Controller:
public IActionResult Index()
{
var schemeInfo = new PackagewiseSchemeInfo()
{
Id = 1,
SchemeNum = "aaa"
};
ViewData["SchemeNum"] = schemeInfo.SchemeNum;
return View();
}
[HttpPost]
public IActionResult Test([ModelBinder(typeof(CustomModelBinder))]int Id)
{
//do your stuff...
}
Result:

MVC Core - strange view rendering issue

I am using MVC to display a simple form in a view:
ViewModel:
public class CreateSaleViewModel
{
public string OrderId { get; set; }
public decimal TotalAmount { get; set; }
public bool ShowInstoreConfirmDetails { get; set; }
}
Controller action:
[HttpGet]
public IActionResult CreateSale()
{
return View(new CreateSaleViewModel());
}
View:
#model CreateSaleViewModel
<form asp-controller="Sales" asp-action="CreateSale" method="post">
<input asp-for="OrderId" />
<input asp-for="TotalAmount" />
<button type="submit" name="CreateSale" id="CreateSale">
button
</button>
</form>
I then post to a new view, where the same details need to be entered. To do this I store the old values in hidden inputs and provide another form to re-enter the details.
ViewModel:
public class ConfirmDetailsViewModel
{
public string OrderId { get; set; }
public decimal TotalAmount { get; set; }
public string ConfirmOrderId { get; set; }
public decimal ConfirmTotalAmount { get; set; }
}
Controller:
[HttpPost("Confirmdetails")]
public IActionResult ConfirmDetails(CreateSaleViewModel model)
{
var viewModel = new ConfirmDetailsViewModel
{
ConfirmOrderId = model.OrderId,
ConfirmTotalAmount = model.TotalAmount,
OrderId = string.Empty,
TotalAmount = 0.0m
};
return View("ConfirmDetails", viewModel);
}
View:
#model ConfirmDetailsViewModel
<form asp-controller="Sales" asp-action="Summary" method="post">
<input type="hidden" value="#Model.ConfirmOrderId" id="OrderIdConfirm" />
<input type="hidden" value="#Model.ConfirmTotalAmount" id="TotalAmountConfirm" />
<input type="hidden" value="#Model.OrderId" id="banana" />
<input asp-for="OrderId" />
<input asp-for="TotalAmount" />
<button type="submit" name="CreateSale" id="CreateSale">
button
</button>
</form>
My problem is on the confirmdetails view orderId and TotalAmount retain the values that were posted from the previous page.
I have debugged the controller and can see the ConfirmOrderId and ConfirmTotalAmount properties have the correct values, and also OrderId and TotalAmount are empty strign and 0 respectively.
Even stranger is that
<input type="hidden" value="#Model.OrderId" id="banana" />
Has the correct value of "".
Does anyone know what is causing this issue?
MVC stores the posted back values in ModelState.
These values are used by default in #Html helpers - as a convenience. This allows the values of hidden form fields to be preserved through postbacks, even if they don't have properties in the view-model.
Unfortunately what is usually a convenience turns into a headache, if you try to modify the model's properties within the action. Helpers take their values from ModelState, ignoring the updated view-model.
To solve this, call ModelState.Clear()
removes all the posted back values from ModelState
the helpers will now use the values from the view-model.
Controller:
[HttpPost]
public IActionResult ConfirmDetails(CreateSaleViewModel model)
{
var viewModel = new ConfirmDetailsViewModel
{
ConfirmOrderId = model.OrderId,
...
};
ModelState.Clear(); // force asp-helpers to use the updated model's values
return View("ConfirmDetails", viewModel);
}

BeginForm in ChildAction uses wrong id

There is something simple I don't understand with ChildActions.
I've created a simple View for a model, that loads a child action with a form.
The child action has another model than its parent, with a different id property.
Html.HiddenFor(m => m.Id) still outputs the parents id, although #Model.id outputs the correct value!
Can't I reliably use the Helper methods in ChildActions, or is this a known bug?
HomeController
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new Models.HomeModel { id = 1, message = "bugmodel" };
return View(model);
}
[HttpGet]
[ChildActionOnly]
public ActionResult Child(int id)
{
var model = new Models.HomeChildModel { id = 100, parentId = id, childMessage = "My Child message" };
return PartialView(model);
}
[HttpPost]
[ActionName("Child")]
[ValidateAntiForgeryToken()]
public ActionResult ChildPost(Models.HomeChildModel model)
{
return RedirectToAction("Index");
}
}
Models
public class HomeModel
{
public int id { get; set; }
public string message { get; set; }
}
public class HomeChildModel
{
public int id { get; set; }
public int parentId { get; set; }
public string childMessage { get; set; }
}
Home view
#model ChildActionBug.Models.HomeModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.DisplayFor(m=>m.id)
#Html.DisplayFor(m=>m.message)
#Html.Action("Child", new { id = Model.id })
**Child view**
#model ChildActionBug.Models.HomeChildModel
<h3>Child here</h3>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m=>m.id)
#Html.HiddenFor(m=>m.parentId)
#Html.EditorFor(m=>m.childMessage)
<div>Child Model ID: #Model.id</div>
<button type="submit">Save</button>
}
Based on the answer given in the SO question I posted in the comment, you're better off explicitly creating the hidden fields
ASP.Net MVC Html.HiddenFor with wrong value
That's normal and it is how HTML helpers work. They first use the
value of the POST request and after that the value in the model. This
means that even if you modify the value of the model in your
controller action if there is the same variable in the POST request
your modification will be ignored and the POSTed value will be used.
So instead, hand craft the hidden fields:
<input type="hidden" name="Id" value="#Model.Id" />
<input type="hidden" name="ParentId" value="#Model.ParentId" />
<input type="hidden" name="ChildMessage" value="#Model.ChildMessage" />

MVC Binding Dictionary

I am following this tutorial. Can anyone please explain to me how I can make the textboxes bind back to the Dictionary? Right now all that happens is the data is being displayed in the textboxes - but if I change the textboxes, how do I bind back to the object?. What am I missing? Below is my code:
<input type="text" name="#Model.ExpirimentToRemove[i].Value.Title" value="#Model.ExpirimentToRemove[i].Value.Title"/>
<input type="text" name="#Model.ExpirimentToRemove[i].Value.PreviewDescription" value="#Model.ExpirimentToRemove[i].Value.PreviewDescription"/>
<input type="text" name="#Model.ExpirimentToRemove[i].Value.FullDescription" value="#Model.ExpirimentToRemove[i].Value.FullDescription"/>
The name of your text input field is wrong. You have put the value of the model instead of naming it appropriately as explained in the Hanselman's blog post.
So let's assume that you have some view model:
public class ItemViewModel
{
public string Title { get; set; }
public string PreviewDescription { get; set; }
public string FullDescription { get; set; }
}
and a main view model containing the dictionary:
public class MyViewModel
{
public Dictionary<string, ItemViewModel> ExpirimentToRemove { get; set; }
}
and your POST controller action takes this view model as parameter:
[HttpPost]
public ActionResult Remove(MyViewModel model)
{
...
}
In order to correctly bind to this view model you could have the following:
#model MyViewModel
#using (Html.BeginForm())
{
#for (var i = 0; i < Model.ExpirimentToRemove.Count; i++)
{
<div>
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Key",
Model.ExpirimentToRemove[i].Key
)
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Value.Title",
Model.ExpirimentToRemove[i].Value.Title
)
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Value.PreviewDescription",
Model.ExpirimentToRemove[i].Value.PreviewDescription
)
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Value.FullDescription",
Model.ExpirimentToRemove[i].Value.FullDescription
)
</div>
}
<p><button type="submit">OK</button></p>
}
In this example the key of the dictionary is a simple string value but you could also use a complex type.

MVC Radio Button Lists are not grouped when using the HtmlHelper class

Having trouble creating a list of radio buttons that are grouped together, in MVC 3 specifically, but this also applies to MVC 2.
The problem arises when radio buttons are generated using Html helpers and the model is part of an array.
Here is the cut down version of my code.
public class CollectionOfStuff {
public MVCModel[] Things { get; set }
}
/*This model is larger and represents a Person*/
public class MVCModel {
[UIHint("Hidden")]
public string Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
/*Assigned to new CollectionOfStuff property Things*/
var items = new[] {
new MVCModel() { Id="0" Name = "Name here" }, new MVCModel() { Id="1" Name = "Name there" }
}
My parent view
#model CollectionOfStuff
#for (int i = 0; i < Model.Things.Length; i++) {
#Html.EditorFor(m => m.Things[i]);
}
My view rendering individual MVCModel objects
#Model MVCModel
#{
var attr = new {
Checked = Model.IsSelected ? "checked=checked" : ""
};
}
#Html.RadioButtonFor(model => model, Model.Id, attr)
Produces this output:
<input type="radio" value="0" name="MVCModel[0]" id="MVCModel_0_" data-val-required="You need to choose" data-val="true" />
<input type="radio" value="1" name="MVCModel[1]" id="MVCModel_1_" data-val-required="You need to choose" data-val="true" />
The radio buttons are not grouped, however it has the obvious advantage of writing out the meta data for validation.
The other way is by calling:
#Html.RadioButton(name: "GroupName", value: Model.Id, isChecked: Model.IsSelected)
Produces:
<input type="radio" value="0" name="MVCModel[0].GroupName" id="MVCModel_0__GroupName">
<input type="radio" value="1" name="MVCModel[1].GroupName" id="MVCModel_1__GroupName">
Again, this doesn't produce the desired result. It's also missing the validation meta data.
Another other option is creating a custom template, but the problem with this approach is that all the meta data required for validation is not present.
Any ideas on how I can create grouped radio buttons or obtain meta data so I can create a template myself?
You haven't shown how does your view model look like but you could group them by some property. So let's take an example:
public class MyViewModel
{
[Required]
public string SomeProperty { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View:
#model AppName.Models.MyViewModel
#using (Html.BeginForm())
{
<div>A: #Html.RadioButtonFor(x => x.SomeProperty, "a")</div>
<div>B: #Html.RadioButtonFor(x => x.SomeProperty, "b")</div>
#Html.ValidationMessageFor(x => x.SomeProperty)
<input type="submit" value="OK" />
}
Now if you want to preselect some radio simply set the property of the view model to the corresponding value of the radio instead of writing some ugly C# code in your views:
public ActionResult Index()
{
var model = new MyViewModel
{
SomeProperty = "a" // select the first radio
};
return View(model);
}
Obviously this technique works with any simple property type (not only strings) and with any number of radio buttons that could be associated to this property.

Resources