public class MetaToHeader : ActionFilterAttribute { private readonly string header; public MetaToHeader(string header) { this.header = header; } public override void OnActionExecuted(ActionExecutedContext filterContext) { var result = filterContext.Result as JsonResult; if (result != null && result.Data != null) { var meta = GetMeta(result.Data); var jsonMeta = new JavaScriptSerializer().Serialize(meta); var jsonMetaBytes = Encoding.UTF8.GetBytes(jsonMeta); filterContext.HttpContext.Response.Headers.Add(header, Convert.ToBase64String(jsonMetaBytes)); } base.OnActionExecuted(filterContext); } private static IDictionary<string, object> GetMeta(object model) { var meta = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()); return meta.Properties.ToDictionary( p => p.PropertyName, p => new { label = p.GetDisplayName(), title = p.Description, placeholder = p.Watermark, readOnly = p.IsReadOnly } as object); } }
public class ValidationToHeader : ActionFilterAttribute { private readonly string header; private static readonly Dictionary<string, Func<ModelClientValidationRule, List<object>>> Rules; public ValidationToHeader(string header) { this.header = header; } static ValidationToHeader() { Rules = new Dictionary<string, Func<ModelClientValidationRule, List<object>>>() { { "length", r => { var result = new List<object>(); if (r.ValidationParameters.ContainsKey("max")) result.Add(new {maxLength = r.ValidationParameters["max"]}); if (r.ValidationParameters.ContainsKey("min")) result.Add(new {minLength = r.ValidationParameters["min"]}); result.Add(new { msg = r.ErrorMessage }); return result; } }, { "range", r => { var result = new List<object>(); if (r.ValidationParameters.ContainsKey("max")) result.Add(new {max = r.ValidationParameters["max"]}); if (r.ValidationParameters.ContainsKey("min")) result.Add(new {min = r.ValidationParameters["min"]}); result.Add(new {msg = r.ErrorMessage}); return result; } }, { "remote", r => { var result = new Dictionary<string, object>(); if (r.ValidationParameters.ContainsKey("url")) result.Add("url", r.ValidationParameters["url"]); if (r.ValidationParameters.ContainsKey("type")) result.Add("type", r.ValidationParameters["type"]); result.Add("msg", r.ErrorMessage); return new List<object> { new {remote = result} }; } }, { "required", r => new List<object> { new { required = true, msg = r.ErrorMessage } } }, { "number", r => new List<object> { new { pattern = "number", msg = r.ErrorMessage } } } }; } public override void OnActionExecuted(ActionExecutedContext filterContext) { var result = filterContext.Result as JsonResult; if (result != null && result.Data != null) { var meta = GetRules(result.Data, filterContext.Controller.ControllerContext); var jsonMeta = new JavaScriptSerializer().Serialize(meta); var jsonMetaBytes = Encoding.UTF8.GetBytes(jsonMeta); filterContext.HttpContext.Response.Headers.Add(header, Convert.ToBase64String(jsonMetaBytes)); } base.OnActionExecuted(filterContext); } public static IDictionary<string, object> GetRules(object model, ControllerContext context) { var meta = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()); return meta.Properties.ToDictionary( p => p.PropertyName, p => PropertyRules(p, context) as object); } private static object[] PropertyRules(ModelMetadata meta, ControllerContext controllerContext) { return meta.GetValidators(controllerContext) .SelectMany(v => v.GetClientValidationRules()) .SelectMany(r => Rules[r.ValidationType](r)) .ToArray(); } }
(function () { var models = window.App.Models; models.DataMetaModel = Backbone.Model.extend({ metaHeader: 'data-meta', validationHeader: 'data-validation', urlRoot: '', initialize: function (options) { this.urlRoot = options.url; }, parse: function (response, xhr) { var metaData = xhr.xhr.getResponseHeader(this.metaHeader); var validationData = xhr.xhr.getResponseHeader(this.validationHeader); this.meta = metaData ? $.parseJSON(Base64.decode(metaData)) : undefined; this.validation = validationData ? $.parseJSON(Base64.decode(validationData)) : undefined; return response; } }); })();
(function () { var views = window.App.Views; views.dataMetaView = Backbone.View.extend({ events: { 'submit': 'evSubmit', 'blur input[type=text]': 'evBlur', }, initialize: function (options) { _.extend(Backbone.Validation.callbacks, { valid: this.validCallback, invalid: this.invalidCallback, }); _.extend(Backbone.Validation.validators, { remote: this.remoteValidator }); Backbone.Validation.bind(this, { offFlatten: true // . backbone-validation.js : var flatten = function (obj, into, prefix) { }); }, render: function () { this.addMeta(); }, addMeta: function () { _.each(this.model.meta, function (meta, name) { $('label[for=' + name + ']').text(meta.label); $('input[name=' + name + ']').attr({ title: meta.title, placeholder: meta.placeholder, readonly: meta.readOnly }); }); }, evBlur: function (e) { var $el = $(e.target); this.model.set($el.attr('name'), $el.val(), {validate: true, validateAll: false}); }, evSubmit: function (e) { if (!this.model.isValid(true)) return false; }, validCallback: function (view, attr, selector) { var control = view.$('[' + selector + '=' + attr + ']'); var group = control.parents(".control-group"); group.removeClass("error"); if (control.data("error-style") === "tooltip") { // CAUTION: calling tooltip("hide") on an uninitialized tooltip // causes bootstraps tooltips to crash somehow... if (control.data("tooltip")) control.tooltip("hide"); } else if (control.data("error-style") === "inline") { group.find(".help-inline.error-message").remove(); } else { group.find(".help-block.error-message").remove(); } }, invalidCallback: function (view, attr, error, selector) { var control = view.$('[' + selector + '=' + attr + ']'); var group = control.parents(".control-group"); group.addClass("error"); if (control.data("error-style") === "tooltip") { var position = control.data("tooltip-position") || "right"; control.tooltip({ placement: position, trigger: "manual", title: error }); control.tooltip("show"); } else if (control.data("error-style") === "inline") { if (group.find(".help-inline").length === 0) { group.find(".controls").append("<span class=\"help-inline error-message small-text\"></span>"); } var target = group.find(".help-inline"); target.text(error); } else { if (group.find(".help-block").length === 0) { group.find(".controls").append("<p class=\"help-block error-message small-text\"></p>"); } var target = group.find(".help-block"); target.text(error); } }, remoteValidator: function (value, attr, customValue, model) { var result, data = model.toJSON(); data[attr] = value; $.ajax({ type: customValue.type || 'GET', data: data, url: customValue.url, async: false, success: function (state) { if (!state) result = customValue.msg || 'remote validation error'; }, error: function () { result = "remote validation error"; } }); return result; } }); })();
Backbone.Validation.bind(this, { offFlatten: true // . backbone-validation.js : var flatten = function (obj, into, prefix) { });
remoteValidator: function (value, attr, customValue, model) { var result, data = model.toJSON(); data[attr] = value; $.ajax({ type: customValue.type || 'GET', data: data, url: customValue.url, async: false, success: function (state) { if (!state) result = customValue.msg || 'remote validation error'; }, error: function () { result = "remote validation error"; } }); return result; }
[Remote("RemoteEmailValidation", "Friends", ErrorMessage = " ")]
[MetaToHeader("data-meta")] [ValidationToHeader("data-validation")] public ActionResult GetData() { return Json(new Friend(), JsonRequestBehavior.AllowGet); }
(function() { var models = window.App.Models; models.FriendModel = models.DataMetaModel.extend({ initialize: function(options) { models.DataMetaModel.prototype.initialize.call(this, options); } }); })();
(function () { var views = window.App.Views; views.NewFriend = views.dataMetaView.extend({ initialize: function (options) { views.dataMetaView.prototype.initialize.call(this); this.model.on('sync', this.render, this); this.template = _.template($(options.template).html()); }, render: function () { this.$el.html(this.template(this.model.toJSON())); views.dataMetaView.prototype.render.call(this); return this; }, load: function () { this.model.fetch(); } }); })();
views.dataMetaView.prototype.render.call(this);
in order to add metadata (name, placeholder, etc.) to the drawn fields in accordance with the model description on the server side. However, the validation rules passed to the client are not added to the DOM. They are only used by the backbone-validation.js library. public class Friend { public int Id { get; set; } [Display(Name = "", Prompt = " ", Description = " ")] [Required(ErrorMessage = "First name required")] [StringLength(50, MinimumLength = 2)] public string FirstName { get; set; } [Display(Name = "", Prompt = " ", Description = " ")] [Required(ErrorMessage = "Last name required")] [StringLength(50, MinimumLength = 2)] public string LastName { get; set; } [Display(Name = "", Prompt = " ", Description = " ")] [Required(ErrorMessage = "Age required")] [Range(0, 120, ErrorMessage = "Age must be between 0 and 120")] public int? Age { get; set; } [Display(Name = " ", Prompt = " ", Description = " ")] [Required(ErrorMessage = "Email required")] [Email(ErrorMessage = "Not a valid email")] [Remote("RemoteEmailValidation", "Friends", ErrorMessage = " ")] public string Email { get; set; } }
[MetaToHeader("data-meta")] [ValidationToHeader("data-validation")] public ActionResult GetData() { return Json(new Friend(), JsonRequestBehavior.AllowGet); }
<script type='text/template' id='dataMeta-template'> <form action="/Friends/Create" method="post"> <div class="control-group"> <label for="FirstName"></label> <div class="controls"> <input type='text' name="FirstName" value='<%- FirstName %>' /> </div> </div> <div class="control-group"> <label for="LastName"></label> <div class="controls"> <input type='text' name="LastName" value='<%- LastName %>' /> </div> </div> <div class="control-group"> <label for="Age"></label> <div class="controls"> <input type='text' name="Age" value='<%- Age %>' /> </div> </div> <div class="control-group"> <label for="Email"></label> <div class="controls"> <input type='text' name="Email" value='<%- Email %>' /> </div> </div> <p><button class="btn" type="submit">Create</button></p> </form> </script>
<script> (function($) { var models = window.App.Models, views = window.App.Views; var dataMetaModel = new models.FriendModel({ urlRoot: '/Friends/GetData' }); var dataMetaView = new views.NewFriend({ el: '#dataMeta', model: dataMetaModel, template: '#dataMeta-template' }); dataMetaView.load(); })(jQuery); </script>
Source: https://habr.com/ru/post/178823/
All Articles