Having started studying asp net core 6, I ran into the problem of invalidity of my model.
What is the hidden problem?
Model of Product
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace TestWebApplication.Models
{
public class Product
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
[Range(0.1, int.MaxValue)]
public double Price { get; set; }
public string Image { get; set; }
[Display(Name = "Category Type")]
public int CategoryId { get; set; }
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
}
}
Model of ProductVM
using Microsoft.AspNetCore.Mvc.Rendering;
namespace TestWebApplication.Models.ViewModels
{
public class ProductVM
{
public Product Product { get; set; }
public IEnumerable<SelectListItem> CategorySelectList { get; set; }
}
}
View
#model TestWebApplication.Models.ViewModels.ProductVM
#{
var title = "Create Product";
}
<form method="post" enctype="multipart/form-data">
#if(Model.Product.Id != 0)
{
title = "Edit Product";
<input asp-for="Product.Id" hidden />
}
<div class="border p-3">
<div class="form-group row">
<h2 class="text-info pl-3">#title</h2>
</div>
<br />
<div class="row">
<div class="col-8">
<div class="form-group row">
<div class="col-4">
<label asp-for="Product.Name"></label>
</div>
<div class="col-8">
<input asp-for="Product.Name" class="form-control"/>
<span asp-validation-for="Product.Name" class="text-danger"></span>
</div>
</div>
<br />
<div class="form-group row">
<div class="col-4">
<label asp-for="Product.Price"></label>
</div>
<div class="col-8">
<input asp-for="Product.Price" class="form-control"/>
<span asp-validation-for="Product.Price" class="text-danger"></span>
</div>
</div>
<br />
<div class="form-group row">
<div class="col-4">
<label asp-for="Product.Description"></label>
</div>
<div class="col-8">
<textarea asp-for="Product.Description" class="form-control summernote"> </textarea>
<span asp-validation-for="Product.Description" class="text-danger"></span>
</div>
</div>
<br />
<div class="form-group row">
<div class="col-4">
Image
</div>
<div class="col-8">
<input type = "file" name="files" id="uploadBox" multiple class="form-control"/>
</div>
</div>
<br />
<div class="form-group row">
<div class="col-4">
<label asp-for="Product.CategoryId"></label>
</div>
<div class="col-8">
<select asp-for="Product.CategoryId" asp-items="#Model.CategorySelectList" class="form-control">
<option disabled selected>---Select Category---</option>
</select>
<span asp-validation-for="Product.CategoryId" class="text-danger"></span>
</div>
</div>
<br />
<div class="form-group row">
<div class="col-8 offset-4 row">
<div class="col">
#if (Model.Product.Id != 0)
{
//update
<input type="submit" class="btn btn-info w-100" value="Update"/>
}
else
{
//create
<input type="submit" onclick="return validateInput()" class="btn btn-primary w-100" value="Create"/>
}
</div>
<div class="col">
<a asp-action="Index" class="btn btn-success w-100">
Back
</a>
</div>
</div>
</div>
</div>
<div class="col-4">
#* Keep this empty *#
</div>
</div>
</div>
</form>
#section Scripts
{
#{
<partial name= "_ValidationScriptsPartial.cshtml"/>
}
<script>
$(document).ready(function() {
$('.summernote').summernote(
{
height:250
});
});
function validateInput()
{
if (document.getElementById("uploadBox").value == "")
{
Swal.fire
(
'Error!',
'Please, upload an image',
'error'
)
return false;
}
return true;
}
</script>
}
Controller
using TestWebApplication.Data;
using TestWebApplication.Models;
using TestWebApplication.Models.ViewModels;
namespace TestWebApplication.Controllers
{
public class ProductController : Controller
{
private readonly ApplicationDbContext _db;
private readonly IWebHostEnvironment _webHostEnvironment;
public ProductController(ApplicationDbContext db, IWebHostEnvironment webHostEnvironment)
{
_db = db;
_webHostEnvironment = webHostEnvironment;
}
public IActionResult Index()
{
IEnumerable<Product> objList = _db.Product;
foreach (var item in objList)
{
item.Category = _db.Category.FirstOrDefault(category => category.Id == item.Id);
}
return View(objList);
}
// get - upsert
public IActionResult Upsert(int? id)
{
//IEnumerable<SelectListItem> CategoryDropDown = _db.Category.Select(i => new SelectListItem
//{
// Text = i.Name,
// Value = i.Id.ToString()
//});
//ViewBag.CategoryDropDown = CategoryDropDown;
//Product product = new Product();
ProductVM productVM = new ProductVM()
{
Product = new Product(),
CategorySelectList = _db.Category.Select(i => new SelectListItem
{
Text = i.Name,
Value = i.Id.ToString()
})
};
if(id == null)
{
// for create
return View(productVM);
}
else
{
productVM.Product = _db.Product.Find(id);
if (productVM.Product == null)
NotFound();
return View(productVM);
}
}
// post - upsert
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Upsert(ProductVM obj)
{
if (ModelState.IsValid)
{
var files = HttpContext.Request.Form.Files;
string webRootPath = _webHostEnvironment.WebRootPath;
if(obj.Product.Id == 0)
{
//Creating
string upload = webRootPath + WC.ImagePath;
string fileName = Guid.NewGuid().ToString();
string extention = Path.GetExtension(files[0].FileName);
using(var fileStream = new FileStream(Path.Combine(upload, fileName + extention), FileMode.Create))
{
files[0].CopyTo(fileStream);
}
obj.Product.Image = fileName + extention;
_db.Product.Add(obj.Product);
}
else
{
//Updating
}
return RedirectToAction("Index");
}
return View();
}
}
List of fields that participated in validation
From one answer, I realized that the Category field will remain null, and ef core will substitute the values. But I don't quite understand why so many fields are validated without being marked required in the model.
By receives a POST action, ModelState takes all of the name-value pairs and adds them as individual instances of ModelStateEntry to an instance of ModelStateDictionary.
ModelState checks all properties regardless of whether they have a Data annotations or not, such as you get a number, text, file etc. But for those data annotations you put on the properties the ModelState makes a special check based on it.
Just the properties of the model you passed to controller would be validated,you could try custom model validation follow this document:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-6.0#custom-attributes
And you could try with ModelState.AddModelError(string.Empty, "errormessage");add model level error in backend.
Related
When I try to get data from data for edit page it's showing me the following error:
So I am building a website where I want to implement a edit banner form when the User can Edit his banner data.
And here is where the problem I raise, when I run the code, it return always as null.
Here is my controller:
[HttpGet]
public ActionResult EditBanner(int id)
{
var abd = (from a in _db.Banner
where id == a.BannerId
select a).FirstOrDefault();
return View(abd);
}
[HttpPost]
public ActionResult UpdateBannerDatas(BannerViewModels bnrupdate)
{
var bnrdata = _db.Banner.Where(x => x.BannerId == bnrupdate.BannerId).FirstOrDefault();
if (bnrdata != null)
{
bnrdata.BannerTitle = bnrupdate.BannerTitle;
bnrdata.BannerUrl = bnrupdate.BannerUrl;
bnrdata.BannerIndex = bnrupdate.BannerIndex;
bnrdata.BannerDescription = bnrupdate.BannerDescription;
bnrdata.BannerIndex = bnrupdate.BannerIndex;
_db.SaveChanges();
}
return Redirect("BannerDetails");
}
Here is my viewmodel:
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SZGMC.Web.Models;
namespace SZGMC.Web.Areas.Admin.ViewModels
{
public class BannerViewModels
{
public int BannerId { get; set; }
public string BannerTitle { get; set; }
public string BannerDescription { get; set; }
public string BannerImg { get; set; }
public IFormFile BannerImg1 { get; set; }
public string BannerUrl { get; set; }
public string BannerIndex { get; set; }
public int? BMasterId { get; set; }
public byte? IsDeleted { get; set; }
public virtual BannerMaster BMaster { get; set; }
}
}
Here is my model:
using System;
using System.Collections.Generic;
// Code scaffolded by EF Core assumes nullable reference types (NRTs) are not used or disabled.
// If you have enabled NRTs for your project, then un-comment the following line:
// #nullable disable
namespace SZGMC.Web.Models
{
public partial class Banner
{
public int BannerId { get; set; }
public string BannerTitle { get; set; }
public string BannerDescription { get; set; }
public string BannerImg { get; set; }
public string BannerUrl { get; set; }
public string BannerIndex { get; set; }
public int? BMasterId { get; set; }
public byte? IsDeleted { get; set; }
public virtual BannerMaster BMaster { get; set; }
}
}
Here is my view:
<form asp-controller="Home" asp-action="UpdateBannerDatas" enctype="multipart/form-data" method="post">
<div id="main-content">
<div class="container-fluid">
<!-- Page header section -->
<div class="block-header">
<div class="row clearfix">
<div class="col-lg-6 col-md-5 col-sm-12">
<h1>Hi, Welcomeback!</h1>
<span>You can edit banner here</span>
</div>
<div class="col-xl-6 col-md-7 col-sm-12 text-md-right">
<div class="d-flex align-items-center justify-content-md-end mt-4 mt-md-0 flex-wrap vivify pullUp delay-550">
<div class="mb-3 mb-xl-0 ">
<a asp-action="BannerDetails" class="btn btn-dark">Banner List</a>
</div>
</div>
</div>
</div>
</div>
<div class="row clearfix">
<div class="col-12">
<div class="card">
<div class="body">
<div class="header">
<h2><strong>Enter Banner Details</strong></h2>
</div>
<br />
<input asp-for="BannerId" type="hidden" />
<div class="row">
<div class="col-12">
<div class="form-group c_form_group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"></span>
</div>
<input type="text" class="form-control" asp-for="BannerTitle" placeholder="Banner Title" aria-label="bannertitle" aria-describedby="basic-addon1">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group c_form_group">
<label>Banner Description</label>
<div class="input-group">
<textarea class="form-control" asp-for="BannerDescription" aria-label="Banner Description" rows="6"></textarea>
</div>
</div>
</div>
<div class="col-6">
<div class="drop-zone">
<span class="drop-zone__prompt">Drop file here or click to upload</span>
<input type="file" asp-for="BannerImg1" name="myFile" class="drop-zone__input" accept="image/*" data-allowed-file-extensions='["jpg", "png" , "jpeg"]' required>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group c_form_group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"></span>
</div>
<input type="text" class="form-control" asp-for="BannerIndex" placeholder="Banner Index" aria-label="bannerindex" aria-describedby="basic-addon1">
</div>
</div>
</div>
<div class="col-6">
<div class="form-group c_form_group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"></span>
</div>
</div>
</div>
</div>
</div>
<div class="mb-2" align="center">
<button type="submit" class="btn btn-success btn-round">Edit Banner</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
When i try To Edit the data in the browser it Throw exception, in database i have value but when i tried to fetch data its showing error.
thank you in advance to everybody for any help or advice.
According to the error message it is necessary to pass the BannerViewModels instance instead of the Banner entity:
public ActionResult EditBanner(int id)
{
var abd = _db.Banner.Where(a => a.BannerId == id)
.Select(b => new BannerViewModels()
{
BannerId = b.BannerId,
BannerTitle = b.BannerTitle,
BannerDescription = b.BannerDescription,
BannerImg = b.BannerImg,
BannerUrl = b.BannerUrl,
BannerIndex = b.BannerIndex,
BMasterId = b.BMasterId,
IsDeleted = b.IsDeleted
})
.FirstOrDefault();
return View(abd);
}
Passing an entity to the view as a data model is bad idea.
See the following post: Why it's not a good idea to pass entities as Models in MVC?
I'm trying to get a value from each dropdownlist in the view model. I'm doing with a foreach but that doesn't seem to work. Can anyone help me here please?
I'm doing in the controller the foreach. I'm using Entity Framework and repository methods to this. Any help is helpful.
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(int? id, AddSaidasServicoViewModel model, string command)
{
if (ModelState.IsValid)
{
//TODO: adicionar Userhelper para ver quem cria a saida. Criar campo na tabela BD. Ver apontamento Notepad
if (command.Equals("submit1"))
{
await _saidaservicoRepository.AddSaidaServicoAsync(model);
return RedirectToAction(nameof(Create));
}
else
{
foreach(var elementoId in model.Elementos )
{
await _elementoRepository.UpdateElementoSaidaServicosAsync(model);
}
}
return RedirectToAction(nameof(Index));
}
return View(model);
}
View markup:
#model FireHouseGest.web.Models.AddSaidasServicoViewModel
#{
ViewData["Title"] = "Create";
}
<h2>Criar</h2>
<h4>Saída Serviço</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="container">
<div class="row">
<div class="text-center">
<div class="col-md-3 text-center">
<div class="form-group">
<label asp-for="ServicoId" class="control-label"></label>
<select asp-for="ServicoId" asp-items="Model.Servicos" class="form-control"></select>
<span asp-validation-for="ServicoId" class="text-danger"></span>
</div>
</div>
<div class="col-md-3 text-center">
<div class="form-group">
<label asp-for="ViaturaId" class="control-label"></label>
<select asp-for="ViaturaId" asp-items="Model.Viaturas" class="form-control"></select>
<span asp-validation-for="ViaturaId" class="text-danger"></span>
</div>
</div>
</div>
</div>
<div class="form-group">
<input type="submit" name="command" value="submit1" class="btn btn-default" />
</div>
</div>
<div class="container">
<div class="row">
<div class="col-lg-2 text-center">
<div class="form-group">
<label asp-for="ElementoId" class="control-label"></label>
<select asp-for="ElementoId" asp-items="Model.Elementos" class="form-control"></select>
<span asp-validation-for="ElementoId" class="text-danger"></span>
</div>
</div>
<div class="col-lg-2 text-center">
<div class="form-group">
<label asp-for="ElementoId" class="control-label"></label>
<select asp-for="ElementoId" asp-items="Model.Elementos" class="form-control"></select>
<span asp-validation-for="ElementoId" class="text-danger"></span>
</div>
</div>
<div class="col-lg-2 text-center">
<div class="form-group">
<label asp-for="ElementoId" class="control-label"></label>
<select asp-for="ElementoId" asp-items="Model.Elementos" class="form-control"></select>
<span asp-validation-for="ElementoId" class="text-danger"></span>
</div>
</div>
<div class="col-lg-2 text-center">
<div class="form-group">
<label asp-for="ElementoId" class="control-label"></label>
<select asp-for="ElementoId" asp-items="Model.Elementos" class="form-control"></select>
<span asp-validation-for="ElementoId" class="text-danger"></span>
</div>
</div>
<div class="col-lg-2 text-center">
<div class="form-group">
<label asp-for="ElementoId" class="control-label"></label>
<select asp-for="ElementoId" asp-items="Model.Elementos" class="form-control"></select>
<span asp-validation-for="ElementoId" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<input type="submit" name="command" value="submit2" class="btn btn-default" />
</div>
</div>
<div class="form-group">
<label asp-for="Adecorrer" class="control-label"></label>
<input asp-for="Adecorrer" class="form-control" />
<span asp-validation-for="Adecorrer" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Inicio" class="control-label"></label>
<input asp-for="Inicio" class="form-control" />
<span asp-validation-for="Inicio" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Fim" class="control-label"></label>
<input asp-for="Fim" class="form-control" />
<span asp-validation-for="Fim" class="text-danger"></span>
</div>
</form>
</div>
</div>
ViewModel
namespace FireHouseGest.web.Models
{
public class AddSaidasServicoViewModel
{
public int SaidaServicoId { get; set; }
[Display(Name = "Elementos")]
public int ElementoId { get; set; }
public IEnumerable<SelectListItem> Elementos { get; set; }
[Display(Name = "Viatura")]
public int ViaturaId { get; set; }
public IEnumerable<SelectListItem> Viaturas { get; set; }
[Display(Name = "Serviço")]
public int ServicoId { get; set; }
public IEnumerable<SelectListItem> Servicos { get; set; }
[Display(Name = "Estado")]
public int Adecorrer { get; set; }
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy hh:mm }", ApplyFormatInEditMode = false)]
public DateTime Inicio { get { return DateTime.Now; } }
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy hh:mm }", ApplyFormatInEditMode = false)]
public DateTime? Fim { get; set; }
public User user { get; set; }
}
}
This is what I have. Open to suggestions.
Before coding, you need know the following things:
1.Model Binding find and bind the property by matching the name of the input.
2.If you have multiple inputs with the same name, you need set a List<T> type parameter to receive all the values.
3.Dropdownlist gets the selected option value(not text),be sure the type of the value is the same with the type of matched property. (e.g. ElementoId is int type,so the value of each SelectListItem in Elementos should be int or it could be converted to int):
var model = new AddSaidasServicoViewModel()
{
Elementos = new List<SelectListItem>() {
new SelectListItem() { Value = "1", Text = "Elementos1" },
new SelectListItem() { Value = "2", Text = "Elementos2" },
new SelectListItem() { Value = "3", Text = "Elementos3" }
},
//....
};
4.Dropdownlist selected value will match the property by searching the same name of the asp-for="xxx". (e.g. asp-for="ElementoId" will generate html:id="ElementoId" name="ElementoId",so this input value will match the property ElementoId in your model)
5.Although you have two submit button, but the submit button will always submit all the input values which they are in the same form.
Change your code like below and you will receive all the selected ElementoId values in List<string> ElementoId:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(int? id, AddSaidasServicoViewModel model,
List<string> ElementoId, string command)
{
if (ModelState.IsValid)
{
//TODO: adicionar Userhelper para ver quem cria a saida. Criar campo na tabela BD. Ver apontamento Notepad
if (command.Equals("submit1"))
{
//...
}
else
{
foreach (var elementoId in ElementoId)
{
//....
}
}
return RedirectToAction(nameof(Index));
}
return View(model);
}
Thanks Rena for answer. this was what i did.
public async Task UpdateElementosSaidasAsync(AddSaidasServicoViewModel model, List<string> Elementos)
{
int SaidaServicos = _context.SaidaServicos.Max(item => item.Id);
var Saida = await _context.SaidaServicos.FindAsync(SaidaServicos);
if (Elementos == null)
{
return;
}
else
{
foreach (var elementoId in Elementos)
{
var updateElementoSaida = _context.Elementos.Where(e => e.Id == Convert.ToInt32(elementoId)).FirstOrDefault();
updateElementoSaida.saidaServico = Saida;
await _context.SaveChangesAsync();
}
}
return;
}
I have an application which simulates a clothing site.I try to build my C# object with the data that the user writes in the form and the products they bought on the site.The products are represented by a JSON object.I use 2 classes: one for the shopping cart and one for the product.
public class Product
{
[Key]
public int id { get; set; }
public string productName { get; set; }
public string productPrice { get; set; }
public string quantity { get; set; }
}
public class ShoppingCart
{
[Key]
public int? id { get; set; }
public List<Product> productList { get; set; }
public string clientName { get; set; }
public string clientAddress { get; set; }
public string clientMail { get; set; }
}
I use a controller which has the method "Save" which uses the [FromForm] attribute.After the objects is binded from the client side I add it to my database.The problem is that I get "null" values for every property in my ShoppingCart object that is sent to the method.Moreover in my browser, the data is sent correctly to the server:
screenshot from network tab in chrome
The controller that I use looks like this:
[Route("SendItems/Save")]
[ApiController]
public class SendItemsController : Controller
{
private AppDbContext _db;
public SendItemsController(AppDbContext db)
{
_db = db;
}
[HttpPost]
[Consumes("application/json")]
public async Task<IActionResult> Save([FromForm] ShoppingCart s)
{
await _db.ShoppingCarts.AddAsync(s);
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
[HttpGet("~/ThankYou/Index")]
public IActionResult Index()
{
return View();
}
}
My html for the form is written like this:
#model ShoppingCart
<div class="form-group row">
<div class="col-4">
<label id="clienId"></label>
</div>
<div class="col-6">
<input asp-for="id" id="idClient" type="hidden" />
</div>
</div>
<div class="form-group row">
<div class="col-4">
<label id="clientProds"></label>
</div>
<div class="col-6">
<input asp-for="productList" id="inputProducts" type="hidden" />
</div>
</div>
<div class="form-group row">
<div class="col-4">
<label id="clientName"></label>
</div>
<div class="col-6">
<input asp-for="clientName" id="inputName" type="text" />
</div>
</div>
<div class="form-group row">
<div class="col-4">
<label id="clientAddress"></label>
</div>
<div class="col-6">
<input asp-for="clientAddress" id="inputAddress" type="text" />
</div>
</div>
<div class="form-group row">
<div class="col-4">
<label id="clientMail"></label>
</div>
<div class="col-6">
<input asp-for="clientMail" id="inputMail" type="text" />
</div>
</div>
<div class="form-group row">
<div class="col-3 offset-4">
<button class="btn btn-primary" id="orderB" asp-controller="SendItems" action="Save" type="submit">ORDER</button>
</div>
</div>
</form>
Also another issue is that if I don't use this piece of javascript the client is not redirected anymore to the "ThankYou" page :
var orderB = document.getElementById("orderB");
orderB.addEventListener("click", function () {
var inputName = document.getElementById("inputName").value;
var inputAddress = document.getElementById("inputAddress").value;
var inputMail = document.getElementById("inputMail").value;
var auxArray = [];
for (var i = 0; i < productsAux.length; i++) {
if (productsAux[i]!="") {
auxArray[i-1] = { "productName": productsAux[i].titlu, "productPrice": productsAux[i].pret, "quantity": localStorage.getItem(productsAux[i].titlu) };
}
}
document.getElementById("inputProducts").value = JSON.stringify(auxArray);
var shoppingCart = {
productList: auxArray,
clientName: inputName,
clientAddress: inputAddress,
clientMail: inputMail
};
$.ajax({
type: "POST",
data: JSON.stringify(shoppingCart),
url: "senditems/save",
contentType: "application/json;charset=utf-8",
})
})
Here is a demo worked:
Cart.cshtml(You need to change the bind of `productList,and then you don't need to use ajax to pass data to controller):
<form method="post" asp-controller="SendItems" asp-action="Save">
<div class="form-group row">
<div class="col-4">
<label id="clienId"></label>
</div>
<div class="col-6">
<input asp-for="id" id="idClient" type="hidden" />
</div>
</div>
<div class="form-group row">
<div class="col-4">
<label id="clientProds"></label>
</div>
<div class="col-6">
#for (var i = 0; i < Model.productList.Count(); i++)
{
<input type="hidden" asp-for="#Model.productList[i].id" />
<input type="hidden" asp-for="#Model.productList[i].productName" />
<input type="hidden" asp-for="#Model.productList[i].productPrice" />
<input type="hidden" asp-for="#Model.productList[i].quantity" />
}
</div>
</div>
<div class="form-group row">
<div class="col-4">
<label id="clientName"></label>
</div>
<div class="col-6">
<input asp-for="clientName" id="inputName" type="text" />
</div>
</div>
<div class="form-group row">
<div class="col-4">
<label id="clientAddress"></label>
</div>
<div class="col-6">
<input asp-for="clientAddress" id="inputAddress" type="text" />
</div>
</div>
<div class="form-group row">
<div class="col-4">
<label id="clientMail"></label>
</div>
<div class="col-6">
<input asp-for="clientMail" id="inputMail" type="text" />
</div>
</div>
<div class="form-group row">
<div class="col-3 offset-4">
<button class="btn btn-primary" id="orderB" type="submit" >ORDER</button>
</div>
</div>
</form>
Controller(SendItems):
[HttpPost]
public async Task<IActionResult> Save(ShoppingCart s)
{
return RedirectToAction("Index");
}
[HttpGet("~/ThankYou/Index")]
public IActionResult Index()
{
return View();
}
public IActionResult Cart()
{
ShoppingCart s = new ShoppingCart { id = 1, clientAddress = "address", clientName = "name1", clientMail = "123#123", productList = new List<Product> { new Product { id = 1, productName = "p1", productPrice = "10", quantity = "1" }, new Product { id = 2, productName = "p12", productPrice = "12", quantity = "3" } } };
return View(s);
}
result:
I'm working on a ASP.NET Core webpage using Razor Pages. On one page, a customer is added or selected and the customer_id is passed through correctly in the OnGet function (seen through debugging). When I then have the user fill out the form, I try to make a call to a SQL server database using Entity Framework. Everything should be working correctly, however, in OnPostAsync(), when I try to import the customer_id value I had set in OnGet(), I get an error because the value somehow gets set to 0 instead of what I had it previously set as. I don't know why this is happening as I'm generally new to Razor Pages and Entity Framework, but I've posted the code below. Any suggestions would be great!
AddAssessment.cshtml.cs:
namespace CustomerPageTest.Pages.Assessment
{
public class AddAssessmentModel : PageModel
{
private readonly CustomerPageTest.Data.CustomerPageTestContext _context;
public AddAssessmentModel(CustomerPageTest.Data.CustomerPageTestContext context)
{
_context = context;
}
public static List<SelectListItem> InDatabaseUserData()
{
List<SelectListItem> Users = new List<SelectListItem>();
string connString = "Data Source = DESKTOP-5A23I9M; Initial Catalog = DataWarehouse; User ID = sa; pwd = 2128Swan";
string commandString = "SELECT user_id, name FROM KelderUser";
SqlConnection conn = new SqlConnection(connString);
SqlDataAdapter adapter = new SqlDataAdapter(commandString, conn);
DataTable dtCustomers = new DataTable();
adapter.Fill(dtCustomers);
foreach (DataRow dataRow in dtCustomers.Rows)
{
SelectListItem temp = new SelectListItem()
{
Value = dataRow[0].ToString(),
Text = dataRow[1].ToString()
};
Users.Add(temp);
}
return Users;
}
[BindProperty]
public CustomerPageTest.Assessment Assessment { get; set; } = new CustomerPageTest.Assessment();
public List<SelectListItem> SelectUser { get; set; } = new List<SelectListItem>();
public void OnGet(int customerId)
{
SelectUser = InDatabaseUserData();
Assessment.customer_id = customerId;
if(Assessment == null)
{
return RedirectToPage("/Customer/List");
}
return Page();
}
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
foreach(var item in SelectUser)
{
if (item.Selected)
{
try { Assessment.user_id = Convert.ToInt32(item.Value); } catch (Exception) { }
}
}
try { Assessment.imported_data_datetime = Convert.ToDateTime(Assessment.imported_data_datetime); } catch(Exception) { }
_context.Assessment.Add(Assessment);
await _context.SaveChangesAsync();
return RedirectToPage("/Customer/List");
}
}
}
Then AddCustomer.cshtml:
#page
#model CustomerPageTest.Pages.Assessment.AddAssessmentModel
#{
ViewData["Title"] = "AddAssessment";
}
<h1>AddAssessment</h1>
<h4>Assessment</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Assessment.vcenter" class="control-label"></label>
<input asp-for="Assessment.vcenter" class="form-control" />
<span asp-validation-for="Assessment.vcenter" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Assessment.imported_data_datetime" class="control-label"></label>
<input asp-for="Assessment.imported_data_datetime" class="form-control" />
<span asp-validation-for="Assessment.imported_data_datetime" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Assessment.notes" class="control-label"></label>
<input asp-for="Assessment.notes" class="form-control" />
<span asp-validation-for="Assessment.notes" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Assessment.user_id"></label>
<select class="form-control" asp-for="Assessment.user_id" asp-items="Model.SelectUser">
<option value="">Select a User</option>
</select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="/Customer/List">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Also, If you have any questions about what is going on in my code, let me know as there are other classes and pages before this. The main idea is that I have an Assessment class with an integer: customer_id, that needs to be set and it's not getting set.
I write a demo,and i find you didn't bind the customer_id in the cshtml,so when you post the date to cshtml.cs,you didn't post the customer_id.You can add <input hidden asp-for="Assessment.customer_id" class="form-control" /> in the cshtml to bind customer_id.
Here is a demo worked:
cshtml:
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Assessment.vcenter" class="control-label"></label>
<input asp-for="Assessment.vcenter" class="form-control" />
<span asp-validation-for="Assessment.vcenter" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Assessment.imported_data_datetime" class="control-label"></label>
<input asp-for="Assessment.imported_data_datetime" class="form-control" />
<span asp-validation-for="Assessment.imported_data_datetime" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Assessment.notes" class="control-label"></label>
<input asp-for="Assessment.notes" class="form-control" />
<span asp-validation-for="Assessment.notes" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Assessment.user_id"></label>
<select class="form-control" asp-for="Assessment.user_id" asp-items="Model.SelectUser">
<option value="">Select a User</option>
</select>
</div>
<input hidden asp-for="Assessment.customer_id" class="form-control" />
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
cshtml.cs:
public class AddCustomerModel : PageModel
{
[BindProperty]
public Assessment Assessment { get; set; } = new Assessment();
public List<SelectListItem> SelectUser { get; set; } = new List<SelectListItem>();
public IActionResult OnGet()
{
for (int i = 1; i < 6; i++) {
SelectListItem temp = new SelectListItem()
{
Value = i+"",
Text = "user"+i
};
SelectUser.Add(temp);
}
Assessment.customer_id = 2;
Assessment.vcenter = "vcenter";
return Page();
}
public IActionResult OnPost() {
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
}
Result:
The reason this happens is because HTTP is stateless. Any state (data) that you created in one request is destroyed once that request has been processed unless you take steps to persist it.
There are multiple ways to manage state in Razor Pages (https://www.learnrazorpages.com/razor-pages/state-management), but in your case, you should use a hidden form field, posting the selected customer ID to the OnPost handler
When attempting to bind a model to a view, I'm getting a 404 error. The ActionResult is called 'SortDesc' but the view is just a standard 'Index' view. I understand that this isn't loading because the there is now view called 'SortDesc'. However I have two buttons one to sort data ascending and one to sort data decending hence the two seperate functions. I'm just not sure what the best solution would be here, either to continue with the two seperate functions and somehow pass in the correct view to load or to create a new, single HttpPost function on Index that will know which button has been clicked and sort accordingly. Here's the code I have so far:
Models:
public class NumberSetList
{
public int NumberSetListId { get; set; }
public List<NumberList> Numbers { get; set; }
public string SortOrder { get; set; }
}
public class NumberList
{
public int NumberListId { get; set; }
public int Number1 { get; set; }
public int Number2 { get; set; }
public int Number3 { get; set; }
public int Number4 { get; set; }
public int Number5 { get; set; }
public string SortOrder { get; set; }
}
Razor:
#{
ViewBag.Title = "csi media web test";
}
<div class="jumbotron">
<h1>csi media web test</h1>
<p class="lead">Liane Stevenson</p>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-info">
<div class="panel-heading"><i class="glyphicon glyphicon-arrow-right"></i> Enter Your Four Numbers</div>
<div class="panel-body">
<form class="form-inline">
<div class="col-md-9">
<div class="form-group">
<label class="sr-only" for="number1">1st Number</label>
<input type="number" class="form-control" id="number1" name="Number1" placeholder="#1">
</div>
<div class="form-group">
<label class="sr-only" for="number2">2nd Number</label>
<input type="number" class="form-control" id="number2" name="Number2" placeholder="#2">
</div>
<div class="form-group">
<label class="sr-only" for="number3">3rd Number</label>
<input type="number" class="form-control" id="number3" name="Number3" placeholder="#3">
</div>
<div class="form-group">
<label class="sr-only" for="number4">4th Number</label>
<input type="number" class="form-control" id="number4" name="Number4" placeholder="#4">
</div>
</div>
<div class="col-md-3 text-right">
<a class="btn btn-default" href="#Url.Action("SortDesc", "Home")"><i class="glyphicon glyphicon-arrow-down"></i> Sort Desc</a>
<a class="btn btn-default" href="#Url.Action("SortAsc", "Home")"><i class="glyphicon glyphicon-arrow-down"></i> Sort Asc</a>
</div>
</form>
<p>
#if (Model != null)
{
foreach (int number in Model.Numbers)
{
<span class="label label-info">#number</span>
}
}
</p>
</div>
</div>
</div>
</div>
Home Controller:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult SortDesc([Bind(Include = "Number1, Number2, Number3, Number4")] NumberSetList model)
{
if (!ModelState.IsValid)
{
return View();
}
else
{
NumberSetList list = new NumberSetList();
List<int> numbers = new List<int>();
numbers.Add(model.Number1);
numbers.Add(model.Number2);
numbers.Add(model.Number3);
numbers.Add(model.Number4);
numbers.OrderByDescending(i => i);
list.SortOrder = "Desc";
return View(list);
}
}
Try this
public class NumberSetList {
public int NumberSetListId { get; set; }
public List<NumberSetItem> Numbers { get; set; }
public string SortOrder { get; set; }
}
public class NumberSetItem {
public int Value { get; set; }
}
, use for loop to create inputs for the numbers
#using (Html.BeginForm()) {
<div class="col-md-9">
<div class="form-group">
#if (Model != null && Model.Numbers != null) {
#for (int i = 0; i < Model.Numbers.Count; i++) {
<div class="form-group">
#Html.LabelFor(m => m.Numbers[i].Value, "Number " + (i+1).ToString())
#Html.TextBoxFor(m => m.Numbers[i].Value, new { #class = "form-control" })
</div>
}
<div class="col-md-3 text-right">
<input id="SortOrder" value="Desc" type="button" class="btn btn-default"><i class="glyphicon glyphicon-arrow-down"></i> Sort Desc</input>
<input id="SortOrder" value="Asc" type="button" class="btn btn-default"><i class="glyphicon glyphicon-arrow-down"></i> Sort Asc</input>
</div>
}
</div>
</div>
}
And in the View
[HttpPost]
public ActionResult Index(NumberSetList model) {
if (!ModelState.IsValid) {
return View();
} else {
var numbers = model.SortOrder == "Desc" ?
model.Numbers.OrderByDescending(n => n.Value) :
model.Numbers.OrderBy(n => n.Value);
model.Numbers = numbers.ToList();
return View(model);
}
}
You can fix the issue using a variety of techniques.
Pass the name of the view into the View method like this:
return View("Index", list);
The View method has many overloads and you can see them all here. It is nice to get yourself familiar with the overloads for scenarios like the one you are facing.
If your view is not in the same folder as the action, you will need to pass the path for the view instead of just the name.
Another way is to have a single action and you can call this one Index as well but this one will take a model as an argument. Your model can have the numbers and an enum for sort: Desc, Asc. In your action method you can analyze the model, and do whatever is needed.
The approach with the model is the one I prefer.
EDIT
I just noticed you have this line of code in your view:
<a class="btn btn-default" href="#Url.Action("SortDesc", "Home")"><i class="glyphicon glyphicon-arrow-down"></i> Sort Desc</a>
That will trigger an HTTP GET and therefore it will not hit your SortDsc action method. Remove POST from your action method or use a post technique (Form) to hit your action method using POST.
EDIT 2
Honestly what you are doing is far simpler than you are making it. First thing is when you are using Url.Actio you are not passing nothing to your action method so how will it know about the numbers? I would normally call my action method using AJAX and get the result and display the result.
Without AJAX, you need to use one form for ascending and one for descending. Or you can use a radio button so user can select the order first.
Here is some code to do it using radio buttons.
Action:
[HttpPost]
public ActionResult SortDesc([Bind( Include = "Number1, Number2, Number3, Number4, SortOrder" )] NumberList model) {
if( !ModelState.IsValid ) {
return View("Index");
}
model.Numbers = new List<int>();
model.Numbers.Add( model.Number1 );
model.Numbers.Add( model.Number2 );
model.Numbers.Add( model.Number3 );
model.Numbers.Add( model.Number4 );
model.Numbers.Add( model.Number5 );
if (model.SortOrder == "Desc" ) {
model.Numbers = model.Numbers.OrderByDescending( i => i ).ToList();
}
else {
model.Numbers = model.Numbers.OrderBy( i => i ).ToList();
}
return View("Index", model);
}
Model:
public class NumberList {
public int Number1 { get; set; }
public int Number2 { get; set; }
public int Number3 { get; set; }
public int Number4 { get; set; }
public int Number5 { get; set; }
public int NumberListId { get; set; }
public List<int> Numbers { get; set; }
public string SortOrder { get; set; }
}
View:
#using( Html.BeginForm( "SortDesc", "Home", FormMethod.Post ) ) {
<div class="row">
<div class="col-md-12">
<div class="panel panel-info">
<div class="panel-heading"><i class="glyphicon glyphicon-arrow-right"></i> Enter Your Four Numbers</div>
<div class="panel-body">
<form class="form-inline">
<div class="col-md-9">
<div class="form-group">
<label class="sr-only" for="number1">1st Number</label>
<input type="number" class="form-control" id="number1" name="Number1" placeholder="#1">
</div>
<div class="form-group">
<label class="sr-only" for="number2">2nd Number</label>
<input type="number" class="form-control" id="number2" name="Number2" placeholder="#2">
</div>
<div class="form-group">
<label class="sr-only" for="number3">3rd Number</label>
<input type="number" class="form-control" id="number3" name="Number3" placeholder="#3">
</div>
<div class="form-group">
<label class="sr-only" for="number4">4th Number</label>
<input type="number" class="form-control" id="number4" name="Number4" placeholder="#4">
</div>
<div class="form-group">
<input type="radio" class="form-control" id="number4" name="SortOrder" value="Desc">
</div>
<div class="form-group">
<input type="radio" class="form-control" id="number4" name="SortOrder" value="Asc">
</div>
</div>
<div class="col-md-3 text-right">
<button class="btn btn-default"><i class="glyphicon glyphicon-arrow-down"></i> Sort </button>
#*<a class="btn btn-default"><i class="glyphicon glyphicon-arrow-down"></i> Sort Asc</a>*#
</div>
</form>
<p>
#if( Model != null ) {
foreach( int number in Model.Numbers ) {
<span class="label label-info">#number</span>
}
}
</p>
</div>
</div>
</div>
</div>
}