Blasteroid
Blasteroid

Reputation: 37

Repeating ViewComponent data

I have a customer view model which has drop down lists for selecting country, area and language. I am using ViewComponent to load the necessary data for the dropdowns and it's working like a charm. My problem is that when they are multiple client models on the page I am making multiple calls to the external API to receive the same data. I tried to put the Component.InvokeAsync part inside a cache tag helper but it also keeps the elements naming from the first call and messes up the model binding. Is there a way to avoid the multiple calls for same data?

Here is what the code looks like. It's nothing special. The views just bind the properties and there isn't anything special there.

[ViewComponent(Name = "LocationSelector")]
public class LocationSelector : ViewComponent
{
    private readonly ILocationService locationsService;

    public LocationSelector(ILocationService locationService)
    {
        this.locationsService = locationService;
    }

    public async Task<IViewComponentResult> InvokeAsync()
    {
        var locationManager = new LocationSelectorViewModel();
        locationManager.Areas = await this.locationsService.GetAreas();
        locationManager.Countries = await this.locationsService.GetCountries();
        return View("Default", locationManager);
    }
}

And the component is called inside the customer model like this. The problem is that I may have multiple customers in my page and they all will make requests to the service for the exact same data.

@await Component.InvokeAsync(nameof(LocationSelector))

Upvotes: 3

Views: 790

Answers (1)

Shyju
Shyju

Reputation: 218732

You should consider caching the data. You can use the IMemoryCache imeplemenation. I prefer to create my own abstraction layer which i would use it wherever i need.

public interface ICache
{
    T Get<T>(string key, Func<T> updateExpression = null, int cacheDuration=0);       
}
public class InMemoryCache : ICache
{
    readonly IMemoryCache memoryCache;
    public InMemoryCache(IMemoryCache memoryCache)
    {
        this.memoryCache = memoryCache;
    }
    public T Get<T>(string key, Func<T> updateExpression = null,int cacheDuration=0)
    {
        object result;
        if (memoryCache.TryGetValue(key,out result))
        {
            return (T) result;
        }
        else
        {
            if (updateExpression == null)
            {
                return default(T);
            }
            var data = updateExpression();
            if (data != null)
            {
                var options = new MemoryCacheEntryOptions
                {
                    AbsoluteExpiration = DateTime.Now.AddSeconds(cacheDuration)
                };
                this.memoryCache.Set(key, data, options);
                return data;
            }
            return default(T);
        }
    }
}

Now in your startup class's ConfigureServices method, enable caching and define our custom ICache-InMemoryCache mapping.

public void ConfigureServices(IServiceCollection services)
{
   services.AddTransient<ICache, InMemoryCache>();
   services.AddMemoryCache();
}

Now you can inject ICache to any class and use it to get/store data to it.

public class LocationSelector : ViewComponent
{
    private readonly ILocationService locationsService;
    private readonly  ICache cache;
    public LocationSelector(ILocationService locationService,ICache cache)
    {
        this.locationsService = locationService;
        this.cache = cache;
    }

    public async Task<IViewComponentResult> InvokeAsync()
    {
        var locationManager = new LocationSelectorViewModel();

        var areas = await this.cache.Get(CacheKey.Statuses, () =>
                                                   this.locationsService.GetAreas(),360);
        locationManager.Areas = areas;

        return View("Default", locationManager);
    }
}

Upvotes: 2

Related Questions