When I develop some software solution, I have a rough idea of what the user interface will be. Sometimes it can change drastically, and sometimes it can remain almost the same as my initial design. When working with services, (non-UI) components or when making some complex computations or statistical calculations, it is often required to specify multiple parameters and to use them easily. Typing in a console is a natural way of achieving all of those. However, for displaying complex graphics and for good user interactions the console it maybe not the first choice. More, when working with threads and using the console, it is not so easy to detect UI blocking or other common issues, just because every thread can easily update the console. So I have developed simple and customizable solution to address all of those small problems.
Entire solution is created from scratch. It will require some typing and some work. If there is a place, where you give up and move to another reading, then this one is the best. This article will not explain the code line by line, you need to read the code itself and figure it out. Of course, if there are questions, I will try to answer them to my best knowledge.
This is how http server (win 10 OS) console looks like:
This is how the ws server (win 10 OS) console looks like:
and this is how the WPF client application looks like, after the help command has been executed in order to display implement commands, and 2 000 random points were generated by one of the servers and displayed in Skia UI element. Only part of the screen-shot is shown here, in order to have the same scale ration as previous two images.
You can list available colors and change them to your taste. You will be able to see several color combinations through the article. This gif will show the sample in action.
This solution was created around end of 2018, so the current versions at this time of all products and corresponding NuGet or NPM packages were used.For desktop application, the following IDE was used:
and the following NuGet packages has been installed, together with their dependencies:
For services, the following IDE was used:
as server application, hosting the services:
and the following npm packages:
For testing the http service it might be useful to install application like Postman, Fiddler or similar. Postman was used during this demo. Demo solution can be completed by using other IDE’s, so this is kind of personal choice. After you decide which IDE to use, have them installed and having node.js properly installed, it is time to start.
Even that our client will be simple, it has to properly mimic a real application. So it need to have content navigation and to use other threads than the UI thread. MVVM pattern is
going to be used, with a basic view model and implementation of
ICommand interface. After implementing this skeleton, we are going to create custom text block, which will
inherit the default one. We are going to add one
CustomInlinesProperty, to this new control, so that we can bind to this property from
view models and update it via binding.
TextBlock has property called Inlines of type
InlineCollection, but it is not a dependency one, so we cannot use it
and we have to implement the custom one.
We start Visual Studio and create new project by clicking File →New - > Project… A “New Project” dialog appears, from which we select WPF app (.NET Framework) (even that WPF for .NET Core is available, we will use the old way for now), specify name and location and press OK. When I was making this article, I have created a folder called devdotnet on my D: drive, which is used for location, and the name of the solution and project is LocalBrowser. Build and run the solution to see that all is working fine.
We will install one NuGet package in order to have nice UI for the client application. So we right click the project, LocalBrowser, and select Manage NuGet packages… from the dropdown. NuGet window is shown, we click on Browse tab and then type MahApps.Metro in search box and then perform a search. If we typed without typos, the first result will be the desired one and we have to select it and then click on Install button. A popup called Preview Changes will appear and we confirm by clicking OK. After installation is complete, we will look for MahApps.Metro.IconPacks and we will install it as well, following the same approach. If we now click on Installed tabs, we should see something like that:
We again build and run the client. It should look exactly the same as the first time we build it. But not for long.
We open solution explorer and expand LocalBrowser project. Then, right click on MainWindow.xaml and select Rename… Then, we change the name to Shell. The xaml should be Shell.xaml and the code-behind file should be Shell.xaml.cs. Then, we open the code-behild file, Shell.xaml.cs. We right click on the file and select Remove and Sort Usings. Then, we right click on the MainWindow on line 8 and we select Rename and rename the class name to Shell as well. Finally, Shell class inherits Window class. We delete this Window class and type MetroWindow, then right click and select Quick Actions and Refactorings… and select using MahApps.Metro.Controls.
After that, the Shell class name should be underlined in red color, because we did not yet fixed the xaml file. Let’s save it and leave it like that for now. We open the xaml file, Shell.xaml. Let’s change it to this one. Keep in mind that we will further change this file in next steps.
Then, we go and open App.xaml. We change the xaml to this one. No more changes will be made to it.
We can open the app.xaml.cs and remove unnecessary usings there as well. Then, we build and run the client again. This time we should see a much bigger metro window. We can change the width and height to values pleasant for us.
Now it is time to create proper folder structure of our solution and to make few basic classes, which we are going to use further. In Solution Explorer, right click your project name (LocalBrowser in my case) and from drop-down menu select Add.. and then New Folder. Name the folder Custom. Following same approach, create the following folders : Models, ViewModels, Views and Infrastructure. Your solution should appear like this now:
Right click on ViewModels folder and select Add -> Class. Name the class ViewModelBase. Add this code to it, so that it has the following content:
Ok, now it is time for next view model. Right click on ViewModels folder, select Add → Class and this time name the class
ShellViewModel. We will go back to it shortly.
Now, add two more view models, called
ConsoleSimplifiedViewModel. Now, we will add two user controls in Views folder. As with
ViewModels, we will change the generated code, so that they belong to main namespace. So, Right click Views → Add and then select User Control… Name the user control
ConsoleSimplifiedView. Add one more user control and name it
AnotherView. Go to code-behind of
ConsoleSimplifiedView and remove all
unnecessary usings. Then, change the namespace to LocalBrowser only. The Initialize method will be underlined red. We will fix this shortly. Proceed the same way with other view.
Now, we go to xaml view and remove some of the markup there. We have to fix the x:Class, so that it correctly represent the changes we made in code-behind. We will change the console simplified view in following paragraphs, but for the other view we are ready – the markup should look like this.
It remains to create an
ICommand implementation. So, we right click on Infrastructure and select Add → Class and we name the class
final code for it is as follows. Thank you, Josh Smith.
At this point we build and run the client again. It should run and look exactly as before. If it does not, read the section again and fix all possible issues, before going further.
We stated earlier that we will use a custom text block in order to add inline objects from view model via binding. Now it is time to create our custom text block. It will inherit
from framework TextBlock and it wil just add one dependency property of type
ObservableCollection<Inline> and it will raise an
So, this means it will not be thread-safe and we have to take care of this later, in the corresponding view model.
We select Custom folder from Solution explorer, right click and select Add →Class. We name the class
CustomInlinesTextBlock. Here is the code for it:
We need view model, which is going to work with custom text block and do some basic stuffs. We want the following things:
consoleviewmodel, so that they are able to add their own implementations
This is the final code of ConsoleViewModel and the used approach to fulfill the requirements.
ImplementedConsoleCommands dictionary holds the string and the action, which
is called when this string is typed into console and users confirms the entry. Because it is too long and eventual screen-shot will be too high, we have to display it using html. Not
the best way, but it works.
This is the place where we create working draft for those two view models and add some markup to their corresponding views. Bear in mind we will change both view models in part three of
this article. Below you can find the draft for
and this is the corresponding draft markup view
and here is the markup for shell view. Please notice the spinning font awesome icon. It might seems deliberate, but it will show us whether our UI hangs or is always responsible. This view will remain unchanged, we will only change a little bit the corresponding view model in third part of article.
and here is the
ShellViewModelLet's say it once more - this is draft and it will be slightly changed in part three of this article, Client Again
We are almost at the end of part one of the article. It remains to set the DataContext of shell to it's view model. We can do this in XAML or in code-behind. Or in few other ways, outside the scope of this article. This time anyway we do it in code-behind, just like that. So, this is the code-behind of shell view and it will remain like that for entire demo solution.
and this is the how the solution explorer looks at the end of part one:
We build and run the application. We should see the spinning logo and be able to navigate from one user control(view) to another by clicking the icons. If you notice, that If you navigate to console view and start typing, but no characters are shown, then you notice correct. This is due to the fact that textbox is not automatically focused when control is loaded. You can try to fix this in XAML, but probably it won't work. The proper fix for this is not yet implemented, and it will be implemented when? - yes, in part three. We will fix this with small code-behind trick, which is really good explained in separate code project article by different author. So the desired behaviour is just as it is shown on the showcase gif in the beginning of this article. However, at this point we can still do some stuffs with our console. Let's run the application, navigate to console view and focus the text box. Then list all available colors by typing listcolors and press enter. You can choose some colors and change the font color or background color or paragraph color. This is how I changed the console for the last screen-shot of part one:
In this part of the article we discuss creation of two services, hosted on node.js. Those services are quite like REST, they are stateless, client which will connect them cannot say whether they are connected directly or via intermediary, so they are layered system, there are no way to get stale data, so we have cacheability, etc. There are several features which should be implemented by a web service so that it can be REST, I did not check the entire list, so for that reason we call it REST like.
We are going to implement two services, one handling http requests and other handling web socket requests. We are going to use those particular two because there are two existing
.NET classes, which can be used on desktop client side,
HttpClient for working with http, and
ClientWebSocket for working with web sockets. I was wondering
whether to create few more services, one working with messaging queue like RabbitMQ or ZeroMQ or some similar Apache stuffs, or to create something working with gRPC or similar. Most of
the message solutions require separate installation of message server (ZeroMQ does not by the way), so they are not OK for this demo. gRPC require .NET core for client side, so we
skip it as well for this demo solution. So, without further adu, let's go to
Then, we go back to node.js prompt and type npm init. Ho ho, no, we don't. I'm just kidding. We just type npm install express and then npm install body-parser. This is how the node.js prompt look after that:
Back to node.js console, we type node httpserver.js and we expect to see the confirmation console message we just created few lines above in editor. Now, without canceling execution on our script by node.js, we go and open our browser. Or, if we have more than one, we open those of them who is recently updated and is working fine. Then, we type the following in address bar: http://localhost:3001 and confirm. If we see our message from node.js server file, Hello, server is working in my case, then we are good for the next step. If we look back to node.js console, we will see long text, which is the parsed incoming message.
We are going to create a file, called business.js, which will contain one method called getFigure. This method will create an object and it will randomly assign one
enum and one string property. We are going to use this method in our httpServer.js and later in wsServer.js and we will export this method not with its' name, but with
Random and the other, using the node.js crypto library
randomFillSync. In production environment I would suggest that you use the
is not used here because it requires a callback and because the sync method is performing quite well, even when I make lots of requrests, from five clients making 2 000 or 50 000
requests almost simultaneously.
We modify the existing httpServer.js and add some post handling methods. Those methods will be used later by our client - it will call them and it will receive back information from httpServer. This is how the final httpServer.js look like:
You can comment/uncomment the console logging to have some information, this will be very useful when you develop futher. Now, it is time to start again the server by typing node httpServer.js in node.js console and confirm. After that, we should see again the console message confirming that server is running. We are going to use Postman to do basic testing of post requests. There are other tools available as well, I think you can do this with Fiddler as well, and maybe more tools which I do not recall at the moment. When Postman window loads, we will make a GET request to see whether all is good, the same thing we did earlier with browser. So we select GET from postman dropdown, type http://localhost:3001 and confirm. We should see the same message,"Hello, server is working", returned. If all good, we are going to make our POST request. So, this time we select POST from dropdown menu and specify http://localhost:3001/ping in Postman text box. Then, we click on Body tab and we enter text formatted as json - we specify JSON (application/json) from another dropdown, just below Body tab. See the screenshot:
We press then Send button and it should return again json, and this time the value should be "Pong". Then, we can try another thing, change the value in body to something else, like something else and again press Send. Here is the result:
This all might see quite easy, but it is very important. Try to misformat json message and your will get server exception. Try to log the parsed message in console to see how node.js colors the json objects which are treated as objects. If you send json object, but it is treated as string by node.js (by your logic actually), it will be displayed in a different manner.
While httpServer.js is running in its node.js console, we start another node.js console and navigate to the same folder as before. We are going to use the same business logic, but this time we will create a server, which will listen for web socket messages. For working with web sockets we are going to use ws module. This is how we install it:
and this is the wsServer.js code, together with final server solution structure:
When we are happy with code, we can start executing it as well. We type node wsServer.js in the second node.js console(the first one means this running the httpServer) and confirm. The notification message appears in console. wsServer is up and running. We are going to leave both node.js console running and we are going to proceed to part three of the article. We will assume that those two servers run always without issues for the remaing parts of article. If some expection occurs during development, we should restart them accordingly.
Final words for this part - I have tried to make the server part simple. Server code is usually more complex and it accounts for lots of things - how the service is hosted, clustering, other modules, authentication, authorization, etc. etc. etc., which goes outside the scope of our article. The good thing is that node.js development comes naturally and there are plenty of resources and sample code, so if you go node.js way, I think you go right way. When I use node.js everything comes quite easy and I feel quite happy. For comparison, I always feel angry at some point when I read documentation prepared by some big companies on similar topics.
In this part we are going to use
ClientWebSocket classes to make http and web socket requests to the services, which we just created in part two. We
have few goals in this chapter, so let's sumarize them:
WebSocketMessage. It will have only two properties, a string one called Method name and an object, called Data. Data will be actually a json string, which we later cast to other objects.
OnPaintSurfaceevent and we will draw our stuffs in this event helper method. We are going to use a
ConcurrentBagfor adding objects and later make drawings based on them on skia canvas. So we will also implement method for calling
InvalidateVisualof skia element, and for clearing all objects currently added to bag. We will have to tune a little bit our navigation and
ConsoleSimplifiedViewModelto handle all of those.
ConsoleSimplifiedViewa little bit more to allow text box, where we type all commands, to be selected when the user control is loaded. This is due to a trick, which is quite good explained in one CodeProject article by different author. Link to the article is placed as a comment in code itself.
So, let's get started with part three of our article.
Following the instructions of installing NuGet packages in part one, we are going to install three more in order to work wiht json and with Skia Here is how installed packages tab looks like after we installed all of those three packages:
Don't mind ControlzEx, it is required by MahApps. Build and run the client to make sure everythiing is fine and it is working properly.
We right click Models folder and add new class called
WebSocketMessage. It looks like that:
Then, we create the enum
Then, we create the enum
Then, we create the class
Figure. Build and run to make sure we are good.
Ok, we have come to this point, which is great. Soon we are going to start making calls to services and using the demo solution. Classes below are important for the sample solution, so please make sure all is good for you and you are happy with them. Please, ask if something should be explained further. All of presented class and markup files are final versions.
This is how the XAML for ConsoleSimplifiedView looks like (with some graphical explanations):
This is how the code-behid for same class look like:
So, this is how we change the
ConsoleSimplifiedViewModel. This is the final version:
and this is the navigation view model, the
At this point, you build and run the solution. If all goes well, we are ready for next steps - calling the services and explaining what the demo does. If there are troubles building and running the solution, you have to troubleshoot and eventually fix it. From this point forward, there is no more code written, we are going to call services, see how Skia draws the result and discuss on some approached and motivations.
We have build such a good demo solution and now it is time to use it. So we start the client, we navigate to view, start typing without clicking anywere and type help We will see a list of all implemented commands - pair of one word and set of other words, describing what will happen if we type the word and confirm. We know from part one how to change the font and background colors, so we can use this knowledge as well. Then, we start working with services.
We type pinghttp and if the service we created in part two and leaved working, is still working, we should see response message. Then, we type toggleDetails and we should see information about changed state in our console like text block. Then we type callhttp and confirm. We have something like this:
We hit the case where all three figures - circle, rectangle and square are generated. The random string is as you can see, and with our custom normalization methods we receive from this random string two x and y normalized coordinates. Skia use them and make the drawings. Let's make twenty more calls. Now we have this situation. We can check that the set of figures and generated strings are different every time and because of that we have different figures at different points on canvas:
Ok, now we type clear to clear the console screen and then type clearskia to clear skia canvas. Then, we type startws to establish connection to web socket server. Then, we type callws. Because we set the console logging to on, we will see detailed information again. Ok, let's call it nine more times for total of ten.
Ok, now it is time to play little bit more. We can start several clients and make large number of requests from them, both to http and ws servers. At some point, depending on our hardware
we are going to reach the limits and encounter some exceptions. Here is quick comparison between http and ws calls. You might ask: what's the difference between http calls that
HttpClient makes and web socket calls what
ClientWebSocket made? There are lots of differences, one call bears one information and the other another, beside the
business one, which is basically the same. Or you can start a release version of client and start making large number of calls until you see something like that:
We have encountered an
HttpRequestException, because we're creating so many
HttpClients, so that UI thread cannot process them and client eventually start to hang.
I'm using a Xeon server processor with two sockets and thirty-two logical processors running on @2.6GHz, RAM is big enough, 48 GB, but it is DDR3 and it's working on 1 333 MHz. For
comparison, I have other machine with Core i7 CPU with eight processors, running on @4.2 GHz, with less memory, 32GB, but it's DDR5 on 2 933 MHz, so there I make 50 000 http calls and
no such errors occurs. It occurs when I increase the number.
Now, let's see how web socket server will perform. We clear both console and skia canvas and type startws. Then, we make 5000 ws calls. No exception and it's faster. Because in our code we update the canvas after all requests are made, we can look at empty canvas for one or two seconds. OK, now let's make 50 000 calls. Still no exception. See the result:
While I was making this demo solution, I was not able to reach the limit of making web socket calls, maybe because I did not used more instances of demo client and making more calls. I
was hoping that at some point I can reach the limitations posed by
randomFillSync, which I used in server logic, but I did not.
It has some design compromises, but still I guess for demo solution is quite OK. I hope someone might find this interesting. The next steps are probably going to cluster the httpServer
and see how this can improve the number of request handled before getting
There is an interface called
IHttpClientFactory, which can be used for creation of HttpClient, available only for .NET Core. You can have more here
Make HTTP requests using IHttpClientFactory in ASP.NET Core. When .NET Core 3.0 is officially released, I guess a refactor
of desktop client sample can be made.