Charanoglu
Charanoglu

Reputation: 1339

How to properly load heavy collection?

I'm learning ASP.NET Core and I have some doubts about the loading of an heavy collection of records, let me explain better.

What I'm trying to do

In my application, after the login execution, the user will redirected to the Dashboard Home View. The Dashboard is the place that contains all the functions for an user. The Dashboard Controller have also other Views like:

Now each View need to display to the user a Table which contains a list of Products, at the bottom of this Table there is the content of the View.

Problem

First problem: is the Table redundancy code, which I solved creating a _PartialView that contains the html of the Table that contains the products to display. Coming from c# + WPF I used the same logic of UserControl, so this is a good solution for me.

Second problem: the products to display inside the Table, these products are downloaded from an API, now as I said before, these records must be always displayed in the products Table (which is available in different View using the _PartialView). Imagine that every time the user click on a Dashboard item (which load a Dashboard View), the Dashboard Controller will call this method:

 public async Task<List<Products>> GetProducts(string date)
 {
    var client = new RestClient(Url);

    var request = new RestRequest("product/get_products/{date}", Method.GET);
    request.AddUrlSegment("date", date);

    var cancellationTokenSource = new CancellationTokenSource();
    var response = await client.ExecuteTaskAsync(request, cancellationTokenSource.Token);

    List<Products> products =  JsonConvert.DeserializeObject<List<Product>>(response.Content);
    return products;
 }

For me, this is not a really good practice because each time the _PartialView will call this method and reload the data, so I need to store somehow this data (a temp store). How can I store these records to the user session without reload each time the _PartialView being called?

Between, I have some doubts about the API method: Should I place all the API calls inside the Service folder? Repository folder? Or Controller folder?

Folder tree

View <- Folder
    Dashboard <- Folder
        Home
        Analysis 
        Performance
       _ProductsTable

The View Home, Analysis, Performance load _ProductsTable in the following way:

@await Html.PartialAsync("_LeftSidebar")

Upvotes: 1

Views: 115

Answers (1)

Chris Pratt
Chris Pratt

Reputation: 239400

Use view components. They're essentially self-contained modules of functionality that return views, which you can embed in other views, without main view or action having to know about any of it.

First, create a directory call ViewComponents. Inside add new class, like ProductsViewComponent. Then, you'll want something like:

public class ProductsViewComponent : ViewComponent
{
    private readonly HttpClient _client;

    public ProductsViewComponent(HttpClient client)
    {
        _client = client ?? throw new ArgumentNullException(nameof(client));
    }

    public async Task<IViewComponentResult> InvokeAsync(string date)
    {
       using (var response = await _client.GetAsync($"/"product/get_products/{date}"))
       {
           response.EnsureSuccessStatusCode();
           var products = await response.Content.ReadAsAsync<List<Product>>();
           return View(products);
       }
    }
}

Then, create the view, Views\Shared\Components\Products\Default.cshtml. Inside, add the HTML to render your list of products. Finally, where you want the product table to appear add:

@await Component.InvokeAsync("Products", new { date = myDate })

The above code uses HttpClient rather than RestClient, since honestly, it's completely unnecessary at this point to have a separate library for making HTTP calls. HttpClient is built-in and has been extended with functionality in Core to make this much easier, such as the ReadAsAsync method used above, which transparently deserializes your JSON response into the generic type argument. Additionally, you now have things like IHttpClientFactory which ensures that you have properly scoped HttpClient instances. As a result, the above code also assumes adding something like the following to your Startup.cs:

services.AddHttpClient<ProductsViewComponent>(c =>
{
    c.BaseAddress = new Uri('https://api.myservice.com');
    // add default headers and such if you need them
});

You can also then use the Polly integration to setup automatic retries, circuit breakers, etc., allowing you handle all sorts of API scenarios such as temporarily unavailable, rate limits, etc. See the full documentation for both IHttpClientFactory and its Polly integration for more info.

Lastly, if this is a scenario where you don't need realtime data, you can also inject an instance of IDistributedCache into your view component and add logic to set the result of your API call in that, and retrieve it from there first, before making the call again, allowing you to significantly reduce the load both on your app and the API (especially if do have something where rate limits apply).

Upvotes: 1

Related Questions