代码改变世界

山寨版的为视图自定义辅助方法(下)

2009-04-29 12:56  Otis's Technology Space  阅读(2276)  评论(6编辑  收藏  举报

伟大的老赵写了 为视图自定义辅助方法(上) 。此方法,我之前在老赵的Webcast的讲座中早已抄了过来。 虽然概念上不是很清楚,但是的确是可用了。而且,我还抄了一些牛人验证思想,在此上进行了扩展。使之更简单,更自动化,输入的代码更少。

嗯。少说废话,先看看使用方法和最终效果:

一般的,我们先会定义以下Model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using MVCJqueryValidationDemo.Validation;
 
namespace MVCJqueryValidationDemo.Models
{
    public class User
    {
        public Guid UserID { get; set; }
        [Required]
        [StringMinMaxLength(Min = 4, Max = 16, ErrorMessage = "用户名必须为{1}至{2}个字符!")]
        public string LoginName { get; set; }
 
        [Required]
        [StringMinMaxLength(Min = 6, Max = 30, ErrorMessage = "密码必须为{1}至{2}个字符!")]
        public string Password { get; set; }
 
        [Required]
        [DataType(DataType.EmailAddress, ErrorMessage = "请填入正确的Email!")]
        public string Email { get; set; }
    }
}
 

再写一个UserService。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MVCJqueryValidationDemo.Validation;
using MVCJqueryValidationDemo.Models;
 
namespace MVCJqueryValidationDemo.Services
{
    public class UserServie
    {
        public void Add(User user)
        {
            Validation(user);
            // add user to database;
        }
        private void Validation(User user)
        {
            var errors = DataAnnotationsValidationRunner.GetErrors(user);
            if (errors.Any()) throw new RuleException(errors);
 
        }
    }
}
 

然后写下以下Controller

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using MVCJqueryValidationDemo.Models;
using MVCJqueryValidationDemo.Validation;
using MVCJqueryValidationDemo.Services;
namespace MVCJqueryValidationDemo.Controllers
{
    public class DemoValidationController : Controller
    {
 
        public ActionResult Index()
        {
            return View();
        }
        public ActionResult Create()
        {
            return View();
        }
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create(User user)
        {
            UserServie us = new UserServie();
 
            try
            {
                us.Add(user);
            }
            catch (RuleException ruleEx)
            {
                ValidationHelper.AddModelStateErrors(ruleEx.Errors, this.ModelState,null);
            }
            return ModelState.IsValid ? RedirectToAction("Index") : (ActionResult)View();
        }
    }
 
}

然后用Add View 方法,建立View.

image

 

于是得到以下的View(自动生成)

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MVCJqueryValidationDemo.Models.User>" %>
 
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Create
</asp:Content>
 
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
 
    <h2>Create</h2>
 
    <%= Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %>
 
    <% using (Html.BeginForm()) {%>
 
        <fieldset>
            <legend>Fields</legend>
            <p>
                <label for="UserID">UserID:</label>
                <%= Html.TextBox("UserID") %>
                <%= Html.ValidationMessage("UserID", "*") %>
            </p>
            <p>
                <label for="LoginName">LoginName:</label>
                <%= Html.TextBox("LoginName") %>
                <%= Html.ValidationMessage("LoginName", "*") %>
            </p>
            <p>
                <label for="Password">Password:</label>
                <%= Html.TextBox("Password") %>
                <%= Html.ValidationMessage("Password", "*") %>
            </p>
            <p>
                <label for="Email">Email:</label>
                <%= Html.TextBox("Email") %>
                <%= Html.ValidationMessage("Email", "*") %>
            </p>
            <p>
                <input type="submit" value="Create" />
            </p>
        </fieldset>
 
    <% } %>
 
    <div>
        <%=Html.ActionLink("Back to List", "Index") %>
    </div>
 
</asp:Content>
 
<asp:Content ID="Content3" ContentPlaceHolderID="Script" runat="server">
</asp:Content>
 
 

然后,再将View改成这样:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MVCJqueryValidationDemo.Models.User>" %>
<%@ Import Namespace=" MVCJqueryValidationDemo.Helpers" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Create
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>
        Create</h2>
    <%= Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %>
    <% using (Html.BeginForm("Create", "DemoValidation", FormMethod.Post, new { id = "createUser", name = "createUser" }))
       {%>
    <fieldset>
        <legend>Fields</legend>
        
        <p>
            <label for="LoginName">
                LoginName:</label>
            <%= Html.TextBox("LoginName") %>
            <%= Html.ValidationMessage("LoginName") %>
        </p>
        <p>
            <label for="Password">
                Password:</label>
            <%= Html.TextBox("Password") %>
            <%= Html.ValidationMessage("Password") %>
        </p>
        <p>
            <label for="confirmPassword">
                confirmPassword:</label>
            <%= Html.TextBox("confirmPassword")%>
            <% this.JQuery().Validations().EqualTo("confirmPassword", "Password", "两次输入的密码不一至!"); %>
        </p>
        <p>
            <label for="Email">
                Email:</label>
            <%= Html.TextBox("Email") %>
            <%= Html.ValidationMessage("Email") %>
        </p>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
    <% } %>
    <div>
        <%=Html.ActionLink("Back to List", "Index") %>
    </div>
    
<% this.JQuery().Validations().BuildValidationByModelType(typeof(MVCJqueryValidationDemo.Models.User), null); %>
 
<script type="text/javascript">
$(document).ready(function(){
<%= this.JQuery().Validations().ToScripts("#createUser") %>
});
</script>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Script" runat="server">
    <script src="http://www.cnblogs.com/Scripts/jquery-1.3.2.js" type="text/javascript"></script>
    <script src="http://www.cnblogs.com/Scripts/Validation/jquery.validate.js" type="text/javascript"></script>
</asp:Content>
 

 

 

 

所有工作已经完成。。
 
其中:
    <% this.JQuery().Validations().BuildValidationByModelType(typeof(MVCJqueryValidationDemo.Models.User), null); %>
这句,是自动提取Model的Attribue,生成客户端和服务器端验证代码。
 
    <% this.JQuery().Validations().EqualTo("confirmPassword", "Password", "两次输入的密码不一至!"); %>
这句,说明客户端验证代码还可以灵活自动增加。
 
下面看一下最终效果。
 
下图是在启用javascript和没有Submit的验证。
 
image
 

下图是在禁用javascript和Submit后的效果。

image

 

 

 

 

 

 

这样,是不是很简单?

不知道伟大的老赵的“品牌版”为视图自定义辅助方法(下)会是什么样子的。真让人期待。

 

再说一下。如果我要以Ajax方式保存数据,那又应该怎么样?

其实也很简单灵活, 只要改动Controller ,就一切OK。

        [AcceptVerbs(HttpVerbs.Post)]
        [AjaxRequest(false)]
        public ActionResult Create(User user)
        {
            UserServie us = new UserServie();
 
            try
            {
                us.Add(user);
            }
            catch (RuleException ruleEx)
            {
                ValidationHelper.AddModelStateErrors(ruleEx.Errors, this.ModelState,null);
            }
            return ModelState.IsValid ? RedirectToAction("Index") : (ActionResult)View();
        }
        [AcceptVerbs(HttpVerbs.Post)]
        [AjaxRequest(true)]
        public ActionResult Create(User user)
        {
            UserServie us = new UserServie();
 
            try
            {
                us.Add(user);
            }
            catch (RuleException ruleEx)
            {
                //返回失败的json串
                this.Json(ruleEx.Errors);
            }
            //返回成功的json串
            return this.Json("");
        }

 

其中的: AjaxRequestAttribute 也是伟大的老赵的作品。代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace MVCJqueryValidationDemo.Attribute
{
    public class AjaxRequestAttribute : ActionNameSelectorAttribute
    {
        public AjaxRequestAttribute(bool isAjax)
        {
            this.IsAjax = isAjax;
        }
        public bool IsAjax { get; private set; }
        public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo)
        {
            //x-requested-with    XMLHttpRequest
            var requestByAjax = controllerContext.HttpContext.Request.Headers["x-requested-with"] == "XMLHttpRequest";
            return !(this.IsAjax ^ requestByAjax);
        }
    }
}
 

 

 

下面说说实现思路:

1,抓取Model类的验证信息的Attribute.

2,根据验证信息生成客户端效验。

3,submit后在BBL层验证客户端输入,如果验证不通过,则抛出RuleException异常。

4,在Controller中抓到RuleException,如果是Ajax请求,返回错误的集合的json字符串,如果不是Ajax请求,则把错误的集合加在ModelState里。

 

抓取Model类的验证信息的关键代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
 
namespace MVCJqueryValidationDemo.Validation
{
    public class DataAnnotationsValidationRunner
    {
        public static IEnumerable<ErrorInfo> GetErrors(object instance)
        {
            return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
                   from attribute in prop.Attributes.OfType<ValidationAttribute>()
                   where !attribute.IsValid(prop.GetValue(instance))
                   select new ErrorInfo
                   {
                       PropertyName = prop.Name,
                       ErrorMessage = attribute.FormatErrorMessage(string.Empty),
                       Object = instance
                   };
        }
    }
}
 

ErrorInfo 类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace MVCJqueryValidationDemo.Validation
{
    public class ErrorInfo
    {
        public string ErrorMessage { get; set; }
        public object Object { get; set; }
        public string PropertyName { get; set; }
    }
}
 

RuleException类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace MVCJqueryValidationDemo.Validation
{
    public class RuleException : Exception
    {
        public RuleException(IEnumerable<ErrorInfo> errors)
        {
            Errors = errors;
        }
 
        public RuleException(string propertyName, string errorMessage)
            : this(propertyName, errorMessage, null) { }
 
        public RuleException(string propertyName, string errorMessage, object onObject)
        {
            Errors = new[] { new ErrorInfo { PropertyName = propertyName, ErrorMessage = errorMessage, Object = onObject } };
        }
 
        public IEnumerable<ErrorInfo> Errors { get; private set; }
    }
}
 

把错误集合加在ModelState的帮助类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
 
namespace MVCJqueryValidationDemo.Validation
{
    public class ValidationHelper
    {
        public static IEnumerable<ModelValidationInfo> GetValidationInfo(Type modelType)
        {
            return from prop in TypeDescriptor.GetProperties(modelType).Cast<PropertyDescriptor>()
                   from attribute in prop.Attributes.OfType<ValidationAttribute>()
                   // where !attribute.IsValid(prop.GetValue(instance))
                   select new ModelValidationInfo
                   {
                       PropertyName = prop.Name,
                       ErrorMessage = attribute.FormatErrorMessage(string.Empty),
                       AttributeObject = attribute
                   };
        }
 
        public static void AddModelStateErrors(IEnumerable<ErrorInfo> errors, ModelStateDictionary modelState, string prefix)
        {
            AddModelStateErrors(errors, modelState, prefix, x => true);
        }
 
        public static void AddModelStateErrors(IEnumerable<ErrorInfo> errors, ModelStateDictionary modelState, string prefix, Func<ErrorInfo, bool> errorFilter)
        {
            if (errorFilter == null) throw new ArgumentNullException("errorFilter");
            prefix = prefix == null ? "" : prefix + ".";
            foreach (var errorInfo in errors.Where(errorFilter))
            {
                var key = prefix + errorInfo.PropertyName;
                modelState.AddModelError(key, errorInfo.ErrorMessage);
 
                // Workaround for http://xval.codeplex.com/WorkItem/View.aspx?WorkItemId=1297 (ASP.NET MVC bug)
                // Ensure that some value object is registered in ModelState under this key
                ModelState existingModelStateValue;
                if (modelState.TryGetValue(key, out existingModelStateValue) && existingModelStateValue.Value == null)
                    existingModelStateValue.Value = new ValueProviderResult(null, null, null);
            }
        }
 
    }
}
 

生成客户端验证代码的关键代码:

 /* add by otis */
        public void BuildValidationByModelType(Type modelType, string prefix)
        {
            if (modelType == null) throw new ArgumentNullException("modelType");
            prefix = string.IsNullOrEmpty(prefix) ? "" : prefix + ".";
            IEnumerable<ModelValidationInfo> infos = ValidationHelper.GetValidationInfo(modelType);
            foreach (ModelValidationInfo info in infos)
            {
                string name = info.PropertyName;
                string message = info.ErrorMessage;
 
                RequiredAttribute rq_attr = info.AttributeObject as RequiredAttribute;
                if (rq_attr != null)
                {
                    this.Required(name, message);
                    continue;
                }
                RangeAttribute rng_attr = info.AttributeObject as RangeAttribute;
                if (rng_attr != null)
                {
                    AddRangeValidator(prefix, info, rng_attr);
                    continue;
                }
                StringMinMaxLengthAttribute stringMinMax_attr = info.AttributeObject as StringMinMaxLengthAttribute;
                if (stringMinMax_attr != null)
                {
                    this.RangeLength(name, stringMinMax_attr.Min, stringMinMax_attr.Max, message);
                    continue;
                }
                #region DataTypeAttribute
                DataTypeAttribute dt_attr = info.AttributeObject as DataTypeAttribute;
                if (dt_attr != null)
                {
                    switch (dt_attr.DataType)
                    {
                        case DataType.Currency:
                            break;
                        case DataType.Custom:
                            break;
                        case DataType.Date:
                            this.Date(name, message);
                            break;
                        case DataType.DateTime:
                            this.Date(name, message);
                            break;
                        case DataType.Duration:
                            break;
                        case DataType.EmailAddress:
                            this.Email(name, message);
                            break;
                        case DataType.Html:
                            break;
                        case DataType.MultilineText:
                            break;
                        case DataType.Password:
                            break;
                        case DataType.PhoneNumber:
                            break;
                        case DataType.Text:
                            break;
                        case DataType.Time:
                            break;
                        case DataType.Url:
                            break;
                        default:
                            break;
                    }
                    continue;
                }
                #endregion
 
 
 
            }
 
        }
 
这个是上面用到的扩展的验证Attribute:  StringMinMaxLengthAttribute
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
 
namespace MVCJqueryValidationDemo.Validation
{
    public class StringMinMaxLengthAttribute : ValidationAttribute
    {
        public int Min { get; set; }
        public int Max { get; set; }
        public override bool IsValid(object value)
        {
            string value_str = value.ToString();
            if (!string.IsNullOrEmpty(value_str))
            {
                if (value_str.Length >= Min && value_str.Length < Max) return true;
                return false;
            }
            return true;
        }
        public override string FormatErrorMessage(string name)
        {
            return string.Format(CultureInfo.CurrentCulture, base.ErrorMessageString, new object[] { name, this.Min, this.Max });
 
        }
    }
}
 

 

可以随便扩展验证Attribute,然后同时要更新BuildValidationByModelType方法和Jquery.Validation。

个人认为这种方法可以解决大部分快速开发中遇到的验证数据问题了。

如果那位有更好的方法,欢迎分享!同时也非常期待 伟大的老赵的“品牌版”为视图自定义辅助方法(下)的内容!