I have a page, which looks something like this:
<div>
<MyToggle ToggleState="#ToggleState" ToggleStateChanged="#OnToggleStateChanged"/>
<MyData ToggleState="#ToggleState"/>
</div>
public partial class MyToggle
{
[Parameter]
public EventCallback<bool> ToggleStateChanged { get; set; }
[Parameter]
public bool ToggleState { get; set; }
private async Task OnButtonClicked()
{
this.ToggleState = !this.ToggleState;
await this.ToggleStateChanged.InvokeAsync(this.ToggleState);
}
}
public partial class MyData
{
private IObservableCollection<Data> Data;
[Parameter]
public bool ToggleState { get; set; }
[Inject]
protected IRefreshService RefreshService { get; set; }
protected override async Task OnInitializedAsync()
{
this.RefreshService.OnRefresh += async () => await this.RefreshData();
await base.OnInitializedAsync();
await this.RefreshData();
}
private override async Task<int> LoadData()
{
if (this.ToggleState)
{
return await this.Logic.GetDataA();
}
return await this.Logic.GetDataB(this.Company);
}
private async Task RefreshData()
{
this.Data = await this.LoadData();
}
}
public partial class MyPage
{
private bool ToggleState { get; set; }
[Inject]
private IRefreshService RefreshService { get; set; }
private async Task OnToggleStateChanged(bool toggleState)
{
this.ToggleState = toggleState;
await this.RefreshService.Refresh();
}
}
Basically, I want to toggle, which data is loaded. As soon as the toggle state changes, the an event is emitted, which forces all child components to load their respective data. The problem I have is, that as soon as I press the toggle button, MyPage.OnToggleStateChanged gets called. This is expected and works perfectly. The parameter of said method is always correct. As you can see, the property MyPage.ToggleState is then set to the correct value. Afterwards, the event to refresh the data is emitted (also expected). When I set a breakpoint at MyData.RefreshData the method is now called as intended. Therefore, the workflow is correct.
The "funny" thing is, that as soon as I set a breakpoint at MyData.LoadData, MyData.ToggleState still holds the old value. I observed this with a breakpoint and the property MyData.ToggleState is set AFTER MyData.RefreshData is called. For me this does not make any sense, that there is a small delay.
I tried using StateHasChanged in MyPage.OnToggleStateChanged after the MyPage.ToggleState is set, but this does not make any difference. The only thing which works is, to make a small delay of 10ms after I have set the value.
Related
I recreated a new app maui and to make the navigation I redid what worked on another app that I made (on the other app it still works). As soon as I navigate on a new page everything goes well but it's as soon as I want to come back on the page where I was or I have a bug.
NavigationService.cs
public async Task NavigateAsync(NavigationPages page)
{
try
{
await Shell.Current.GoToAsync(page.ToString(), true);
}
catch (Exception ex)
{
}
}
ViewModel.cs
public ICommand GoBacKCommand { get; }
public ParamViewModel(INavigationService navigationService)
{
GoBacKCommand = new Command(GoBacKExecute);
this.navigationService = navigationService;
}
private void GoBacKExecute()
{
navigationService.NavigateAsync(BusinessModels.NavigationPages.MainPage);
}
MauiProgram.cs
Routing.RegisterRoute(nameof(MainPage), typeof(MainPage));
services.AddTransient<MainPage>();
services.AddTransient<MainViewModel>();
and the Exception is : Relative routing to shell elements is currently not supported. Try prefixing your uri with ///: ///MainPage
but if I do what it says (///MainPage) the navigation does not work anymore
How can I solve this problem?
If you want go back to a page (like PopUp) you need to need use GotoAsync as below
ViewModel.cs
public ICommand Back_Command { get; set; }
public ViewModel()
{
Back_Command = new Command(Back);
}
private async void Back()
{
await Shell.Current.GoToAsync("..");
}
Page.xaml
<Shell.BackButtonBehavior>
<BackButtonBehavior
Command="{Binding Back_Command}"/>
</Shell.BackButtonBehavior>
Further explanation has been stated in this documentation https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/navigation
I'm trying to update an object with another object asynchronously. I'm trying to get the CustomerId value from Statues and then use it to call a specific customer and pass those values into PreviousStatuses. Then update the StatusToAdd with PreviousStatuses. If I pass Statues to StatusToAdd the values update. However, it's the wrong customer id. That's why I'm using PreviousStatuses.
This is the error I get:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object.
at DSPDRewrite.Pages.Popups.AddStatusComponent.UpdateValues(String id)
at DSPDRewrite.Pages.Popups.AddStatusComponent.OnInitializedAsync()
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
[Parameter]
public int CustomerId { get; set; }
[CascadingParameter]
public Task<AuthenticationState> AuthState { get; set; }
public Status Statuses;
public Status PreviousStatuses;
//IList<Status> PreviousStatuses;
public Dspd1056Status StatusToAdd = new Dspd1056Status();
public Customer customer;
public int AccountStatusId = 0;
protected override async Task OnInitializedAsync()
{
Statuses = await dataService.Get1056StatusById(CustomerId);
//int id = Int32.Parse(Statuses.CustomerId);
// Statuses = await dataService.Get1056StatusById(id);
Console.WriteLine(Statuses.CustomerId);
await UpdateValues(Statuses.CustomerId);
}
async Task UpdateValues(string id)
{
PreviousStatuses = await dataService.Get1056StatusById(Int32.Parse((id)));
StatusToAdd.AccountCurrent = PreviousStatuses.AccountCurrent;
StatusToAdd.StartDate = PreviousStatuses.StartDate;
StatusToAdd.EndDate = PreviousStatuses.EndDate;
StatusToAdd.Units = PreviousStatuses.Units;
StatusToAdd.Ppc = PreviousStatuses.Ppc;
StatusToAdd.EndStatus = PreviousStatuses.EndStatus;
StatusToAdd.ContinuallyFunded = PreviousStatuses.ContinuallyFunded;
StatusToAdd.AnnualUnits = PreviousStatuses.AnnualUnits;
StatusToAdd.Elg = PreviousStatuses.Elg;
StatusToAdd.ReceiptDate = PreviousStatuses.ReceiptDate;
StatusToAdd.RahTripsFunded = PreviousStatuses.RahTripsFunded;
StatusToAdd.Rate = PreviousStatuses.Rate;
StatusToAdd.AccountTotal = PreviousStatuses.AccountTotal;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (CustomerId != 0)
{
customer = await dataService.GetCustomerById((int)CustomerId);
StateHasChanged();
}
}
}
A few things to consider:
OnInitializedAsync
Can your .Get1056StatusById() method return null (e.g. if CustomerId is not found)? If so that appears to be the cause of the error: your code calls
await UpdateValues(Statuses.CustomerId);
That suggests Statuses is null. I would add a null check to this code to handle that case. Not sure why the Console.WriteLine didn't throw the null reference first, I assume the sample code didn't have it when the error was thrown.
A few other points: fields and methods in Components should normally be private or protected. If you are going to expose outside the Component they should be properties. Methods are rarely called outside a component. My suggested changes would be:
[Parameter] public int CustomerId { get; set; }
[CascadingParameter] public Task<AuthenticationState> AuthState { get; set; }
Status Statuses;
Status PreviousStatuses;
Dspd1056Status StatusToAdd = new Dspd1056Status();
Customer customer;
int AccountStatusId = 0;
protected override async Task OnInitializedAsync()
{
Statuses = await dataService.Get1056StatusById(CustomerId);
if(Statuses != null)
{
Console.WriteLine(Statuses.CustomerId);
await UpdateValues(Statuses.CustomerId);
}
}
async Task UpdateValues(string id)
{
PreviousStatuses = await dataService.Get1056StatusById(Int32.Parse((id)));
// rest...
Your component HTML should obviously check for a null Statuses before it tries to render:
#if(Statuses != null)
{
<p>Customer is #Statuses.CustomerId</p>
}
This is such a frequently used pattern in Blazor apps I usually use a <NotNull> component:
https://gist.github.com/conficient/eab57ade2587104d71ac6f26ddfd4865
My working SignalR ASP.NET Core 5 Windows Service with a simple string payload (or even more value type parameters) does not work anymore when I change it to a (simple) complex object ("CommonMessage" in my case). From what I read, it should work out of the box. The "SendCommonMessage" method doesn't get called anymore and I am not getting any error. What am I missing? Is there no way of debugging/getting the error shown? (The ASP.NET Service gets called by a WPF Core 5 application.)
public class CommonMessageHub : Hub
{
public async Task SendCommonMessage(CommonMessage commonMessage)
{
await Clients.All.SendAsync("ReceiveCommonMessage", commonMessage);
}
}
public class CommonMessage
{
public int MessageType { get; set; }
public int Id { get; set; }
public string Text { get; set; }
}
The caller (WPF app) looks like so:
public class CommonMessageService
{
public CommonMessageService(HubConnection connection)
{
_connection = connection;
_connection.On<CommonMessage>("ReceiveCommonMessage", commonMessage => CommonMessageReceived?.Invoke(commonMessage));
}
private readonly HubConnection _connection;
public event Action<CommonMessage> CommonMessageReceived;
public async Task Connect()
{
await _connection.StartAsync();
}
public async Task SendCommonMessage(CommonMessage commonMessage)
{
await _connection.SendAsync("SendCommonMessage", commonMessage);
}
}
Why does the Status of my Task return "WaitingForActivasion" instead of "Running" ?
If I remove Task.Run I get stuck in the while loop, so I assume its not running asynchronous.
public class StateManagerTest
{
[Fact]
public void Start_TaskStatus()
{
StateManager manager = new StateManager();
manager.Start();
Assert.True(manager.Status == System.Threading.Tasks.TaskStatus.Running.ToString());
}
}
public class StateManager
{
private CancellationTokenSource cts = new();
private Task updateTask;
public HashSet<StateItem> StateItems { get; private set; }
public Provider Provider { get; private set; }
public List<OutputService> OutputServices { get; private set; }
public string Status
{
get => updateTask.Status.ToString();
}
public StateManager()
{
StateItems = new();
OutputServices = new();
Provider = new();
}
public void Stop()
{
cts.Cancel();
}
public void Start()
{
updateTask = Task.Run(() => Update(cts.Token))
.ContinueWith(t => Debug.WriteLine(t.Exception.Message), TaskContinuationOptions.OnlyOnFaulted);
}
private async Task Update(CancellationToken token)
{
while (true)
{
// get changes from outputs
Dictionary<StateItem, object> changes = new Dictionary<StateItem, object>();
foreach (var service in OutputServices)
{
var outputChanges = await service.GetChanges();
foreach (var change in outputChanges)
changes.TryAdd(change.Key, change.Value);
}
// write changes to provider source
await Provider.PushChanges(changes);
// update state
await Provider.UpdateStateItems();
// update all services
foreach (var service in OutputServices)
await service.UpdateSource();
if (token.IsCancellationRequested)
return;
}
}
}
As others have noted, WaitingForActivation is the correct state for a Promise Task that is not yet completed. In general, I recommend not using Task.Status or ContinueWith; they are relics from a time before async/await existed.
How to get status of long running task
I believe you would want progress reporting, which is done yourself. The T in IProgress<T> can be a string if you want a simple text update, or a double if you want a percentage update, or a custom struct if you want a more complex update.
I am new to Unit Testing MVVM and using PRISM on my project. I am implementing Unit Testing on our current project and not having luck finding resources online that would tell me how totest DelegateCommand that calls async method. This is a follow up question to my post - How to Unit Test a ViewModel with async method. on how to unit test an async methods in MVVM and was answered that public methods can be tested using async TestMethod. This scenario will work only if the method that I want to test are public methods.
The problem is I want to test my DelegateCommand as this are the only public details that I want to expose on other classes and everything else are private. I can expose my private methods as public but I will never do this as its a bad design. I am not sure on how to go about this - Is DelegateCommand needs to be tested, or there are some other work around this? I am interested to know how other go about this and somehow lead me to the right path.
Here are my codes again
async void GetTasksAsync()
{
this.SimpleTasks.Clear();
Func<IList<ISimpleTask>> taskAction = () =>
{
var result = this.dataService.GetTasks();
if (token.IsCancellationRequested)
return null;
return result;
};
IsBusyTreeView = true;
Task<IList<ISimpleTask>> getTasksTask = Task<IList<ISimpleTask>>.Factory.StartNew(taskAction, token);
var l = await getTasksTask; // waits for getTasksTask
if (l != null)
{
foreach (ISimpleTask t in l)
{
this.SimpleTasks.Add(t); // adds to ViewModel.SimpleTask
}
}
}
also here is the command in my VM that calls the async method above
this.GetTasksCommand = new DelegateCommand(this.GetTasks);
void GetTasks()
{
GetTasksAsync();
}
and now my Test Method goes like
[TestMethod]
public void Command_Test_GetTasksCommand()
{
MyViewModel.GetTaskCommand.Execute(); // this should populate ViewModel.SimpleTask
Assert.IsTrue(MyBiewModel.SimpleTask != null)
}
Currently what I am getting is that my ViewModel.SimpleTask = null this is because it does not wait for the async method to finish.
I wrote an AsyncCommand class that returns a Task object from the Execute method. You then need to implement ICommand.Execute explicitly, awaiting the Task from your Execute implementation:
public class AsyncCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public Func<Task> ExecutedHandler { get; private set; }
public Func<bool> CanExecuteHandler { get; private set; }
public AsyncCommand(Func<Task> executedHandler, Func<bool> canExecuteHandler = null)
{
if (executedHandler == null)
{
throw new ArgumentNullException("executedHandler");
}
this.ExecutedHandler = executedHandler;
this.CanExecuteHandler = canExecuteHandler;
}
public Task Execute()
{
return this.ExecutedHandler();
}
public bool CanExecute()
{
return this.CanExecuteHandler == null || this.CanExecuteHandler();
}
public void RaiseCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
{
this.CanExecuteChanged(this, new EventArgs());
}
}
bool ICommand.CanExecute(object parameter)
{
return this.CanExecute();
}
async void ICommand.Execute(object parameter)
{
await this.Execute();
}
}
You can then pass async Task-returning methods to the command class:
public class ViewModel
{
public AsyncCommand AsyncCommand { get; private set; }
public bool Executed { get; private set; }
public ViewModel()
{
Executed = false;
AsyncCommand = new AsyncCommand(Execute);
}
private async Task Execute()
{
await(Task.Delay(1000));
Executed = true;
}
}
In your unit tests, you simply await the Execute method:
[TestMethod]
public async Task TestAsyncCommand()
{
var viewModel = new ViewModel();
Assert.IsFalse(viewModel.Executed);
await viewModel.AsyncCommand.Execute();
Assert.IsTrue(viewModel.Executed);
}
The UI, on the other hand, will call the explicitly implemented ICommand.Execute method which takes care of awaiting the task.
(*) In the meantime I noticed that if you follow common naming conventions, the Task-returning method should actually be named ExecuteAsync.
In Prism 6, you can create DelegateCommand and DelegateCommand<T> from async handler.
For example:
startParsingCommand=DelegateCommand
.FromAsyncHandler(StartParsingAsync,CanStartParsing)
.ObservesProperty(()=> IsParserStarted);
Since I cannot add comments, for completeness sake, in PRISM 6 you could try:
ParsingCommand = new DelegateCommand<string>(async (x) => await StartParsing(x));