Is there a way to only display ValidationSummary errors associated with a partial view? - asp.net5

I building a form in .NET5. I have view which has several nested partial views which reference different levels of my model. I perform validation and display any errors below the field. I also display a summary of the errors using ValidationSummary.
What I would like to do is use the ValidationSummary at the partial view level. Meaning, if there are errors in the first partial view, the validation summary will display only the errors associated with that partial view. So far, I've not been able to accomplish this and I'm wondering if this is even possible.
For some context, this is a large dynamic form with several collapsible fields. The parent view contains several nested partial views, and each partial view may also have nested partial views. The specific partial views rendered are determined at runtime based on the form type. Each partial view is passed only the "model level" needed in the RenderPartialAsync() call.
Example of my Model
public class Customer
{
public Address Address { get; set; }
public History History { get; set; }
}
public class Address
{
[Required]
public string StreetAddress { get; set; }
[Required]
public string City { get; set; }
[Required]
public string ZipCode { get; set; }
}
public class History
{
[Required]
public string SomeOtherProperty { get; set; }
}
Example of my View with nested partials:
#model Customer
<div class="row">
<div class="table-responsive table-responsive pl-1">
#{
await Html.RenderPartialAsync("_Address",
Model.Address,
new ViewDataDictionary(ViewData){});
}
</div>
<div class="table-responsive table-responsive pl-1">
#{
await Html.RenderPartialAsync("_History",
Model.History,
new ViewDataDictionary(ViewData){});
}
</div>
</div>
Example of my partial view _address
#model Address
<div>
<div>
<table>
<thead>
<th>Address</th>
<th>City</th>
<th>State</th>
<th>Zip</th>
</thead>
<tbody>
<tr>
<input asp-for="Model.StreetAddress" />
<span class="text-danger" asp-validation-for="Model.StreetAddress"></span>
</tr>
<tr>
<input asp-for="Model.City" />
<span class="text-danger" asp-validation-for="Model.StreetAddress"></span>
</tr>
<tr>
<input asp-for="Model.ZipCode" />
<span class="text-danger" asp-validation-for="Model.StreetAddress"></span>
</tr>
</tbody>
</table>
</div>
<div class="text-danger" asp-validation-summary>
#* Here is where I want to display a summary of errors for Address *#
</div>
</div>
Example of my partial view _history
#model History
<div>
<div>
<table>
<thead>
<th>Label</th>
<th>...</th>
</thead>
<tbody>
<tr>
<input asp-for="Model.SomeOtherProperty" />
<span class="text-danger" asp-validation-for="Model.StreetAddress"></span>
</tr>
<tr>
<input asp-for="Model..." />
<span class="text-danger" asp-validation-for="Model.StreetAddress"></span>
</tr>
</tbody>
</table>
</div>
<div class="text-danger" asp-validation-summary>
#* Here is where I want to display a summary of errors for History *#
</div>
</div>
Unfortunately, this approach has failed and instead, both validation summary calls in my partial views display all errors on the page, thus Address errors are displayed in the summary of the partial _history view.
Note, this is just an example of what I'm trying to achieve. Thanks in advance.

Related

Why does ASP.NET Core 7 MVC ModelValidator always set ModelState.IsValid to true when the Model is invalid

I'm converting an ASP.NET 4.8 MVC web site to ASP.NET Core 7 MVC. I'm running into a problem with ModelValidation where my ModelState is always valid, even when it is not. I'm using the pattern that was used in .Net 4x and worked fine. Microsoft Learn link
Controller
[HttpPost]
public async Task<IActionResult> Create(TcPhoneType tcPhoneType)
{
if (ModelState.IsValid)
{
await _phoneTypeService.CreatePhoneType(tcPhoneType);
return RedirectToAction("Index");
}
return View(tcPhoneType);
}
Model.cs
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace App.Models;
public partial class TcPhoneType
{
public string PhoneTypeId { get; set; }
public string PhoneTypeDescription { get; set; }
public virtual ICollection<MemberPhone> MemberPhones { get; } = new List<MemberPhone>();
}
ModelMetadata.cs
using System.ComponentModel.DataAnnotations;
namespace App.Models;
[MetadataType(typeof(TcPhoneTypeMetadata))]
public partial class TcPhoneType
{
}
public class TcPhoneTypeMetadata
{
[Required(AllowEmptyStrings = false, ErrorMessage = "Item is required")]
[StringLength(1)]
public string? PhoneTypeId { get; set; }
[Required(AllowEmptyStrings = false)]
[StringLength(5)]
public string? PhoneTypeDescription { get; set; }
}
Create.cshtml
#model SeasonCourt7.Models.TcPhoneType
#{
ViewBag.Title = "Add Phone Type Code";
}
<h2>#(ViewBag.Title)</h2>
<br>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="row mb-3">
<div class="col-sm-2">
<label asp-for="PhoneTypeId" class="col-form-label">Code</label>
</div>
<div class="col-sm-10">
<input asp-for="PhoneTypeId" class="form-control" autofocus />
</div>
<span asp-validation-for="PhoneTypeId" class="text-danger"></span>
</div>
<div class="row mb-3">
<div class="col-sm-2">
<label asp-for="PhoneTypeDescription" class="col-form-label">Description</label>
</div>
<div class="col-sm-10">
<input asp-for="PhoneTypeDescription" class="form-control" />
</div>
<span asp-validation-for="PhoneTypeDescription" class="text-danger"></span>
</div>
<div class="row mb-3">
<div class="offset-sm-2 col-sm-10">
<input type="submit" value="Create" class="btn btn-primary" />
Back
</div>
</div>
</div>
}
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
I set a breakpoint on the if (ModelState.IsValid) line so I could inspect the model and ModelState. I can see the data that is bound to the model is as expected and the ModelState is always valid.
If I pass null data in the model, I should see the ModelState.IsValid = false, but it's always true.
The model.cs is generated by EF and could be regenerated in the future which makes it impractical to add the metadata attributes for validation, as the file could be regenerated in the future.
The only way I've been able to get the ModelValidation to work as expected, is to decorate the model directly with the validation attributes.

Change Data in DataTable with Select from Dropdownlist

I have a view with a Datatable, and I want to change the data every time I select a category from a drop-down list.
I want to only display the albums from the selected category, or all albums from all categories, using Ajax and jQuery. The drop-down list must be placed above the table.
Here is my code:
#using System.Collections.Generic;
#using CakeStore.App.Areas.Admin.Models.Albums;
#using CakeStore.App.Services.Contracts;
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#inject IAlbumService AlbumService
#{ ViewData["Title"] = "Category Albums";
Layout = "~/Areas/Admin/Views/Shared/_AdminLayout.cshtml";
var albums = ViewBag.CategoryAlbums as List<AlbumTableViewModel>;
}
<h1 class="text-center text-header-page">Category Albums</h1>
<hr class="hr-admin-divider" />
<div class="form-group">
<a class="btn button-black-white d-flex justify-content-left" href="/Admin/Albums/Create">Create</a>
</div>
<hr class="hr-admin-divider" />
<div class="d-flex">
<table class="table table-striped table-hover" id="myTable">
<tr>
<th>#</th>
<th>Name</th>
<th>Category</th>
<th></th>
<th></th>
<th></th>
</tr>
#for (int i = 0; i < albums.Count(); i++) {
<tr>
<td class="col-md-1">#albums[i].Id</td>
<td class="col-md-3">#albums[i].Name</td>
<td class="col-md-2">#albums[i].Category</td>
<td><a class="btn button-table-edit" href="/Admin/Albums/Edit?id=#albums[i].Id">EDIT</a></td>
<td><a class="btn button-table-delete d-flex justify-content-right" href="/Admin/Albums/Delete?id=#albums[i].Id">DELETE</a></td>
<td><a class="btn button-table-view d-flex justify-content-right" href="/Admin/Products/CategoryAlbums?id=#albums[i].Id">PRODUCTS</a></td>
</tr>
}
</table>
</div>
<div class="row d-flex align-items-end flex-column" style="font-size:12px">
<a class="btn-link pull-right col-lg-2" asp-controller="Categories" asp-action="Category">Back to Categories</a>
</div>
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
You could use the Partial View ,I made the demo ,you could refer to it
Use ajax to call the GetCityList method to get the data corresponding to the countryId passed .
<div class="form-group">
<label class="col-form-label">Country</label>
<select class="form-control" asp-items="#ViewBag.Country" id="selectlist">
<option>All</option>
</select>
</div>
<div class="form-group" id="cityListWrapper">
<partial name="_CityListPartial" model="#ViewBag.City" />
</div>
#section Scripts
{
<script type="text/javascript">
$("#selectlist").change(function () {
var countryId = $("#selectlist").val();
$.ajax
({
type: "GET",
url: "/Countries/GetCityList?CountryId="+countryId,
success: function (result) {
$("#cityListWrapper").html(result)
}
});
});
</script>
}
Initial rendering the main view , show all albums when not selected
public async Task<IActionResult> Index()
{
ViewBag.Country = new SelectList(_context.Country, "Id", "CountryName");
ViewBag.City = _context.City.ToList();
return View();
}
The GetCityList action ,render the partial view using a statement that returns different values
[HttpGet]
public async Task<IActionResult> GetCityList(int? countryId)
{
if (countryId == null)
{
var CityList = await _context.City.ToListAsync();
return PartialView("_CityListPartial", CityList);
}
var Cities =await _context.City.Where(c => c.Country.Id == countryId).ToListAsync();
return PartialView("_CityListPartial", Cities);
}
How it works

SPRING WEB MVC ANNOTATIONS-Method for Partial Page Rendering

Hello Friends,
[Need some help regarding spring web mvc annotation based:]
How to use partial page rendering on spring web mvc annotations.
Master Page Name: CreateEntity.jsp
Partial jsp page: Account.jsp, Lead.jsp etc etc*
Now, this master page consists of drop down list where if Account is selected then partial page Account will get load into master page named CreateEntity. Else if Lead is Selected then Lead will get into the master page.
I have Created the Controller named CreateEntityController.java into which have written code as follows:
***CreateEntityController.java***
#RequestMapping(value = { "/CreateEntity" }, method = RequestMethod.GET)
public ModelAndView CreateEntity(Model model) {
model.addAttribute("module", "Account");
model.addAttribute("msg", "This is the partial view page!");
return new ModelAndView("CreateEntity");
}
JSP Page Code:
(CreateEntity.jsp)-Master Page
<li><c:url value="/CreateEntity" var="CreateEntity" />
<a data-params='{"module":"CreateLeadStatus"}'
href="<c:out value='${CreateEntity}'/>">Create Entity</a></li>
<div>
<%#include file="CreateLeadStatus.jsp"%>
</div>
JSP Page Code 2: *Partial Page Code: ***Account.jsp****
<spring:url value="CreateEntity" var="CreateEntity" />
<form class="form-Account" action="${CreateEntity}" method="POST" role="form" >
<div class="crmBodyWin" style="">
<div id="" class="p30 lbox set_mw">
<div id="secDiv_Lead_Information" class="editParentSection pL30 pR30">
<table style="width: 100%" cellspacing="0" cellpadding="0"
id="secHead_Lead_Information">
<tbody>
<tr>
<td class="contHeadInfo">Lead Status</td>
</tr>
</tbody>
</table>
<div style="width: 100%" class="secContent" border="0"
cellspacing="1" cellpadding="0" id="secContent_Lead_Information">
<div class="contInfoTab floatL formViewCreate">
<div id="Leads_fldRow_COMPANY" class="textFld COMPANY tabDivCreate">
<div class="labelTabCreate pL5 pR newmandatory"
id="Crm_Leads_COMPANY_label">Status Name</div><div class="labelValCreate mL45">
<input
type="text" class="textField" style="width: 100%"
id="txtleadname" name="txtleadname" tabindex="5" maxlength="100"
data-maxlength="100" data-customfield="false"
data-decimal-length="2" data-label="leadname"
data-readonly="false" value="">
</div>
<div class="clearB"></div>
</div>
<br>
<button class="btn btn-lg rest-btn-active btn-block" type="submit">Create Lead Status-TEST</button>
</div>
<div class="contInfoTab floatR formViewCreate"></div>
</div>
</div>
</div>
</div>
</form>
Please tell me that is there any solution for it such a manner:
- Example:
If{
(Account )-is selected then
Account.jsp - partial view loads on that master page**(CreateEntity)**
}
else if (Lead)-is selected then
Lead.jsp -partial view loads on that master page**(CreateEntity)**
}
module can be the common method in controller:
For example on selected choice as Account. The URL lookes something like this:
For Account its should look something like this:
http://localhost:8080/projectname/CreateEntity?module=Account
For Lead its should look something like this:
http://localhost:8080/projectname/CreateEntity?module=Lead
Please kindly help me regarding this topic.
Thank You!
As far as i understand your question. Your goal is to include a jsp page based on your URL param(module). To do that you have to change your controller to this
***CreateEntityController.java***
#RequestMapping(value = { "/CreateEntity" }, method = RequestMethod.GET)
public ModelAndView CreateEntity(String module,Model model) {
model.addAttribute("module", module);
model.addAttribute("msg", "This is the partial view page!");
if(module.equals("Account"){
//account related attributes
// call your service or bo and send account related object
model.addAttribute("account",yourAccountObject)
}
if(module.equals("Lead"){
//lead related attributes
// call your service or bo and send lead related object
model.addAttribute("lead",yourLeadObject)
}
return new ModelAndView("CreateEntity");
}
CreateEntity.jsp
<%-- common code --%>
<li>
<a data-params='{"module":"CreateAccountStatus"}' href="<c:url value='CreateEntity?
module=Account'/>">Create Account </a>
</li>
<li>
<a data-params='{"module":"CreateLeadStatus"}' href="<c:url value='CreateEntity?
module=Lead'/>">Create Account </a>
</li>
<div>
<%-- Your partial condition --%>
<c:choose>
<c:when test="${module=='Account'}">
<jsp:include page="Account.jsp" />
</c:when>
<c:when test="${module=='Lead'}">
<jsp:include page="Lead.jsp" />
</c:when>
</c:choose>
</div>

MVC - Displaying a List of Files in a View

I have the following MVC code:
#model ExchangeSite.Entities.BicycleSellerListing
<fieldset>
<legend>Images</legend>
<div>
<div class="imagelabel">
Image #1
</div>
<div>
<input type="file" name="[0].files" id="files_0" style="width:275px"/>
</div>
<div class="imagelabel">
Image #2
</div>
<div>
<input type="file" name="[1].files" id="files_1" style="width:275px"/>
</div>
<div class="imagelabel">
Image #3
</div>
<div>
<input type="file" name="[2].files" id="files_2" style="width:275px"/>
</div>
</div>
</fieldset>
Here is my model / entity, or at least a portion of it:
public class BicycleSellerListing
{
[HiddenInput(DisplayValue=false)]
public int BicycleSellerListingId { get; set; }
public virtual ICollection<BicycleImage> ImageList { get; set; }
}
When the associated controller is called, I retrieve the files the user has already uploaded to the system (a max of three) from our database. What I don't know how to do is to display the names of those files in the MVC view shown above.
You could put the list of files in your model (MyList in the example below) and then on your view you could razor's foreach to iterate over your list and repeat the html. Something like this (notice you can pull the Id from the item too):
<fieldset>
<legend>Images</legend>
<div>
#foreach (var item in Model.MyList) {
<div class="imagelabel">
Image #1
</div>
<div>
<div>#item.FileName</div>
</div>
}
</div>
</fieldset>
Updated: replaced input elements with divs.

Nullable value in Edit Action

I have an Edit form, and have a problem when saving the result.
View:
<div class="editor-label">
#Html.LabelFor(model => model.LoginModel.Uzytkownik)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LoginModel.Uzytkownik)
#Html.ValidationMessageFor(model => model.LoginModel.Uzytkownik)
</div>
Model:
namespace Restauracja.Models
{
public class pracownikModel
{
public LoginModel LoginModel { get; set; }
public uzytkownikModel uzytkownikModle { get; set; }
public pracownikModel() {
LoginModel = new LoginModel();
uzytkownikModle = new uzytkownikModel();
}
}
public class LoginModel
{
[Required]
public string Uzytkownik { get; set; }
[Required]
public string Haslo { get; set; }
public string Konto { get; set; }
}
public class uzytkownikModel
{
[Required]
public string imie { get; set; }
....
}
}
Controller:
[HttpGet]
public ActionResult Edit(int LoginID)
{
pracownikModel prac = new pracownikModel();
var pr = (from p in baza.Logowanies where LoginID == p.LoginID select p).First();
prac.LoginModel.Uzytkownik = pr.Login;
return View(prac);
}
[HttpPost]
public ActionResult Edit(int LoginID, pracownikModel prac)
{
var xxx = (from z in baza.Logowanies where LoginID == z.LoginID select z).Single();
xxx.Login = prac.LoginModel.Uzytkownik;
baza.SubmitChanges();
return RedirectToAction("Index", "Foo");
}
Function with [HttpGet] is working properly, showing result from database. The problem is with second function... prac is null... So this cannot work because I can't write null to the database. I don't know how to solve this problem.
I would recommend you to use only standard ASCII letters and numbers when naming your variables and classes. The default model binder uses those names when trying to bind the properties from the POST request. Also since those property names are used as name and id attribute of the input elements in the HTML you end up with invalid HTML according to the specification which states:
ID and NAME tokens must begin with a letter ([A-Za-z]) and may be followed by any number of letters, digits ([0-9]), hyphens ("-"), underscores ("_"), colons (":"), and periods (".").
So you should not use extended ASCII or unicode symbols such as ł and ż. Replace them with their corresponding ASCII character and the default model binder will be able to correct fetch the values.
UPDATE:
After looking at the sample code you sent me in your Edit view you currently have 3 forms:
#model Restauracja.Models.pracownikModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
<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>Edit</legend>
<div class="editor-label">
#Html.LabelFor(model => model.LoginModel.Uzytkownik)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LoginModel.Uzytkownik)
#Html.ValidationMessageFor(model => model.LoginModel.Uzytkownik)
</div>
</fieldset>
}
<table id="nostyle">
<tr>
<td>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<input type="submit" value="Save" />
}
</td>
<td>
#using (Html.BeginForm("Anuluj", "Pracownik", FormMethod.Post, new { id = "AnulujForm" }))
{
<input id="Anuluj" type="submit" value="Cancel" />
}
</td>
</tr>
</table>
The first form is the only one that contains input fields for your model. So it is by submitting only the first form that you can expect to populate some of the properties of your model in the POST action. Unfortunately this first form doesn't have a submit button so you cannot submit it. The second and third form contain submit buttons but they do not contain any input field so submitting them will leave the model totally blank.
So what you need here is to move the Save submit button inside the first form:
#model Restauracja.Models.pracownikModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
<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>Edit</legend>
<div class="editor-label">
#Html.LabelFor(model => model.LoginModel.Uzytkownik)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LoginModel.Uzytkownik)
#Html.ValidationMessageFor(model => model.LoginModel.Uzytkownik)
</div>
</fieldset>
#:<table id="nostyle">
#:<tr>
#:<td>
<input type="submit" value="Save" />
#:</td>
}
<td>
#using (Html.BeginForm("Anuluj", "Pracownik", FormMethod.Post, new { id = "AnulujForm" }))
{
<input id="Anuluj" type="submit" value="Cancel" />
}
</td>
</tr>
</table>
or since the Cancel submit button posts currently to a controller action tat performs a redirect you could simply replace it with an anchor which will redirect:
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Edit</legend>
<div class="editor-label">
#Html.LabelFor(model => model.LoginModel.Uzytkownik)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LoginModel.Uzytkownik)
#Html.ValidationMessageFor(model => model.LoginModel.Uzytkownik)
</div>
</fieldset>
<table id="nostyle">
<tr>
<td>
<input type="submit" value="Save" />
</td>
<td>
#Html.ActionLink("Cancel", "zarzadzaj_pracownikami", "Pracownik")
</td>
</tr>
</table>
}

Resources