blazor webassembly validation does not block 2nd submit button - c#

We have two buttons inside of an Editform. They do a bit of different things. But we want the Data annotation validation to work on both button click. The first button is easy, it triggers the OnValidSubmit. But the second button, although it triggers page validation, still writes to console.
Below is the class used:
using System.ComponentModel.DataAnnotations;
namespace MyApp.Client.Models
{
public class TestModel
{
[Required]
public string MyName { get; set; }
}
}
Below is the razor component which includes two buttons and one inputtext field:
#page "/test"
<h3>test</h3>
<EditForm Model="model" OnValidSubmit="PostAsync" class="mt-5">
<DataAnnotationsValidator></DataAnnotationsValidator>
<ValidationSummary></ValidationSummary>
<div class="row w-100">
<div class="col-md-6">
<div class="float-lg-right row">
<div class="col-6 text-center">
<button type="submit" class="btn btn-success text-black w-100">Save</button>
</div>
<div class="col-6 text-center">
<button type="submit" class="btn btn-success text-black w-100" #onclick="(async ()=>await SaveAndGoToListAsync())">Save & Go To List</button>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<label for="MyName" class="col-form-label">My Name:</label>
<InputText id="MyName" class="form-control" #bind-Value="model.MyName"></InputText>
<ValidationMessage For="#(()=>model.MyName)"></ValidationMessage>
</div>
</div>
</EditForm>
#code {
private TestModel model;
public Test()
{
model = new();
}
private void PostAsync()
{
Console.WriteLine("PostAsync");
}
private async Task SaveAndGoToListAsync()
{
Console.WriteLine("SaveAndGoToListAsync");
}
}

This is because the second button has an onclick method.
#onclick="(async ()=>await SaveAndGoToListAsync())"
Here, instead of OnValidSubmit, you should use Context and Anonymous Functions, you can call the methods related to onclicks and pass the formContext to them. For validation, you can check the validity of the form for the corresponding method using Context.Validate in the corresponding method.
#page "/test"
<h3>test</h3>
<EditForm Model="model" Context="formContext" class="mt-5">
<DataAnnotationsValidator></DataAnnotationsValidator>
<ValidationSummary></ValidationSummary>
<div class="row w-100">
<div class="col-md-6">
<div class="float-lg-right row">
<div class="col-6 text-center">
<button type="submit" class="btn btn-success text-black w-100" #onclick="(()=>PostAsync(formContext))">Save</button>
</div>
<div class="col-6 text-center">
<button type="submit" class="btn btn-success text-black w-100" #onclick="(async ()=>await SaveAndGoToListAsync(formContext))">Save & Go To List</button>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<label for="MyName" class="col-form-label">My Name:</label>
<InputText id="MyName" class="form-control" #bind-Value="model.MyName"></InputText>
<ValidationMessage For="#(()=>model.MyName)"></ValidationMessage>
</div>
</div>
</EditForm>
#code {
private TestModel model;
public Test()
{
model = new();
}
private void PostAsync(EditContext formContext)
{
bool formIsValid = formContext.Validate();
if (formIsValid == false)
return;
Console.WriteLine("PostAsync");
}
private async Task SaveAndGoToListAsync(EditContext formContext)
{
bool formIsValid = formContext.Validate();
if (formIsValid == false)
return;
Console.WriteLine("SaveAndGoToListAsync");
}
}

Related

Error while Injecting dependency in ASP.NET Core [duplicate]

This question already has answers here:
Unable to resolve service for type while attempting to activate
(10 answers)
Closed 1 year ago.
I'm currently facing a problem while trying to test my Create View, here's the error I'm getting
Here's my startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IClientService, ClientService>()
.AddScoped<IServiceDossier, ServiceDossier>()
.AddScoped<ISpecialiteService, SpecialiteService>()
.AddTransient<IUnitOfWork, UnitOfWork>()
.AddScoped<IDataBaseFactory, DataBaseFactory>();
services.AddControllersWithViews();
}
Here's my controller along with constructor and the two create methods. I'm not understanding the problem because everything was injected correctly in the constructor
public class CreationDossier : Controller
{
private IServiceDossier doss;
private IClientService cl;
private IAvocatService av;
public CreationDossier(IServiceDossier doss, IClientService cl, IAvocatService av)
{
this.doss = doss;
this.cl = cl;
this.av = av;
}
public ActionResult Create()
{
ViewBag.AvocatFK = new SelectList(av.GetMany(), "AvocatId", "Avocat");
ViewBag.ClientFK = new SelectList(cl.GetMany(), "CIN", "Client");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Dossier collection)
{
try
{
doss.Add(collection);
doss.Commit();
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
Here's my cs.html file
#model Domain.Dossier
#using Domain
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Dossier</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="form-group">
<label asp-for="DateDepot" class="control-label"></label>
<input asp-for="DateDepot" class="form-control" />
<span asp-validation-for="DateDepot" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="Clos" /> #Html.DisplayNameFor(model => model.Clos)
</label>
</div>
<div class="form-group">
<label asp-for="Frais" class="control-label"></label>
<input asp-for="Frais" class="form-control" />
<span asp-validation-for="Frais" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="AvocatFK" class="control-label"></label>
<select asp-for="AvocatFK" class="form-control" asp-items="ViewBag.AvocatFK"></select>
</div>
<div class="form-group">
<label asp-for="ClientFK" class="control-label"></label>
<select asp-for="ClientFK" class="form-control" asp-items="ViewBag.ClientFK"></select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
You haven't registered IAvoatService in Startup.cs you need something like
public void ConfigureServices(IServiceCollection services)
{
// register other services as in original implementation
services.AddTransient<IAvocatService, ??AvocatServiceImplementation>()
}
Where AvocatServiceImplementation is the concrete class that implements IAvocatService.

Using HierarchyId with ASP.NET Core causes error in send post request?

I want to make countries using HierarchyId type so
I created a Country model class with these properties:
namespace DotNetCore5Crud.Models
{
public class Country
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public HierarchyId parentId { get; set; }
}
}
I then created an Index view which works fine:
#model IEnumerable<Category>
<partial name="_addCategory" model="new Category()" />
#{
if (!Model.Any())
{
<div class="alert alert-warning" role="alert">
there Is no Categories
</div>
}
else
{
<div class="container">
<table class="table table-sm table-hover table-striped">
<thead>
<tr class="bg-primary text-white font-weight-bold">
<th>id</th>
<th>Name</th>
<th>HierarchyId</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>#item.Id</td>
<td>#item.Name</td>
<td>#item.hierarchyId</td>
</tr>
}
</tbody>
</table>
</div>
}
}
Then, I inject a partial view to AddCountry:
#model Category
<!-- Button trigger modal -->
<button type="button" class="btn btn-sm mb-2 btn-primary" data-toggle="modal" data-target="#exampleModalCenter">
<i class="bi bi-plus-circle"></i> Add Category
</button>
<!-- Modal -->
<div class="modal fade" id="exampleModalCenter" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<form method="post" asp-action="Create" asp-controller="Categories">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle"><i class="bi bi-plus-circle"></i> Add Category</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label class="text-muted" asp-for="Name"></label>
<input type="text" asp-for="Name" class="form-control" />
<span class="text-danger" asp-validation-for="Name"></span>
</div>
<div class="form-control">
<label class="text-muted" asp-for="hierarchyId"></label>
<select class="form-control" asp-for="hierarchyId" id="hierarchyId">
#foreach (var item in ViewBag.AllCAT)
{
<option value="#item.Id">#item.Name</option>
}
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-sm btn-primary">Save</button>
</div>
</div>
</div>
</form>
</div>
And finally, when I send data from the view, the name is sent correctly but the HierarchyId column is null like this :
enter image description here
I do not know why that is the case... I have searched a lot and have not yet found an explanation
I'd appreciate it if you can tell my why this is the case.
Model Binding can’t bind HierarchyId type automatically , so the parentId will show ‘null’ in your controller, You can get this property manually by using Request.Form["parentId"].
The <select> passes the value through the 'value' attribute in <option>.In your code,Id is int, It does not match the type of parentId,so I replaced it with parentId.
_addCategory.cshtml
#model Country
<!-- Button trigger modal -->
<button type="button" class="btn btn-sm mb-2 btn-primary" data-toggle="modal" data-target="#exampleModalCenter">
<i class="bi bi-plus-circle"></i> Add Category
</button>
<!-- Modal -->
<div class="modal fade" id="exampleModalCenter" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<form method="post" asp-action="Create" asp-controller="Home">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle"><i class="bi bi-plus-circle"></i> Add Category</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label class="text-muted" asp-for="Name"></label>
<input type="text" asp-for="Name" class="form-control" />
<span class="text-danger" asp-validation-for="Name"></span>
</div>
<div class="form-control">
<label class="text-muted" asp-for="parentId"></label>
<select class="form-control" asp-for="parentId" id="hierarchyId">
#foreach (var item in ViewBag.Country)
{
<!--change here...-->
<option value="#item.parentId">#item.Name</option>
}
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-sm btn-primary">Save</button>
</div>
</div>
</div>
</form>
</div>
Controller
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult Create() {
//For the convenience of testing, I hard-coded here
ViewBag.Country = new List<Country>()
{
new Country(){ Id=1,Name= " USA ",parentId = HierarchyId.Parse("/1/") },
new Country(){ Id=2,Name= "Canada",parentId =HierarchyId.Parse("/2/") },
new Country(){ Id=3,Name= "China",parentId = HierarchyId.Parse("/3/") },
new Country(){Id=4,Name= "Japan" ,parentId = HierarchyId.Parse("/4/")}
};
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<object> Create(Country country)
{
//Model Binding can’t bind HierarchyId automatically,
//so I use ‘Request.Form’ to accept parentId, and convent parentId to string type and use PId to accept it, Then I convert PId to HierarchyId and Assign this value to parentId
var PId = Request.Form["parentId"].ToString();
country.parentId = HierarchyId.Parse(PId);
//do your stuff....
return View();
}
}
You can see the result here

Partial that expands into input tag helper

The book Pro ASP.NET Core 3 has the the following code in its example for chapter 27:
<div class="m-2">
<h5 class="bg-primary text-white text-center p-2">HTML Form</h5>
<form asp-page="FormHandler" method="post" id="htmlform">
<div class="form-group">
<label>Name</label>
<input class="form-control" asp-for="Product.Name" />
</div>
<div class="form-group">
<label>Price</label>
<input class="form-control" asp-for="Product.Price" />
</div>
<div class="form-group">
<label>Category</label>
<input class="form-control" asp-for="Product.Category.Name" />
</div>
<div class="form-group">
<label>Supplier</label>
<input class="form-control" asp-for="Product.Supplier.Name" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<button form="htmlform" asp-page="FormHandler" class="btn btn-primary mt-2">
Sumit (Outside Form)
</button>
</div>
As you can see, the <div class="form-group>...</div> pattern is repeated four times. As a test of partials, I added the file Pages\_FormGroupPartial.cshtml:
#model FormGroupParams
<div class="form-group">
<label>#Model.LabelContent</label>
<input class="form-control" asp-for="#Model.ForString" />
</div>
In Pages\FormHandler.cshtml.cs I added the following class:
public class FormGroupParams
{
public string LabelContent;
public string ForString;
public FormGroupParams(string labelContent, string forString)
{
LabelContent = labelContent;
ForString = forString;
}
}
To test this out, I edited Pages\FormHandler.cshtml to use the new partial right after the original approach:
<div class="form-group">
<label>Supplier</label>
<input class="form-control" asp-for="Product.Supplier.Name" />
</div>
<partial name="_FormGroupPartial" model='new FormGroupParams("Supplier", "Product.Supplier.Name")' />
In the resulting page, we see that the partial did not quite generate the same code; note that the value is now Product.Supplier.Name instead of the expected Splash Dudes:
Looking at the source, we see that the original code:
<div class="form-group">
<label>Supplier</label>
<input class="form-control" asp-for="Product.Supplier.Name" />
</div>
expanded into:
<div class="form-group">
<label>Supplier</label>
<input class="form-control" type="text" id="Product_Supplier_Name" name="Product.Supplier.Name" value="Splash Dudes" />
</div>
Whereas our partial:
<partial name="_FormGroupPartial" model='new FormGroupParams("Supplier", "Product.Supplier.Name")' />
expanded into:
<div class="form-group">
<label>Supplier</label>
<input class="form-control" type="text" id="ForString" name="ForString" value="Product.Supplier.Name" />
</div>
Is there a good way to implement this partial so as to get the same output as the original code?
UPDATE 2020-09-30
PeterG suggests the following in his answer below:
<partial name="_FormGroupPartial" model='new FormGroupParams("Supplier", #Model.Product.Supplier.Name)' />
This expands into the following:
<div class="form-group">
<label>Supplier</label>
<input class="form-control" type="text" id="ForString" name="ForString" value="Splash Dudes" />
</div>
whereas the goal is the following:
<div class="form-group">
<label>Supplier</label>
<input class="form-control" type="text" id="Product_Supplier_Name" name="Product.Supplier.Name" value="Splash Dudes" />
</div>
So the value is now correct. But the id and name are still off.
It will be very complex to bind a varibale to input tag helper and make it be recognized as the corresponding model field.
This will involve the source code of the input tag helper, and consider different types of input.
What I suggest is to directly add the name and id attributes after asp-for in the partial view to overwrite the original generation.
You only need to pass an extra value parameter when calling the partial view:
Here is the code:
Pages\FormHandler.cshtml:
<div class="form-group">
<label>Supplier</label>
<input class="form-control" asp-for="Product.Supplier.Name" />
</div>
<partial name="_FormGroupPartial"
model='new FormGroupParams("Supplier","Product.Supplier.Name", #Model.Product.Supplier.Name)' />
Pages\FormHandler.cshtml.cs:
public class FormGroupParams
{
public string LabelContent;
public string ForString;
public string Value;
public FormGroupParams(string labelContent, string forString, string value)
{
LabelContent = labelContent;
ForString = forString;
Value = value;
}
}
Pages_FormGroupPartial.cshtml:
#model FormGroupParams
<div class="form-group">
<label>#Model.LabelContent</label>
<input class="form-control" asp-for="#Model.Value" id="#Model.ForString.Replace(".","_")" name="#Model.ForString" />
</div>
Here is the rendered html :
The author of the book, Adam Freeman, has provided an example to me via email which demonstrates solving this using a tag helper.
Add the file TagHelpers/FormGroupTagHelper.cs:
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace WebApp.TagHelpers
{
[HtmlTargetElement("form-group")]
public class FormGroupTagHelper : TagHelper
{
public string Label { get; set; }
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
output.Attributes.SetAttribute("class", "form-group");
var label = new TagBuilder("label");
label.InnerHtml.Append(Label ?? For.Name);
var input = new TagBuilder("input");
input.AddCssClass("form-control");
input.Attributes["type"] = "text";
input.Attributes["id"] = For.Name.Replace(".", "_");
input.Attributes["name"] = For.Name;
input.Attributes["value"] = For.Model.ToString();
output.Content.AppendHtml(label);
output.Content.AppendHtml(input);
}
}
}
Now, in Pages/FormHandler.cshtml instead of this:
<div class="form-group">
<label>Supplier</label>
<input class="form-control" asp-for="Product.Supplier.Name" />
</div>
you can use the following:
<form-group label="Supplier" for="Product.Supplier.Name"/>
Thanks Adam!
I think the issue is caused by passing "Product.Supplier.Name" as the string value into your FormGroupParams object for the partial model. Instead you need to pass the actual value of the Product.Supplier.Name property from the model.
Something like this maybe:
<partial name="_FormGroupPartial" model='new FormGroupParams("Supplier", #Model.Product.Supplier.Name)' />
Here's an approach which uses a template function instead of a partial.
The following code:
<div class="form-group">
<label>Name</label>
<input class="form-control" asp-for="Product.Name" />
</div>
<div class="form-group">
<label>Price</label>
<input class="form-control" asp-for="Product.Price" />
</div>
<div class="form-group">
<label>Category</label>
<input class="form-control" asp-for="Product.Category.Name" />
</div>
<div class="form-group">
<label>Supplier</label>
<input class="form-control" asp-for="Product.Supplier.Name" />
</div>
can be replaced with:
#{
await Template("Name", "Product.Name");
await Template("Price", "Product.Price");
await Template("Category", "Product.Category.Name");
await Template("Supplier", "Product.Supplier.Name");
}
given the following template function at the beginning of the Razor page:
#{
async Task Template(string label, string str)
{
var expr = Model.CreateModelExpression(str);
<div class="form-group">
<label>#label</label>
<input class="form-control" asp-for="#expr.Model" />
</div>
}
}
The CreateModelExpression method referenced there is defined in the page model:
public class FormHandlerModel : PageModel
{
private ModelExpressionProvider _modelExpressionProvider { get; set; }
private DataContext context;
public FormHandlerModel(DataContext dbContext, ModelExpressionProvider modelExpressionProvider)
{
context = dbContext;
_modelExpressionProvider = modelExpressionProvider;
}
public ModelExpression CreateModelExpression(string expr)
{
var dict = new ViewDataDictionary<FormHandlerModel>(ViewData);
return _modelExpressionProvider.CreateModelExpression(dict, expr);
}
...
}
Note that a ModelExpressionProvider is brought in via dependency-injection.
The above Razor page on github: link.
The above page model on github: link.
Suggestions on how to improve this are welcome!

MVC Core popup modal parameters

I have a scenario where i should create number of buttons based on no. of items on DB.
and when a button is clicked, a popup modal should appear with a submit button.
when i submit, the parameters that is selected is not passed to my post controller Activate(Code code)
Can anyone help ?
I have this model:
public class Code
{
public int CodeId { get; set; }
public string CodeName { get; set; }
public string CodeColor { get; set; }
public int PagerNo { get; set; }
}
and This is my controller:
public IActionResult Index()
{
return View(_code.Entity.GetAll());
}
[HttpPost]
public IActionResult Activate(Code code)
{
int codeId = code.CodeId;
return RedirectToAction("Index");
}
And my View:
#model IEnumerable<CoreLibrary.Entities.Code>
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<div class="container">
#foreach (var item in Model)
{
<button class="btn btn-success btn-lg" data-toggle="modal" data-target="#item-#item.CodeId">
#item.CodeName
</button>
}
#foreach (var item in Model)
{
<form asp-action="Activate" asp-controller="Activation" method="post">
<div class="modal fade" id="item-#item.CodeId" tabindex="-1" role="dialog" aria-labelledby="ModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="ModalLongTitle">Are you sure?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
#item.CodeName will be activated !
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<input type="submit" class="btn btn-primary form-control" value="Submit" />
</div>
</div>
</div>
</div>
</form>
}
</div>
You can directly add hidden field which name is CodeId , that will bind to Code object during model binding :
#foreach (var item in Model)
{
<form asp-action="Activate" asp-controller="home" method="post">
<input name="CodeId" type="hidden" value="#item.CodeId"> <-- pass CodeId
<div class="modal fade" id="item-#item.CodeId" tabindex="-1" role="dialog" aria-labelledby="ModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="ModalLongTitle">Are you sure?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
#item.CodeName will be activated !
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<input type="submit" class="btn btn-primary form-control" value="Submit" />
</div>
</div>
</div>
</div>
</form>
}
See below: I have added hidden fields
#model IEnumerable<CoreLibrary.Entities.Code>
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<div class="container">
#foreach (var item in Model)
{
<button class="btn btn-success btn-lg" data-toggle="modal" data-target="#item-#item.CodeId">
#item.CodeName
</button>
}
#foreach (var item in Model)
{
<form asp-action="Activate" asp-controller="Activation" method="post">
#Html.HiddenFor(m => item.CodeId)
#Html.HiddenFor(m => m.CodeName)
#Html.HiddenFor(m => m.CodeColor)
#Html.HiddenFor(m => m.PagerNo)
<div class="modal fade" id="item-#item.CodeId" tabindex="-1" role="dialog" aria-labelledby="ModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="ModalLongTitle">Are you sure?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
#item.CodeName will be activated !
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<input type="submit" class="btn btn-primary form-control" value="Submit" />
</div>
</div>
</div>
</div>
</form>
}
</div>

ASP Core tag helper with inheritance in model

Let's say we have these simplified models:
public class Person {
public string Address {set;get;}
}
public class Student: Person {
public float Grade {set;get;}
}
public class Teacher: Person {
public string Department {set; get;}
}
Now we want to have a create page for student and teacher. my question is how we can use benefits of inheritance to create only one page for student and teacher?
I tried this:
#model Person
#{
bool isStudent = Model is Student;
}
<form asp-action="Create">
<div class="form-horizontal">
#if (isStudent)
{
<div class="form-group">
<label asp-for="((Student)Model).Grade" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="((Student)Model).Grade" class="form-control" />
<span asp-validation-for="((Student)Model).Grade" class="text-danger"></span>
</div>
</div>
} else {
<div class="form-group">
<label asp-for="((Teacher)Model).Department" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="((Teacher)Model).Department" class="form-control" />
<span asp-validation-for="((Teacher)Model).Department" class="text-danger"></span>
</div>
</div>
}
<div class="form-group">
<label asp-for="Address" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Address" class="form-control" />
<span asp-validation-for="Address" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-success" />
</div>
</div>
</div>
</form>
but it does not allow asp-for="((Student)Model).Grade"
found it:
According to https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms#the-input-tag-helper
"#" character can be used to start an inline expression
So this is correct:
asp-for="#(Model as Student).Grade"
Look into this ViewModel with inheritance in ASP .NET Core discussion on github:
It suggests a solution where a subclass tag-helper surround the fields that are specific to each subclass. That fields are rendered either if the model is null or if a specific subclass of that class is passed.
Here is the example: link

Categories

Resources