在公司做一個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.survivalescaperooms.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
);
第一次寫,先寫一下后臺的東西
新聞熱點
疑難解答