36

I am new to web development and trying to learn ASP.Net MVC 5. I am looking for one record in database if the record is not found then I want to display an error message to the user. Below is my attempt:

Controller

    [HttpGet]
    public ActionResult Search()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Search(ForgotPasswordMV viewModel)
    {
        if (Temp.Check(viewModel.Email))
            return RedirectToAction("VerifyToken", new { query = viewModel.Email });
        else
        {
            ViewBag.ErrorMessage = "Email not found or matched";
            return View();
        }
    }

View:

<p>@ViewBag.ErrorMessage</p>

ViewModel

public class ForgotPasswordMV
{
    [Display(Name = "Enter your email"), Required]
    public string Email { get; set; }
}

But I read somewhere that I should put one property in my view model and set the error message on that property. I am confused now, how to achieve that and how to display the error in View then? And which one is the recommended/best practice?

Unbreakable
  • 7,776
  • 24
  • 90
  • 171

5 Answers5

61

But I read somewhere that I should put one property in my view model and set the error message on that property. I am confused now, how to achieve that and how to display the error in View then? And which one is the recommended/best practice?

The best practice is to alter the ModelState dictionary property of your controller like this:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Search(ForgotPasswordMV viewModel)
{
    // ... 
    else
    {
        ModelState.AddModelError(nameof(ForgotPasswordMV.Email), "Email not found or matched");
        return View(viewModel);
    }
}

Then in your view add the line below next to your email field;

@Html.ValidationMessageFor(m => m.Email)
CodeNotFound
  • 22,153
  • 10
  • 68
  • 69
  • I like this solution as it is using Html Helper so I do not need to take care of other stuffs. So. I should follow this approach as best practice. But another answer to this question is more towards adding the error property to the Model. Can you please explain me What is actually ModelState.AddModelError is doing. I know if we put a field as `[Required]` and then we check the `modelstate.isvalid` then we can catch the error there. Is this `AddError` doing the same thing under the hood but its more custom made? – Unbreakable Apr 22 '17 at 16:16
  • Yes that is exact. This is the best practice because imagine that you have many properties on your model you will end up creating many error message property for each of your model properties which will end up bloating your model. Using `ModelState.AddModelError` help to avoid that kind of things. – CodeNotFound Apr 22 '17 at 16:19
  • One thing I noticed it that KeyName should be same as the property name so that it can tied to that particular property.So basically first parameter in `AddModelError` is kind of tells that to which property in our model we need to bind this error message to. Kindly rectify me if I am wrong. But anyways thank you for the answer. :) – Unbreakable Apr 22 '17 at 16:43
  • Yes. Also you can put an empty string as a key so the error message with be tied to the entire model. If you use thiat solution you must use this line `@Html.ValidationSummary(true, "The following error has occured:")` – CodeNotFound Apr 22 '17 at 16:46
  • So, it gives flexibility to add some general errors in forms. This clears my doubt. Thank you sire! – Unbreakable Apr 22 '17 at 16:48
  • 1
    @CodeNotFound, i had a similar situation and your solution worked like a charm for me. Much appreciated !! – Ron Aug 15 '17 at 14:05
  • Sir, One doubt. I understood that this solution is better than the other solution mentioned. But suppose if I have many errors, for the model properties, and then I need to display the error then how to handle it. I mean do I need to write so many `if statement` for each property explicitly and keep on adding it to the ModelState.AddModelError? – Unbreakable Sep 20 '17 at 20:37
  • `If(Temp.CheckEmail(viewModel.Email)`, `if(Temp.CheckUserName(viewMode.username)` so on and so forth. – Unbreakable Sep 20 '17 at 20:40
  • I have tried this but it adds the error message on page load? Any way to get around that? –  Jun 16 '20 at 20:05
6

But I read somewhere that I should put one property in my view model and set the error message on that property.

That's correct. You could add the error message to your view model:

public class ForgotPasswordMV
{
    [Display(Name = "Enter your email"), Required]
    public string Email { get; set; }

    public string ErrorMessage { get; set; }
}

and then set this property on your view model and pass the view model to the view:

...
else
{
    viewModel.ErrorMessage = "Email not found or matched";
    return View(viewModel);
}

and finally in your strongly typed view use the property on your model:

@model ForgotPasswordMV
...
<p>@Model.ErrorMessage</p>

So basically here we are replacing the use of ViewBag with a strongly typed view model.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • And is this the recommended practice? How you would have done if you had to do it? Viewbag or property in modelview? – Unbreakable Apr 22 '17 at 16:03
  • Can you kindly see below answer, and let me know if that is the best practice as it is using helper methods – Unbreakable Apr 22 '17 at 16:12
  • @Darin Dimitrov I have tried this but I am getting a NullReferenceException upon page load? –  Jun 16 '20 at 20:26
4

If anyone is looking for a simple fix and NOTHING PERMANENT than feel free to use this answer as this helped me out. DO NOT use this fix if you have to worry about security within your application.

In your controller:

TempData["Message"] = "This is my Error";

In your Error.cshtml file:

<h3><strong>@TempData["Message"]</strong></h3>

Result:

enter image description here

Zach Pedigo
  • 396
  • 2
  • 9
2

As for me accepted anwser is not the best practice. We can handle all errors in annotations.
In our ViewModel we specify ErrorMessages for our properites.

public class UserLoginViewModel
{
    [Required(ErrorMessage = "User name is required")]
    [Display(Name = "User name")]
    [StringLength(500, ErrorMessage = "User name is too short", MinimumLength = 3)]
    public string Login { get; set; }

    [Required(ErrorMessage = "Password is required")]
    [Display(Name = "Password")]
    public string Password { get; set; }
}

In our controller

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(UserLoginViewModel model)
{
    if (!this.ModelState.IsValid)
    {
        return this.View();
    }
    ...
}

And our view

@Html.ValidationMessageFor(model => model.Login)
@Html.EditorFor(model => model.Login)
@Html.ValidationMessageFor(model => model.Password)
@Html.EditorFor(model => model.Password)
Marek
  • 1,189
  • 3
  • 13
  • 33
  • 3
    Here we are talking about errors that are coming from business logic, not the standard ones. – Unbreakable Feb 19 '18 at 18:29
  • Aww, right. So maybe Remote Validation will be useful for someone. You could also use RV to check for email match/find. – Marek Feb 19 '18 at 23:25
-1

I spent a lot of time to find the best solution for this too. It's very simple. In your Controller you can send an message like this.

        if (UnitCount >= 1000)
        {
            TempData["MsgChangeStatus"] = "Only 1000 units are allowed to modify at the same time!";
            return RedirectToAction("ChangeStatus");
        }

It's very important, after you use the TempData command you have to use an Return View() or return RedirectToAction() immediately, because these commands can send your message to the View.

In the View, you have to add the following part.

 @{
ViewBag.Title = "UnitState Change";
Layout = "~/Views/Shared/_Layout.cshtml";
var message = TempData["MsgChangeUS"] ?? string.Empty;
}


<script type="text/javascript">
var message = '@message';
if(message)
    alert(message);
</script>
zalpivan
  • 9
  • 1
  • 1