MVC 5与EF 6在Visual Basic中 - 使用文件

This 教程探索如何在ASP.NET MVC应用程序中上传文件 如何使用实体框架将它们存储在数据库中。它构建在一系列12中,教导您如何使用实体框架构建MVC 5应用程序进行数据访问和Visual Basic。本教程没有对应物 原辅导系列,由汤姆Dykstra生产 里克安德森 ( @rickandmsft. ) 使用C#语言编写。 C#版本是 可用.

下载代码

可用本节的代码 这里 。 拯救 。压缩 文件到方便的位置,然后提取内容。确保您安装了一个版本的Visual Studio 2013(Express for Web,Community,Professional,Premium或Ultimate),双击 .sln. 文件。项目在IDE中打开后,按 Shift + Ctrl + B 建立解决方案。这将确保从Nuget恢复所有包,并且可能需要一段时间根据您的互联网连接速度。

通过完整系列13教程的导航路径如下:

  1. 创建实体框架数据模型
  2. 实施基本的CRUD功能
  3. 排序,过滤和分页
  4. 连接弹性和命令拦截
  5. 代码首次迁移和部署
  6. 创建更复杂的数据模型
  7. 阅读相关数据
  8. 更新相关数据
  9. 异步和存储过程
  10. 处理并发
  11. 实现 - 继承
  12. 高级实体框架方案
  13. (奖金教程)使用文件

我应该将文件存储在数据库或文件系统中吗?

开发人员论坛中最常见的问题之一,这个难题的答案是"it depends"。两种方法都有利弊,本教程不会寻求添加到辩论,但将涵盖如何接近两个选项。您将修改学生CRUD方法和视图以存储数据库中的化身图像。然后,您将修改教师CRUD方法和视图以将图像中的图像存储在数据库中的字段系统中及其元数据(名称,位置)。

为文件类型创建文件类型枚举

该应用程序将满足将用于各种目的的文件,其中一些目前尚未知道。您将从将作为学生的化身和指导员的照片,这两种类型的文件将是作为学生的头像。这可能是为每个文件添加到用户实体的新属性。但是,它在软件开发的性质中,有一天,利益相关方将要求修改申请,以满足个人报告,成绩,会议说明和各种其他文件。每次需要满足新文件类型时,您都必须更改用户类并添加新的迁移。相反,您将创建一个枚举以表示每个文件的目的,并将一个属性添加到用户实体以保存文件集合,无论其目的如何。

将新类文件添加到 楷模 文件夹并命名它 filetype.vb.。用以下内容替换现有代码:

Public Enum FileType
    Avatar = 1
    Photo
End Enum

在数据库中存储文件

第一个示例将介绍将文件存储为数据库表中的二进制数据。如果您已经决定将文件存储在文件系统中并希望跳过此部分,请随时导航到教程的第二部分。

创建文件实体

Newer versions of SQL Server offer the FileStream data type for storing file data. Existing versions of Entity Framework (6.1.2 as of the publication date of this tutorial) do not support the FileStream data type via Code First. When you store file content in a database, the content itself is stored in binary format. This is represented as an array of bytes in .NET which Entity Framework (currently) maps to the SQL Server varbinary(max) data type.

  1. 右键点击 楷模 文件夹并选择 添加新项目。添加类文件,名称它 file.vb. 并用以下内容替换现有内容:

    Imports System.ComponentModel.DataAnnotations
    
    Namespace  楷模 
        Public Class File
            Public Property FileId As Integer
            <StringLength(255)>
            Public Property FileName As String
            <StringLength(100)>
            Public Property ContentType As String
            Public Property Content As Byte()
            Public Property FileType As FileType
            Public Property  人 ID As Integer
            Public Overridable Property As End Class
    End Namespace

    The File entity has an overridable property, which is one part of establishing a one-to-many relationship with the class.

    MVC5与EF6.

  2. 通过添加文件集合作为属性来完成此关联 class:

    Public Overridable Property Files As ICollection(Of File)
  3. Now add a DbSet to the SchoolContext class:

    Public Property Files As DbSet(Of File)
  4. 通过将以下键入包管理器控制台(PMC)来添加新迁移:

    add-migration Files
  5. 通过将以下键入PMC来运行迁移。

    update-database

更改学生创建表单和控制器

使突出显示的变化 create.vbhtml. 文件在 观点\学生 folder:

@Using (Html.BeginForm("Create", "Student", Nothing, FormMethod.Post, New With{.enctype = "multipart/form-data"})) 
    @Html.AntiForgeryToken()
    
    @<div class="form-horizontal">
        <h4>Student</h4>
        <hr />
        @Html.ValidationSummary(true)
        <div class="form-group">
            @Html.LabelFor(Function(model) model.LastName, New With { .class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(Function(model) model.LastName)
                @Html.ValidationMessageFor(Function(model) model.LastName)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(Function(model) model.FirstMidName, New With { .class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(Function(model) model.FirstMidName)
                @Html.ValidationMessageFor(Function(model) model.FirstMidName)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(Function(model) model.EnrollmentDate, New With { .class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(Function(model) model.EnrollmentDate)
                @Html.ValidationMessageFor(Function(model) model.EnrollmentDate)
            </div>
        </div>

         <div class="form-group">
             @Html.Label("Avatar", New With {.class = "control-label col-md-2"})
             <div class="col-md-10">
                 <input type="file" id="Avatar"  名称 ="upload" />
             </div>
         </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
End Using

您已添加文件上传到表单:

MVC5与EF6.

You did this by specifying the type as "file" on an input element. However, this is not enough to ensure that uploaded file data is accessible on the server. You also used one of the longer overloads of the Html.BeginForm helper to add an enctype attribute to the form and sets its value to multipart/form-data. This is an essential step to getting file uploading to work.

Now add the highlighted code below to the HttpPost Create method in the StudentsController:

<HttpPost()>
<ValidateAntiForgeryToken()>
Function  创造 (<Bind(Include:="ID,LastName,FirstMidName,EnrollmentDate")> ByVal student As Student, ByVal upload As HttpPostedFileBase) As ActionResult
    Try
        If ModelState.IsValid Then
            If Not upload Is Nothing Then
                If upload.ContentLength > 0 Then
                    Dim avatar = New File()
                    avatar.FileName = upload.FileName
                    avatar.ContentType = upload.ContentType
                    avatar.FileType = FileType.Avatar
                    Using reader = New System.IO.BinaryReader(upload.InputStream)
    				avatar.Content = reader.ReadBytes(upload.ContentLength)
					End Using
                    student.Files = New List(Of File)
                    student.Files.Add(avatar)
                End If
            End If
            db.Students.Add(student)
            db.SaveChanges()
            Return RedirectToAction("Index")
        End If
    Catch dex As RetryLimitExceededException
        'Log the error (add a line here to write a log)
        ModelState.AddModelError("", "Unable to save changes. Try again, and of the problem persists see your system administrator. ")
    End Try
    Return View(student)
End Function

The first change adds a new parameter to the 创造 method's signature - upload, which is an instance of the HttpPostedFileBase type. The parameter name matches the 名称 attribute's value of the input type="file" in the Create form so that the MVC model binder can bind the uploaded file to the parameter that you added to the 创造 method.

The highlighted code block that you added is responsible for extracting the binary data from the request and populating a new File entity instance ready to be added to the student's Files collection. If the user adds a new student without uploading a file, the upload parameter will equate to Nothing, which is why that condition is checked before any attempt to access the HttpPostedFileBase ContentLength property. If you do not perform this check, and then reference the ContentLength property when no file has been uploaded, an exception will be raised. You then check the ContentLength because it is perfectly possible to upload an empty file. If the user does that, the ContentLength property will return 0, and there is no point in storing a file with no data in it. If the upload passes both tests, you create a new File object and assign the relevant properties with values taken from the upload parameter. The binary data is obtained from the InputStream property of the uploaded file, and a Stream object is used to read that into the Content property of the File object. Finally, you add the new File object to the student object.

更改详细信息方法和视图

Having stored the image in the database, you need to make some alterations to obtain the image and display it in the Details view. First, you will use the Include method in the LINQ query that fetches the student from the database to bring back releated files. 该包含方法不支持过滤,所以下面的突出显示的线 全部 无论类型如何,与学生关联的文件。

' GET: /Student/Details/5
Function Details(ByVal id As Integer?) As ActionResult
    If IsNothing(id) Then
        Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
    End If
    Dim student As Student
    student = db.Students.Include(Function(s) s.Files).SingleOrDefault(Function(s) s.ID = id)
    If IsNothing(student) Then
        Return HttpNotFound()
    End If
    Return View(student)
End Function

将突出显示的代码部分添加到 观点\ student \ details.vbhtml 文件。

<h4>Student</h4>
<hr />
<dl class="dl-horizontal">
    @If Model.Files.Any(Function(f) f.FileType = FileType.Avatar) Then
        @<dt>
            Avatar
        </dt>
        @<dd>
            <img src="~/File?id=@Model.Files.First(Function(f) f.FileType = FileType.Avatar).FileId" alt="avatar" />
        </dd>
    End If
    <dt>
        @Html.DisplayNameFor(Function(model) model.LastName)
    </dt>

您返回与学生关联的所有文件(如果有的话),因此必须检查是否有与化身文件类型匹配的任何文件。如果有的话,您呈现第一个文件。使用标准HTML IMG标记呈现图像。

MVC5与EF6.

按照惯例,图像映射的URL映射到名为file的控制器的索引方法,以文件的ID传递为查询字符串值。

添加文件控制器

  1. 右键单击控制器文件夹并选择 添加..控制器

  2. 选择 MVC 5控制器 - 空 from the selection.

    MVC5与EF6.

  3. 说出它 filecontroller..

    MVC5与EF6.

  4. 用以下内容替换模板合计代码:

    Imports System.Web.Mvc
    Imports ContosoUniversity.DAL
    
    Public Class filecontroller.
        Inherits Controller
    
        Private db As New SchoolContext
    
        ' GET: /File
        Function Index(ByVal id As Integer) As ActionResult
            Dim fileToRetrieve = db.Files.Find(id)
            Return File(fileToRetrieve.Content, fileToRetrieve.ContentType)
        End Function
    End Class

    代码基于在查询字符串中传递的ID值获取正确的文件。然后它将图像返回给浏览器 FileResult.

您已完成功能以将要存储在数据库中的图像添加到新学生,并将其显示为其详细信息的一部分。下一节介绍如何提供功能以允许用户编辑现有的学生的化身图像。

自定义编辑方法和视图

  1. 改变 code in the HttpGet Edit method in the StudentController to include the retrieval of files as in the highlighted code below.

    ' GET: /Student/Edit/5
    Function Edit(ByVal id As Integer?) As ActionResult
        If IsNothing(id) Then
            Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
        End If
        Dim student As Student = db.Students.Include(Function(s) s.Files).Where(Function(s) s.ID = id).First()
        If IsNothing(student) Then
            Return HttpNotFound()
        End If
        Return View(student)
    End Function
  2. 改变 观点\ student \ edit.vbhtml file to include the correct enctype attribute in the form, and to both display the existing image and to provide a file upload should the user wish to provide a replacement:

    @Using (Html.BeginForm("Edit", "Student", Nothing, FormMethod.Post, New With{.enctype = "multipart/form-data"}))
        @Html.AntiForgeryToken()
    
        @<div class="form-horizontal">
            <h4>Student</h4>
            <hr />
            @Html.ValidationSummary(True)
            @Html.HiddenFor(Function(model) model.ID)
    
            <div class="form-group">
                @Html.LabelFor(Function(model) model.LastName, New With {.class = "control-label col-md-2"})
                <div class="col-md-10">
                    @Html.EditorFor(Function(model) model.LastName)
                    @Html.ValidationMessageFor(Function(model) model.LastName)
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(Function(model) model.FirstMidName, New With {.class = "control-label col-md-2"})
                <div class="col-md-10">
                    @Html.EditorFor(Function(model) model.FirstMidName)
                    @Html.ValidationMessageFor(Function(model) model.FirstMidName)
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(Function(model) model.EnrollmentDate, New With {.class = "control-label col-md-2"})
                <div class="col-md-10">
                    @Html.EditorFor(Function(model) model.EnrollmentDate)
                    @Html.ValidationMessageFor(Function(model) model.EnrollmentDate)
                </div>
            </div>
    
            @If Model.Files.Any(Function(f) f.FileType = FileType.Avatar) Then
                @<div class="form-group">
                    <span class="control-label col-md-2"> Current Avatar</span>
                    <div class="col-md-10">
                        <img src="~/File?id=@Model.Files.First(Function(f) f.FileType = FileType.Avatar).FileId" alt="avatar" />
                    </div>
                </div>
            End If
    
            <div class="form-group">
                @Html.Label("Avatar", New With {.class = "control-label col-md-2"})
                <div class="col-md-10">
                    <input type="file" id="Avatar"  名称 ="upload" />
                </div>
            </div>
    
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Save" class="btn btn-default" />
                </div>
            </div>
        </div>
    End Using
    
  3. 使突出显示的变化 HttpPost Edit method as shown below.

    <HttpPost(), ActionName("Edit")>
    <ValidateAntiForgeryToken()>
    Function EditPost(ByVal id? As Integer, ByVal upload As HttpPostedFileBase) As ActionResult
        If id Is Nothing Then
            Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
        End If
        Dim studentToUpdate = db.Students.Include(Function(s) s.Files).Where(Function(s) s.ID = id).First()
        If TryUpdateModel(studentToUpdate, "", New String() {"LastName", "FirstMidName", "EnrollmentDate"}) Then
            Try
                If Not upload Is Nothing Then
                    If upload.ContentLength > 0 Then
                        If studentToUpdate.Files.Any(Function(f) f.FileType = FileType.Avatar) Then
                            db.Files.Remove(studentToUpdate.Files.First(Function(f) f.FileType = FileType.Avatar))
                        End If
                        Dim avatar = New File()
                        avatar.FileName = upload.FileName
                        avatar.ContentType = upload.ContentType
                        avatar.FileType = FileType.Avatar
                        Dim fileStream = upload.InputStream
                        avatar.Content = New Byte(upload.ContentLength - 1) {}
                        fileStream.Read(avatar.Content, 0, upload.ContentLength)
                        studentToUpdate.Files.Add(avatar)
                    End If
                End If
                db.Entry(studentToUpdate).State = EntityState.Modified
                db.SaveChanges()
                Return RedirectToAction("Index")
            Catch Dex As DataException
                'Log the error (uncomment dex variable name and add a line here to write a log.
                ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.")
            End Try
        End If
        Return View(studentToUpdate)
    End Function

You altered the signature of the HttpPost Edit method to accept an instance of the HttpPostedFile. As in the 创造 method, this represents the uploaded file. The highlighted code block checks to see if a file was uploaded, and if one was, that it contains data. These are the same checks as you made in the 创造 方法。 If the file contains data, you find any existing files associated with the current student that are Avatar file types. If found, you mark the file for deletion. Then you add the uploaded file as a replacement.

使用文件系统

The next section covers how to store uploaded files in the file system instead of in the database. You will create a FilePath entity to represent a single file.

  1. 为...添加新课程 楷模 文件夹称为 filepath.vb. 并用以下内容替换模板化代码:

    Imports System.ComponentModel.DataAnnotations
    
    Namespace  楷模 
        Public Class FilePath
            Public Property FilePathId As Integer
            <StringLength(255)>
            Public Property FileName As String
            Public Property FileType As FileType
            Public Property  人 ID As Integer
            Public Overridable Property As End Class
    End Namespace
  2. Add a DbSet to the SchoolContext for the new entity:

    Public Property FilePaths As DbSet(Of FilePath)
  3. Add a new navigational property to the class to accommodate a collection of FilePath objects:

    Public Overridable Property FilePaths As ICollection(Of FilePath)

    这在人和文件路之间创造了一对多关系:

    MVC5与EF6.

  4. ctrl + shift + b 为确保您的项目成功构建,然后通过将以下键入PMC来为项目添加新迁移:

    add-migration FilePaths
  5. Apply the changes to the database by typing update-database into the PMC.

修改教师创建和详细信息视图和方法

In this example, you will only alter the 创造 and Details methods and views. These alterations cover the core concepts that you need to understand when saving files to the file system.

  1. 首先,将文件夹添加到调用的根目录 图片 .

  2. 改变 Html.BeginForm helper in 观点\ jorightor \ create.vbhtml to add the correct enctype to the form:

    @Using (Html.BeginForm("Create", "Instructor", Nothing, FormMethod.Post, New With {.enctype = "multipart/form-data"}))
  3. 现在,将文件输入添加到表单本身,刚刚在办公室位置输入之后以及在DIV之前,该课程包含分配的课程复选框:

    <div class="form-group">
         @Html.Label("Photo", New With {.class = "control-label col-md-2"})
         <div class="col-md-10">
             <input type="file" id="Photo"  名称 ="upload" />
         </div>
    </div>
  4. 将以下导入指令添加到顶部 ChinanceRController.vb. file:

    Imports System.IO
  5. 使突出显示的变化 HttpPost Create method in ChinanceRController.vb.:

    <HttpPost()>
    <ValidateAntiForgeryToken()>
    Function  创造 (<Bind(Include:="ID,LastName,FirstMidName,HireDate,OfficeAssignment")> ByVal instructor As Instructor, ByVal selectedCourses As String(), ByVal upload As HttpPostedFileBase) As ActionResult
        If selectedCourses IsNot Nothing Then
            instructor.Courses = New List(Of Course)
            For Each id In selectedCourses
                Dim courseToAdd = db.Courses.Find(Integer.Parse(id))
                instructor.Courses.Add(courseToAdd)
            Next
        End If
        If upload IsNot Nothing Then
            If upload.ContentLength > 0 Then
                 Dim photo = New FilePath()
                 photo.FileName = Path.GetFileName(upload.FileName) 'use the existing file's name
                 photo.FileType = FileType.Photo
                 instructor.FilePaths = New List(Of FilePath)
                 instructor.FilePaths.Add(photo)
                 upload.SaveAs(Path.Combine(Server.MapPath("~/images"), photo.FileName))
            End If
        End If
        If ModelState.IsValid Then
            db.Instructors.Add(instructor)
            db.SaveChanges()
            Return RedirectToAction("Index")
        End If
        PopulateAssignedCourseData(instructor)
        Return View(instructor)
    End Function

You added an additional parameter to the 创造 method's signature - upload of type HttpPostedFileBase. This is mapped to the file upload based on the parameter's name matching the 名称 attribute on the input type=file that you added in the Create view, and provides access to the properties of the uploaded file. The added code checks to ensure that the upload is not null and then checks its ContentLength property to ensure that an empty file as not uploaded. If both of those checks succeed, a new FilePath object is created and its FileName property is assigned the value of the uploaded file's FileName, which is obtained by the System.IO.Path.GetFileName 方法。

笔记: Some browsers provide more than just the file name when files are uploaded. Versions of Internet Explorer will provide the full client file path when you use it locally, i.e in development and test. Other browsers may or may not prepend the file name with values such as C:\FakePath\. In any event, you cannot rely on a browser providing just the file's name. Equally, you cannot rely on the browser to provide the original path of the uploaded file. Only some versions of Internet Explorer (currently) do this, and even then, only when your client and server are the same machine.

使用从上载作为文件名的值将文件本身保存到图像文件夹中。

笔记: it is not always a good idea to use the file's original name when you store uploads in the file system. Two files with the same name cannot exist in the same location so if a new file is uploaded and saved with the same name as an existing one, the original file will be overwritten when the new one is saved. A common resolution to this problem is to rename the file prior to saving it. Typically, a Guid is used as a file name on the basis that it can pretty much guarantee uniqueness. If you need to adopt this strategy, you can replace the existing line that assigns the FilePath object's FileName property with the following two lines of code:

Dim fileName = Guid.NewGuid().ToString() & Path.GetExtension(upload.FileName)
photo.FileName = fileName

修改教练细节方法和视图

Make the highlighted amendment to the Details method in ChinanceRController.vb.:

Function Details(ByVal id As Integer?) As ActionResult
    If IsNothing(id) Then
        Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
    End If
    Dim instructor As Instructor = db.Instructors.Include(Function(i) i.FilePaths).Single(Function(i) i.ID = id)
    If IsNothing(instructor) Then
        Return HttpNotFound()
    End If
    Return View(instructor)
End Function

最后,将以下代码添加到 观点\ constructor \ details.vbhtml filejust before the closing </dl> tag

@If Model.FilePaths.Any(Function(f) f.FileType = FileType.Photo) Then
    @<dt>
        Photo
    </dt>
    @<dd>
        <img src="~/images/@Model.FilePaths.First(Function(f) f.FileType = FileType.Photo).FileName" alt="" />
    </dd>
End If

The alteration that you made to the Details method in the controller ensures that any filepath objects belonging to the instructor are retrieved along with the rest of their details. The code that you added to the View checks to see if there are any filepath objects matching the FileType.Photo type, and if there are, the first one is displayed using an img element. The src attribute value is generated by concatenating the location where files are saved with the name that was given to this particular file. Notice that you didn't include the folder name as part of the value that you used for the FileName property. Doing so may make things more diffcult if you ever needed to change the file storage location.

概括

在本教程中,您了解如何使用实体框架在MVC应用程序中使用文件。您探讨了两种持续文件信息的方法:将文件数据存储在数据库中;并将文件存储在文件系统中。