你的位置:首页 > ASP.net教程

[ASP.net教程]互联网菜鸟历险记之一


  在公司做一个Offline的服务管理系统,功能很简单主要是记录服务申请单,审批以及监控等。这个系统的架构是同事搭建的(像我这种菜鸟公司也不会让我搭建),架构很常规,三层架构,但是同事说另外加上了领域层(他也没有给我讲清楚,但是我看到在这一层上,都是一些log,metric的记录行为)。

  为了低耦合高内聚的目的,我们使用了IOC框架(通过IOC相当于初始化,省去了New,可以直接调用,但是IOC有个IOC容器在配置文件

  以上是整个系统的设计思路,其中的一些细节才是关键。

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
);

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