MVC Core - strange view rendering issue - asp.net-mvc

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);
}

Related

MVC Return Fields to Controller

I've got a controller to retrieve and return values for my drop down, and a second, that when an option from the dropdown is selected, uses the values (Title and ID) in an API Request.
Controllers
public ActionResult GetEpics()
{
//Code to retrieve list
Epics = new GetEpicsViewModel();
Epics.Epics = epicsList;
return View(Epics);
}
[HttpPost]
public ActionResult Build(GetEpicsViewModel epic)
{
GetEpicsViewModel epicTest = epic;
//API Request
return View();
}
This is displayed in my drop down list as below:
View
#using (Html.BeginForm("Build", "GetEpics", FormMethod.Post))
{
<label for="input_OutputType"> Process: #Html.DropDownListFor(model => model.Id, new SelectList(Model.Epics, "Id", "Title")) </label>
<input type="submit" value="Submit" />
}
This works fine, but how would I then go about passing both the Title and ID to my controller?
I can pass the ID through fine, but cant figure out how to pass the Title as well.
Screenshot
Models
public class DevOpsEpic
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
and
public class GetEpicsViewModel
{
public string Id { get; set; }
public string Title { get; set; }
public List<DevOpsEpic> Epics { get; set; }
}
Realise this is probably a really simple answer, but just cant figure it out!
You can use jQuery for that, so when your dropdown is changed, set title value in hidden file.
#using (Html.BeginForm("Build", "GetEpics", FormMethod.Post))
{
<label for="input_OutputType"> Process: #Html.DropDownListFor(model => model.Id, new SelectList(Model.Epics, "Id", "Title"),new { name = "Id" }) </label>
<input type="hidden" id="Title" name="Title" />
<input type="submit" value="Submit" />
}
$('#dropdownId').change(function(){
$('#Title').val($('#dropdownId option:selected').text());
});

SelectediAlertIndex from SelectTagHelper (apparently) not passed to Index.cshtml from LoadAlert(…) …RedirectToAction(“Index”,”Alerts”)

Struggling with getting the SelectedAlertIndex (int) from AspNetCore MVC view page:
#{
<h4>#Model.Alerts.Count Alerts</h4>
<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>
}
thru AlertsController.cs:
[HttpPost]
public IActionResult LoadAlert(Alert obj, string LoadAlert)
{
if (!string.IsNullOrEmpty(LoadAlert))
{
ViewBag.Message = "Alert loaded successfully";
}
return RedirectToAction("Index", "Alerts")
}
to #foreach loop inside table in Views/Alerts/Index.cshtml:
#model IEnumerable<edxl_cap_v1_2.Models.ContentViewModels.Alert>
…
<table id="elementTable" class="smallText">
#foreach (var item in Model)
{
#*#while(item.AlertIndex == item.SelectedAlertIndex)
{*#
<tr>
Without the nested #while loop, the table of rows, one for each data element, displays all three records of 13 data elements each, but what I want is to display the set of 13 data elements for the one selected record using the int value of the AlertIndex selected from the Select Tag Helper dropdownlist. If I uncomment the #while loop only the border of the “elementTable” is displayed with no rows. This also happens when I try adding a “Where” clause to the #foreach loop:
#foreach (var item in Model.Where(item => item.AlertIndex == item.SelectedAlertIndex))
{
I’m trying to only show relevant code for the problem, but just in case it’s needed here’s the basic model:
public class AlertViewModel
{
public int SelectedAlertIndex { get; set; }
public List<SelectListItem> Alert_Identifiers { get; set; }
public List<AlertVm> Alerts { get; set; }
}
public class AlertVm
{
[Key]
public int AlertIndex { get; set; }
[MaxLength(150)]
public string Alert_Identifier { get; set; }
}
I decided it probably wasn’t needed to show the Alert class of 13 elements that I ended up adding to the view model, from the original Alert.cs class.
I needed to change value of asp-action in the form tag helper usage to Index:
#{
<h4>#Model.Alerts.Count Alerts</h4>
<form asp-controller="Alerts" asp-action="Index" method="post">
<select asp-for="SelectedAlertIndex" asp-items="#Model.Alert_Identifiers">
<option>Select one</option>
</select>
<br />
<input type="submit" name="Index" value="LoadAlert" />
</form>
}
Then I needed to make sure that SelectedAlertIndex was spelled the same in all instances; changed its type to Nullable int (int?); put a null check in the HttpPost Index() action; and add a Where condition:
[HttpPost]
public IActionResult Index(Alert obj, int? SelectedAlertIndex)
{
if (SelectedAlertIndex.HasValue)
{
ViewBag.Message = "Alert loaded successfully";
}
return View(_context.Alert.Where(x => x.AlertIndex == SelectedAlertIndex));
}
Many Thanks to #Shyju

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" />

Postback a single item on a Razor ListBoxFor

I have a ListBoxFor helper that is populated with users that have to be verified from a db. I click a single user and then click a submit button. This works fine and sets the admin verified bit in the db to true.
However what I am trying to do is on an item in the list being clicked, a value auto posted back and then I will fill a textarea with the users description. I gather I will use AJAX but have found it hard to get good documentation on using AJAX with HTMLHelpers in the this way.
EDIT: Updated the Model, View and Controller as per suggestions.
Model:
public class UserAdminVerifyModel
{
public SelectList ToBeVerifiedAdmin { get; set; }
public string[] SelectedUsers { get; set; }
public List<string> userdesc { get; set; }
}
Controller:
public ActionResult AdminVerifyListBox()
{
UserAdminVerifyModel verifusermodel = new UserAdminVerifyModel();
verifusermodel.ToBeVerifiedAdmin = GetUsersToBeVerified();
return View(verifusermodel);
}
View:
#using (Html.BeginForm("AdminVerifyListbox", "UserRegLog"))
{
#Html.ListBoxFor(x => x.SelectedUsers, Model.ToBeVerifiedAdmin)
<br />
<input type="submit" value="Submit" title="submit" />
}
}
ListBoxFor is used to generate a multiple selection list. This means that you should not be binding it to a simple string property. You should use an array of strings:
public class UserAdminVerifyModel
{
public SelectList ToBeVerifiedAdmin { get; set; }
public string[] SelectedUsers { get; set; }
public List<string> userdesc { get; set; }
}
and in the view bind the ListBoxFor to the SelectedUsers collection property:
#using (Html.BeginForm("AdminVerifyListbox", "UserRegLog"))
{
#Html.ListBoxFor(x => x.SelectedUsers, Model.ToBeVerifiedAdmin)
<br />
<input type="submit" value="Submit" title="submit" />
}
Also your ToBeVerifiedAdmin is already a SelectList. You should not be calling the constructor once again in your view. This should be done in your controller action which is responsible for populating this ToBeVerifiedAdmin property from wherever your information is stored.

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