I have a blazor app page, and when i go there by the link from another page, it works and i can see h1 header with correct value, but if i will click F5 or call reloadpage method, it throws NullReferenceException on line with h1 header, like item object is null. How can i avoid that behaviour?
...
#inject NavigationManager MyNavigationManager
#page "/items/{id}"
...
<h1>#_item.Name<h1>
...
[Parameter]
public string Id { get; set; }
private Item _item;
protected override async Task OnInitializedAsync()
{
_item= await ItemsService.GetItem(Id);
}
private void ReloadPage(bool forceLoad = false)
{
MyNavigationManager.NavigateTo($"/items/{Id}", forceLoad);
}
OnInitializedAsync is exactly what it sounds like, it's an asynchronous method. The data for Item may not be available when the razor component renders. You should check for nullability before trying to display it.
#if(_item is not null)
{
<h1>#_item.Name<h1>
}
You may be able to do something like this as well:
<h1>#(_item.Name ?? "Not Available")</h1>
Read more here
Related
First of all, let me explain why I think I need a singleton. I'm integrating several Hosted Checkout payment processors into my Blazor Server application. All of them work as follows;
Index.razor has an Iframe that displays a payment processors url.
When the customer completes the payment the iframe redirects back to a url specified by my application, PaymentComplete.razor.
PaymentComplete.razor uses a scoped service HostedCheckoutService to raise an event to Index.razor containing the payment response.
This is where the problem comes in. PaymentComplete.razor is hosted inside an iframe therefore is treated with a separate scope. Any property changes or events raised by HostedCheckoutService from within PaymentComplete.razor wont be Index.razor. This makes it (nearly?) impossible to merge information from within the iframe to the scope of Index.razor.
What obviously solves this issue is registering HostedCheckoutService as a singleton. Now the problem is that when one client raises an event from PaymentComplete.razor, all clients will have to handle it.
To solve this I created an IndexBase with a unique property named EventTargetId. When the iframe payment is completed, the return url to PaymentComplete.razor will contain the EventTargetId in the query string.
IndexBase.cs
<iframe style="width:100%;height:50vh" src="#HostedCheckoutFrameSrc " frameborder="0" ></iframe>
public class IndexBase : ComponentBase, IDisposable
{
[Inject] NavigationManager NavigationManager { get; set; }
[Inject] HostedCheckoutService HostedCheckoutService { get; set; }
[Inject] PaymentApi PaymentApi { get; set; }
public string HostedCheckoutFrameSrc { get; set; }
public string EventTargetId { get; set; } = Guid.NewGuid().ToString();
protected override void OnInitialized()
{
HostedCheckoutService.OnPaymentComplete += PaymentComplete;
}
public void Dispose()
{
HostedCheckoutService.OnPaymentComplete -= PaymentComplete;
}
private void PaymentComplete(string eventTargetId, string paymentJson)
{
// Hosted checkout iframe has returned a successfull payment.
// Do something, send order, notification, ect.
}
public async Task InitializePayment()
{
string returnUrl = NavigationManager.BaseUri + $"/PaymentComplete?eventTargetId={EventTargetId}";
InitializePaymentResponse response = await PaymentApi.CreatePaymentRequest(returnUrl);
// Set iframe src property to third party payment providers url.
// When customer completes third party payment url, the iframe redirects to PaymentComplete.razor (returnUrl).
HostedCheckoutFrameSrc = PaymentApi.baseUrl + response.PaymentId;
}
}
PaymentComplete.razor (redirected from third party url, hosted inside iframe)
This page will grab the EventTargetId from the query string and raise an event on our singleton service.
[Inject] NavigationManager NavigationManager { get; set; }
[Inject] PostFormService PostFormService { get; set; }
[Inject] HostedCheckoutService HostedCheckoutService { get; set; }
protected override async Task OnInitializedAsync()
{
// We face double render problem, but Form values will be null on secord render anyways.
if (PostFormService.Form != null)
{
NavigationManager.TryGetQueryString<string>("eventTargetId", out string eventTargetId);
string paymentJson = PostFormService.Form?["PaymentResponse"];
HostedCheckoutService.PaymentCompleted(eventTargetId, paymentJson);
}
}
In my singleton HostedCheckoutService, I filter out any subscribers using the EventTargetId.
public class HostedCheckoutService
{
public event Action<string, string> OnPaymentComplete;
public void PaymentCompleted(string eventTargetId, string paymentJson)
{
// Instead of raising the event for every instance attached to this action
// only raise the event for the specified target.
var instance = OnPaymentComplete.GetInvocationList()
.Where(d => d.Target is IndexBase && ((IndexBase)d.Target).EventTargetId == eventTargetId)
.FirstOrDefault();
instance?.DynamicInvoke(eventTargetId, paymentJson);
}
}
Finally, the question! Does this seem like an unacceptable use of singleton events or does anyone have a better approach? Even though every client wont be handling the event, a call to GetInvocationList() would still contain a list of every subscribed class.
Note: Each event subscriber would not actually be a full IndexBase class. It would be a simple payment component (I simplified this example).
My main concern would be scaling on calling all the registered methods on the event.
As I don't know what else HostedCheckoutService does, what about having a singleton PaymentTransactionService that contains a simple collection of Guids against Actions - and probably registration times to operate a timeout system . Index calls a Register method on PaymentTransactionService to register it's Guid and it's Action. - and obviously a ReRegister method when it Disposes. PaymentComplete calls a TransactionComplete method on PaymentTransactionService. It checks it's list and executes the registered action if there is one - and logs an error if there isn't. You can use each PaymentComplete call to also kick off a managment routine that checks timeouts and removed expired registrations.
I'm currently working on the POM of a web app, that allows to open modals from the navigation bar. The navigation bar stays the same for every page you're on. Each modal can be opened from every page.
I have defined a page object for each modal. Also the navigation bar is a pageobject,
What would be the best way to return to the page, that the modal was opened from?
So for example, you are on the Page FooPage and open modal AboutModal. What is the best way to return to FooPage? It should also work for BarPage and other Pages.
My first approach was, that i define a BasePage Object, which only includes the webdriver and navigationbar. i extend every Page on the web app from this BasePage. Then i could do something like this:
Code for FooPage:
public class FooPage: BasePage
{
private NavigationBar NavBar;
public FooPage(IWebDriver driver): base(driver)
{
...
this.NavBar = new NavigationBar(driver);
}
public NavigationBar Navigate()
{
return NavBar;
}
...
}
public class NavigationBar
{
...
public openAboutModal(BasePage currentPage)
{
log.Info("Open About Modal");
Action.Click(NavigationBarElements.AboutButton);
return new AboutModal(Driver, currentPage);
}
}
public class AboutModal
{
...
protected BasePage ReturnPage;
public AboutModal(IWebDriver driver, BasePage returnPage)
{
...
this.ReturnPage = returnPage;
}
public BasePage CloseAboutModal()
{
return this.ReturnPage;
}
...
}
This is not practical and not intuitive, because we have to remember on which pageobject we currently are, when writing tests. Also only the methods from BasePage are available, which means we have to additionaly navigate back to the page we wanted to be on.
So instead of writing
public class ModalTests
{
[Test]
public void CheckAboutModal()
{
Login() // FooPage
.Navigate() //NavigationBar
.openAboutModal() // AboutModal
.doSomeStuff() //AboutModal
.CloseAboutModal(); //FooPage
}
}
we have to do
public void CheckAboutModal()
{
Login() // FooPage
.Navigate() //NavigationBar
.openAboutModal(new FooPage(Driver)) // AboutModal
.doSomeStuff() // AboutModal
.CloseAboutModal() // BasePage
.Navigate() //NavigationBar
.ToFooPage(); // FooPage
}
}
How can I return to the calling Page of the modal, without making Testwriting to complicated?
Rather than write your test as one giant method-chaining call, use variables whenever you need to refer back to a certain page model. Your test can simply become:
var foo = Login();
foo.Navigate()
.openAboutModal()
.doSomeStuff()
.CloseAboutModal();
// Continue your test after closing the modal
foo.SomeOtherOperation();
In cases like this, the modal doesn't need to return anything. The CloseAboutModal() method can be a void return type. Your test should understand the larger context in which the modal is being used, and create local variables appropriately in order to "return" back to the main page.
This seems so simple in theory, but I can't find a way to make it work:
in _Layout.cshtml
window.insertComponent = function (selector, component) {
$(selector).append(component);
}
In my main component:
private object Model {get;set;}
private string elementId {get;set;}
//...
private async void OnChange(EventArgs e)
{
await JSRuntime.InvokeVoidAsync("insertComponent", new[] { "#" + elementId, new SomeChildComponentOrView() { Model = this.Model }});
}
What, if anything, can I do to make this work? Obviously, new SomeChildComponentOrView() won't compile. Where do I go from here?
Using ASP.NET Core 3.1 with Blazor.
I suppose you may check your Setup.cs file, it should be configured to add a service and use it.
app.UseStaticFile()
causing if you don't use it, it will be no function on JS or JQ.
I have a server-side blazor client and I'm trying to modify the MainLayout razor page by having a Login check. I'm currently using Blazored for localstorage saving, and I'm currently using to see if a token is saved to see if user is logged in, however I'm not sure how I translate this in the if statement in razor page because it wants async method.
My login check is pretty simple as shown below.
public async Task<bool> IsLoggedIn()
{
return await m_localStorage.ContainKeyAsync("token").ConfigureAwait(false);
}
In my Razor page I'm doing this statement check - which obvious doesn't work as there's no async modifier
#if (!await AppState.IsLoggedIn()) //Requires async modifier
{
Login
}
I've also tried doing it using the .Result property, but this results in an exception thrown: (System.AggregateException: 'Information: Executed an implicit handler method, returned result Microsoft.AspNetC)' with an inner-exception -> NullReferenceException: Object reference not set to an instance of an object.
But from what I can see AppState is injected correctly and the local storage seems to be injected correctly in AppState.
#if (!AppState.IsLoggedIn().Result)
{
Login
}
So my question is what is the correct way to approach this, is there a way to execute async methods in razor pages?
is there a way to execute async methods in razor pages?
No, there isn't a way to use await in a Razor component. This is because you can't do async work as part of the rendering of the component.
Incidentally, the local storage mechanism provided by the Blazor team supports data protection, and is recommended for use by Steve Sanderson.
Note: The async Lifecycle methods of the component are where async work is done, and thus you can design your code accordingly, as for instance, calling AppState.IsLoggedIn() from OnInitializedAsync, and assigning the returned value to a local variable which can be accessed from your views.
AsyncComponent.razor
#typeparam TResult
#typeparam TInput
#if (Result != null)
{
#DataReadyFragment(Result)
}
else if (DataMissingFragment != null)
{
#DataMissingFragment
}
#code {
[Parameter] public RenderFragment<TResult> DataReadyFragment { get; set; }
[Parameter] public RenderFragment DataMissingFragment { get; set; }
[Parameter] public Func<TInput, Task<TResult>> AsyncOperation { get; set; }
[Parameter] public TInput Input { get; set; }
TResult Result { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
AsyncOperation.Invoke(Input).ContinueWith(t => { Result = t.Result; InvokeAsync(StateHasChanged); });
}
}
Usage
<AsyncComponent TResult="User" TInput="string" Input="Pa$$W0rd" AsyncOperation="#AsyncMethodName">
<DataReadyFragment Context="result">
#if(result.IsLoggedIn)
{
<h3>Logged-In , Username:#result.Name</h3>
}
else
{
<h3>Wrong Password</h3>
}
</DataReadyFragment>
<DataMissingFragment>
<h3>Please Login :)</h3>
</DataMissingFragment>
</AsyncComponent>
Based on LazZiya example, worked for me. In my case an event was not async as supported by a component, but the call I had required await. Using this example I could return data from the call based on a model.
public string Name => Task.Run(() => Service.GetNameAsync()).GetAwaiter().GetResult();
I ended up with this and it worked like a charm.
Task<AssessmentResponsesModel> task = Task.Run(() => _IAssessmentResponse.CreateAsync(APIPathD, argsItem, token).GetAwaiter().GetResult());
I am working on an ASP.NET Core Blazor application with .Net Core 3.0 (I am aware of 3.1, but due to Mordac I am stuck with this version for now).
I have a multiple-component page, and some of those components require access to the same data and need to all be updated when the collection is updated. I've been trying to use EventHandler-based callbacks, but those get invoked on their own threads at about the same time (if I understand correctly), causing the callbacks in the .razor components to attempt to make service calls to the context at the same time.
Note: I've tried making my DbContext`s lifetime transient, but I'm still getting the race conditions.
It's quite possible that I gotten myself into an async blender and don't know how to get out.
I've tentatively concluded that the event EventHandler methodology will not work here. I need some way to trigger "collection changed" updates to the components without triggering a race condition.
I've thought about updating the services involved in these race conditions with the following:
Replace every search function with a publically bindable collection property
Having every create/update/delete call update every single one of these collections
This would allow the components to bind directly to the collections that are changed, which I think will cause every binding to it in any component to update without the needing to be explicitly told, and this in turn would allow me to ditch the "collection changed" event handling entirely.
But I'm hesitant to try this and haven't done it yet because it would introduce a fair amount of overhead on each major service function.
Other ideas? Please help. If a collection has changed, I want Blazor components that rely on that collection to somehow be able to update, whether through notifications or binding or some other way.
The following code is a heavy simplification of what I've got, and it's still causing race conditions when the event handlers are invoked from the service.
Model
public class Model
{
public int Id { get; set; }
public string Msg { get; set; }
}
MyContext
public class MyContext : DbContext
{
public MyContext() : base()
{
Models = Set<Model>();
}
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
Models = Set<Model>();
}
public DbSet<Model> Models { get; set; }
}
ModelService
public class ModelService
{
private readonly MyContext context;
private event EventHandler? CollectionChangedCallbacks;
public ModelService(MyContext context)
{
this.context = context;
}
public void RegisterCollectionChangedCallback(EventHandler callback)
{
CollectionChangedCallbacks += callback;
}
public void UnregisterCollectionChangedCallback(EventHandler callback)
{
CollectionChangedCallbacks -= callback;
}
public async Task<Model[]> FindAllAsync()
{
return await Task.FromResult(context.Models.ToArray());
}
public async Task CreateAsync(Model model)
{
context.Models.Add(model);
await context.SaveChangesAsync();
// No args necessary; the callbacks know what to do.
CollectionChangedCallbacks?.Invoke(this, EventArgs.Empty);
}
}
Startup.cs (excerpt)
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
string connString = Configuration["ConnectionStrings:DefaultConnection"];
services.AddDbContext<MyContext>(optionsBuilder => optionsBuilder.UseSqlServer(connString), ServiceLifetime.Transient);
services.AddScoped<ModelService>();
}
ParentPage.razor
#page "/simpleForm"
#using Data
#inject ModelService modelService
#implements IDisposable
#if (AllModels is null)
{
<p>Loading...</p>
}
else
{
#foreach (var model in AllModels)
{
<label>#model.Msg</label>
}
<label>Other view</label>
<ChildComponent></ChildComponent>
<button #onclick="(async () => await modelService.CreateAsync(new Model()))">Add</button>
}
#code {
private Model[] AllModels { get; set; } = null!;
public bool ShowForm { get; set; } = true;
private object disposeLock = new object();
private bool disposed = false;
public void Dispose()
{
lock (disposeLock)
{
disposed = true;
modelService.UnregisterCollectionChangedCallback(CollectionChangedCallback);
}
}
protected override async Task OnInitializedAsync()
{
AllModels = await modelService.FindAllAsync();
modelService.RegisterCollectionChangedCallback(CollectionChangedCallback);
}
private void CollectionChangedCallback(object? sender, EventArgs args)
{
// Feels dirty that I can't await this without changing the function signature. Adding async
// will make it unable to be registered as a callback.
InvokeAsync(async () =>
{
AllModels = await modelService.FindAllAsync();
// Protect against event-handler-invocation race conditions with disposing.
lock (disposeLock)
{
if (!disposed)
{
StateHasChanged();
}
}
});
}
}
ChildComponent.razor
Copy-paste (for the sake of demonstration) of ParentPage minus the label, ChildComponent, and model-adding button.
Note: I've also experimented with attempting to insert a block of code into the HTML portion of the component, but that didn't work either since I can't use an await there.
Possibly bad idea that I experimented with (and that still didn't avoid the threading collision):
#if (AllModels is null)
{
<p><em>Loading...</em></p>
#Load();
#*
Won't compile.
#((async () => await Load())());
*#
}
else
{
...every else
}
#code {
...Initialization, callbacks, etc.
// Note: Have to return _something_ or else the #Load() call won't compile.
private async Task<string> Load()
{
ActiveChargeCodes = await chargeCodeService.FindActiveAsync();
}
}
Please help. I'm experimenting in (for me) uncharted territory.
Since i'm currently in a situation that looks awfully lot like yours, let me share what i found out. My issue was "StateHasChanged()". Since i've seen that call in your code too, maybe the following helps:
i got a pretty simple callback handler:
case AEDCallbackType.Edit:
// show a notification in the UI
await ShowNotification(new NotificationMessage() { Severity = NotificationSeverity.Success, Summary = "Data Saved", Detail = "", Duration = 3000 });
// reload entity in local context to update UI
await dataService.ReloadCheckAfterEdit(_currentEntity.Id);
the notification function does this:
async Task ShowNotification(NotificationMessage message)
{
notificationService.Notify(message);
await InvokeAsync(() => { StateHasChanged(); });
}
the reload function does this:
public async Task ReloadCheckAfterEdit(int id)
{
Check entity = context.Checks.Find(id);
await context.Entry(entity).ReloadAsync();
}
The problem was the StateHasChanged() call. It tells the UI to re-render. The UI consists of a datagrid component. The datagrid calls a query in the dataservice, to fetch data from the DB.
This happens just right before "ReloadAsync" is called, which is "awaited". Once ReloadAsync actually executes, it happens in a different thread, causing the dreaded "A second operation started on this context before a previous operation completed" exception.
My Solution was to remove the StateHasChanged line completely from where it was, and call it once after everything else was completed. No more concurrent caller issues.
Good luck solving this, i feel your pain.