State Management in Blazor Apps

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everybody we are back with dotnet calm focus on blazer we have my good friend Jeremy lignan us all the way from the UK if he were to be on campus he would literally be standing right next to me but because of magic of the internet and technology we were able to make it work out this way so Jeremy thank you so much for taking the time out of your busy day to speak to us about state management and blazer what's going on with this it's my pleasure very very excited I finally got to visit a UK office I'm at the Microsoft scale up which is underneath the reactor here and I've got some some good Tibbets I think people will be excited to hear the information I have to share perfect all right let's get started all right sounds good let me go ahead and share my screen let's get over to the presentation start sharing and we're gonna pop over here all right looking good as we said all right fantastic well we are talking today about state management and blazer apps my name is Jeremy licinus I'm a cloud advocate with Microsoft and I'm excited to share some tips tricks techniques and solutions for you today but before I get started I want to set the stage for what we're going to talk about so imagine just for a second that you're filling out the world's longest online form and I know everyone's run into this you've got dozens and dozens of fields you've filled out your name your birth date the last seven cities you visited tons of information you go to click the submit button alright so everything's looking fine we're good to go something disconnected and you get that message that you're offline no problem right so you're gonna try to get back into your form unfortunately you're forced to start over that forms clear you hit the back button there's nothing there that is not a great experience in fact users having that experience are gonna throw the website in the trashcan figuratively of course so what we want to talk about is how we address this problem of managing state with inside Blaser apps and the first thing I'm going to focus on is what do I mean when I talk about state what is state inside a blazer application and it's really three key pieces of information that are part of that experience the user has on your website the first is the HTML or the UI this is literally the elements that are being retained in your browser it's the document object model or the DOM and all of that information now the UI is something that can usually be rebuilt from information but it's important as a user if I navigate away and come back to a page and I'm expecting there to be a seamless experience that that is a consistent piece of state that I'm presented with the other is of course the fields and properties it's the data that makes up your form and it's not just the data output that you're showing but it's the data being input by the hard-working user who's trying to get that information over the line to you and then it's also the services the beautiful thing about blazar apps is they can register and run different services and we want the state of those services to be consistent so that we don't have side effects or strange behaviors when we come back into the site now as people working with blazer know there are two different blazer flavors if you will and the first one is blazer webassembly this is the client side blazer this is what is still in preview but it is that blazer that runs completely inside your browser when you're running blazer webassembly the state of your application is retained inside the browser's memory that's the HTML Dom in code unfortunately because it's inside the browser's memory if you force a refresh on the page or if you navigate away from that page that's going to destroy that state and remove it from memory so I want to give you a quick example of what that looks like and then we'll go on to the other flavor of blazer everything that I'm going to show you is inside of this repo don't worry I'll share the link later on that's this blazer state repo but what we're going to do is launch the client-side version of this and what I did is I took a application that I built way back in the angular days I took an angularjs application if you remember those days and i ported that to the newer version of angular and then when blazer came out i ported it to blazer so that users and developers could see the experience and compare and contrast this is a very simple health form you enter some information I'm gonna put in some fictional info here we've just entered a height I'm gonna enter a fictional weight and it's going to update some of these fields and it's giving me my body mass index and some other information if I navigate within the application so I'm clicking on this navigation you can see it consistently shows the results we've got the same results here however let's refresh the age I'm just gonna force a refresh and we lost the information so that's one issue right there let's put this information back in and I'm going to enter that same wait but this time instead of clicking on the navigation I'm going to force the navigation by typing it into my url bar and again I lose that information so that's not a great experience that is client-side blazer next thing I want to look at is server side blazer in server side blazer although you do have some state in the browser most of that state is retained on the server and it's retained in something that we call circuits which is a signal art term but it's basically think of a sandbox for every session that's connected to that that blazer server so it's in the server's memory and because the server is maintaining that circuit if you refresh force a refresh or you navigate it will retain that that's in the server's memory it's taken over for you however if that server goes down and it disconnects when you reconnect by default that's going to go away let's take a look at the server side example I'm gonna go ahead and close the client example we're gonna switch to blazer state server set that is our startup project and launch that and we're gonna see an identical form and I think you'll find the way this is architected is it's pretty interesting we'll get into that in a second I'm going to go ahead and put the same information five-foot-nine winter 205 pounds we're going to navigate to results back to home that looks good let's force a refresh I'm just going to refresh my web page and that refresh happens so fast you might not have seen it but the server maintained that state I'm going to manually navigate to results and again it's maintained that so that's that's great that's a good behavior but if I open up my is Express and I stop the site I get this disconnection let's go ahead and quickly bring that server back up well the server rebooted so of course it's no longer maintaining state we go back to the default if I go to this tab and I reload I'm back to where I started this is the experience that we're trying to avoid let me close these and come back so before I dive into the solutions what I tried to package up for you is a set of solutions that will impact your code base as minimally as possible no one wants to have to write duplicate code in every component in every page that they add to a site we would much prefer to have some sort of service that handles the state for us unfortunately we can do that I took an approach of lowest common denominator I tried to build libraries so that the libraries are shareable as possible so at the bottom of this I have a view model that manages the health information we can look at this project blazer state view model if we look at the project file you can see this is a just Adonit standard toooo project so this can be shared across dotnet projects whether it's a WPF project it could be a xamarin application or in this case it's a blazer application that's the view model piece of this and I'm showing that here we've got our WPF our UWP xamarin the next step up one of the things I love about the architecture of blazer is the support for Razer class libraries Razer class libraries are again their dotnet standard toooo libraries but they can contain view information JavaScript images and CSS that are referenceable from other Blaser projects so if you're sharing things across multiple projects you can use that even server and client projects so if we look at blazer state shared we can see for example I'm just going to open up this age component this component is shared across the blazer server and the blazer client solutions that I showed you it's got some HTML markup we've got age we've got some calculations going on here probably not using Jimmy's Sat or CSS styling to do the the best practice for how I'm handling the validations but that's okay this is a very simple validation if we look at the code it's basically pulling in information from that view model and then it's checking for a range so if the age is outside of a range it's setting in error state so that we have a flag and everything else but the nice thing about is I write this component once and that component can be shared between the server and the client this also is using this health model base if we look at health model base what I've done is I've injected the view model into this base class and then after render I'm opting into property change notification now this isn't necessary for state management I chose to use the model view model pattern Blaser doesn't have an opinion of what framework that you use you're welcome to use whatever because I'm using the view model pattern I can have this base component that basically tells Blazer whenever the view model changes that's what we're doing here anytime some input changes we inform Blaser and it's able to rerender and update the UI as you saw in real time so that's what this does and this is shared if we look at our server project and we look at our dependencies and go into projects you can see that what this is used to thing is view model and that shared we go into our client project let me close this come back down to the webassembly project open up dependencies you can see it's using that same project so that's that next tier that we're using here I just made a rhyme and I wasn't expecting to but that's the Blazer and the web assembly so now we know how this demo was architected let's talk a little bit about solutions so there's several approaches to managing state in the blazer application it may seem obvious but it is not so obvious to everyone that service registration is one way to maintain state and it's sort of the first one I want to focus on and that's using the services to configure services and what do I mean by that because I just said everything's a memory or in that circuit on the server why do I care about this while I'm talking about state management well if we come over to this screen what I've done is just done a file new blazer project just create a simple client-side project I didn't customize it this is the out-of-the-box template and it comes with this convenient counter it's got some great examples of fetching data of navigation and of using a counter I've got this running let's go ahead and launch it I actually had it on another webpage that's fine now if we look at the counter out of the box we have this functionality we can click it and that's great I'm gonna go to my home page I'm gonna come back to the counter and notice that the count reset to zero why is that why did that count reset to zero it's because the count is a property on the component and it's scoped to the lifecycle of this component when I navigate away and the component goes away when I come back it creates a new copy and that component gets initialized with that zero value let's address this with a service this is the first step into managing some state in our project I'm going to create a new class so I'm just going to do add class and I'm very creative with my naming so I'm going to call this counter service you can see where I'm going here this is gonna expose account property and because I want the service to really manage this I'm going to give it a private setter and instead of directly manipulating the count we're gonna expose a method called increment it just adds one to the count very simple service I'm going to use my favorite feature here remove unnecessary usings make a nice clean class so we've got a class now we need to use that class we're going to come into startup right here and we're going to configure it as a service now typically what we would do is we would create a singleton I'm sorry we would create an interface and have an implementation for that interface because I'm making a very simple demo here I'm not going to take the time to add that interface so I'm going to tell it the counter service is the signature and counter service is the implementation but that provides one copy of that service throughout the application and if you notice my background suddenly got dark I don't know if I need to wave my arms or run around but we've got some energy-saving things going on here but it's quite all right as long as I'm still connected to the Internet right so let's go back to our counter class and let's use the service so the first thing we're going to do is we're gonna ask it to inject the service so I would like a copy of the counter service and let's just call it SVC for short so now I'm going to let the Blazer framework hand off this copy of the component for me now I can reference that component or that service I should say so instead of using the count on the component I'm gonna use a count on the service and I have my full intellisense everything's working out fine there on this click event instead of calling the components increment count I'm gonna call increment on the service itself and now I don't even need this code behind because everything's handled by the service for me this is live coding at its best what could possibly go wrong let's just run this and see what happens I'm building it right now and you know this trick right if it's taking a little long we just move the mouse clockwise to speed up the build and look at that it worked we're loading and we're inside the application let's go to our counter make sure it still works like it did before great we can click click click we're going to navigate away come back to it and it's retained that count of 9 so that's what I mean when I'm referring to service registration it's an important first step to be able to handle state within your application now there's another approach and that's the demo making the counter count there's another approach of encoding data in the URL I'm not going to spend too much time on this because I think URLs make more sense as navigation rather than state but you can't set up properties in your route and you can set up parameters they get passed into a component and you can have the URL encode some of that information again I believe this makes much more sense when you're approaching it from the perspective of navigation where I'm I in my application but that's also an option for you next thing we're gonna look at is the browser's own cache modern browsers especially the ones that support web assembly have built-in options for a cache here you're seeing an example of the local storage API and local storage is local to a site this will be a cache that exists for my domain for my url there's also session storage which as you would suspect is scoped to a session that means if I open a new tab navigate to the same site it gets its own copy of that cache and depending on the functionality you're trying to capture the type of state management you want you can either use local storage or session storage I'm going to show the example with local storage first thing we'll do is actually take a look at the application down here I've got a project called wasm local I'm going to set that as my startup project and I'll run it to show you what the functionality looks like and this will probably look very familiar to you you've seen this application somewhere before but notice that it's already pre-populated some values I'm going to go ahead and change those to keep the example consistent bring this up to five foot nine take this to 205 and then I'm gonna go to the results you can see that is consistent will force a refresh and it still retained those same values now we're getting somewhere I'm going to force the navigation here two results and it's preserved that value before I crack open the lid and go behind the scenes I want to show you another example because the solution that I'm proposing will work for local for a client-side web assembly blazer as well as server-side so let's set that server side as the startup project let's launch into some code and again you can see this has come pre-populated let's queue up our consistent values here that I've been using take this to 205 navigate to results now before I showed you that with the server-side those circuits maintained state so if i refresh or force a navigation then the results go away but what happened is when I came here and I went into IAS Express and I disconnected the server in this case by stopping it we get this disconnect let's go ahead and launch it again first thing I want you to see is this new tab that launched came in with our existing values there's the five-foot-nine there's the 205 and the results are consistent if I come back to this one and I reload the page it also reloads so now we've solved the problem how did we do this if I open up my developer tools I'll give you some insights I'm going into application navigate it to local storage notice their session storage I talked about that we're looking at local storage and we've got this health model which is just a json serialized version of our view model if we come down here it will expand it out and give us the different values now I'm not going to be able to zoom and change it at the same time but if you just take a look at the bottom part of your screen while I change these values I'm going to drop an inch off the height here and we can see this change to 30 1.2 1900 if we add that inch back on we got 30.3 in 1913 so adding an inch of height for a male will add 13 calories to the basal metabolic rate in case you were wondering how did we do this let's go back into our code now I implemented this in the shared project so this is one solution works for server side or client side can be shared across projects and the key to this is inside a storage helper before I go into the storage helper I want to show you how we share assets across Blaser projects in WWE let's get into the right project and WW root I don't know why it keeps bouncing around like that let me zoom in I have a stylesheet and I have this state management without JavaScript if I open the state management JavaScript file it should look pretty familiar this is what I showed you on the slide just that local storage in my Blaser server local project there's an app I'm sorry there's a it's going to pages there's a host dot CS HTML this is the host file for server-side Blaser you can see here i've included a stylesheet and the convention is underscore content the name of the assembly and the path to the resource the change from the previous example is that I also added this JavaScript so it's available to the server-side blazer the client-side blazer is going to look very very similar instead of the host CS HTML if we go into this wasm local and we go into WW root there's an index.html in that client-side blazer project this is the host for client-side blazer if we look at this though the convention is the same its underscore content name of the assembly and the path to the JavaScript so a very very easy way to add this the other thing that I did was I created a razor view a reusable component called storage helper there's a few things going on in storage helper so I'm going to step through this we've got some using statements we're injecting this ijs runtime this is an interface for JavaScript Interop and it's great that Blazers provided us an interface because then if we're doing unit testing we can mock that interface and not have to have it active within a browser for those tests to work so we've got that interface coming in we also have the view model interface this is the interface for the view model being injected as model I've got a template here that says if everything's loaded render the child content otherwise show this text of loading that happened so quickly that you probably didn't see the loading you saw the Blazer loading but not the the component loading but that's okay if we had a slower connection this would make it a better user experience we'd probably have a progress indicator etc so we're tracking has a loaded is it deserializing if you look at this parameter this render fragment of child content this is the convention that you use in Blazer to have a component that can wrap other components simply by exposing this parameter as a render fragment Blaser understands that there's going to be content inside this component that it should also parse and render etc that's just a convention that uses here's where the interesting thing start to happen when this components initialized we're calling the JavaScript runtime invoking it asynchronously and we're asking it to call this load method with a parameter which is just the name of the health model if we go back to our state management JavaScript state manager is sort of the namespace we've created it's an object on the global window and load is the function that just returns the value from storage that's all this is doing is grabbing it now there's a reason why I have this in a try-catch block and I'm catching invalid operation exception client-side Blaser this is going to work every time once that components initialized it'll call javascript it'll pull from local storage we're good to go on server-side Blaser the rendering process is two-step there's a server-side render that gets everything ready and emits the HTML to the client then there's another client-side path that takes care of anything that needs at HTML Dom to be active I'm catching this so that on the server side render when Java scripts not available when it throws that exception I just say that's fine well we'll catch it on the client side on the client side this will go through if we get that deserialized value we deserialize it using the built-in json serializer so we're just deserializing the health model and if we get that deserialize value we set this flag and we move the values over so we've got this copy remember this the view model variable is what i deserialized up here the model with capital m is the view model being used throughout my applications so I'm moving it from what I DC realized over the reason why I'm using this flag is because of what happens down here when the model property changes if it's deserializing we return otherwise we serialize it to storage basically what this does is it says anytime some property on the view model mutates update the copy that we have in the cache now this will happen any time something changes but you can set your preferences how you want for example if you want to batch update if you want to capture a few changes or even wait for a period of time however you want to handle this in my case any time it changes i'm serializing it out if i did this without the flag just updating the model would get us into a endless loop because I would deserialize I would update it to the model that would trigger a property change the property change would serialize it and would go back and forth so I have this flag so it knows that hey I'm taking care of moving it from storage now you're fine otherwise capture those changes now this is one component again all this component does is it interrupts with JavaScript to serialize that JSON and it taps into that property change notification if we look at the way we pull it into our application again something I love about blazer the way components work is if we look at our server local we have app dot razor this is where our router exists and we just wrapped the rosen router in that storage helper just one place in the application now it's automatically handled throughout the application for us I don't have to do any other changes to code interestingly enough if we come to our client side project and we look at app dot razor you're gonna see the exact same so I'm using the component from a shared library the exact same way to manage that state the only other caveat I'll share before I move on to another method is obviously this is taking place in plain text if you have sensitive information then you're going to want to take some extra steps either store it on the server which we're going to talk about next or encrypt it fortunately there are a few ways that you can encrypt this there is a data protection services package that you can bring into your application that's provided by Microsoft that you can use to encrypt and decrypt data so if you have sensitive data want to protect it one layer there's a fairly straightforward API to do that however there's also a blazer component that's in experimental preview right now but it's a blazer storage component that raps all this functionality for you and does the encryption and decryption so that's another option that you can look into as well and all of this documentation is available on the github repo by the way so I'm going to step out of this server-side persistence now when we look at server-side persistence this should not be unfamiliar this is a typical interior application and the model that we follow a lot of times in the web is we have our client that's running inside of the browser and it's using api's to reach out to a server and that server is managing information it might be using a sequel database and no sequel database cosmos DB Azure SQL whatever that is that's being handled and marshaled back and forth by an API we're used to writing applications that have very explicit interactions with api's so that I click the Save button and it's explicitly going out calling a save API but there's no reason why we can't automate that to manage our state as well what I'm going to do is jump into this now I've already demonstrated that this will work consistently for server-side and client-side so I'm just going to use the client model what I did here is I created a project that's a blazer web assembly project but in this case I tooked that little box that said asp net core hosted so what it did is it created the stet the project that creates the static assets for the blazar client but it also created a host so that I can stand up api's and controllers and in this host I happen to have a state controller the state controller is where you would do something like use a Redis cash or use sequel or use no sequel or whatever persistence mechanism of your choice in my case to keep the demo simple I'm just storing it in memory I have a static dictionary I'm not doing any you know concurrent dictionary or anything like that there's a lot of practices for production that you would want to look into but to keep the demo simple this exposes a get in point and the get in point is I'm cheating a little bit just getting the IP address that the gets coming from and returning the model from the cache and then there's a post of a view model that stores it in the cache I'm using the IP address because I don't have any authentication wrapped around this project again to keep it simple you would probably have a user login authenticate and you would be using that user ID or information as a key to store this but this is as straightforward as we can get on a controller on this end point I can post a view model to it and I can get a view model from it again in my shared project so I can reuse the code across Blaser projects even if they're server-side or client side I created a state service this state service gets a copy of the view model this time I'm using constructor injection instead of property injection it's getting a view model it's getting this state service config option and it's getting a copy of the HTTP client why would I inject the HTTP client this is an important step if we're using the HTTP client and Blaser webassembly there's a special configured version of the client that understands how to work inside the browser sandbox you never want a new HCV client and client-side Blaser webassembly you want to have that injected so you get the appropriate copy on the server side I'm assuming it just injects a fresh copy of the HTTP client what we're doing is we're saving those values and once again because I'm using the MVVM pattern I'm plugging into property change notification if you were to use some other tool like redux redux for state management you would plug into your actions in redux and do something similar so there's an approach there's some hook that happens when the state mutates and that's what you plug into I'm exposing this initialization method that's calling the endpoint this is just using the HTTP client saying get that endpoint with a URL that's passing through configuration deserializing it moving it over and then on the property change notification it's posting it out so this is doing a server post instead of a client cache that's pretty much it for the the details of how this is working if I go into my project and I look at my startup what I've done here and my startup is I've implemented this configuration service so I can inject the URL end point here again bad practice I'm hard coding it you would use whatever configuration approach you want to dynamically pass in what that endpoints going to be but I need some way to pass that endpoint into the service and then I'm adding the view model adding the service configuration adding the state service the other thing that I have to do is I'll automatically handle property change notification but I need to handle that initial load the first time you go into the application so what I'm doing is in my pages when I route to a page when that page is initialized I'm calling initialize on the service let's go ahead and set the server as my startup project and run this okay so we're in our default now I'm going to add in that weight and I'm going to go ahead and force a refresh and you can see it populated I accidentally put 295 instead of 2:05 and if I force that navigation to results we get a consistent result in results and I'm just going to show you one more thing if I open up my network and I change a property you can see it's making these calls to state it's dynamically posting the update and then when i refresh it's calling it back so that's a way of handling server side again you could throttle it you could have some check point to save it etc however you like so to recap what we covered we could do nothing and have a terrible user experience we can store it in memory which is recommended so that your services are consistent this will last as long as the session or the connection is there we can do it on the client side with the cache and the storage that's available there and then we can use the server side and we have examples for these all of these examples are in this github repo github calm Jeremy lick Ness which is also my Twitter blaze our state that also has links to the official Microsoft documentation for state management as well as some other tips and tricks for you so having said that I think we have a few more minutes left I'd like to open it up for questions hey Jeremy yes we do have a couple questions that were that our viewers have been putting up and asking via Twitter the first question is I was wondering a blazer component automatically refreshes when a property's inject an injected service or view model changes or if we have to subscribe to its property change event and call state has changed that's a great question and that's why I wanted to emphasize that the state management for a blazer era yeah the change management for Blazers opt in it's not assumed so if you're using an MVVM pattern blazer can make some assumptions if a property on a component changes it knows it has to rerender it but if you're binding to a view model there's no real way blazer knows that view mode doesn't have automatic detection that says it's using property change notification so that's why I use that base view model to plug into the property change notification and call status change so there is a way to inherit so that you only have to do that in one place you don't have to duplicate that code across your components but you do need some mechanism to tell Blazer that yes our data is mutated and you should update by calling state has changed perfect that's a great answer so Beth has another question up for us let's see is it possible to have the app reconnect automatically without requiring the user to reload the page after you make any changes right now I believe that there's a timeout that's set and then what it's going to do is if the server comes up within that designated period it's gonna auto reconditioning out-of-the-box that there's some code that you can write in JavaScript for example that says after a period of time pole for that connection and try that again perfect that's a good and that's a great answer I think we got another one up that of here perfect are there any current libraries for handling state I've heard of one called blazer state do you know any of anybody that's creating something that's reusable for people to just kind of plop into their applications the main one that I'm aware of because I believe it's refer reference in the Microsoft documentation is the blazer State and the nice thing about that component is it doesn't force you to call JavaScript directly the way that I showed in my simple example it provides an actual service for state management and it does have the protection services built-in so its handling encryption for you so that's a great one to look out for I am not familiar with other out-of-the-box solutions but it is a common problem to solve so I wouldn't be surprised if they exist perfect well thank you so much Jeremy for taking the time out of your evening to talk to us we really appreciate everything you presented for us and that's it
Info
Channel: dotnet
Views: 23,504
Rating: undefined out of 5
Keywords: .NET
Id: zjlUstW7ISU
Channel Id: undefined
Length: 41min 31sec (2491 seconds)
Published: Thu Jan 16 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.