#c# #asp.net #asp.net-core #razor-pages
#c# #asp.net #asp.net-core #страницы razor
Вопрос:
Я использую ASP Net Core, и я создал проект с Razor Pages.
У меня есть страница, на которой перечислены продукты, связанные с текущим пользователем, и он может добавить новый продукт.
После того, как OnPost
вызван метод, я хочу обновить страницу, чтобы на ней отображались все продукты пользователя без повторного запроса базы данных
Ниже приведено мое текущее решение:
public async Task OnGet()
{
var currentUser = await GetCurrentUser();
if (currentUser != null)
Products = await _dbContext.Products.Where(x => x.AtpUserId == currentUser.Id).ToListAsync();
}
public async Task OnPost(Product product)
{
var currentUser = await GetCurrentUser();
if (currentUser != null)
{
product.UserId = currentUser.Id;
product.Currency = "USD";
await _dbContext.Products.AddAsync(product);
await _dbContext.SaveChangesAsync();
Products = await _dbContext.Products.Where(x => x.UserId == currentUser.Id).ToListAsync(); // can I avoid this ?
}
}
Я не понимаю, как добиться того же результата без последней строки, которая перезагружает данные из базы данных.
- Я просмотрел несколько примеров, но все примеры со страниц scaffold используют промежуточную
Create
страницу, а затем перенаправляют наIndex
страницу. Я хочу, чтобы пользователь мог создавать с индексной страницы - Я попытался заменить последнюю строку
OnPost
метода вызовомRedirectToPage("Index");
, но методOnGet
больше не вызывается, и таблица, содержащая продукты, пуста при обновлении. Почему?
Комментарии:
1. Если
RedirectToPage("Index")
сработает,OnGet()
будет вызываться по очереди, который также выполнит запрос к базе данных, так что это все равно не будет делать то, что вы ищете. Однако вы можете заставить его работать сRedirectToPage("./Index")
2. Ну, я попробовал и, что удивительно
RedirectToPage("./Index")
, также отображает страницу без загруженных данных. Почему вы думали, что это загрузит данные вместоRedirectToPage("Index")
3. В некоторых примерах Microsoft они используют
RedirectToPage("./Index")
, поэтому я подумал, что вы могли бы попробовать.4. На самом деле, чтобы заставить это работать, вам нужно
return RedirectToPage("./Index");
(обратитеreturn
внимание) и изменить подписьOnPost
наpublic async Task<IActionResult> OnPost(Product product)
5. @Christian действительно возвращает
Task<IActionResult>
делает трюк
Ответ №1:
Во-первых, это отличный вопрос, спасибо, что задали!
То, чего вы пытаетесь достичь, не может быть сделано с помощью перенаправления. База данных будет вызвана еще раз, и это неизбежно. Но есть другой подход, который я бы рекомендовал, если вы действительно не хотите запрашивать данные из базы данных дважды. Давайте рассмотрим это ниже:
Вам нужно будет передать Products
данные обратно в модель при OnPost
вызове. Это означает, что вы должны связать эти данные, чтобы они передавались вместе с данными формы в модель при отправке post.
Сначала отметьте свойство products в модели страницы Razor с помощью [BindProperty]
, а затем добавьте <input type="hidden" value="@Model.Products" />
в свой HTML. Это отправит ваши Products
данные вместе с отправкой формы.
Теперь в вашем OnPost
Products будет иметь значение, потому что оно будет привязано. И ко времени OnPost
возврата Page
@Model.Products
будут иметь те же значения, которые были восстановлены с исходного OnGet
. Я делал это раньше, и это сработало, поэтому я могу заверить вас, что это сработает.
Вот несколько примеров кода:
public class SomePageModel{
[BindProperty]
public IList<Product> Products {get; set;}
[BindProperty]
public Product Product {get; set;}
public async Task OnGet()
{
var currentUser = await GetCurrentUser();
if (currentUser != null)
Products = await _dbContext.Products.Where(x => x.AtpUserId == currentUser.Id).ToListAsync();
}
public async Task OnPost()
{
var currentUser = await GetCurrentUser();
if (currentUser != null)
{
Product.UserId = currentUser.Id;
Product.Currency = "USD";
await _dbContext.Products.AddAsync(Product);
await _dbContext.SaveChangesAsync();
}
}
}
Для работы привязки вам необходимо передать данные обратно в модель, поэтому включите для этого скрытый <input />
:
<form method="POST">
<input type="hidden" asp-for="Products" value="@Newtonsoft.Json.JsonConvert.SerializeObject(Model.Products)" />
</form>
Затем в вашем OnPost()
просто десериализуйте его обратно в List<Products>
Products = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Product>>(FormInput.Products)
Другой подход, который я бы использовал, заключается в следующем:
Добавьте метод в модель страницы, который называется GetProductsAsync(User currentUser)
следующим образом:
public async Task<List<Product>> GetProductsAsync(User currentUser)
{
return await _dbContext.Products.Where(x => x.AtpUserId == currentUser.Id).ToListAsync();
}
Затем в вашем razor page .cshtml вызовите этот метод, чтобы получить список продуктов:
var products = await Model.GetProductsAsync(Model.currentUser) // This will run everytime the page loads
Тогда ваш код модели будет выглядеть следующим образом:
public User currentUser { get; set; }
public async Task<List<Product>> GetProductsAsync(User currentUser)
{
return await _dbContext.Products.Where(x => x.AtpUserId == currentUser.Id).ToListAsync();
}
public async Task OnGet()
{
var currentUser = await GetCurrentUser();
}
public async Task OnPost(Product product)
{
var currentUser = await GetCurrentUser();
if (currentUser != null)
{
product.UserId = currentUser.Id;
product.Currency = "USD";
await _dbContext.Products.AddAsync(product);
await _dbContext.SaveChangesAsync();
}
}
Комментарии:
1. Интересно, я не знал, что это возможно. Возможно, вы забыли добавить продукт в
Products
свойство, потому что, если MVC действительно может десериализовать то, что находится в скрытом вводе, обратно вProducts
, он все еще содержит продукты без добавленного продукта. Также в вашем примере кода вы написали,asp-for="Products"
но, вероятно, имели в видуvalue="@Model.Products"
то, что вы написали выше.2. Помните, что продукт будет десериализован обратно в объекты C #, поэтому потребуется добавить его к существующему
Products
использованиюProducts.Add(Product)
.3. Идея также в том, чтобы он попробовал это. Я делал это раньше, но не со списками… Но я не думаю, что это будет иметь большое значение, давайте дадим ему попробовать это и посмотрим, что он получит
4. Спасибо @MosiaThabo Я попробовал ваш подход (с
value=@Model.Products"
), но скрытое поле содержит толькоToString()
представление списка<input type="hidden" value="System.Collections.Generic.List1[MyProject.Models.Product]" id="Products" name="Products">
, поэтому оно не работает «легко», как это. Однако я нашел это объяснение того, как связать сложные коллекции , которые могут помочь5. Да, вам нужно сериализовать ваши продукты в jsonString, а затем передать его в value. Вы можете использовать Newtonsoft для сериализации своих продуктов.
Ответ №2:
По моему опыту, лучший способ управлять списками элементов на веб-странице — это управлять ими в клиентском коде и просто запрашивать базу данных, когда мои пользователи отправляют.
Я попытаюсь подробнее:
Если у вас есть страница, на которой отображается список элементов, и ваши пользователи могут добавлять, изменять или удалять элементы оттуда, лучший способ для этого (ИМХО) следующий:
-Загрузите страницу с текущей информацией, полученной из базы данных (вы можете сохранить представление этой информации в своем клиентском коде, скажем, например, в объекте JSON).
-Позвольте вашему пользователю управлять информацией на странице. Когда он добавляет новый элемент, вы обновляете информацию о своем объекте JSON и отражаете ее на экране, но не вступаете в контакт с базой данных, пока не будут отправлены изменения.
— Вы делаете то же самое с обновлениями или удалениями, всегда сохраняя актуальную информацию в вашем основном объекте JSON, но без вызова сервера для чего-либо.
-Когда операция завершена (и это может означать много разных вещей в зависимости от вашей бизнес-модели), вы получаете обновленный объект JSON с текущим полным списком элементов и отправляете его на сервер, чтобы применить изменения к уникальной транзакции базы данных.
Это генерирует меньше сетевого трафика, меньше операций с базой данных, быстрее, надежнее, обеспечивает более простой способ обновления представлений и может почти полностью управляться с помощью клиентского кода.
Это не может применяться к определенным сценариям, например, если ваша коллекция элементов должна быть изменена разными пользователями одновременно или внешними событиями из текущего взаимодействия с пользователем, но это чистый и эффективный способ управлять многими простыми пользовательскими взаимодействиями, которые мы используем, чтобы излишне усложнять.
Комментарии:
1. Я понимаю ваш совет, и он имеет смысл. Я задал этот вопрос, потому что я пытаюсь выполнить свой проект максимально простым способом, используя ASP.NET Ядро по умолчанию. Конечно, это неоптимально и генерирует больше сетевого трафика и нагрузки на сервер, но, по крайней мере, я могу избежать изучения Javascript (снова) и заблудиться в сотнях фреймворков. Вы можете спросить, почему я забочусь о выполнении еще одного вызова базы данных, и дело в том, что мое первоначальное решение казалось слишком «сырым», мне не понравилось дублировать код метода Get и показалось странным, что метод перенаправления выдавал мне пустую страницу.
2. Но этот подход работает с одностраничным приложением, он использует традиционный многостраничный сайт, поэтому он не будет работать.