天天看點

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 包管理器” > “包管理器控制台”。

ASP.NET CORE RAZOR :将檔案上傳至 ASP.NET Core 中的 Razor 頁面

在 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");
        }      

連結檔案上傳 Razor 頁面

打開“_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>      

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

ASP.NET CORE RAZOR :将檔案上傳至 ASP.NET Core 中的 Razor 頁面

這裡隻能上傳文本檔案,還要注意.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))      
ASP.NET CORE RAZOR :将檔案上傳至 ASP.NET Core 中的 Razor 頁面

修改後上傳檔案中包含中文字元就沒問題了。

添加計劃删除确認頁面

使用者單擊删除計劃時,為其提供取消此操作的機會。 向“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 頁面

頁面加載時,計劃标題、公用計劃和專用計劃的标簽和輸入将呈現送出按鈕:

ASP.NET CORE RAZOR :将檔案上傳至 ASP.NET Core 中的 Razor 頁面

在不填充任何字段的情況下選擇“上傳”按鈕會違反此模型上的 

[Required]

 特性。 

ModelState

 無效。 會向使用者顯示驗證錯誤消息:

ASP.NET CORE RAZOR :将檔案上傳至 ASP.NET Core 中的 Razor 頁面

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

ASP.NET CORE RAZOR :将檔案上傳至 ASP.NET Core 中的 Razor 頁面

使用者可單擊該表中的“删除”連結以通路删除确認視圖,并在其中選擇确認或取消删除操作。

 ASP.NET CORE RAZOR 到此結束

源代碼

 連結:https://pan.baidu.com/s/1c4k5au8

 密碼:rtr7

轉載于:https://www.cnblogs.com/djd66/p/8487825.html