Blazor preload data even though the data is not completed - asynchronous

Is there a way to show the user the data that has been processed in real time?
I mean: suppose this scenario: I created a default Blazor app to use it as example. Go to the default page FetchData, it uses as model the following:
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
The page uses this mock service as default:
public class WeatherForecastService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public Task<WeatherForecast[]> GetForecastAsync(DateOnly startDate)
{
return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray());
}
}
Here's the page where the service is used:
#page "/fetchdata"
#using BlazorApp1.Data
#inject WeatherForecastService ForecastService
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
#if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
#foreach (var forecast in forecasts)
{
<tr>
<td>#forecast.Date.ToShortDateString()</td>
<td>#forecast.TemperatureC</td>
<td>#forecast.TemperatureF</td>
<td>#forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
#code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now));
}
}
Okay so now suppose that the service instead of using 5 items we put a high number such as 2000000 by example.
public Task<WeatherForecast[]> GetForecastAsync(DateOnly startDate)
{
return Task.FromResult(Enumerable.Range(1, 20000000).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray());
}
When we reload the page it wont render until all the weather forecasts are loaded.
What is the best way to render the data in real time to show the user the page even though the service is working backwards?

If you'd like to receive each part of the data as soon as it arrives, rather than waiting for the complete data to be available, you can use AsAsyncEnumerable. You wouldn't like to block the CPU while waiting for the chunks of data. This is where IAsyncEnumerable can help you.
You can use AsAsyncEnumerable in your query to achieve the better performance, such as bellow:
public IAsyncEnumerable<WeatherForecast> GetForecastAsync()
{
return _context.WeathreForecasts
.AsAsyncEnumerable();
}
The photo below shows this:
General async method (e.g. .ToListAsync()):
AsAsyncEnumerable method:
For more info you can refer to IAsyncEnumerable with yield in C#

In your example, where you need to handle very large data sets in an expedite manner, the simplest way is to use the Virtualize component. This demonstrates several key principles in handling lists:
Only get the data you actually need.
Pass request objects into the data pipeline and get result objects back.
Use IEnumerable in the pipeline to minimize materialization.
The data pipeline needs restructuring to better resemble a real world example.
Break out the data provider.
public class WeatherForecastProvider
{
private IEnumerable<WeatherForecast> _records = Enumerable.Empty<WeatherForecast>();
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public WeatherForecastProvider()
=> GetForecasts();
public IEnumerable<WeatherForecast> WeatherForecasts
=> _records;
private void GetForecasts()
{
DateOnly startDate = DateOnly.FromDateTime(DateTime.Now);
_records = Enumerable.Range(1, 2000000).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
});
}
}
WeatherForecastService now looks like this. It's the data broker between the application and the data source.
public class WeatherForecastService
{
private WeatherForecastProvider _weatherForecastProvider;
public WeatherForecastService(WeatherForecastProvider weatherForecastProvider)
=> _weatherForecastProvider= weatherForecastProvider;
public async ValueTask<ItemsProviderResult<WeatherForecast>> GetForecastAsync(ItemsProviderRequest request)
{
// normally request will be async calling into a DbContext or API, so pretend it is
await Task.Delay(250);
var query = _weatherForecastProvider.WeatherForecasts;
var count = query.Count();
query = query
.Skip(request.StartIndex)
.Take(request.Count);
return new ItemsProviderResult<WeatherForecast>(query, count);
}
}
And then FetchData looks like this.
#page "/fetchdata"
#using SO75219173;
#inject WeatherForecastService ForecastService
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
<Virtualize Context="forecast" ItemsProvider=this.ForecastService.GetForecastAsync>
<tr>
<td>#forecast.Date.ToShortDateString()</td>
<td>#forecast.TemperatureC</td>
<td>#forecast.TemperatureF</td>
<td>#forecast.Summary</td>
</tr>
</Virtualize>
</tbody>
</table>
#code {
}
Note the use of IEnumerable throughout. The methods don't pass Lists or Arrays.
We do this to minimize what is known as Materialization. An IEnumerable is any object that can be enumerated.
In this code query:
query = query
.Skip(request.StartIndex)
.Take(request.Count);
Doesn't actually get enumerated (materialized) until the Virtualize does it's internal foreach loop. Put in some break points to check.
We do have to do one materialization: getting the record count to pass back in the ItemsProviderResult instance.
You can also use paging controls. See QuickGrid - https://aspnet.github.io/quickgridsamples/
Note: You should always use paging in any List request. Never get an unconstrained dataset from a data store. Could your code handle 2 million records gracefully? If you want to get the lot, such as for a select and know there should be say 30 records, set a realistic maximum page size, say 1000 records.

Related

ASP.NET MVC Conditional ViewModel Abstraction

I am new to ASP.NET MVC and I am stuck on a point. I am working on a classified site. My situation is, I have a lot of categories in which a user can post their ads and each ad category have different View. I have created a Controller Action like
public ActionResult PostAd(string CategoryName, string SubCategoryName)
{
if(categoryName == "Vehicle" && SubCategoryName == "Cars")
{
var model = new CarAdViewModel();
// set CarAdViewModel properties...
return View("CarAdCreateView", model);
}
else if(categoryName == "Vehicle" && SubCategoryName == "Bikes")
{
var model = new BikeAdViewModel();
// set BikeAdViewModel properties...
return View("BikeAdViewModel", model);
}
else if(categoryName == "Property" && SubCategoryName == "RentHouse")
{
var model = new RentHouseAdViewModel();
// set RentHouseAdViewModel properties...
return View("RentHouseAdViewModel", model);
}
else................... so on and so on
}
My problem is I have huge number of Categories and Sub Categories almost 60+. And if I keep on coding like above for 60+ categories and subcategories, my PostAd method is going to blast and become unmanageable.
Please tell me some best practice or pattern which can bring me out of this problem.
Unfortunately, some of what you are doing cannot be avoided. There needs to be some form of model and view selection based on category.
Use a factory pattern. Create a base class:
public abstract class BaseCategory
{
public abstract string GetViewName();
public abstract Object CreateModelFromFormData();
}
For each category, create a sub-class derived from BaseCategory and implement the abstract functions.
In your action, do the following:
public ActionResult PostAd(string categoryName, string subCategoryName)
{
BaseFactory factory;
if (categoryName == "Vehicle")
{
if (subCategoryName == "Cars")
{
factory = new CarsFactory();
}
else ...
}
else ...
return View(factory.GetViewName(), factory.CreateModelFromFormData());
}
I have a couple reasons for this schema:
I am purposefully using if/else for the factory selection. Your controller is going to be created and re-created for every action call. So pre-populating a list will constantly and needlessly create objects for categories that will not be selected. A simple if/else will be more efficient. If you want to prevent the if/else, you can put your factories in a Dictionary and select based on the categories, but that would be a lot of needless constructor actions.
I made the CreateModelFromFormData a function because I assume you'll need to copy data from the posted form data. This may require passing in data, but I left the function parameterless.
I used base/derived classes because the copying of the form data will probably need to be custom from the model being created and the form data being posted. Also, saving to persistent storage (file or database) may be category-specific as well.
It would be one of some possible solutions
public class PostAdData
{
public string CategoryName;
public string SubCategoryName;
public string ViewName;
public Type Model;
}
public class PostController : Controller
{
private readonly List<PostAdData> _theData;
public HomeController()
{
_theData = InitializeData();
}
public ActionResult PostAd(string categoryName, string subCategoryName)
{
var data = _theData.FirstOrDefault(c => c.CategoryName == categoryName && c.SubCategoryName == subCategoryName);
if (data != null)
{
var model = Activator.CreateInstance(data.Model);
return View(data.ViewName, model);
}
return View("Error");
}
[NonAction]
public List<PostAdData> InitializeData()
{
var result = new List<PostAdData>
{
new PostAdData
{
CategoryName = "Vehicle",
SubCategoryName = "Cars",
ViewName = "CarAdCreateView",
Model = typeof (CarAdViewModel)
}
};
return result;
}
}
You should make this data driven. You create a lookup table that has a compound primary key of category and subcategory. Then it has a table with View in it. Then you simply ad rows for each category/subcategory/view combination.
If you absolutely don't want a database, then you can use a simple hashset or dictionary.
var views = new Dictionary<Tuple<string,string>,string>();
views.Add(new Tuple<string,string>("Vehicle", "Cars"), "CarAdCreateView");
Then in your PostAd you just lookup the correct view.
What a beautiful solution on www.asp.net to my question, here is the link : http://forums.asp.net/t/1923868.aspx/1?ASP+NET+MVC+Conditional+ViewModel+Abstraction
Edit:
My code is :
public class AdsController : Controller
{
private readonly IAdService _adService;
public AdsController(IAdService adService)
{
_adService = adService;
}
public ActionResult PostAd(string Category, string SubCategory)
{
//Here I will call
var strategy = GetStrategy(CategoryName, SubCategoryName);
strategy.FillModel(_adService );
return View(strategy.ViewName, strategy.Model);
}
}

Spring MVC 3 — Bind properties of object list within model element

I have a JSP generated form that needs to present the user with a list of options. Some of these are single-instance options (such as whether or not to generate a table of contents), and that's working fine. I now need to add a list of subordinate options and allow the user to turn each one on or off. I can generate the list of options in the JSP page, and give each one a unique ID. I hope to represent one with a <form:checkbox/> element in the form.
The option class looks like this:
// Option class
public class PdfCatalogOptions
{
private boolean isTableOfContentsIncluded;
private List<ProductGroup> productGroups;
public boolean getTableOfContentsIncluded () {
return isTableOfContentsIncluded;
}
public PdfCatalogOptions setTableOfContentsIncluded ( final boolean isTableOfContentsIncluded ) {
this.isTableOfContentsIncluded = isTableOfContentsIncluded;
return this;
}
public List<ProductGroup> getProductGroups() {
return productGroups;
}
public PdfCatalogOptions setProductGroups( final List<ProductGroup> setProductGroups ) {
this.productGroups = productGroups;
return this;
}
}
The product group class looks like this:
public class ProductGroup
{
private String groupName;
private boolean isSelected;
public String getGroupName () {
return groupName;
}
public ProductGroup setGroupName ( final String groupName ) {
this.groupName = groupName;
return this;
}
public Boolean getIsSelected () {
return isSelected;
}
public ProductGroup setIsSelected ( final boolean selected ) {
isSelected = selected;
return this;
}
}
In the get handler within the controller, I do this:
#RequestMapping( method = RequestMethod.GET )
public String get ( final Model model ) throws Exception {
model.addAttribute ( "options", new PdfCatalogOptions ().setProductGroups ( buildProductGroupList () ) );
return FormName.GENERATE_CATALOG;
}
The logic within buildProductGroupList is of no consequence — it just generates an ArrayList of ProductGroup objects populated with the data I need.
The problem I'm having is that I can't seem to convince Spring to bind to the fields within the individual ProductGroup objects inside the PdfCatalogOptions object.
The JSP looks like this:
<form:form action="generateCatalog.do" commandName="options">
<table>
<tr>
<td colspan="3"><form:errors cssClass="error"/></td>
</tr>
<tr>
<td><spring:message code="catalog.include.toc"/></td>
<td><form:checkbox path="tableOfContentsIncluded"/></td>
</tr>
<tr>
<td> </td>
</tr>
<c:forEach items="options.productGroups" var="productGroup">
<tr>
<td> </td>
<td><form:checkbox path="productGroup.isSelected"/>blah</td>
</tr>
</c:forEach>
</table>
</form:form>
In the inner <c:forEach...> loop I cannot find the right incantation to get Spring to bind to the individual ProductGroup objects so I can end up with a list of ProductGroup objects with the isSelected properties set as required by the user.
As presented, it's complaining that:
org.springframework.beans.NotReadablePropertyException: Invalid property 'productGroup' of bean class [<redacted>.PdfCatalogOptions]: Bean property 'productGroup' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Can anyone point me in the right direction here?
EDIT 1: This correctly lists all product groups:
<c:forEach items="${options.productGroups}" var="pg">
<tr>
<td>${pg.groupName}</td>
</tr>
</c:forEach>
The values are there and display as I would expect. What I can't do, however, is find a way to bind the value path to the checkbox. If I add <form:checkbox path="pg.isSelected"/> then I'm told that pg is not a property of PdfCatalogOptions.
If I add <form:checkbox path="${pg.isSelected}"/> then I'm told — somewhat predictably — that true is not a property of PdfCatalogOptions.

Import two or multiple class models to a single controller on ASP.NET

I'm very new to ASP.NET, but I know a little programming in Java. I want to use a ZIP code to query a database which will return a string, then use that string to query another database. I wanted to do this on the same control model. I thought it would be easy, and it sounds pretty easy.
When I created the controller, I put the model class of the first database, and, so far, I've gotten as far as querying the first database, but now that I have the string I want to query a second database through the DBEntities.
This displays an error saying:
> The model item passed into the dictionary is of type
> 'System.Collections.Generic.List`1[FinalBallot.Models.AgainCandidate]',
> but this dictionary requires a model item of type
> 'System.Collections.Generic.IEnumerable`1[FinalBallot.Models.ZipTable]'.
Is there a way to solve this in an easy way?
public class Default1Controller : Controller
{
private CandidatesDBEntities db = new CandidatesDBEntities();
public string districString = "";
//
// GET: /Default1/
public ViewResult Index(string searchString)
{
var queryZip = from s in db.ZipTables select s;
var queryCandidates = from s1 in db.AgainCandidates select s1;
double sT = 0;
//method so it doesnt display the whole db
if (String.IsNullOrEmpty(searchString))
{
queryZip = queryZip.Where(s => s.ZipL.Equals(0));
}
if (!String.IsNullOrEmpty(searchString))
{
sT = double.Parse(searchString);
queryZip = queryZip.Where(s => s.ZipL.Equals(sT));
try
{
districString = queryZip.ToList().ElementAt(0).District;
}
catch
{
}
if (!String.IsNullOrEmpty(districString))
{
queryCandidates = queryCandidates.Where(s1 => s1.District.Equals(districString));
}
}
return View(queryCandidates.ToList());
}
In your view, did you specify the model to be IEnumerable<ZipTable>? The model that you're passing to your view is IEnumerable<AgainCandidate>, so you would get an error if you specified your model as something else. You'd need to change the model in your view to be IEnumerable<AgainCandidate>.
UPDATE:
Based on your revised explanation, you can do a couple things:
1) create a "ViewModel" that has two properties for each of your collections you want to display on the page like so:
public class MyViewModel
{
IEnumerable<ZipTable> Zips { get; set; }
IEnumerable<AgainCandidate> Candidates { get; set; }
}
Instantiate that in your action method and return that as your model. This would be my preferred approach.
2) Stash your two collections in the ViewData bag in your action method:
ViewData["Zips"] = queryZip.ToList();
ViewData["Candidates"] = queryCandidates.ToList();
return View(ViewData);
You can pull this data in your view like this:
#foreach (var zip in ViewData["Zips"] as IEnumerable<ZipTable>)
{
...
}

Deciding content of html tag based on a conditional in mvc3 asp.net

This sounds really basic yet I couldn't find the answer.
I pass a Message struct to the View and I want to display it.
If the Message.Category field is "Technical" I want to display "Technical Problem" else just display it as it is.
How do I make the view understand that Technical Problem isn't a statement but html text I want to display?
My code:
<span class="cright" id="cat">
#{
if (String.Compare(ViewBag.Message.Category, "Technical") == 0)
{
Technical Problem <----THIS
}
else #ViewBag.Message.Category
}
</span>
More info:
I'm working on a messaging system. Users create a message and as it is being sent they can view it. The category is compulsory (Question, Suggestion or Technical Problem) and to avoid redundancy in the database I truncate the last option to just 'Technical', however when the users view their sent message I want it to show up in full.
Thanks everyone; from all your answers I arrived at:
<span class="cright" id="cat">
#if (ViewBag.Message.Category == "Technical ")
{<text>Technical Problem</text>}
else
{<text>#ViewBag.Message.Category</text>}
</span>
which works just as I wanted.
Original Answer
if(ViewBag.Message.Category == "Technical")
{
<span>Technical problem</span>
}
else
{
<span>Problem is : #(ViewBag.Message.Category)</span>
}
Updated Answer
//Model
public class Message
{
public int ID {get; set;}
public string Message {get; set;}
public string Category {get; set;}
}
//Controller
public ActionResult Index()
{
//If you use Linq to Sql, I made this up but this should give you an idea
using(MessageDataContext context = new MessageDataContext())
{
var messages = context.Messages.Where(m => m.Category == "Technical")
.Select(m => new Message { ID = m.ID, Message = m.Text, Category = m.Category});
return View(messages);
}
}
//View
#model IEnumerable<Message>
#foreach(var message in Model)
{
if(message.Category == "Technical")
{
//Id and class will be "TechnicalMessage1"
//Now you can create css class called "TechnicalMessage1" and only message with Id = 1 will have it
<span id="#(message.Category)Message#(message.ID)" class="#(message.Category)Message#(message.ID)">Technical problem</span>
}
else
{
<span>Problem is : #(ViewBag.Message.Category)</span>
}
}
You can use
<text>Technical Problem</text>
for this.
Is this what you are looking for?
#("Technical Problem")
alternatively
<text>Technical Problem</text>
alternatively
#:Technical Problem
Read http://haacked.com/archive/2011/01/06/razor-syntax-quick-reference.aspx for the syntax ..

MVC3 C#4.0 / Passing variables between views

new to C# and MVC. What I would like to achieve is passing a variable as ViewData from one view to another view without using ID in the ActionResult because this view generates it own variable. I am sure there are better ways to do that, but here what I thought might work.
First I made a model:
public class EventToShow
{
public Int64? ID { get; set; }
public Int64? EventID { get; set; }
}
Then I passed the variable EventID from the first View (Telerik MVC GRID) using the following:
columns.Template(item => Html.Raw(string.Format("{1}", Url.Action("tableread", "Home", new { id = (long)item.Event_ID }), "EventID"))).Width(20);
It worked using the following in my controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult tableread1(long? id)
{
ViewData["EID"] = id;
EventToShow ctx = new EventToShow();
ctx.ID = 1;
ctx.EventID = (long)ViewData["EID"];
return RedirectToAction("EditServerSide");
}
To pass the variable to the other view I tried to use the following (I think it is very wrong):
public ActionResult EditServerSide()
{
EventToShow ctx = new EventToShow();
var model1 = ctx.(x => x.ID == 1); **// The error here is (Identifier** expected)
ViewData["EID"] = ctx.EventID;
var model = from o in new Main().OffLinePayments
select new EditOffLinePayment
{
ID = o.ID,
Amount = o.Amount,
Details = o.Details
};
return View(model, ViewData["EID"]) **(this must be wrong)**
}
I thought maybe I should make the variable like this:
private string GetFullName()
{
EventToShow ctx = new EventToShow();
var name = EventToShow().Where(x => x.ID == 1);
ViewData["EID"] = ctx.EventID;
return name;
}
First I got an error: ‘GridEdit_OfflinePayment.Models.EventToShow' is a 'type' but is used like a 'variable'
I also did not know how to incorporate returned [name] in the EditServerSide Action.
My question, is there a better way to achieve what I am trying to do, and if this approach is correct, I would appreciate any help to fix these errors
From what I understand of the question is that you would like to pass data between several Actions? Like some sort of wizard steps process where you can pass data between multiple Actions?
If that's the case then here are some related questions and their answers:
How do I pass data across ActionResults in MVC 3?
multi-step registration process issues in asp.net mvc (splitted viewmodels, single model)

Resources