I'm building a fruit and vegetables e-commerce app. I have made a system for users to add items to cart by entering values in an <input type="number"> field, within a form.
The problem I'm having is that when I enter "1.2" for example, my server receives the value "12". But when I enter the value "1", my server just receives the value "1". This really bugs me. Does anyone know why this happens?
Thanks in advance.
Some data for context: my backend is build in Asp.Net Core 3.x. The backend endpoint looks like this:
[HttpPost]
public ActionResult UpdateShoppingCart(int productId, float amount)
{
Console.WriteLine(amount);
var selectedProduct = _productRepository.Products.SingleOrDefault(p => p.Id == productId);
float productCount = 0;
if(selectedProduct != null)
{
productCount = _shoppingCart.UpdateCart(selectedProduct, realAmount);
}
return Redirect(Request.Headers["Referer"].ToString());
}
The html part looks like this
<div class="text-center">
<div class="float-left">
<p>Cantidad:</p>
</div>
<div class="float-right">
<form method="post" class="form-inline" asp-controller="ShoppingCart" asp-action="UpdateShoppingCart">
<input type="hidden" name="productId" value="#Model.Product.Id"/>
<input name="amount" type="number" class="form-control h-auto input-add-to-cart" data-id="#Model.Product.Id" min=0 step="0.1" value="#(Model.ShoppingCartItem != null ? (Model.ShoppingCartItem.Amount).ToString(new CultureInfo("en-US")) : (0).ToString())"/>
<button type="submit" id="update-button-#Model.Product.Id" disabled class="ml-1 btn btn-sm btn-outline-warning"><i class="material-icons material-icons-actual-font" disabled>shopping_cart</i>Actualizar</button>
</form>
</div>
</div>
UPDATE:
The code for the ShoppingCartItem model is the following:
public class ShoppingCartItem
{
public int Id { get; set; }
public Product Product { get; set; }
public int ProductId { get; set; }
public float Amount { get; set; }
public string ShoppingCartId { get; set; }
}
SECOND UPDATE:
Could it be that i'm setting a culture in the thread that uses the comma as a dot?
In the Configure method of Startup, i'm setting
var cultureInfo = new CultureInfo("es-CL");
cultureInfo.NumberFormat.CurrencySymbol = "$";
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
Ok, that was it.
The problem was that my Browser was setting the decimal separator as a dot (for example, 4.7) and my backend was expecting a comma (for example, 4,7) so that confused my application.
The culprit was what was posted on my second update, that was
var cultureInfo = new CultureInfo("es-CL");
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
Related
Under ASP we could bound a control with a model which has member
public string Contact { get; set; }
or directly <input type="email" asp-for="item.Contact"> or through corresponding HTML helper
As well we could use Data Annotation instead of implicitly declare type in Razor page
[EmailAddress]
public string Contact { get; set; }
But what to do if I would like to enter the list of email addresses separated by comma?
It is correct that unbounded HTML5 code <input type="email" multiple> works under latest browsers:
Multiple attribute for type="email" does not work. But when I am trying to bound it to the model it looks like EmailAddressAttribute is applied to the model and only one email address could be validated
Like #pcalkins said, the browsers will not separate it for you, you have to implement some split emails functionality like so
// 1. put this helper in your utilty class
private IEnumerable<string> GetEmails(string input)
{
if (String.IsNullOrWhiteSpace(input)) yield break;
MatchCollection matches = Regex.Matches(input, #"[^\s<]+#[^\s,>]+");
foreach (Match match in matches) yield return match.Value;
}
// 2. now call it to get list of emails
// for e.g. string strEmails = "Last, First <name#domain.com>, name#domain.com, First Last <name#domain.com>..";
string allContactsWithCommas = model.contactsWithCommas;
IEnumerable<string> emails = GetEmails(allContactsWithCommas );
// 3. try to give it something custom to validate
//[Required, MinLength(1, ErrorMessage = "Some validation error")]
[YourClassHoldingObject]
public List<int> Contact { get; set; }
// 4. or implement something custom in for your validation object, so the broswer knows how to handle/waht to call for validation
public class YourClassHoldingObject : IValidatableObject
{
[Required]
List<int> Contact
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// your contact logic validation here
if (Contact.Count < 1)
{
// some validation using System.ComponentModel.DataAnnotations
// - please customize for your needs
yield return new ValidationResult(
$"At least one email should be specified.", new[]
{ System.ComponentModel.DataAnnotations.EmailAddressAttribute().IsValid(
// for e.g. "email#istart.work") });
Contact) });
}
}
}
I have been searching online for an answer to this specific issue but I can't seem to find it.
I am currently creating a form using Razor pages and cannot find out how to create a form input that would be able to take multiple values for one item in the form. Below I will post a quick example of what I mean.
Current issues: when I add another input programmatically it will only add 2 maximum and will not send a value
Model:
public class FormInput
{
public List<Address> Addresses { get; set; }
public List<Category> Categories { get; set; }
}
Razor Page:
public class FormPage : PageModel
{
[BindProperty] public FormInput _Input { get; set; }
}
HTML Page:
<form>
<ul class="Category-Container">
<li>
<input asp-for="_Input.Addresses" type="text" />
<button type="button" onclick="this.AddCategory">
Add New Address
</button>
</li>
</ul>
<div>
<input asp-for="_Input.Categories" type="text" />
<button type="button" onclick="this.AddNewInput">
Add New Category
</button>
</div>
</form>
Javascript:
var categoryContainer = document.getElementById("Category-Container");
function AddCategory() {
var input = document.createElement("input");
input.classList.add("w-100");
input.name = "BusinessCategory";
var inputCol = document.createElement("div");
inputCol.classList.add("col-8");
inputCol.appendChild(input);
var btn = document.createElement("button");
btn.classList.add("btn");
btn.classList.add("btn-primary");
btn.innerText = "Add New Category";
var btnCol = document.createElement("div");
btnCol.classList.add("col");
btnCol.appendChild(btn);
var row = document.createElement("li");
row.classList.add("row");
var part1 = row.appendChild(inputCol);
var part2 = part1.appendChild(btnCol);
categoryContainer.appendChild(part2);
}
There's a little disconnect in the javascript function but you can assume that the Button and the Input in the HTML example are inside of Columns also, i don't think that makes a big difference but please let me know if it would be one
Tag helper cannot pass complex model type data to backend, it only allows simple type. That is to say, you need set the model's property name in asp-for.
Then, if you want to get the list model, you need specific the list index like: _Input.Categories[0].PropertyName.
Not sure what is your whole view, here is a simple demo about how to pass list model to backend:
Model:
public class FormInput
{
public List<Address> Addresses { get; set; }
public List<Category> Categories { get; set; }
}
public class Address
{
public string Name { get; set; }
}
public class Category
{
public string BusinessCategory { get; set; }
}
View:
#page
#model IndexModel
<form method="post">
<ul id="Category-Container">
<li>
<input asp-for="_Input.Categories[0].BusinessCategory" type="text" />
<button type="button" onclick="AddCategory()">
Add New Address
</button>
</li>
</ul>
<div>
<input asp-for="_Input.Addresses[0].Name" type="text" />
<button type="button" onclick="this.AddNewInput">
Add New Category
</button>
</div>
<input type="submit" value="Post"/>
</form>
JS:
#section Scripts
{
<script>
var count = 1; //add count...
var categoryContainer = document.getElementById("Category-Container");
function AddCategory() {
var input = document.createElement("input");
input.classList.add("w-100");
//change the name here.....
input.name = "_Input.Categories["+count+"].BusinessCategory";
var inputCol = document.createElement("div");
inputCol.classList.add("col-8");
inputCol.appendChild(input);
var btn = document.createElement("button");
btn.classList.add("btn");
btn.classList.add("btn-primary");
btn.innerText = "Add New Category";
var btnCol = document.createElement("div");
btnCol.classList.add("col");
btnCol.appendChild(btn);
var row = document.createElement("li");
row.classList.add("row");
var part1 = row.appendChild(inputCol);
part1.appendChild(btnCol); //change here...
categoryContainer.appendChild(part1); //change here...
count++; //add here ...
}
</script>
}
Backend code:
public class IndexModel: PageModel
{
[BindProperty] public FormInput _Input { get; set; }
public IActionResult OnGet()
{
return Page();
}
public void OnPost()
{
}
}
I am quite new to ASP .Net, and could use some help... I have an ASP .Net Core 1.1 web app. In it, I have an "Edit" view for editing a simple object, which a corresponding controller calls when routed to it. This is the view:
#model InspectionsTestClient.Models.Property
#{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
#Html.ValidationSummary();
<form asp-action="Edit">
<div class="form-horizontal">
<h4>Property</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="UnitNumber" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="UnitNumber" class="form-control" />
<span asp-validation-for="UnitNumber" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="BuildingName" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="BuildingName" class="form-control" />
<span asp-validation-for="BuildingName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Street" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Street" class="form-control" />
<span asp-validation-for="Street" class="text-danger"></span>
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
This is the controller which calls that view:
// GET: Property/Edit/5
public ActionResult Edit(int id)
{
return View();
}
And this is the model:
namespace InspectionsTestClient.Models
{
//[Table("property")]
public class Property
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int? Id { get; set; }
[MaxLength(10, ErrorMessage = "Unit number too long")]
[Display(Name = "Unit #")]
public string UnitNumber { get; set; }
[MaxLength(45, ErrorMessage = "BuildingName name too long")]
public string BuildingName { get; set; }
[MaxLength(45, ErrorMessage = "Street too long")]
public string Street { get; set; }
}
}
So when I navigate to that page, the controller fires up, and returns the Edit view. I have confirmed the parameter "id" is populated. When the Edit view loads in the browser, however, all the input textboxes are empty. I would expect them to be pre-populated with the values for the object in question. What am I missing?
The issue you are experiencing is happening because you are not returning that object to the view.. actually in your case you're not even going out to the db to get the object.
You need to edit you Edit action to something like this:
// GET: Property/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var object = db.TableName.Find(id);
// db = connectionstring
// TableName = database table that holds the object that you want to return
if (object == null)
{
return HttpNotFound();
}
return View(object);
}
Let me know if this helps
public class PropertyController
{
private readonly ApplicationDbContext _dbContext;
public PropertyController(ApplicationDbContext dbContext){
_dbContext = dbContext;
}
//GET: Property/Edit/5
public async Task<IActionResult> Edit(int id)
{
var property = await _dbContext.Property.FirstOrDefaultAsync(p => p.Id == id);
return View(property);
}
}
If you don't pull the data from the database and send it to the view of course it will always be blank. Edit(int id) there will be 2, both slightly different from the other.
[HttpPost]
[ValidateAntiForgeryToken]
//Post: Property/Edit/5
public async Task<IActionResult> Edit(int id, [Bind("Id", "UnitNumber", "BuildingNumber", "Street")] Property property)
{
if(ModelState.IsValid){
}
else{
}
}
not everyting is present but that is part of your adventure.
I'm just looking for a better way to do the following :
I've got an html select :
<form method="post" action="/Account/ChangeUserRole">
<select name="Val" onchange="this.form.submit();" class="span2">
#foreach (var r in ViewBag.UserRoles)
{
#if (u.UserRole.ID == r.ID)
{
<option selected="selected" value="#u.ID/#r.ID">#r.Name</option>
}
else
{
<option value="#u.ID/#r.ID">#r.Name</option> // <-- better way?
}
}
</select>
</form>
I'm posting it as "userid/roleid" and on the controller side doing a string.Split on / to split u.ID and r.ID
I would like to know if it's possible to post it so my controller get's them in this way :
[HttpPost]
public IActionResult ChangeUserRole(int UserID, int RoleID)
Instead of this witchcraft:
[HttpPost]
public IActionResult ChangeUserRole(string Val)
{
char[] splitChar = new char[] { '/' };
string[] s = Val.Split(splitChar);
int UserID = Convert.ToInt32(s[0]);
int RoleID = Convert.ToInt32(s[1]);
}
Sorry for the long post. Hope my question makes sense.
I'm not such a big fan of html helpers.
Side note:
I'm using MVC 6, ASP 5 - RC1
Appreciate the help
Cheers!
The best solution is to use the TagHelpers to build your dropdown. Let's start by creating a view model specific to this view.
public class UserRoleEditVm
{
public List<SelectListItem> Roles { set; get; }
public int RoleId { set; get; }
public int UserId { set; get; }
}
In your get action, create an object of this, load the property values and send it to the view.
public IActionResult Create()
{
// User Id and Role list is hard coded for demo. You may replace it with real data.
var v = new UserRoleEditVm {UserId = 45};
v.Roles = new List<SelectListItem>
{
new SelectListItem {Value = "1", Text = "Admin"},
new SelectListItem {Value = "2", Text = "Editor"},
new SelectListItem {Value = "3", Text = "Reader"}
};
return View(v);
}
And in your view, which is strongly typed to our view model, we will like Tag helpers to for creating the HTML markup.
#model UserRoleEditVm
<form asp-action="ChangeUserRole" asp-controller="Account">
<select asp-for="RoleId" asp-items="#Model.Roles">
<option>Please select one role</option>
</select>
<input type="hidden"asp-for="UserId"/>
<input type="submit"/>
</form>
And in your HttpPost action method, you can use an object of our view model as the parameter and the Model binder will map the posted form values to property values of that object.
[HttpPost]
public ActionResult ChangeUserRole(UserRoleEditVm model)
{
var userId = model.UserId;
var roleId = model.RoleId;
// to do : Do something with the above 2 values
// to do :Save and redirect (PRG pattern)
// return RedirectToAction("Success");
}
I have tried to create dropdown list with tag
<select name="manufacturer" class="form-control">
<option>#null</option>
#foreach (var item in Model) {
<option>#item.Manufacturer</option>
}
</select><br>
but here is a problem: It doesn't populate all available manufacturers and only shows items, from current page. Example: http://i.imgur.com/na0x5eQ.png
that's because it uses the model passed from controller which doesn't always contain every item from the database. (because of pagination or after search)
Here is full sidebar code in partial view
#model IPagedList<Products>
#using PagedList
#using PagedList.Mvc
<div id="filter" class="left">
#using (Html.BeginForm("Filter", "SearchFilter")) {
<div>
<b>Manufacturer:</b> <br>
<select name="manufacturer" class="form-control">
<option>#null</option>
#foreach (var item in Model) {
<option>#item.Manufacturer</option>
}
</select><br>
<b>Name:</b> <br>#Html.TextBox("name", null)<br>
<b>Price From:</b> <br>#Html.TextBox("min", null)<br>
<b>To:</b> <br>#Html.TextBox("max", null)<br>
<button type="submit" value="search"><b>Search</b></button>
</div>
}
</div>
So what are the ways to fix this?
Since you know why it's occurring
that's because it uses the model passed from controller which doesn't always contain every item from the database. (because of pagination or after search)
then why not fix it right there. Maybe you need a separate validation table for manufacturers (if you don't already have one).
Model:
public class ProductsModel
{
public IPagedList<Products> CurrentProducts {get; set;}
public int ManufacturerId {get; set;}
public int MinPrice{get; set;}
public int MaxPrice{get; set;}
}
Controller action:
[HttpGet]
public ViewResult CurrentProducts()
{
ProductsModel model = new ProductsModel();
model.CurrentProducts = _repository.Product;
ViewBag.ManufacturerId = from p in _repository.Product select p.Manufacturer;
return View(model);
}
[HttpPost]
public ViewResult CurrentProducts(ProductsModel model)
{
if(ModelState.IsValid)
{
model.CurrentProducts = _repository.Product.Where(t=>t.ManufacturerId == model.ManufacturerId);
ViewBag.ManufacturerId = from p in _repository.Product select p.Manufacturer;
}
return View(model);
}
And in the view, create a dropdown list like this:
#model MyNameSpace.ProductsModel
#Html.DropDownListFor(model=> model.ManufacturerId, string.Empty);