Use single error message template for all invalid properties - asp.net

Imagine a razor page with a Form that have many inputs that user fills them.
with post method when it wants to validate the posted model like this :
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page(model);
}
}
If for example 3 property of that model (with names : a,b,c) are not valid, it turns back to the razor view and shows the error (because of asp-validation-for for each property) like this :
The a field is required.
The b field is not a valid e-mail address.
The c field is required.
I want to show a specific error for all of them like this :
This Input is not valid.
This Input is not valid.
This Input is not valid.
I know I can use (ErrorMessage ="") for each of them separately, but its not logical in big size! is there any way to show a specific massage for all of invalid ModelStates?
Edit:
For example before showing errors in View, change their error message like this :
#foreach (var error in modelStateErrors)
{
error.text = "Fill it";
}

I created a solution with an extension method for ModelState.
It basically removes any errors from the state and adds them back with the desired message.
Create a ModelStateExtensions.cs in your namespace:
public static class ModelStateExtensions
{
public static void SetAllErrorMessages(this ModelStateDictionary modelState, string errorMessage)
{
foreach (var state in modelState)
{
if (state.Value.Errors.Count > 0)
{
modelState.Remove(state.Key);
modelState.AddModelError(state.Key, errorMessage);
}
}
}
}
Then if your ModelState is invalid you can transform the message before returning the page:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
ModelState.SetAllErrorMessages("Your message here");
return Page(model);
}
}

I know I can use ErrorMessage for each of them separately, but its not
logical! is there any short way to show a specific massage for all of
invalid ModelStates?
As for this issue, I think the easiest way to display the error message is using the ErrorMessage, If you want to display them at together, you could use the asp-validation-summary attribute, like this:
<div asp-validation-summary="All" class="text-danger"></div>
If you don't want to use the above method, you could also get the invalid fields from the ModelState dictionary, then, re-generate the error message. code like this:
public IActionResult OnPostAsync()
{
if (!ModelState.IsValid)
{
//get the new error message, you could also get all inValid fields.
var messages = ModelState.Keys
.SelectMany(key => ModelState[key].Errors.Select(x => string.Format("The {0} is invalid", key)))
.ToList();
ViewData["message"] = messages; //transfer the error message to the view
return Page();
}
return RedirectToPage("./Index");
}
View code (display the error message(without using asp-validation-for and asp-validation-summary)):
<div class="form-group">
#if (ViewData["message"] != null)
{
foreach (var item in (List<string>)ViewData["message"])
{
<span class="text-danger">#item</span><br/>
}
}
<div id="debug">
</div>
</div>
The output as below:
[Note] The above method is the server side validation. If you want to achieve the same behavior using Client validation, you have to get the client side validation result using JavaScript, and then generate the new error message.
So, in my opinion, I suggest you could try to use the first method (using Error Message and asp-validation-summary) to display the error message, and by using the Error Message for each of properties separators, user could easier to understand the validation rules.

If you don't want to make changes to each and every Razor Page, you can use a Page Filter to clear and rename the error messages automatically.
Here's an example Page Filter:
public class ModelStatePageFilter : IPageFilter
{
public void OnPageHandlerExecuted(PageHandlerExecutedContext ctx) { }
public void OnPageHandlerExecuting(PageHandlerExecutingContext ctx)
{
foreach (var (k, v) in ctx.ModelState
.Where(x => x.Value.ValidationState == ModelValidationState.Invalid))
{
v.Errors.Clear();
v.Errors.Add("This Input is not valid.");
}
}
public void OnPageHandlerSelected(PageHandlerSelectedContext ctx) { }
}
You'll need to register this Page Filter in Startup.ConfigureServices. Here's an example of how to do that:
services.AddRazorPages()
.AddMvcOptions(o => o.Filters.Add(new ModelStatePageFilter()));

You can use the Validation Summary (see : https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-validation-tag-helpers).
#model RegisterViewModel
<form asp-controller="Demo" asp-action="RegisterValidation" method="post">
<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>
If you want to change the displayed error message, you can do it in your ViewModel:
[Required(ErrorMessage = "This Input is invalid")]
public string Email { get; set; }
[Required(ErrorMessage = "This Input is invalid")]
public string Password{ get; set; }

Related

Asp.Net Core Remote validation attribute not making call

I have set the Remote attribute to validate that my username is Unique but when I debug it's not firing. What am I messing up?
In my View Model here is the attribute and property:
[Required]
[Remote("VerifyUsername", "Account")]
public string Username { get; set; }
In my form my form attribute is:
<input asp-for="Username" class="form-control mb-4" placeholder="Username" />
<span asp-validation-for="Username"></span>
And in my controller I have tried:
public JsonResult VerifyUsername(string username)
{
if (!_user.UsernameUnique(username))
{
return Json($"{username} is already in use.");
}
return Json(true);
}
And the method format:
[AcceptVerbs("Get", "Post")]
public IActionResult VerifyUsername(string username)
{
if (!_user.UsernameUnique(username))
{
return Json($" {username} is already in use.");
}
return Json(true);
}
I enter a usernam and click around and try tabbing... nothing gets the remote validation to fire. Anyone see what I am missing?
So I found it... kind of a facepalm. I was using another library that was loading another version of jquery. I was not getting an error though so that was weird. I removed that script reference for that other version of jquery and so it just had the current version and it all worked.

Partial refresh in ASP using ajax

How do you do a partial refresh of a page using Ajax in cshtml?
As I understand it, Ajax is required. In my scenario, in my index page I have a table where each row has a program (a batch file) and a Run button. Beneath the table I have the a space for the program output. I would like this to populate (I'm happy to wait for the program to finish just now) without having to refresh the rest of the page.
Code is below, but in summary I have one model for the table data, one model for the selected program log/output. The controller for the index page creates both and passes them in to a view model, which is passed to the view. When the Run button is hit an Index overload method in the controller handles the running of the program and 'getting' the output. It also populates the appropriate model in the VM (possibly not ideal and I'm open to suggestions to improve it).
The overloaded method currently returns a PartialViewResult and the output/logging has it's own PartialView (as I'll want to reuse it elsewhere). This is also why it has a separate model. In the PartialView break points are hit, but it doesn't appear on the page in the browser.
I'm using ASP.NET-MVC-4 with Razor.
View (Index.cshtml)
<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
#model ViewModels.UpdateTestViewModel
#{ ViewBag.Title = "Update Test"; }
#{
<table>
#* Headers *#
<tr>
<td>Programs</td>
</tr>
#* Data *#
<tr>
<td>#Model.ProgramName</td>
<td style="min-width:75px"><input id="btnRun" type="button" value="Run" /></td>
</tr>
</table>
<div id="log">
#Html.Partial("ScriptLog", Model.Log)
</div>
<script>
$("input[type=button]").on("click", function () {
var NAME = ($(this).parent().siblings(":first")).text();
$.post("/UpdateTest/Run", { input: NAME });
});
</script>
}
Partial View
#model Models.ScriptLog
#if (Model != null && Model.Log.Any(x => !string.IsNullOrWhiteSpace(x)))
{
<fieldset>
<legend>Log</legend>
#foreach (string entry in Model.Log)
{
<p>#entry</p>
}
</fieldset>
}
Script Log
public IEnumerable<string> Log { get { // returns log } }
ViewModel
public class UpdateTestViewModel
{
public string ProgramName { get { return "My Program"; } }
public ScriptLog Log { get { return _log; } }
private readonly ScriptLog _log;
public UpdateTestViewModel(ScriptLog log)
{
_log = log;
}
}
Controller
public ActionResult Index()
{
if (SessionFacade.CurrentUpdateTestLog == null)
{
ScriptLog log = new ScriptLog();
SessionFacade.CurrentUpdateTestLog = log; // Store in Session
}
UpdateTestViewModel vm = new UpdateTestViewModel(SessionFacade.CurrentUpdateTestLog);
return View(vm);
}
[ActionName("Run")]
public PartialViewResult Index(string input)
{
ExecuteScript.ExecuteUpdateTestScript(input); // Run batch file
UpdateTestLog(input); // Get log and update in Session
return PartialView("ScriptLog", SessionFacade.CurrentUpdateTestLog);
}
Since you are making an $.post() you need to decorate your /UpdateTest/Run action with [HttpPost].
You also don't define a success handler so, while you are making the request, you never do anything with it.
$.post("/UpdateTest/Run", { input: NAME })
.done(function(partialResult) {
$("#log").html(partialResult);
})
.fail(function(jqxhr, status, error) {
console.log(jqXhr, status, error);
});
With much help and patience from #Jasen I got a working solution which was to extend the existing Ajax, so that it looks like:
$("input[type=button]").on("click", function () {
var NAME = ($(this).parent().siblings(":first")).text();
$.post("/UpdateTest/Run", { input: NAME })
.done(function (partialResult) {
$("#log").html(partialResult);
})
});
Note I also have added the [HttpPost] attribute in the controller

ASP.NET MVC Model validation error beside field

I have a self validation model in my code based on this link:
ASP.NET MVC: Custom Validation by DataAnnotation
public class TestModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("The title is mandatory.", new [] { "Title" });
if (Description == null)
yield return new ValidationResult("The description is mandatory.", new [] { "Description" });
}
}
All of this works well. But my question is this: The error messages above are displayed as ValidationSummary errors. Is there any way to make the title error message display beside the title field (on the form view) and the description error message display beside the description field, just like in client side validation?
First make sure that you have added the correct razor markup next to each field, for example:
#Html.ValidationMesageFor(m => m.Title)
This will display the error message only if there is an error in the ModelState that is associated with the same field, so ensure ModelState["Title"] contains the error, otherwise you won't see the message.
This all is customized, at least, by CSS, or you can always use javascript (jquery).
UPDATE:
Well, i think, this is quite little info, therefore, try to add a some more.Basically, you also can use html.helper ValidationMessageFor + span tag. For instance:
#Html.EditorFor(x=>x.ModelProperty)<span>#Html.ValidationMessageFor(x=>x.ModelProperty)</span>
And in action method after your magic object validates itself, analyze result of your validation.
Just raugh demo:
var result = myObject.Validate(someContext);
if (result != result.Success)
{
ModelState.AddModelError("Title"); // if error in Title prop (otherwise "Description")
return View (myObject);
}
Or, if your model is validated through ValidationAttribute, you can check out this via ModelState.IsValid or ModelState.IsValidField methods.

How do I call an Index action and conditionally pass it a value in an ASP.NET MVC app

I have an index action on a controller as follows...
public ActionResult Index(string errorMsg = "")
{
//do stuff
ViewBag.ErrorMsg=erorMsg;
return View();
}
I have another action that is an http post for Index.
When there is something wrong I want to reload the Index page and show the error...
I have my view already conditionally showing errorMsg. But I cannot figure out how to call Index and pass in the error string?
Typically, you'd just share the view between the two actions. I'm guessing you have actions that look something like this (the more info you provide about what index does, the better my example will be):
public ActionResult Index()
{
return View();
}
[HttpPost, ActionName("Index")]
public ActionResult IndexPost()
{
if (!ModelState.IsValid)
{
ViewBag.ErrorMsg = "Your error message"; // i don't know what your error condition is, so I'm just using a typical example, where the model, which you didn't specify in your question, is valid.
}
return View("Index");
}
And Index.cshtml
#if(!string.IsNullOrEmpty(ViewBag.ErrorMsg))
{
#ViewBag.ErrorMsg
}
#using(Html.BeginForm())
{
<!-- your form here. I'll just scaffold the editor since I don't know what your view model is -->
#Html.EditorForModel()
<button type="Submit">Submit</button>
}
If I understand you correctly you just need to hit the url with the errorMsg in the query string:
/*controllername*/index?errorMsg=*errormessage*
However, when there is something wrong you don't necessarily need to reload the page. Seems like you might be approaching this in the wrong way..?
You can use RedirectToAction to redirect to the page, with a querystring for errorMsg value.
[HttpPost]
public ActionResult Index(YourViewModel model)
{
try
{
//try to save and then redirect (PRG pattern)
}
catch(Exception ex)
{
//Make sure you log the error message for future analysis
return RedirectToAction("Index",new { errorMs="something"}
}
}
RedirectToAction issues a GET request. So your form values will be gone, because HTTP is stateless. If you want to keep the form values as it is in the form, return the posted viewmodel object again. I would get rid of ViewBag and add a new property called ErrorMsg to my ViewModel and set the value of that.
[HttpPost]
public ActionResult Index(YourViewModel model)
{
try
{
//try to save and then redirect (PRG pattern)
}
catch(Exception ex)
{
//Make sure you log the error message for future analysis
model.ErrorMsg="some error";
return View(model);
}
}
and in the view you can check this model property and show the message to user.

Simple Form update without DB persistence

I have a simple ASP.NET MVC 3 dummy app (just learning MVC coming from WebForms).
And I'm pretty confused about how to update a form without actually having some DB stuff in between. I just have a form with a textbox and after I press the button I want to see the string in uppercase. But my nothing happens.
The controller:
public ActionResult Index()
{
ToUppercaseModel model = new ToUppercaseModel { TheString = "testing" };
return View(model);
}
[HttpPost]
public ActionResult Index(ToUppercaseModel model)
{
model.TheString = model.TheString.ToUpper();
return View(model);
}
The model:
public class ToUppercaseModel
{
[Display(Name = "My String")]
public string TheString { get; set; }
}
And the view:
#using (Html.BeginForm()) {
<div>
<div class="editor-label">
#Html.LabelFor(m => m.TheString)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.TheString)
</div>
<p>
<input type="submit" value="Convert" />
</p>
</div>
}
This is a simple as it gets I think. Now obviously the return View(model); in the 2nd Index method is not working. I saw some stuff about RedirectToAction() and storing the data in TempData. Most example just submit some id, but since I don't have db that does not work.
If I do this:
return RedirectToAction("Index", model);
I get a
No parameterless constructor defined for this object.
Error Message. This should be simple, no? I think I understand the Post/Redirect/Get concept, but don't see how to apply it for something simple as this.
Thanks for some clarification.
When MVC renders a view it will use the attempted value of a field rather than the model's value if it exists (eg in a datefield I put "Tuesday", this won't model bind but you'll want to show the user the field with their input and highlighted as invalid), you're changing the model's value but not the attempted value.
The attempted value is held in the modelstate dictionary:
ModelState["KeyToMyValue"].Value.Value.AttemptedValue
Accessing and changing these values can be tricky unless you want a load of magic strings in your code, and as validation happens on modelbinding your changed value won't be validated.
My recommendation in these circumstances is to call ModelState.Clear(), this will remove all validation and attempted values, then change your model directly. Finally you want to get your validation on the model by using TryValidateModel(yourModel).
Be aware that this method is probably the easiest non-hacky method of doing this but will remove attempted values that could not bind from the returned view.
You have to call 1 method from the 2 method, but you have to change it
public ActionResult Index(ToUppercaseModel model)
and send to 1 method your model.
public ActionResult Index(ToUppercaseModel? model)
{
if (model == null)
ToUppercaseModel model = new ToUppercaseModel { TheString = "testing" };
return View(model);
}
I think I got a solution, it works, but not sure if this is the way it should be?
Basically I just put my model into the TempData and call the normal Index method again.
public ActionResult Index()
{
ToUppercaseModel model = null;
if (TempData["FeaturedProduct"] == null)
{
model = new ToUppercaseModel { TheString = "testing" };
}
else
{
model = (ToUppercaseModel)TempData["FeaturedProduct"];
}
return View(model);
}
[HttpPost]
public ActionResult Index(ToUppercaseModel model)
{
model.TheString = model.TheString.ToUpper();
TempData["FeaturedProduct"] = model;
//return View(model);
return RedirectToAction("Index");
}

Resources