• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

一步一步Asp.NetMVC系列_权限管理之权限控制一步一步Asp.NetMVC系列_权限管理之权限控 ...

原作者: [db:作者] 来自: [db:来源] 收藏 邀请
随笔-23  文章-0  评论-114 
 

在权限管理中一个很重要的就是关于权限的拦截验证问题,特别是我们在webform中的验证,比纯winform要更复杂,winform可以通过验证把按钮隐藏或者禁用的方式,但是在web中我们不能仅仅通过隐藏按钮,不显示菜单/按钮之类的手段,因为客户端的代码都是透明的,如果我们不在服务端把好关,那么权限根本就无从谈起,我们必须彻底的进行验证,每一步动作都要进行验证,客户端的每一个ajax提交都要进行验证,如果任何一个ajax 动作都做过验证了,那么至少可以保证基本的安全性了.

在纯webform中,我们通常怎么来进行权限控制呢?

一般情况下,设计基类然后,在基类写好验证方法,子类调用并验证

我们来看看启航动力的开源CMS怎么进行的验证:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using System.Web;
   5:  using System.Web.UI.WebControls;
   6:  using DTcms.Common;
   7:   
   8:  namespace DTcms.Web.UI
   9:  {
  10:      public class ManagePage : System.Web.UI.Page
  11:      {
  12:          protected internal Model.siteconfig siteConfig;
  13:   
  14:          public ManagePage()
  15:          {
  16:              this.Load += new EventHandler(ManagePage_Load);
  17:              siteConfig = new BLL.siteconfig().loadConfig(Utils.GetXmlMapPath("Configpath"));
  18:          }
  19:   
  20:          private void ManagePage_Load(object sender, EventArgs e)
  21:          {
  22:              //判断管理员是否登录
  23:              if (!IsAdminLogin())
  24:              {
  25:                  Response.Write("<script>parent.location.href='" + siteConfig.webpath + siteConfig.webmanagepath + "/login.aspx'</script>");
  26:                  Response.End();
  27:              }
  28:          }
  29:   
  30:          #region 管理员============================================
  31:          /// <summary>
  32:          /// 判断管理员是否已经登录(解决Session超时问题)
  33:          /// </summary>
  34:          public bool IsAdminLogin()
  35:          {
  36:              //如果Session为Null
  37:              if (Session[DTKeys.SESSION_ADMIN_INFO] != null)
  38:              {
  39:                  return true;
  40:              }
  41:              else
  42:              {
  43:                  //检查Cookies
  44:                  string adminname = Utils.GetCookie("AdminName", "DTcms"); //解密用户名
  45:                  string adminpwd = Utils.GetCookie("AdminPwd", "DTcms");
  46:                  if (adminname != "" && adminpwd != "")
  47:                  {
  48:                      BLL.manager bll = new BLL.manager();
  49:                      Model.manager model = bll.GetModel(adminname, adminpwd);
  50:                      if (model != null)
  51:                      {
  52:                          Session[DTKeys.SESSION_ADMIN_INFO] = model;
  53:                          return true;
  54:                      }
  55:                  }
  56:              }
  57:              return false;
  58:          }
  59:   
  60:          /// <summary>
  61:          /// 取得管理员信息
  62:          /// </summary>
  63:          public Model.manager GetAdminInfo()
  64:          {
  65:              if (IsAdminLogin())
  66:              {
  67:                  Model.manager model = Session[DTKeys.SESSION_ADMIN_INFO] as Model.manager;
  68:                  if (model != null)
  69:                  {
  70:                      return model;
  71:                  }
  72:              }
  73:              return null;
  74:          }
  75:   
  76:          /// <summary>
  77:          /// 检查管理员权限
  78:          /// </summary>
  79:          /// <param name="channel_id">频道ID</param>
  80:          /// <param name="action_type">操作类型</param>
  81:          public void ChkAdminLevel(int channel_id, string action_type)
  82:          {
  83:              Model.manager model = GetAdminInfo();
  84:              BLL.manager_role bll = new BLL.manager_role();
  85:              bool result = bll.Exists(model.role_id, channel_id, action_type);
  86:              if (!result)
  87:              {
  88:                  string msbox = "parent.f_errorTab(\"错误提示\", \"您没有管理该页面的权限,请勿尝试非法进入!\")";
  89:                  //ClientScript.RegisterClientScriptBlock(Page.GetType(), "JsPrint", msbox.ToString(), true);  //修正BUG
  90:                  Response.Write("<script type=\"text/javascript\">" + msbox + "</script>");
  91:                  Response.End();
  92:              }
  93:          }
  94:   
  95:          /// <summary>
  96:          /// 检查管理员权限
  97:          /// </summary>
  98:          /// <param name="channel_name">栏目名称</param>
  99:          /// <param name="action_type">操作类型</param>
 100:          public void ChkAdminLevel(string channel_name, string action_type)
 101:          {
 102:              Model.manager model = GetAdminInfo();
 103:              BLL.manager_role bll = new BLL.manager_role();
 104:              bool result = bll.Exists(model.role_id, channel_name, action_type);
 105:              if (!result)
 106:              {
 107:                  string msbox = "parent.f_errorTab(\"错误提示\", \"您没有管理该页面的权限,请勿尝试非法进入!\")";
 108:                  //ClientScript.RegisterClientScriptBlock(Page.GetType(), "JsPrint", msbox.ToString(), true);  //修正BUG
 109:                  Response.Write("<script type=\"text/javascript\">" + msbox + "</script>");
 110:                  Response.End();
 111:              }
 112:          }
 113:   
 114:          /// <summary>
 115:          /// 检查管理员权限
 116:          /// </summary>
 117:          /// <param name="channel_name">栏目名称</param>
 118:          /// <param name="action_type">操作类型</param>
 119:          /// <returns>bool</returns>
 120:          public bool IsAdminLevel(string channel_name, string action_type)
 121:          {
 122:              Model.manager model = GetAdminInfo();
 123:              BLL.manager_role bll = new BLL.manager_role();
 124:              return bll.Exists(model.role_id, channel_name, action_type);
 125:          }
 126:   
 127:          #endregion
 128:   
 129:          #region 枚举==============================================
 130:   
 131:          /// <summary>
 132:          /// 统一管理操作枚举
 133:          /// </summary>
 134:          public enum ActionEnum
 135:          {
 136:              /// <summary>
 137:              /// 所有
 138:              /// </summary>
 139:              All,
 140:              /// <summary>
 141:              /// 查看
 142:              /// </summary>
 143:              View,
 144:              /// <summary>
 145:              /// 添加
 146:              /// </summary>
 147:              Add,
 148:              /// <summary>
 149:              /// 修改
 150:              /// </summary>
 151:              Edit,
 152:              /// <summary>
 153:              /// 删除
 154:              /// </summary>
 155:              Delete
 156:          }
 157:   
 158:          /// <summary>
 159:          /// 属性类型枚举
 160:          /// </summary>
 161:          public enum AttributeEnum
 162:          {
 163:              /// <summary>
 164:              /// 输入框
 165:              /// </summary>
 166:              Text,
 167:              /// <summary>
 168:              /// 下拉框
 169:              /// </summary>
 170:              Select,
 171:              /// <summary>
 172:              /// 单选框
 173:              /// </summary>
 174:              Radio,
 175:              /// <summary>
 176:              /// 复选框
 177:              /// </summary>
 178:              CheckBox
 179:          }
 180:          #endregion
 181:   
 182:          #region JS提示============================================
 183:   
 184:          /// <summary>
 185:          /// 添加编辑删除提示
 186:          /// </summary>
 187:          /// <param name="msgtitle">提示文字</param>
 188:          /// <param name="url">返回地址</param>
 189:          /// <param name="msgcss">CSS样式</param>
 190:          protected void JscriptMsg(string msgtitle, string url, string msgcss)
 191:          {
 192:              string msbox = "parent.jsprint(\"" + msgtitle + "\", \"" + url + "\", \"" + msgcss + "\")";
 193:              ClientScript.RegisterClientScriptBlock(Page.GetType(), "JsPrint", msbox, true);
 194:          }
 195:   
 196:          /// <summary>
 197:          /// 带回传函数的添加编辑删除提示
 198:          /// </summary>
 199:          /// <param name="msgtitle">提示文字</param>
 200:          /// <param name="url">返回地址</param>
 201:          /// <param name="msgcss">CSS样式</param>
 202:          /// <param name="callback">JS回调函数</param>
 203:          protected void JscriptMsg(string msgtitle, string url, string msgcss, string callback)
 204:          {
 205:              string msbox = "parent.jsprint(\"" + msgtitle + "\", \"" + url + "\", \"" + msgcss + "\", " + callback + ")";
 206:              ClientScript.RegisterClientScriptBlock(Page.GetType(), "JsPrint", msbox, true);
 207:          }
 208:          #endregion
 209:   
 210:      }
 211:  }

在子类校验的时候继承ManagePage的基类,然后就这样校验:

 

可以看到这种是通常的设计方案,基类定义,子类校验,不过这个启航动力CMS,他完全是在基类定义枚举,控制仅仅停留在增删改查,浏览,这些,不过CMS确实这一层就可以了,而且它的设计感觉不太好,因为到处都是验证代码,每一个增删改查方法都有这些验证的代码,如果是我我就会用委托的方式绑定方法,在Page_Load里面验证权限,委托绑定增删改查方法,这样,把验证的过程大大节省,这些甚至可以设计成公共的方法,来实现.

 

我们今天讲解的是MVC里面的权限验证,MVC天然的Controller,Action,让我们的权限控制更加容易,而且更简洁,更清晰.

首先,先来看看,MVC里面的一个小特色设计:

 

这个是一个普通的model,在mvc示例项目中,他采用Attribute方式来验证,我当时看到的时候感觉耳目一新,以前看<<CLR VIA C#>> Attribute的时候,当时还在想这些东西可以干什么??仅仅是元数据描述??后来才发现,这东西太强大了,控件的设计,反射,ORM,无处不在,Attributes也让代码更加优雅.

我们可以在mvc中定义各种Attribute来让我们的代码更优雅,而且让权限更简洁,比如我们经常会设计,

  xxxx页面登录后才能访问,

  xxxxx页面匿名用户也能访问,

  xxxx页面必须经过权限验证才能访问,

  xxxxx各种类型

我们可以定义各种Attribute来描述它,这种描述也让权限控制更加优雅.

 

比如:我们有几个登录页面,错误显示页面等等,这些页面,可以单独设计一个Attribute标记来标识.

我们可以定义各种各样的标记来描述权限控制,

可以看到我们在这里只需要标记匿名标记,然后在权限拦截中进行处理,如果存在Anonymous标记就默认允许.

这里在公共基类中设计验证标记,子类继承,这样,我们就可以达到子类全部都需要验证处理..

接下来,我们就看看,我们的权限拦截如何处理的.

我们的权限拦截是通过继承ActionFilterAttribute,自定义拦截器,这里参考传说中弦哥的思路.

简单地说,ActionFilter就是Action过滤器,任何一个请求都像筛子一样的过滤.所以,这个Filter就是筛子,我们需要的就是在这个筛子上做权限控制,这样我们的权限不就容易得多了么?

这时候,我想到了以前的一张asp.net生命周期,一个请求过来同样经过了HttpModule等一层层,在那里做权限拦截,可能效果更好,如果webform设计,可能要尝试一下看看能不能这么做.

   1:    /// <summary>
   2:      /// 权限拦截
   3:      /// </summary>
   4:      [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
   5:      public class PermissionFilterAttribute : ActionFilterAttribute
   6:      {
   7:          /// <summary>
   8:          /// 权限拦截
   9:          /// </summary>
  10:          /// <param name="filterContext"></param>
  11:          public override void OnActionExecuting(ActionExecutingContext filterContext)
  12:          {
  13:              //权限拦截是否忽略
  14:              bool IsIgnored = false;
  15:              if (filterContext == null)
  16:              {
  17:                  throw new ArgumentNullException("filterContext");
  18:              }
  19:              var path = filterContext.HttpContext.Request.Path.ToLower();
  20:              //获取当前配置保存起来的允许页面
  21:              IList<string> allowPages = ConfigSettings.GetAllAllowPage();
  22:              foreach (string page in allowPages)
  23:              {
  24:                  if (page.ToLower() == path)
  25:                  {
  26:                      IsIgnored = true;
  27:                      break;
  28:                  }
  29:              }
  30:              if (IsIgnored)
  31:                  return;
  32:              //接下来进行权限拦截与验证
  33:              object[] attrs = filterContext.ActionDescriptor.GetCustomAttributes(typeof(ViewPageAttribute), true);
  34:              var isViewPage = attrs.Length == 1;//当前Action请求是否为具体的功能页
  35:   
  36:              if (this.AuthorizeCore(filterContext) == false)//根据验证判断进行处理
  37:              {
  38:                  //注:如果未登录直接在URL输入功能权限地址提示不是很友好;如果登录后输入未维护的功能权限地址,那么也可以访问,这个可能会有安全问题
  39:                  if (isViewPage == true)
  40:                  {
  41:                      //跳转到登录页面
  42:                      filterContext.RequestContext.HttpContext.Response.Redirect("~/Admin/Manage/UserLogin");
  43:                  }
  44:                  else
  45:                  {
  46:   
  47:                      //跳转到登录页面
  48:                      filterContext.RequestContext.HttpContext.Response.Redirect("~/Admin/Manage/Error");
  49:                  }
  50:              }
  51:          }

 

这个Attribute直接借鉴的弦哥的模型,

这样我们可以对程序进行细化的处理过程.

针对不同的标记进行处理:

   1:    /// <summary>
   2:          /// [Anonymous标记]验证是否匿名访问
   3:          /// </summary>
   4:          /// <param name="filterContext"></param>
   5:          /// <returns></returns>
   6:          public bool CheckAnonymous(ActionExecutingContext filterContext)
   7:          {
   8:              //验证是否是匿名访问的Action
   9:              object[] attrsAnonymous = filterContext.ActionDescriptor.GetCustomAttributes(typeof(AnonymousAttribute), true);
  10:              //是否是Anonymous
  11:              var Anonymous = attrsAnonymous.Length == 1;
  12:              return Anonymous;
  13:          }
  14:          /// <summary>
  15:          /// [LoginAllowView标记]验证是否登录就可以访问(如果已经登陆,那么不对于标识了LoginAllowView的方法就不需要验证了)
  16:          /// </summary>
  17:          /// <param name="filterContext"></param>
  18:          /// <returns></returns>
  19:          public bool CheckLoginAllowView(ActionExecutingContext filterContext)
  20:          {
  21:              //在这里允许一种情况,如果已经登陆,那么不对于标识了LoginAllowView的方法就不需要验证了
  22:              object[] attrs = filterContext.ActionDescriptor.GetCustomAttributes(typeof(LoginAllowViewAttribute), true);
  23:              //是否是LoginAllowView
  24:              var ViewMethod = attrs.Length == 1;
  25:              return ViewMethod;
  26:          }
  27:   
  28:          /// <summary>
  29:          /// //权限判断业务逻辑
  30:          /// </summary>
  31:          /// <param name="filterContext"></param>
  32:          /// <param name="isViewPage">是否是页面</param>
  33:          /// <returns></returns>
  34:          protected virtual bool AuthorizeCore(ActionExecutingContext filterContext)
  35:          {
  36:   
  37:              if (filterContext.HttpContext == null)
  38:              {
  39:                  throw new ArgumentNullException("httpContext");
  40:              }
  41:              //验证当前Action是否是匿名访问Action
  42:              if (CheckAnonymous(filterContext))
  43:                  return true;
  44:              //未登录验证
  45:              if (SessionHelper.Get("UserID") == null)
  46:              {
  47:                  return false;
  48:              }
  49:              //验证当前Action是否是登录就可以访问的Action
  50:              if (CheckLoginAllowView(filterContext))
  51:                  return true;
  52:   
  53:              //下面开始用户权限验证
  54:              var user = new UserService();
  55:              SysCurrentUser CurrentUser = new SysCurrentUser();
  56:              var controllerName = filterContext.RouteData.Values["controller"].ToString();
  57:              var actionName = filterContext.RouteData.Values["action"].ToString();
  58:              //如果是超级管理员,直接允许
  59:              if (CurrentUser.UserID == ConfigSettings.GetAdminUserID())
  60:              {
  61:                  return true;
  62:              }
  63:              //如果拥有超级管理员的角色就默认全部允许
  64:              string AdminUserRoleID = ConfigSettings.GetAdminUserRoleID().ToString();
  65:              //检查当前角色组有没有超级角色
  66:              if (Tools.CheckStringHasValue(CurrentUser.UserRoles, ',', CurrentUser.UserRoles))
  67:              {
  68:                  return true;
  69:              }
  70:   
  71:              //Action权限验证
  72:              if (controllerName.ToLower() != "manage")//如果当前Action请求为具体的功能页并且不是Manage中 Index页和Welcome页
  73:              {
  74:                  //验证
  75:                  if (!user.RoleHasOperatePermission(CurrentUser.UserRoles, controllerName, actionName))//如果验证该操作是否拥有权限
  76:                  {
  77:                      return false;
  78:                  }
  79:              }
  80:              //管理页面直接允许
  81:              return true;
  82:          }

可以看到我们仅仅这一个PermissionAttribute配合其他的Attribute就实现了权限的拦截控制.

当然,我们仍然需要配置那些东西?

过滤页面.....

有一些页面我们可以直接配置允许访问的页面:

   1:    var path = filterContext.HttpContext.Request.Path.ToLower();
   2:              //获取当前配置保存起来的允许页面
   3:              IList<string> allowPages = ConfigSettings.GetAllAllowPage();
   4:              foreach (string page in allowPages)
   5:              {
   6:                  if (page.ToLower() == path)
   7:                  {
   8:                      IsIgnored = true;
   9:                      break;
  10:                  }
  11:              }
  12:              if (IsIgnored)
  13:                  return;

我们在自定义配置文件中,可以定义允许访问的页面,比如:错误页之类的,可能有些人觉得,不需要,但是这个配置是在程序发布以后,仍然能够很轻松的进行配置,所以,预留一个配置页面还是蛮有必要的.

同时我们也需要配置超级管理员身份和角色....

有人觉得,这些需要么?

我觉得是需要的,因为数据库读取出来的角色,根本没办法分辨超级管理员,只能数据库动态配置,我们就预定义这样的一个超级管理员角色的身份ID,让这个超级管理角色拥有任何的功能和任何的权限,甚至读取数据库的时候,都是读取的所有模块权限,菜单权限.

这样我们的配置工作就非常简单了,特别是数据库没有大部分配置初试信息(比如菜单信息,角色信息,)的时候,我们不需要手动往数据库添加数据,直接在程序中添加就可以了,也算是一个超级管理员身份.

 

接下来,我们的权限控制基本上就差不多了,现在我们可以安心的写页面了,权限神马东西,跟咱关系就不大了,这样,分工不是更爽,干别人的活才是最纠结的.......

 

 

接下来,我们可以继续设计一些公共类来简化我们日常的操作,对于公共类的设计,我觉得要拿出最大的热情与态度,要知道这些是能够真正节省我们时间的东西.

只有设计好它,我们以后才会更快更爽更轻松.

 

我们经常会遇到这样的代码:

   1:    /// <summary>
   2:      /// 保存资料业务
   3:      /// </summary>
   4:      /// <param name="context"></param>
   5:      public void SaveCompany(HttpContext context)
   6:      {
   7:          //用户json数据读取
   8:          company Info=new company ();
   9:          String CompanyStr = context.Request["Company"];
  10:          string id=context.Request["id"];
  11:          string pic = context.Request["pic"];
  12:          if (!Tools.IsValidInput(ref pic, true) || !Tools.IsValidInput(ref id, true))
  13:          {
  14:              return;
  15:          }
  16:        //图片保存
  17:          //System.IO.StreamWriter sw = new System.IO.StreamWriter(context.Server.MapPath("tzt.txt"), true);
  18:          //sw.Write(CompanyStr);
  19:          //sw.Close();
  20:          //使用Newtonsoft.Json.dll组件解析json对象
  21:       
  22:          JObject o = JObject.Parse(CompanyStr);
  23:          Info.Username = (string)o.SelectToken("Username");
  24:          if (!new companyBLL().CheckExistUserName(Info.Username))
  25:          {
  26:              context.Response.Write(false);
  27:              return;
  28:          }
  29:          Info.Password = (string)o.SelectToken("Password");
  30:          Info.Name = (string)o.SelectToken("Name");
  31:          Info.Isrecommend = ((string)o.SelectToken("Isrecommend"))=="true" ? "1" : "0";
  32:          Info.Fac = (string)o.SelectToken("Fac");
  33:          Info.Representative = (string)o.SelectToken("Representative");
  34:          Info.Isshow = ((string)o.SelectToken("Isshow")) == "true" ? "1" : "0";
  35:          Info.State = ((string)o.SelectToken("State")) == "true" ? "1" : "0";
  36:          Info.State1 = ((string)o.SelectToken("State1")) == "true" ? "1" : "0";
  37:         
  38:          Info.Zipcode = (string)o.SelectToken("Zipcode");
  39:          Info.QQ = (string)o.SelectToken("QQ");
  40:          Info.Telephone = (string)o.SelectToken("Telephone");
  41:          Info.mobilephone = (string)o.SelectToken("mobilephone");
  42:          Info.Email = (string)o.SelectToken("Email");
  43:          Info.Address = (string)o.SelectToken("Address");
  44:          Info.Website = (string)o.SelectToken("Website");
  45:          Info.Award = (string)o.SelectToken("Award");
  46:          Info.Introduction = (string)o.SelectToken("Introduction");
  47:          Info.Picturepath = pic;
  48:   
  49:          if (!string.IsNullOrEmpty(id))
  50:              Info.Id = Convert.ToInt32(id);
  51:   
  52:          if (!Info.Id.HasValue)
  53:          {
  54:              Info.rank = 0;
  55:              Info.hit = 0;
  56:              //执行增加操作
  57:              new companyBLL().AddNew(Info);
  58:              SMTP smtp = new SMTP(Info.Email);
  59:              string webpath = context.Request.Url.Scheme + "://" + context.Request.Url.Authority + System.Web.VirtualPathUtility.ToAbsolute("~/Default.aspx");
  60:              smtp.Activation(webpath, Info.Name);//发送激活邮件
  61:          }
  62:          else
  63:          {
  64:              new companyBLL().Update(Info);
  65:          }
  66:   
  67:   
  68:      }

这是我以前一个电子商务网站项目写的,

我现在看,简直到处都是毛病.......

首先,关于form读取内容占了一大块.......

可以想象,到处都是赋值语句,看到都吐血,再加上一些验证非空,验证函数等等,更是罪加一等的恶劣.

当然我们需要一步一步的解决这些问题,首先是剥离关于表单读取的内容,封装到一个类中,然后在类中进行验证处理.

权限验证中的样例,

验证也没有做大块的处理,不过大家可以注意,可以看到想当的简洁,我们同时需要各种辅助类的设计,强制转化的处理,非空验证的处理,等等,我们直接设计成扩展方法就能达到上图中ObjToIntNULL()的形式, 也不需要ConvertToInt32(xxxxx)的形式来转化了,不是更简洁么?

   1:  /*  作者:       tianzh
   2:  *  创建时间:   2012/7/22 15:38:20
   3:  *
   4:  */
   5:  using System;
   6:  using System.Collections.Generic;
   7:  using System.Linq;
   8:  using System.Text;
   9:   
  10:  namespace TZHSWEET.Common
  11:  {
  12:      /// <summary>
  13:      /// 强制转化辅助类(无异常抛出)
  14:      /// </summary>
  15:      public static class ConvertHelper
  16:      {
  17:          #region 强制转化
  18:          /// <summary>
  19:          /// object转化为Bool类型
  20:          /// </summary>
  21:          /// <param name="obj"></param>
  22:          /// <returns></returns>
  23:          public static bool ObjToBool(this object obj)
  24:          {
  25:              bool flag;
  26:              if (obj == null)
  27:              {
  28:                  return false;
  29:              }
  30:              if (obj.Equals(DBNull.Value))
  31:              {
  32:                  return false;
  33:              }
  34:              return (bool.TryParse(obj.ToString(), out flag) && flag);
  35:          }
  36:          /// <summary>
  37:          /// object强制转化为DateTime类型(吃掉异常)
  38:          /// </summary>
  39:          /// <param name="obj"></param>
  40:          /// <returns></returns>
  41:          public static DateTime? ObjToDateNull(this object obj)
  42:          {
  43:              if (obj == null)
  44:              {
  45:                  return null;
  46:              }
  47:              try
  48:              {
  49:                  return new DateTime?(Convert.ToDateTime(obj));
  50:  
                      

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
IISandASP.NET:TheApplicationPool发布时间:2022-07-10
下一篇:
[Solution]ASP.NETIdentity(1)快速入门发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap