BindProperty does not working for objects - forms

I have a form submission in place. I am binding four properties. One is a string, one is an integer, the other two are class objects. Here is my backend:
` [BindProperty]
public PortfolioModel Portfolio { get; set; }
[BindProperty]
public StockModel Stock { get; set; }
[BindProperty]
public string StockSymbol { get; set; }
[BindProperty]
public int QuantityToTrade { get; set; }`
These are all populated in OnGetAsync(). And they are correct during debug mode.
Here is my frontend:
` <form method="post">
<div>
Qty. <input type="text" name="QuantityToTrade" id="QuantityToTrade" />
<input type="hidden" asp-for="#Model.StockSymbol" />
<input type="hidden" asp-for="#Model.Stock" />
<input type="hidden" asp-for="#Model.Portfolio" />
<input class="btn-primary" type="submit" value="Buy" asp-page-handler="Buy" />
<input class="btn-primary" type="submit" value="Sell" asp-page-handler="Sell" />
</div>
</form>`
When I submit the form:
` public async Task<IActionResult> OnPostBuyAsync()
{
OrderModel orderPlaced = null;
if (QuantityToTrade > 0)
{
OrderModel orderModel = new OrderModel()
{
PortfolioId = Portfolio.Id,
StockId = Stock.Id,
StockSymbol = StockSymbol,
QuantityTraded = QuantityToTrade,
};
}
}`
The problem is, in OnPostBuyAsync(), both StockSymbol and QuantityToTrade are correct. But Portfolio and Stock have all 0's for the integer fields, and null for all other fields. Why are the class objects not posted correctly?
The most common answer for this problem on stackoverflow, is that the property setter was set to private. But in my case, they are all public...

Tag helper cannot bind the complex model with single input. You need specify all the property you want get in complex model.
For your scenario, it should be:
<div>
Qty. <input type="text" name="QuantityToTrade" id="QuantityToTrade" />
<input type="hidden" asp-for="#Model.StockSymbol" />
<input type="hidden" asp-for="#Model.Stock.Id" /> //specify the property
<input type="hidden" asp-for="#Model.Portfolio.Id" /> //specify the property
<input class="btn-primary" type="submit" value="Buy" asp-page-handler="Buy" />
<input class="btn-primary" type="submit" value="Sell" asp-page-handler="Sell" />
</div>

Related

Pass pre populated values to SQL Server database table (using Blazor)

I'm trying to make a booking function, where there are a set of buttons to click depending on for how long you want to book a room; 15 min, 30 min, 60 min.
I need the function to send a set of data items (like AppointmentStart, AppointmentEnd, RoomId etc.) to the data table upon a button click. I've tried to figure hidden fields out but getting nowhere.
The code for one of the buttons (which isn't working because I can't choose what to populate the fields with as I can't have both the #bind and a value="x"):
#if (#schedule.AppointmentDateStart >= ChooseTimeSlot.AddMinutes(15))
{
<div class="col">
<EditForm class="" Model="#schedule" OnValidSubmit="#ValidSubmit">
<button class="btn btn-primary form-control">15 min</button>
<input type="hidden" #bind="#schedule.Room.Id" class="form-control" />
<input type="hidden" #bind="#schedule.AppointmentHeading" class="form-control" />
<input type="hidden" #bind="#schedule.AppointmentDateStart" class="form-control" />
<input type="hidden" #bind="#schedule.AppointmentDateEnd" class="form-control" />
<input type="hidden" #bind="#schedule.BookerId" class="form-control" />
</EditForm>
</div>
}
#onclick=#( () => AppointmentChosen(15) )
from #MistMagoo got me back on track, so this is the solution:
<div class="col"><button class="btn btn-primary form-control" #onclick=#(() => TimeSlot(15, TRoomId))>15 min</button></div>
#code{
[Parameter] public int TRoomId { get; set; }
newSchedule = new Schedule();
{
var SlotAdd = RoundUp(DateTime.Now, TimeSpan.FromMinutes(NextTimeSlot));
newSchedule.RoomId = roomId;
newSchedule.AppointmentHeading = "Snabb";
newSchedule.AppointmentDateStart = DateTime.Now;
newSchedule.AppointmentDateEnd = SlotAdd;
newSchedule.BookerId = 1007;
newSchedule.IsConfirmed = true;
}
}

Issue with Custom TagHelper (Autocomplete) when used inside a List of Items (being displayed with EditorFor)

We have written a custom TagHelper to handle Autocomplete in a generic fashion. It has an asp-for attribute, which is defined as as ModelExpression variable.
The autocomplete TagHelper writes out a hidden field (the Id field), as well as an Input field for the autocomplete js code to work on. It ultimately saves the selected items Id value to the hidden field. This autocomplete works very well for multiple fields on a form.
But when the autocomplete TagHelper is incorporated in a list of items using an EditorTemplate to display all items the same (using EditorFor on a List in the model), then we need to set the Z index based name on the hidden field so it comes back to the controller as a List of the items. e.g. z0__*Field*, Z1__*Field*, ...
How do we get the Z index based name prefix that needs to be tacked on to the front of all the field names?
Do we have to make it up ourselves?
or do we extract from the ModelExpression somehow?
The Standard Input TagHelper is getting handled correctly?
Is the EditorFor/EditorTemplate the correct way to handle lists of editable objects in ASP.NET Core 1?
Autocomplete TagHelper
public class AutocompleteTagHelper : TagHelper
{
public ModelExpression AspFor { get; set; }
public ModelExpression AspValue { get; set; }
//public string AspFor { get; set; }
public string Route { get; set; }
public string RouteParameters { get; set; }
public string TargetWrapper { get; set; }
public string DisplayFormat { get; set; }
public string ValueFormat { get; set; }
public string ManageListCallback { get; set; }
public string ListWrapper { get; set; }
public string Placeholder { get; set; }
private SkillDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private IMemoryCache cache;
public AutocompleteTagHelper(SkillDbContext Context, UserManager<ApplicationUser> userManager, IMemoryCache cache)
{
_context = Context;
_userManager = userManager;
this.cache = cache;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var hiddenVal = "";
var displayVal = "";
//asp-for="LandingPointId"
//route="/Lookups/GetLandingPoint"
//route-parameter="SomeOtherId"
//target-wrapper="form" key="Id"
//label="{Name} ({Code})"
//output="{code}"
//AspFor.
//get parent model from AspFor
object thisModel = null;
//get value properties
if (AspValue != null)
{
hiddenVal = ValueFormat;
displayVal = DisplayFormat;
thisModel = AspValue.Model;
}
else if (AspFor.Model != null && !AspFor.Model.Equals((object)0))
{
Object Id = AspFor.Model;
string routeMethod = Route.Split('/').Last<string>();
}
if(thisModel != null)
{
PropertyInfo[] propertyInfo = thisModel.GetType().GetProperties();
foreach (var info in propertyInfo)
{
var val = info.GetValue(thisModel);
if (val != null)
{
hiddenVal = hiddenVal.Replace(("{" + info.Name + "}"), val.ToString());
displayVal = displayVal.Replace(("{" + info.Name + "}"), val.ToString());
}
}
}
var isAcList = ManageListCallback != null && ListWrapper != null;
string aspForName = AspFor.Name.Replace(".", "_");
output.TagName = "input"; // replaces <email> with <a> tag
inputId = inputName = aspForName;
output.Attributes["id"] = aspForName;
output.Attributes["name"] = aspForName;
output.Attributes["type"] = "text";
output.Attributes["route"] = Route;
output.Attributes["route-parameters"] = RouteParameters;
output.Attributes["target-wrapper"] = TargetWrapper;
output.Attributes["placeholder"] = Placeholder;
output.Attributes["value-format"] = ValueFormat;
output.Attributes["display-format"] = DisplayFormat;
output.Attributes["value"] = displayVal;
output.Attributes["class"] = "autocomplete form-control" + (isAcList?" hasList":"");
TagBuilder HiddenValue = new TagBuilder("input");
HiddenValue.Attributes["name"] = inputName;
HiddenValue.Attributes["id"] = inputId + "_hidden";
HiddenValue.Attributes["type"] = "hidden";
HiddenValue.Attributes["value"] = hiddenVal;
output.PreElement.SetContent(HiddenValue);
if (isAcList)
{
TagBuilder AddBtn = new TagBuilder("a");
AddBtn.Attributes["id"] = AspFor.Name.Replace(".", "_") + "_submit";
AddBtn.Attributes["class"] = "moana-autocomplete-list-manager disabled btn btn-primary";
AddBtn.Attributes["listwrapper"] = ListWrapper;
AddBtn.Attributes["href"] = ManageListCallback;
AddBtn.InnerHtml.AppendHtml("Add");
output.PostElement.SetContent(AddBtn);
}
}
This is the model
public class AddressEditorModel
{
public int Id { get; set; }
public string AddressLinkTo { get; set; }
public int AddressLink { get; set; }
public string AddressLine { get; set; }
public int ContactTypeId { get; set; }
public string Suburb { get; set; }
public string City { get; set; }
public string Postcode { get; set; }
public int? CountryId { get; set; }
public string ContactTypeName { get; set; }
public string CountryCode { get; set; }
public string CountryName { get; set; }
}
This is the cshtml
#model List<Skill.ViewModels.AddressEditorModel>
<div class="address-info-wrapper">
#Html.EditorFor(m => m)
<div>
This is the controller method call
public async Task<IActionResult> UpdateAddressInfo(List<AddressEditorModel> addresses)
and finally this is the EditorTemplate
#model Skill.ViewModels.AddressEditorModel
<input type="hidden" asp-for="Id" />
<input type="hidden" asp-for="ContactTypeId" />
<input type="hidden" asp-for="AddressLink" />
<input type="hidden" asp-for="AddressLinkTo" />
<input type="hidden" asp-for="CountryId" />
<label class="col-md-12 control-label" style="padding-bottom:20px;">#Model.ContactTypeName</label>
<div class="form-group">
<label asp-for="AddressLine" class="col-md-2 control-label">Address</label>
<div class="col-md-10">
<input asp-for="AddressLine" class="form-control" style="resize:both" />
<span asp-validation-for="AddressLine" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Suburb" class="col-md-2 control-label">Suburb</label>
<div class="col-md-10">
<input asp-for="Suburb" class="form-control" />
<span asp-validation-for="Suburb" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="City" class="col-md-2 control-label">City</label>
<div class="col-md-10">
<input asp-for="City" class="form-control" />
<span asp-validation-for="City" class="text-danger" />
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Country</label>
<div class="col-md-10">
<autocomplete asp-for="CountryId" route="/Lookups/GetCountry" target-wrapper="form" display-format="{Name} ({Code})" value-format="{Id}"></autocomplete>
<span asp-validation-for="CountryId" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Postcode" class="col-md-2 control-label">Post Code</label>
<div class="col-md-10">
<input asp-for="Postcode" class="form-control" />
<span asp-validation-for="Postcode" class="text-danger" />
</div>
</div>
Note that the autocomplete tag above in the EditorTemplate generates its own internal tags (as part of the tag helper).
This is a portion of the page for the first Address Information (as shown in firefox)
<input id="z0__Id" type="hidden" value="5" name="[0].Id" data-val-required="The Id field is required." data-val="true">
<input id="z0__ContactTypeId" type="hidden" value="1" name="[0].ContactTypeId" data-val-required="The ContactTypeId field is required." data-val="true">
<input id="z0__AddressLink" type="hidden" value="1" name="[0].AddressLink" data-val-required="The AddressLink field is required." data-val="true">
<input id="z0__AddressLinkTo" type="hidden" value="F" name="[0].AddressLinkTo">
<label class="col-md-12 control-label" style="padding-bottom:20px;">Work</label>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__AddressLine">Address</label>
<div class="col-md-10">
<input id="z0__AddressLine" class="form-control" type="text" value="4a Lansdowne Street" name="[0].AddressLine" style="resize:both">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].AddressLine"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__Suburb">Suburb</label>
<div class="col-md-10">
<input id="z0__Suburb" class="form-control" type="text" value="Bayswater" name="[0].Suburb">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].Suburb"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__City">City</label>
<div class="col-md-10">
<input id="z0__City" class="form-control" type="text" value="Auckland" name="[0].City">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].City"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Country</label>
<div class="col-md-10">
<input id="CountryId_hidden" type="hidden" value="1" name="CountryId">
<input id="CountryId" class="moana-autocomplete form-control ui-autocomplete-input" type="text" value="New Zealand (NZ)" display-format="{Name} ({Code})" value-format="{Id}" placeholder="" target-wrapper="form" route-parameters="" route="/Lookups/GetCountry" name="CountryId" autocomplete="off">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].CountryId"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__Postcode">Post Code</label>
<div class="col-md-10">
<input id="z0__Postcode" class="form-control" type="text" value="0604" name="[0].Postcode">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].Postcode"> </span>
</div>
</div>
Note that the Html.EditorFor generates the Zn__fieldname prefixes to the input name attribute as well as an [n].fieldname name for the input id attribute
The issue is how to access the index value, or get this prefix to tack onto our generated inputs from inside the TagHelper i.e. the Zn__* or [n] value, which is essentially the indexor of the EditorFor as it generates the repeated fields
Thanks for any help
Ok I have been working diligently on this.
I eventually had to look at the TagHelpers from the Asp.Net core source project.
I found that we need to access the ViewContext.ViewData.TemplateInfo;
from inside the TagHelper.
Or alternately we could use the IHtmlGenerator object - calling a GenerateTextBox, GenerateHidden, etc to build the TageHelpers as required

Getting CheckList data in MVC6 post

I am doing user roles in MVC6 (using EF7), and I am think I am just missing something, but on a form used to define a user roles I get nothing in the posted back Model to the controller
This is my Model
public class UserRoleItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IdentityUserRole<int> userRole { get; set; }
public bool HasRole { get; set; }
}
public class UsersRolesViewModel
{
public int UserId { get; set; }
public string FullName { get; internal set; }
public List<UserRoleItem> UserRoles;
}
This is the view
#model Skill.ViewModels.Manage.UsersRolesViewModel
<br />
<h3>Set Roles for user - #Model.FullName</h3>
<form asp-action="ChangeUsersRoles" >
<div class="form-horizontal">
<hr />
<input type="hidden" asp-for="UserId" />
#foreach (var userRole in Model.UserRoles)
{
<input type="hidden" asp-for="#userRole.Id" />
<div class="form-group">
<div class="inline-block col-md-8">
<span class="col-md-1" align="center">
<input type="checkbox" asp-for="#userRole.HasRole" />
</span>
<div class="col-md-7">
#userRole.Name (#userRole.Description)
</div>
</div>
</div>
}
<hr />
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
This is the Controller
// GET: Users/ChangeUserRoles/5
public async Task<IActionResult> ChangeUserRoles(int? id)
{
if (id == null)
{
return HttpNotFound();
}
var model = new UsersRolesViewModel();
ApplicationUser appUser = await _context.ApplicationUsers.SingleAsync(m => m.Id == id);
if (appUser == null)
{
return HttpNotFound();
}
else
{
model.UserId = (int)id;
model.FullName = appUser.FullName;
var some = from r in _context.Roles
from ur in _context.UserRoles
.Where(inner => r.Id == inner.RoleId && inner.UserId == id)
.DefaultIfEmpty()
select new UserRoleItem
{
Id = (int)r.Id,
Name = r.Name,
Description = r.NormalizedName,
userRole = ur, // this is needed else it has a hissy fit
HasRole = (ur != null)
};
// get all of the Roles and then also link to the ones the user currently has
model.UserRoles = (some).ToList();
}
return View(model);
}
// POST: Users/ChangeUserRoles/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangeUserRoles([Bind(include:"UserId,UserRoles")]UsersRolesViewModel userModel)
{
if (ModelState.IsValid)
{
// update based on the changes
// return RedirectToAction("Edit", new { userModel.UserId });
}
return View(userModel);
}
So when I get the post back on Save, the UserRoles list is null, so I assume I am just missing an obvious thing here?
Also another small issue is the EF Linq statement. If I remove the
userRole = ur,
statement from the Select portion of the Linq query, the system has a fit and says my schema is out of date (which it isn't). I think it is due to the following statement where I am testing the outer join value
HasRole = (ur != null)
Although this seems perfectly reasonable and works if the ur variable is used prior to testing for null (or not)
After entering this question - I further investigated the issue and found that I could do what I needed using an EditorTemplate
So I created the EditorTemplate Folder under my Users View folder as it was a UsersController and then added the following UserRoleItem.cshtml file
#model UserRoleItem
<input type="hidden" asp-for="Id" />
<div class="form-group">
<div class="inline-block col-md-8">
<span class="col-md-1" align="center">
<input type="checkbox" asp-for="HasRole" />
</span>
<div class="col-md-7">
#Model.Name (#Model.Description)
</div>
</div>
</div>
I then changed the view (called ChangeUsersRole.cshtml) to be
#model Skill.ViewModels.Manage.UsersRolesViewModel
<br />
<h3>Set Roles for user - #Model.FullName</h3>
<hr />
<form asp-action="ChangeUsersRoles">
<div class="form-horizontal">
<input type="hidden" asp-for="UserId" />
#Html.EditorFor(m => m.UserRoles)
<hr />
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
Note also: that I tried using the new format
<input asp-for="UserRoles" />
instead of this line in the view
#Html.EditorFor(m => m.UserRoles)
But it did not work as expected. Again maybe I am still doing something wrong - or maybe that feature isn't working yet?

How to pass data from a form in a view to another controller with C# MVC?

If I have a form that needs to use a textbox input like as below:
#{
if(IsPost){
username = Request.Form["username"]
}
}
<form action="Home/Index" method="post">
<input type="text" name="username" />
<input type="submit" value="submit" />
</form>
The controller is something like below,
public class HomeController : Controller
{
public ActionResult Index (string username) {
if (string username != string.Empty)
{
Console.WriteLine("Your username is " + username);
}
return View();
}
}
Seems like the data is not being passed from the post method. When I hit submit the URL that it requests is Home/Home/Index, which is not were the controller(HomeController) action is located, it should be Home/Index, and use the HomeController right?
What if I need to pass this data to a different controller that has an Index for the action, like UserController?
In this instance, you're missing a slash!
<form action="/Home/Index" method="post">
<input type="text" name="username" />
<input type="submit" value="submit" />
</form>
But when using asp.net mvc, you should use Url.Content with the home directory character to ensure that if your site is deployed in a sub-directory of the site, the correct root will be found. So use:
<form action="#Url.Content("~/Home/Index")" method="post">
<input type="text" name="username" />
<input type="submit" value="submit" />
</form>

Post complex model to controller MVC using Facebook application template C#

I have following model:
class HomePageModel
{
public User user { get; set; }
public ResultHistory resultHistory { get; set; }
public Option option { get; set; }
public HomePageModel()
{
}
}
Code from controller when passing it into view:
[FacebookAuthorize]
public async Task<ActionResult> Index(Context context)
{
ViewBag.AppUrl = GlobalFacebookConfiguration.Configuration.AppUrl;
if (ModelState.IsValid)
{
var user = await context.Client.GetCurrentUserAsync<MyAppUser>();
var option = new Option();
//CODE for reading user from db, simpified
Users dbUser = db.Get(context);
var resultHistory = new ResultHistory(dbUser.NSPGW, dbUser.NSPGL,
dbUser.NMPGW,dbUser.NMPGL,dbUser.NDPGW, dbUser.NDPGL);
HomepageModel homepageModel = new HomepageModel();
homepageModel.option = option;
homepageModel.resultHistory = resultHistory;
homepageModel.user = user;
return View(homepageModel);
}
}
Code from controller
#using (Html.BeginForm("Start", "Home", FormMethod.Post, new { id="opcijeForm"}))
{
<div class="row">
<div class="col-md-4">
<div class="radio">
<label>
<input type="radio" name="radio" value="sp" />SP
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="radio" value="mp" />MP
</label>
</div>
<div>
<input type="submit" value="start" />
</div>
</div>
<div class="col-md-8">
<div id="spoption">
#Html.DropDownListFor(m => m.option.sl, Model.option.slSelectList)
<label style="padding-left:10px">
<input type="checkbox" id="bb" /> BB
</label>
#Html.DropDownListFor(m => m.option.swt, Model.option.swtSelectList, new {id="bbdd" })
<label style="padding-left:10px">
<input type="checkbox" id="ziv" /> ziv
</label>
#* #Html.DropDownListFor(m => m.gameOption.sln, Model.option.slnSelectList, new { id="zivdd"})*#
</div>
<div id="mpoption">
<label>Numer of oppointment</label>
#Html.DropDownListFor(m=>m.option.spn, Model.option.spnSelectList)
#Html.DropDownListFor(m=>m.option.smv, Model.option.smvSelectList)
</div>
</div>
</div>
}
and receiving model on post
[FacebookAuthorize]
[HttpPost]
public ActionResult Start(HomepageModel model)
{
//Some code
//here is for example model.option null
}
When I submit form with above model, in my controller action where I receive same model, all it's properties are null.Each class that is within HomePage has only string and int as properties. If I move something, for example from class Option and put it in HomePageModel direct, then properties I have moved are not null. Is there a way to post model as above, or should I just copy properties from these three class into HomePageModel class?
[EDIT]
It is because of meta-data FacebookAuthorize, when I remove it everything is working
MVC app facebook passing object from view to controller at submit