Search code examples
c#resourcesasp.net-web-apidata-annotationsmodel-validation

ASP.NET Web API: model is valid if error message is set from resources


The problem is that in ApiController ModelState.IsValid is always true if I use .rsx file (Resources) to provide custom error message.

Here is my model:

public class LoginModel
{
    public string Email { get; set; }

    [Required]
    [MinLength(5)]
    public string Password { get; set; }
}

Method in ApiController:

    [HttpPost]
    [ModelValidationFilter]
    public void Post(LoginModel model)
    {
        var a = ModelState.IsValid;
    }

And the filter:

public class ModelValidationFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (actionContext.ModelState.IsValid == false)
        {
            actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
        }
    }
}

I'm sending this in POST request:

{ Email: "[email protected]", Password: "a" }

ModelState.IsValid is false and the response is as expected:

{
   "Message": "The request is invalid.",
   "ModelState":
   {
       "model.Password":
       [
           "The field Password must be a string or array type with a minimum length of '5'."
       ]
   }
}

But if I use the resources (configured as Public and Embedded Resource build action) in validation attributes:

public class LoginModel
{
    public string Email { get; set; }

    [Required]
    [MinLength(5, ErrorMessageResourceName = "Test", ErrorMessageResourceType = typeof(Resources.Localization))]
    public string Password { get; set; }
}

('Test' key holds just 'Test' string value) ModelState.IsValid is true.

Resources class is visible, and resharper correctly validates string provided in ErrorMessageResourceName.


Solution

  • I tried your solution, yet I do not understand class Resources.Localization. Where did it come from? My solution is described below and it was working with good resources.

    Model:

    using TestApp.Properties;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Web;
    
    namespace TestApp.Models
    {
        public class LoginModel
        {
            public string Email { get; set; }
            [Required]
            [MinLength(5, ErrorMessageResourceName="Test", ErrorMessageResourceType=typeof(Resources))]
            public string Password { get; set; }
        }
    }
    

    ModelValidationFilterAttribute:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Web;
    using System.Web.Http.Controllers;
    using System.Web.Http.Filters;
    using System.Net.Http;
    
    namespace TestApp.Controllers
    {
        public class ModelValidationFilterAttribute: ActionFilterAttribute
        {
            public override void OnActionExecuting(HttpActionContext actionContext)
            {
                if (actionContext.ModelState.IsValid == false)
                {
                    actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
                }
            }
        }
    }
    

    Two resource files, one general Resources.resx, containing string key/value Test/something; the other one Resources.de-DE.resx, containing string key/value Test/something_DE.

    Using fiddler I sent this:

    Header:
    User-Agent: Fiddler
    Host: localhost:63315
    Content-Length: 37
    Content-Type: application/json
    

    Body:

    {Email:"[email protected]", Password:"a"}
    

    Response was string:

    HTTP/1.1 400 Bad Request
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Type: application/json; charset=utf-8
    Expires: -1
    Server: Microsoft-IIS/8.0
    X-AspNet-Version: 4.0.30319
    X-SourceFiles: =?UTF-8?B?RDpcV29ya1xDU1xGYkJpcnRoZGF5QXBwXEZiQmRheUFwcDJcYXBpXGxvZ2lu?=
    X-Powered-By: ASP.NET
    Date: Fri, 28 Jun 2013 14:56:54 GMT
    Content-Length: 83
    
    {"Message":"The request is invalid.","ModelState":{"model.Password":["something"]}}
    

    For de-DE, request header was:

    User-Agent: Fiddler
    Host: localhost:63315
    Content-Length: 37
    Accept-Language: de-DE
    Content-Type: application/json
    

    Response was:

    HTTP/1.1 400 Bad Request
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Type: application/json; charset=utf-8
    Expires: -1
    Server: Microsoft-IIS/8.0
    X-AspNet-Version: 4.0.30319
    X-SourceFiles: =?UTF-8?B?RDpcV29ya1xDU1xGYkJpcnRoZGF5QXBwXEZiQmRheUFwcDJcYXBpXGxvZ2lu?=
    X-Powered-By: ASP.NET
    Date: Fri, 28 Jun 2013 14:57:39 GMT
    Content-Length: 86
    
    {"Message":"The request is invalid.","ModelState":{"model.Password":["something_DE"]}}
    

    As you can see, message was with "_DE" as it was read from localized resources file.

    Is this something that you wanted to achieve?

    Kind regards.

    Edit: route configuration was

    config.Routes.MapHttpRoute(
              name: "LoginRoute",
              routeTemplate: "api/login",
              defaults: new { controller = "Login", action = "PostLogin" },
              constraints: new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) }
              );
    

    Web.config had next section added:

    <system.web>
    ...
            <globalization culture="auto" uiCulture="auto" />
    ...
    </system.web>