转载

互联网菜鸟历险记之一

在公司做一个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

);

第一次写,先写一下后台的东西

正文到此结束
Loading...