ASP.NET Routes config not following tutorial - asp.net-core-mvc

I'm trying to follow a basic tutorial to get forms working:
https://www.completecsharptutorial.com/asp-net-mvc5/4-ways-to-create-form-in-asp-net-mvc.php
After running the first example my code trying to render localhost:5001/form1 instead of the example's localhost:5001/Home/form1
I'm assuming the issue is my Starup.cs routes needs to be tweaked. It currently looks like:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Controller code:
namespace test_ops.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location =
ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId =
Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
[HttpPost]
public ActionResult form1(int txtId, string txtName, string chkAddon)
{
ViewBag.Id = txtId;
ViewBag.Name = txtName;
if (chkAddon != null)
ViewBag.Addon = "Selected";
else
ViewBag.Addon = "Not Selected";
return View("Index");
}
}
}
Model code:
namespace test_ops.Models
{
public class TestModel
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Addon { get; set; }
}
}
View code in Home/index.cshtml:
<h4 style="color:purple">
<b>ID:</b> #ViewBag.ID <br />
<b>Name:</b> #ViewBag.Name <br />
<b>Addon:</b> #ViewBag.Addon
</h4>
<hr />
<h3><b>Forms: Weakly Typed</b></h3>
<form action="form1" method="post">
<table>
<tr>
<td>Enter ID: </td>
<td><input type="text" name="txtId" /></td>
</tr>
<tr>
<td>Enter Name: </td>
<td><input type="text" name="txtName" /></td>
</tr>
<tr>
<td>Addon: </td>
<td><input type="checkbox" name="chkAddon" /></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Submit Form" /></td>
</tr>
</table>
</form>
After submitting the form the page comes back with the a 404:
"This localhost page can’t be foundNo webpage was found for the web address: https://localhost:5001/form1
HTTP ERROR 404"
Can someone please let me know what needs to be changed to get this working?

try to add controller to your form
<form action="#Url.Action("form1", "home")" method="post">
...
but is much better to use this syntax instead of form
#using (Html.BeginForm("form1", "Home", FormMethod.Post))
{
<table>
<tr>
//...
<tr>
<td colspan="2"><input type="submit" value="Submit Form" /></td>
</tr>
}
and check a file _ViewImports.cshtml in a View folder. It should have
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Related

ASP.NET Core 6 MVC : HTML table to view model collection

Starting up with ASP.NET Core 6 MVC and in my case I have one view which lists few properties of one object and then some other for their children in a editable table (user can edit the values)
View model has the properties and an IEnumerable of the children:
public class MyObjectViewModel
{
public String Id { get; set; }
public String Descr { get; set; }
public IEnumerable<ChildrenObject> Children { get; set; }
public MyObjectViewModel()
{
Children = Enumerable.Empty<ChildrenObject>();
}
public class ChildrenObject
{
public String? Id { get; set; }
public String? Name { get; set; }
}
}
All under the same form:
#using (Html.BeginForm("Save", "Controller", FormMethod.Post))
{
<input type="submit" value="SAVE"/>
<br />
#Html.LabelFor(model => model.Id)
#Html.TextBoxFor(model => model.Id, new { #readonly = "readonly" })
<br />
#Html.LabelFor(model => model.Descr)
#Html.EditorFor(model => model.Descr)
<br />
<table>
<tbody>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
#foreach (var child in Model.Children)
{
<tr>
<td>#child.Id</td>
<td><input class="ef-select" type="text" value="#child.Name"></td>
</tr>
}
</tbody>
</table>
}
So when the button is pressed the data is dumped back to the model to perform the SAVE action in the controller.
All ok for the simple fields (I can get the data in the controller as the view model), but not sure how to accomplish it with the table / children property...
Any simple way without needing to use JS to serialise or pick up the data from the table?
Thanks
If you want to pass id and name of ChildrenObject in table,you can try to add hidden inputs for the Id,and set name attribute for name inputs:
#{var count = 0; }
#foreach (var child in Model.Children)
{
<tr>
<td>
#child.Id
<input value="#child.Id" name="Children[#count].Id" />
</td>
<td><input class="ef-select" type="text" value="#child.Name" name="Children[#count].Name"></td>
</tr>
count++;
}

Posted ViewModel return null properties

I am having issues with a view model that constantly return null properties after a post. Below is my code (it could be a syntax issue or two calling a class or property the same name as i saw in other posts but i could not see any such issue in code):
VIEW MODEL:
public class ProductItem
{
public int ProductID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string LongDescription { get; set; }
public int SupplierID { get; set; }
public string Dimensions { get; set; }
public double Price { get; set; }
public bool On_Sale { get; set; }
public double DiscountedPrice { get; set; }
public string Thumbnail { get; set; }
public string LargeImage { get; set; }
public string LargeImage2 { get; set; }
public string LargeImage3 { get; set; }
public string CrossRef { get; set; }
public byte Available { get; set; }
public double Weight { get; set; }
public byte Important { get; set; }
public virtual ICollection<ProductCategory> ProductCategories { get; set; }
// this is required on the page to allow products to be marked for deletion
public bool IsForDelete { get; set; }
}
public class ProductListViewModel
{
public IEnumerable<ProductItem> ProductItems { get; set; }
public IEnumerable<Category> CategoryItems { get; set; }
}
CONTROLLER:
public ActionResult ProductList()
{
var productList = new ProductListViewModel();
productList.ProductItems = productRepository.GetProductsWithDeleteOption().ToList();
productList.CategoryItems = categoryRepository.GetCategories().ToList();
return View(productList);
}
[HttpPost]
public ActionResult ProductList(ProductListViewModel productViewModel, FormCollection formCollection, string submit)
{
if (ModelState.IsValid)
{
// Check for submit action
if (submit == "Change Sort")
{
if (formCollection["Sortby"] == "ProductID")
{
OrderBy(productViewModel, formCollection, "m.ProductID");
}
else if (formCollection["Sortby"] == "Code")
{
OrderBy(productViewModel, formCollection, "m.Code");
}
else if (formCollection["Sortby"] == "Name")
{
OrderBy(productViewModel, formCollection, "m.Name");
}
else if (formCollection["Sortby"] == "Price")
{
OrderBy(productViewModel, formCollection, "m.Price");
}
}
else if (submit == "Delete all selected")
{
}
else if (submit == "Update All")
{
}
else if (submit == "Restrict Display")
{
}
}
return View(productViewModel);
}
VIEW:
#model Admin.Models.ViewModels.ProductListViewModel
#{
ViewBag.Title = "View Products";
}
#using (Html.BeginForm())
{
<h2>Product List as at #DateTime.Now.ToString("dd/MM/yyyy")</h2>
<table>
<tr>
<td>Sort by:</td>
<td>
<select name="Sortby">
<option value="ProductID">ProductID</option>
<option value="Code">Code</option>
<option value="Name">Name</option>
<option value="Price">Price</option>
</select>
</td>
<td>
<input type="radio" name="sortDirection" checked="checked" value="Asc" /> Ascending
<input type="radio" name="sortDirection" value="Desc" /> Descending
</td>
<td>
<input type="submit" name="submit" value="Change Sort" />
</td>
</tr>
<tr>
<td>Display only : (category)</td>
<td>#Html.DropDownList("CategoryID", new SelectList(Model.CategoryItems, "CategoryID", "Name"), "All Categories")</td>
<td colspan="2"><input type="submit" name="submit" value="Restrict Display" /></td>
</tr>
<tr>
<td colspan="4"><br />Total Number of products: #Model.ProductItems.Count()</td>
</tr>
</table>
<table>
<tr>
<th>
Edit
</th>
<th>
Code
</th>
<th>
Name
</th>
<th>
Price
</th>
<th>
On_Sale
</th>
<th>
DiscountedPrice
</th>
<th>
Weight
</th>
<th>
Delete
</th>
<th></th>
</tr>
#for (var i = 0; i < Model.ProductItems.ToList().Count; i++)
{
<tr>
<td>
#Html.HiddenFor(m => m.ProductItems.ToList()[i].ProductID)
#Html.ActionLink(Model.ProductItems.ToList()[i].ProductID.ToString(), "ProductEdit", new { id = Model.ProductItems.ToList()[i].ProductID })
</td>
<td>
#Html.DisplayFor(m => m.ProductItems.ToList()[i].Code)
</td>
<td>
#Html.DisplayFor(m => m.ProductItems.ToList()[i].Name)
</td>
<td>
#Html.EditorFor(m => m.ProductItems.ToList()[i].Price)
</td>
<td>
#Html.CheckBoxFor(m => m.ProductItems.ToList()[i].On_Sale, new { id = "On_Sale_" + Model.ProductItems.ToList()[i].ProductID })
</td>
<td>
#Html.EditorFor(m => m.ProductItems.ToList()[i].DiscountedPrice)
</td>
<td>
#Html.EditorFor(m => m.ProductItems.ToList()[i].Weight)
</td>
<td>
#Html.CheckBoxFor(m => m.ProductItems.ToList()[i].IsForDelete, new { id = Model.ProductItems.ToList()[i].ProductID })
</td>
<td>
#Html.ActionLink("Edit", "ProductEdit", new { id = Model.ProductItems.ToList()[i].ProductID }) |
#Html.ActionLink("Details", "Details", new { id = Model.ProductItems.ToList()[i].ProductID }) |
#Html.ActionLink("Delete", "Delete", new { id = Model.ProductItems.ToList()[i].ProductID })
</td>
</tr>
}
</table>
<p>
<input name="submit" type="submit" value="Delete all selected" />
</p>
<p>
<input name="submit" type="submit" value="Update All" />
</p>
<p>
#Html.ActionLink("Add a new product", "ProductAdd")
</p>
}
In the post action, the productViewModel argument has the ProductItems and CategoryItems properties as null.
Ok, so there are two problems.
I don't understand why you want to post list of the CategoryItems You should only expect the selected category and not the list
The problem with ProductItems is the name generated for <input> tags. Currently, the name being generated is name="[0].Price" whereas it should have been name="ProductItems[0].Price"
I changed the following code
#Html.EditorFor(m => m.ProductItems.ToList()[i].Price)
to
#Html.EditorFor(m => m.ProductItems[i].Price)
and it worked.
Note: I changed IEnumerable<ProductItem> ProductItems to List<ProductItem> ProductItems in ProductListViewModel
Yes it will be null on post back. I have a similar table in one of my projects and this is what I would have done given my situation.
You could change your view model to look like this then you don't have to do so many converting to lists in your view:
public class ProductListViewModel
{
public List<ProductItem> ProductItems { get; set; }
public List<Category> CategoryItems { get; set; }
}
Now in you view it could look something like this (this is just part of it, then rest you can just go and add):
#for (int i = 0; i < Model.ProductItems.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(m => m.ProductItems[i].Name)
#Html.HiddenFor(m => m.ProductItems[i].Name)
</td>
</tr>
<tr>
<td>
#Html.CheckBoxFor(m => m.ProductItems[i].IsForDelete)
</td>
</tr>
}
Add the code and do some debugging to see how the values are returned on submit
I hope this helps.

I lost my data on submit MVC 3

I have a MVC 3 application with entity framework.
In my page I use a custom Model that contains all objects I use. The page is rendered perfectly, but when I press the submit button my object loses the data.
This is my custom model:
public class ControleAcessoModel
{
private List<Controle> controles = new List<Controle>();
public GRUPO_ACESSO_TB grupo_acesso_tb { get; set; }
public List<Controle> Controles
{
get
{
return controles;
}
}
public void AddTela(byte id, string nome)
{
Controle ctrl = new Controle();
ctrl.ID_TELA = id;
ctrl.NM_TELA = nome;
controles.Add(ctrl);
}
public class Controle
{
public bool Selecionado { get; set; }
public byte ID_TELA { get; set; }
public string NM_TELA { get; set; }
public bool FL_SALVAR { get; set; }
public bool FL_ALTERAR { get; set; }
public bool FL_EXCLUIR { get; set; }
}
}
this is my Razor Html code:
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<table>
<tr>
<th>Salvar</th>
<th>Editar</th>
<th>Excluir</th>
<th>Tela</th>
</tr>
#foreach (var item in Model.Controles)
{
<tr>
<td style="text-align: center">
#Html.EditorFor(modelItem => item.FL_SALVAR)
</td>
<td style="text-align: center">
#Html.EditorFor(modelItem => item.FL_ALTERAR)
</td>
<td style="text-align: center">
#Html.EditorFor(modelItem => item.FL_EXCLUIR)
</td>
<td>
#Html.DisplayFor(modelItem => item.NM_TELA)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="Salvar" />
</p>
}
This is my create code, where I put the data on database.
Is in this part, that my object controleacessomodel is empty.
[HttpPost]
public ActionResult Create(ControleAcessoModel controleacessomodel, byte id)
{
if (ModelState.IsValid)
{
for (int i = 0; i < controleacessomodel.Controles.Count; i++)
{
if (ValidaSelecao(controleacessomodel.Controles[i]))
{
PERMISSAO_GRUPO_ACESSO_TELA_TB permissao = new PERMISSAO_GRUPO_ACESSO_TELA_TB();
permissao.ID_GRUPO_ACESSO = controleacessomodel.grupo_acesso_tb.ID_GRUPO_ACESSO;
permissao.ID_TELA = controleacessomodel.Controles[i].ID_TELA;
permissao.FL_SALVAR = controleacessomodel.Controles[i].FL_SALVAR;
permissao.FL_ALTERAR = controleacessomodel.Controles[i].FL_ALTERAR;
permissao.FL_EXCLUIR = controleacessomodel.Controles[i].FL_EXCLUIR;
db.PERMISSAO_GRUPO_ACESSO_TELA_TB.AddObject(permissao);
}
}
db.SaveChanges();
return RedirectToAction("Edit", "GrupoAcesso", new { id = id });
}
return View(controleacessomodel);
}
Why my object is empty after submit?
It's not possible to use the Foreach loop construct as the generated ids will be not be correct, therefore MVC is not able to map the values back to the model. You need to use a for loop instead:
#for (int i = 0 ; i < Model.Controles.Count; i++)
{
<tr>
<td style="text-align: center">
#Html.EditorFor(m => m.Controles[i].FL_SALVAR)
</td>
<td style="text-align: center">
#Html.EditorFor(m => m.Controles[i].FL_ALTERAR)
</td>
<td style="text-align: center">
#Html.EditorFor(m => m.Controles[i].FL_EXCLUIR)
</td>
<td>
#Html.DisplayFor(m => m.Controles[i].NM_TELA)
</td>
</tr>
}
Phil Haack write a good blog post about this. Also Scott Hanselman wrote a nice post too.

EditorTemplate + Model not being returned on POST

I can't seem to figure this out, it is driving me crazy!
Essentially, I have a list of rows I need to display with one drop down list per row.
I have a view model:
public class UserMembershipViewModel:BaseViewModel
{
public List<ProgramMembership> ProgramMembership { get; set; }
}
In my parent view I have, as you can see I am using an editor template which is located in "/Views/Shared/EditorTemplates/ProgramMembership.cshtml":
#using AcnCS.Model
#model AcnCS.Model.ViewModels.User.UserMembershipViewModel
#{
ViewBag.PageHeader = "Membership for " + Model.User.FullName;
ViewBag.PageTitle = "Membership for " + #Model.User.FullName;
ViewBag.HideNav = true;
}
#if (Model.ProgramMembership != null)
{
<div class="row-fluid">
<div class="span12">
<div id="permissions">
#using (Html.BeginForm())
{
<table class="table table-bordered">
<thead>
<tr>
<td>Program</td>
<td>Effective Membership?</td>
<td>Permission Type</td>
</tr>
</thead>
#Html.EditorFor(m => Model.ProgramMembership, "ProgramMembership")
</table>
<input type="submit" class="btn btn-primary" value="Save Changes"/>
}
</div>
</div>
</div>
}
My Editor template (ProgramMembership.cshtml) is:
#using AcnCS.Model
#model List<AcnCS.Model.ProgramMembership>
#foreach(ProgramMembership membership in Model)
{
<tr>
<td>#membership.ProgramName</td>
<td>
#if (membership.IsMember)
{
<span class="label label-success">#membership.IsMember</span>
}
else
{
#membership.IsMember
}
</td>
<td>#Html.DropDownListFor(x => membership.PermissionType, membership.PermissionTypes)</td>
</tr>
}
Everything is being displayed properly, but when I submit, my model object is null, even the ProgramMembership property in the model is null:
[HttpPost]
public ActionResult Membership(UserMembershipViewModel model)
{
// model IS NULL!!
return View(model);
}
Any help would be greatly appreciated!
I would pluralize the Property name since it is a collection, for better readability
public class UserMembershipViewModel:BaseViewModel
{
public List<ProgramMembership> ProgramMemberships { get; set; }
}
and you dont need a Loop inside your EditorTemplate file
#model AcnCS.Model.ProgramMembership
<tr>
<td>#membership.ProgramName</td>
<td>
#if (membership.IsMember)
{
<span class="label label-success">#membership.IsMember</span>
}
else
{
#membership.IsMember
}
</td>
<td>#Html.DropDownListFor(x => membership.PermissionType, membership.PermissionTypes)</td>
</tr>
In your main view,call your EditorTemplate like this
#Html.EditorFor(m=>m.ProgramMemberships)

How to pass a list of objects instead of one object to a POST action method

I have the following GET and POST action methods:-
public ActionResult Create(int visitid)
{
VisitLabResult vlr = new VisitLabResult();
vlr.DateTaken = DateTime.Now;
ViewBag.LabTestID = new SelectList(repository.FindAllLabTest(), "LabTestID", "Description");
return View();
}
//
// POST: /VisitLabResult/Create
[HttpPost]
public ActionResult Create(VisitLabResult visitlabresult, int visitid)
{
try
{
if (ModelState.IsValid)
{
visitlabresult.VisitID = visitid;
repository.AddVisitLabResult(visitlabresult);
repository.Save();
return RedirectToAction("Edit", "Visit", new { id = visitid });
}
}
catch (DbUpdateException) {
ModelState.AddModelError(string.Empty, "The Same test Type might have been already created,, go back to the Visit page to see the avilalbe Lab Tests");
}
ViewBag.LabTestID = new SelectList(repository.FindAllLabTest(), "LabTestID", "Description", visitlabresult.LabTestID);
return View(visitlabresult);
}
Currently the view display the associated fields to create only one object,, but how i can define list of objects instead of one object to be able to quickly add for example 10 objects at the same “Create” request.
My Create view look like:-
#model Medical.Models.VisitLabResult
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#section scripts{
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>VisitLabResult</legend>
<div class="editor-label">
#Html.LabelFor(model => model.LabTestID, "LabTest")
</div>
<div class="editor-field">
#Html.DropDownList("LabTestID", String.Empty)
Your viewModel
public class LabResult
{
public int ResultId { get; set; }
public string Name { get; set; }
//rest of the properties
}
Your controller
public class LabController : Controller
{
//
// GET: /Lab/ns
public ActionResult Index()
{
var lst = new List<LabResult>();
lst.Add(new LabResult() { Name = "Pravin", ResultId = 1 });
lst.Add(new LabResult() { Name = "Pradeep", ResultId = 2 });
return View(lst);
}
[HttpPost]
public ActionResult EditAll(ICollection<LabResult> results)
{
//savr results here
return RedirectToAction("Index");
}
}
Your view
#model IList<MvcApplication2.Models.LabResult>
#using (Html.BeginForm("EditAll", "Lab", FormMethod.Post))
{
<table>
<tr>
<th>
ResultId
</th>
<th>
Name
</th>
</tr>
#for (int item = 0; item < Model.Count(); item++)
{
<tr>
<td>
#Html.TextBoxFor(modelItem => Model[item].ResultId)
</td>
<td>
#Html.TextBoxFor(modelItem => Model[item].Name)
</td>
</tr>
}
</table>
<input type="submit" value="Edit All" />
}
Your view will be rendered as follows, this array based naming convention makes it possible for Defaultbinder to convert it into ICollection as a first parameter of action EditAll
<tr>
<td>
<input name="[0].ResultId" type="text" value="1" />
</td>
<td>
<input name="[0].Name" type="text" value="Pravin" />
</td>
</tr>
<tr>
<td>
<input name="[1].ResultId" type="text" value="2" />
</td>
<td>
<input name="[1].Name" type="text" value="Pradeep" />
</td>
</tr>
If I understand your question correctly,
you want to change your view to be a list of your model object #model List, then using a loop or however you wish to do it, create however many editors you need to for each object
then in your controller your receiving parameter of create will be a list of your model instead too.

Resources