在公司做一个Offline的服务管理系统,功能很简单主要是记录服务申请单,审批以及监控等。这个系统的架构是同事搭建的(像我这种菜鸟公司也不会让我搭建),架构很常规,三层架构,但是同事说另外加上了领域层(他也没有给我讲清楚,但是我看到在这一层上,都是一些log,metric的记录行为)。
为了低耦合高内聚的目的,我们使用了IOC框架(通过IOC相当于初始化,省去了New,可以直接调用,但是IOC有个IOC容器在配置文件XML中,需要你将接口与其实现类一一对应好(MapTo),除了关系对应还有文件的路径,命名空间,构造函数参数等),Core层(也就是业务逻辑层,或者是服务接口层),在这一层在接口中定义服务方法,然后继承实现,所操作的实体对象都是接口类,这些实体接口类的实例化在Data层中,然而IOC实现了Core对Data层无依赖关系。在Controller中(也就是Web层)对服务进行调用是通过IOC实现的,通过IOC Resolver方法创建服务对象(IOC的初始化放在Global.asax文件的Application_Start()方法中,Global.asax文件首先被调用,其中包括着过滤器,错误日志处理,路由规则,还有就是IOC的初始化。),在创建服务对象时,将数据仓库注入到服务对象中,这样就可以直接使用服务对象调用仓库中的数据库处理方法了。因为仓库的接口类是在Core层中定义的,所以注入的时候是注入的仓库类接口对象,而仓库接口的具体实现是在Data层,操作着不同的数据实体类(具体实现的对象了),完成数据层的方法(无非是一些增删改查一类的,当然其中有一些函数,像对实体对象的增加,修改等,直接调用基类中已经封装好的就行),只有那些具体的需求的方法自己拼sql写即可。
以上是整个系统的设计思路,其中的一些细节才是关键。
1.如何写权限?因为在系统中分为管理员,审核人,用户这三种角色,我们是用enum类型定义
public enum RoleType
{
Admin = 1,
User = 2,
Auditor =3
}
在web层的Controller中每个控制器都继承于一个BaseController(这个类是自己定义的),在这个类中,我们写上权限的判别方法。通过
public static UserInfo CurrentUser
{
get
{
var obj = System.Web.HttpContext.Current.Session["user"];
try
{
if (obj == null)
{
// Get windows user
var loggedUser = System.Web.HttpContext.Current.User.Identity;
if (loggedUser != null && loggedUser.IsAuthenticated)
{
string userName = loggedUser.Name;
userName = userName.Substring(userName.IndexOf('//') + 1);
UserInfo user = new UserInfo();
user.UserName = userName;
//to-do find role
IUserService svc = IoC.Resolve<IUserService>();//这就是ioc通过用户服务接口实现用户服务对象的初始化
var roleInfo = svc.GetUserInfo(userName);用户服务调用数据库查询,判断数据库中是否已经存在该用户
if (roleInfo != null)
{
user.RoleId = roleInfo.RoleId;
}
else
{
//如果数据库没有该用户,则该用户是默认的用户即为User
user.RoleId = (int)RoleType.User;
var repo = IoC.Resolve<IUserRoleRepository>();//通过IOC将用户仓库类反转(相当于初始化,省去了New),repo可以直接调用IUserRoleRepository中的方法
var entity = repo.GetEntityInstance();
entity.RoleId = user.RoleId;
entity.Operator = user.UserName;
repo.Add(entity);
}
System.Web.HttpContext.Current.Session.Add("user", user);
obj = user;
}
}
}
catch (Exception ex)
{
LogFormat.Write(LogFormat.BusinessError, "Auth-Exception", "获取用户验证信息时发生错误!", ex, LogErrorLevel.Error);
}
return (obj == null) ? null : obj as UserInfo;
}
}
这个方法就是获取UserInfo类的对象CurrentUser,在如下的方法中进行调用:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
ViewBag.UserName = CurrentUser.UserName;
ViewBag.RoldId = CurrentUser.RoleId;
base.OnActionExecuted(filterContext);
}
当然退出登录的方法也是写在里面:
public void LogOff()
{
Session.Abandon();
CasAuthentication.SingleSignOut();
}
这样在Controller中每个action上加上[Authorize][HttpGet],[ActionAuth(RoleType.User)]标签 , 其中 ActionAuth就是可以控制权限了
2.在Global.asax文件中需要些路由配置,不然系统找不到你的action,RouteConfig.RegisterRoutes(RouteTable.Routes); 所以在RouteConfig类中定义一个static的方法RegisterRoutes,将你用到的Controller中的Action都配置出来如下:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "BaseServiceMaintainanceIndex",
url: "Maintain/BaseService/Index",
defaults: new { controller = "Maintain", action = "SearchIndex"}//默认的参数
);
3.在Controller中使用System.Linq,可以方便写程序,例如你查询了一个List<T>,你需要按照时间降序排序,则可以用list.OrderByDescending(c => c.DataChange_LastTime).ToList();
4.分页,分页的机制主要是url传入参数,页码,行数之后,通过数据端limt{0页码}{1行数} 把数据返回,对了,还要返回整体的总数据便于前端计算分多少页。在Controller中处理页码行数用ViewData["PagerRecord"] = pager;返回到View层Pager pager = ViewData["PagerRecord"] as Pager;,在View层通过 ViewDataDictionary viewData = new ViewDataDictionary(); viewData 对象封装页面需要传递的信息(这个类ViewDataDictionary 很重要,当两个页面cshtml之间数据进行传递时就会用到它),在A页中使用@Html.Partial("PagerPartial", pager, viewData),在页面传递时,有时会用到路由参数,因为页面传递时要刷新页面,要保证刷新页面不变,所以路由参数必须一致所以本次还用到RouteValueDictionary dict = new RouteValueDictionary();最后赋值完毕之后,将之赋值给viewData对象
5.本次查询系统由于参数太多,所以在Core层使用了参数类,还有查询返回值也是用了结果类:其中Data就是返回的数据List,有一些操作也是用返回类实现的
//查询结果类
public class PagerResult<T>
{
public int PageIndex { get; set; }
public int TotalCount { get; set; }
public IList<T> Data { get; set; }}
//操作结果类
[Serializable]
public class ModifyResult
{
private string _status = "success";private string _message = "提交成功!";
/// <summary>
/// 修改状态
/// </summary>
public virtual string status
{
get { return _status; }
set { _status = value; }
}
/// <summary>
/// 信息
/// </summary>
public virtual string message
{
get { return _message; }
set { _message = value; }
}
}
public class SuccessResult :ModifyResult
{
public override string message
{
get
{
return "提交成功!";
}
}
public override string status
{
get
{
return "success";
}
}
}
6.编程需要注意一点,不要在数据库端写select all 然后在业务逻辑层使用foreach()进行循环遍历,这样既能拖垮数据库又会给业务层带来压力,最好的办法是通过参数传入数据端进行查询,举个例子:Appid与sid相同的记录不能存在两条,所以在新增或者更新时需要判断,写一个check函数,getListByAppid(string appid){}这样返回一个List 然后在业务层对resultList进行判断,使用Linq:resultList.Where(t=>t.sid ==e.sid);e.sid是你要判断数据的sid。实在不行你也可以写getListByXid(string appid,int sid){}判断有无返回值就可以了。
7.关于多张表的插入,删除,更新操作,需要保持几张表同步更新/变化,所以你需要使用事务原则。在C#中我目前是用using (TransactionScope tsCope = new TransactionScope()) {}需要引入System.Transactions,在保证代码完整的完成事务操作的地方加上tsCope.Complete();
using (TransactionScope tsCope = new TransactionScope())
{
data.DataChange_CreateTime = DateTime.Now;
data.DataChange_LastTime = DateTime.Now;
Object iresult = _dataRepo.Add(data);
int iReturn = ConvertHelper.ToInt32(iresult);
if (iReturn != 0)
{
string operatorid = data.Operator;
//将操作信息写入Log库
IOperatorLog ol = IoC.Resolve<IOperatorLog>();
ol.WriteLogInfo(iReturn, operatorid, LogMessage.Commit_Service_Application);
res = new SuccessResult();
tsCope.Complete();
}
else
{
res = new FailedResult();
}
}
8.因为Core层没有依赖Data层(这是解除耦合必须的),所以有时候你在Core想初始化Data层的数据实体,这是不可能的,怎么办呢?要处理数据不能一直把数据传递到Data层再做操作吧,那样多麻烦。遇到这样的问题你就要在Core层调用相应实体仓库接口中定义一个返回 数据实例化的方法,例如我在AppForm服务中调用AppFormEntity,但是没有办法new一个AppFormEntity,所以我在IAPPFormRepository(这个类还是在Core层)中写一个函数 IAppFormEntity CreateEntity();但是其实现类APPFormRepository在Data层了,哈哈,
public IAppFormEntity CreateEntity()
{
return new AppFormEntity();
}
这就OK了吧!!当然比较明智的是,在IAPPFormRepository所继承的基类中写一个泛型接口方法并在其实现类中实现。例如IRepository接口中写:TEntity GetEntityInstance();
在Repository中写
public TInterface GetEntityInstance()
{
return new TEntity();
}
然后,IAPPFormRepository继承IRepository,其实现类APPFormRepository需要继承Repository与IAPPFormRepository
9.数据库多参数查询时,需要拼sql,怎么拼比较好呢?我在这使用了一种很常规的方式,用List<string>拼接实现的,具体实现如下:
public static StringBuilder GenerateSearchSql(SearchParams sp)
{
StringBuilder sql = new StringBuilder(ConstSqlStr.SQL_APP_COUNT);//这是SQL语句,联表查询,没有包含where条件
List<string> wheres = new List<string>();
List<StatementParameter> listParameter = new List<StatementParameter>();//参数话查询
if (sp.Pid != 0)
{
wheres.Add(" a.Pid=@Pid ");
listParameter.Add(new StatementParameter { Name = "@Pid", Direction = ParameterDirection.Input, DbType = DbType.Int32, Value = sp.Pid });
}
if (!string.IsNullOrEmpty(sp.AppId))
{
wheres.Add(" a.AppId = @AppId ");
listParameter.Add(new StatementParameter { Name = "@AppId", Direction = ParameterDirection.Input, DbType = DbType.AnsiString, Value = sp.AppId });
}
if (!string.IsNullOrEmpty(sp.ApplicantId))
{
wheres.Add(" a.ApplicantId = @ApplicantId ");
listParameter.Add(new StatementParameter { Name = "@ApplicantId", Direction = ParameterDirection.Input, DbType = DbType.AnsiString, Value = sp.ApplicantId });
}
if (!string.IsNullOrEmpty(sp.ServiceName))
{
wheres.Add(" s.ServiceName = @ServiceName ");
listParameter.Add(new StatementParameter { Name = "@ServiceName", Direction = ParameterDirection.Input, DbType = DbType.AnsiString, Value = sp.ServiceName });
}
if (!string.IsNullOrEmpty(sp.UnitName))
{
wheres.Add(" r.UnitName = @UnitName ");
listParameter.Add(new StatementParameter { Name = "@UnitName", Direction = ParameterDirection.Input, DbType = DbType.AnsiString, Value = sp.UnitName });
}
if (sp.Status != 0)
{
wheres.Add(" a.Status = @Status ");
listParameter.Add(new StatementParameter { Name = "@Status", Direction = ParameterDirection.Input, DbType = DbType.Int32, Value = sp.Status });
}
//判断用户是否选择了条件
if (wheres.Count > 0)//说明是有条件的查询
{
string wh = string.Join(" and ", wheres.ToArray());将条件用 and 拼接,
sql.Append(" where " + wh);//当把所有的条件用and 连接完成之后,再在前面加上where 即可
}
//执行即可
base.SelectList<T>(sql,listParameter).Cast<IT>().toList();//因为返回的是实体接口类,所以当把实体List查出来后还要做转换,将之转成其父类的形式用Cast<T>()方法,不能用一般的 as 方法,as 是做不到的。
Cast<>()方法讲解可以参照http://www.cnblogs.com/ldp615/archive/2009/08/17/1548369.html
}
注意!!C#中Join函数有两种,如下,但是value值都是数组类型,所以需要将List<string>类型转换成Array类型
[C#]
public static string Join(
string separator,
string[] value
);
[C#]
public static string Join(
string separator,
string[] value,
int startIndex,
int count
);
第一次写,先写一下后台的东西