What do you want to learn?
Skip to main content
by Carlos Saloio
Start CourseBookmarkAdd to Channel
Table of contents
What Is a Dev Console?
What Is a Dev Console?
Hello. My name is Carlos Saloio, and in this course, we're going to be walking through the creation of a developer console that could be added to your existing or new web applications. Adding a console into a web project can be a lot of fun and a huge time saver both during development and after the project is completed. But before I get into the reasons why I think having a developer console is a good idea, let's first make sure we're on the same page as to what I mean by the term, developer console. By a developer console, I mean a CLI, or a Command Line Interface, that you can bring up at any time from within your web application and that can be used to enter commands to have your application perform various tasks. Now the user experience that I have in mind is very similar to that of the consoles found in video games. So if you've ever played around with the consoles available inside of a game series such as Quake, The Elder Scrolls, or The Witcher, then you have a good idea of what I'm talking about. But in case you haven't, let's take a look at an example so that you'll have a clearer idea of what we'll be looking to achieve. Unfortunately, due to copyrights, I'm unable to show you an example from a real game. So, what I'm going to do is attempt to simulate the experience here of a fake game that I've thrown together using a web-browser. Now because video games are pretty sophisticated pieces of software, trying to test and debug them can be challenging. And one of the tools used to do this is a console. Or what I like to call a developer console. By convention, consoles are activated by pressing the Backquote key on the keyboard, it shares the same key as the tilde and is located in the top-left portion of the keyboard just below the Escape key. And you'll notice that when I press that key, that the lower portion of the screen has darkened giving me a rectangular area to enter in text commands and receive output. And then I can close the console and resume the game simply by pressing the Backquote key again. So if I wanted to, I could now enter in a command, such as help, which lists all the commands available, and we've got a few to choose from in this exam. Let's begin with the weather command. The sky actually does look a bit threatening in this scene. So how about I use the weather command and pass in an argument of rain. And just like that it's now raining. If you look in the top left corner of the screen, you'll notice that I have a Health and Magic bar as a part of my hud. Now let's say that I'd like to similate having low health. So, I can use the health command to set my health down to maybe 10% and after that maybe I'd like to cast a powerful spell, but don't have quite enough magic available to do it. So I can use the magic command to boost that up to 60. And there's a basic command history here, so by pressing up on the keyboard I can bring up a prior command so maybe I'd rather my health be around 80. One of my favorite things to do with a video game console is to instansiate objects into the game world which we can do in this demo with the add command. So let's say I wanted to add an archer or perhaps a mage into the world. And then it might be interesting to also have maybe a knight and then finally let's add in a dragon. And after playing with these 3 a bit, maybe I'd like to just focus in on the knight. So, I'll use the kill command to remove the dragon object, and then I'll use it again to remove the mage as well. leaving us with just the knight alone in the rain. And of course if I were to enter in a bogus command, I'll get an error message back. And while--you get the idea. So a developer console allows you to give commands to your software at runtime via a command line interface. Even though the application may in fact be graphical in nature.
Why Create a Console for Your App?
So hopefully, you now have a decent mental image of what a developer console is and what the user experience is like. But you may not be convinced that your application would benefit from one, after all, it's entirely possible that the software you're working on doesn't involve either mages or knights. In fact, I would guess that for many of the people watching this video, the dragons in your apps tend to look a bit more like this. Now that's still pretty scary, for sure, but obviously it is a bit different. So how does a console like what we just saw translate into a web application? Well let's take a look at a slightly more mundane example and see. Let's say you're developing a line of business application for the company you work for and it's called The CRUD Master 2000 because it essentially performs a lot of different data access operations, such as creating, reading, updating, and deleting various records in the database, and as a part of this application, you have a login screen, and because you care a lot about security and want to prevent brute force attacks, you really want to implement a feature into the login process that locks the account of any user that enters their password incorrectly more than say five times. Once that happens, an administrator needs to unlock the account before they can sign in with it again. Now it may be a while before it happens, which you know eventually someone, we'll say it's Jane in accounting, is going to forget their password and will need to have their account unlocked. So obviously at some point during the development process, you'll need to write the code to unlock an account. And taking a look at this problem with your developer hat on, you realize that this is really a very simple thing to do. It really only requires a single line of code to unlock an account and probably looks something like the following in SQL. So here we're saying that we're going to update the account table and set the IsLocked field to 0 or possibly false depending on your system, where the username is jane, and that's it. Piece of cake, right. Probably could execute that command on the database in less than 30 seconds from beginning to end, but then you put on your designer hat and start working through what that translates into in terms of a web user interface and all of a sudden it doesn't seem quite as simple anymore, so let's walk through it. Alright, so let's imagine we've got an administrator working with the application and they're carting along when all of a sudden they receive a phone call telling them that Jane needs her account unlocked, so how do they do that? Well, we could have a header area on the site, and in that header, we might have a link called admin that when clicked would cause the user to navigate away from what they were doing and switch over to a screen for performing administrative operations, and by the way, this admin link wouldn't appear for all users. We'd want to code it so that it would only be visible if the user was, in fact, an administrator for this application. And from the admin screen, there's likely to be a bunch of different administrative subsections that they could go to, but amongst them, there would likely be one called something like accounts, right, so they click on the account link and that would take them to yet another screen that had a bunch of account-related options in it and you can imagine it might have operations for maybe adding a new account or what not, but somewhere in here, there's got to be a way to identify a particular user account that we want to work with. So maybe there's a search box where we can type in Jane's username and that might send off an AJAX request to the server returning a list of users with a username matching that search text and then we can choose Jane from amongst those results. And now, we go to yet another screen, which shows us the account or profile page for Jane and now somewhere on this page will be a link to unlock Jane's account. So now we can click on that link and Jane's account should be unlocked. So great. We've done it, but that did require the admin user to navigate away from what they had been doing and search through various options across several pages in order to perform the unlock operation. Now let's take a look at what the same situation might look like with a dev console in place. So once again, our administrator user is CRUDing away when they get a call saying that Jane needs the account unlocked, but this time, they hit the Backquote key on their keyboard, up pops their development console, and then they type in the command unlock jane, and they press Enter, and just like that, Jane's account is now unlocked. They hit the Backquote key one more time to hide the console and they're right back where they had left off. It works out pretty well I think. Okay, so a few points I'd like to make in favor of having a developer console. First, the command-line interface can provide a faster workflow for the user performing the action than a GUI version can. As we saw in the last example, the amount of network traffic required to perform an operation can be made significantly less. Now, I think the graphical UI we saw for unlocking an account is fine, but keep in mind that all input lends itself to a graphical user interface. There's a reason we still use programming languages to create software after all. They're kind of fun to implement. To be honest, the original reason I started experimenting with a console was because I thought it would be cool to do. It's kind of novel and you don't see it done very often. But most important, I think, is that the development time needed to implement an operation is dramatically reduced. With a console, adding a new command such as this account unlock would probably take me less than three minutes to do. Remember, the operation being performed by the server is essentially just this one line of code, but if I had to create a graphical user interface for the unlock command, there's no way I could do it in three minutes. I probably couldn't do it in three hours. Maybe I could pull it off in three days, but depending on the complexity of the UI in question, who knows, it could end up being more like three weeks. My point is that when you implement a new feature, often the work required on the server is kind of trivial, but can still end up taking a long time to get anything usable up and running because of the disproportional amount of effort required to create a graphical user interface. Now I want to be clear. I'm not saying that you shouldn't create a graphical interface for a commands unlocking a user account. There are many users that will be best served by a graphical interface, and personally, I love creating graphically-rich UIs and I think it's great that today you can create web apps that rival even the most engaging desktop software. So I'm certainly not suggesting that you turn your beautiful web application into some sort of Return to Zorch text adventure game, but what I am saying is that there are some compelling reasons to incorporate a developer console as a feature within your web application, particularly if time is a factor. So if you have a feature such as an account unlock to implement and you're running up against a deadline requiring you to have something usable up and running quickly, you may want to consider implementing that feature using a command line interface, and then once things are up and running and you have a bit more time on your hands, well then go ahead and create the graphical interface for it. But even once you have the graphical interface in place, you may still decide to keep the console implementation around, maybe just for developers or maybe for a subset of your user base, and the reason why is because it can often provide a faster workflow for those comfortable using a CLI and because it's kind of cool.
Required Skills / Project Setup
In this module, we've learned what a developer console is, mainly that it's a command-line interface for entering commands within an otherwise graphical application and we saw an example of one in action. We then went over an example demonstrating how adding a developer console to a web application can save time and make you more productive both during development and afterwards. Next, we reviewed the technical skills required to create a console and we learned that it can be built easily using basic web development tools. Finally, we create a skeleton project, set up a server with live reload capabilities, and added some basic styling to our page. We're now ready to start diving straight into the creation of the developer console itself in the next module. I hope you'll check it out.
Designing a Console Control
Designing a Console Control
Styling the Console
We've now got our HTML markup pretty well taken care of, but the results don't yet look anything like a developer console. So let's fix that now by creating some style rules that will give our control the look of a console. The first thing we need to do is to link the Style Sheet for the command line interface into this document. So I'm going to copy the existing Style Sheet link that we have here for the app.css and just swap out the names, so this one will be for webcli. And now, we can open that file and we can begin writing our style rules. One of the first things that comes to my mind when I think of a console or even a text editor for code is that they all use a monospaced font. A monospaced font is any font whose character's each take up exactly the same amount of horizontal space. This allows you to line up the characters that you write into formatted columns very easily and keep everything looking neat and tidy. So I think that's the first thing that I'd like to set up. So I'm going to begin by defining what the font styling rules should be for the text inside of our console control and I'm going to set this on webcli and webcli-input explicitly to help protect against any existing style rules the app may have and I'm going to give the font a size of 20px and a line height of 20px to make it easier to read in this video, but you may want to use something a bit smaller. Next, I'm going to set the font family to be Lucida Console, which is a monospaced font and seems to be named appropriately for our use case. If for some reason that font is not available, as a backup, we could use Consolas, and if that's not available either, we could also fall back to using Courier New. If the style rules inside your application are such that they may override this, you may want to specify that this rule is important so that it takes precedence. So that takes care of our font. Now let's write some rules that are specific to the control itself. So these will be rules for webcli only and I think the first thing I want to set is its background. I want the console to be a very dark, essentially, the color of black, but I do want it to be slightly translucent, so I will give it a .7 opacity to let the background bleed through a little bit. Okay, that's not bad, but it is a little thin, so I'm going to give mine a height of 300px. The next problem we've got to deal with is the positioning of the console. I'd like it to be more like the examples we saw earlier where it was at the very bottom of the screen and took the full width of the browser. So the easiest way to do that is to give our control a position that is absolute. I can now specify that I want it to be positioned 0 pixels from the bottom of the document body and that will cause it to snap to the very bottom of the window and I'll do the same thing with the left. I also want it to be 0 pixels from the left-hand side of the body. And finally, the right-hand side, same deal again, 0 pixels from the right-hand side of the body. So now we have it positioned at the bottom and stretched across the width of the browser. Okay, we're getting there. Let's now start addressing the input control because it's rather glaring. In fact, you probably don't really want the input control to be visible at all, so let's start writing some rules for webcli-input. I'm going to have it display as a block element and I think I'm also going to give it a width of 100% so it takes up the entire line. So right now, it's pretty obvious that we're looking at an input control and I'd like to try and hide that fact, so let's set its background to be transparent. Okay, so that's take care of the background, but we've also got to get rid of that border, so let's set the border to be none. That's better. We now no longer have a really obvious input control inside of our console. We can still place our cursor inside, but it's not quite so obvious how it's constructed. The input control's black text, however, is now very difficult to read. We're going to want to set its color to be something brighter. You may want to use something like white, but I think I'm going to set mine to be a greenish color. So let's see how this looks. Okay, so that shows up pretty well. Now we probably could get away without doing this, but I think I'm going to set the margin and padding on this control to 0 just to account for any possible discrepancies between browser defaults. Okay, so I think the next thing that I'd like to change is the amount of padding around the inside of the console. I think the text is just a little bit too tight up against the edges, so let's add a little bit of padding, maybe 5px worth around the inside of the console, and that'll give us a little more breathing room around our content. And now, let's actually create a border around the outside of the console to create a little more definition, so I'm going to create a border that is solid and has 1px thickness and a color that's basically white, but not completely white, so we'll give it a .5 opacity, so that'll give us just a little nicer edge around our console. And then just to give it a little bit more depth, let's give it maybe a 3px box shadow and this will be extremely subtle, only barely visible at the top of the console. But where I'd like the shadowing to be more pronounced is just on the inside of the console around the border to create some depth, so there's going to be an 8px blur radius on this one and this is going to be shaded black and this one's going to be marked as inset, so it's a shadow on the inside of the console making it look like the borders are raised. Now in case that's a little difficult to see in the video, let me just color this red temporarily so that you can see the area that's going to be shaded in and will create that illusion of depth and give me a little more of that window look that I'm going for. Alright, that looks fairly good. So now I could enter my console and type in some text like StartJob 1 Now or something like that if I wished. It's starting to look like you'd expect a console to, except you may notice there's this X in the upper right-hand corner, which is kind of specific to the edge browser. It's somewhat of a nice feature in that you can click it and it will clear the input for you, which may be something you like or it may be something you'd rather not have in your console. If you'd like to remove it, it's not difficult to do. We just need to add another style rule. So let's add it down here. The style rule needs to be placed on our input control using the ms-clear pseudo element, which is that X that we saw at the end of the input control and here you can see that's what my editor is telling me. Ms-clear represents the clear button of a text input control in Edge or IE10. So we'll just say that we'd like that element to have a display of none and we might want to just add a little comment here that explains what this is. This rule is for the clear button in Edge. And so now if I put focus on that input control, that clear button or X is no longer displayed. So that's an example of a styling detail specific to a particular browser, in this case, Edge. But each browser can have its own unique quarks, so it's a good idea to review your styling rules across the major browsers every once in a while to make sure that things are working as you expect. So now, let's take a look at the same page, this time in Firefox. As you can see, Firefox is rendering our page basically the same way as Edge did, except we've got some red squigglies under the first word and that's because I have spellcheck enabled in my Firefox browser and StartJob as a single word is not in the dictionary. Many of the commands or parameters that you may wish to enter into a console may have this problem. So you'll likely want to turn this feature off for the console's input. Fortunately, this is pretty easy to fix too. If we head back to our mark up, we can add an attribute on the input control specifying that we want spellcheck to have a value of false, and with that change in place, there's no more red squigglies. Let's now take a look at this page using the Chrome browser. Here again, you can see that the page is being rendered essentially the same as it was in the two previous browsers, except that when the input control gets focus in Chrome, it has blue outline appearing around it. Now you may like this or you may not, but it's certainly not consistent across the browsers, so to fix that, we can explicitly specify an outline rule. So I'm going to add to our input rules that the outline should have a value of none. And again, I'll just add a comment here specifying that this is really only necessary for Chrome. And with that change in place, I can now put focus on the input control and there's no longer any outline being shown in Chrome. So now all three major browsers are showing our console basically the same way. The main point that I'd like to impress upon you though is that if I had been writing my code using only a single browser, it's very likely I would have missed some of these subtle differences, so it's a good idea to swap out browsers now and then as you write and test your code because each one may make you aware of different issues.
Our next step is to handle the commands entered into the console after the user presses the Enter key. Once a command is submitted, we'll first want to record it so we can implement a command history. With a command history in place, the user can press the up and down arrow keys to cycle through previous commands. Now this is useful if you want to re-run a previous command or maybe just correct a typo. Then we'll need to clear the text from our input element and move it into our output element to set up for the next command and create the proper appearance for a command being submitted in the console. And then finally, we need to process the submitted command to run on either the client or what's more likely on the server. Let's begin by adding a couple of properties to our class to keep track of the commands that have been entered. First, I'll add a history property, which will be an array, and this will hold each submitted command and make up our command history. And let's also create an Offset property, which will have an initial value of 0 and this will be like a reverse offset into the history. So we'll always start at the end of the history array with the last command that was entered, and each time the user presses the up arrow key, we'll subtract one from this offset and move towards the beginning of the array. Now in order to handle command submissions, we'll need to extend our onKeyDown function and add a case for the Enter key. So let's add a new case for a keycode of 13, which is for the Enter key. And when the Enter key is down, let's call self.runCmd, which will be a function to process the text that is currently inside the console's input element. So now let's create that function and inside here we'll set self to this object and also store the txt that was entered into the inputEl, but let's trim away any white space that was at the beginning or end of it. Now the next thing we want to do is make it so that each time a command is submitted, our cmdOffset into the history is reset back to 0 and this way after entering a command, you can press up and immediately get back that last command entered. And then what we want to do is clear the text from the inputEl just before we write that text to our outputEl. This will create the look of the command being submitted in the console and clear the control for future commands. So now, let's append the command text to the outputEl using our writeLine helper function, passing in the cmd txt that we saved and writing it out using the cmd style. And I can just put a comment here saying that we are writing the cmd to the output. Now before we attempt to process the command, we should first check if anything was in fact entered. For example, it's totally possible that someone just hit Enter on a blank line. So let's test if the command's text is equal to an empty string, and if so, we can just return from this function and I'll just add a comment saying that if it's empty, we'll stop processing the command, but if a command was entered, we should add it to our history. So let's push this new command onto our history array and we can add a comment just explaining what this line does. Okay. How about we try out what we have so far and see how this is looking. So if I open the console now, I can enter a command and press Enter and you'll notice that the command moves from the input element where the text is green into our output element where because it has the command style, its color becomes gray. And each time we add a command to the output, the output elements height grows causing our input control to push down towards the bottom of the console giving us just the look we're going for. You'll also notice that I can enter blank lines and they work fine too. There is a slight problem though. When I enter enough commands to hit the bottom of the console, you can see the output is bleeding right through the console control and this is happening because I forgot to set the overflow property when styling the control. Fortunately, this is really easy to correct. So let's jump over to the CSS file for our console and inside the webcli section, I want to add an overflow property and give it a value of auto. This will make it so that if content is too large to fit inside the control, scroll bars will appear and the content will be able to scroll inside of the webcli div. So now, if we head back to our console, you'll see that the changes have taken effect and now all of the content fits inside the console and I can use the scroll bar to scroll up and down just as you would expect, and also, if I were now to add some additional new lines, everything's working fine here as well. Now it would be kind of nice if I had a command available to clear the screen and return the console to a pristine state. So as we continue writing our runCmd function, that might be a worthwhile thing to remember to add-in. Okay, so let's jump back to our runCmd function, and at the point that we left off, the command has been entered and added to the history, so it's now ready to be processed and there are two types of processing that we can do. We could run a client-side command or we could run a server command. Now 99% of the time, you'll only be interested in server commands, unless you have a very sophisticated single page app that can in fact do meaningful work without involving a server. Still, having some basic client side commands, such as clearing the screen and navigating history are useful. So let's hack together some basic client side functionality. The first step is to break each part of the command into a series of tokens. So I'm going to create an array of tokens by taking the command txt and splitting it wherever there is a space character. And now, we can determine the command name by grabbing the first element of the tokens array. In the other elements will be arguments that should be passed to the command. Now if you don't want your commands to be case sensitive, you may want to convert the name of the command to uppercase and then test against that. I now have what I need to put together a clear screen command, which I'm going to name CLS, but feel free to be creative and make a Winexe command if you prefer. Its implementation will simply be to set our output elements in our HTML property to an empty string and then return. Now as we'll see later, there are situations where this approach of getting tokens is overly simplistic and running commands in this way might not scale ideally. So we'll look at a better implementation when we tackle command processing on the server, but this is certainly adequate for our needs at the moment. Okay, let's give the CLS command a try. Alright, so I'll open the console and I'll enter in a few bogus commands here just to litter the window and prove that these commands don't currently do anything, but when I enter CLS, you can see that the console's content is cleared. Because the output element is now empty, it's height is reduced to nothing and our input is back at the top just like we'd want. And the same is true even if we had lots of content entered into the console and had visible scroll bars. Again, entering CLS regardless of the case will properly clear out the console. Let's now finish up our command history implementation by allowing users to cycle through past commands using the up and down arrow keys. So to do that, we're going to need to jump back to our onKeyDown function and we're going to want to add another case this time for a keycode of 38, which is for the upkeep. And in the case where the up key is pressed, I want to test if the command history's length + our cmdOffset is greater than 0, and if it is, then it's okay for me to subtract 1 from our cmdOffset, which will move us back one position in the history towards the beginning of the array. And now, I can assign that historical command to the input control by setting our inputEl.value equal to the history array element that has an index of this history length + the cmdOffset. Now if you're like me, this might be a little hard to follow without a visualization of what is happening here, so let's walk through an example. Imagine that our history array has three commands entered into it. The first command entered is at Index 0, the next command is at Index 1, and the third is at Index 2. So obviously in this case, the length of the history array is 3. Initially, our cmdOffset is 0, so a history length of 3 + an offset of 0 would put us here at Index 3, which is 1 beyond the array's limits. But if a user presses up, the cmdOffset is decremented to -1. So now, 3 + -1 gives us an index of 2 and we can load the last command in the array. This continues each time the user presses the up key so long as decrementing the offset wouldn't give us an index value of less than 0. We wouldn't want to decrement our offset beyond that point or we'd run past the beginning of the array. The main thing to keep in mind is that we're starting from the end of the array and moving backwards. If we handled the up key, I also want to tell the event to prevent the browser's default action. In some browsers like Chrome, the default action for the up key when inside an input control is to place the cursor at the beginning of the line, and usually, this isn't what you'll want, so I want to prevent that from happening. And we can follow almost the exact same logic for handling down key presses. So I'll just copy and paste this and I'll set the keycode to 40 for the down key and change our test to ensure our cmdOffset has a value less than -1, and instead of decrementing, we increment the offset because we want to move in the other direction towards the end of the array. The rest can stay the same. And also before I forget, we've got to add breaks to the end of our up and our down cases and that's all there is to it, so let's give it a try. So if I open the application once again, I can open up the command console and then enter in a few commands, and then once they've been entered, I can use my up and down arrow keys to cycle through the command history and it looks like everything is working just fine; however, there is a problem. I can also cycle through the command history if focus was on the input control above, not just when the console has focus, and obviously, we don't want that. The problem is that our keyDown event is placed on the document, which means that keyDown events from anywhere in the document will be handled by our code, which is ideal for toggling the console's visibility, but we need to make sure to only handle the other keyDown events when the console has focus. So I'm going to pull out the backquote handling code from the rest and write a comment saying that Ctrl + Backquote can happen anywhere within the document and then just add the keyCode test inside of here, and after handling this, we'll return from the function, but for all other keys, I only want this code to run if the console's input has focus, which we can test by checking if our input element matches the documents currently activeElement and then we just want to wrap this entire switch inside of the if block. And with that change, we'll get the behavior that we want.
In case it's not obvious, I just wanted to point out that unlike traditional consoles, our developer console is not limited to only displaying text. In fact, because our console is essentially just a div in a web page, it can contain any type of content that a web page can display. So if it's useful for a command of yours either on the client or the server to return, for example, an image, instead of plaintext, that's not a problem. If it made sense for the output of your command to use SVG to display the results perhaps as a chart, that would be fine too. In fact, if you wanted, the results of your command could be in the form of a video or even as an iFrame to another website altogether. That said, if plaintext meets your needs, then I'd encourage you to keep things simple. After all, one of the main purposes to having a developer console is to save development time. Just keep in mind that you have this capability available should you need it and that there's no reason to constrain your thinking to just what traditional consoles are capable of. So before we transition into server commands, I just wanted to spend a little time demonstrating some non-text output. So let's imagine that I had a cmd that was called IMG and whenever that command was run, I wanted it to display an image in the console, which I could do by writing out some HTML. So in this case, I want to output an img tag and I'll need to give it a src that will point to some image I want to display. And then finally, I want to just return after processing the command. Okay, so let's quickly go find an image. We can go to Google and see what we can find. Actually, we can just use this image here. So I'll copy this image's address and then we can just head back and I can just paste that right in for the source of our image. So let's head back to our console now and let's just give it a try. So I'm going to open up our console and enter in our new image command, and sure enough, here's an image right here inside of our developer console and there's really nothing special about it. It reacts the same way text would. We can enter in new commands, we can see that it will scroll through the console, we can use cls to clear it out, same as we would text. There's really no difference. So now, let's try an example with video. So I'm going to do a quick search and see if we can find maybe a Pluralsight video on YouTube somewhere and see about injecting that. Okay, so this seems like their channel. Okay, welcome to the new Pluralsight. (Video playing) So let's say I was interested in this video being the output of my command. So if I go to Share and then Embed, I can copy out the HTML they recommend for adding the video to their page. So with this in our clipboard, let's head back to our editor and let's make a new command. This one is going to have the name of YOUTUBE, and once again, I'm going to be using our writeHTML helper function and I'm just going to paste in what they gave me, although, I think I am going to add an autoplay value of 1 on our source just so the video will immediately start playing. And as usual, after finishing the client-side command, we will simple return from this function. Okay, and with that, I think we're ready to try it out. Okay, so now if I were to open up my developer console, I can run my YouTube command and up pops the video. Of course, I can still continue to interact with my console. So I can enter in additional commands, and of course, I can still scroll through the content while the video continues to play, and as before, I can still use the cls command to clear the console window. Now obviously, the image and YouTube commands as I've implemented them here are a bit ridiculous, but my goal at this point isn't to demonstrate a well-built command. Instead, I just want to impress upon you that our developer console can contain graphically-rich content and that if you're willing to use your imagination a bit, you could build some very interesting commands.
Sending Server Commands
We're now ready to connect our console to a server, and to do that, we need to transmit the user's command to the server and then display the server's response. Now there are a few ways we could do this. If your application uses a third-party library or framework, such as jQuery, Aurelia, or Angular, then you may wish to use their helper functions to send the request to the server. I'd like to have a console that has no third-party dependencies and is really quick and easy to drop into any project. So I'm going to avoid this option, but there's nothing wrong with it. For example, if you know every application you'll be writing will be using jQuery, then it might make sense to use jQuery's AJAX function to send the request. Another option would be to go old school and use the XMLHttpRequest API to send the request and this would be a very safe choice, but Microsoft designed this API long ago and I find it to be a bit clumsy to use in my applications, so what I'm going to do is use a more modern implementation of XHR, known as the Fetch API. Now you could argue that Fetch is a bit of an experimental technology at this point, but it has good support in modern browsers like Edge, Firefox, Chrome, and Opera. If you'd like to use Fetch on a browser that doesn't yet support it, GitHub has written a Fetch polyfil which you can get on surprisingly at GitHub. So this is what I'll be using to send the request, but feel free to swap out Fetch for whatever you're most comfortable using to send a request to your server. Okay, so here we are back at our runCmd function, and as it's written now, it can handle blank input or input that matches one of our three simple client commands. But if we get a command that doesn't match any of those, then we need to send the command off to the server to process it. So I'm going to do that using the Fetch function. It's first argument is the path to the resource you want to fetch. Now I'm going to use /API/webcli on my server, but you can use any route that you'd like. The second argument is an object is an object that you can use to configure your request. The first property I want to set on the object is called method and I can use this to specify the HTTP method the request should use, in this case, post. Next, you can use the headers property and assign it a new Headers object, which can contain any headers you wish your request to have. In my case, I just want to specify that the type of content I'll be transmitting is going to be in the JSON format. And finally, you can use the body property to specify the body of your request. In our case, we use JSON.stringify to serialize a simple object that contains a single property called cmdLine and we'll set the cmdLine equal to the cmdLine text that we got from the user's input. Now when you call Fetch, you will return a promise for a response object. In case you haven't used promises before, a promise is an object that represents an operation that hasn't completed yet, in this case, our Fetch request to the server. The promise we get back has a then function, which allows you to specify a function to call when the promise resolved successfully. This function will receive the response object. Now the response object has a JSON function we can call, which reads the response stream to completion and returns yet another promise for a JSON object. So we can now chain another then function and specify a function to call when the JSON object is ready. This function will receive the JSON object in its result argument. So it's inside of here where we'll be able to look at the result object we got back from the server. Before we tackle that though, I'd like to add one last function to our promise chain called catch. Catch allows you to specify a callback function in case there was an error resolving one of the promises in our chain, which could happen if there was a network failure and we were unable to contact the server. So inside of here, I'll just have our console write out a line of text and have it say that there was an Error sending the request to the server. And finally, I'll just specify that this should be output using our error CSS style. Okay, so now we can deal with the response the server sent us after it processed the command. But first, we need to think about what type of response the server should send us and we need to come up with a response type that will work for a wide variety of possible commands. Here's one way it could be done. We could have the server send us a result object that has an output property and this would be a string that contains the output to display in the console. The output may be a successful result or it might be an error message. The way we'll know the difference is by a second property called isError and this will be a Boolean that will be true in the event the output contains an error message. And finally, we'll have a third property called isHTML, which is also a Boolean, and if this is true, we'll know the output contains HTML, rather than plain text and these three properties should give us enough information to display the results. So I'm going to begin by storing the output from the result object into a temp variable and then I'm going to determine the style to use when outputting text by checking if the isError property is true. And if it is, I want to use the error style, but if it's not, I'll use the ok style. Then I'll check if the isHTML property is true. If it is, I'll write the output using our writeHTML helper function. But if isHTML is not true, then I'll write the output using the writeLine function. And that should do it. So let's go over to our browser and try out what we have. Okay, so I'm going to open the console and make sure that my client commands still work and it looks like they do. So now I'll try and run a new command. And you can see that we're getting that catch error message and that makes total sense. The browser is trying to send the request to the server, but I haven't set up any server-side code yet to handle the API/webcli request. But in terms of our client-side console code, things seem to be working pretty well.
In this module, we flushed out our WebCLI class adding a bunch of useful functionality to our developer console. We began by writing event handlers for keyboard and mouse events allowing the console to respond to input appropriately. We then created a number of helper functions for writing output to the console. And we wrote some simple command processing code to handle basic client commands, such as clearing the screen and navigating the command history. We also learned that our console can contain content beyond text, such as images or even video. And finally, we wrote the code necessary to send commands to a server for further processing and then display the returned results. So this module focused on building the necessary client-side functionality for a developer console. Next, we'll take a look at things from the server side.
Making a Responsive Server with Node.js and ASP.NET
Processing Commands - Node.js
Now that our servers can handle requests for static files, it's time to add support for processing command requests from our developer console. Let's begin with our node project. First, I'm going to add a new file to the project called webcli.js, and once again, I'm going to enable strict mode. I'm going to place all of the code related to handling console commands inside of this file, which works fairly well for small programs like this demo, but in larger apps, you may prefer to split the code into multiple files. So I'll take that approach when we create the .NET implementation, so we'll have an example of both ways of organizing the code. Next, I'm going to export a function that will be passed the application object that we created in the server.js file. And now, we can use the app object to create a route handler for post requests to API/webcli and then specify a function to handle the request, which will be passed a request and a response object. Now you may remember that once this function processes a command, it needs to send the client back a result object that looked like this. So let's define a command result class that we can send to the client. I'll have its constructor accept values for its output, isHTML, and isError properties. Now let's add the output property to this object giving it the passed in output or if none was given, it will default to an empty string and this will hold our success or our error output. Okay, and we've got two more of these to do and we'll do the same for the isHTML property giving it a default value of false and this will specify if the output property contains plaintext or an HTML string. And finally, we'll set up the isError property and this is also going to have a default value of false, but it will be true in the event that the output is an error message. Okay, so let's create the result object that we'll be sending back to the client and we'll have it start out with a generic error message of invalid commands. IsHTML will be set to false and isError will be true, at least until we successfully execute a command. Now let's create a try/finally block. We'll attempt to process the command inside of the try block and then use the finally block to ensure that regardless of any exceptions thrown, we'll always return a result by sending it out in our response. Okay, now let's split the command-line into an array of tokens just like we did on the client by looking at the requests body and splitting the command-line property on the space character. Then we'll store the command name by grabbing the first element in our args array and then converting it to uppercase characters. We're now ready to search through our available commands for one matching the command name we've got and then executing it. But first, we need to create this collection of commands, so let's do that now. To do that, I'm going to create an object literal in this module called _commands, which will hold all of our command objects. Now let's define a command class to make it easier to add these new commands. I'm going to pass each new command a help description, as well as a function parameter, and then assign them as properties of the command. The help message should come in handy later for creating a help command and the function property will store the actual implementation for executing the command. With that done, we can now create commands by adding command objects as properties of _commands. I'm going to start out with a simple ECHO command that will echo back to the client the first argument that it was given. Each command's implementation function we pass the args array that we made in the route handler above. So to implement this command, I'll just test if we were passed at least one argument other than the command name, and if so, return a command result when output of that same first argument, but if we didn't get any argument other than the command name, we can just return an empty string as our output. Now if we go back to our route handler, we can now replace our default error result with a result from executing the command that had a matching command name and passing its function the args we received in order to execute the command. And then finally, returning the command result back to the client. All that remains now is to add this route handling function to our application. So let's head over to our server file and do that now. We want to both create and handle the Web CLI route, which we can do by requiring the Web CLI module that we just wrote and passing it our application object. And now, if we launch the server and then open up our web browser, we should be able to test if our ECHO command is working. So I'm going to try and have the server echo back an argument of Hello! And yep, I'm getting Hello! back. Now let's try echo again, but this time using some numbers and yes that looks fine too. Now let's try an echo Hello World and you can see that only Hello is being returned and this makes sense because I wrote the command to only echo back the very first argument after the command name and world is the second argument. But what if I really did need to pass Hello World as a single argument? Well often quotes are used to wrap around arguments to express that they should be treated as a single token, such as a file name that has spaces in it, but that doesn't work given the way we're currently splitting the command line into tokens. We're separating whenever there's a space character. So if your commands are such that you won't have spaces in your arguments, then you might as well stick with the simple command line parsing we currently have. But if this is a scenario that you need to handle, you'll need to upgrade to a more sophisticated way of parsing the command line. So let's see if we can improve our command line splitting a bit without adding too much additional complexity to our code. One way to accomplish this would be to use a regular expression, which is a sequence of characters that can define a search pattern. In our case, we'd be searching for arguments within a command-line string. A benefit of regular expressions is that they allow for complex string processing using an amazingly concise syntax that is supported across many different languages. The downside to regular expressions though is that they can be very cryptic leading to code that is hard to read and to maintain, and for that reason, I tend to use them sparingly, but this is a situation where I think they fit the bill pretty well. It can be helpful to construct your regular expressions using a tool, such as the Scriptular website so that you can test the results as you write your expressions. I'm going to begin by entering a test string that has a command name followed by a bunch of arguments, the third one being a quoted string containing a space. Next, I'm going to enable global matching so that all of the matches will be found. And now I can begin writing my regular expression. I'm going to begin using the except bracket syntax to say that I want to match any character, except a space or a quote, and then I'm going to use the + character to specify that this should happen one or more times. So what I'm currently matching is any character that's not a space or a quote one or more times. And as you can see, this gives results similar to what we already have, except that it knows to ignore the quote characters. I'm now going to add an or to get it another way to find a match. And now I'm going to say that you can also match a quote followed by any number of characters that are not quotes followed by a closing quote. And now it's able to match the quoted argument even though it contains spaces. The only problem here is that the quotes are being included in the match, but this is about as complex a regular expression as my little brain likes to deal with. So once we have the matches, I'll just use a string replace to strip out the quote characters. Let's now replace our string split with a new function called getArgs, which we use our regular expression to parse the command line. And then down here, I'm going to paste in an implementation of that function and you can see that it creates a variable to hold the regex object that matches tokens and another that simply matches quotes. We then can get a collection of arguments by taking our cmdLine and running the match function on it, passing in our token regex. Next, I loop through each of the arguments we got back and remove any quote characters that they may have and replacing any quotes with an empty string. And finally, I return our argument collection. Now this improves over what we had before, but it's still not perfect. There are additional edge cases that this can't handle. So we could continue to improve it, but I think it currently provides a decent bang for your coding buck without being overly complex. I found this to be all that I have needed to parse my commands. Of course, if you have more complex needs, you can certainly enhance this with more sophisticated parsing logic, but I'd encourage you to keep things simple if you can. With this in place, we can now run our echo command passing it a quoted argument, such as Hello World and get back our desired results.
Processing Commands - ASP.NET
Now let's set up the command processing code for our .NET project. We'll be porting over much of the code that was explained in the previous node implementation, so hopefully you followed what was covered there. However, in this server example, I'd like to organize things in a little more modular way. So let's begin by creating some folders to help organize our files. First, I'll create a WebCLI folder, and then within that, I'll make a folder to hold all of our commands, and another called Infrastructure to hold all of the files that will assist in implementing our CLI processing. And we really only need to focus in on the WebCLI folder now, so I'm going to scope our view just to that folder. Of course, we can always back up our view to the entire solution if need be, but for now, I'd like to just focus in on the WebCLI folder. Now let's add a controller that can handle the requests from our client and I choose an empty controller to begin with and then I'm going to give it a name of WebCLI Controller. Now to keep things simple, I'm going to keep everything inside of the DevCon namespace. So inside of here, we're going to need to write an action method that will be called whenever a post request comes in for API/webcli. So I'll create a public method that returns a ConsoleResult object to the client and I will name it Post and it will read from the body of the request a CommandInput object that we will call command. Now obviously, we haven't actually defined the ConsoleResult or CommandInput class yet, so let's do that now. First up, let's define the ConsoleResult object. And into here, I'm going to paste in the properties for this class that I basically copied from our node example. It just poured them over to C#. The main difference is that I called this class CommandResult in the node project instead of ConsoleResult, but they have the same purpose. Next, I'm going to take advantage of C#'s ability to have overloaded constructors and create both a default constructor and one that can take a string parameter called output. This will give me a nice short syntax for creating client responses that were successful. Along the same lines, I'm going to create another class in this file called ConsoleErrorResult and I'm going to have it inherit from the ConsoleResult class above. It will differ in that its default constructor will set isError to true and default to have an output message of Invalid syntax. Finally, I'll overload this constructor allowing it to also take a string parameter giving me a short syntax for creating custom error results. And it looks like these using statements are actually not needed, so let's just clean those up and I think they should be good. So now let's add the other class that we needed, which was CommandInput and let's once again set the namespace to be just DevCon. Okay. So you may remember that our client sends the server an object with a single property called cmdLine, so we'll add that here. Now while it may seem like overkill to be dealing with objects and classes for what is really just a single string, there are some advantages to this. For one, if we later decide to send additional properties to the server besides just the command-line string, it's really easy to add them to this class and this class also gives us a logical place to put our command-line parsing code instead of just having it floating around by itself. So I'm going to paste in a port of the getArgs function that we put together in the node example, and with this in place, we can now simply ask our CommandInput object to parse itself. Okay, it looks like I'm missing a using statement for a regular expression, so let's add that and then we can remove the ones that are not needed. I'm using the same regular expression as before, except that here I needed to escape the quotes by doubling them up. I then called the Matches method on the regex, which returns a MatchCollection. If you're wondering why I'm casting here, it's because I want to use link to convert this MatchCollection into a string array. The thing is most link methods target IEnumerables of T, but MatchCollection only implements ICollection and IEnumerable, but not IEnumerable of T. But Cast is the exception to the rule, so I'm using it to convert what you might call a weakly-typed collection to a generic IEnumerable of T. Once that's been done, the rest of the link extension methods light up and this allows me to call Select to grab the string value for each match object and then call the string's Replace method to remove any quote characters that may be in the string. And finally, I convert the collection to an Array, giving us the return type that we want, which is an array of strings. Okay, so with these two classes implemented, we can close out these files and then head back to our controller. We'll begin by storing the command line as an array of string arguments by calling the GetArgs method on our CommandInput object. And then we'll store the name of the command by grabbing the first element of the args array and then converting it to uppercase. Next, I'm going to create a type variable that will hold the type for the Command class that we will want to instantiate and run and I'll set it to null because we don't yet know what it will be. And then our next task would be to search for the command to run, and then once we know what it is, we can then instantiate and then run the command. Now when running a command, an exception could be thrown, so let's write a try/catch block that ensures that in case of an exception, we'll return to the client a default ConsoleErrorResult object. Okay, so that's our outline. But before we can actually search for our command type and run it, we first need to actually have a command. So let's turn our attention to creating commands. Each one of our commands will be a class that implements the IConsoleCommand interface. If you're not familiar with interfaces, they're basically a way of listing requirements, such as properties or methods that a class must have in order to be set to implement that interface. In the case of a console command, the only requirement will be that the class implements a method that returns a ConsoleResult object is called Run and receives as an argument, a string array. And that's all there is to it. It's a very simple interface. Before writing our command, I'd like to take advantage of another .NET feature, attributes. With attributes, you can declaratively add information to elements of your code such as methods or classes and then query that information at runtime. In our case, I'd like to create an attribute called ConsoleCommand that we can use to decorate our command classes with their name and description. We'll want to inherit from the base attribute class and then I'll add an attribute to our Attribute class to limit its usage to just decorating other classes. Okay. Now we'll create two properties, one for the command's Name and then a second one for the command's description. And of course, you could extend this with whatever additional properties you like. Then we'll write the constructor for our attribute and we'll have it take the name and the description as parameters and then we'll just assign them to the classes properties and that's all there is to it. So let's take what we've got now and write a .NET version of the Echo command. So I'm going to add a new class to our project that's called Echo and I'm just going to clean this up a bit. Now let's add our ConsoleCommandAttribute to this class specifying a name of echo and a description of Echos back the first arg received, and of course, this class needs to implement the IConsoleCommand interface and I'll just ask Visual Studio to create a skeleton implementation of that for me. And now we can implement our command in the Run function, which you may notice looks a bit like the main function used by the C family of languages in that it receives as a parameter an array of strings from the command line. So we'll want to test if we have at least one argument besides the command name, and if so, return it in a ConsoleResult. Otherwise, we'll just return a default console result object, which will just send back an empty string. Alright, so now that we actually have a command, we can finish up our controller. In order to locate the command to run, we'll need to construct a list of possible command types. So let's add to our controller a static list of types called CommandTypes. Now the simplest way to populate this list is to do it manually. So inside of here, I could just list each of the possible commands that can be run, such as the Echo command that we just wrote. Another option would be to populate this list programmatically by querying the application's assemblies for any types that implement the IConsoleCommand interface. Here's a constructor that just does that. You can see that I'm getting a list of assemblies that have been loaded into the AppDomain and then I get the types within each assembly flattened into a single collection using the SelectMany method and then store them in types. I can then get a list of CommandTypes by getting a collection of each types interfaces and keeping only those who have the IConsoleCommand interface. If you go with the programmatic method, there's no reason to keep the explicit list. Next, I'm going to store the Type for the custom attribute we created for console commands because it will be convenient to have available for command lookups, and with that, we're now ready to actually perform a command lookup. What we'll do is loop through each type in our CommandTypes collection and read out the custom attribute that was placed on each one. We'll also need to cast the attribute that we get back to our specific ConsoleCommandAttribute type so we can access its properties. Now each command should have exactly one attribute on it, but let's verify that the attribute we got back isn't null and then test if it has a name that when converted to uppercase matches the command name from the client. If it does, we found the correct cmdTypeToRun and we can break out of the loop, but if after searching through all of the types we didn't find a match, we'll return a ConsoleErrorResult. Now let's create an instance of our command, which we can do by using the Activator and telling it to Create an Instance of our cmdTypeToRun and casting the returned object as an IConsoleCommand. Finally, we can simply call the Run method on that object passing it the arguments and returning the result. Let's run this application and test it out. So if I were to open the console now, I can test out our echo command with a quoted argument, which looks like it's working well, and then if I run it again with no arguments, I get back an empty string, which is expected, and then finally, if I run a non-existent command, I'm getting back the default ConsoleErrorResult of invalid syntax. So our processing code seems to be working just fine.
In this module, we focused on server-side code and we set up two server projects that could serve the static client files we had written previously. We also added support for handling quoted parameters in our commands and we wrote a simple echo command so that we had something to test with. Next, we wrote the code to process the command sent by the client and return an appropriate response. And we walked through an example of how to do this, first using Node.js and then we saw an alternative approach using ASP.NET. Now that the basic functionality needed is in place, we'll look at some ways to further enhance what we've done and add some additional commands to the console demo.
In this module, we're going to look for ways to enhance what we've built so far beginning with adding a basic help function to our console, but we only have one server command at the moment, so let's add a second, so we'll have a little more for our help command to work with. Fortunately, with our infrastructure now in place, adding commands is really easy to do. Let's say that for some reason I wanted to add a command that could perform simple addition. My workflow would look something like the following in .NET. First, I'd make a copy of an existing command class and then I'd just rename the file to Add. Then I'd give it a command name and a description, such as Adds 2 numbers together. Next, I'd correct the class name and then I'm free to implement the command inside the body of the run function. So taking a simplistic approach, I might do something like storing into a temp variable called x, the Parsed value of the first argument I received, and then doing the same thing with the second argument. And now I can return a console result that is the sum of those two numbers converted to a string. And just like that, I've extended my application with another command. Now granted, performing addition may not be the best use of a server, but it does give us another command to work with. Let's give it a quick test. If I run the application now, I can open the console and enter a command to add the numbers 4 and 3 together and then get back a result of 7, a demonstration of the awesome power of .NET, I know, but what's significant is that the result came from the server, which means that rather than doing addition, I could just have easily been purging files or modifying database records. Now let's implement this command inside of our node project. In this case, I can attach a new property onto our _commands object for Add and then instantiate a new Command object and I'll give it a description of Returns the sum of two numbers. And finally, I can specify an implementation function. Now inside of here, I'll do basically the same thing as before, parsing the first argument into a temp variable called X and then the second argument into a temp variable that's called Y. And then I can calculate their sum and return the result to a string. Finally, I can return a CmdResult object passing it the sum. Now let's just verify that this works as well. So this time I'll try adding two decimals, 3.5 and 2.2, and as you can see, we're getting back the correct result of 5.7. So it looks like we're good. So now let's write the help command and we'll follow the same process as before where we add a help property to the _commands object and then instantiate a new Command object. And I'm going to give this a description of lists available commands. Now I'd like this command to list available commands and their descriptions and because we're using a monospaced font, we could do all the formatting using just white space if we used a CSS style to preserve it such as white-space:pre; however, I think I'd rather use an HTML table. So I'm going to begin constructing the markup for a table and give it a class name of webcli-tbl, so we'll be able to style it a bit. And then we'll need to write out our rows at some point and then append a closing table tag. Finally, we'll return a CmdResult passing it the markup string we built and setting isHTML to true specifying that we're returning HTML content instead of plain text. Now before we write the table rows, let's add a few style rules to our CSS file to help us style this table. So I'm going to open the webcli.css file and then paste in a few table rules. By default, I'll have the text color be green, the cells will have two pixels of padding on the top and bottom, and five on the sides with the exception of the first cell in each row. It won't need any left padding. Then I'll just define a couple color rules for things that I want to designate as being a label or a value, so nothing fancy. And because I want our two projects to stay in sync, I'm going to copy this updated webcli.css file from the node project to the .NET project. Now let's finish writing the help command. I'll use the Object.keys method to get an array of properties on _commands and then loop through them and I'll store the cmd object in a temp variable called cmd. Now I can start actually building the row. The first cell will be styled as a label and contain the key, which is the command's name. The second cell will just hold a colon and will act as a separator. And the third cell will be styled as a value and this will contain the cmd.help description and that's all there is to it. Let's try it out. Okay, so it's basically working, but there are a couple small problems. First, the command names are being displayed in all caps because that's how the _command properties are written, but I'd rather they be displayed in lowercase in the help. The more important problem though is shown in the row for the echo command. There's actually a word missing from the help description. If you take a look at it in our code, you'll notice that the word token is surrounded in angle brackets, which are, of course, special characters in HTML, and therefore, we need to escape them. So at the top of this file, let's require the escape HTML module that we added to the project at the beginning and this can perform HTML encoding for us. Now we can fix the help command. First, I'm going to create a temp variable called name and store the escaped key string that I wanted converted to lowercase. And now, I can use name in place of key and also just make sure to escape our help description. And with those changes in place, I can re-run the application, and now the help command is giving me the output that I wanted. Let's now implement the help command in our .NET project. Because the help command will need access to the list of available command types in our WebCLIController, I'm going to make it in our custom console command attribute public static readonly properties. And now I'm going to just drag a completed help command into the project so that you won't have to watch me type it again. As you can see, it's building up an HTML markup string for a table just as in the node implementation and then it loops through the available command types stored on our controller. For each type, I attempt to read the console command attribute from it and test to make sure that I in fact was able to. Once we have the attribute, we append the markup for the row using the HTML encode method to escape the command's name and its help description. Finally, the closing table tag is appended and we return a console result passing it the markup string and specifying that the result contains HTML. I can now run the application to test it in .NET, and as you can see, it's working just fine. Now of course, we could continue to improve this, possibly allowing an optional parameter to specify a command name to give detailed help for, which we could store on the command object or the attribute, but I think what we have will suffice. Another nice touch might be to modify the greeting message inside of our console control and here you could add a line to inform users when the console first opens that they can enter help to get a list of available commands, but I'll leave that as an exercise for the viewer. As far as displaying help for client-side commands, such as clearing the screen, one option would be to include them as server commands with no implementation so that they get included in the list of commands shown by the help. In .NET, that might look like the following. So here, I've created a class for our client-side CLS command that implements the IConsoleCommand interface and that is decorated with the console command attribute just like I would if it were a server command. The difference though is that I don't actually bother to implement it and this is because it should get handled by the client. The only purpose for creating the class is so that it will get listed by the help command. We can accomplish much the same thing with our node server by adding the client-side commands to the _commands object with no implementation function. And with that done, if I launch the application and run the help command, I can get a list of both the client and server commands together.
Long Running Commands
Additional Server Commands
At this point, we've built a functional foundation that can be used to quickly add new commands to an application. Unfortunately, I don't know what database you use or even what your application does, so only you know the specific commands that will be useful in your scenario. Still, I'd like to spend a little time extending our demo with a couple more commands that are generic enough that hopefully they'll be of use. To our node server, I'll add a command called status that can return some basic server information such as the amount of RAM available and the machine's uptime. And then to our ASP.NET server, I'll add a command called Diskspace that can display the amount of free space on one or all of the systems drives. Let's begin with our node project. The first thing we'll want to do is require the OS module at the top of our code because it contains the functions that we'll want to call in order to get information about the OS. Then I can scroll down to where our commands are and make a copy of one of them for our new command giving it a name of status and then a description of Displays server status info. And now I'll begin the command by creating a variable called freeMem, which will store the result of the os.freemem function, which will give us the amount of RAM available in bytes. And then I can convert this to megabytes by dividing it by 1024 twice and then I can pass this whole expression to Math.floor so that we'll get back an integer result. And now I can make a copy of this and repeat the same logic for getting the total amount of memory that the system has. So now that we have our memory values, I know want to calculate the machine's uptime and break it down into days, hours, and minutes. So I'll create a var to store the results at the upTime function, which will tell us the number of seconds that the machine has been up and running. And now I'm going to paste in some boring conversions to get what those seconds break down to in terms of days, hours, and minutes. Here's a quick guide of the conversions that I used, 86,400 is the number of seconds in a day, 3600 is the number of seconds in an hour, and of course 60 is the number of seconds in a minute. And now I'm going to use those values to store a string in uptime that we can use to display the breakdown of the days, hours, and minutes. And now, let's build the string that we'll be sending to the client. So we're going to report the freeMem out of the total available memory in MB and then follow that up with the machine's upTime. And finally, we can just return a CmdResult passing it the string that we just built. Now let's start this server and give this command a try. So if I run help, you can see that the status command is being listed along with the others, and if I run it, you can see that my machine has around 4700 MB of memory free out of 8 GB and the machine has been up for about 11 and a half hours. Now let's switch over to our .NET project and write the Diskspace command. I'll begin by copying an existing command and then renaming it to DiskSpace. Next, I'm going to add a using statement for System.IO since we'll need that in order to get information about the systems drives. Now let's fix the class name, as well as the command name and then give our command a description of Reports the amount of free disk space in bytes. Alright, now I'll just clear out the old commands code and we should be good. Let's begin by getting a collection of system drives by calling DriveInfo.GetDrives and then I'm going to create a result string that will be built up inside this function and that will ultimately return to the console. Now if a drive argument is specified, we'll only want to return results on that one drive, so let's test if we received an argument other than the command name. If not, we'll show info on all the drives. So if a drive is specified, let's grab a single drive from the drives collection whose name when converted to lowercase matches the first argument converted to lowercase and the string to return will be the result of a call to the GetDriveSpace function that we haven't actually written yet. So let's ask Visual Studio to stub out that function for us. Now in the case of showing all drives, we'll loop through each drive in the collection, and append the result string we get back from calling GetDriveSpace for that drive. After that, we'll want to pass the result string to the console result object that we returned to the client. Now all we have to do is implement the GetDriveSpace function. So I'm going to create a format string for what we'll be returning and it will be div that I'm going to style with an amber color and I'm also going to use white-space:pre this time as an alternative to using a table. And we'll just display the Drive name followed by the amount of bytes that it has free. Now all that's left to do is to return a call to string.Format passing it our fmt string and giving it the drive.Name as the first argument and the drive.AvailableFreeSpace as the second and that should do it. Let's give it a try. So once again, a call to help shows that our new command is being listed and if I now run diskspace on my Z drive, you can see that it has I don't know something less than 4 TB free. And then if I run diskspace again with no arguments, you can see the number of bytes free across all of the drives on this system.
In this module, we added some improvements to our console beginning with adding basic help functionality, allowing users to get a list of available commands and their descriptions. We then took a look at error handling and learned that it's a good idea to anticipate common user mistakes, and when possible, give meaningful error messages in response that guide users in the right direction while taking care never to give attackers valuable information about your software. Next, we improve the console's handling of long-running commands by blocking input until a command completes and displaying a busy indicator to the user to let them know work is being done. Finally, we added a couple more server commands to fill out our demo that allow you to display the server's memory usage and uptime in node and to check the diskspace available in .NET. Now it's time for you to take what you've learned and build some commands specific to your own web projects and needs. Maybe you need a command to perform a database operation, start a job, or clear out a memory cache. You've seen just how quickly the console we've built can be extended with new commands, so be creative and try things out. My hope is that you'll be able to build upon the examples shown in this course and create something even better. I found the developer console to be a huge timesaver in my projects and a lot of fun. I hope you will too. Thank you for watching.
Carlos Saloio has been programming since 1998 across a wide variety of areas, ranging from 3D graphics engines to line-of-business applications. Currently, his focus is on creating modern web...
Released18 Aug 2016