Blazor Pages rendering before async methods finish, breaking the page?
The below code is crashing my app because
applicationUser
is null when it's referenced in the HTML. This is a common issue I keep facing, how are you meant to handle it?
I thought the while loop would ensure that it can never continue but I guess it continues because it's returning a Task
?
ChatGPT suggests I added a Delay of 200ms after the command but it doesn't really feel like it would guarantee the issue is solved.
Thanks!95 Replies
it won't, and that's why you shouldn't be using chatgpt
you need to conditionally render based on your state
check if it's null in your markup and only render the stuff that depends on it if it's not null
Ahhh so in the HTML itself put the
@if(applicationUser == null)
bit to ensure it can never do it?yep
my components are full of if (not null) { stuff } else { loading icon }
if you want you could make a wrapper component for that
maybe there's one built in idk
Perfect thank you! I should've thought of that, it was a demo in the Weather Forecast class...
Something I don't quite understand is how it realises the variable is now no longer null so re-runs the page/component?
ASP.NET Core Razor component lifecycle
Learn about the ASP.NET Core Razor component lifecycle and how to use lifecycle events.
this could help
Thanks, will give it a read 🙂
Thank you!
also that DB access should definitely be async
I was hoping if I didn't have them async I would be able to avoid this issue. However, there doesn't seem to be a
UserManager.FindById()
, it has to be aysnc.if they aren't async your page is going to freeze while it's talking to the database
if you overrode the sync oninitialized method I think it would fix the problem
but you don't want that
because it introduces a much worse problem
there is no reason not to use async options for web stuff, especially blazor
You're right, freezing the page when it's talking to the DB isn't a great idea. I'll sort that out.
One more question - and this may be a very stupid one - But is there any easy way to get data from the Server to the Client?
For example, I wanted the Server to query the DB, get a list of bank transactions and then send them to the client so the client can manipulate them locally and push them back to the server when complete. That way the strain on the server is minimal.
The only thing I could find was to create an API endpoint and somehow get the auth token to the Client but it seemed over kill
Also, don't try to "avoid the issue" of async code. It's not an issue.
so the client can manipulate them locallyYou want the user to be able to just do anything?
if you have a server and client you'll need an API for them to communicate and some kind of authorization to protect it
My thought process would be:
User signs in, server downloads the transactions for that user to the client. User can then edit the data in their browser, hit submit and it'll push the changes to the server which can then update the DB.
but i suspect you're using blazor server considering you're accessing the DB directly from your components
Browser has exactly zero security, just FYI
If you let the user edit data however, they will be able to edit it however
Send a request to the backend that describes the changes
Validate that the changes are... valid
Perform them if so
That's fine, the server could validate the changes before pushing to the database, that wouldn't really be an issue.
To be honest I've gone off the idea anyway because I see how smooth these websockets will make stuff but I'm still curious to know how you'd do it
This is the bit I was struggling with:
I could get the user to sign in using the server side and I could access the API in the browser which would print out lots of json output. However, if I tried to do any sort of HttpClient request from the client side it failed because it didn't seem to have access to the auth token.
Since I've had that issue I don't really know why I would want client side things anymore if I can't get my db data to it... Again, could be me doing something stupid!
that will depend on how your authentication system works
When I created the project I set up Identity and then configured Microsoft SSO, if that helps at all 😕
you're most likely doing it wrong because that's how plenty of SPAs do it
What's an SPA sorry?
single page application
the kind of webapp that needs access to the token itself
Any ideas where I may have gone wrong?
I read somewhere I should just create a HttpClient on the client side. I realise it's usually best practice but they said the browser would be managing the ports anyway. So I did that but then obviously it doesn't naturally emed the tokens.
At the time I couldn't for the life of me get the server-side httpclient to inject into the client side one. The site would load but when I tried to do things it would error saying the HttpClient I had injected was null.
it really depends on your setup, whether you have cross site cookies, http only cookies, cors issues, etc
No worries, I gave up in the end and started again anyway!
not something i personally have a lot of experience with other than doing it wrong myself
Fair enough! 🙂 Thank you though! I appreciate it
you can modify the database in the same way that you query it
unless I'm misunderstanding something
just like you have some code in your initialization method to get the data, you can have a form or something with a handler that updates the data
Sorry i went to bed.
So the idea was the user would make changes locally, submit it to the server which would rub some validation and submit it if ok.
The problem I had was I couldn't even get the data to the client. It just didn't seem to have any knowledge that I was authenticated
is this blazor server?
yeah
with blazor server you're authenticated once you load the app
wdym when you say it didn't have any knowledge you were authenticated
yeah that's what was confusing me
with blazor server the C# code is running on the server already
you don't need a http client because your code is running in the same process as the rest of your server
one of the main benefits of blazor server; you don't need to authenticate every request
"request" used loosely, as there's really only the initial requests before you establish the signalR connection
yeah, you don't need a formal web API at all because there's no reason to use one
i send all my "requests" through mediatr so there's still decoupling but no actual http requests need to be involved
This is all exactly why I got really confused.
I had logged in, I was authenticated, I wanted to query the DB and return the resuts (
List<Transactions>
to the client so it could manipulate the data and I just couldn't get it to the client. Then when I googled it more it was suggested I needed to use an API to bridge the two
I thought the server and the client could work together but I couldn't get data from one to the other. But maybe I did something really stupidthat suggestion is wrong in the context of blazor server
This wasn't just blazor server. This was a Blazor solution which creates the Server and Client projects
that sounds like blazor webassembly
though i think that still just creates one project?
either way, in blazor server, you do not need a separate api
two seconds opening up VS to check
Blazor Web App. Not WebAssembly.
What complicates it more is latest version of Blazor doesn't seem to be very well documented just yet. Or wasn't a month ago when I was trying this.
looks like "Blazor Web App" is webassembly
that's annoying
I'm not sure I understand, it's got a server side so should do both?
ASP.NET Core Blazor hosting models
Learn about Blazor hosting models and how to pick which one to use.
Blazor Server and Blazor WebAssembly are two distinct types of application
Blazor Server runs all your C# code on the server and sends UI updates to the client over a SignalR (websocket) connection
Blazor WebAssembly runs all your C# code on the client via WebAssembly and relies on HTTP requests to interact with the server, like a typical SPA
Yeah and I think that may be hte issue I had with webapp. I was having to send new HTTP Request to the server so I had to have an API?
Yes, with Blazor WebAssembly, you need an API
Yeah... That's where I got to previously. I was hoping there was some way I could go between the browser and the server without an API but maybe it's just the Render Modes
That's exactly what Blazor Server is lol
so what situation would you maybe one the server/client one?
read this
It explains all of the benefits and limitations of each hosting model
ok will do cheers
render modes don't apply to wasm, only server
wasm webapps are always fully interactive
Sorry, another question about handling async calls.
It was mentioned yesterday that I should be putting the logic in the HTML side (so if accounts == null say loading). That way the component will automatically update when this is populated.
However, what do I do if I need to do two async calls?
In the below example, I need to get the AuthenticationState before I can determine the user and userId variables. If they fire too earlier then accounts will be null.
Or do I get the user in the code and then trigger a second method in the HTML?
This is what I'm currently doing but not sure if it's the best way? Note how I wait until applicationState isn't null to call the next method.
`
That's not what you want to do
HTML is markup, not programming
Honestly I'm not even sure what putting
RetrieveAccounts();
in there like that does
The simple way to think about it is just "don't try to render things that are null"It is working... But I don't know how else I could do it.
If I put it all in the OnInitialize then there's a risk it'll try to get the accounts but the userprincipal is null because async
You want to render a list of accounts. You can't do that if the list of accounts is null. What do you think the only thing you need to null check is?
I don't know, lol...
The issue I have is to get the accounts I have to get the UserID so I can do a LINQ query.
So I have to check the user has been retrieved from authenticationState first and then I can pass through the data to the service which gets the accounts.
So Ithink have to check each one isn't null and call them one after the other... It just feels messy
I guess what I'm saying is I can't get the accounts without the user details and that's async too. That's where I'm tripping up.
async void :bonk:
especially bad in blazor, pretty sure it's completely illegal in wasm
Sorry what do you mean?
you basically never want to use async void
*sorry
if you want to load your accounts after you get the authentication state, just do it in OnInitializedAsync
Maybe I've misunderstood async then I thought:
"Cheese" would print before MyFunction() may have finished?
no
await specifically waits until the async call has completed
So the method pauses/waits at the await but lets the method invoker continue?
essentially
the method pauses but the underlying thread is free to do other work
but "other work" is not anything to do with the function we're in
it could be any other tasks scheduled on the thread pool
Thread Pools bit out of my league at the moment but it sounds like that's where I've been tripping up. I thought it would carry on because a Task has been returned, even if it's technically an empty task for now.
it would carry on, if you didn't await the task being returned
AH lightbulb moment
e.g.
You can wait a variable?
you can await tasks
async methods return tasks, unless you use async void (which is one of the reasons you should never do that)
So in the example you just gave:
-
task
is assigned an empty taskno
I pushed enter too early
task is assigned a task representing the async work AsyncMethod is doing
it's not empty
Ok sorry just bare with me here, I'm struggling as it is without getting the terminology exact
So in the example you just gave:
-
task
is assigned a task but that represents the async work
- You then print cheese
- Then you are waiting for the task to finish and then I guess the variable is changed from a task to the actual result?the variable is always a task, tasks have a completion state and optionally a result object
Ok, good to know. I always found it interesting that you can access the data as normal afterwards.
you could
var result = await task;
or something to that effect
the problem with async void is that you can't await it, so your code would actually be trying to render a list that you may not have received yet
it also has other implications for handling exceptionsYeah that makes sense. I don't really need that method at all now I understand it a bit better.
My big mistake was thinking the method would continue processing
Thanks!
also as a side tip, it is generally better to separate your presentation logic and business logic
what you have is close but it seems to be doing extra authentication related stuff in order to set up the data service (idk what it does, maybe it's needed maybe it isn't)
If you were writing an application with a Database and the User should only see their Accounts, how would you implement that?
I seem to have to reverse-engineer their claims to get their userId and then I can do a Linq Query based on it.
yes, you'd pull it out of the authenticated user's claims
but you don't have to do that in a component, you could do that in the service itself
Oh interesting, I didn't realise the service could access that data
I just assumed it would have to be on the page.
with blazor server all your C# is running on the server in a single service scope per tab
so anything you get from DI can resolve any other services you've registered
Ok great, thank you. I hadn't considered that the cookies etc can be accessed by services. That'll make things a lot easier
Another question, I changed a method to it's async version but I'm getting an error
The error I'm getting is:
Argument 1: cannot convert from 'System.Security.Claims.ClaimsPrincipal' to 'MyApp.Data.ApplicationUser'
But this seemed to be fine when I wasn't doing the async versionthe signatures might be different between the sync and async versions
i don't use identity so i'm not familiar with those APIs
Ok no worries, thank you!