How to refresh the page content after a Post submission on ASP.NET Core Razor Pages?

Viewed 10520

I am using ASP Net Core, and I created a project with Razor Pages.

I have a page which lists products associated with the current user, and he can add a new product.

After the OnPost method is called, I want to refresh the page so that it displays all the products of the user without requesting the database again

Below is my current solution:

    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 ?
        }
    }

I don't find how I achieve the same result without the last line which reloads the data from the database.

  • I looked at some examples but examples from the scaffold pages all use an intermediate Create page, and then redirect to the Index page. I want the user to be able to create from the Index page
  • I tried to replace the last line of the OnPost method by a call to RedirectToPage("Index"); but the method OnGet is not called again and the table containing the products is empty on refresh. Why?
2 Answers

Firstly, That's a great question, Thanks for asking!

What you are trying to achieve cannot be done through redirecting. The database will be called once again and that's unavoidable. But there's another approach, which is what I'd recommend if you really don't want to request data from the database twice. Let's explore it below:

You will need to pass the Products data back to the model when OnPost is called. This means that you must bind that data so that it's passed with form data to the model on post submission.

First, mark your products property in your Razor Page Model with [BindProperty], and then add an <input type="hidden" value="@Model.Products" /> in your HTML. This will submit your Products data with form submission.

Now in your OnPost, Products will have a value because it will be binded. And by the Time OnPost returns Page, @Model.Products will have same values that were retrived from the initial OnGet. I have done this before, and it's worked, so I can assure you that it's going to work.

Here's some sample code:

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();
      }
  }
}

For Binding to work, you need to pass the data back to the Model, so include a hidden <input /> for it:

<form method="POST">
  <input type="hidden" asp-for="Products" value="@Newtonsoft.Json.JsonConvert.SerializeObject(Model.Products)" />
</form>

Then in your OnPost(), Just de-serialize it back to List<Products>

Products = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Product>>(FormInput.Products)

Another Approach I would use is this:

Add a method in page model called GetProductsAsync(User currentUser) like this:

public async Task<List<Product>> GetProductsAsync(User currentUser)
{
  return await _dbContext.Products.Where(x => x.AtpUserId == currentUser.Id).ToListAsync();
}

Then in your razor page .cshtml, call that method to get the list of products:

var products = await Model.GetProductsAsync(Model.currentUser) // This will run everytime the page loads

Then your model code would be like this:

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();
    }
}

In my experience, the best way to manage lists of elements on a webpage, is to manage them on client code, and just query the database when my users submit.

I'll try to elaborate further:

If you have a page where a list of elements is shown, and your users can add, modify or erase elements from there, the best way to to this (IMHO) is the following:

-Load the page with current info obtained from database (you can store a representation of that info in your client code, lets say, for example, on a JSON object).

-Let your user manage the info on the page. When he adds a new element, you update the info on your JSON object and reflect it on the screen, but don't get in contact with the database until changes are submitted.

-You make the same with updates or deletes, always maintaining the actual info in your main JSON object, but without calling the server for anything.

-When the operation is finished (and this can mean a lot of different things depending on your business model) you get your updated JSON object, with current complete list of elements, and send it to the server to apply changes on a unique database transaction.

This generates less network traffic, less database operations, is faster, more reliable, allow an easier way to update the views, and can be almost completely managed through client code.

It cannot apply to certain scenarios, for example if your collection of elements need to be modified by different users at the same time, or by external events out of the current user interaction, but is a clean and efficient way to manage many simple user interactions which we use to make too complex on an unnecesary way.

Related