使用.NET WebAPI进行服务器端验证

Gri*_*zly 8 css c# validation asp.net-web-api asp.net-mvc-5

为了论证,让我们说我在创建视图.如果我将所有文本框留空并点击提交,我将返回相同的表单,但在每个文本框下都需要验证消息,这是通过客户端验证完成的.现在,当发生这种情况时,每个文本框都会被称为一个类名称input-validation-error,如果我设置了样式,我可以使该框变为红色,使其更突出用户.

但现在,让我们说其中一个文本框需要一个电子邮件地址.电子邮件地址是唯一的,所以在我的webapi控制器中我有这个:

// POST: api/ControllerName
[ResponseType(typeof(TestObject))]
public IHttpActionResult PostTestObject(TestObject testObject)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    if (
        db.TestObjects.Any(
            x =>
                x.Email.Equals(testObject.Email, StringComparison.CurrentCultureIgnoreCase) &&
                x.ID != testObject.ID))
    {
            ModelState.AddModelError("Email", "This Email Already Exists!");
            return BadRequest(ModelState);
    }

    db.TestObjects.Add(testObject);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = testObject.ID }, testObject);
}
Run Code Online (Sandbox Code Playgroud)

在我的创建视图中,我有这个来显示该异常消息:

.error(function (jqXHR, textStatus, errorThrown) {
    var status = capitalizeFirstLetter(textStatus);
    var error = $.parseJSON(jqXHR.responseText);
    toastr.error(status + " - " + error.exceptionMessage);
 });
Run Code Online (Sandbox Code Playgroud)

这会在toastr通知中显示异常消息.但它没有给电子邮件文本框一个类名input-validation-error,我希望它能以红色显示文本框.

WebApi控制器方法中是否有一种方法可以返回将该类添加到该文本框的内容?我知道在常规.Net控制器中我能做到

ModelState.AddModelError("Email", "This email already exists!")
return View(testObject);
Run Code Online (Sandbox Code Playgroud)

这将返回具有css类名称的文本框的视图.

任何帮助表示赞赏.

基于Nkosi的答案如下:

当我 console.log(JSON.stringify(error));

答案是这样的:

{"$id":"1","message":"The request is invalid.","modelState":
{"$id":"2","email":["This Email Already Exists!"]}} 
Run Code Online (Sandbox Code Playgroud)

好的,所以我更改了格式以适应JSON响应,我也将var id行更改为var id = "#" + key.replace('$', '');

现在我收到一个错误valmsg.text(value.join());Object doesn't support property or method 'join'...所以我安慰了价值,它是2..不是"This Email Already Exists!"

UPDATE

.error(function (jqXHR, textStatus, errorThrown) {
    var error = jqXHR.responseJSON;
    console.log(JSON.stringify(error));
    var message = error.message;
    var modelState = error.modelState;

    $.each(modelState,
        function (key, value) {
            var id = "#" + key.replace('$', '');
            var input = $(id);
            console.log(id); // result is #id
            if (input) { // if element exists
                input.addClass('input-validation-error');
            }
            //get validation message
            var valmsg = $("[data-valmsg-for='" + key + "']");
            if (valmsg) {
                valmsg.text(value.join()); // Object doesn't support property or method 'join'
                valmsg.removeClass("field-validation-valid");
                valmsg.addClass("field-validation-error");
            }
Run Code Online (Sandbox Code Playgroud)

Nko*_*osi 2

更新

基于此样本数据

{"$id":"1","message":"The request is invalid.","modelState":
{"$id":"2","email":["This Email Already Exists!"]}} 
Run Code Online (Sandbox Code Playgroud)

突出显示无效元素的代码片段将变为

var handleError = function (jqXHR, textStatus, errorThrown) {
    var error = jqXHR.responseJSON;        
    var message = error.message;
    var modelState = error.modelState;
    //highlight invalid fields                    
    $.each(modelState, function (key, value) {
        var id = "#" + key; //construct id
        var input = $(id); //get the element
        if(input) { //if element exists
            input.addClass('input-validation-error'); //update class
        }            
    });
}
Run Code Online (Sandbox Code Playgroud)

原来的

以下POC用于演示原始问题

网络API

[HttpGet]
[Route("testobject")]
public IHttpActionResult TestObject() {
    ModelState.AddModelError("Email", "This Email Already Exists!");
    return BadRequest(ModelState);
}
Run Code Online (Sandbox Code Playgroud)

MVC控制器

[HttpGet, Route("")]
public ActionResult Index() {
    var model = new TestVM();
    return View(model);
}
Run Code Online (Sandbox Code Playgroud)

MVC 视图:索引

@model TestVM
@{
    ViewBag.Title = "Index";
}
<div class="container">
    <div class="form-group">
        @Html.LabelFor(m => m.Email)
        @Html.TextBoxFor(model => model.Email, new { data_bind = "value: Email", @class = "form-control" })
        @Html.ValidationMessageFor(model => model.Email)
    </div>
    <button type="button" data-bind="click: testEmail" class="btn btn-success submit">Test</button>
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval", "~/bundles/knockout")
    <script type="text/javascript">
        //Pay no attention to this. custom strongly typed helper for routes
        var url = '@(Url.HttpRouteUrl<TestsApiController>(c => c.TestObject()))';
        $(function () {
            /*using knockout for binding*/
            function viewModel() {
                var self = this;
                //properties
                self.Email = ko.observable(@(Model.Email));
                //methods
                self.testEmail = function () {
                    $.ajax({
                        url: url,
                        type: 'Get',
                        contentType: 'application/json',
                        dataType: 'json',
                        success: handleResponse,
                        error: handleError,
                    });
                };

                var handleError = function (jqXHR, textStatus, errorThrown) {
                    var error = jqXHR.responseJSON;
                    console.log(JSON.stringify(error));
                    var message = error.Message;
                    var modelState = error.ModelState;
                    //highlight invalid fields                    
                    $.each(modelState, function (key, value) {
                        var id = "#" + key;
                        $(id).addClass('input-validation-error');
                        //get validation message
                        var valmsg = $("[data-valmsg-for='" + key + "']");
                        if (valmsg) {
                            valmsg.text(value.join());
                            valmsg.removeClass("field-validation-valid");
                            valmsg.addClass("field-validation-error");
                        }
                    });
                }

                var handleResponse = function (data) {
                    //No-op
                };
            }
            var vm = new viewModel();
            ko.applyBindings(vm);
        });

    </script>
}
Run Code Online (Sandbox Code Playgroud)

使用上述基于问题中原始示例的概念证明,返回的结果模型如下所示。

{"Message":"The request is invalid.","ModelState":{"Email":["This Email Already Exists!"]}}
Run Code Online (Sandbox Code Playgroud)

主要关注处理返回的错误响应,我能够使用以下结构实现所需的行为。

var handleError = function (jqXHR, textStatus, errorThrown) {
    var error = jqXHR.responseJSON;
    console.log(JSON.stringify(error));
    //logs {"Message":"The request is invalid.","ModelState":{"Email":["This Email Already Exists!"]}}
    var message = error.Message;
    var modelState = error.ModelState;
    //highlight invalid fields                    
    $.each(modelState, function (key, value) {
        var id = "#" + key;
        $(id).addClass('input-validation-error');
        //get validation message
        var valmsg = $("[data-valmsg-for='" + key + "']");
        if (valmsg) {
            valmsg.text(value.join());
            valmsg.removeClass("field-validation-valid");
            valmsg.addClass("field-validation-error");
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

上述执行时的结果是

图像

从以下观点来看

<div class="container">
    <div class="form-group">
        @Html.LabelFor(m => m.Email)
        @Html.TextBoxFor(model => model.Email, new { data_bind = "value: Email", @class = "form-control" })
        @Html.ValidationMessageFor(model => model.Email)
    </div>
    <button type="button" data-bind="click: testEmail" class="btn btn-success submit">Test</button>
</div>
Run Code Online (Sandbox Code Playgroud)