Blazor Pre-Rendering

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
I've been doing a few videos recently on ASP.NET  Blazor and the distinction between Blazor   WebAssembly and Blazor Server: Blazor WebAssembly  where everything is processed in the browser and   Blazor Server when the processing is done back on  the server. But what I'm going to talk about this   week is a way to combine the two to solve quite  a common problem in web development - across all   different technologies, when you're doing SPAs,  Single Page applications - the problem of the   initial load. So let's have a look at what we mean  by that. And so what I've got here is just a very   simple Blazor WebAssembly application. And if I  run that up, then we can see that when the page   initially appears, we're getting that status  display to show that the page is loading,   once the page ha s loaded, then we can navigate  within it to the different tabs and that sort   of thing. And that's very, very quick, because  all of that happens within the browser itself. So   there's no more calls made to the server for that  sort of thing to happen. But if we do something   like a refresh … so in navigating to it again,  again we have this wait of a few seconds, and the   indication that something's happening. And that's  the problem that we get whether you're doing this   sort of thing with Angular, or with React.js or  here with Blazor, that when you've got this idea   of what they call a single page web application,  it has this odd way of starting up. Because if we   actually take a look at the page source, this is  what was initially downloaded when we navigated   to the site. So it's just a very simple HTML page  with very little on it. Basically, just there   we've got the way that it was displaying that  circle for the progress. But then the real trick   that we've got here is that we're pulling in  various scripts - like the one we've got there,   and that one there. And that pulls in the whole of  the Blazor WebAssembly framework that's required,   plus it pulls in all of our DLLs that we've  written in our particular application. But   that's what slows it down, because this initial  page is what we request. That comes down and then   that makes further requests for the other  files, which then have to be pulled in,   then have to do the processing within the browser.  And only then does the page actually appear. So   that's why we have those various steps and that  delay. And although it's not an awful experience,   sometimes it'd be nicer if you just immediately  got what we're seeing here, the actual thing   we have on the page. And with a server-side  application, like written in something like MVC,   that's exactly what would happen. But then, of  course, with something like MVC, every request   goes through that. So what we want is to have  the very first view of the page processed on the   server and delivered to us whole, and then after  we've done that have the client-side processing   in WebAssembly that is going to do the work for  us. And it's very common problem in all of these   SPAs, if you're using something like React, then  there's also a library called Next which will help   with this sort of thing. But we're going to see  how we can do this with Blazor. Now it's worth   pointing out immediately one slight disadvantage  to what we're going to do. If you look at this   simple Blazor application, as I said, there's  no server-side processing at all. All the web   server does for an application like this is simply  does what a web server did back 30 years ago: it   just serves up files. So the browser requests the  files - gets them. No processing on the server,   so no high-powered computing needed. So it  can be very much cheaper for hosting than what   we're going to see here, where you do need some  server-side processing. So what I'm going to do   is do a new project. And this one is going to be  a Blazor WebAssembly app again. So we'll click on   that one. And I'll call this ServerSideStartup.  And then the really important thing I've got to   do here is click that box there. 'Cause if I didn't  do that, I'd get exactly the same sort of project   I had before, which is pure WebAssembly - only  going to run the browser. Putting this in here,   we're now saying ‘ASP.NET Core Hosted’ that  is going to create a server project as well   as the client-side project. You can see we've got  that there. So there is our server-side project,   so everything in there is going to run on the  server. Here we've got our client project. So   that's what's going to run in the browser under  WebAssembly. And one of the nice things about   this, you can also have classes that are shared  between the two. So that's what I've got in   that separate library that we have there. Now the  reason initially that you do this sort of thing is   typically if on the server, you want to be running  a web service that's going to give you data. And   in fact, that's already configured here because  if we look on the server, you can see you've got   the traditional WeatherForecastController that you  see in a lot of these applications. And that just   gives us this weather forecasting. And something  to just note while we're looking at that is the   way this works is it randomises the data so  the temperatures we get there are randomized.   Remember that, because we'll see that later  on. What I'm actually going to do here, though   is just to simulate the real world, when we'd  be running on the internet, not just locally on my   machine - let's just put a delay in here, so it's  a little bit slow to get this data. So I'm going   to turn that into an ‘async Task’ of all of that.  And then we'll just in here say ‘await Task.Delay’   and then we'll make it two seconds. And the  reason I put that in is just to show a further   slight problem we get when we're doing rendering  in the browser. That is, if I run that up again,   then of course, we still get the delay on  startup and then finally get that. But if   I go to this Fetch Data, you can see there's an  additional delay that's mostly due to that two   seconds I deliberately put in - but it will  still be there in the real world. And so if   I actually do a refresh on this page, we get the  one delay for downloading the application and then   once we're in there, we get the second delay  of waiting for the data to come. So ideally,   we'd like that page just to appear all there  all ready for us. So that's one of the things we're   going to solve. So how do we go about doing this  - to make this so that we don't get that circle   to indicate that we're waiting, we just get the  data straightaway? Well, there's a number of steps   we've got to do for that. First thing we have to do is  … at the moment, the thing - as we said - that's   being downloaded is this Index page. So that's  really what I was just showing you in the browser,   where we had the <div> for the loading progress  and that sort of thing. We need to replace that by   a server-side page. So in the server project,  under Pages, I'm going to give this a new   Razor page - not a Blazor page, a Razor page - I'm  going to have that as an empty one and I'm going   to call this underscore ‘_Host’ – call it what you like, really, but that's quite a common thing   to call it. And so we're on that, we just get  that basic Razor page in there. So remember,   this is running server-side. Then what I do is  I go back to that Index, and I take the whole   of that and paste it into this new .cshtml page  that we've got there. And the only thing I need   to leave in there is just the ‘@page’ at the top  so that it knows it's a Razor page. And then I can   also delete a couple of things. I can delete the  Index.html, because we're going to be effectively   generating that from our _Host. And then I'm also  going to delete the code behind because we don't   actually need any code here so I can delete that  as well just to simplify things. So now, this is   going to be the page that has some server-side  processing, but then gets delivered to the browser   as the whole thing. So what we want to do there is  firstly, we just need to enable tag helpers. So if   you're familiar with MVC, then you'll be familiar  with tag helpers, but that's what we're going to   do here. So I'm going to say ‘@addTagHelper’  and then I want all the tag helpers from   Microsoft.AspNetCore.Mvc.TagHelpers. So these are  just the standard tag helpers that come with MVC.   And then what I'm going to do is in here, I'm  going to get rid of that loading progress and   I'm going to put in there a ‘<component>’. So  we're going to use this idea of a <component>,   so this is what we can do in server-side  rendering. And that <component> is going to be   of the ‘type’ and that's going to be ‘typeof’  and then we're going to use the application   component in the client-side application. So in  the client-side application, you can see we've   got this thing called ‘App.razor’. And that  actually is the starting point for our site,   so that is what we want to render, but server side  in here. So what we need to do is ‘typeof’ and   then just the fully qualified name. So that's  going to be ‘ServerSideStartup.Client.App’.   And so that's the thing that we're going  to be rendering server-side. And then we're   going to do the thing that's really key  to the whole of this, we are going to set   a ‘render-mode’. And that render-mode is going to  be ‘WebAssemblyPrerendered’. So does exactly what   it says on the tin - this is what we're trying to  do prerender it. So that's basically just saying   do it the once on the initial load, but not then  subsequently. So that's what we put in there. So   this is going to run on the server and generate  the entire of our first page as if it would be   doing it when it runs on the browser normally.  Got to do a little bit more there. We've also   then got to go to the program - again the server  program. And in here, if we just go down here,   you can see that at the bottom here, it's saying  ‘app.MapFallbackToFile(“index.html”)’. So that's   saying that's what we should initially download,  which of course we've just deleted because we   don't want that anymore. So we change that  not to map to a file, but to map to a page,   because we're going to have to do some  processing on here. And then we put in   what we just had, which is going to be a ‘/_Host’,  the name of the file that we just wrote. One   other thing we're going to do, just to avoid some  errors, is if we go to the program in the client,   you can see what it does there is it looks  for a <div> with an id of ‘app’. Well,   that <div> we actually just deleted because back  in the _Host, when - if I just control-Z that   a bit - when we deleted all of the code that we  had here, there is our <div> with the id of ‘app’,   so it's not going to be able to get hold of  that anymore, because we've basically replaced   it by putting this in here. So we just need to  remove that line of code - we can also get rid of   that ‘using’ as well. So that should really be  all we need to do. So if we now run that up,   then we can see that the page appeared immediately, if I click refresh, you can see,   although obviously, it's still got some slight  delay, it's not got the progress bar. It is just   taking it straight there. And we can see that  difference slightly more concretely, because if   we look at the page source, you can see the page  that was actually delivered to it had the things   we want. So you can see, we've got the ‘Welcome to  your new app’, we've got all that sort of thing in   there, because that was generated on the server.  And that's what's been handed over. We do still,   of course, have the various JavaScript files  that we need for the WebAssembly, because once   it's delivered that initial page, all the other  interactions are now occurring within the browser.   So it's still got to download that as well.  But it means it can display as the initial view   and then download those things in the background  rather than us waiting for those downloads before   anything happens. There is however, still a slight  problem. If I go to the Fetch Data … Now that one,   we had the slight delay that I deliberately put  in there when it was delivering the data on the   server, but it still worked perfectly well because  when I went there by navigating within the menu,   that was still a client-side navigation.  But the problem is if I refresh this,   now what it's going to do is go back to the  server, try and get the server to generate   this page server-side and give it us back. And if  we do that, we get this horrible error. And the   reason is - you can make itout basically from  what's happening in there - is that we haven't   configured dependency injection. Because if you  look at how that client-side page works … so here   we are in the client project, go to FetchData  and we are using HttpClient to make that work,   which is done with the injection of HttpClient,  which is configured again in the client-side   program. And you can see there, that's where we're  configuring it to work. But remember, this one is   only run the client-side. So when that same  page gets run for the prerendering on the server,   it can't find the injection. So all we've got to  do is remember to inject it in both. So we'll go   to the original one and we'll copy that and then  go to the server-side program and we'll just go   up here and paste that in. We then hit a slight  problem, because this HostEnvironment only makes   sense when we're doing Blazor WebAssembly,  because it's telling us what's happening on   the backend server. We don't have it here, so we  can't get the address. Now, there's lots of ways   we could do this, but I'm going to really just  do it in the slightly hacky way. I'm going to go to   the LaunchSettings and let's just get hold of that  one there, which is the one we're using, and paste   that in. So in a real application, you'd obviously  have to have some way of configuring that. But for   this quick demo, I'm simply going to get  rid of that and put it in there. And now we   have a server-side configuration for that  injection. And so now if I run that up,   obviously that all runs up okay, but now if we go  to the Fetch Data … well, that works fine, because   that's the navigation within the browser. But  the important thing is now if I do the refresh,   then it takes a moment because we've got that two  second delay still happening there. Obviously,   we'll finally remove that. But then you see it did  something a bit odd. Let's just watch that again.   So it's delivered us directly the data, but then  it does another ‘Loading…’. And then it gives us   back - and in this case - some different data. And  it's different data because of that randomization.   So remember, the weather forecast is random  each time. And the problem is we're requesting   it twice. So we do the refresh that requests it  on the server, so gets the data from itself puts   that into the completed page. But then once the  page has been downloaded, then all of our Blazor   WebAssembly kicks off. And the code running in  the browser makes another request for the data.   And that's why we're seeing what happens there,  that it initially loads it - we've got that first   temperature of 17 - and then it loads it again.  And in this case, we've got a completely different   temperature. Now, in most cases, actually,  making two requests at approximately the same   time - within a second or two - to a web service  would give you back the same results, so the data   won't actually change. But it's still inefficient,  once you've already got the data, to be wasting   time getting it again. And this is something that  was solved only as recently as .NET 6. So this is   a fairly new feature that we've got in Blazor that  can get us around that. So what we have to do is   this, firstly, we go back to that _Host page that  we just created and we add another tag helper,   and that is called ‘<persist-component-state>’.  So don't have to do anything special, that is   just going to be there. And that just configured  the system to do a few of the things that we need.   Then what we need to do is go to the actual  Blazor component that's causing the problem,   so that is our FetchData.razor’. And what I'm  going to have to do is a couple of things.   Firstly, I'm going to do another injection,  so ‘@inject’ and then this is a thing called   ‘PersistentComponentState’ and so that's  what we'll inject. And we'll just call that   ‘ApplicationState’. So that's the way that we can  do dependency injection in a Blazor component,   and so that means we've now got an additional  property that's generated there called   ‘ApplicationState’. The other thing I'm going  to do is I'm going to implement an interface.   And that's simply going to be ‘IDisposable’,  because we've got to do a little bit of tidying   up. So then let's go down to the  code. So all of that HTML can stay the same,   but in the code we've got to do a few more  things. So the next thing I'm going to do,   after we have that array of WeatherForecasts  that we're getting back here on the HTTP request,   I'm going to put in there another class. It's  going to be a ‘private’ and this one is called   ‘PersistingComponentStateSubscription’ -  and we'll see what all these do once it all   fits together. And let's just call  this thing ‘persistingSubscription’.   And then in our OnInitializedAsync, where we  are loading the data from the web service,   this is where we've got to make a decision.  So we've got to discover whether that data has   already been displayed as part of the prerender,  in which case, we can just use the existing data   again - we don't need to make the HTTP request.  Or if we haven't had that data, because we're   navigating to this page within the browser  rather than refreshing from the server, then   we are going to need to get that data. So first  thing I'm actually going to do is put in here,   a ‘private’ method, returning a ‘Task’ and  is going to be called ‘PersistForecasts’.   And so this is where we are going to do  the storage that we can retrieve later   on. And it's very simple, we can just use that  ‘ApplicationState’ - which remember was the   property that I just configured for injection  - and from that we can say ‘PersistAsJson’. So   the data we persist has to be something that's in  a format that can be turned to JSON. And then we   just pass in there the key that we're going to  give it just so we can identify it in future.   So we'll call this ‘weatherData’ - so just any  label that you want - and then the actual data,   which is going to be our ‘forecasts’. So that  will store that information there for us. Notice this   is returning a Task, but we didn't have anything  async to do, so we just have to say ‘return' and   then ‘Task.CompletedTask’. So that just keeps that  happy. Then the last thing we've got to do is in   our ‘OnInitializedAsync’, where we're getting that  data, we then have that ‘persistingSubscription’   that I just created. And we initialise that to,  again using the ApplicationState, but this time   we can say ‘RegisterOnPersisting’. And then we  give it that function that I've just written,   that ‘PersistForecasts’. So that PersistForecasts’  isn't going to be called immediately - we're   registering it so the ApplicationState can call  it if necessary. And then having done that, we're   going to say ‘if’ and then ‘ApplicationState’  again ‘.TryTakeFromJson’. So what we're doing   here is trying to see whether we've had that data  persisted. That then is a generic for the type   of data that we're storing. So the thing that we  persisted – ‘forecasts’ - we know is this array   of WeatherForecasts. So we'll put that in as the  generic type, so that it can deserialize it. Then   the next parameter we have in there is the key. So  that was the ‘weatherData’ that we had in there.   And then as an ‘out’ parameter, we can have the  actual data we're fetching out, which we'll just   give the name ‘restored’. So what we're doing here  is we're basically saying, if this PersistAsJason   has been called, we'll get back a true from here  and we'll be given that data that was stored. And   so in that case, we can simply say ‘forecasts  = restored’. So just put that restored data   into the forecasts. Otherwise, if it hasn't been  stored, then we'll just do what we did before and   we'll actually get the data. One final thing we've  got to do there, which is we have to implement   IDisposable, because, remember, we declared that  at the top. So we'll have a ‘public void Dispose’.   And then what we have to do with  that is simply we've got to Dispose   this persistingSubscription - so that is also  IDisposable. So when the component is disposed,   we've got to dispose that as well. Now  let's run that up - see what happens.   Obviously, we get that there quickly. We go  to the Fetch Data, that first one it did,   by going to the page in the browser and  then calling the server. That's why we got   the delay. But now let's see what should  have changed. Now, if I do the refresh,   there we get the new data coming back. But  that's it. That is now staying where it should   be. It's not doing that second call, and us seeing  different weather forecast data and all of that.   Let's just debug that a little bit so we can  see what's going on. So all I'm going to do   is put a break in the PersistForecasts  and a break in the OnInitializedAsync.   And what we should see here, if we navigate  to that page, then we're coming in here,   doing the OnInitializedAsync and when we try to  get that data, there's no data there, because we   haven't done any server-side rendering. So we go  and fetch the data. And then we'll get - with a   two second delay - that coming back. However, if  I click refresh here, then we hit that. And the   thing to remember now is this bit of code that we  can see at the moment is running in the server.   Okay, so this code is running in the server.  And as we step around in that we haven't got   the data yet. So on the server, it calls its  own web service to do that. And then still on   the server - and you can see we're still on the  server because we're in there at the very root   of it all - it takes the data and it converts it  to JSON and persists it. And then that JSON data   will be sent off with all the other stuff down  to the browser. Now, if I do another continue,   now we can see it's about to restart. So now on  the browser - as you can see here in the stack,   that's clearly running on the browser - now as  part of WASM. It's now that it comes into the   OnInitializedAsync. It has a look to see if  it's got persisted data and it has. So that   restored data is the deserialisation of the JSON  data that was serialised by the server and sent   along with everything else. And so there we've  got our data displayed without the refresh. But   if now we look at the page source - so this is  what was generated on that first request by the   prerendering - and look down here, there we've  got the ‘Blazor-Component-State’. So that is   that weather forecast data. We'd said we’d encoded  to JSON, but it's then further encoded to Base64,   just to make it more compact. But that's where the  client-side component reads the data from that it   got back from that persisted application state.  So quite a complicated set of steps there, but   really quite an important one that you want to do.  Otherwise, you'd have lots of these situations,   whenever you went to a page that made an HTTP  request back to the server, it would deliver   the initial data through the prerendering and then  waste time - and possibly have differing results   like we had here - by making that second call. So  quite a lot of things that we need to do there,   but it's fairly straightforward. If you follow  the steps and do all the things I've done there   then you will have got yourself a prerendered  Blazor WebAssembly application. So I hope that   was useful. If you enjoyed it, do click like.  Do subscribe, and I'll see you next time.
Info
Channel: Coding Tutorials
Views: 3,450
Rating: undefined out of 5
Keywords: .NET, C#, SPAs, Prerendering, captioned, Blazor, WebAssembly
Id: mdDcNGlJFKk
Channel Id: undefined
Length: 24min 50sec (1490 seconds)
Published: Fri Feb 24 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.