Visual Basic OOP Part 2 -- 繼承與介面之靈光一閃

之前之Allen大大聊天時,有討論到「繼承與介面」一些內容,雖然我之前有寫過「繼承Inherit、介面Interface的使用時機及建議」一文,但昨晚在帶著大小寶貝散步時,突然靈光一閃,又有新的認知。

首先,我們要了解在 .NET Framework只能單一繼承,也就是每一個類別(Class)只能繼承一個基礎類別(BaseClass),但深度沒有限制。

A Class --> B Class --> C Class --> D Class
             --> E Class --> F Class
                                --> G Class

但介面可以有多重繼承,也就一個類別可同時繼承多個介面,以 List類別為例,

Public Class List(Of T) _
 Implements IList(Of T), ICollection(Of T),  _
 IEnumerable(Of T), IList, ICollection, IEnumerable

它同時實作了六個介面,

  1. IList(Of T)
  2. ICollection(Of T)
  3. IEnumerable(Of T)
  4. IList
  5. ICollection
  6. IEnumerable

那我靈光一閃什麼呢?

我們需要的組織或架構是「樹狀, Tree」,那就可以規劃使用繼承。繼承的方向單純,走的是「Top-Down」。例如,我們看一下「人的科學分類」,這就很合適使用繼承。

我們需要的織組或架構是「網狀, Net」,那就可以規劃使用介面。介面是多方向,走的是「Matrix,矩陣」、「放射狀」。



以上就是我小小一閃的心得。

Visual Basic OOP Part 2 -- 封裝補充

經過Allen大大的指教,又對OOP多了一分了解。以下是Allen指教的內容:



封裝,雖然有講到將演算法隱藏起來,但重點並不是因為編譯了,看不到,而是因為將它宣告成private,所以,同一支dll projrct的各class裡,也許叫用不到另一class的某資訊,重點不在於因為它編譯過了,而是因為它被宣告成private,另外, 封裝的另一個解釋,是class裡,也許有某個行為日後一定會一直改變,那麼,便可以將這行為抽離開,讓它單獨變成一個class,如此一來,主要的class就不必一直被變更code,"將可能變動的因素抽離走,也叫封裝"。


學徒與師父的功力果然不一樣。

Entity Framework 4.1 新功能介紹資源

剛看到外國一本 Entity Framework 書作者文章,作者很清楚的整理一系統的文章及影片,文章所說的內容基本上跟影片是一樣的,整理的很好,留下來當個記錄。( 國內什麼時候才有EF的專書! )


EF 系列文章:

  • WPF使用Entity Framework做資料綁定
  • ASP.NET MVC 3使用Entity Framework 4.1的Code First
  • ASP.NET MVC 3使用Entity Framework 4.1的Model First
  • ASP.NET MVC 3使用Entity Framework 4.1的Database First
就算看不懂文章( 英文 ),我還是建議看他的影片,就算聽不懂他在說什麼,看他怎麼做,他實作的步驟還算簡單。

julie lerman 個人網站上也有許多Entity Framework的資源,如果有在學習或使用Entity Framework的人可加入參考及留意。

雖然大家都在談Entity Framework 4.1的Code First,有興趣的人還是可以了解一下。我想尤其是有在開發"手機"方面應用程式的人。基本上,"我"資料庫的毒中的太深,所以以Database First為主,如果寫一些測試Code,臨時需要Model,應該會考慮使Model First。

ASP.NET MVC 3 新HTML Helper筆記(1) -- WebGrid Class

事情永遠是自己想太多。 -- By KKBruce
我在三月時寫了 ASP.NET MVC 3概觀正體中文版及五月時寫了 ASP.NET MVC 3 RTM Tools Update for CHT,算是把整個 ASP.NET MVC 3 所有更新的內容整個灠覽一篇,但其中一直有一點一直讓我心癢癢的,就是在ASP.NET MVC 3概觀正體中文版裡關於 Razor 提供新的 HTML Helper (Chart, WebGrid, Crypto, WebImage, WebMail),為什麼好東西不和好朋友分享呢?因為相關文章都把這些 ASP.NET MVC 3 新 Helper 和 Razor 放在一起使用,造成我的誤解,以為要使用這些新 Helper 必須要和 Razor 一起才能使用,後來發現我錯了。

在查詢 ASP.NET MVC 3參考 (英文) (ASP.NET MVC 3 Reference) 時,發現了一些好玩的事,怎麼我找不到任何關於 ASP.NET MVC 3 新增的 HTML Helper 的相關類別,ASP.NET MVC 3 只新增了一個 System.Web.Mvc.Razor 類別,其他四個與ASP.NET MVC 2 一樣,經查詢,這些 ASP.NET MVC 3 的新 HTML Helper 是在 System.Web.Helper 類別,而且此類別是位於 Extensions 之下,而非 Frameowrk,如果你專案是開 ASP.NET MVC 3 專案,預設是會加入參考。


都已經預設加入參考,那我可不可以在非 Razor 下使用它呢?答案當然是 No problem。

MVC 3 New HTML Helper 1 -- WebGrid

在MVC 3之前(不就 MVC 2呢?),如果我們要對一個表格(或說 Model)要做一些進階的效果,例如,我們想做分頁,或除了分頁還是做分頁的Ajax效果…?網路上都有範例可以參考,例如使用jqGrid,或使用Martijn Boland實作的 MvcPaging函式庫,或大陸有位杨涛所實作 MvcPager分页控件等,都是很棒的解決方案。

現在我們能有還能使用 ASP.NET MVC 3 所提供的 WebGrid。我們使用預設的 HomeController 的 Index Action來示範,我們先清除Index Action的程式碼及刪除Index.aspx這個View。

Function Index() As ActionResult

    Dim nw As New NorthwindEntities
    Dim customers = nw.Customers().ToList()

    Return View(customers)
End Function

Index()動作很簡單,使用Entiry Framework來傳入Customer資料表。

然後我們使用強型別(Customers)來建置 Index.aspx 這個View,請直接拉到最後面

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage(Of IEnumerable (Of MvcWebGrid.Customers))" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Index
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

<h2>Index</h2>

<p>
    <%: Html.ActionLink("Create New", "Create") %>
</p>
<table>
    <tr>
        <th>
            CompanyName
        </th>
        <th>
            ContactName
        </th>
        <th>
            ContactTitle
        </th>
        <th>
            Address
        </th>
        <th>
            City
        </th>
        <th>
            Region
        </th>
        <th>
            PostalCode
        </th>
        <th>
            Country
        </th>
        <th>
            Phone
        </th>
        <th>
            Fax
        </th>
        <th></th>
    </tr>

<% For Each item In Model %>
    <% Dim currentItem = item %>
    <tr>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.CompanyName) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.ContactName) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.ContactTitle) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.Address) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.City) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.Region) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.PostalCode) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.Country) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.Phone) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.Fax) %>
        </td>
        <td>
            <%: Html.ActionLink("Edit", "Edit", New With {.id = currentItem.CustomerID}) %> |
            <%: Html.ActionLink("Details", "Details", New With {.id = currentItem.CustomerID}) %> |
            <%: Html.ActionLink("Delete", "Delete", New With {.id = currentItem.CustomerID}) %>
        </td>
    </tr>
<% Next %>

</table>

<h2>WebGrid (有分頁及排序)</h2>
<p>
<%: New WebGrid(Model).GetHtml()%>
</p>
</asp:Content>

我們使用List Scaffold建置出Index.aspx這個View,在最後面新增一行「<%: New WebGrid(Model).GetHtml()%>」,讓List Scaffold 所產生效果與 WebGrid 產生的效果比較一下,


上半部分,原始的 List 是使用 For Each來一筆一筆跑出資料,利用 79行程式碼跑出一個超長的Table。下半部分,WebGrid 產生的 Table ,預設有含有「排序及分頁」兩大功能,而且全部只有一行程式加一個參數,有沒有感動到。

WebGrid 能做的不只有這樣,我們針對傳入的Model及Html控制到很細項目,例如,針對Model的控制,

例如,

<%: New WebGrid(Model, defaultSort:="ContactName", rowsPerPage:=5).GetHtml()%>

預設我想針對「ContactName」來排序,且每一分頁使用 5 筆資料。另外,由於預設參數很多,建議可以使用「指名參數(:=, 冒號加等號)」的寫法來設定。能不能有分頁,使用「CanPage」,能不能排序,使用「canSort」,實在很方便。


再來,我們再針對HTML (table) 來進行設置,


這邊我們改寫一下Code,先產生WebGrid的執行個體,這樣才有辨法傳入GetHtml()方法當參數:

<% Dim wg = New WebGrid(Model, defaultSort:="CustomerID", rowsPerPage:=5)%>
<%: wg.GetHtml(columns:=wg.Columns(
                        wg.Column("Address", "地址"),
                        wg.Column("City", "城市"),
                        wg.Column("CompanyName", "公司")))%>

我們看一下結果,


我們希望產生三個Column,而且對應欄位顯示中文。那麼我們可不可以產生CURD呢?還是那一句話「No problem!」,不然就像桌子少一支腳,怎麼行。

<% Dim wg = New WebGrid(source:=Model, defaultSort:="CustomerID", rowsPerPage:=5)%>
<%: wg.GetHtml(columns:=wg.Columns(
                           wg.Column(format:=Function(item) Html.ActionLink("編輯", "Edit", New With {.id = item.CustomerID})),
                           wg.Column(format:=Function(item) Html.ActionLink("刪除", "Delete", Nothing,
                                                                            New With {.onclick =
                                                                                      String.Format("deleteRecord('CustomerID', '{0}')", item.CustomerID),
                                                                                      .href = "JavaScript:void(0)"})),
                           wg.Column("Address", "地址"),
                           wg.Column("City", "城市"),
                           wg.Column("CompanyName", "公司"))
                        )%>

我們產生兩個新欄位,我們透過匿名函式來產生對應的Link,編輯及刪除所產生對應的HTML Code,

<!-- 編輯 -->
<a href="/Home/Edit/ALFKI">編輯</a>
<!-- 刪除 -->
<a onclick="deleteRecord('CustomerID', 'ALFKI')" href="JavaScript:void(0)">刪除</a>

對照HTML Code來看,就比較能了解我們設定那些參數的用意。再來,我們在進行分頁時,可以看得出來是使用 Postback 的方法,如果要使用 Ajax 的方法,WebGrid也是支援的,

<div id="DivCustomer">
<% Dim wg = New WebGrid(source:=Model, defaultSort:="CustomerID", rowsPerPage:=5, ajaxUpdateContainerId:="DivCustomer")%>
<%: wg.GetHtml(columns:=wg.Columns(
                           wg.Column(format:=Function(item) Html.ActionLink("編輯", "Edit", New With {.id = item.CustomerID})),
                           wg.Column(format:=Function(item) Html.ActionLink("刪除", "Delete", Nothing,
                                                                            New With {.onclick =
                                                                                      String.Format("deleteRecord('CustomerID', '{0}')", item.CustomerID),
                                                                                      .href = "JavaScript:void(0)"})),
                           wg.Column("Address", "地址"),
                           wg.Column("City", "城市"),
                           wg.Column("CompanyName", "公司"))
                        )%>

我們在第一行的程式碼內指定屬性「ajaxUpdateContainerId:="DivCustomer"」,意用是我們要使用Ajax,更新到「HTML元素id為『DivCustomer』內」,這裡有兩點需注意,
  1. 需把WebGrid包在指定的HTML元素內;
  2. 需引用jQuery;

最後我們來討論「分頁」效能問題,其實也不用討論,直接看它所傳入SQL Server的T-SQL:

SELECT 
[Extent1].[CustomerID] AS [CustomerID], 
[Extent1].[CompanyName] AS [CompanyName], 
[Extent1].[ContactName] AS [ContactName], 
[Extent1].[ContactTitle] AS [ContactTitle], 
[Extent1].[Address] AS [Address], 
[Extent1].[City] AS [City], 
[Extent1].[Region] AS [Region], 
[Extent1].[PostalCode] AS [PostalCode], 
[Extent1].[Country] AS [Country], 
[Extent1].[Phone] AS [Phone], 
[Extent1].[Fax] AS [Fax]
FROM [dbo].[Customers] AS [Extent1]

這不是件好事,它不會先做分頁再回傳,而是每一次回傳所有資料再進行分頁及排序等動作。( 因為你原始的 Action 裡的資料,本來就沒進行分頁 )

這時候你有兩種解決方法,一是前面所提使用Martijn Boland實作的 MvcPaging函式庫,兩是參考這篇「Efficient Paging with WebGrid Web Helper - ASP.NET MVC 3」所提供的方法。

解決 WebGrid 分頁問題 -- 使用MvcPaging函式庫

  1. 引用MvcPaging專案,然後編譯;
  2. 在HomeController.vb裡 Imports MvcPaging
  3. 在HomeController.vb 加上page 參數,撰寫分頁程式,
  4. Index.aspx裡Inherits的Of IEnumerable改為Of MvcPaging.IPagedList
  5. 在Index.aspx撰寫分頁程式

HomeController.vb
Function Index(page As Integer?) As ActionResult
    Dim nw As New NorthwindEntities
    'Dim customers = nw.Customers().ToList()
    Dim customers = nw.Customers().OrderBy(Function(c) c.CustomerID).ToPagedList(If(page >= 1, page - 1, 0), 5)

    Return View(customers)
End Function

Index.aspx:在原WebGrid程式碼之加上
<%= Html.Pager(Model.PageSize,Model.PageNumber ,Model.TotalItemCount) %>

我們利用MvcPaging來分頁,因為MvcPaging設定的分頁數量與我們設定WebGrid的「rowsPerPage:=5」一樣,所以我們只是單純的把WebGrid當成一個顯示用的物件,另外ajaxUpdateContainerId設定也可以移除了,因為我們不是使用 WebGrid 來進行分頁。

解決 WebGrid 分頁問題 -- 使用Malcolm Sheridan MVP提供的方案(修正運作無誤版)

原網頁上提供的 jQuery 無法正常運作,我修正了一下,在 Chrome 11.x, Firefox 4.x, IE 9 都能正常運作。

  1. 在 HomeController.vb 匯入 System.Web.Helpers
  2. 新增兩個Action,一個用來檢示,一個用來處理分頁要求
  3. 撰寫 jQuery 分頁程式碼
  4. 在檢示View中,引用 jQuery 程式碼。
建立兩個Action,撰寫相關程式碼,WebGridPager 產生 View ( WebGridPager.aspx )
''' <summary>
''' 檢示結果
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Function WebGridPager() As ActionResult
    Return View()
End Function

''' <summary>
''' 處理分頁
''' </summary>
''' <param name="page">第幾頁</param>
''' <returns>JSON物件</returns>
''' <remarks></remarks>
Function EfficientPaging(page As Integer?) As ActionResult
    ' 使用 Northwind 資料庫
    Dim nw As New NorthwindEntities
    Dim skip As Integer = If(page.HasValue, page.Value - 1, 0)
    ' 以 Customers 資料表為例
    Dim data = nw.Customers().OrderBy(Function(c) c.CustomerID).Skip(skip * 5).Take(5).ToList()
    ' 傳入資料
    Dim grid As New WebGrid(data)
    ' 產生Table Tag
    Dim htmlstring = grid.GetHtml(tableStyle:="webGrid",
                                  headerStyle:="header",
                                  alternatingRowStyle:="alt",
                                  htmlAttributes:=New With {.id = "DataTable"})

    ' 回傳JSON物件
    ' 無安全性問題,可使用 HTTPGET
    Return Json(New With {.Data = htmlstring.ToHtmlString(),
                          .Count = nw.Customers.Count() / 5},
                JsonRequestBehavior.AllowGet)
End Function

我們新增一個 Pager.js ,撰寫相關 jQuery Code (修正原程式碼分頁無法運作的問題)

$(function () {
    $.getJSON('/Home/EfficientPaging', null, function (html) {
        // 把JSON取得動態 WebGrid 的HTML加入body
        $('body').append(html.Data);

        // 建立 footer
        var footer = createFooter(html.Count);
        
        // 動態為分頁 link 加上 click 事件       
        $('#DataTable tfoot a').live('click', function (e) {
            e.preventDefault();

            // 取得文字,當成 page 參數回傳
            var data = {
                page: $(this).text()
            };

            // 當有 click 時,重新取得分頁資料
            $.getJSON('/Home/EfficientPaging', data, function (html) {
                // 先將舊Table移除
                $('#DataTable').remove();
                // 把JSON取得動態 WebGrid 的HTML加入body
                $('body').append(html.Data);

                // 預設Table沒有tfoot,先加上
                $('#DataTable thead').after('');
                // 加入link到tfoot
                $('#DataTable tfoot').append(footer);
            });
        });
    });
});

function createFooter(count) {
    
    // 預設Table沒有tfoot,先加上
    $('#DataTable thead').after('');

    // 產生link
    var footer = "";
    for (var i = 1; i < (count + 1); i++) {
        footer = footer + '<a href="#">' + i + '</a>&nbsp;';
    }

    // 加入link到tfoot
    $('#DataTable tfoot').append(footer);

    return footer;    
}   

在 WebGridPager.aspx 的 View 中加入引用 Pager.js (需 jQuery,如果 MasterPage 已經引用,只需引用 Pager.js ) 即可。

<body>
  <script src="../../Scripts/jquery-1.6.1.min.js" type="text/javascript"></script>
  <script src="../../Scripts/Pager.js" type="text/javascript"></script>
</body>

那上兩種方法都是使用到 LINQ 的 Skip() 與 Take() 來進行分頁,些種分頁方法能很有效率的只取回相關的記錄,你以 5 筆為一頁,它一次就只會拿回 5 筆,這讓我們可以考慮把 WebGrid 使用在大量資料處理上。

WebGrid 參數

1 Source WebGrid指定資料來源。
2 columnNames Source中欄位的集合。
3 defaultSort 預設序排欄位名稱
4 rowsPerPage 每頁幾筆資料
5 canPage 是否允許分頁,預設true
6 canSort 是否允許排序,預設true
7 ajaxUpdateContainerId 指定Ajax更新的HTML元素ID
8 fieldNamePrefix 給WebGrid產生的參數欄位加前綴字串,例如,未加時"?sort=CustomerID&sortdir=ASC",加一個前綴"grid_"後,"?grid_sort=CustomerID&grid_sortdir=ASC”
9 pageFieldName 自定義分頁QueryString參數名稱
10 sortFieldName 自定義排序QueryString參數名稱
11 sortDirectionFieldName 自定義排序QueryString參數名稱

參考


增強Visual Studio中Visual Basic的Refactoring功能

在Visual Studio中的Visual Basic一直少了樣功能,而這個功能在C#中是很基礎的功能,就是Refactor (重構),而且很好玩的是,Visual Studio也建議Visual Basic使用者去安裝第三方軟體來加強Refactor的功能,那他們建議是的那一套呢?原來是鼎鼎大名的 DevExpress,不過他們的套裝軟體價格不低。

後來找後一會兒,原來,DevExpress也有出 Express 版本軟體 (不太好找),就像 SQL Server Express / Visual Studio Express 一樣的意思,你可以在 IDE Productivity 分類下(看網址 Visual_Studio_Add-in 很明顯吧),找到一個 Free Editions 連結,你能下載到三套 Express 免費版本的擴充工具,基中的「Free Coding Assistance Add-in」就是我們要的 CodeRush Xpress,它是CodeRush的子集,但提供了我們所需要的基本功能:


(引用自DevExpress)

加強的特色:
  1. 選取加強
    五個選擇功能的加強。
  2. 程式碼巡覽加強
    七個程式碼巡覽的加強。
  3. 宣告加強
    許多宣加方面的加強。
重構的加強 ( C# and VB ):
  1. 變更參數宣告
  2. 陳述式
  3. 宣告和初始化
  4. 運算式
  5. Lambda運算式和匿名函式
  6. 迴圈和段落
  7. 屬性和欄位
  8. 移動方法
  9. 資源檔與字串
  10. 型別
  11. 能見度
DevExpress 都有提供詳細文件,請自行參考。

參考: