How to properly load heavy collection? - c#

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:
Home
Analysis
Performance
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")

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).

Related

Starting an Elsa workflow from a Workflow model in a controller

I am planning to use Elsa workflow for an enterprise application with acceptance flow (State machine). I am not sure about this process:
I have multiple workflow models designed by admin user. Each is related to a different entity. Now in the corresponding api controller I want to start a workflow for the entity.
The question is which libraries to inject to controller and use to create and instance and get the created instance id?
i.e.
public async Task<IActionResult> StartFlowOfSomeEntity(int entityID)
{
//Is it the right way to load registry?
var registeryList = await _workflowRegistry.ListAsync(CancellationToken.None);
var relatedRegistery = registeryList.FirstOrDefault(uu => uu.Name == "MyFlowName");
//Now need to create new instance?
}
I am using UpdateInputAsync of WorkflowStorageService to apply outputs to the workflow instance.
I tried to use IWorkflowInstanceStore as sugested here but could not achieve the goal
WorkflowRegistery is the right way to load models. But I prefer to use another method of it:
var relatedRegistery = await _workflowRegistry.FindByNameAsync("MyFlowName", VersionOptions.Latest);
For creating workflow instances you should inject WorkflowStarter to your controller and then do this:
RunWorkflowResult workflowInstance = await _wfStarter.StartWorkflowAsync(relatedRegistery , null, null, null, entityID.ToString());
Then workflowInstance.WorkflowInstance.Id has the value of the instance id you want.
You can also see this code at github doing similar thing with Elsa.

Associate class instance to Session in asp.net core

I'm currently working on a webserver in asp.net core.
I want the server to process the users input and data and am looking for a good solution to save complex Objects for the runtime.
So my first approach was to use Sessions. In Asp.net, sessions used to work like Session["key"] = new ValueObject()
In asp.net core however you can only use the methods SetString, SetInt32 and Set for byte arrays. I found a lot of solutions which basically converted the objects into Json strings. However in my case this isn't possible due to the objects containing other object references and more.
My second idea was to create a list of objects with the SessionId as identifier. Problem with this is that every time I would make request to the server, it needs to go through all existing Sessions to find the matching one, so this would probably drastically increase the time for the request.
So my question is what would be the best way to save user related objects?
Is using Sessions even the best way for solving this problem or am I missing something?
Note: Request are handled by JQuery AJAX, so reloading the page for accessing data is not an option.
You could try using the MemoryCache that can hold any .net type. It is not a problem but given it is a shared structure, it will be shared to all users, so, you have to carefull manage it. To do it, you could use HttpContext.Session.Id to define the keys on the memory cache instance. For sample (pseudo-code I didn't test):
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
public async Task<IActionResult> CacheGetOrCreateAsynchronous()
{
string cacheKey = $"{HttpContext.Session.Id}_data";
var cacheEntry = await
_cache.GetOrCreateAsync(cacheKey , entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Task.FromResult(DateTime.Now);
});
return View("Cache", cacheEntry);
}
}

ASP.NET Core MVC - Adding element permanently to Dictionary in Action

I'm working on a ASP.NET Core MVC web app. I have a Model that includes a Dictionary. In one Action I'm adding a new element to it. Then I have other actions supposed to use the object from the Dictionary that was just added. But as it turns out - the dictionary is empty after the first action finished executing. Is there a way to fix it, so that the object is added permanently to the dictionary?
Update:
Well, the object I need to store is basically a virtual medical slide with a Deep Zoom tile generator. The flow is as follows: user click on the link to open the slide -> the ViewSlide Action creates the slide object -> then the OpenSeadragon viewer on the corresponding view sends requests to get XML metadata and JPEG tiles (256x256) on various Deep Zoom levels (based on mouse cursor position). So there's going to be a lot of requests for the tiles and I'm looking for a way to optimize the time needed to create them.
Here's a code snippet of the said actions:
[Route("[controller]/{slug}")]
public IActionResult ViewSlide(string slug)
{
try
{
var currentSlide = slideSet.Get(slug);
return View(currentSlide);
}
catch (Exception)
{
return RedirectToAction("Index");
}
}
public Slide Get(string slideUrl)
{
if (Slides.ContainsKey(slideUrl))
return Slides[slideUrl];
var pathToSlide = FilePaths[slideUrl];
Slides[slideUrl] = new Slide(pathToSlide);
return Slides[slideUrl];
}
[Produces("application/xml")]
[Route("[controller]/{slug}.dzi")]
public string Dzi(string slug)
{
try
{
return slideSet.Get(slug).DeepZoomGenerator.GetDziMetadataString(DEEPZOOM_FORMAT);
}
catch (Exception)
{
RedirectToAction("Index");
return "";
}
}
If you want to add the item permanently you can store it in:
Session (will not work in a web farm)
Cookie
Database
File
Here is how to store it in session:
// Place something in session
System.Web.HttpContext.Current.Session["whatever"] = value;
// Read from session
var whatever = System.Web.HttpContext.Current.Session["whatever"];
MVC also provides TempData which is basically a session which lives during the lifecycle of the trip on the server.
Depending on how you want to use this data, you have different options:
You can store it in the Session, Cookie, or TempData, if it's tied to the client, and no one else will need it. How long do you want to store the data? Cookies can be cleared, and you don't want to hold too much data in the Session either for a long time.
If the data does not belong to specific users, you can use a repository (e.g. singleton dictionary / database / HttpCache), but the first two needs to be cleaned regularly, while the HttpCache is not guaranteed to hold the data until it's requested.
And you could also rethink this concept, and stay stateless. This also makes it easier to scale your application horizontally, as well as adding HTTP cache, or even reverse proxy.
So basically it depends on what kind of data would you like to persist between action calls.

Should I use WebApi for this? ASP.net MVC

I have a question about MVC asp.net
I have a link that provides a data using HTTP in this XML format
[...]
<Item>
<Name>Money</Name>
<Unit>1000</Unit>
</Item>
[...]
If I want this to display data on view in my application - what should I use? WebAPI?
The data ( < unit > ) change every few minutes, so always view have to display the current data.
Data are also possible to get in JSON format.
To clear up the confusion from all existing answers and comments: your actual problem statement is this:
I have a third-party URL that when requested, gives you some JSON which I wish to display in a table on an MVC view.
This is very trivial. See Deserializing JSON into an object to generate classes to deserialize the JSON. This provides you with a statically typed class that you can use from code.
Then you define a view model to hold a list of items:
public class JsonViewModel
{
public List<JsonItem> Items { get; set; }
}
public class JsonItem
{
public string Name { get; set; }
public string Unit { get; set; }
}
And in the controller you perform an HTTP GET request to retrieve the JSON (HTTP GET request and XML answer), parse it, map it to your view model and return it to your view:
public class FooController : Controller
{
public ActionResult Index()
{
// 1. Perform HTTP request to retrieve the JSON.
var webClient = new WebClient();
string rawJson = webClient.DownloadString("json-url");
// 2. Parse the JSON.
var jsonRootObject = JsonConvert.DeserializeObject<JsonRootObject>(rawJson);
// 3. Map to your viewmodel
var viewModel = new JsonViewModel
{
Items = jsonRootObject.Items.Select(i => new JsonItem
{
Name = i.Name,
Unit = i.Unit
}).ToList()
};
// 4. Return the model to your view
return View(viewModel);
}
}
Then finally you render the model in your view:
#model JsonViewModel
<table>
<tr>
<th>Name</th><th>Unit</th></tr>
</tr>
#foreach (var item in Model.Items)
{
<tr>
<td>#item.Name</td><td>#item.Unit</td>
</tr>
}
</table>
In your controller, call the external service to get the data. Can be XML, but JSON is more lightweight; I'd go for that. Parse the data into a view model that you then pass to the view. The view model contains the parsed information in the format that is best suited for your view; this will make sure your view can be kept as simple as possible (focusing on view logic).
To keep your controllers light, you might want to decide to move the retrieval and parsing logic in dedicated components, and use those from within your controller.
Basically, what to use - JSON or View - is fully up to you and depends on your knowledges.
You can reach what you need here using JSON(or even XML) and js code in the page, or partial view and less js code.
I'd suggest you to return partial view from your MVC controller and update whole block.
You can simply use a periodic ajax call using setInterval to the 3rd party endpoint and then update your view accordingly.
A better approach would be to use MVVM so you just need to update the view model and the framework will do the rest.
If you want to decouple your site from the 3rd party or you just want to hide that 3rd party address, You can use Web Api to call the 3rd party endpoint and call the web api endpoint from the client.
You will need to handle CORS this way, if the web api is in a different domain.
You can also use mvc action instead of the web api but if you're planning on building large scale application. It's better that you start using SOA Architecture.
I prefer Json instead of Xml because it is lightweight, so... you can go through three different ways:
Create a method that returns a JsonResult in your Controller (that is what i think you are doing) inside your asp.net mvc application;
Create a WebApiController inside your asp.net mvc application just to have the web api stuff without creating a new application;
Create a Web Api application and return your data (if you need only one service, i don't think you should create a new app for that, it is too much work)
If you choose the third option and you are not familliar with Web Api, i will recommend you to read this.

How to clear Session after View Rendering

This initially looked like a game, but bit after bit I ended up carrying this issue for quite a long time. Here is my situation. I fire Notifications from my Domain Model.
These notifications are just objects containing a title and a description that I store in a collection in order to render them at the top of the pages of my website. However I'm having trouble to find the appropriate "session" mechanism with MVC.
I started by using HttpContext.Items to store all my session data, but I found out that it wasn't suitable for Redirection Scenarios - when I redirect the user to an other Action Method. Indeed a new HttpContext is created and the Items object is lost.
Consequently I tried storing my session stuff in HttpContext.Session but the issue I have now is that there is no appropriate time to clear the Session (I don't want to carry on the notifications from one request to the other). OnActionExecuted and OnResultExecuted seem to run before the View is Rendered.
Here is how I display the notifications in my Layout page :
#foreach(var notification in ISession.Notifications)
{
#Html.Partial("_NotificationPartial", new Mvc.Models.NotificationViewModel(notification))
}
ISession is mapped to a store (HttpContext.Items / HttpContext.Session) in my IOC container.
Do you have any workaround idea ?
Try using the TempDataDictionary. It is included on the Controller base class as the TempData property. It is meant to persist data from one request to another. It is then cleared out automatically.
In the action method:
TempData["Notifications"] = new List<Notification>()
In the view:
#{
if(TempData["Notifications"] != null)
{
var notifications = TempData["Notifications"] as List<Notification>
}
}

Categories

Resources