Search code examples
asp.net-mvcvalidationlocalizationbusiness-logic

What's the best way to localize non Data Annotation Errors with ASP.NET MVC 3?


With Data Annotations it's now easy to localize error messages using Resource.resx files like this for example:

public class Student
{
    . . .

    [Required(ErrorMessageResourceName ="Required",
     ErrorMessageResourceType = typeof(StudentResources))]
    [StringLength(16)] 
    [Display(Name = "FirstName", ResourceType = typeof(StudentResources))]
    public string FirstName { get; set; }

    . . .
}

Now, let's say I want to check if a Student has already made a Payment for a given month and year:

public bool CheckIfAlreadyPaid(Payment payment)
{
    return repository.GetPayments().Any(p => p.StudentId == payment.StudentId &&
                                        p.Month == payment.Month &&
                                        p.Year == payment.Year);
}

If he has already made the Payment, I'm doing the following in my Services layer:

if (CheckIfAlreadyPaid(payment))
{
    modelState.AddModelError("AlreadyPaid",
    Resources.Views.Payment.PaymentCreateResources.AlreadyPaid);
}

It works, but I don't fell confident about referencing the Resource file inside the Services layer.

Is there a standard or better way of localizing error messages that are not tied to model properties (Data Annotation) - errors that come from business logic rules? Should I still add these errors to the ModelStateDictionary?


Solution

  • I did it in a different way. The Service layer is used to check if the Payment was already made. In my Controller I add a validation error message to the ModelState object passing to it a localized string resource. Now I feel more comfortable with this approach.

    Here's the code:

    /// <summary>
    /// Performs validation of business logic...
    /// </summary>
    /// <param name="payment"></param>
    /// <returns></returns>
    private bool ValidatePayment(Payment payment)
    {
        if (paymentService.IsPaymentMade(payment))
        {
            ModelState.AddModelError("AlreadyPaid", Localization.AlreadyPaid);
        }
    
        return ModelState.IsValid;
    }
    

    EDIT:

    To complement my answer, I just found today that ValidationSummary @Html.ValidationSummary(true) does exactly what I want:

    Html.ValidationSummary returns an unordered list (ul element) of validation messages that are in the ModelStateDictionary object and optionally displays only model model-level errors.

    I'm passing true and it will only display model-level errors (non data annotation errors) in the summary just at the top of the page. This is great but only if it would work... :)

    I was facing a problem where my custom error messages not tied to model properties weren't appearing when I set ValidationSummary(true). I then searched using Google and found this post. I tried his solution but it didn't work. I then searched a little bit more and found this link in Google Books (Pro ASP.NET MVC 2 Framework by Steven Sanderson).

    I tried what is described there passing an empty string as key (string.Empty) and it did the job.

    if(paymentService.IsPaymentMade(payment))
    {
        ModelState.AddModelError(string.Empty, Localization.PaymentAlreadyCreated);
    }