Custom Tag helper debugging - c#

I'm trying to test a new tag helper that I'm trying to create. I've stumbled into a shortcoming of the new .net core validation and cannot change the class post validation. So if I wanted to give my errors a red background, the span is always there and won't change. So, I've decided to make my own tag helper. the problem is I can't seem to get it to work or trigger. I can't even get it to hit a break point. Here is what I have for a tag helper so far.
namespace MusicianProject.TagHelpers
{
// You may need to install the Microsoft.AspNetCore.Razor.Runtime package into your project
[HtmlTargetElement("invalid-class", Attributes = "validation-class")]
public class ValidateClassTagHelper : TagHelper
{
public ValidateClassTagHelper(IHtmlGenerator generator)
{
}
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
return base.ProcessAsync(context, output);
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes.Add("class", "test");
var attr = context.AllAttributes;
}
}
}
and here is the usage in my register view.
<div class="container">
<form asp-controller="Account" asp-action="Register" method="post">
<div class="col-md-4 col-md-offset-4">
<div class="form-group">
<label asp-for="FirstName"></label>
<input class="form-control" type="text" asp-for="FirstName" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName"></label>
<input class="form-control" asp-for="LastName" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Email"></label>
<input class="form-control" type="text" asp-for="Email" />
<span validation-class="alert alert-danger" invalid-class="test" asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" type="password" id="password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword" type="password" id="confirm-password" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<div class="btn-group text-center">
<button class="btn btn-default">Sign up!</button>
<button class="btn btn-danger">Cancel</button>
</div>
</div>
</form>
</div>
#section Scripts {
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
And yes I have register in my _ViewImports.cshtml file, the projects assembly name.
#using MusicianProject
#using MusicianProject.Models
#addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
#addTagHelper "*, MusicianProject"
Now I'm not sure if placement of certain files matters, but my _ViewImports.cshtml file is located within my views folder root (src/Views/_ViewImports.cshtml), and my tag helpers have their own folder in the root (src/TagHelpers/*).
What did I miss and how can I correct it?

You have 2 issues
1- The HtmlTargetElement is the name of the tag where this tag helper can be used, ex: span, div, table ...
2- You didn't use the tag helper in your code, accordingly, it will never get fired. it is not applied by default to all the tags specified in the HtmlTargetElement, you should call it by adding the validate-class attribute to the span tag.
To know more about tag helpers, please check this link https://blogs.msdn.microsoft.com/msgulfcommunity/2015/06/17/developing-custom-tag-helpers-in-asp-net-5/

If I understood correctly your problem is that your breakpoint doesn't get hit. That problem lies here:
[HtmlTargetElement("invalid-class", Attributes = "validation-class")]
The first parameter of HTMLTargetElement is the tag you are targeting, so in your case that would be "span". You entered a class name there.
This way at least your breakpoints will get hit and from there I'm sure you'll figure out the rest.

Related

Why do I receive null values in my controller when I submit a form from a view with multiple forms? With ASP.NET Core 5 MVC

I am developing a web application using ASP.NET Core 5 MVC, in which I seek to make multiple submissions of POST type forms to my controller from a view that receives an IEnumerable (from a model called Result) with which I am filling in dynamically the values of the inputs of each form.
However, when I send one of those forms from the view through the controller, in the controller I only receive an object from the model with all the null or empty values, which tells me that it seems that this data was never sent through the form to my controller.
Is there a better way to accomplish this or how do I pass these values from multiple forms to my controller? In advance an apology if what I am doing is already totally wrong, I have been learning ASP.NET Core MVC for a few days.
CONSIDERATIONS
What I seek to achieve is that the user can enter multiple values that belong to the same model in the same view, since each form although it is the same seeks to update a different record or column in the same model or table, so when the submit of the form is sent to the Controller the view does not change, and only the records in the database are updated with each submit in the view. If there is a better way or correct way to do this, I am willing to change the logic, because as I mentioned, I have been using the Framework for little.
Explained the problem and my goal, I will explain in greater detail the flow and code mentioned:
From the Mechanical method of my controller, I return a list of Objects to their corresponding View, which are brought by a DataBaseContext:
// CONTROLLER() that passes an enumerable list of Objects to View Mechanical.cshtml
public IActionResult Mechanical()
{
IEnumerable<Result> objList = _db.Results;
return View(objList);
}
In the Mechanical() view, I get this list of objects and iterate over it through a forEach() loop, where for each object I create a form that directs to the same controller method called Update(), in which I get the values of the object in null and empty (that being my problem):
// VIEW
#model IEnumerable<FatForm.Models.Result>
#if (Model.Count() > 0)
{
#foreach(var result in Model)
{
<form method="post" asp-action="Update">
<input asp-for="#result.Id" hidden />
<input asp-for="#result.Type" hidden />
<input asp-for="#result.FatId" hidden />
<div class="border p-3">
<div class="form-group row">
<h2 class="text-black-50 pl-3">Edit Result</h2>
</div>
<div class="row">
<div class="col-12">
<div class="form-group row">
<div class="col-3">
<label asp-for="#result.Section"></label>
</div>
<div class="col-3">
<label asp-for="#result.Procedure"></label>
</div>
<div class="col-3">
<label asp-for="#result.ResultDescription"></label>
</div>
<div class="col-3">
<label asp-for="#result.Passed"></label>
</div>
</div>
<div class="form-group row">
<div class="col-3">
<input asp-for="#result.Section" class="form-control" />
</div>
<div class="col-3">
<input asp-for="#result.Procedure" class="form-control" />
</div>
<div class="col-3">
<input asp-for="#result.ResultDescription" class="form-control" />
<span asp-validation-for="#result.ResultDescription" class="text-danger"></span>
</div>
<div class="col-3">
<input asp-for="#result.Passed" class="form-control" />
<span asp-validation-for="#result.Passed" class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<div class="col-8 offset-2 row">
<div class="col">
<input type="submit" class="btn btn-info w-75" value="Save" />
</div>
</div>
</div>
</div>
</div>
</div>
</form>
}
}
else
{
<p>No results created yet</p>
}
#section Scripts{
#{
<partial name="_ValidationScriptsPartial" />
}
}
I'm supposed to be looking to send the form values to the following Update() controller method, in which I get all the object values to null:
// POST Update
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Update(Result obj)
{
if (ModelState.IsValid)
{
_db.Results.Update(obj);
_db.SaveChanges();
//return RedirectToAction("Index");
}
return View(obj);
}
As I explained at the beginning, I hope can help me by indicating what I am doing wrong or in what way I should do it. Thanks in advance for your help.
Model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix.In your code,the asp-for tag helper will generate the name like:result.propertyName.It could not match with backend model. You need use [Bind(Prefix ="result")] to specify the prefix:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Update([Bind(Prefix ="result")]Result obj)
{
//do your stuff...
}
As for receive IEnumerable parameter in action, firstly you need change your razor view to move the form outside the foreach loop, then you need change the asp-for tag helper tattribute to #Model.ToList()[index].PropertyName:
#model IEnumerable<Result>
#if (Model.Count() > 0)
{
<form method="post" asp-action="Update">
#for (int i = 0; i < Model.Count(); i++)
{
<input asp-for="#Model.ToList()[i].Id" hidden />
<input asp-for="#Model.ToList()[i].Type" hidden />
<input asp-for="#Model.ToList()[i].FatId" hidden />
<div class="border p-3">
<div class="form-group row">
<h2 class="text-black-50 pl-3">Edit Result</h2>
</div>
<div class="row">
<div class="col-12">
<div class="form-group row">
<div class="col-3">
<label asp-for="#Model.ToList()[i].Section"></label>
</div>
<div class="col-3">
<label asp-for="#Model.ToList()[i].Procedure"></label>
</div>
<div class="col-3">
<label asp-for="#Model.ToList()[i].ResultDescription"></label>
</div>
<div class="col-3">
<label asp-for="#Model.ToList()[i].Passed"></label>
</div>
</div>
<div class="form-group row">
<div class="col-3">
<input asp-for="#Model.ToList()[i].Section" class="form-control" />
</div>
<div class="col-3">
<input asp-for="#Model.ToList()[i].Procedure" class="form-control" />
</div>
<div class="col-3">
<input asp-for="#Model.ToList()[i].ResultDescription" class="form-control" />
<span asp-validation-for="#Model.ToList()[i].ResultDescription" class="text-danger"></span>
</div>
<div class="col-3">
<input asp-for="#Model.ToList()[i].Passed" class="form-control" />
<span asp-validation-for="#Model.ToList()[i].Passed" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
}
<div class="form-group row">
<div class="col-8 offset-2 row">
<div class="col">
<input type="submit" class="btn btn-info w-75" value="Save" />
</div>
</div>
</div>
</form>
}
else
{
<p>No results created yet</p>
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Update(IEnumerable<Result> obj)
{
return View("Mechanical", obj);
}
Result:
Here you need to add FromBody attribute in your action method parameter.
public IActionResult Update([FromBody]Result obj)
{
.....
}

NET Core 3.1 MVC Input Validation does not work

In some viewmodel classes the [Required] attributes works as expected. When submitting the form, the error spans show an error message underneath the fields and the form is not submitted if the required fields do not meet the annotation requirements.
But in the example shared below, the form is submitted even if the required fields are empty (such as CompanyName) and I haven't figured out why, some help would be appreciated please.
Startup.cs
...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage(); // throw a classic error page is evironment is development
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
...
EditCompanyVM.
public class EditCompanyVM
{
public EditCompanyVM()
{
Contact = new Contact();
Locations = new List<CompanyLocation>();
}
public int CompanyID { get; set; }
[Required(ErrorMessage = "Company name is required.")]
public string CompanyName { get; set; }
[Required(ErrorMessage = "Company description is required.")]
public string CompanyDescription { get; set; }
[Url]
public string CompanyWebsite { get; set; }
public Contact Contact { get; set; }
public List<CompanyLocation> Locations { get; set; }
}
EditCompany.cshtml
#model MyApp.Models.ViewModels.EditCompanyVM
#{
ViewBag.Title = "Edit Company";
}
<div class="col-md-8">
<h4 style="margin:0 0 30px 0;">Edit Company</h4>
<form method="post">
<div class="form-group">
<label asp-for="CompanyID" class="control-label">Company ID</label>
<input asp-for="CompanyID" disabled class="form-control" />
<span asp-validation-for="CompanyID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CompanyName" class="control-label"></label>
<input asp-for="CompanyName" class="form-control" type="text" />
<span asp-validation-for="CompanyName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CompanyDescription" class="control-label">Company Description</label>
<input asp-for="CompanyDescription" class="form-control" />
<span asp-validation-for="CompanyDescription" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CompanyWebsite" class="control-label">Company Website</label>
<input asp-for="CompanyWebsite" class="form-control" />
<span asp-validation-for="CompanyWebsite" class="text-danger"></span>
</div>
<div id="contactSection">
<h4 style="margin:0 0 20px 0;">Contact Section</h4>
#if (Model.Contact != null)
{
<div class="form-group">
<label asp-for="Contact.ContactID" class="control-label">Contact ID</label>
<input asp-for="Contact.ContactID" disabled class="form-control" />
<span asp-validation-for="Contact.ContactID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Contact.FirstName" class="control-label">First Name</label>
<input asp-for="Contact.FirstName" class="form-control" />
<span asp-validation-for="Contact.FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Contact.LastName" class="control-label">Last Name</label>
<input asp-for="Contact.LastName" class="form-control" />
<span asp-validation-for="Contact.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Contact.PhoneNumber" class="control-label">Phone Number</label>
<input asp-for="Contact.PhoneNumber" class="form-control" />
<span asp-validation-for="Contact.PhoneNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Contact.Email" class="control-label">Email</label>
<input asp-for="Contact.Email" class="form-control" />
<span asp-validation-for="Contact.Email" class="text-danger"></span>
</div>
}
else
{
<a asp-action="AddContact" asp-route-userId="#Model.CompanyID" style="width:auto" class="btn btn-outline-primary">
Add Contact
</a>
}
</div>
<div id="locationsSection">
<h4 style="margin:20px 0 20px 0;">Company Locations</h4>
#if (Model.Locations.Any())
{
int counter = 1;
for (int i = 0; i < Model.Locations.Count; i++)
{
<h3>Location #counter</h3>
<div class="form-group">
<label asp-for="#Model.Locations[i].Street" class="control-label">Street</label>
<input asp-for="#Model.Locations[i].Street" class="form-control" />
<span asp-validation-for="#Model.Locations[i].Street" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.Locations[i].City" class="control-label">City</label>
<input asp-for="#Model.Locations[i].City" class="form-control" />
<span asp-validation-for="#Model.Locations[i].City" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.Locations[i].Province" class="control-label">Province</label>
<input asp-for="#Model.Locations[i].Province" class="form-control" />
<span asp-validation-for="#Model.Locations[i].Province" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.Locations[i].Country" class="control-label">Country</label>
<input asp-for="#Model.Locations[i].Country" class="form-control" />
<span asp-validation-for="#Model.Locations[i].Country" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.Locations[i].ZipCode" class="control-label">ZipCode</label>
<input asp-for="#Model.Locations[i].ZipCode" class="form-control" />
<span asp-validation-for="#Model.Locations[i].ZipCode" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.Locations[i].PhoneNumber" class="control-label">Location Phonenumber</label>
<input asp-for="#Model.Locations[i].PhoneNumber" class="form-control" />
<span asp-validation-for="#Model.Locations[i].PhoneNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.Locations[i].Email" class="control-label">Branch Email</label>
<input asp-for="#Model.Locations[i].Email" class="form-control" />
<span asp-validation-for="#Model.Locations[i].Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.Locations[i].Manager" class="control-label">Manager Name</label>
<input asp-for="#Model.Locations[i].Manager" class="form-control" />
<span asp-validation-for="#Model.Locations[i].Manager" class="text-danger"></span>
</div>
counter++;
}
}
else
{
<a asp-action="AddLocation" asp-route-id="#Model.CompanyID" style="width:auto;" class="btn btn-outline-primary">
Add Location
</a>
</div>
}
<div class="form-group row" style="margin-top:80px;">
<div class="col-sm-10">
<button type="submit" class="btn btn-outline-primary">Update</button>
<a asp-action="ListCompanies" class="btn btn-outline-primary">Cancel</a>
</div>
</div>
</form>
</div>
CompanyController.cs
[HttpPost]
public async Task<IActionResult> EditCompany (EditCompanyVM model)
{
// model is submitted with null fields, even though warnings should trigger in the view
// by asp-validation-for
return RedirectToAction("ListCompanies");
}
There's been a lot of discussion in the comments, so I'd like to clear some things up.
Client-side validation happens in a user's browser, and requires scripts to take place. If a form is not valid, the script can prevent the form from being submitted, and display error messages for a user to correct. This is an enhancement for users, and servers, as it stops the majority of invalid requests being sent to the server, when they could be handled by the user in their browser.
Server-side validation takes place in addition to client-side validation - client-side validation is the optional extra, not the other way around, because users cannot be trusted to be non-malicious. Server-side validation within ASP.NET Core takes place using a combination of data annotations, and validators, that culminate in ModelState. Server-side validation will not prevent a form from being submitted in a user's browser, but can still send errors back to the browser via ModelState and returning your current model:
[HttpPost]
public async Task<IActionResult> EditCompany(EditCompanyVM model)
{
// This property indicates if the model's data,
// based on the data annotations it's
// decorated with, is valid.
if (!ModelState.IsValid)
{
// Returning the same instance of the model back
// to the view causes Razor to use the `ModelState`'s
// data to render errors via `asp-validation-for`.
return View(model);
}
// If no errors, redirect.
return RedirectToAction("Index");
}
Now, if your question is why you're able to submit the form without validation first taking place, and subsequently preventing invalid data from being submitted, this is all because of the client-side validation - it has nothing to do with your controller at all; this is all down to what's going on in your view.
If you take a look in your ~Views/Shared folder, you should see a view called _ValidationScriptsPartial.cshtml. The default views prefixed with a _ are intended to be used as partial views, although this is a convention you don't have to follow with your own views. If you open it up, you'll see it contains the following:
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
These are scripts for client-side validation, but they're not included by default. Therefore, to fix your problem, simply add the following at the bottom of your EditCompany.cshtml view:
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
This will now render those scripts to the browser, loading client-side validation. Trying to submit your form now will trigger client-side validation, without hitting the server if the data is invalid.

.NET CORE_Blazor Cannot find Http.SendAsJsonAsync()

Please I have trying to call my web API request in .Net Core Blazor project. I cannot seem to find Http.SendAsJsonAsync what I have is Http.SendAsAsync Please what do I need to do? Does this mean that in 3.1 we cannot use SendAsJsonAsync or am I missing out a particular reference?
Please find the image screenshot and code below.
The sample code is here:
#page "/counter"
#using BlazeCRUD.Models;
#using BlazeCRUD.Services;
#using System.Net.Http;
#inject HttpClient Http
#inherits OwningComponentBase<BlazeServices>
<h3><b>New Loan Application</b></h3>
<p></p>
<div class="row">
<div class="col-md-4">
<form>
<div class="form-group">
<label for="Name" class="control-label">Name</label>
<input for="Name" class="form-control" bind="" />
</div>
<div class="form-group">
<label asp-for="Department" class="control-label">Department</label>
<input asp-for="Department" class="form-control" bind="" />
</div>
<div class="form-group">
<button type="submit" class="btn btn-default" onclick="">Save</button>
<button class="btn" onclick="">Cancel</button>
</div>
</form>
</div>
</div>
#code {
Performanceappuser pc = new Performanceappuser();
protected async Task AddLoanApplication()
{
await Http.
}
List<Performanceappuser> pau;
protected override void OnInitialized()
{
pau = Service.appUser();
}
}

pass textbox value as parameter to controller route

I'm trying to use a textbox value in controller route and am having some trouble with the syntax. Essentially, I have my end users selecting a file and then submitting the data to the api to process and send the data off. I have working static code below, but can't get the xml path to be dynamically populated via the textbox value.
note that (as far as I can tell) the forward slash at the end of the path is required since there is a dot in the file path.
#using (Html.BeginForm("", "api/food/dummy food file.xml/"))
{
<div class="row">
<div class="col-lg-6">
<label class="control-label">Select File</label>
<div class="input-group">
<label class="input-group-btn">
<span class="btn btn-default">
Browse… <input type="file" style="display: none;" single>
</span>
</label>
<input id="food.filepath" name="food.filepath" type="text" class="form-control" readonly>
</div>
</div>
</div>
<br />
<div>
<button id = "btnSubmit" type="submit" class="btn btn-primary">Submit</button>
</div>
}
I don't what the syntax would be, but I can't get something like the below to work.
#using (Html.BeginForm("", "api/food/" + food.filepath + "/"))
{
<div class="row">
<div class="col-lg-6">
<label class="control-label">Select File</label>
<div class="input-group">
<label class="input-group-btn">
<span class="btn btn-default">
Browse… <input type="file" style="display: none;" single>
</span>
</label>
<input id="food.filepath" name="food.filepath" type="text" class="form-control" readonly>
</div>
</div>
</div>
<br />
<div>
<button id = "btnSubmit" type="submit" class="btn btn-primary">Submit</button>
</div>
}
Because the form is rendered on server and the value food.filepath is on client, you cannot mix them. Changing form action according to client values need to be done on client with javascript.
You can remove the file from action a add it on submit javascript action, for example by changing BeginForm to this:
#using (Html.BeginForm("", "api/food/", FormMethod.Get, new { onSubmit = "this.dataset.act = this.dataset.act||this.action; this.action = this.dataset.act + this['food.filepath'].value" } ))

A form with multiple views in .NET

I am a junior developer and i find it hard to get this form to work. What i am trying to achieve is a multistep form where each view is a form that redirects to the next view containing further more fields for the user to input values into. I Think i got it right how i pass the Model(which is to be modified for every step). I have 4 views and 4 ActionResults. Step1, Step2 etc.
So how do i pass both the model and the chosen value from "Choose amount" and the custom amount?
This is what the markup looks like:
<form method="post" id="formStepOne" action="#Url.Action("Step1", "SupporterController", Model)">
<div class="row">
<div class="large-6 columns">
<label>Choose amount</label>
<input type="radio" name="belopp1" value="500" id="value1"><label for="value1">100 dollars</label>
<input type="radio" name="belopp2" value="350" id="value2"><label for="value2">200 dollars</label>
<input type="radio" name="belopp3" value="200" id="value3"><label for="value3">400 dollars</label>
</div>
<div class="large-4 columns">
<label>
Amount(minimum of 400 dollars)
<input type="text" placeholder="amount" />
</label>
</div>
</div>
<div class="large-12 columns">
<div class="row collapse">
<div class="small-2 columns">
<button type="submit" form="formStepOne" value="Submit">Fortsätt</button>
</div>
</div>
</div>
And this is what the controllers/actionResults look like
[HttpPost]
public ActionResult Step2(SupporterViewModel model)
{
//Validate and return
return PartialView("~/Views/SupporterBlock/SupporterStepThree.cshtml", model);
}

Categories