Story about desktop client who wanted to be more

14 May 2019

Motivation

I have decided to make a simple solution, which somehow mimics real world application, and to change it several times, using different UI technlogies and keeping the specific logic the same. I have created simple dotnet solution consisting of WinForms client and .NET core library. Then I created WPF client. Then I created Http.Sys server project which uses the library. From this point both desktop clients were making HTTP calls to Http.Sys server instead of calling the library directly (as before). Finally I created express.js node.js hosted solution which host a web application client, making calls to the Http.Sys server. Because node.js server and Http.Sys server are running on different domains (ports in demo), cross-origin restrictions are present. Most browsers restrict cross-origin HTTP requests. So I decided not to implement CORS (Cross-Origin Resource Sharing) on Http.Sys server, which is pretty fine solution, but to implemented http proxy in node.js for the Http.Sys server. So finally none of the clients were seeing the Http.Sys and they were working only with node.js server. Every change which I made solved an existing challenge and it demonstrate usage of a new technology. And of course, it introduces new challenges to solve...

What you will be able to make if you follow the entire article

Entire solution is created from scratch. It will require some typing and some work. If there is a place, where you give up reading it 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.

The idea of the article is to show how an application can be changed from single user desktop solution to multiple user locally hosted solution, accessible all over the web*, supporthing both web and desktop clients and even providing kind of web api. I wanted demo to be useful and for that reason I have created an app, which can do statistical classification and I am using really simple way of doing it. So the main idea is change - functionality stays the same - same textboxes and same buttons do the same thing, only UI technology is changing and when needed the related libraries. Logic stays the same - text processed in part one gives same result as same text processed in part four.

Technical view

The entire demo solutions consists of .NET solution project, mixing .NET Core and .NET Frameworks projects, and javascript node.js solution. .NET solution have Http.Sys server, library for “business” logic, two desktop clients, one WinForms and one WPF, and one small proxy project. Node.js solution is hosting one web application or you might call it Website if you prefer. Client javascript library is Vue.js, library for making http requests is axios.js. Calls from web application are proxied to Http.Sys server via node.js middleware.

Functional view

In machine learning and statistics, classification is the problem of identifying to which of a set of categories a new observation belongs, on the basis of a training set of data, containing observations which category membership is known. Our solution can be used exactly for that. However, this is a general reading programming article, so I will not use statistical and machine learning terms in it.

So, our functional view is like that: Users provide some text. Then this text is processed – some characters are removed, and remaining text is spitted into words. Those words are then placed into dictionary and ranked based on their occurrence in text. Word with most occurrences is ranked first, those with second most occurrences ranked second etc. Then users can decide whether to save the data or not. With time, when more and more text is processed and saved, the data grows. So, when users provide new text, the new text is processed and decomposed into words and then compared with existing data from previous inputs. The words who has similar ranking in both sets are colored, so that users can easily identify them. If ranks are very close, they are colored in one color, if they are not so close, they are colored in different color etc.

Live demo view

So, our solution is working and it is now in initial state. The HttpSys server is running on localhost:3010 and express node.js server is running on localhost:3011. Screen-shots to follow... I am going to enter some text, to process it and then to save it, so that I create an existing set. I need text which serve similar purpose, but it is created by different persons, with different ways of expressing and different way of constructing the text. An announce is a perfect candidate for that. It can be announce for car or real estate sale, announce for job, something else. Because we as developers sometimes needs to deal with job posts or at least know what a developer job post should contain, I am going to use job post for making my set. So, I used duck duck go/qwant to find out who is the top US job posting site, navigate to it and search for the term “javascript”. I took the first 100 job posts, no matter whether they are real or not, and extract the text (the file size of those saved as txt file happened to be 310 KB). Then, I process it and save it. This is how it looks on WPF client (word is shown in first column, number of occurrences ( or in more general terms the score) in second and whether to be used or not is indicated by a state of the checkbox in third column):

Then I removed words like, and, to, the, of...(generally known as determiners), then I removed most of the verbs or words which are not IT terms. Result is something like that, courtesy of WinForms client:

Ok, so far so good. Now it is time to test whether a newly entered text is similar to our existing set. So I go and search for another job post with term javascript. Then I paste it in text box and press Preview. This is how it look on the web application client:

Words which are very close are colored in green – word experience rank 0 in existing set and rank 2 in test set, those who are close are colored in yellow and those who are not so close but still in the game – in light gray. As we can see, the test text is quite similar to our existing set.

Now, let’s go and try with another job post, but this time with different search term, like sales or HR (sales in screenshot).

As you can see, we don’t have anymore green matches, but still we nail few yellow and light gray. So, it is not a job post for javascript something, but still it is a job post.

Now, let’s try with something completely different – for example current Top 100 number one song lyrics or car sale post or news report or something else (top 100 # 1 lyrics in screenshot):

No colors, no match. It appear that test text and our existing set does not have much in common.

Prerequisites to follow this article and make the described solution

This solution was created in spring of 2019, so the current versions at this time of all products and corresponding NuGet or NPM packages were used. For the .NET solution, the following IDE was used:

and the following NuGet packages has been installed, together with their dependencies:

For node.js solution the following IDE was used:

together with:

the following npm packages:

the following client side javascript frameworks:

and the following CSS style framework:

For all clients “Josefin Sans” font was used as default font. For Windows clients you need to install the font in order to use it, for web client you can reference it from google provided url. The font is available for download from google fonts site. For testing the Http services 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. Now it is time to start.

First steps – making the business logic library and WinForms client

The first version of the .NET Framework was released on 13 February 2002. More than 17 years from the days of writing of this article. Windows Forms or as they are also commonly called WinForms were included in .NET 1.0 release. So it is proper to start our demo with this UI technology. Our project will target however .NET Framework 4.7.2. Even that .NET Core 3 Preview 5 is available at the time of writing, we don’t have an official .NET Core 3.0

Creating solution and windows forms project

  1. We are going to create a folder on D: drive called devdemos and in this folder, a folder called dotnet
  2. Then, we start Visual Studio 2019 and from dialog window we select “Create a new project”
  3. We scroll down and select Windows Forms App (.NET Framework) and click Next
  4. We name the project DesktopClientWhoWantedToBeMore and specify as Location the folder we just created, D:\devdemos\dotnet and then we check the “Place solution and project in the same directory”. Then we confirm with Create
  5. The solution and the Windows Forms projects have the same name. We are going to right-click the project, select Rename and call it WinForms
  6. Then, we are going to select the project again, right-click it and select Properties from drop-down. We are going to change the Assembly name and Default Namespace to WinForms. We save the properties
  7. Then we are going to open the file Program.cs and rename the default namespace to WinForms by right clicking the existing name and selecting Rename and then typing WinForms
  8. We rename the file Form1.cs to Main.cs. A dialog appears and we confirm with Yes. The designer file should be renamed as well
  9. Finally, we build and run the solution

Creating the business logic project ( as .NET Core 2.2 project)

We right-click the solution and select Add → New Project. From the menu Add a new project we select Class Library (.NET Standard) and we choose Next. We name our project Business and we confirm with Create. We delete Class1.cs and we create two new classes, Word and WordProcessor. We want that our application process text and turn it into words, who have a score and grace to this score they can be ranked in certain way. We are going to create a class, which fulfill those real world requirements. The class will be called Word and it will look like that:

We will save all of the words that are designated to be saved by users. Still, for some sampling not all words will be necessary. So, the Show property will be used in this case. We will filter word returned to client by this property. Now, the most important class, the WordProcessor. This is the specific logic of the sample, and if we look one step further and imagine this is a real application, the class(es) will consist the specific business rules which every (small) company is using. Those rules are probably going to change in some way in future. In ideal case we would like that this change not affect the clients they continue to work with same method definitions as before. So we will make our business methods little bit more general – we are going to return interfaces instead of classes or void.

This is how we are going to implement the WordProcessor class. For this implementation we decided to use Newtonsoft.Json for serialization and deserialization. For better performance I would suggest using protobuf-net. I choose json in this case becasue it's easier to read saved data and later to examine services output in postman or in browser.

We are now ready to build the library and to continue with WinForms client.

Creating the windows forms UI

Working with WinForms means in most of the cases working with the designer. Usually controls are drag and dropped from a toolbox into a panel, which is placed into the form. Working with raw designer cs/vb files is also a common practice, especially when the form is big and contains lots of controls, but in our article we are not going to employ it. So:

  1. We add a reference to Business library by right-click References and then from Project tab we check Business
  2. We need to install Newtonsoft.Json nuget for this project too.
  3. Then we select the form file, Main.cs, so that our active window is Main.cs [Design]
  4. We click on the form to select it and then select Properties tab. Here we will do some changes. We will specify values for Size, Width will be 1024 and Height will be 640. The Text is going to be winforms. And the (Name) will be frmMain. We save our changes.
  5. From the toolbox we select Panel control and drag and drop it onto the form. There will appear a panel menu, which will show an option “Dock in Parent Container” when clicked. We select it. We select again the Properties tab and specify for the property (Name) value pnl. We save
  6. Now, we can select a TextBox or RichTextBox as our next control. TextBox will suit us well, but it has a limitation that it does not allow large text with more than 1024 new lines in it. So we go for RichTextBox, because we want to process very large texts. It has some drawbacks, for example it miss Padding. Anyway, drag and drop the control onto panel.
    • We specify as Location, X to 12 and Y to 12
    • For Size we specify 984 and 100
    • For (Name) we set rtb
    • And for Font we specify Josefin Sans, Regular, 16pt which will be shown as Josefin Sans 15.75 pt.
    We save. We might build the solution to see everything is fine.
  7. Now, we select BindingSource from Toolbox and place it into panel. It is automatically moved below the form. We select it and we set for (Name) - bindingSourceCurrent, and we set AllowNew to false. Then, for DataSource we select the arrow pointing down so that a drop-down menu appear and we click on Add Project Data Source. Data Source Configuration Wizard appears. On first screen we select Object and confirm with Next. Then we select Word class from Business library Business namespace and we confirm with Finish.
  8. We do the actions described in previous step, the only difference this time being naming binding source bindingSourceHistory. We save.
  9. From Toolbox we select DataGridView and place in into panel. From the Data grid view menu we set the data source for the grid view to bindingSourceCurrent. We remove checks from Enable Adding and Enable Deleting. We select the data grid view and we go to its Properties.
    • For (Name) we specify dgvLeft
    • For BackgroundColor and GridColor we set Window
    • For Location we specify 12, 118
    • For Size we specify 487, 421
    • For ColumnHeadersVisible we set false
    • For RowHeadersVisible we set false

    Then we go back go data grid menu and show it. Then we select Edit Columns… We mark Name and Count columns as read-only. We hide Rank (Visible set to false) column for now, it might be useful to show it when you want to verity the ranking and coloring further in the demo. We specify widths of columns which suits us well. We confirm with OK.

    It remains only to specify the data grid font. We call the DefaultCellStyle CellStyle Builder dialog by clicking the .. button. We change the Font to Josefin Sans Regular 10 pt, whichwill be shown as Josefin Sans, 9.75pt.

    We confirm with OK. We save.

  10. We do the actions described in previous step, whit the following differences:
    • we bind to bindingSourceHistory
    • (Name) is dvgRight
    • Location is 509, 118
  11. From Toolbox we select a Button and we place it on panel. We specify the following:
    • For (Name) we set btnPreview
    • For Text we set Preview
    • For Location we specify 836, 545
    • For Size we specify 160, 44
    • For Font we specify Josefin Sans, SemiBold, 16pt
  12. We do the actions described in previous step, with the following differences:
    • For (Name) we set btnSave
    • For Text we set Save
    • For Location we specify 670, 545
  13. Again, we follow the actions described in previous step, with the following differences:
    • For (Name) we set btnUpdate
    • For Text we set Update
    • For Location we specify 504, 545
    We save.
  14. It remains to specity the TabIndex for every control. rtb should have TabIndex = 0 and for the other controls we specify the following sequence: dvgLeft, dvgRight, btnPreview, btnSave, btnUpdate Our form should look something like that (or the way you want to be, if you decide to change the design a litle bit):

  15. We have to create event handlers for button clicks. We can do that by double-clicking the button in design mode or by selecting the button, then selecting Properties main tab and from it, selecting Events sub-tab and there, we specify the Click event method handler. We will remain the autogenerated method names later.
  16. We add an event handler for the frmMain FormClosing event and for the SizeChanged
  17. Finally, we are going to add some event handlers for rbt. We need event handlers for following events: Enter, KeyUp, Leave and TextChanged. This is because we are going to implement a custom placeholder for rich text box and we want to handle some keyboard events. More, we want when the key combination Shift + Enter is executed in rbt same things to happend as we have clicked the Preview button.

Creating code-behind logic

Now it is time to create code-behind logic, which will determine how our application works, at this moment of the development of the entire solution. We want the application to behave in the following manner:

We want few additional things.

Even that in today's software industry it is innovation that is respected, but not tradition, we will honor previous WinForms developers and we will name our event handlers and arguments in a specific way, even only for a small demo app. The designer names the event handlers in the following way - NameOfTheControl_EventName, and all event arguments are called e. We will use the naming On + NameOfTheControl + EventName and we will name EventArgs - ea, KeyEventArgs - kea etc. According some guidlines, worlds like Please should be ommited, if from users it is required to do something, that application would normally accept, like "Enter Password" or even "Password" instead of "Please, enter your password". This cue banner message starts with "Please" because in my native language well written instructions start with please, even it is sometimes the only choice or it imperative. And finally, this is the code-behind for frmMain

We build and run the solution. Application should be working fine, if we have created everything properly. Now let's to do the Part One live demo and close the first chapter.

Live demo of WinForms client and recap of the demo solution so far

Good job! We have completed part one of this article! We have created a WinForms client and .NET Core library and we have processed some text. Let's recap the good things:

And now let's recap the things which can be improved:

  1. The UI allows only one history set to be saved and worked with - this is not so difficult to implement and we leave it to the reader
  2. Only one user can work with one set, it is not possible two or more users to simultaneously provide set data and save it/process it, all are working with their history set
  3. In order to get an updated client version, we need to provide users with recompiled application.
  4. When coloring the matching word rows, we access directly the row index, in SpecifyColor method. If in future we implement more complicated ranking system, it might happen that by mistake we pass an index which is outside the row's array or we can provide negative index. So even for simple methods we have to put extra care.
  5. Our custom resizing method need change every time we add or remove control from the panel.
  6. For every users actions - button clicks, keyboard shortcuts, we have a separate event handler. Some of those event handlers call same private helper methods. For this form we have nine event handlers, which just call some helper methods. In order to track event handler methods related to every UI element, we need to constantly look at designer or designer cs file.
  7. Custom Cue Banner implementation - this adds complexity and it will be nice if we have something out of the box

It's time for Part Two - Making the WPF client

Part two: Adding WPF client to our existing demo project

Windows Presentation Foundation, WPF, was included in .NET Framework 3.0 release, on 21 November 2006. Four and half years after WinForms and more than 13 years from the days of writting this article. As WinForms, WPF is for making windows GUI applications. There are differences between the two and one of those differences is that WPF uses a mark-up language, called XAML, for definng the UI, as opposed to forms resources and code-behind designer generated files in WinForms. WPF development can be done like WinForms, with lots of code-behind code, or using a pattern, called MVVM, Model-View-ViewModel, or using a mixture between two. We are going to use the MVVM approach in our demo. When using MVVM model view basically does not care much about view model and view model does not care much about the view, they are loosely coupled. We can specify a command name and parameter in the view, but view model does not care whether the command was called by a key stroke, by pressing a button, by clicking a menu or by toggling a toggle. Similar, view model provides a collection or collection view source view, but does not care whether this is used as Item Source (for example) of a combo box, listbox, listview, datagrid etc. So, we can almost independently working on views and on view models and later just bind them. Or a designer can work on the View to make it really cool and developer can work on view model. Or..., ok you get the idea.

Adding the project, creating simple project folder structure and creating classes for base view model and command implementation

The focus of our demo solution is not WPF, so we are not going to use additional UI libraries and we are not going to implement content-based navigation or some little bit more advanced stuffs. You can browse the net for such examples. Simple content navigation is described in one of my other articles, Easy prototyping

We are ready with the basics for this part, let's move forward.

Creating the view models and implementing the same user interactions like in WinForms

As we already have a really good working WinForms apps, we just need to "translate" code from WinForms to our project. Some of the code will be unchanged, but some of it will change. We have to decide on those stuffs:

Time for the code. Here is the enum WordDistanceEnum. We place the file in infrastructure folder, becasue we don't have designated folder for enums.

and here is WordViewModel, placed in View Models folder

and here is the view model for the main window. We called it MainWindowViewModel. It is more than one screen long, so it is easier to paste it directly:

Time for the mark-up. Before we go to this, there is one thing remaining - we open the code-behind files of App.xaml, App.xaml.cs and MainWindow.xaml, MainWindow.xaml.cs and we remove all non-used references and clean all default comments made by wizard. We save and build the project.

Creating the XAML mark-up for Main View and binding it to view model

Let's make a summary about the main view - we have items controls for current and history word sets - we are going to use list views for them, we have simple item template and triggers for it and we have triggers for GotFocus and LostFocus (and forms Closing event). We have the view model as a data context set directly in the markup. Below you can see the full XAML*

Resources and interactivity triggers are collapsed so that the first screen-shot fits on one screen. You can find them expanded below.

This is how the finished project looks like, together with WinForms and Business projects. Both WPF and WinForms projects reference the Business library project. Time for small live demo for WPF client and then we close this part.

WPF - Live demo

Good job! We have completed part two of the article! We have not only WinForms, but also a WPF client, working fine with our business library.

The good things for client said in end of part one hold for this WPF client as well - It works and it does the job propertly. And we have a robust UI with proven technology. We have resolved some of the things, which we wanted to improve in part one - we don't need to invent any methods for layouts rearangement when window is resized, WPF do this for us, because we set relative column sized. We use commands and we don't create "manually" events for every user action. And we don't deal with row indexes when we do the colloring of the matching words. Our solution is more robust. Maybe it has become more complex. When we talk about commercial applications using commercial third-party controls and components, WPF or WinForms is actually a personal choise for the developer, both can produce excellent applications.

Still, one of the main problems remains - only one user can create a word set at the time and multiple users cannot use the same set simultaneously. It is still single user application. So this will be our goal for part three. We can easily solve this by using a database and use it to handle multiple users. This is perfectly fine solution and the common case in company owned network. It will be risky however to allow users to connect directly to a database over the Internet. So we are going to create a web server instead, which will provide our business logic to various clients via http connections.

Part three: going web, going async

From WinForms and WPF we move to ASP.NET Core. Main reason for using ASP.NET Core is because our business logic is written in C#. C#/VB.NET are great for making business logic. However, if we want to use other technology for the web, something not .NET related, we have to think about a way to access the existing logic from the other server. This will complicate the solution. One other option will be to re-write the existing logic using other language. Even for our simple logic this will not be a trivial task, for a real-world stuffs it might happen to be very difficult. The main reason we used C#(for VB.NET is the same) back in part one and two was the ease of creating a native applications for Windows. Still, the C# for the web and the C# for the desktop are different, event that the language looks the same (meaning C# is standartized and its specification is the same for everybody), it feels different. The frameworks and tools are written by a different people, the way of writting feels different. Maybe they are not approaching each other with My kung fu C# is stronger than yours, but their code feels different. There are C# developers which concatenate strings with + sign, others uses string.Format with so-called composite format strings, and others use interpolated strings. Reasons for that vary and one of them is because they come from other languages/technology background and are accustomed to one way or another. This 'discussion' goes outside the scope of the article and we stop it here. But we promise to thing about using other technology (node.js - I am talking to you) in our solution and we will think again in part four. Now, let's go back to this part and ASP.NET Core.

ASP.NET core ships with three options for web servers - the Kestrel server, which is the default, cross-platform HTTP server, IIS HTTP Server, in-process server for IIS, and Http.Sys server, Windows-only HTTP server. I wanted application, which self-host the web server and it was easier (for me) to achieve this using the third option, the Http.Sys. In this part of the article, we are going to create server for handling http calls, GET and POST request mostly, and one proxy project, which will use HttpClient for making calls to server and implementing the same methods, which WinForms and WPF clients expect, as if they were working with WordProcessor class from Business library directly.

Creating the server project

We are going to install four NuGet packages:

We are not going to use transport security in this demo, neither authentication or authorization. We are going to have three endpoints, current, history and save, which will respond to few HTTP verbs - GET, POST and PUT. For the others we will return default messages. Response from server will be provided as "application/json" (RFC4627). Our server will use an object with state, this will be the instance of WordProcessor. It will remain in memory. For our demo performance of this will be just fine - we will be avoiding usage of save method directly, because this method serialize the content of the dictionary to the disk and it can be the only slow thing, especially if called multiple times. More of this in part four. Finally, if you want to use object with state in your server, think carefully before doing it. Below you can find the code for Startup

OK, now let's build and run the solution. If all goes well, you should see something like that:

We are going to make few basic request to the server to make sure it works properly. We start Postman and make a GET request to http://localhost:3010. We have a response. As you can see shortly just few lines below, the first request to Http.Sys took some time, but after that every other request is processed very fast.

We make another request. POST to http://localhost:3010/current with some demo data:

And now, POST to http://localhost:3010/history. This call is not expected to run response data, only successfull status of 200

Let's make another one. This time the response time on server should be less than a millisecond.

And now let's get the processed word data from server - we make GET call to http://localhost:3010/history?top=100&useShow=true. The first call to a particular endpoint is fast and compared to other server products is OK. The subsequent calls to the endpoint however are as we said above, very fast. We make the exact same call:

And those are the times at server side. Compare the times for a first time call and subsequent call.

And now let's focus on what's happens after the subsequent get call, when I initiated few more of those. We are again below millisecond :)

I would advise that you spend some time and properly play with the web api, making multiple calls and testing all exposed endpoints, taking advantage of the excellent debugging support, which Visual Studio offers. When you are ready and feel OK with our solution so far, we move to the client side.

Creating a proxy project in order to ease server interaction for desktop clients

At this point of development of our solution the two desktop clients, which we have created, still reference the Business project. They use the Word and WordProcessor classes, defined in this library. We have created a web server and this web server have a reference to Business library. So, it is now the web server who provides all the logic from Business, this time over the http and using application/json. Those desktop clients no longer need a reference to Business project and they have to consume what web server provide. For that purpose we will create a project specifically for working with HttpClient and in this project we will also create a Word class, which is going to be used only by clients. Then we can start modifying both Word classes according to the needs of server project and proxy project. For example, our ranking is done always on client side, so the Word class on server will no longer need the Rank property. Here are the steps we are going to follow now:

We select Word class from Business project and we remove Rank property. This is now the class should look after. We then compile only the business library.

We rename the default Class1.cs to Word and we rename the class name to Word as well. Then we create few properties. This is the Word from Proxy project. If you want to change the names of the properties, this is just fine, but then you have to add few custom lines to make the proper deserialization of the server response. And to change all those names in both clients too. And to make sure it works as before. And..., well you get the idea.

We are going to add an enum called HttpVerbs. Here is it:

and then we create class called BusinessProxy, which will hold an instance of HttpClient and will communicate with server. Both clients will have a reference to the Proxy project and they will use the BusinessProxy class instead of WordProcessor. We will make the method names of BusinessProxy the same as those of WordProcessor just to make our refactoring of existing client code little bit easier and save some renaming.

Threads are no joke

You might be wondering - ok, MakeRequest method returns a Task<string>, why don't we use the Result directly on this, but we have use Task.Run(() => something).Result. Short answer - give it a try - it will hang out your client and you might wonder why this is hanging and there is no exception thrown. For more, you might want to consult an MSDN Magazine Article (from March 2013), where such situations are explained in greater details - here

We build the Proxy project. Now it remains to fix the both desktop clients projects.

Fixing WinForms and WPF projects

How it is time to fix WPF project.

At this point we are ready to build the solution and run it. All should go without troubles and HttpSys server console should appear, because this is still our starting project. This is how the final dotnet solution look like:

During debug session, we will select Solution Explorer tab, and from there, right-click on WinForms or WPF, and Debug -> Start new Instance. Or we can Publish the .NET core server project to our local drive, start it from there and run the clients the old way.

Live demo - Http.Sys server and desktop clients

In case we want to produce an executable from a .NET Core project, we have to Publish the project. We are going to publish the project to a local folder. Those are profile settings, which we are going to use:

For this demo we have published the http.sys server as executable and we start it. As we mentioned before, first calls to every endpoint will took more time, but they still will be relatively fast, and the subsequent calls will execute very fast. So we enter some dummy text to make a history set and we put it on server. Then, every new instance of every client has this history set and start with it. We can add more words to it or update show property for every word.

If you made it so far, excellent job! We have completed part three of our article! We have created Http.Sys server for providing our existing logic over the http and we have tuned our existing desktop clients projects to use the server and to make calls to it. We are using http protocol for our communication instead some custom and so common alternative and this makes our solution even more robust. We have solved one more challange from those set at the end of chapter one - multiple users can access and use the same data, our solution is now distributed. We do not rely on database, neither on third-party service to achieve this. Using databases in production application is a must, but it introduces additonal complexity and it is risky to connect to databases over the net. Or at least not so easy as accessing urls with browsers or with fat clients. Using third-party services is common in production and they basically do everything for you, but you pay additionally for this service. And sometimes this service can be rejected by owner and your solution will no longer work in such case.

Finally, we have a web server and we have desktop clients. It remains to find a solution to last challenge from part one - easy update of client logic, for example ranking logic. One of the best candidates for that is a web client, which will provide same user functionality as existing clients, but because it is provided over the http as well, it can be very easily updated. Time for the last part, part four.

Part four and final: node.js hosted web client and node.js proxy

Our article has reached its final part. We are going to create a web client which will use the same endpoints, we might say the same web api as the existing desktop clients and it will provide the same functionality to the users. User experience from native application and from web application might not be the same, but in our case the interactions are simple and we can agree that in this particular case web clients can be used perfectly fine. We can continue with Http.Sys server and build more stuffs there and add more middleware, not only our custom stuffs and the middleware for responce compression. Hosting of static data can be done without any additional Nuget and it will be just fine. However, we are on the web now. We are not bound to any particular technology, we can use one or another approach. So, ASP.NET Core is good and will do the job, but let's try something else. Three part for dotnet up to now, let's make one part for node.js stuffs

Initial release of node.js was on 27 May 2009, 10 years ago from the days of writting of this article. The package manager for node.js, npm, was introduced in January 2010. Initial release of Vue.js was made in February 2014, or about 5 years ago. And initial release of axios.js was on 29 August 2014, let's say 4 and half years ago. We get an idea about the main stuffs for this chapter. It is time to move on.

Setting up the node.js solution

Our solution should look like this:

Creating the markup

This is the markup for Main.html. The markup of the page should be simple. We will have a textarea, three buttons and two unordered list. List template will have to display the name, count and the boolean property Show. We can control the color of the element by setting its class. The css framework which we are using colors the element if we add specific word to its class. We are going to use this. For filling the data we are going to use Vue.js provided binding. The idea is very, very similar to the situation that we have with the WPF client. This similarity will be further emphasized when we look at the main.js script few lines later.

Main.js - heavily using Vue.js and axios.js

The main.js will contain the code for the web client. Axios will be used for making async calls to the server and vue.js will do the binding, event handling, property change tracking etc. We have called the main Vue variable mainViewModel to again emphasis simularities between MVVM approach we used in WPF client and approach here. Those framework models are called MV* for a reason. However, let's give more details on the code:

And finally, here is the main.js file. If you have some troubles here, use a browser debugging IDE and put console.log or breakpoints. Not so cool as native debugggin in Visual Studio, but it is not so bad either. Getting better with time.

Server file, proxy and buffer rewriting if needed

Now it is time for final part of node.js solution - the server file. Node.js will serve two functions - will host the main.html (we can open the file main.html manually and it will still work, if node.js is running and api part is accessible) and provide it with css and js files, and will proxy request from /api to Http.Sys server. For making the proxy calls we will use the http-proxy-middleware. Pecularity of this middleware is that if node.js uses npm package body-parser or in newer versions (as ours :)) even express.json(), this means that node.js will read the buffer of a POST or PUT or other request*. And buffer usually can be read only once. Why - outside the scope of this article. So, in such case we have to re-write the buffer and move on. This is why there is commented code on the screen-shot.

And that's it for the web part. We are ready with web client stuffs. Time for the final live demo!

Web client and http proxy live demo

Cool! We have achieved the remaining goal from part one - easy deployment of a client. It's so easy like typing the url and confirming. Now we have both desktop and web clients and we have native experience and if needed, very easy deployment. The idea is that all clients should be used together. We did a good job!

Still, now we have to care for two web servers using two different languages, clients developed using one framework and language, and other clients developer using another language and other frameworks. Surely there will be new challanges as well.

I hope you enjoyed the article

Maybe it is overly simplistics, but I hope it emphasis at all points on which I intended. I have tried to make it like a retrospective of how the development was years ago, what changed then, and what changed after that. Of course, there are some things which were missed, some things were not considered in great details, but it is what it is. I have checked typos (code or functional) and I hope there are none which are considerable. I hope someone find this article interesting.