Как обновить содержимое страницы после отправки сообщения на ASP.NET Основные страницы Razor?

#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. Но этот подход работает с одностраничным приложением, он использует традиционный многостраничный сайт, поэтому он не будет работать.