ASP.NET MVC - 檔案下載使用非主鍵,例如Name, GUID

我們在前一篇「單一檔案與多檔案上傳及下載管理」有一段下載檔案的程式碼,如下:

Function Download(id As Integer) As ActionResult
        Using db As New FilesEntities

            ' 取得檔案資訊
            Dim getfile = (From f In db.FileDown Where f.FileId = id Select f).FirstOrDefault()

                If getfile.FileName IsNot Nothing Then
                    Dim FilePath As String = Server.MapPath("~/Files/" & getfile.FileName)

                    ' 進行下載
                    If System.IO.File.Exists(FilePath) Then
                        Return File(FilePath, getfile.FileType, getfile.FileName)
                    End If
                Else
                    ' 回應錯誤
                    Return Content("無法下載檔案!")
                End If
            End Using

            Return RedirectToAction("Index")
 End Function

一般而言,id 會是主鍵(PK),那如果我不想使用主鍵可不可以,也是可以,你只需要把LINQ那段語法修改一下即可,例如,我想使用檔案名稱來下載:

' 記得要傳入name 參數
Function DownloadByName(name As String) As ActionResult
    Dim getfile = (From f In db.FileDown Where f.FileName = name Select f).FirstOrDefault()
    ' 其他一樣

不過像id, name 這種都很好猜,我可以手動輸入就可以把檔案下載回來。如果是這樣,那我們還可以使用GUID來讓使用者下載。

' 記得guid 型別是 Guid
Function DownloadByGuid(guid As Guid) As ActionResult
    Dim getfile = (From f In db.FileDown Where f.rowguid= guid Select f).FirstOrDefault()
    ' 其他一樣

另外,為確保傳入為Guid,再加強一下程式:

Function DownloadByGuid(guid As String) As ActionResult
    Dim parseGuid As Guid = System.Guid.Parse(guid)
    Dim getfile =  (From f In db.FileDown Where f.rowguid= guid Select f).FirstOrDefault()

經過 Guid.Parse() 的好處是,我可以使用Try ... Catch 直接進行錯誤處理。最後程式修改為:

Function DownloadByGuid(guid As String) As ActionResult
    Try
        Dim parseGuid As Guid = System.Guid.Parse(guid)
        Dim getfile =  (From f In db.FileDown Where f.rowguid= guid Select f).FirstOrDefault()

        If getfile.FileName IsNot Nothing Then
            Dim FilePath As String = Server.MapPath("~/Files/" & getfile.FileName)

            ' 存在,進行下載
            If System.IO.File.Exists(FilePath) Then
                Return File(FilePath, getfile.FileContentType, getfile.FileName)
            End If
        End If
    Catch ex As ArgumentNullException
        ViewBag.Message = ex.Message
    Catch ex As FormatException
        ViewBag.Message = ex.Message
    Catch ex As Exception
        ViewBag.Message = ex.Message
    End Try

    Return RedirectToAction("Index")
End Function

下載網址會是這樣:「http://localhost:15454/File/DownloadByGuid?guid=aeb238f8-d339-4997-bd9b-111c60906a15」,先把guid當成String,然後System.Guid.Parse(guid) 轉換為 Guid型別,成功才進行 LINQ 查詢,有資料就下載。

ASP.NET MVC - 單一檔案與多檔案上傳及下載管理

之前在 ASP.NET 寫過一篇「透過資料庫上傳下載檔案」,不過此方法有些缺點,例如,資料庫肥大,這些進入資料庫的檔案都是二進位儲存,所以就算是使用資料庫壓縮,會讓資料庫長的肥肥胖胖的。再來,如果你的架構比較大,是將Web Application與Database分開不同伺服器,那麼Web Application與Database之間的頻寬、Disk I/O成本也會大些。

我們來改來方式,將檔案上傳至Web Application特定目錄下,然後將此上傳檔案的資訊儲存至資料庫,這些資訊都是文字,所以對資料庫大小影響不大,就算是上萬十萬筆資訊,文字資料的壓縮率是非常不錯的。當我要下載檔案時,從資料庫取出檔案資訊,然後由Web Application的特定目錄讀出檔案,傳送給Browser進行下載。

以下以ASP.NET MVC實作,會有兩個部分,一是單一檔案上傳,二是多檔案上傳。

資料庫資料


我在 App_Data 裡新檔一個 Files.mdf,表格名稱 FileDown,Schema為

FileId, int, PK
FileName, nvarchar(50)
FileSize, nvarchar(50)
FileType, nvarchar(50)
FileVersion, nvarchar(50)
PostDate, datetime2(7)
UploadDate, datetime2(7)
rowguid, uniqueidentifier

在 Models 目錄下建立 FileModel.edmx 及 FileModel.Context.tt,FileModel.tt。(Entity Framework 4.1)

MVC 單一檔案上傳


新增 FileController.vb,把我們的架構先寫出來。

Imports System.IO

Public Class fileController
    Inherits System.Web.Mvc.Controller

    ' 顯示檔案列表
    Function Index() As ActionResult
        Return View()
    End Function

    ' 單一檔案上傳表單
    Function Upload() As ActionResult
        Return View()
    End Function

    ' 處理單一檔案上傳
    <HttpPost()>
    Function Upload(upfile As HttpPostedFileBase, formData As FormCollection) As ActionResult
        Return View()
    End Function

    ' 多檔案上傳表單
    Function MultiUpload() As ActionResult
        Return View()
    End Function

    ' 處理多檔案上傳
    <HttpPost()>
    Function MultiUpload(form As FormCollection) As ActionResult
        Return View()
    End Function

    ' 處理檔案下載
    Function Download(id As Integer) As ActionResult
        Return View()
    End Function

我們一個一個來處理。

    ' 顯示檔案列表
    Function Index() As ActionResult
        Dim db As New FilesEntities
        Return View(db.FileDown)
    End Function
Index.aspx 內容
<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of IEnumerable (Of UpDownFileFromDBMvc.FileDown))" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    檔案列表
</asp:Content>

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

<h2>檔案列表</h2>
<ul>
    <li><%: Html.ActionLink("單一檔案上傳", "Upload")%></li>
    <li><%: Html.ActionLink("多檔案上傳", "MultiUpload")%></li>
</ul>
<table>
    <tr>
        <th>
            FileId
        </th>
        <th>
            FileName
        </th>
        <th>
            FileSize
        </th>
        <th>
            FileType
        </th>
        <th>
            FileVersion
        </th>
        <th>
            PostDate
        </th>
        <th>
            UploadDate
        </th>
        <th>
            rowguid
        </th>
    </tr>

<% For Each item In Model %>
    <% Dim currentItem = item %>
    <tr>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.FileId) %>
        </td>
        <td>
            <%: Html.ActionLink(currentItem.FileName, "download", New With {.id = currentItem.FileId})%>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.FileSize) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.FileType) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.FileVersion) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.PostDate) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.UploadDate) %>
        </td>
        <td>
            <%: Html.DisplayFor(Function(modelItem) currentItem.rowguid) %>
        </td>
    </tr>
<% Next %>

</table>

</asp:Content>

要注意的有 <%: Html.ActionLink(currentItem.FileName, "download", New With {.id = currentItem.FileId})%> ,上傳後,我們希望可以直接點擊檔案名稱就可以下載檔案,所以我們把原本的DisplayFor()改寫為Html.ActionLink()。

        ''' <summary>
        ''' MVC 單一檔案上傳
        ''' </summary>
        ''' <param name="upfile">上傳檔案</param>
        ''' <param name="formData">其他表單資料</param>
        <HttpPost()>
        Function Upload(upfile As HttpPostedFileBase, formData As FormCollection) As ActionResult
            Using db As New FilesEntities
                ' 需有上傳檔案
                If upfile IsNot Nothing Then
                    ' 0 < 容量 < 4 MB
                    Dim MBSize As Integer = upfile.ContentLength / 1000 / 1000
                    If upfile.ContentLength > 0 AndAlso MBSize < 4 Then
                        Dim savePath As String = Path.Combine(Server.MapPath("~/Files/"), upfile.FileName)

                        ' 只能上傳 7z 壓縮的檔案
                        If Path.GetExtension(savePath) <> ".7z" Then
                            ModelState.AddModelError("upfile", "檔案必須由 7z 壓縮才能上傳!")
                            Return View()
                        End If

                        ' 日期需正確
                        If IsDate(formData("PostDate")) = False Then
                            ModelState.AddModelError("PostDate", "日期格式不正確!")
                            Return View()
                        End If

                        ' 版本不得空白
                        If formData("FileVersion") = "" Then
                            ModelState.AddModelError("FileVersion", "請輸入此檔案的版本!")
                            Return View()
                        End If

                        ' 檔案不存在,進行上傳儲存動作
                        If System.IO.File.Exists(savePath) = False Then

                            ' 進行資料庫確認
                            Dim FileDuplicate As FileDown = (From f In db.FileDown
                                                             Where (f.FileName = upfile.FileName)
                                                             Select f).FirstOrDefault()

                            ' 檔名未重複
                            If FileDuplicate Is Nothing Then
                                ' 儲存至Disk
                                upfile.SaveAs(savePath)

                                ' 設定檔案資訊
                                Dim file As New FileDown
                                file.FileName = upfile.FileName
                                file.FileSize = upfile.ContentLength
                                file.FileType = upfile.ContentType
                                file.PostDate = formData("PostDate")
                                file.FileVersion = formData("FileVersion")
                                file.UploadDate = Date.Now()
                                file.rowguid = Guid.NewGuid()

                                ' 將檔案資訊儲存至資料庫
                                db.FileDown.Add(file)
                                db.SaveChanges()
                            Else
                                ModelState.AddModelError("upfile", "資料庫檔案資訊未刪除!")
                                Return View()
                            End If
                        Else
                            ModelState.AddModelError("upfile", "檔案已存在!")
                            Return View()
                        End If
                    End If
                End If
            End Using

            Return RedirectToAction("Index")
        End Function

我們在專案目鍵下建立一個 Files 目錄,用來儲存上傳的檔案。這裡要注意傳入參數的前後順序,Upload.aspx 的表單順序,如果上傳檔案欄位第一個,那麼 upfile As HttpPostedFileBase 就要在第一個,反之亦然。

Upload.aspx
<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Upload One File
</asp:Content>

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

<h2>Upload One File</h2>

<%Using Html.BeginForm("Upload", "file", FormMethod.Post, New With {.enctype = "multipart/form-data"})%>
    <%: Html.ValidationSummary(True)%>
    File:<input id="upfile" name="upfile" type="file" value="" />
          <%: Html.ValidationMessage("upfile")%>
    <br />
    PostDate:<%: Html.TextBox("PostDate")%>
          <%: Html.ValidationMessage("PostDate")%>
    <br />
    Version:<%: Html.TextBox("FileVersion")%>
          <%: Html.ValidationMessage("FileVersion")%>
    <br />
    <input type="submit" value="Upload" />
<%End Using%>

</asp:Content>

MVC 多檔案上傳

接下來我們來看多檔案上傳。

        ''' <summary>
        ''' MVC 多檔案同時上傳
        ''' </summary>
        ''' <param name="form">表單資料</param>
        <HttpPost()>
        Function MultiUpload(form As FormCollection) As ActionResult
            ' 資料檢查,請自行設計

            Dim Msg As String = String.Empty
            For i As Integer = 0 To Request.Files.Count - 1
                Msg += Request.Files(i).FileName & " 上傳成功!<br />"
                Request.Files(i).SaveAs(Server.MapPath("~/Files/") & Request.Files(i).FileName)
            Next

            ViewBag.Msg = MvcHtmlString.Create(Msg)

            Return View()
        End Function

這裡讓我偷懶一下,檔案的檢查與資訊新增至資料庫都和Upload差不多,讓你動點手。

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Upload Multi-Files
</asp:Content>

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

<h2>Upload Multi-Files</h2>
<%: Html.ActionLink("Go back Index","Index") %>
<% Using (Html.BeginForm("MultiUpload", "File", FormMethod.Post, New With {.enctype = "multipart/form-data"}))%>
        <div class="uploadfiles">
            <p>
                <input type="file" name="files" />
            </p>
        </div>
        <p>
            <a href="#add" id="additem">Add Upload File</a>
            <br /><br />
            <input type="submit" value="Upload" />
        </p>
<% End Using%>

<%: ViewBag.Msg %>
<script type="text/javascript">
    $('#additem').live('click', function () {
        $('.uploadfiles').append($("<p><input type='file' name='files' /></p>"));
    });
</script>
</asp:Content>

重點在列下方那一段 jQeruy,讓我們重態新增要上傳資料的欄位。

MVC 檔案下載

最後的Download程式。
Function Download(id As Integer) As ActionResult
            Using db As New FilesEntities

                ' 取得檔案資訊
                Dim getfile = (From f In db.FileDown
                              Where f.FileId = id
                              Select f).FirstOrDefault()

                If getfile.FileName IsNot Nothing Then
                    Dim FilePath As String = Server.MapPath("~/Files/" & getfile.FileName)

                    ' 進行下載
                    If System.IO.File.Exists(FilePath) Then
                        Return File(FilePath, getfile.FileType, getfile.FileName)
                    End If
                Else
                    ' 回應錯誤
                    Return Content("<span style='color:red'>無法下載檔案!</span>")
                End If
            End Using

            Return RedirectToAction("Index")
        End Function

我們資料表設計裡有Guid,你也可以設計一個使用Guid來讓使用者下載的方法,這樣使用者就無法使用1,2,3,4這樣的好猜的數字來下載資料。

MVC 檔案刪除

又想偷懶了,程式給你寫。

  1. 傳入id從資料庫找到檔案名稱。
  2. 使用Path.Combine與Server.MapPath()組合出完整路徑。
  3. 使用File.Exists(路徑)判斷檔案是否存在。
  4. 存在,刪除檔案及資料庫資訊。( File.Delete(路徑) )
這樣一個可上傳、下載、刪除的簡易檔案管理程式就完成了。

參考資料

  1. ASP.NET MVC 檔案上傳下載是很方便的 
  2. ASP.NET MVC的檔案上傳與下載 
  3. ASP.NET MVC FileUpload 檔案上傳

SQL 數值對應 .NET Framework 數值

在ASP.NET MVC中使用Google地圖(Visual Basic版本)

阿源哥哥有發表過一篇「在ASP.NET MVC中使用Google地圖」,我改寫為VB版本。

首先,你必須去申請Google API Key;

在「模組」中撰寫VB的延伸方法;
Imports System.Runtime.CompilerServices
Imports System.Web
Imports System.Web.Configuration
Imports System.Web.Helpers

Public Module GoogleMapExtension

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="helper">擴充型別</param>
    ''' <param name="address">地址</param>
    ''' <param name="mapWidth">寬度</param>
    ''' <param name="mapHeight">高度</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function GoogleMap(helper As HtmlHelper, address As String, mapWidth As Integer, mapHeight As Integer) As MvcHtmlString
        Dim mapHtml = New StringBuilder()

        mapHtml.Append("<div id=""map"" style=""width:" & mapWidth.ToString() & "px; height:" & mapHeight.ToString() & "px""></div>")
 
        mapHtml.Append("<script src=""http://maps.google.com/maps?file=api&v=2&key=""" & WebConfigurationManager.AppSettings("GoogleMapApiKey") & "\"" type=""text/javascript""></script>")

        mapHtml.Append("<script type=""text/javascript"">")
        mapHtml.Append("var geocoder;")
        mapHtml.Append("var map;")
        mapHtml.Append("var address = '" & address & "';")
        mapHtml.Append("map = new GMap2(document.getElementById(""map""));")
        mapHtml.Append("map.addControl(new GLargeMapControl());")
        mapHtml.Append("map.addControl(new GMapTypeControl());")
        mapHtml.Append("geocoder = new GClientGeocoder();")
        mapHtml.Append("geocoder.getLocations(address, addToMap);")
        mapHtml.Append("function addToMap(response) {")
        mapHtml.Append("place = response.Placemark[0];")
        mapHtml.Append("point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);")
        mapHtml.Append("map.setCenter(point, 13);")
        mapHtml.Append("marker = new GMarker(point);")
        mapHtml.Append("map.addOverlay(marker);")
        mapHtml.Append("marker.openInfoWindowHtml(place.address);")
        mapHtml.Append(" }")
        mapHtml.Append("</script>")

        Return MvcHtmlString.Create(mapHtml.ToString())
    End Function

End Module

在使用要使用的View中,引用Namespage,然以使用以下指令傳入相關參數即可。
<%: Html.GoogleMap(ViewBag.Address, 400, 350)%>

上面的程式碼,被Blog用亂了,請直接看檔案。

ASP.NET MVC 3, 在Visual Studio 2010 Express中玩NUnit

在Visual Studio 2010之中,只有蟹老闆等級(錢錢)付費版本才測試功能,如果你是使用Visual Studio 2010 Express版本,那很抱歉,些版本不支援,請先付錢在說。

那有沒有不付錢的方法,有,請看 ASP.NET MVC Video Training from Pluralsight 第一篇 Introduction 裡教。

環境確認:Visual Studio 2010 Service Pack 1

設定NUnit測試環境

  1. 以下為Visual Basic for ASP.NET MVC 3 Test專案實作步驟:
  2. 在原始專案中,加入一個「類別檔專案」;
  3. 使用 NuGet 加入 「NUnit」,指令為「Install-Package NUnit」,我的版本號為「2.5.10.11092」,等一下會用到,請依安裝的版本號修變改下面資訊。
  4. 加入原始專案的參考。
  5. 修改類別檔專案中,預設 Class1.vb 的檔案,我們要測試的是 HomeController ,那就命名為 HomeControllerTests ,後面加上 Tests。
  6. 在 HomeControllerTests.vb 中,「Imports NUnit.Framework」,另在「Public Class HomeControllerTests」中,加上「<TestFixture()>」屬性。
  7. 新增方法,例如,「Public Sub Puts_Message_In_ViewBag() ... 」,加上「<Test()>」屬性。
  8. 修改原專案裡 HomeController 的 Index Action,把回傳型別修改為 「ViewResult」。你回傳什麼型別,就改為什麼型別。
  9. 在測試方法中新增程式碼。 
  10. 將原始專案及測試專案「建置」。

Imports NUnit.Framework

''' <summary>
''' 測試類別
''' </summary>
<testfixture()>
Public Class HomeControllerTests

    ''' <summary>
    ''' 測試方法
    ''' </summary>
    <test()>
    Public Sub Puts_Message_In_ViewBag()
        ' HomeController 物件
        Dim controller = New HomeController()
        ' 取得物件 Index() 的結果
        Dim result = controller.Index()
        ' Assert 測試 Index 結果裡的 ViewBag.Message 不是空的
        Assert.IsNotEmpty(result.ViewBag.Message)

    End Sub

End Class

這樣就完成整個測試專案。


執行NUnit測試


讓開啟 CMD.exe ,進行到原始專案目錄下,能發現有一個「packages」目錄,讓我們順著目錄走進去「D:\dev\OdeToFood\packages\NUnit.2.5.10.11092\tools」,進到 tools 目錄後,有兩個指令:

nunit.exe :會啟動 GUI 介面程式
nunit-console.exe :文字介面程式

先在 tools 目錄下執使以下指令「D:\dev\OdeToFood\packages\NUnit.2.5.10.11092\tools>nunit-console.exe ..\..\..\OdeToFood.Tests\bin\Debug\OdeToFood.Tests.dll」,意思很簡單,就是執行那個一測試專案。等一下,就能看到執行結果。

例如:

NUnit version 2.5.10.11092
Copyright (C) 2002-2009 Charlie Poole.
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.
Copyright (C) 2000-2002 Philip Craig.
All Rights Reserved.

Runtime Environment -
   OS Version: Microsoft Windows NT 6.1.7601 Service Pack 1
  CLR Version: 2.0.50727.5446 ( Net 2.0 )

ProcessModel: Default    DomainUsage: Single
Execution Runtime: Default
.
Tests run: 1, Errors: 0, Failures: 0, Inconclusive: 0, Time: 0.980056 seconds
  Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0

我們測試結果 Errors: 0,測試成功了。你可以修改原始專案 ViewBag.Message2 ,把變數名稱多加個 2 ,重新建置專案,結果出現:

NUnit version 2.5.10.11092
Copyright (C) 2002-2009 Charlie Poole.
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.
Copyright (C) 2000-2002 Philip Craig.
All Rights Reserved.

Runtime Environment -
   OS Version: Microsoft Windows NT 6.1.7601 Service Pack 1
  CLR Version: 2.0.50727.5446 ( Net 2.0 )

ProcessModel: Default    DomainUsage: Single
Execution Runtime: Default
.F
Tests run: 1, Errors: 1, Failures: 0, Inconclusive: 0, Time: 1.0290589 seconds
  Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0

Errors and Failures:
1) Test Error : OdeToFood.Tests.HomeControllerTests.Puts_Message_In_ViewBag
System.Reflection.AmbiguousMatchException : 多載解析失敗,因為沒有公用 'IsNotEmpty' 對這些引數而言是最適合的:
    'Public Shared Sub IsNotEmpty(collection As System.Collections.ICollection)':
        不是最特定。
    'Public Shared Sub IsNotEmpty(aString As String)':
        不是最特定。
   於 Microsoft.VisualBasic.CompilerServices.OverloadResolution.ResolveOverloadedCall(String MethodName, List`1 Candidates, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, BindingFlags LookupFlags, Boolean ReportErrors, ResolutionFailure& Failure)
   於 Microsoft.VisualBasic.CompilerServices.OverloadResolution.ResolveOverloadedCall(String MethodName, MemberInfo[] Members, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, BindingFlags LookupFlags, Boolean ReportErrors, ResolutionFailure& Failure)
   於 Microsoft.VisualBasic.CompilerServices.NewLateBinding.ResolveCall(Container BaseReference, String MethodName, MemberInfo[] Members, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, BindingFlags LookupFlags, Boolean ReportErrors, ResolutionFailure& Failure)
   於 Microsoft.VisualBasic.CompilerServices.NewLateBinding.CallMethod(Container BaseReference, String MethodName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack, BindingFlags InvocationFlags, Boolean ReportErrors, ResolutionFailure& Failure)
   於 Microsoft.VisualBasic.CompilerServices.NewLateBinding.ObjectLateCall(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack, Boolean IgnoreReturn)
   於 OdeToFood.Tests.HomeControllerTests.Puts_Message_In_ViewBag() 於 D:\StudyTest\OdeToFood\OdeToFood.Tests\HomeControllerTests.vb: 行 19 

出現 Errors: 1,而且還很明確的說是錯在第幾行。

設定NUnit與Visual Studio 2010結合


前面的用法,很不方便,每次都要開CMD而且還要Key一大堆路徑。讓我們使 nunit-console.exe 來與 Visual Studio 2010 結合使用。

測試專案 → 屬性 → 編譯 (這裡的畫面與影片不同)


在「建置後進行編輯」→ 巨集 →

「$(SolutionDir)\packages\NUnit.2.5.10.11092\tools\nunit-console.exe $(TargetPath)」

$(SolutionDir) 為 D:\dev\OdeToFood\
$(TargetPath) 為 D:\dev\OdeToFood\OdeToFood.Tests\bin\Debug\OdeToFood.Tests.dll

按「確定 * 2」即完成。

重新「建置」測試專案。你就能在「輸出」看見測試結果。


當然,以上使用NUnit不只是在Express才能使用,錢錢版,一樣能用。