YbSoftwareFactory 代码生成插件【九】:基于JQuery、WebApi的ASP.NET MVC插件的代码生成项目主要技术解析
YbSoftwareFactory目前已可快速生成ASP.NET WebForm、MVC、WinForm和WPF的解决方案源代码,所生成的源代码可直接在VS中打开并运行。终端用户还可自行二次开发自己所需的插件。本章将对ASP.NET MVC代码生成插件所生成项目的主要特点和技术进行解析,其Demo地址:http://mvcdemo.yellbuy.com/。
主要技术特点:
一、数据字典的排序
有童鞋问到数据字典的排序是如何实现的,其实数据字典的排序比较简单。首先,需要定义一个排序字段,并根据这个字段升序排列。如果要优先显示某个字典项,则需减小该排序字段的值。如果该排序字段再首位,则无需操作,第二位,则为第一位的排序值小1000,其他设置为前面两项的值的平均数。反之亦然~
在所生成的解决方案中,使用了通用的Api组件,你只需配置好主代码即可。
二、数据分页和动态查询
现在很多童鞋使用Linq查询,确实是非常方便,并且是强类型的,这样编写代码也不容易出错;但最大的缺点就是灵活性差,例如无法把查询语法写到一个统一的基类中。在本解决方案中,设计比较巧妙之处就是把过滤查询的实现写到了一个泛型基类中,仅需在具体实现类中定义查询的方式字符串即可,代码不仅更加简洁,而且修改、维护也更加方便。使用动态Linq技术可以在泛型基类中统一以如下的方式进行数据查询、分页和排序,而无需在具体类中总是写重复的代码:
/// <summary> /// 分页查询 /// </summary> /// <param name="filter">查询条件</param> /// <param name="page">页号</param> /// <param name="rows">记录数</param> /// <param name="sort">排序字段名称</param> /// <param name="order">排序方式</param> /// <returns></returns> public ResponseResult<M> Get(string filter, int page, int rows, string sort, string order) { var table = Table; if (!string.IsNullOrWhiteSpace(filter)) { var str = GetFilterFormat(); //有过滤条件,进行过滤查询 if (!string.IsNullOrWhiteSpace(str)) table = table.Where(str, filter); } var total = table.LongCount(); table = table.OrderBy(string.Format("{0} {1}", sort, order)).Skip((page - 1) * rows).Take(rows); var items = table.ToList(); var models = InjectFrom(items); return new ResponseResult<M>(total, models); }
如果感兴趣,可以在前一章中下载DynamicLinq的实现源码。
三、授权和身份认证
授权和验证关系到系统的安全,普通的授权验证方式无法实现本系统所要求的灵活性以及细粒度的权限控制,因此需对授权方式进行扩展,这主要需从AuthorizeAttribute继承,本解决方案中新增了一个PermissionKey属性,更据所设置的PermissionKey名称进行相应的授权处理,通过添加“EveryOne”角色名甚至能设置匿名用户的操作权限,不可谓不灵活。主要代码如下:
1 public class YbApiAuthorizeAttribute : System.Web.Http.AuthorizeAttribute 2 { 3 private const string BasicAuthResponseHeader = "WWW-Authenticate"; 4 private const string BasicAuthResponseHeaderValue = "Basic"; 5 public string PermissionKey { get; set; } 6 public override void OnAuthorization(HttpActionContext actionContext) 7 { 8 if (actionContext == null) 9 throw new ArgumentNullException("actionContext"); 10 if (AuthorizationDisabled(actionContext) || AuthorizeRequest(actionContext.ControllerContext.Request)) 11 return; 12 this.HandleUnauthorizedRequest(actionContext); 13 } 14 15 protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) 16 { 17 if (actionContext == null) 18 throw new ArgumentNullException("actionContext"); 19 actionContext.Response = CreateUnauthorizedResponse(actionContext.ControllerContext.Request); 20 } 21 22 private HttpResponseMessage CreateUnauthorizedResponse(HttpRequestMessage request) 23 { 24 var result = new HttpResponseMessage() 25 { 26 StatusCode = HttpStatusCode.Unauthorized, 27 RequestMessage = request 28 }; 29 //we need to include WWW-Authenticate header in our response, 30 //so our client knows we are using HTTP authentication 31 result.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue); 32 return result; 33 } 34 35 private static bool AuthorizationDisabled(HttpActionContext actionContext) 36 { 37 //support new AllowAnonymousAttribute 38 if (!actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any()) 39 return actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any(); 40 return true; 41 } 42 43 private bool AuthorizeRequest(HttpRequestMessage request) 44 { 45 //匿名用户的权限验证 46 if (!HttpContext.Current.User.Identity.IsAuthenticated) 47 { 48 var permissionsOfEveryOne = Permissions.GetPermissionsInRole("EveryOne"); 49 return CheckPermission(request, permissionsOfEveryOne); 50 } 51 //未设置权限Key,则任何用户均可访问 52 if (string.IsNullOrWhiteSpace(PermissionKey)) return true; 53 54 //登录用户的权限验证 55 var allowPermissions = Permissions.GetPermissionsForUser(); 56 return CheckPermission(request, allowPermissions); 57 } 58 59 private bool CheckPermission(HttpRequestMessage request, Permission[] permissionsOfEveryOne) 60 { 61 if (permissionsOfEveryOne == null || permissionsOfEveryOne.Length == 0) 62 return false; 63 var permissionKeys = permissionsOfEveryOne.Select(c => c.PermissionKey); 64 if (request.Method.Method.Equals("GET", StringComparison.OrdinalIgnoreCase)) 65 return permissionKeys.Contains(PermissionKey.ToLower()); 66 if (request.Method.Method.Equals("PUT", StringComparison.OrdinalIgnoreCase)) 67 return permissionKeys.Contains(string.Format("{0}.add", PermissionKey.ToLower())); 68 if (request.Method.Method.Equals("POST", StringComparison.OrdinalIgnoreCase)) 69 return permissionKeys.Contains(string.Format("{0}.edit", PermissionKey.ToLower())); 70 if (request.Method.Method.Equals("DELETE", StringComparison.OrdinalIgnoreCase)) 71 return permissionKeys.Contains(string.Format("{0}.delete", PermissionKey.ToLower())); 72 return false; 73 } 74 75 protected string[] RolesSplit 76 { 77 get { return SplitStrings(Roles); } 78 } 79 80 protected string[] UsersSplit 81 { 82 get { return SplitStrings(Users); } 83 } 84 85 protected static string[] SplitStrings(string input) 86 { 87 if (string.IsNullOrWhiteSpace(input)) return new string[0]; 88 var result = input.Split(',').Where(s => !String.IsNullOrWhiteSpace(s.Trim())); 89 return result.Select(s => s.Trim()).ToArray(); 90 } 91 92 /// <summary> 93 /// Implement to include authentication logic and create IPrincipal 94 /// </summary> 95 protected bool TryCreatePrincipal(string user, string password, out IPrincipal principal) 96 { 97 principal = null; 98 if (!Membership.ValidateUser(user, password)) 99 return false; 100 string[] roles = System.Web.Security.Roles.Provider.GetRolesForUser(user); 101 principal = new GenericPrincipal(new GenericIdentity(user), roles); 102 return true; 103 } 104 }
当然,你也需要在具体的Controller上添加相应的Attribute标记,例如:[YbApiAuthorize(PermissionKey = "Categories")]
四、实体对象的验证
实体对象的验证使用Fluent Validator组件进行验证,YbSoftWareFactory自动生成基本的客户端和服务器端的验证代码(如非空检查,字符串长度检查等),所生成的服务器端的验证代码示例如下:
public class CustomersValidator : AbstractValidator<Customers> { public CustomersValidator() { //此处添加验证逻辑 RuleFor(x => x.CustomerID).NotEmpty().WithMessage("必须输入标识"); RuleFor(x => x.CompanyName).NotEmpty().WithMessage("必须输入公司名"); RuleFor(x => x.ContactName).Length(30).WithMessage("联系人最多只能输入30个字符"); RuleFor(x => x.ContactTitle).Length(30).WithMessage("职务最多只能输入30个字符"); RuleFor(x => x.Address).Length(60).WithMessage("地址最多只能输入60个字符"); RuleFor(x => x.City).Length(15).WithMessage("市最多只能输入15个字符"); RuleFor(x => x.Region).Length(15).WithMessage("省/自治区最多只能输入15个字符"); RuleFor(x => x.PostalCode).Length(10).WithMessage("邮编最多只能输入10个字符"); RuleFor(x => x.Country).Length(15).WithMessage("国家最多只能输入15个字符"); RuleFor(x => x.Phone).Length(24).WithMessage("电话最多只能输入24个字符"); RuleFor(x => x.Fax).Length(24).WithMessage("传真最多只能输入24个字符"); } }
五、日志
为方便调试和错误诊断,系统集成long4net日志组件,所有异常信息均输出至相应的日志文件中。
有童鞋问YbSoftwareFactory怎么使用,好用不?(见过很多人是一根筋,自己把自己搞得太累~)其实使用YbSoftwareFactory生成ASP.NET MVC项目的源代码超级简单,仅需三步:
第一步:运行YbSoftwareFactory并连接数据库
第二步:编辑元数据信息,以便生成的界面更加友好
第三步:点击“解决方案”按钮生成源代码
短暂的等待后,在VS中打开所生成的源码解决方案,然后就是运行它....
注:本插件已正式发布,需要插件和代码的请和我联系。