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

时间:2017-06-26 18:03:32

标签: c# css 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);
}

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

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

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

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

ModelState.AddModelError("Email", "This email already exists!")
return View(testObject);

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

感谢任何帮助。

基于Nkosi的答案:

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

答案如下:

{"$id":"1","message":"The request is invalid.","modelState":
{"$id":"2","email":["This Email Already Exists!"]}} 

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

现在我收到valmsg.text(value.join()); Object doesn't support property or method 'join'上的错误,因此我确认了该值,它是2 ..而不是"This Email Already Exists!"

更新

.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");
            }

6 个答案:

答案 0 :(得分:2)

更新

基于此样本数据

{"$id":"1","message":"The request is invalid.","modelState":
{"$id":"2","email":["This Email Already Exists!"]}} 

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

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
        }            
    });
}

原始

以下POC用于演示原始问题

的WebAPI

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

MVC控制器

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

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>
}

使用基于问题中原始示例的上述概念证明,返回的结果模型看起来像这样。

{"Message":"The request is invalid.","ModelState":{"Email":["This Email Already Exists!"]}}

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

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");
        }
    });
}

上述执行时导致

image

从具有以下内容的视图

<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>

答案 1 :(得分:0)

你试过吗

return BadRequest("This Email Already Exists!");

另一个版本的BadRequest而不是抛出异常?

答案 2 :(得分:0)

当您的view / html调用Web API方法时,Web API基本上不知道您的页面或输入框是否存在。它纯粹得到一些输入并返回一些输出。在这种情况下,因为您在Web API中抛出异常,因此http响应是内部服务器错误的错误代码500&#34;。这将导致xhr错误处理程序运行。由于其他事情出错(数据库关闭,客户端连接丢失等),您将显示电子邮件验证错误,这并不理想。 一个更好的想法是返回一个更具信息性的响应,给出每个字段的验证结果,但是你需要一个比TestObject更多的响应类型,类似于结果,其中有一些字段用于验证错误。< / p>

作为一种快速解决方法,您可能希望使用一些前端UI库手动将该类添加到该字段中。 JQuery中的示例,就在您的toastr行之前/之后:

$('#emailfield').addClass('input-validation-error')

答案 3 :(得分:0)

您必须将.error函数中的input-validation-error类添加到所需的文本框或控件中。

注册以下脚本进行验证

$(".selector").validate({
    highlight: function(element, errorClass) {
        // Override the default behavior here
    }
});

答案 4 :(得分:0)

如果您要在控制器中返回:

Sub move()

Selection.Offset(-1, 1).Value = Selection.Value
Selection.ClearContents

End Sub

然后json返回应该是:

ModelState.AddModelError("Email", "This Email Already Exists!");
return BadRequest(ModelState);

在视图的HTML输出中,您应该有一个输入,其name属性设置为Email:

{"Email":["This Email Already Exists!"]}

同样,错误JSON中的所有其他键将具有匹配的表单控件,其名称属性与这些键匹配。

因此,在错误函数中,您可以遍历键并应用适当的CSS类:

<input name="Email" type="text" />

答案 5 :(得分:0)

我建议您使用FluentValidation

检查这个很棒article

因此,使用此方法,您将能够将验证逻辑从控制器中移出,并使错误响应遵循该模式。

但是为了在客户端添加css类,您必须将public List<string> Errors { get; set; }中的ResponsePackeage替换为public Dictionary<string, string> Errors { get; set; },其中key将是属性名称,值将是相关的错误信息。

祝你好运!