使用Asp.net mvc + Linq + mvc_scaffold_gen_setup.exe 生成一个完整的家庭帐册大管家程序 之二

[前言]
前几天发过一篇blog:使用Asp.net mvc + Linq + mvc_scaffold_gen_setup.exe 生成一个完整的家庭帐册大管家程序 之一 
在第一篇blog中,展示了使用Mvc_Scaffold_gen_setup.exe生成的Mvc代码,包括其中的一些错误。
隔了几天,我把生成的代码重新整理下,并加些数据进去做测试。发现了几个问题(稍后列出问题),想在这里向各位咨询。

[业务介绍]
写这个程序的初衷是为了熟悉Asp.net MVC,正好要给自己写一个家庭帐务管理的小程序。我想尽可能简单化所有的业务需求,尽量不涉及专业的业务,另外,后期考虑增加帐务预估和报表反馈等功能。
下面介绍下示例中的家庭帐册大管家的业务需求及数据结构:
顾名思义,就是辅助记帐。目前的功能模块有:
1、收入,对应的数据模型是Income;
2、支出,对应的数据模型是Expense;
3、借还,对应的数据模型是AttachAccount;
4、帐务管理,对应的数据模型是Account;
5、帐务分类管理,包括大类和小类,对应的数据模型分别是PrimaryClass和ChildClass;
6、转账,对应的数据模型是TranferAccount;
7、家庭成员管理,对应的数据模型是Member。
下面见数据模型图,是从解决方案AccountBookDB.dbml截图来的。


[代码说明]
供下载的程序中,各个数据库可以进行增、删、改。但这些功能都是mvc_scaffold_gen_setup.exe这个工具之前生成的。(关于本工具生成的代码中存在的问题,请见之一
但是,在这里我希望把大家的注意力放在以下这个部分:
1、Controllers/ChildClassController.cs
2、Views/ChildClass/Add.aspx、Edit.aspx、List.aspx
注:本文要说明的内容都是这几个文件相关的部分,其余的模块后续介绍。
[操作步骤]
下面从生成数据库开始详细介绍,直到本文为止的最新内容及发现的问题。
1、打开“开始”、“程序”、Microsoft Sql Server 2005 、SQL Server Management Studio Express。
2、新建数据库AccountBookDB,路径自己选择,把下载回来的压缩包中的AccountBookDB.sql执行一下,数据库就建好了。
3、打开VS2008 (我用的是英文版),应该没什么区别。新建一个Asp.net mvc web Application,取名“AccountBook”,暂时不选单元测试的项目。
4、在项目中连接数据库,把AccountBookDB连接进来。
5、在解决方案的Model文件夹中,添加一个Linq to Sql Classes,命名为AccountBookDB.dbml。然后在VS2008的Server Explorer中,把AccountBookDB所有表一次性拉到AccountBookDB.dbml的左边的空白处。就会出现上面的那张数据库模型图。
6、默认已安装mvc_scaffold_gen_setup.exe(该工具下载地址,搜下园子里的blog),打开它(Mvc Scaffold Generator),第一行的Dbml File选择前面建好的AccountBookDB.dbml;第二行的NameSpace保持默认值。第三行选择生成的文件存放路径,这里选择之前新建的解决方案所在路径。Select All Tables 可不选。
7、在解决方案中把第6步生成的文件Include in Project.
8、修改代码中的一个错误:
 View/PrimaryClass/Add.aspx中:第25行的“<%=Item.IsAdmin== true ? "checked= true":""%>”,其中的IsAdmin错误,我们的数据模型中可没有这个字段。PrimaryClass这个表中有一个字段ClassType(意思是该分类属于收入还是支出),是bit类型,该工具错误地当作IsAdmin了。我把它修改为“ClassType;同样View/PrimaryClass/Edit.aspx中的IsAdmin同样需要修改。
9、便于调试,我们在Site.Master的第22行增加了如下代码,生成几个链接。
<li>
                    
<%= Html.ActionLink("Account""List""Account"%></li>
                
<li>
                    
<%= Html.ActionLink("AttachAccount""List""AttachAccount")%></li>
                
<li>
                    
<%= Html.ActionLink("ChildClass""List""ChildClass")%></li>
                
<li>
                    
<%= Html.ActionLink("PrimaryClass""List""PrimaryClass")%></li>
                
<li>
                    
<%= Html.ActionLink("Expense""List""Expense")%></li>
                
<li>
                    
<%= Html.ActionLink("Income""List""Income")%></li>
                
<li>
                    
<%= Html.ActionLink("TranferAccount""List""TranferAccount")%></li>
                
<li>
                    
<%= Html.ActionLink("Member""List""Member")%></li>
10、编译,运行,增加一些数据,或者下载我的数据库备份:点击下载

[修改代码]
好,以上的程序都能正常运行,现在来看这个模块:http://localhost:19374/ChildClass/Add (前面的19374端口可能有变,自己修改)
输入ChildClassName和PrimaryClassID,很显然,这里的PrimaryClassID不够人性化,还不能做到Html下拉框的方式。
于是我们作如下修改:
    1)在Models中增加类文件ChildClassViewData.cs,里面包含两个类:ChildClassEditViewDataChildClassNewViewData,代码如下:
namespace AccountBook.Models
{
    
public class ChildClassEditViewData
    
{
        
public ChildClassInfo ChildClass getset; }
        
public List<PrimaryClassInfo> PrimaryClassList getset; }
    }


    
public class ChildClassNewViewData
    
{
        
public List<PrimaryClassInfo> PrimaryClassList getset; }
    }
 
}
    2)然后打开ChildClassController.cs,修改其中的Add()方法为:
     public void Add()
        
{
            ChildClassNewViewData viewData 
= new ChildClassNewViewData();
            viewData.PrimaryClassList 
= DBContext.GetPrimaryClassList();
            RenderView(
"Add", viewData);
        }
    这里是获取了PrimaryClassList,保存在ChildClassNewViewData的实例中,然后RenderView到Add.aspx,这样,在Add.aspx.cs中,页面继承自ViewPage<ChildClass>必须修改为ViewPage<ChildClassNewViewData>,这样在Add.aspx这个视图部分,ViewData代表的就是强类型的ChildClassNewViewData对象。
    3)打开Views/ChildClass/Add.aspx,可以看到,提供输入PrimaryClassID的是一个<input type="text"......>
<input type="text" name="PrimaryClassID" id="PrimaryClassID" value="<%= Item.PrimaryClassID%>" />
    现在我们就可以将其修改为:
<%=Html.Select("PrimaryClassID", ViewData.PrimaryClassList) %>
    使用的是HtmlHelp中的Select重载方法来绑定下拉框。“PrimaryClassID”是控件名称,第二个参数是数据源。
    同样,在Views/ChildClass/Edit.aspx中,我们同样将相关代码
<input type="text" name="PrimaryClassID" id="PrimaryClassID" value="<%= Item.PrimaryClassID%>" />
    替换为如下:
<%=Html.Select("PrimaryClassID", Item.PrimaryClassList, Item.ChildClassInfo.PrimaryClassID) %>
    在Edit.aspx页面,显示出当前ChildClass所对应的PrimaryClassID,也是以下拉框的形式表示,同时默认选择PrimaryClassID对应的PrimaryClassName,各参数含义同上,第三个参数就是默认选择的PrimaryClassID。
    4)好,现在打开Views/ChildClass/List.aspx页面,显示了当前所有ChildClass,我们看下这部分的代码:
<h2>ChildClass List   <%=    Html.ActionLink("Add","Add" ) %>    </h2>   
    
<% AccountBookTest.Models.ChildClassList Lst = ViewData; %> 
    
<%
        
if (ViewContext.TempData["msg"!= null)
        
{         
    
%>
        
<p><%=ViewContext.TempData["msg"%></p>
    
<%
        }
    
    
%>            
    
<form>
      
<table>
            
            
<tr>
                
<td><strong>ChildClassName</strong></td>
                
<td><strong>PrimaryClassID</strong></td>
            
</tr>
            
<%foreach (AccountBookTest.Models.ChildClass item in Lst) %>
            
<%{%>
            
<tr>
                
<td><%=item.ChildClassName.ToString()%></td>
                
<td><%=item.PrimaryClassID.ToString()%></td>
                
<td><%=Html.ActionLink("Edit","Edit",new {id = item.ChildClassID})%></td>
                
<td><%=Html.ActionLink("Delete","Delete"new {id = item.ChildClassID})%></td>
            
</tr>
            
<%}
%>
            
      
</table>
      
<p></p>
    
</form>
    可以看到,有一个很亲切的c#代码foreach语句出现,它的作用是循环当前的AccountBookTest.Models.ChildClassList Lst,逐一显示ChildClass的信息:ChildClassName,PrimaryClassID以及两个可选操作Edit和Delete。
    现在我们进行如下修改:
<h2>
        ChildClass List
        
<%=Html.ActionLink("Add","Add" ) %>
    
</h2>
    
<% AccountBook.Models.ChildClassList Lst = ViewData; %>
    
<%
        
if (ViewContext.TempData["msg"!= null)
        
{         
    
%>
    
<p>
        
<%=ViewContext.TempData["msg"%></p>
    
<%
        }
    
    
%>
    
<table>
        
<tr>
            
<td>
                
<strong>ChildClassName</strong>
            
</td>
            
<td>
                
<strong>PrimaryClass</strong>
            
</td>
            
<td>
                
<strong>Edit</strong>
            
</td>
            
<td>
                
<strong>Delete</strong>
            
</td>
        
</tr>
        
<asp:Repeater ID="AllChildClass" runat="server" 
            onitemdatabound
="AllChildClass_ItemDataBound">
            
<ItemTemplate>
                
<tr>
                    
<td>
                        
<%# Eval("ChildClassName"%>
                    
</td>
                    
<td>
                        
<%# Eval("PrimaryClassID"%>
                    
</td>
                    
<td>
                        
<asp:HyperLink ID="hlEdit" runat="server">Edit</asp:HyperLink>
                    
</td>
                    
<td>
                        
<asp:HyperLink ID="hlDelete" runat="server">Delete</asp:HyperLink>
                    
</td>
                
</tr>
            
</ItemTemplate>
        
</asp:Repeater>
    
</table>
    用一个服务器端控件Reapter绑定ChildClassList.因此,List.aspx.cs中的代码修改为如下:
public partial class ChildClassListView : ViewPage<ChildClassList>
    
{
        
protected System.Web.UI.WebControls.Repeater AllChildClass;//这里居然要声明下控件(下同),否则在codefile中无法访问Reapter控件?困惑!
        
protected System.Web.UI.WebControls.HyperLink hlEdit;
        
protected System.Web.UI.WebControls.HyperLink hlDelete;

        
public void Page_Load()
        
{
            AllChildClass.DataSource 
= ViewData;//这里绑定的数据源就是ChildClassList实例对象。
            AllChildClass.DataBind();
        }


        
protected void AllChildClass_ItemDataBound(object sender, System.Web.UI.WebControls.RepeaterItemEventArgs e)
        
{
            
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
            
{
                ChildClassInfo childClassInfo 
= (ChildClassInfo)e.Item.DataItem;

                HyperLink hlEdit 
= (HyperLink)e.Item.FindControl("hlEdit");
                HyperLink hlDelete 
= (HyperLink)e.Item.FindControl("hlDelete");
                hlEdit.NavigateUrl 
= "http://www.cnblogs.com/ChildClass/Edit/" + childClassInfo.ChildClassID;
                hlDelete.NavigateUrl 
= "http://www.cnblogs.com/ChildClass/Delete/" + childClassInfo.ChildClassID;
            }

        }

    }
    现在我的问题来了:
    1)页面上使用的服务器端控件Reapter,在CodeFile中居然必须声明,否则无法识别,为什么会这样?所有服务器控件都必须声明,我之前看的一个例子就没有,怎么回事?麻烦各位帮我找找原因。
    [2008.04.17晚回答]在解决方案上右键——>Convert to Web Applications就可以把解决方案变成Web Application的形式。这一个Convert其实就是给每个页面(比如Test.aspx)增加对应的Test.designer.cs的文件,然后页面上的控件的声明全部放在这里。其实这个答案应该早就想到,只是太专注于MVC,反而忘了根本的web Application文件结构。
    2)在修改之前的页面代码中的foreach语句,可以使用<%=Html.ActionLink("Edit","Edit",new {id = item.ChildClassID})%>来很方便很直接地放置一个Edit链接,链接地址是ChildClass/Edit/[ChildClassID],
    现在问题是,我使用了Reapter控件直接绑定数据源之后,怎样才可以类似<%=Html.ActionLink("Edit","Edit",new {id = item.ChildClassID})%>这样的写法,直接生成一个Edit的链接呢?疑惑在于,在Reapter中无法把[ChildClassID]传到<%=Html.ActionLink("Edit", "Edit", ……)%>中的"……"中。
    怎么办?我暂时只能采用webForm的做法,在ItemBound事件中动态绑定......那请问,是我的做法有错误还是本身就不能通过服务器控件绑定的方式来动态生成Edit按钮?
    ps:Delete的也是同样问题。
    3)2008.04.17补充一个问题:显示ChildClass信息的时候,由于Model.ChildClassInfo中仅有PrimaryClassID属性 ,但如果要显示出PrimaryClassID对应的PrimaryClassName,那该如何实现?在Model.ChildClassInfo中增加PrimaryClassName属性??还是在Controllers中进行匹配?补充:这其实是类设计的范畴,和mvc没多大关系的。

总结:今天的blog就写这些内容,就是ChildClass的Add和Edit,以及List的显示。同时提了个问题,留给大家帮我解决。

本文最新Demo下载地址是:AccountBook.rar(最新)  
以及数据库的备份(含数据):AccountBookDB.rar

posted @ 2008-04-16 23:39  Kevin Lin  阅读(4087)  评论(32编辑  收藏  举报