Загружаем файл с данными, отображаем и управляем данными при помощи Razor Pages¶
Примечание
Целью данной книги является демонстрация возможностей использования gRPC для разработки приложения с использованием ASP.NET Core и .NET 6. Поэтому мы опустим некоторые объяснения использования ASP.NET Core Razor Pages. Если вы хотите изучить вопрос подробнее, прочтите документацию Microsoft по этой ссылке1
Начнем со страницы Index.cshtml, на которую добавим форму для загрузки файла и HTML-таблицу со списком стран.
Вот Razor-разметка для страницы:
@page
@model IndexModel
@{
ViewData["Title"] = "Country Wiki main page";
}
<div class="text-center">
<h1 class="display-5">Country Wiki main page</h1>
</div>
<form method="post" enctype="multipart/form-data">
<div class="container mb-5 mt-5">
<div>
Upload countries (JSON only):
<input type="file" asp-for="Upload" />
</div>
<div><input type="submit" value="Upload" asp-page-handler="upload"/></div>
<div class="text-danger">@Model.UploadErrorMessage</div>
@if (Model.GlobalOptions.ProcessingUpload)
{
<div class="text-center text-danger"><h2>A file upload is in progress...</h2></div>
}
</div>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Capital City</th>
<th>Anthem</th>
<th>Spoken languages</th>
<th>Flag</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
@foreach (var country in Model.Countries)
{
<tr>
<td>@country.Id</td>
<td>@country.Name</td>
<td>@country.Description</td>
<td>@country.CapitalCity</td>
<td>@country.Anthem</td>
<td>@string.Join(", ", country.Languages)</td>
<td><img src="@country.FlagUri" alt="@country.Name" height="25" width="45"/></td>
<td><a asp-page="./Edit" asp-route-id="@country.Id">Edit</a></td>
<td><input type="submit" asp-page-handler="delete" asp-route-id="@country.Id" value="Delete" /></td>
</tr>
}
</tbody>
</table>
</form>
Обратите внимание, что поле
GlobalOptions.ProcessingUpload используется для определения, нужно ли отображать сообщение о том, обрабатывается ли загруженный файл.Вот код
IndexModel с обработчиками страницы.namespace CountryWiki.Web.Pages;
public class IndexModel : PageModel
{
private readonly ICountryServices _countryServices;
private readonly ICountryFileUploadValidatorService _countryFileUploadValidatorService;
private readonly ISyncCountriesChannel _syncCountriesChannel;
public readonly GlobalOptions GlobalOptions;
public IEnumerable<CountryModel> Countries { get; set; } = new List<CountryModel>();
public string UploadErrorMessage { get; set; } = string.Empty;
[BindProperty]
public IFormFile? Upload { get; set; }
public IndexModel(
ICountryServices countryServices,
ICountryFileUploadValidatorService countryFileUploadValidatorService,
ISyncCountriesChannel syncCountriesChannel,
GlobalOptions globalOptions)
{
_countryServices = countryServices;
_countryFileUploadValidatorService = countryFileUploadValidatorService;
_syncCountriesChannel = syncCountriesChannel;
GlobalOptions = globalOptions;
}
public async Task OnGetAsync()
{
Countries = await _countryServices.GetAllAsync();
}
public async Task<IActionResult> OnPostUploadAsync(CancellationToken cancellationToken)
{
if (Upload == null)
{
return await HandleFileValidation("File is missing");
}
var uploadedFile = new CountryUploadedFileModel
{
FileName = Upload.FileName,
ContentType = Upload.ContentType
};
if (!_countryFileUploadValidatorService.ValidateFile(uploadedFile))
{
return await HandleFileValidation("Only JSON files are allowed");
}
var parsedCountries = await _countryFileUploadValidatorService
.ParseFile(Upload.OpenReadStream());
var createCountryModels = parsedCountries?.ToList();
if (createCountryModels == null || !createCountryModels.Any())
{
return await HandleFileValidation("Cannot parse the file or the file is empty");
}
await _syncCountriesChannel.SyncAsync(createCountryModels, cancellationToken);
return RedirectToPage("./Index");
}
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
await _countryServices.DeleteAsync(id);
return RedirectToPage("./Index");
}
private async Task<PageResult> HandleFileValidation(string errorMessage)
{
UploadErrorMessage = errorMessage;
Countries = await _countryServices.GetAllAsync();
return Page();
}
}
Здесь реализованы следующие обработчики:
OnGetAsyncдля загрузки страницы. Загружает страны при помощиICountryServices.GetAllAsync()2;OnPostUploadAsyncвалидирует файл при помощиICountryFileUploadValidatorService, после чего отправляет файл вISyncCountriesChannel3. Если всё проходит удачно, возвращается редирект4 на страницу;OnPostDeleteAsyncудаляет страну при помощиICountryServices.DeleteAsync().
Теперь подготовим файл с данными, например, такой:
[
{
"name": "Canada",
"description": "Maple leaf country",
"capitalCity": "Ottawa",
"anthem": "O Canada!",
"flagUri": "https://anthonygiretti.blob.core.windows.net/countryflags/ca.png",
"languages": [1, 2]
},
{
"name": "USA",
"description": "Uncle Sam country",
"capitalCity": "Washington",
"anthem": "The Star-Spangled Banner",
"flagUri": "https://anthonygiretti.blob.core.windows.net/countryflags/us.png",
"languages": [2, 3]
},
{
"name": "United Kingdom",
"description": "Sovereign country of North-western Europe",
"capitalCity": "London",
"anthem": "God save the Quuen",
"flagUri": "https://anthonygiretti.blob.core.windows.net/countryflags/gb.png",
"languages": [1]
},
{
"name": "France",
"description": "Human rights country",
"capitalCity": "Paris",
"anthem": "La marseillaise",
"flagUri": "https://anthonygiretti.blob.core.windows.net/countryflags/fr.png",
"languages": [2]
},
{
"name": "Mexico",
"description": "Cradle of civilization country",
"capitalCity": "Mexico City",
"anthem": "Himno Nacional Mexicano",
"flagUri": "https://anthonygiretti.blob.core.windows.net/countryflags/mx.png",
"languages": [3]
}
]
Теперь, если мы загрузим файл и отправим его на обработку, нам покажут сообщение о том, что файл обрабатывается. Если после этого перезагрузить страницу, мы увидим загруженные данные.
Перейдём к странице редактирования. Вот её разметка:
@page
@model EditModel
@{
}
<h1>Edit Country - @Model.CountryName</h1>
<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<input asp-for="CountryToUpdate.Id" type="hidden" value="@Model.CountryToUpdate.Id" />
<div>
<label asp-for="CountryToUpdate.Description"></label>
<div>
<textarea rows="4" cols="50" name="@Html.NameFor(m => m.CountryToUpdate.Description)">@Model.CountryToUpdate.Description</textarea>
</div>
<div><span asp-validation-for="CountryToUpdate.Description" class="text-danger"></span></div>
</div>
<div>
<input type="submit" value="Save" />
</div>
</form>
Вот модель
namespace CountryWiki.Web.Pages;
public class EditModel : PageModel
{
private readonly ICountryServices _countryServices;
public string CountryName { get; set; }
[BindProperty]
public UpdateCountry CountryToUpdate { get; set; }
public EditModel(ICountryServices countryServices)
{
_countryServices = countryServices;
}
public async Task OnGetAsync(int id)
{
await RetrieveCountry(id);
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
await RetrieveCountry(CountryToUpdate.Id);
return Page();
}
await _countryServices.UpdateAsync(new UpdateCountryModel
{
Id = CountryToUpdate.Id,
Description = CountryToUpdate.Description
});
return RedirectToPage("./Index");
}
private async Task RetrieveCountry(int id)
{
var country = await _countryServices.GetAsync(id);
CountryName = country.Name;
CountryToUpdate = new UpdateCountry
{
Id = country.Id,
Description = country.Description
};
}
}
Здесь использована DTO
UpdateCountry, вот код:namespace CountryWiki.Web.Models;
public class UpdateCountry
{
public int Id { get; set; }
[Required, StringLength(200, MinimumLength = 10)]
public string Description { get; set; }
}
Мы пометили с помощью атрибута, что свойство
Description для прохождения валидации должно быть заполнено и иметь длину минимум в 10, а максимум в 200 символов.
-
Или книгу Эндрю Лока ↩
-
Реализация
ICountryServicesиICountryFileUploadValidatorServiceописана здесь ↩ -
Это пример шаблона “POST-REDIRECT-GET” ↩
Последнее обновление :
17 июня 2023 г.
Дата создания : 17 июня 2023 г.
Дата создания : 17 июня 2023 г.