《ASP.NET办公自动化系统开发实例导航》 系统管理模块设计
2006-08-31 09:22 Jacky_Xu 阅读(1154) 评论(0) 编辑 收藏 举报简略的UML活动图如下:
数据库有:
登录日志信息表LogInfo
操作日志信息表sysOptLog
机构基本信息表mrBranch
部门基本信息表mrDepartment [机构比部门大一级,机构可以包括多个部门]
人员基本信息表mrBaseInf [也就是用户表]
模块基本信息表sysFuncDic
角色基本信息表sysRolesDic
用户角色关系表sysEmpRoles
模块角色关系表sysFuncRights
目录基本信息表sysTab
一个用户可以对应多个角色,而一个角色可以包含多个用户:一个角色可以包含多个模块,一个模块也可以对应多个角色.所以,他们全部都是多对多的关系,可以处理复杂的权限问题.
一.管理登录日志
对SQL操作全部使用存储过程.;对所有的页面全部继承基类PageBase.cs
1.通用类中用到一个比较常用的函数:(删除不可见字符函数)
{
System.Text.StringBuilder sBuilder = new System.Text.StringBuilder(131);
for(int i = 0;i < sourceString.Length; i++)
{
int Unicode = sourceString[i];
if(Unicode >= 16)
{
sBuilder.Append(sourceString[i].ToString());
}
}
return sBuilder.ToString();
}
2.自由操纵DataGrid.
可以使用一些技巧来自由操纵DataGrid:
(1)比如要在每行头部加入CheckBox,可以加入一个模板列,模板中放入一个CheckBox控件:
<ItemTemplate>
<asp:CheckBox id="CheckBox1" runat="server"></asp:CheckBox>
</ItemTemplate>
</asp:TemplateColumn>
{
if(((CheckBox)thisItem.Cells[0].Controls[1]).Checked) //表示此一行的第一列中的第一个控件
{
string strLoginID = DataGridLogininfo.DataKeys[thisItem.ItemIndex].ToString(); //设定DataGridLogininfo的DataKeyField为LoginID
DelLoginLog(strLoginID);
}
}
{
((CheckBox)thisItem.Cells[0].Controls[1]).Checked = CheckBox2.Checked;
}
同样,我们可以扩展,比如为每条记录前面加个序号,如何加?
有两种方法:
第一种,和前面一样,加一个模板列,用一个页面全局变量的number++;
第二种,对绑定前得到的DataTable进行处理,加入一列:
DataColumn mycolumn= mytable.Columns.Add ("number",System.Type .GetType ("System.String")); //在数据源DataTable中加入一列,注意技巧
for (int i=0;i<mytable.Rows.Count ;i++)
{
mytable.Rows [i]["number"]=(i+1).ToString ();
}
还有,如何加入比较复杂的功能,比如,鼠标到每一条记录上都变色呢?
这个可以通过DataGrid的ItemDataBound事件来处理,可以给每一项加上属性,方法如下:
private void DataGridLogininfo_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
{
ListItemType itemType = e.Item.ItemType;
if (itemType == ListItemType.Item )
{
e.Item.Attributes["onmouseout"] = "javascript:this.style.backgroundColor='#dedfde';";
e.Item.Attributes["onmouseover"] = "javascript:this.style.backgroundColor='#fff7ce';cursor='hand';" ;
}
else if( itemType == ListItemType.AlternatingItem)
{
e.Item.Attributes["onmouseout"] = "javascript:this.style.backgroundColor='#ffffff';";
e.Item.Attributes["onmouseover"] = "javascript:this.style.backgroundColor='#fff7ce';cursor='hand';" ;
}
}
二.管理操作日志
与上面一个没什么区别,同样要利用PageBase的PageBegin方法,这样可以确定所在的模块并检查权限,页面基类的好处已经体现出来了.
视图状态ViewState在ASP.NET里是默认存在的,在表单的发送之间几乎所有的ASP.NET控件都会保留属性值.它实际上就是一个隐藏的表单域,即HIDDEN的INPUT.
为服务器控件添加javascript脚本:
三.模块管理
模块管理再次证明了一点:只有想不到,没有做不到的.
里面对于权限的DataList绑定CheckBoxList的复杂处理可谓精彩绝伦.
如果对于绑定到DataList等里面的控件操作,一般都要使用
(ControlType)DataList.Items[Index].FindControl("ControlName")来取得控件,然后进行处理.
对于命令按钮,都要设置CommandName,然后在DataList_ItemCommand里面通过判断来进行处理,所以CommandName不仅仅是可以用edit或delete,还可以多种多样,处理十分灵活.
由于类中的连接常常是局部变量,外部调用时不可能关闭,所以用
DataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
这样自动关闭连接比较实用.
四.目录管理
这里主要有一个知识点:如果编辑比较简单,可以直接用DataGrid的自有编辑方法:
这里,主要有dgCatalog_EditCommand,dgCatalog_DeleteCommand,dgCatalog_CancelCommand,dgCatalog_UpdateCommand这几个事件.同样,都是先取得DataKey关键字,然后进行处理;Update的时候,同样是取得(TextBox)Cells[0].Controls[1].Text这样的值,然后再运行存储过程等等更新.
Feedback
比如:
public bool UpdateCatalog(CatalogData catalogData)
{
CatalogDB CatalogAccess = new CatalogsDB();
return CatalogAccess.UpdateLog(catalogData);
}
直接调用数据层的就完事了.
我们前面把处理都放在数据层主要是数据比较简单,不需要增加逻辑层来复杂操作.但是这里为什么又增加呢?
主要因为一点:扩展性.虽然这里对数据没有任何处理,只是将表示层的数据直接传给下一层数据层,这里加入这一层是为了以后业务层扩展使用,根据业务层的需要,表示层传来的数据可以经过业务逻辑层的加工再传给数据层.
(比如,如果将来目录的一些路径改变了,这里就必须进行处理,而不需要去修改数据层,数据层一般只处理对处理好的数据的数据库操作)
1.提供初始化方法PageBegin,那么我们在每个页面Page_Load里面都调用它,形式:
Public void PageBegin(moduleName, isCheck)
moduleName我们根据每个页面的不同写模块名称,isCheck为了灵活处理,如果不需要检验权限的页面则isCheck参数为false.这样,根据取得Session["EmpID"]来判断对于现在模块moduleName的权限,如果不够就RedirectTo Login.aspx.
2.写入操作日志.由于操作日志要确定操作员和模块名称,所以每个页面不一样,所以把其放入PageBase里.
3.错误处理.
唯一的PageBase中处理的Page事件:
protected void PageBase_Error(object sender, System.EventArgs e)
里面用EventLog.WriteEntry写入系统日志.
下面这个是目录模块信息类CatalogData:
public class CatalogData : DataSet //继承DataSet,这样可以拥有成员Tables来返回DataTable
{
//表格名称,其实这些最好用public static readonly string ...来代替
public const string CATALOG_TABLE_NAME = "CatalogData";
public const string PK_FIELD = "pkid";
public const string TABID_FIELD = "tabid";
public const string INDEXID_FIELD = "indexid";
public const string NAME_FIELD = "name";
public const string DESCRIPTION_FIELD = "description";
public const string URL_FIELD = "url";
//构造函数
public CatalogData()
{
BuildTable();
}
//创建数据结构表格
private void BuildTable()
{
DataTable table = new DataTable(CATALOG_TABLE_NAME);
DataColumnCollection cols = table.Columns; //同指向table的Columns,对列和行的集合就是DataColumnCollection和DataRowCollection
cols.Add(PK_FIELD,typeof(System.Int16)); //列的表示:(名称, 类型)
cols.Add(TABID_FIELD,typeof(System.String));
cols.Add(INDEXID_FIELD,typeof(System.String));
cols.Add(NAME_FIELD,typeof(System.String));
cols.Add(DESCRIPTION_FIELD,typeof(System.String));
cols.Add(URL_FIELD,typeof(System.String));
Tables.Add(table); //这样,新建的CatalogData类就有了一个CatalogData.Tables[CatalogData.CATALOG_TABLE_NAME]的DataTable
}
}
注释中已经说明了这种定义的一些特点:
同理,我们在数据层的应用中,对于Update,Insert等各种方法,使用了下面的方法:
public bool UpdateCatalog(CatalogData catalogData)
{
commandAdp.UpdateCommand = GetCommand();
//因为插入的GetCommand()取得命令不需要pkid,所以pkid参数单独加入
commandAdp.UpdateCommand.Parameters.Add(new SqlParameter(paramChg(CatalogData.PK_FIELD),SqlDbType.Int));
commandAdp.UpdateCommand.Parameters[paramChg(CatalogData.PK_FIELD)].SourceColumn = CatalogData.PK_FIELD;
commandAdp.UpdateCommand.CommandText = "UpdateCatalogs";
commandAdp.Update(catalogData,CatalogData.CATALOG_TABLE_NAME); //同更新DataSet (catalogData)
if(catalogData.HasErrors) //继承DataSet的是否含有错误
{
catalogData.Tables[CatalogData.CATALOG_TABLE_NAME].GetErrors()[0].ClearErrors();
return false;
}
else
{
//catalogData.AcceptChanges(); //Update(DataSet)后其实不用DataSet.AcceptChanges
return true;
}
}
public bool CreateCatalog(CatalogData catalogData)
{
commandAdp.InsertCommand = GetCommand();
commandAdp.InsertCommand.CommandText = "InsertCatalog";
commandAdp.Update(catalogData,CatalogData.CATALOG_TABLE_NAME);
if(catalogData.HasErrors)
{
catalogData.Tables[CatalogData.CATALOG_TABLE_NAME].GetErrors()[0].ClearErrors();
return false;
}
else
{
catalogData.AcceptChanges();
return true;
}
}
private SqlCommand GetCommand()
{
SqlCommand command = new SqlCommand();
command.Connection = con;
command.CommandType = CommandType.StoredProcedure;
SqlParameterCollection param = command.Parameters;
param.Add(new SqlParameter(paramChg(CatalogData.TABID_FIELD),SqlDbType.VarChar));
param.Add(new SqlParameter(paramChg(CatalogData.INDEXID_FIELD),SqlDbType.VarChar));
param.Add(new SqlParameter(paramChg(CatalogData.NAME_FIELD),SqlDbType.VarChar));
param.Add(new SqlParameter(paramChg(CatalogData.DESCRIPTION_FIELD),SqlDbType.VarChar));
param.Add(new SqlParameter(paramChg(CatalogData.URL_FIELD),SqlDbType.VarChar));
//设置源列的名称并设置参数值
param[paramChg(CatalogData.TABID_FIELD)].SourceColumn = CatalogData.TABID_FIELD;
param[paramChg(CatalogData.INDEXID_FIELD)].SourceColumn = CatalogData.INDEXID_FIELD;
param[paramChg(CatalogData.NAME_FIELD)].SourceColumn = CatalogData.NAME_FIELD;
param[paramChg(CatalogData.DESCRIPTION_FIELD)].SourceColumn = CatalogData.DESCRIPTION_FIELD;
param[paramChg(CatalogData.URL_FIELD)].SourceColumn = CatalogData.URL_FIELD;
return command;
}
/* 注意,上面生成的是DataAdapter.UpdateCommand.
* 一般来说,我们都使用自动生成法:
* 如果用于检索填充 DataSet 的数据的 SELECT 语句基于单个数据库表,则可利用 CommandBuilder 对象自动生成
* DataAdapter 的 DeleteCommand、InsertCommand 和 UpdateCommand 属性。 这将简化并减少执行插入、更新和删除操作所必需的代码。
* 也就是说,比如:
* ada.SelectCommand.CommandText = "select * from table";
* SqlCommandBuilder cmdBuilder = new SqlCommandBuilder(ada);
* 这样,就可以使用Update命令:ada.Update(dataSet, tableName);
* 如果要手动生成,则要象上面一样,设置param,并要设置每个param对应表中的列名,即SourceColumn.(还有一个SourceVersion,来决定是用新值还是旧值,一般较少用)