前文索引:
ASP.NET Core教程【二】從保存數據看Razor Page的特有屬性與服務端驗證
ASP.NET Core教程【一】關於Razor Page的知識
實體字段屬性
再來看看我們的實體類
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
說明,上面的代碼需要引用:using System.ComponentModel.DataAnnotations;
Display屬性標志這個字段在頁面上顯示的時候,需要顯示什么名字;
我們在上一篇文章中用到的:
<label asp-for="Movie.Title" class="control-label"></label>
這里就會顯示Display屬性指定的名字;
DataType屬性標志這個字段是什么類型的;
上一章中我們說到的,數據類型的驗證工作,就是依據這里標志的數據類型來完成的
比如你可以增加如下數據約束
[StringLength(60, MinimumLength = 3)]
[Required]
[Range(1, 100)]
[RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
[DataType(DataType.Currency)]
如果你想格式化輸出的內容,你可以使用如下的屬性注釋
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}"]
你可以在同一行代碼中標記多個屬性,如下:
[RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$"), Required, StringLength(30)]
更多說明文檔,可以查閱:https://docs.microsoft.com/zh-cn/aspnet/mvc/overview/older-versions/mvc-music-store/mvc-music-store-part-6
鏈接標簽
在上一篇文章中我們簡單說了一下鏈接標簽,再來看第一章中提到的這個場景:
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a>
<a asp-page="./Details" asp-route-id="@item.ID">Details</a>
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
asp-page和asp-route-id兩個屬性共同決定了最終編譯出來的鏈接地址;
以前我們可能要拼字符串來構造href屬性,現在不用了,可以分開寫,代碼更優雅;
上面的代碼,編譯后生成的href屬性的值是這樣的:
http://localhost:5000/Movies/Details?id=2
現在我們分別打開Edit.cshtml、Details.cshtml、Delete.cshtml
把頁面中的第一個命令:@page,修改為:@page "{id:int}"
重新編譯運行,發現上面的鏈接變成了:
http://localhost:5000/Movies/Details/1
看到這里你會說“呦~”嗎?😄
如果這個時候你請求這個地址:
http://localhost:5000/Movies/Details
並沒有傳入ID的值,那么服務器會返回404,
如果你的設計是希望ID是一個可選的傳入參數,那么你可以把page指令修改成:
@page "{id:int?}"
這樣就好啦!
如果你想讓頁面接收一個字符串,可以把這個“路由模版”寫成如下這個樣子:
@page "{searchString?}"
並發數據異常
當一個用戶刪除了一個實體,另一個用戶同時又要更新這個實體的話
第二個用戶的請求就會拋出並發數據異常(這里姑且稱作並發,微軟官網就是這么說的),來看代碼:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Movie.Any(e => e.ID == Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
上面代碼中DbUpdateConcurrencyException就是專門針對這種異常定義的異常類;
NotFound方法將返回404異常
文件上傳及讀取
如果你想上傳一個文件,可以撰寫如下razor page的代碼(只提供一部分表單域)
<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>
這個表單域對應的實體如下
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; }
}
}
我們只要關注第二個字段即可,UploadPublicSchedule是一個IFormFile類型的字段;
當表單提交后,ASP.NET CORE 也會把文件流綁定到這個字段上;
如果上傳的是一個文本文件,那么我們看看怎么直接讀取這個文本文件;
public static async Task<string> ProcessFormFile(IFormFile formFile, ModelStateDictionary modelState)
{
var fieldDisplayName = string.Empty;
// 通過反射拿到實例的字段,再拿到字段的DisplayAttribute
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} ";
}
}
// 通過Path.GetFileName拿到文件名
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.");
}
// 判斷文件長度
if (formFile.Length == 0)
{
modelState.AddModelError(formFile.Name, $"The {fieldDisplayName}file ({fileName}) is empty.");
}
else
{
try
{
string fileContents;
using (var reader = new StreamReader(formFile.OpenReadStream(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), detectEncodingFromByteOrderMarks: true))
{
fileContents = await reader.ReadToEndAsync();
// 再驗證一遍文件內容的長度,以防文件只有一個BOM頭
if (fileContents.Length > 0)
{
return fileContents;
}
else
{
modelState.AddModelError(formFile.Name, $"The {fieldDisplayName}file ({fileName}) is empty.");
}
}
}
catch (IOException ex)
{
modelState.AddModelError(formFile.Name, $"The {fieldDisplayName}file ({fileName}) upload failed. Please contact the Help Desk for support.");
}
}
return string.Empty;
}
調用上面方法的代碼如下:
var publicScheduleData = await FileHelpers.ProcessFormFile(FileUpload.UploadPublicSchedule, ModelState);
其中ModelState是PageModel特有的屬性
在本示例中,用於給頁面添加錯誤信息~