I am working on investment tracking app that will be free for everyone. I am using net Core with blazor and Blazorise for charts.
I stumbled upon a problem with rendering the charts. From the official Blazorise documentation I added method protected override async Task OnAfterRenderAsync(bool firstRender) (see in the code below). This method should redraw the charts the first time the page renders. The problem is that this method always fires twice. It renders the charts in the first go and the second time it leaves them empty (as firstRender = false the second time it fires). If I remove the if block the charts render ok.
Furthermore I've added button that should refresh the data + charts. After pressing this button the charts refresh twice (this is unwanted behaviour as it distracts the users) and what is interesting the data itself (the values) change after the second go.
Have anybody dealt with this problem before?
My html code
...
<div class="btn" #onclick="(async () => await RerenderPage())">Refresh Data</div>
...
My code
List<Models.VM.OverView> overview = new List<Models.VM.OverView>();
protected override async Task OnInitializedAsync()
{
overview = await GetOverview(); //gets overview from api
}
public async Task<List<Models.VM.OverView>> GetOverview()
{
return await Http.GetFromJsonAsync<List<Models.VM.OverView>>("/api/Overview/GetOverView/" + await GetUserIdAsync);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await HandleRedraw();
}
}
async Task HandleRedraw()
{
await pieChart.Clear();
//this method goes in the overview object and gets data from it
await pieChart.AddLabelsDatasetsAndUpdate(GetLabelsPieChart(), GetPieChartDataset());
}
What is the await Rerenderpage() doing ?
From the samples it looks like it employs the user of a bool isAlreadyInitialised flag.
protected override async Task OnAfterRenderAsync( bool firstRender )
{
if ( !isAlreadyInitialised )
{
isAlreadyInitialised = true;
await HandleRedraw();
}
}
I am assuming that async () => await RerenderPage()) calls something that then calls the chart.Update in order for the component to know its statuschanged ?
Related
I want get data from db once on OnInitializedAsync. I try to use tableLoading to judue,but it's not work.
protected override async Task OnInitializedAsync()
{
if (tableLoading)
{
return;
}
tableLoading = true;
users = await userService.GetSome(1, userType);
_total = await userService.GetCount(userType);
tableLoading = false;
Console.WriteLine("OnInitializedAsync");
}
This is the official way to solve your problem. You have to persist component state during first load so that your services won't be called second time during second load.
First add <persist-component-state /> tag helper inside your apps body:
<body>
...
<persist-component-state />
</body>
Then inject PersistentComponentState in your component and use like this:
#implements IDisposable
#inject PersistentComponentState ApplicationState
#code {
private IEnumerable<User> _users;
private int _total;
private PersistingComponentStateSubscription _persistingSubscription;
protected override async Task OnInitializedAsync()
{
_persistingSubscription =
ApplicationState.RegisterOnPersisting(PersistState);
if (!ApplicationState.TryTakeFromJson<IEnumerable<User>>("users", out var restoredUsers))
{
_users = await userService.GetSome(1, userType);
}
else
{
_users = restoredUsers;
}
if (!ApplicationState.TryTakeFromJson<int>("total", out var restoredTotal))
{
_total = await userService.GetCount(userType);
}
else
{
_total = restoredTotal;
}
}
private Task PersistState()
{
ApplicationState.PersistAsJson("users", _users);
ApplicationState.PersistAsJson("total", _total);
return Task.CompletedTask;
}
void IDisposable.Dispose()
{
_persistingSubscription.Dispose();
}
}
How i know blazor OnInitializedAsync exec in once or twice?
It usually loads twice.
Once when the component is initially rendered statically as part of the page.
A second time when the browser renders the component.
However, If you want to load it once, in that case, you could go to _Host.cshtml and change render-mode="ServerPrerendered" to render-mode="Server", and it would be called only once as a result it would then load your data from the database once only.
Note: For more information you could refer to the official documents here.
I know it's usually loads twice, i want to know when the function is run, how to konw it's run on once or twice. This is my solution.
static bool first = true;
protected override async Task OnInitializedAsync()
{
if (first)
{
first = false;
Console.WriteLine("first time");
return;
}
Console.WriteLine("second time");
}
I'm trying to recover some data asynchronously from localStorage then activate a specific tab from MudTabs component if data is found.
<MudTabs Elevation="2" Rounded="true" Centered="true" Color="Color.Primary" ApplyEffectsToContainer="true" Class="custom-tab-panel" PanelClass="pa-6 bg-white" #ref="tabs">
The problems come from my method OnAfterRenderAsync who doesn't work updating the MudBlazor component state, here is the code:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var result = await protectedSessionStore.GetAsync<int>("Call");
if (result.Success)
{
tabs.ActivatePanel(1);
StateHasChanged();
}
}
//return base.OnAfterRenderAsync(firstRender);
}
I tested a previous version of it who worked pretty well but i cannot use the await operator inside of it, the method not being asynchronously:
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
tabs.ActivatePanel(1);
}
return base.OnAfterRenderAsync(firstRender);
}
I didn't found what is specifically the difference between returning the base method and calling the StateHasChanged. When using the StateHasChanged method the active panel remain on the 0 index.
If you have any ideas, thanks in advance.
I created a .razor Notification Component in Blazor, and I'm trying to autoclose the notification div after xx seconds.
So far it works with this Method
private async Task CloseToast(Guid Id, bool autoclose = false)
{
if (autoclose)
{
await Task.Delay(TimeSpan.FromSeconds(5));
}
//Code to remove the notification from list
StateHasChanged();
}
The problem is that for 5 seconds the UI data binding is stuck, any one way or two way binding update to variables (text fields etc..) is on hold until the Notification is closed and the Task resumes.
How can I launch a method or code block after xx seconds without blocking the main UI task in Blazor?
A component with a timer that counts back
<h3>#Time</h3>
#code {
[Parameter] public int Time { get; set; } = 5;
public async void StartTimerAsync()
{
while (Time > 0)
{
Time--;
StateHasChanged();
await Task.Delay(1000);
}
}
protected override void OnInitialized()
=> StartTimerAsync();
}
Usage:
<Component />
<Component Time="7"/>
Tested on client side Blazor. Should behave the same way in server-side Blazor.
Hope this helps
You can use .NET Timer from System.Timers as well and set the Delay in milisec. When it elapsed event will triggered and you can put your logic into the event handler. If you don't want to bother with all the config and Disposing of Timer you can use this Nuget package. It is a very convenient wrapper for the Timer with many extra features see docs.
<AdvancedTimer Occurring="Times.Once()" IntervalInMilisec="#_closeInMs" AutoStart="true" OnIntervalElapsed="#(e => { IsVisible = false; })" />
#code {
private int _closeInMs = 5000;
...
}
The official Blazor Server EFCore sample project includes this as an example, in TextFilter.razor. The essence of the code is:
Timer? timer;
// ... code in a function to start the timer
timer?.Dispose();
timer = new(DebounceMs);
timer.Elapsed += NotifyTimerElapsed;
timer.Enabled = true;
private async void NotifyTimerElapsed(object? sender, ElapsedEventArgs e)
{
timer?.Dispose();
timer = null;
// SomeMethodAsync will need to call StateHasChanged()
InvokeAsync(() => SomeMethodAsync());
}
and a Dispose() function for the page to dispose any timer in progress when user navigates away.
I am using MassTransit 3.0.0.0 and I have a hard time understanding how to intercept messages in a Request-Response scenario on their way out and add some information to the headers field that I can read on the receiver's end.
I was looking at the Middleware, as recommended in the MassTransit docs - see Observers warning - but the context you get on the Send is just a Pipe context that doesn't have access to the Headers field so I cannot alter it. I used the sample provided in Middleware page.
I then, looked at IPublishInterceptor
public class X<T> : IPublishInterceptor<T> where T : class, PipeContext
{
public Task PostPublish(PublishContext<T> context)
{
return new Task(() => { });
}
public Task PostSend(PublishContext<T> context, SendContext<T> sendContext)
{
return new Task(() => { });
}
public Task PrePublish(PublishContext<T> context)
{
context.Headers.Set("ID", Guid.NewGuid().ToString());
return new Task(() => { });
}
public Task PreSend(PublishContext<T> context, SendContext<T> sendContext)
{
context.Headers.Set("ID", Guid.NewGuid().ToString());
return new Task(() => { });
}
}
Which is very clear and concise. However, I don't know where it is used and how to link it to the rest of the infrastructure. As it stands, this is just an interface that is not really linked to anything.
If you need to add headers when a message is being sent, you can add middleware components to either the Send or the Publish pipeline as shown below. Note that Send filters will apply to all messages, whereas Publish filters will only apply to messages which are published.
// execute a synchronous delegate on send
cfg.ConfigureSend(x => x.Execute(context => {}));
// execute a synchronous delegate on publish
cfg.ConfigurePublish(x => x.Execute(context => {}));
The middleware can be configured on either the bus or individual receive endpoints, and those configurations are local to where it's configured.
You can also add headers in the consumer class:
public async Task Consume(ConsumeContext<MyMessage> context)
{
....
await context.Publish<MyEvent>(new { Data = data }, c => AddHeaders(c));
}
public static void AddHeaders(PublishContext context)
{
context.Headers.Set("CausationId", context.MessageId);
}
http://masstransit-project.com/MassTransit/advanced/middleware/custom.html
Shows adding an extension method to make it clear what you're setup. That's a big help if it's an interceptor that will be used a lot, so it's clear that purpose. You can skip that step if you want.
Basically, just...
cfg.AddPipeSpecification(new X<MyMessage>());
When configuring the transport.
I'm not sure the correct way to structure this test. I've got a view model here:
public class ViewModel
{
public ReactiveCommand PerformSearchCommand { get; private set; }
private readonly ObservableAsPropertyHelper<bool> _IsBusy;
public bool IsBusy
{
get { return _IsBusy.Value; }
}
public ViewModel(IAdventureWorksRepository _awRepository)
{
PerformSearchCommand = new ReactiveCommand();
PerformSearchCommand.RegisterAsyncFunction((x) =>
{
return _awRepository.vIndividualCustomers.Take(1000).ToList();
}).Subscribe(rval =>
{
CustomerList = rval;
SelectedCustomer = CustomerList.FirstOrDefault();
});
PerformSearchCommand.IsExecuting.ToProperty(this, x => x.IsBusy, out _IsBusy);
PerformSearchCommand.Execute(null); // begin executing immediately
}
}
The dependency is a data access object to AdventureWorks
public interface IAdventureWorksRepository
{
IQueryable<vIndividualCustomer> vIndividualCustomers { get; }
}
Finally, my test looks something like this:
[TestMethod]
public void TestTiming()
{
new TestScheduler().With(sched =>
{
var repoMock = new Mock<IAdventureWorksRepository>();
repoMock.Setup(x => x.vIndividualCustomers).Returns(() =>
{
return new vIndividualCustomer[] {
new vIndividualCustomer { FirstName = "John", LastName = "Doe" }
};
});
var vm = new ViewModel(repoMock.Object);
Assert.AreEqual(true, vm.IsBusy); //fails?
Assert.AreEqual(1, vm.CustomerList.Count); //also fails, so it's not like the whole thing ran already
sched.AdvanceTo(2);
Assert.AreEqual(1, vm.CustomerList.Count); // success
// now the customer list is set at tick 2 (not at 1?)
// IsBusy was NEVER true.
});
}
So the viewmodel should immediately begin searching upon load
My immediate problem is that the IsBusy property doesn't seem to get set in the testing scheduler, even though it seems to work fine when I run the code normally. Am I using the ToProperty method correctly in the view model?
More generally, what is the proper way to do the full 'time travel' testing when my object under test has a dependency like this? The issue is that unlike most testing examples I'm seeing, the called interface is not an IObservable. It's just a synchronous query, used asynchronously in my view model. Of course in the view model test, I can mock the query to do whatever rx things I want. How would I set this up if I wanted the query to last 200 ticks, for example?
So, you've got a few things in your code that is stopping you from getting things to work correctly:
Don't invoke commands in ViewModel Constructors
First, calling Execute in the constructor means you'll never see the state change. The best pattern is to write that command but not execute it in the VM immediately, then in the View:
this.WhenAnyValue(x => x.ViewModel)
.InvokeCommand(this, x => x.ViewModel.PerformSearchCommand);
Move the clock after async actions
Ok, now that we can properly test the before and after state, we have to realize that after every time we do something that normally would be async, we have to advance the scheduler if we use TestScheduler. This means, that when we invoke the command, we should immediately advance the clock:
Assert.IsTrue(vm.PerformSearchCommand.CanExecute(null));
vm.PerformSearchCommand.Execute(null);
sched.AdvanceByMs(10);
Can't test Time Travel without IObservable
However, the trick is, your mock executes code immediately, there's no delay, so you'll never see it be busy. It just returns a canned value. Unfortunately, injecting the Repository makes this difficult to test if you want to see IsBusy toggle.
So, let's rig the constructor a little bit:
public ViewModel(IAdventureWorksRepository _awRepository, Func<IObservable<List<Customer>>> searchCommand = null)
{
PerformSearchCommand = new ReactiveCommand();
searchCommand = searchCommand ?? () => Observable.Start(() => {
return _awRepository.vIndividualCustomers.Take(1000).ToList();
}, RxApp.TaskPoolScheduler);
PerformSearchCommand.RegisterAsync(searchCommand)
.Subscribe(rval => {
CustomerList = rval;
SelectedCustomer = CustomerList.FirstOrDefault();
});
PerformSearchCommand.IsExecuting
.ToProperty(this, x => x.IsBusy, out _IsBusy);
}
Set up the test now
Now, we can set up the test, to replace PerformSearchCommand's action with something that has a delay on it:
new TestScheduler().With(sched =>
{
var repoMock = new Mock<IAdventureWorksRepository>();
var vm = new ViewModel(repoMock.Object, () =>
Observable.Return(new[] { new vIndividualCustomer(), })
.Delay(TimeSpan.FromSeconds(1.0), sched));
Assert.AreEqual(false, vm.IsBusy);
Assert.AreEqual(0, vm.CustomerList.Count);
vm.PerformSearchCommand.Execute(null);
sched.AdvanceByMs(10);
// We should be busy, we haven't finished yet - no customers
Assert.AreEqual(true, vm.IsBusy);
Assert.AreEqual(0, vm.CustomerList.Count);
// Skip ahead to after we've returned the customer
sched.AdvanceByMs(1000);
Assert.AreEqual(false, vm.IsBusy);
Assert.AreEqual(1, vm.CustomerList.Count);
});