Asp.net MVC 权限过滤器实现方法的最佳实践

在项目开发中,为了安全、方便地判断用户是否有访问当前资源(Action)的权限,我们一般通过全局过滤器来实现。

Asp.net MVC 页面中常见的权限判断使用过滤器主要在以下几种情况(根据权限判断的先后顺序):

1、判断要访问的Controller或Action是可以匿名访问;

2、判断要访问的Controller或Action是否登录就可以访问;

3、判断是否有访问某个Controller的Action的权限;

4、资源访问权限,如在下拉菜单中获取当前账号可以显示几个下拉项;

通过在全局过滤器中逐项判断,我们可以让有权限的用户顺利通过,没有权限的就会被禁止掉,下面来看看过滤器的代码实现:

    public class CheckPermissionFilter : IAuthorizationFilter
{
/// <summary>
/// 权限判断
/// </summary>
/// <param name="filterContext"></param>
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.HttpContext.Request.Url == null)
{
throw new ArgumentNullException("filterContext");
}
string pageUrl = filterContext.HttpContext.Request.Url.AbsolutePath; //OperateContext.GetThisPageUrl(false);
// 是否是Ajax请求
var bAjax = filterContext.HttpContext.Request.IsAjaxRequest();
// 1、允许匿名访问 用于标记在授权期间要跳过 AuthorizeAttribute 的控制器和操作的特性
var actionAnonymous = filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>;
var controllerAnonymous = filterContext.Controller.GetType().GetCustomAttributes(typeof(AllowAnonymousAttribute), true) as IEnumerable<AllowAnonymousAttribute>;
if ((actionAnonymous != null && actionAnonymous.Any()) || (controllerAnonymous != null && controllerAnonymous.Any()))
{
return;
}
// 判断是否登录或登录已超时 需要重新登录
if (Infrastructure.OperateContext.Current.UserInfo == null)
{
if (bAjax)
{
BusinessResultBase result = new BusinessResultBase();
result.Title = "未登录或登录已超时";
result.Status = false;
result.StatusCode = BusinessStatusCode.LoginTimeOut.ToString();
result.StatusMessage = "请重新登录系统。"; var jsonResult = new JsonResult();
jsonResult.Data = result;
filterContext.Result = jsonResult;
}
else
{
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = "Account", action = "Login" }));
}
}
else
{
// 2、拒绝某个账号登录当前系统
if (OperateContext.Current.IsDenyVisit())
{
if (bAjax)
{
BusinessResultBase result = new BusinessResultBase();
result.Title = "拒绝访问当前系统";
result.Status = false;
result.StatusCode = BusinessStatusCode.AccessDeny.ToString();
result.StatusMessage = "您的账号不允许访问当前系统。";
var jsonResult = new JsonResult();
jsonResult.Data = result;
filterContext.Result = jsonResult;
}
else
{
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = "Common", action = "DenyAccess", bAjaxReq = false, message = "没有获取您拥有的权限菜单,请尝试重新登录。" }));
}
}
else
{
// 3、判断登录状态 Controller Action 标签 某些功能只需判断是否登录 用户没登录 调到登录页面
// 判断Controller上是否有CheckLoginAttribute标签 只需要登录就可以访问
var checkLoginControllerAttr = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(CheckLoginAttribute), true) as IEnumerable<CheckLoginAttribute>;
if (checkLoginControllerAttr != null && checkLoginControllerAttr.Any())
{
return;
}
// 判断action上是否有CheckLoginAttribute标签 只需要登录就可以访问
var checkLoginActionAttr = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CheckLoginAttribute), true) as IEnumerable<CheckLoginAttribute>;
if (checkLoginActionAttr != null && checkLoginActionAttr.Any())
{
return;
}
// 4、有些要判断是否有某个菜单 action的权限 具体判断某个用户是否有某个权限
// 用于标记在授权期间需要CustomerResourceAttribute 的操作的特性
var attNames = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CustomerResourceAttribute), true) as IEnumerable<CustomerResourceAttribute>;
// 用户具有的菜单
var moduleList = OperateContext.Current.GetPermissionList();
if (moduleList == null || !moduleList.Any())
{
if (bAjax)
{
BusinessResultBase result = new BusinessResultBase();
result.Title = "没有访问权限";
result.Status = false;
result.StatusCode = BusinessStatusCode.AccessDeny.ToString();
result.StatusMessage = "没有获取您拥有的权限菜单,请尝试重新登录。";
var jsonResult = new JsonResult();
jsonResult.Data = result;
filterContext.Result = jsonResult;
}
else
{
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = "Common", action = "DenyAccess", bAjaxReq = false, message = "没有获取您拥有的权限菜单,请尝试重新登录。" }));
}
}
else
{
// 判断用户的权限菜单中的code是否与控制器上标示的资源的code一致
var joinResult = (from aclEntity in moduleList
join attName in attNames on aclEntity.Code equals attName.ResourceName
select attName).Any();
if (!joinResult)
{ if (bAjax)
{
BusinessResultBase result = new BusinessResultBase();
result.Title = "没有访问权限";
result.Status = false;
result.StatusCode = BusinessStatusCode.AccessDeny.ToString();
result.StatusMessage = "您没有访问权限:" + pageUrl;
var jsonResult = new JsonResult();
jsonResult.Data = result;
filterContext.Result = jsonResult;
}
else
{
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { Controller = "Common", action = "DenyAccess", bAjaxReq = false, message = "您没有访问权限:" + pageUrl }));
}
}
else
{
return;
}
}
}
}
}
}

基于这样的过滤器,我们的Controller和Action该怎么写呢?下面分别举出几个demo

1、匿名访问的Controller,这是一个测试安装环境中数据库连接是否连通的功能:

    /// <summary>
/// InstallationController
///
/// 安装环境测试
///
/// 2015-12-25 版本:1.0 SongBiao 创建文件。
///
/// <author>
/// <name>SongBiao</name>
/// <date>2015-12-25</date>
/// </author>
/// </summary>
[AllowAnonymous]
public class InstallationController : Controller
{
//
// GET: /InstallationEnvironment/
public ActionResult Index()
{
string result;
try
{
// 获取数据库时间 测试数据库是否通
using (IDbHelper dbHelper = DbHelperFactory.GetHelper(BaseSystemInfo.UserCenterDbType, BaseSystemInfo.UserCenterDbConnection))
{
result ="数据库已连通,当前数据库时间:"+ DateTime.Parse(dbHelper.GetDbDateTime()).ToString(BaseSystemInfo.DateTimeFormat);
}
}
catch (Exception ex)
{
result="数据库连接异常:"+ex.Message;
}
ViewBag.Result = result;
return View();
}
}

这个头部增加了AllowAnonymous标签,实现匿名访问功能,用户不需登录即可访问。

2、只需要登录就可以访问的功能

namespace DotNet.MVC.Controllers
{
using DotNet.Business;
using DotNet.Model;
using DotNet.MVC.Attributes;
using DotNet.MVC.Infrastructure; /// <summary>
/// 系统菜单权限
///
/// 修改纪录
///
/// 2015-09-09 版本:1.0 SongBiao 创建文件。
///
/// <author>
/// <name>SongBiao</name>
/// <date>2015-09-09</date>
/// </author>
/// </summary>
[CheckLogin]
public class ItemsSystemController : BaseController
{
//
// GET: /ItemsSystem/
public ActionResult Index()
{
return View();
} /// <summary>
/// 返回全部子系统下拉列表数据
/// </summary>
/// <returns></returns>
public ActionResult GetSystemSelect()
{
string tableName = "ItemsSystem";
Hashtable hashTable = new Hashtable();
List<BaseItemDetailsEntity> list = null;
var itemDetailsManager = new BaseItemDetailsManager(UserCenterDbHelper, UserInfo, tableName);
// 管理员取得所有数据
if (UserInfo.IsAdministrator)
{
list= itemDetailsManager.GetList<BaseItemDetailsEntity>(new KeyValuePair<string, object>(BaseItemDetailsEntity.FieldDeletionStateCode, )
, BaseItemDetailsEntity.FieldSortCode);
}
else
{
string permissionCode = "Resource.ManagePermission";
BasePermissionScopeManager permissionScopeManager = new BasePermissionScopeManager(UserCenterDbHelper, UserInfo);
string[] ids = permissionScopeManager.GetResourceScopeIds(UserInfo.Id, tableName, permissionCode);
list = itemDetailsManager.GetList<BaseItemDetailsEntity>(ids);
} if (list != null && list.Any())
{
var result = (from p in list select new { key = p.ItemName, value = p.ItemValue }).ToList();
hashTable.Add("list", result);
}
else
{
hashTable.Add("list", null);
}
return Json(hashTable, JsonRequestBehavior.AllowGet);
}
}
}

这里在控制器上使用 CheckLogin 标签来实现只要用户登录就可以访问当前控制器里的Action,当然也可以指定Controller下某个Action只要登录就可以访问,如下写法

        #region  public ActionResult LogOff() 退出
/// <summary>
/// 退出
/// </summary>
/// <returns></returns>
[CheckLogin]
public ActionResult LogOff()
{
try
{
OperateContext.Current.RemoveCurrent();
return RedirectToAction("Login", "Account");
}
catch (Exception)
{
return RedirectToAction("Login", "Account");
}
}
#endregion

那么,上面的CheckLogin怎么写呢?请看如下实现(OperateContext.Current.UserInfo是我实现的上下文中获取当前用户的方法,Views层也可以使用)。

namespace DotNet.MVC.Attributes
{
using DotNet.MVC.Infrastructure; /// <summary>
/// CheckLoginAttribute
/// 用于检测用户是否处于登录状态的标签
/// 某些功能只需要用户登录就可以使用
///
/// 修改纪录
///
/// 2015-10-11 版本:1.0 SongBiao 创建文件。
///
/// <author>
/// <name>SongBiao</name>
/// <date>2015-10-11</date>
/// </author>
/// </summary> public class CheckLoginAttribute :System.Web.Mvc.AuthorizeAttribute //AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool pass = false;
// httpContext.Session[DotNet.Business.Utilities.SessionName]
//!httpContext.Request.IsAuthenticated
if (OperateContext.Current.UserInfo==null)
{
httpContext.Response.StatusCode = ;//无权限状态码
pass = false;
}
else
{
pass = true;
}
return pass;
} protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
if (filterContext.HttpContext.Response.StatusCode == )
{
filterContext.Result = new RedirectResult("/Account/Login");
}
}
}
}

3、判断是否具有某个Action的权限

        /// <summary>
/// 获取列表数据
/// </summary>
/// <param name="quoteId"></param>
/// <param name="pager"></param>
/// <param name="sort"></param>
/// <param name="direction"></param>
/// <param name="firstHaveData"></param>
/// <returns></returns>
[AjaxRequest]
[CustomerResource("XpressionList")]
public ActionResult GetList(string quoteId, QuiGridPager pager, string sort, string direction, bool firstHaveData = true)
{
XpressionManager xpressionManager = new XpressionManager(BusinessDbHelper, UserInfo);
List<XpressionEntity> list = xpressionManager.ExecuteReaderByPage(quoteId, pager, sort, direction, "*", firstHaveData).ToList<ZtoQuoteExpressionEntity>();
return JsonQuiGridPager(pager, list, pager.totalRows, sort, direction, StartTime, firstHaveData);
}

通过在Action 上添加标签[CustomerResource("XpressionList")]实现,在过滤去中会自动去判断当前账号是否具有这个Action的访问权限,这个标签的实现如下,

namespace DotNet.MVC.Attributes
{
/// <summary>
/// CustomerResourceAttribute
///
/// 自定义的对方法应用的属性,在Action上标注权限菜单对应的Code
///
/// 修改纪录
///
/// 2015-10-11 版本:1.0 SongBiao 创建文件。
///
/// <author>
/// <name>SongBiao</name>
/// <date>2015-10-11</date>
/// </author>
/// </summary> [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class CustomerResourceAttribute : Attribute
{
private readonly string _resourceName; public CustomerResourceAttribute(string resourceName)
{
_resourceName = resourceName;
}
/// <summary>
/// 资源名称
/// </summary>
public string ResourceName
{
get { return _resourceName; }
} /// <summary>
/// 资源描述
/// </summary>
public string Descript { get; set; }
}
}

这里CustomerResourceAttribute是实现权限细分判断的关键点,在权限配置中,我们把每一个Action都存储到数据库中,实现界面(B/S)

Asp.net MVC 权限过滤器实现方法的最佳实践

通过Code实现当前用户是否具有该权限(当然也可以通过ID),Code实现了不可重复的检查。

目前这个过滤器底层使用了通用权限管理系统,主要用于权限菜单的存储及判断,数据库及功能设计上完全考虑到了各种形式的判断(按用户、公司、角色)。结合Asp.net MVC,轻轻松松就实现了权限过滤器功能。开发人员只需要会配置系统菜单及系统角色,通过接口或底层dll完全就可以了,开发人员只需关注业务功能的实现。

其它语言,如有类似权限判断全局访问过滤器的功能,也可参照实现。

对以上功能,如有疑问,欢迎一起探讨~~~

根据园友“wzmzang”的需求,提供OperateContext源码,大家参考~~

namespace DotNet.MVC.Infrastructure
{
using DotNet.MVC.Infrastructure.Caching;
using DotNet.Utilities;
using DotNet.Model;
using DotNet.Business;
using DotNet.MVC.Models; /// <summary>
/// OperateContext
///
/// 上下文
///
/// 修改纪录
///
/// 2015-09-13版本:1.0 SongBiao 创建文件。
///
/// <author>
/// <name>SongBiao</name>
/// <date>2015-4-13</date>
/// </author>
/// </summary> public class OperateContext
{
/// <summary>
/// 秘钥
/// </summary>
private string LoginUserKey = "LoginUserKey";
/// <summary>
/// 登陆提供者模式:Session、Cookie
/// </summary>
private string LoginProvider = BusinessSystemInfo.LoginProvider; #region public static OperateContext Current 获取当前 操作上下文
/// <summary>
/// 获取当前 操作上下文 (为每个处理浏览器请求的服务器线程 单独创建 操作上下文)
/// </summary>
public static OperateContext Current
{
get
{
OperateContext oContext = CallContext.GetData(typeof(OperateContext).Name) as OperateContext;
if (oContext == null)
{
oContext = new OperateContext();
CallContext.SetData(typeof(OperateContext).Name, oContext);
}
return oContext;
}
}
#endregion #region 增加的子系统之间跳转认证的code
/// <summary>
/// 生成Code
/// </summary>
/// <param name="openid">openid</param>
/// <param name="key">向谁跳传谁规定的key</param>
/// <returns></returns>
public static string MakeCode(string openid, string key)
{
DateTime time = DateTime.Now;
string data = time.ToString("dd") + "_" + openid + "_" + time.ToString("yyyy") + "_" + key + "_" + time.ToString("MM");
MD5 md5 = new MD5CryptoServiceProvider();
byte[] result = Encoding.Default.GetBytes(data);
byte[] output = md5.ComputeHash(result);
data = BitConverter.ToString(output).Replace("-", "").ToLower();
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
byte[] bytes = sha1.ComputeHash(Encoding.ASCII.GetBytes(data));//"596d42faf5710b35c7ea0f0a9600ee31" F69D39E1CA07FC23C1CE62F549E9D5B9780
//转16进制 清除前面0
StringBuilder strB = new StringBuilder();
for (int i = ; i < bytes.Length; i++)
{
string strX2 = bytes[i].ToString("X2");
if (strX2.Substring(, ) == "")
{
strX2 = strX2.Substring(, );
}
strB.Append(strX2);
}
return strB.ToString();
}
/// <summary>
/// Code验证
/// </summary>
/// <param name="openid">openid</param>
/// <param name="code">待验证的数据</param>
/// <param name="key">自己系统规定的key</param>
/// <returns></returns>
public static bool ValidateCode(string openid, string code, string key)
{
string signedData = MakeCode(openid, key);
return code.Equals(signedData, StringComparison.OrdinalIgnoreCase);
}
#endregion public BaseUserInfo _userInfo = null;
/// <summary>
/// 获取当前用户
/// </summary>
/// <returns></returns>
public BaseUserInfo UserInfo
{
get
{
try
{
if (string.Equals(LoginProvider, "Cookie", StringComparison.OrdinalIgnoreCase))
{
if (!string.IsNullOrWhiteSpace(CookieHelper.GetCookie(LoginUserKey)))
{
_userInfo = JsonConvert.DeserializeObject<BaseUserInfo>(SecretUtil.Decrypt(CookieHelper.GetCookie(LoginUserKey)));
}
}
else
{
if (SessionHelper.Get(LoginUserKey) != null)
{
_userInfo = JsonConvert.DeserializeObject<BaseUserInfo>(SecretUtil.Decrypt(SessionHelper.Get(LoginUserKey).ToString()));
}
}
return _userInfo;
}
catch
{
throw new Exception("登录信息超时,请重新登录。");
}
}
set
{
_userInfo = value;
}
} #region Http上下文 及 相关属性
/// <summary>
/// Http上下文
/// </summary>
HttpContext ContextHttp
{
get
{
return HttpContext.Current;
}
}
/// <summary>
/// 输出对象
/// </summary>
HttpResponse Response
{
get
{
return ContextHttp.Response;
}
}
/// <summary>
/// 请求对象
/// </summary>
HttpRequest Request
{
get
{
return ContextHttp.Request;
}
}
/// <summary>
/// Session对象
/// </summary>
System.Web.SessionState.HttpSessionState Session
{
get
{
return ContextHttp.Session;
}
} #endregion #region public void AddCurrent(BaseUserInfo userInfo) 写入登录信息
/// <summary>
/// 写入登录信息
/// </summary>
/// <param name="userInfo">成员信息</param>
public void AddCurrent(BaseUserInfo userInfo)
{
try
{
userInfo.SystemCode = BaseSystemInfo.SystemCode;
if (LoginProvider == "Cookie")
{
CookieHelper.WriteCookie(LoginUserKey, SecretUtil.Encrypt(JsonConvert.SerializeObject(userInfo)), );
}
else
{
SessionHelper.Add(LoginUserKey, SecretUtil.Encrypt(JsonConvert.SerializeObject(userInfo)));
}
//清除上一次的
ICacheManager cacheManager = new MemoryCacheManager();
cacheManager.Clear();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
#endregion #region public void RemoveCurrent() 删除登录信息
/// <summary>
/// 删除登录信息
/// </summary>
public void RemoveCurrent()
{
if (LoginProvider == "Cookie")
{
HttpCookie objCookie = new HttpCookie(LoginUserKey.Trim());
objCookie.Expires = DateTime.Now.AddYears(-);
ContextHttp.Response.Cookies.Add(objCookie);
}
else
{
SessionHelper.Remove(LoginUserKey.Trim());
}
ICacheManager cacheManager = new MemoryCacheManager();
cacheManager.Clear();
}
#endregion #region public bool IsOverdue() 是否过期
/// <summary>
/// 是否过期
/// </summary>
/// <returns></returns>
public bool IsOverdue()
{
object str = "";
if (LoginProvider == "Cookie")
{
str = CookieHelper.GetCookie(LoginUserKey);
}
else
{
str = SessionHelper.Get(LoginUserKey);
}
if (str != null && str.ToString() != "")
{
return true;
}
else
{
return false;
}
}
#endregion #region public bool IsStaticResource() 是否是访问静态资源
/// <summary>
/// public bool IsStaticResource() 是否是访问静态资源
/// </summary>
/// <returns></returns>
public bool IsStaticResource()
{
if (ContextHttp == null)
throw new ArgumentNullException("request");
string path = ContextHttp.Request.Path;
string extension = VirtualPathUtility.GetExtension(path);
if (extension == null) return false;
switch (extension.ToLower())
{
case ".axd":
case ".ashx":
case ".bmp":
case ".css":
case ".gif":
case ".htm":
case ".html":
case ".ico":
case ".jpeg":
case ".jpg":
case ".js":
case ".png":
case ".rar":
case ".zip":
return true;
}
return false;
}
#endregion #region public string ServerVariables(string name) 获取服务器端变量
/// <summary>
/// Gets server variable by name
/// </summary>
/// <param name="name">Name</param>
/// <returns>Server variable</returns>
public string ServerVariables(string name)
{
string result = string.Empty;
try
{
if (ContextHttp == null || ContextHttp.Request == null)
return result; //put this method is try-catch
//as described here http://www.nopcommerce.com/boards/t/21356/multi-store-roadmap-lets-discuss-update-done.aspx?p=6#90196
if (ContextHttp.Request.ServerVariables[name] != null)
{
result = ContextHttp.Request.ServerVariables[name];
}
}
catch
{
result = string.Empty;
}
return result;
}
#endregion #region public static string GetClientIP() 获取客户端的IP地址
/// <summary>
/// 获取客户端的IP地址
/// </summary>
/// <returns></returns>
public static string GetClientIP()
{
string result = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (!string.IsNullOrEmpty(result))
{
//可能有代理
if (result.IndexOf(".", StringComparison.Ordinal) == -)
//没有"."肯定是非IPv4格式
result = null;
else
{
if (result.IndexOf(",", StringComparison.Ordinal) != -)
{
//有",",估计多个代理。取第一个不是内网的IP。
result = result.Replace(" ", "").Replace("", "");
string[] temparyip = result.Split(",;".ToCharArray());
for (int i = ; i < temparyip.Length; i++)
{
if (IsIPAddress(temparyip[i]) && temparyip[i].Substring(, ) != "10." && temparyip[i].Substring(, ) != "192.168" && temparyip[i].Substring(, ) != "172.16.")
{
return temparyip[i];//找到不是内网的地址
}
}
}
else if (IsIPAddress(result)) //代理即是IP格式
return result;
else
result = null;//代理中的内容 非IP,取IP
}
}
string ipAddress =
(HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] != null &&
HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] != String.Empty) ? HttpContext.Current.Request.ServerVariables
["HTTP_X_FORWARDED_FOR"] : HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
if (string.IsNullOrEmpty(result))
result = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
if (string.IsNullOrEmpty(result))
result = HttpContext.Current.Request.UserHostAddress;
return result;
}
/// <summary>
/// ip判断
/// </summary>
/// <param name="str1"></param>
/// <returns></returns>
private static bool IsIPAddress(string str1)
{
if (string.IsNullOrEmpty(str1) || str1.Length < || str1.Length > )
return false;
string regformat = @"^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$";
Regex regex = new Regex(regformat, RegexOptions.IgnoreCase);
return regex.IsMatch(str1);
}
#endregion /// <summary>
/// 解决Error"基础连接已经关闭: 未能为SSL/TLS 安全通道建立信任关系。"
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
} /// <summary>
/// 业务库连接
/// </summary>
public static IDbHelper BusinessDbHelper
{
get
{
return DbHelperFactory.GetHelper(BaseSystemInfo.BusinessDbType, BaseSystemInfo.BusinessDbConnection);
}
} /// <summary>
/// 用户中心库连接
/// </summary>
public static IDbHelper UserCenterDbHelper
{
get
{
return DbHelperFactory.GetHelper(BaseSystemInfo.UserCenterDbType, BaseSystemInfo.UserCenterDbConnection);
}
} }
}
上一篇:Matrix Factorization, Algorithms, Applications, and Avaliable packages


下一篇:LabVIEW是一种通用的编程语言吗?