ASP.NET CORE RAZOR :將文件上傳至 ASP.NET Core 中的 Razor 頁面


本部分演示使用 Razor 頁面上傳文件。

本教程中的 Razor 頁面 Movie 示例應用使用簡單的模型綁定上傳文件,非常適合上傳小型文件。 有關流式傳輸大文件的信息,請參閱通過流式傳輸上傳大文件

在下列步驟中,向示例應用添加電影計划文件上傳功能。 每個電影計划由一個 Schedule 類表示。 該類包括兩個版本的計划。 其中一個版本 (PublicSchedule) 提供給客戶。 另一個版本 (PrivateSchedule) 用於公司員工。 每個版本作為單獨的文件進行上傳。 本教程演示如何通過單個 POST 將兩個文件上傳至服務器。

添加 FileUpload 類

創建 Razor 頁以處理一對文件上傳。 添加 FileUpload 類(此類與頁面綁定以獲取計划數據)。 右鍵單擊“Models”文件夾。 選擇“添加” > “類”。 將類命名為“FileUpload”,並添加以下屬性:

using Microsoft.AspNetCore.Http;
using System.ComponentModel.DataAnnotations;

namespace RazorPagesMovie.Models
{
    public class FileUpload
    {
        [Required]
        [Display(Name="Title")]
        [StringLength(60, MinimumLength = 3)]
        public string Title { get; set; }

        [Required]
        [Display(Name="Public Schedule")]
        public IFormFile UploadPublicSchedule { get; set; }

        [Required]
        [Display(Name="Private Schedule")]
        public IFormFile UploadPrivateSchedule { get; set; }
    }
}

此類有一個屬性對應計划標題,另各有一個屬性對應計划的兩個版本。 3 個屬性皆為必需屬性,標題長度必須為 3-60 個字符。

添加用於上傳文件的 helper 方法

為避免處理未上傳計划文件時出現代碼重復,請首先上傳一個靜態 helper 方法。 在此應用中創建一個“Utilities”文件夾,然后在“FileHelpers.cs”文件中添加以下內容。 helper 方法 ProcessFormFile 接受 IFormFile 和 ModelStateDictionary,並返回包含文件大小和內容的字符串。 檢查內容類型和長度。 如果文件未通過驗證檢查,將向 ModelState 添加一個錯誤。

using System;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Utilities
{
    public class FileHelpers
    {
        public static async Task<string> ProcessFormFile(IFormFile formFile, ModelStateDictionary modelState)
        {
            var fieldDisplayName = string.Empty;

            // Use reflection to obtain the display name for the model 
            // property associated with this IFormFile. If a display
            // name isn't found, error messages simply won't show
            // a display name.
            MemberInfo property = 
                typeof(FileUpload).GetProperty(formFile.Name.Substring(formFile.Name.IndexOf(".") + 1));

            if (property != null)
            {
                var displayAttribute = 
                    property.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;

                if (displayAttribute != null)
                {
                    fieldDisplayName = $"{displayAttribute.Name} ";
                }
            }

            // Use Path.GetFileName to obtain the file name, which will
            // strip any path information passed as part of the
            // FileName property. HtmlEncode the result in case it must 
            // be returned in an error message.
            var fileName = WebUtility.HtmlEncode(Path.GetFileName(formFile.FileName));

            if (formFile.ContentType.ToLower() != "text/plain")
            {
                modelState.AddModelError(formFile.Name, 
                                         $"The {fieldDisplayName}file ({fileName}) must be a text file.");
            }

            // Check the file length and don't bother attempting to
            // read it if the file contains no content. This check
            // doesn't catch files that only have a BOM as their
            // content, so a content length check is made later after 
            // reading the file's content to catch a file that only
            // contains a BOM.
            if (formFile.Length == 0)
            {
                modelState.AddModelError(formFile.Name, $"The {fieldDisplayName}file ({fileName}) is empty.");
            }
            else if (formFile.Length > 1048576)
            {
                modelState.AddModelError(formFile.Name, $"The {fieldDisplayName}file ({fileName}) exceeds 1 MB.");
            }
            else
            {
                try
                {
                    string fileContents;

                    // The StreamReader is created to read files that are UTF-8 encoded. 
                    // If uploads require some other encoding, provide the encoding in the 
                    // using statement. To change to 32-bit encoding, change 
                    // new UTF8Encoding(...) to new UTF32Encoding().
                    using (
                        var reader = 
                            new StreamReader(
                                formFile.OpenReadStream(), 
                                new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), 
                                detectEncodingFromByteOrderMarks: true))
                    {
                        fileContents = await reader.ReadToEndAsync();

                        // Check the content length in case the file's only
                        // content was a BOM and the content is actually
                        // empty after removing the BOM.
                        if (fileContents.Length > 0)
                        {
                            return fileContents;
                        }
                        else
                        {
                            modelState.AddModelError(formFile.Name, 
                                                     $"The {fieldDisplayName}file ({fileName}) is empty.");
                        }
                    }
                }
                catch (Exception ex)
                {
                    modelState.AddModelError(formFile.Name, 
                                             $"The {fieldDisplayName}file ({fileName}) upload failed. " +
                                             $"Please contact the Help Desk for support. Error: {ex.Message}");
                    // Log the exception
                }
            }

            return string.Empty;
        }
    }
}

以上內容如果看不懂就不用看了,核心思想就是檢查上傳文件是否合乎要求。

將文件保存到磁盤(拓展內容,本案例未用到)

示例應用將文件內容保存到數據庫字段。 若要將文件內容保存到磁盤,請使用文件流

using (var fileStream = new FileStream(filePath, FileMode.Create))
{
    await formFile.CopyToAsync(fileStream);
}

將文件保存到 Azure Blob 存儲(拓展內容,本案例未用到)

若要將文件內容上傳到 Azure Blob 存儲,請參閱使用 .NET 的 Azure Blob 存儲入門。 本主題演示如何使用UploadFromStream 將文件流保存到 blob 存儲。

添加 Schedule 類

右鍵單擊“Models”文件夾。 選擇“添加” > “類”。 將類命名為“Schedule”,並添加以下屬性:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Schedule
    {
        public int ID { get; set; }
        public string Title { get; set; }

        public string PublicSchedule { get; set; }

        [Display(Name = "Public Schedule Size (bytes)")]
        [DisplayFormat(DataFormatString = "{0:N1}")]
        public long PublicScheduleSize { get; set; }

        public string PrivateSchedule { get; set; }

        [Display(Name = "Private Schedule Size (bytes)")]
        [DisplayFormat(DataFormatString = "{0:N1}")]
        public long PrivateScheduleSize { get; set; }

        [Display(Name = "Uploaded (UTC)")]
        [DisplayFormat(DataFormatString = "{0:F}")]
        public DateTime UploadDT { get; set; }
    }
}

此類使用 Display 和 DisplayFormat 特性,呈現計划數據時,這些特性會生成友好型的標題和格式。

更新 MovieContext

在 MovieContext (Models/MovieContext.cs) 中為計划指定 DbSet

using Microsoft.EntityFrameworkCore;

namespace RazorPagesMovie.Models
{
    public class MovieContext:DbContext
    {
        public MovieContext(DbContextOptions<MovieContext> options)
            : base(options)
        {
        }

        public DbSet<Movie> Movie { get; set; }
        public DbSet<Class> Class { get; set; }
        public DbSet<Schedule> Schedule { get; set; }
    }
}

將 Schedule 表添加到數據庫

打開包管理器控制台 (PMC):“工具” > “NuGet 包管理器” > “包管理器控制台”。

在 PMC 中執行以下命令。 這些命令將向數據庫添加 Schedule 表:

Add-Migration AddScheduleTable
Update-Database

添加文件上傳 Razor 頁面

在“Pages”文件夾中創建“Schedules”文件夾。 在“Schedules”文件夾中,創建名為“Index.cshtml”的頁面,用於上傳具有如下內容的計划:

 

@page
@model RazorPagesMovie.Pages.Schedule.IndexModel
@{
    ViewData["Title"] = "Schedules";
}

<h2>Schedules</h2>
<hr />

<h3>Upload Schedules</h3>
<div class="row">
    <div class="col-md-4">
        <form method="post" enctype="multipart/form-data">
            <div class="form-group">
                <label asp-for="FileUpload.Title" class="control-label"></label>
                <input asp-for="FileUpload.Title" type="text" class="form-control" />
                <span asp-validation-for="FileUpload.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="FileUpload.UploadPublicSchedule" class="control-label"></label>
                <input asp-for="FileUpload.UploadPublicSchedule" type="file" class="form-control" style="height:auto" />
                <span asp-validation-for="FileUpload.UploadPublicSchedule" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="FileUpload.UploadPrivateSchedule" class="control-label"></label>
                <input asp-for="FileUpload.UploadPrivateSchedule" type="file" class="form-control" style="height:auto" />
                <span asp-validation-for="FileUpload.UploadPrivateSchedule" class="text-danger"></span>
            </div>
            <input type="submit" value="Upload" class="btn btn-default" />
        </form>
    </div>
</div>

<h3>Loaded Schedules</h3>
<table class="table">
    <thead>
        <tr>
            <th></th>
            <th>
                @Html.DisplayNameFor(model => model.Schedule[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Schedule[0].UploadDT)
            </th>
            <th class="text-center">
                @Html.DisplayNameFor(model => model.Schedule[0].PublicScheduleSize)
            </th>
            <th class="text-center">
                @Html.DisplayNameFor(model => model.Schedule[0].PrivateScheduleSize)
            </th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Schedule)
        {
            <tr>
                <td>
                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Title)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.UploadDT)
                </td>
                <td class="text-center">
                    @Html.DisplayFor(modelItem => item.PublicScheduleSize)
                </td>
                <td class="text-center">
                    @Html.DisplayFor(modelItem => item.PrivateScheduleSize)
                </td>
            </tr>
        }
    </tbody>
</table>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

每個窗體組包含一個 <label>,它顯示每個類屬性的名稱。 FileUpload 模型中的 Display 特性提供這些標簽的顯示值。例如,UploadPublicSchedule 特性的顯示名稱通過 [Display(Name="Public Schedule")] 進行設置,因此呈現窗體時會在此標簽中顯示“Public Schedule”。

每個窗體組包含一個驗證 <span>。 如果用戶輸入未能滿足 FileUpload 類中設置的屬性特性,或者任何 ProcessFormFile方法文件檢查失敗,則模型驗證會失敗。 模型驗證失敗時,會向用戶呈現有用的驗證消息。 例如,Title 屬性帶有 [Required] 和 [StringLength(60, MinimumLength = 3)] 注釋。 用戶若未提供標題,會接收到一條指示需要提供值的消息。如果用戶輸入的值少於 3 個字符或多於 60 個字符,則會接收到一條指示值長度不正確的消息。 如果提供不含內容的文件,則會顯示一條指示文件為空的消息。

添加頁面模型

將頁面模型 (Index.cshtml.cs) 添加到“Schedules”文件夾中:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
using RazorPagesMovie.Utilities;

namespace RazorPagesMovie.Pages.Schedule
{
    public class IndexModel : PageModel
    {
        private readonly RazorPagesMovie.Models.MovieContext _context;

        public IndexModel(RazorPagesMovie.Models.MovieContext context)
        {
            _context = context;
        }

        [BindProperty]
        public FileUpload FileUpload { get; set; }

        public IList<Models.Schedule> Schedule { get; private set; }

        public async Task OnGetAsync()
        {
            Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
        }

        public async Task<IActionResult> OnPostAsync()
        {
            // Perform an initial check to catch FileUpload class
            // attribute violations.
            if (!ModelState.IsValid)
            {
                Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
                return Page();
            }

            var publicScheduleData =
                await FileHelpers.ProcessFormFile(FileUpload.UploadPublicSchedule, ModelState);

            var privateScheduleData =
                await FileHelpers.ProcessFormFile(FileUpload.UploadPrivateSchedule, ModelState);

            // Perform a second check to catch ProcessFormFile method
            // violations.
            if (!ModelState.IsValid)
            {
                Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
                return Page();
            }

            var schedule = new Models.Schedule()
            {
                PublicSchedule = publicScheduleData,
                PublicScheduleSize = FileUpload.UploadPublicSchedule.Length,
                PrivateSchedule = privateScheduleData,
                PrivateScheduleSize = FileUpload.UploadPrivateSchedule.Length,
                Title = FileUpload.Title,
                UploadDT = DateTime.UtcNow
            };

            _context.Schedule.Add(schedule);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

頁面模型(Index.cshtml.cs 中的 IndexModel)綁定 FileUpload 類:

[BindProperty]
public FileUpload FileUpload { get; set; }

此模型還使用計划列表 (IList<Schedule>) 在頁面上顯示數據庫中存儲的計划:

 public IList<Models.Schedule> Schedule { get; private set; }

頁面加載 OnGetAsync 時,會從數據庫填充 Schedules,用於生成已加載計划的 HTML 表:

public async Task OnGetAsync()
{
    Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
}

將窗體發布到服務器時,會檢查 ModelState。 如果無效,會重新生成 Schedule,且頁面會呈現一個或多個驗證消息,陳述頁面驗證失敗的原因。 如果有效,FileUpload 屬性將用於“OnPostAsync”中,以完成兩個計划版本的文件上傳,並創建一個用於存儲數據的新 Schedule 對象。 然后會將此計划保存到數據庫:

        public async Task<IActionResult> OnPostAsync()
        {
            // Perform an initial check to catch FileUpload class
            // attribute violations.
            if (!ModelState.IsValid)
            {
                Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
                return Page();
            }

            var publicScheduleData =
                await FileHelpers.ProcessFormFile(FileUpload.UploadPublicSchedule, ModelState);

            var privateScheduleData =
                await FileHelpers.ProcessFormFile(FileUpload.UploadPrivateSchedule, ModelState);

            // Perform a second check to catch ProcessFormFile method
            // violations.
            if (!ModelState.IsValid)
            {
                Schedule = await _context.Schedule.AsNoTracking().ToListAsync();
                return Page();
            }

            var schedule = new Models.Schedule()
            {
                PublicSchedule = publicScheduleData,
                PublicScheduleSize = FileUpload.UploadPublicSchedule.Length,
                PrivateSchedule = privateScheduleData,
                PrivateScheduleSize = FileUpload.UploadPrivateSchedule.Length,
                Title = FileUpload.Title,
                UploadDT = DateTime.UtcNow
            };

            _context.Schedule.Add(schedule);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }

打開“_Layout.cshtml”,然后向導航欄添加一個鏈接以訪問文件上傳頁面:

            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-page="/Index">Home</a></li>
                    <li><a asp-page="/About">About</a></li>
                    <li><a asp-page="/Contact">Contact</a></li>
                    <li><a asp-page="/Product">產品展示</a></li>
                    <li><a asp-page="/Movies/Index">RpMovie</a></li>
                    <li><a asp-page="/Schedule/Index">文件上傳</a></li>
                </ul>
            </div>

現在可以運行看一下效果了

這里只能上傳文本文件,還要注意.txt文件必須為Unicode文件類型,否則文件中帶有漢字的話,上傳后會提示錯誤消息

Error: Unable to translate bytes [D7] at index 0 from specified code page to Unicode.

 解決方法:FileHelpers.cs文件中修改讀取字符編碼的格式為

                    using (
                        var reader =
                            new StreamReader(
                                formFile.OpenReadStream(), 
                   new UTF32Encoding(false, false),//使用此字符編碼 //new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true),//原字符編碼 detectEncodingFromByteOrderMarks: true))

修改后上傳文件中包含中文字符就沒問題了。

添加計划刪除確認頁面

用戶單擊刪除計划時,為其提供取消此操作的機會。 向“Schedules”文件夾添加刪除確認頁面 (Delete.cshtml):

@page "{id:int}"
@model RazorPagesMovie.Pages.Schedule.DeleteModel

@{
    ViewData["Title"] = "Delete Schedule";
}

<h2>Delete Schedule</h2>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Schedule</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Schedule.Title)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Schedule.Title)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Schedule.PublicScheduleSize)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Schedule.PublicScheduleSize)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Schedule.PrivateScheduleSize)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Schedule.PrivateScheduleSize)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Schedule.UploadDT)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Schedule.UploadDT)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Schedule.ID" />
        <input type="submit" value="Delete" class="btn btn-default" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

頁面模型 (Delete.cshtml.cs) 在請求的路由數據中加載由 id 標識的單個計划。 將“Delete.cshtml.cs”文件添加到“Schedules”文件夾:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;

namespace RazorPagesMovie.Pages.Schedule
{
    public class DeleteModel : PageModel
    {
        private readonly RazorPagesMovie.Models.MovieContext _context;

        public DeleteModel(RazorPagesMovie.Models.MovieContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Models.Schedule Schedule { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Schedule = await _context.Schedule.SingleOrDefaultAsync(m => m.ID == id);

            if (Schedule == null)
            {
                return NotFound();
            }
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Schedule = await _context.Schedule.FindAsync(id);

            if (Schedule != null)
            {
                _context.Schedule.Remove(Schedule);
                await _context.SaveChangesAsync();
            }

            return RedirectToPage("./Index");
        }
    }
}

OnPostAsync 方法按 id 處理計划刪除:

public async Task<IActionResult> OnPostAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Schedule = await _context.Schedule.FindAsync(id);

    if (Schedule != null)
    {
        _context.Schedule.Remove(Schedule);
        await _context.SaveChangesAsync();
    }

    return RedirectToPage("./Index");
}

成功刪除計划后,RedirectToPage 將返回到計划的“Index.cshtml”頁面。

有效的 Schedules Razor 頁面

頁面加載時,計划標題、公用計划和專用計划的標簽和輸入將呈現提交按鈕:

在不填充任何字段的情況下選擇“上傳”按鈕會違反此模型上的 [Required] 特性。 ModelState 無效。 會向用戶顯示驗證錯誤消息:

上傳一個或多個計划時,“已加載計划”部分會顯示已加載計划:

用戶可單擊該表中的“刪除”鏈接以訪問刪除確認視圖,並在其中選擇確認或取消刪除操作。

 ASP.NET CORE RAZOR 到此結束

源代碼

 鏈接: https://pan.baidu.com/s/1c4k5au8

 密碼:rtr7


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM