Sunday, September 21, 2014

Exposing a REST-ful WCF service through a Windows Service using C# and .NET Framework

Requirements:

Expose a REST service on Windows that will handle Web requests and will call an external long running client Windows application, will wait for it's response, and will call an external REST service to inform about the fulfillment of the request. The REST service must be able to handle a multiple Web requests by putting them in a queue and executing them one by one. If the maximum number of requests is achieved, the service will return an error response to inform it about the impossibility to handle the request. The service must start automatically with Windows.

Solution:

We will create a WCF Service Library, that will be hosted in a Windows Service. For the purpose of testing the WCF Service, we will also create a Console Application that will host the service to make the debugging easier. To simulate a long running operation, we will create a console application that will have a Thread.Sleep(...) inside.

Pre-requisites:

Visual Studio Professional 2012. If you want to use express edition, you will not be able to create a windows service project(though there is a workaround).

Steps:

  1. Creating the solution. We will create a blank solution

  1. Adding the WCF Service Library project

  1. Visual Studio 2012 will generate a sample Service. We will create our own ones. Please delete Service1.cs and IService1.cs files.

  2. Now we will create the WCF service interface that will be implemented by our class that processes the jobs for us. First of all Add a folder named Interfaces where we will put the interface. Then add the interface, and name it IRestJobRequestProcessor. Open the file and replace the create interface with the following code:

    [ServiceContract]
    public interface IRestJobRequestProcessor
    {
    // This is for launching a new job using a get method by specifying
    // the job name in the Uri
    [ WebGet (UriTemplate = "QueueWorkWebGet/{pJobName}" )]
    String QueueWorkWebGet( String pJobName);

    // Same as QueueWorkWebGet, but using a POST method
    [ WebInvoke (Method = "POST" , UriTemplate = "QueueWorkWebInvokePost/{pJobName}" )]
    String QueueWorkWebInvokePost( String pJobName);

    // Returns The string that you provide as jobName
    [ WebGet (UriTemplate = "SimpleGetRequestResponse/{pJobName}" )]
    String SimpleGetRequestResponse( String pJobName);

    [ WebGet (UriTemplate = "ShutdownJobProcessorThread/{pKeyword}" )]
    String ShutdownJobProcessorThread( String pKeyword);
    }
  1. Now add a class with the name RestJobRequestServiceProcessor.

  2. Before you add the code from below to this file, please download log4net and add a reference to it(don't forget about its configuration xml, otherwise you will get information in Visual Studio about it not being able to validate the log4net section in App.config), and also add a reference to System.Configuration.dll. First is necessary for us because we want to log the result of the public Web Service we will get when our service finishes the long running task. The second is required for us in order to be able to get the path to the executable the service will call and wait for result. We will configure this path in the App.config later.

  3. So, here is the code for our processor class:

    namespace WcfRESTService
    {
    // Start the service and browse to http://<machine_name>:<port>/RestJobRequestServiceProcessor/help to view the service's generated help page
    // NOTE: By default, a new instance of the service is created for each call; change the InstanceContextMode to Single if you want
    // a single instance of the service to process all calls.
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] // Required in order to work as a REST
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    // NOTE: If the service is renamed, remember to update the global.asax.cs file
    public class RestJobRequestServiceProcessor : IRestJobRequestProcessor
    {
    private static readonly int JobSlots = 3;
    private static readonly ILog Log = LogManager.GetLogger(typeof(RestJobRequestServiceProcessor));
    private static BlockingCollection<Process> _jobsBlockingCollection;
    private static BackgroundWorker _backgroundWorker;
    private static String _responseRestUri;
    private static String _clientConsoleAppToLaunchPath;
    private static int _jobProcessorThreadDown;
    private static CancellationTokenSource _cancTokenSource;
    public RestJobRequestServiceProcessor()
    {
    try
    {
    // I prefer configuring log4net in code, but I left the configuration in the app.config also, for reference
    String logForNetConfigFileName = "log4netConfigFile.xml";
    FileInfo fI = new FileInfo(logForNetConfigFileName);
    if (fI.Exists)
    {
    log4net.Config.XmlConfigurator.Configure(fI);
    }
    else
    {
    log4net.Config.XmlConfigurator.Configure();
    }
    _cancTokenSource = new CancellationTokenSource();
    _jobProcessorThreadDown = 0;
    _backgroundWorker = new BackgroundWorker();
    _jobsBlockingCollection = new BlockingCollection<Process>(JobSlots);
    string devUrl = string.Empty;
    var executionFilePaths = ConfigurationManager.GetSection("ExecutionFilePaths") as NameValueCollection;
    if (executionFilePaths != null)
    {
    _responseRestUri = executionFilePaths["responseRestUri"].ToString();
    _clientConsoleAppToLaunchPath = executionFilePaths["clientConsoleAppToLaunchPath"].ToString();
    }
    Log.Info("responseRestUri: " + _responseRestUri + "; clientConsoleAppToLaunchPath: " + _clientConsoleAppToLaunchPath);
    _backgroundWorker.DoWork += bw_DoWork;
    _backgroundWorker.RunWorkerAsync();
    }
    catch (Exception exc)
    {
    Log.Error("Error: ", exc);
    _cancTokenSource.Cancel();
    }
    }
    void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
    CancellationToken cT = _cancTokenSource.Token;
    try
    {
    while (true)
    {
    Process p;
    // We block here until a new Process is available
    p = _jobsBlockingCollection.Take(cT);
    p.Start();
    // Do not wait for the child process to exit before
    // reading to the end of its redirected stream.
    // p.WaitForExit();
    // Read the output stream first and then wait.
    string output = p.StandardOutput.ReadToEnd();
    p.WaitForExit();
    // Now we will simulate the calling of another REST service,
    // This will be required if you must inform someone about the task
    // being finished. Here we just test if it works with an external REST service
    WebRequest request = WebRequest.Create(_responseRestUri);
    WebResponse ws = request.GetResponse();
    DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(List<Todo>));
    var obj2 = (List<Todo>)ser.ReadObject(ws.GetResponseStream());
    String resultantTodos = String.Empty;
    foreach (var todo in obj2)
    {
    resultantTodos += todo.ToString() + Environment.NewLine;
    }
    Log.Info("todos: " + resultantTodos);
    cT.ThrowIfCancellationRequested();
    }
    }
    catch (OperationCanceledException exc)
    {
    Log.Error("OperationCanceledException: ", exc);
    Log.Info("Shutting down worker!");
    }
    CleanUp();
    }
    private void CleanUp()
    {
    // We set the flag that we will not be able to process job requests anymore
    Interlocked.Increment(ref _jobProcessorThreadDown);
    // We drop other Processes that were added to be processed
    for (int i = 0; i < _jobsBlockingCollection.Count; i++)
    {
    _jobsBlockingCollection.Take();
    }
    _jobsBlockingCollection.Dispose();
    }
    public String QueueWorkWebGet(String pJobName)
    {
    return PushNewJob(pJobName);
    }
    public String QueueWorkWebInvokePost(String pJobName)
    {
    return PushNewJob(pJobName);
    }
    public String SimpleGetRequestResponse(String pJobName)
    {
    return String.Format("Ping/Pong test. You sent: {0}", pJobName);
    }
    private String PushNewJob(String pJobName)
    {
    String result = String.Empty;
    if (_jobProcessorThreadDown == 0)
    {
    Process p = new Process();
    // Redirect the output stream of the child process.
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.FileName = _clientConsoleAppToLaunchPath;
    p.StartInfo.Arguments = "15000 thisisatest";
    try
    {
    if (_jobsBlockingCollection.TryAdd(p))
    {
    result = "Job " + pJobName + " pushed, now in queue: " + _jobsBlockingCollection.Count + " from " + JobSlots;
    }
    else
    {
    result = "Cannot schedule job, queue full. Please try again later";
    }
    }
    catch (ObjectDisposedException exc)
    {
    Log.Error("Error: ", exc);
    result = "Cannot schedule job. Worker already stopped!";
    }
    }
    else
    {
    result = "Processor not running!";
    }
    return result;
    }
    public String ShutdownJobProcessorThread(String pKeyword)
    {
    String result = "Shutdown request denied!";
    if (pKeyword.Equals("forfeit9"))
    {
    result = "Shutdown request registered. Tasks still to be executed and cancelled: " + _jobsBlockingCollection.Count;
    _cancTokenSource.Cancel();
    }
    return result;
    }
    }
    }
  1. Now, lets configure the App.Config file. We need to add the configuration for the log4net, we will need to add the path to our external program that will simulate the long running operation, and also our declaration of the service. Here's the App.Config:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
    <section name="ExecutionFilePaths" type="System.Configuration.NameValueSectionHandler"/>
    </configSections>
    <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <log4net>
    <appender name="FileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="WcfRESTWindowsServiceHost"/>
    <param name="ImmediateFlush" value="true" />
    <appendToFile value="true"/>
    <rollingStyle value="Date"/>
    <datePattern value=".yyyyMMdd'.log'"/>
    <staticLogFileName value="false"/>
    <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
    </layout>
    </appender>
    <root>
    <level value="INFO" />
    <appender-ref ref="FileAppender" />
    </root>
    </log4net>
    <ExecutionFilePaths>
    <add key="responseRestUri" value="http://jsonplaceholder.typicode.com/todos" />
    <add key="clientConsoleAppToLaunchPath" value="LongRunningOpSimulator.exe" />
    </ExecutionFilePaths>
    <system.serviceModel>
    <services>
    <service name="WcfRESTService.RestJobRequestServiceProcessor" behaviorConfiguration="WcfRESTServiceBehaviour" >
    <endpoint address="http://localhost:8000/WcfRESTServiceDemo"
    binding="webHttpBinding" behaviorConfiguration="WebHttpBehaviour"
    contract="WcfRESTService.Interfaces.IRestJobRequestProcessor">
    </endpoint>
    </service>
    </services>
    <behaviors>
    <serviceBehaviors>
    <behavior name="WcfRESTServiceBehaviour" >
    <serviceDebug includeExceptionDetailInFaults="true"/>
    <serviceAuthorization principalPermissionMode="None">
    </serviceAuthorization>
    </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
    <behavior name="WebHttpBehaviour">
    <webHttp automaticFormatSelectionEnabled="false" defaultBodyStyle="Wrapped"
    defaultOutgoingResponseFormat="Json" helpEnabled="true" />
    </behavior>
    </endpointBehaviors>
    </behaviors>
    </system.serviceModel>
    </configuration>
  1. We need to do an additional thing for this project, to disable self hosting, because we will create special projects that will host our service. We can do that by doing in the properties and unchecking the “Start WCF Service Host when debugging another project in the same solution” checkbox:

  1. Now, next step is to create the Console application that will simulate the long running operation.

And the code is nothing special:

static void Main(string[] args)
{
if (args.Length == 2)
{
int sleepAmount;
bool parsed = Int32.TryParse(args[0], out sleepAmount);
if (!parsed)
sleepAmount = 2 * 1000; // 2 seconds
String retMessage = args[1];
Console.WriteLine("Received task request. Message: " + retMessage);
Thread.Sleep(sleepAmount);
Console.WriteLine("Done with the Job");
}
}
  1. Next, we need a console application that will execute our Service, before we create a windows service for it. This is necessary because it is easier to Debug the application, in comparison with Debugging a windows service.

  1. In this Project, the most important parts are the Program.cs file and the App.config. While the App.config is a copy paste of the WcfRESTService's App.config, the Program.cs contains very little code:

    class Program
    {
    static void Main(string[] args)
    {
    var host = new ServiceHost(typeof(RestJobRequestServiceProcessor));
    host.Open();
    Console.WriteLine("The service is ready at {0}", host.Description.Endpoints[0].ListenUri);
    Console.WriteLine("Press <Enter> to stop the service.");
    Console.ReadLine();
    host.Close();
    }
    }
  1. Now, we will do a very important thing from my point of view. This is not related to the solution of our problem, but it is a general recommendation that I can give to anyone that does .NET development. We will change the location of bin and obj folders of all our three solutions. Why? Imagine that you are working with a subversion system, and you want to commit. If you'll do that, you will get tons of temporary files, like .obj's, or .pdb's. You will have hard time to find what you really want to commit. So, first of all, we change the bin folder location. Go to every project in this solution, and change:

to:

This will force Visual Studio to create the bin folder two folders up the folder tree. Make sure you have such a structure. Why one back is not enough? Because all three projects will be in the same folder, and when you do the commit, you usually want to do that for all the solution (This is my experience with TurtoiseSVN and Visual SVN). This simplifies a lot of things.

  1. OK, we did this, but there is another unpleasant thing. The obj folder is still generated for every project in it's folder. We don't want that. Sadly, there is not a visual way to change this. You must close the solution, and open each .csproj and(or change) the following property:

<BaseIntermediateOutputPath>..\..\obj\LongRunningOpSimulator\Debug\</BaseIntermediateOutputPath>
  1. In order to make sure no collisions happen between obj files, I create a folder for each project in the target obj folder. Also, Note that these changes (changing bin and obj outputs) must be done for every build configuration. Yes, it is a bit of work, but it will save you an important amount of time and nerves :) P.S. Also note that sometimes Visual Studio might create the obj folder inside you project's folder. This is a known bug...

  2. Now, if you'll want to start the application and test it, you need to set the console application to be the Startup Project, and also you need to start Visual Studio as Administrator. The following screen shots represent the results of running the application

If you go and type the address from the screen shot below in your browser, you will get the following result:

Now, if you queue a work named “job1”, you will get the following result:

  1. Allright. We're nearly done. Now, lets create the Windows Service that will host our WCF Service. For that, we will add a new project to our solution, named WcfRESTWindowsService.

  1. Visual studio will create a simple Windows service for us. Now, we need to replace the code in two files. First is Program.cs

namespace WcfRESTWindowsService
{
[RunInstaller(true)]
public class ProjectInstaller : Installer
{
private ServiceProcessInstaller process;
private ServiceInstaller service;
public ProjectInstaller()
{
process = new ServiceProcessInstaller();
process.Account = ServiceAccount.LocalSystem;
service = new ServiceInstaller();
service.ServiceName = "WcfRESTWindowsServiceHost";
Installers.Add(process);
Installers.Add(service);
}
}
}

In order that code to work, we need to add a reference to the following Assembly:

Next, we need to Rename our Service1.cs file to WcfRESTWindowsServiceHost, and replace the code in the file with the following one:

namespace WcfRESTWindowsService
{
[RunInstaller(true)]
public class ProjectInstaller : Installer
{
private ServiceProcessInstaller process;
private ServiceInstaller service;
public ProjectInstaller()
{
process = new ServiceProcessInstaller();
process.Account = ServiceAccount.LocalSystem;
service = new ServiceInstaller();
service.ServiceName = "WcfRESTWindowsServiceHost";
Installers.Add(process);
Installers.Add(service);
}
}
}

Also, in order this code to work, you must add a reference to System.ServiceModel, and also to our WCF service library.

  1. Copy the contents of the app.config from WCF service project to the Windows service (You'll have 3 app.config with the same content, that's right)

  2. Now, don't forget to configure the output bin and obj paths, for convenience.

  3. We're ready to test the Windows Service. In order to do that, you must open the Developer command prompt, and execute the command shown in the screen shot below, and you will see the corresponding output (to uninstall just precede the name with /u):

  1. In order to see if the installation was successful, go to the control panel, administrative tools, services, and check for our service:

  1. Start it, and access the help section of the service to see if it's working. Normally it should, and just push a job to it:

  1. Now, if you open the log file in the same folder the windows service executable is (in our custom bin output folder), you should see something like this:

  1. DONE! :D

Thursday, April 18, 2013

C++/CLI bridge to connect a C# project to a native C++ DLL

After a week I finished the test interconnection between our project's C++ DLL and my WPF demo project I was required to create at work, I thought to write a post about everything I've done, first, to have a future reference, and second, to help others that face the same problem if they will have it. All the solution took me less than 3 hours (This with the creation of screenshots for this post etc). I'm ashamed to admit at this point that for this task I've spent several days trying to figure out the solution (here I added the time for solving the linking problems also, with undefined reference that can be very annoying and not obvious at all, especially if you have a big project to work with). The reasons were several:

  1. C++ is not my strongest programming language, I do seldom something in C++

  2. I never worked with C++ from C#

  3. I had already a C++ DLL project that I needed to modify so that it could be loaded in C#, and it did not contain any import/export logic/interfaces

  4. Doing a quick search on the web, I've found that people used two main approaches: C++/CLI and PInvoke. I didn't have any experience in either of them. (And as in the most of the cases, stackoverflow's community proved to be the best when it comes to answers. Just type on Google PInvoke vs C++/CLI, or just use these links directly: 1.Resource 1 2. Resource 2

After reading the answers from the links provided above, I found that PInvoke is used mainly for existing C libraries, meanwhile for C++ libraries it is preferred to create a C++/CLI wrapped. Of course, if you want to create a C interface for your interface, go ahead, but it wasn't my case though, so I've ended choosing the creation of a C++/CLI wrapper.

So, right now I will show you how this can be done.

Step 1: Creating the Native Project

When I had the requirement at work to link the C++ library with C#, I was focusing my attention to the existing C++ library. This led me to several hours of searching how can I access the native code from a dynamically linked library. If I knew, then, that visual studio (even Visual C++ 2010 Express) provides you with a wizard that creates such a project for you (Actually I found this at home when I've started writing all this), then I wouldn't have spent so much time in searching.

Just follow these steps:

  1. Create a new project (I named it cppbusinesslogic):

  1. In the Application Settings, make sure Application type is set to DLL, and the Additional option “Export symbols” is checked:

  1. Now, after pressing OK, if we check what Visual Studio has created for us, we can see the following code in cppbusinesslogic.h:

// The following ifdef block is the standard way of creating macros which make exporting

// from a DLL simpler. All files within this DLL are compiled with the CPPBUSINESSLOGIC_EXPORTS

// symbol defined on the command line. This symbol should not be defined on any project

// that uses this DLL. This way any other project whose source files include this file see

// CPPBUSINESSLOGIC_API functions as being imported from a DLL, whereas this DLL sees symbols

// defined with this macro as being exported.

#ifdef CPPBUSINESSLOGIC_EXPORTS

#define CPPBUSINESSLOGIC_API __declspec(dllexport)

#else

#define CPPBUSINESSLOGIC_API __declspec(dllimport)

#endif

// This class is exported from the cppbusinesslogic.dll

class CPPBUSINESSLOGIC_API Ccppbusinesslogic {

public:

Ccppbusinesslogic(void);

// TODO: add your methods here.

};

extern CPPBUSINESSLOGIC_API int ncppbusinesslogic;

CPPBUSINESSLOGIC_API int fncppbusinesslogic(void);

We can see the #ifdef section, an example how to export a class, an example how to export a variable and an example how to export a function. Pretty simple, isn't it? Oh yeah, there is one more thing, if we go to C++ properties, we will see two more preprocessor definitions: _USRDLL and CPPBUSINESSLOGIC_EXPORTS. First one is for MFC, and the second one is set in order __declspec(dllexport) to be defined.


4. We will add just a simple method that will return the value 77, to make sure that our class have a return method for us to use in the example.

Remark: This a simpler example, because if you were adapting an existing C++ DLL to export, you would be required to create an export class, that would have been the interface of the DLL, because in large projects the total C++ code might be too big to export all classes, and even if not, for sure there are classes that must not be exported at all.

Step 2: Creating the C++/CLI bridge

OK, the Native test project is ready, now we need to create our C++/CLI bridge.

  1. Add a new project to the solution (I named it cppblbridge):

  1. Automatically after creation the file cppblbridge.h is opened, and we see the following code:

// cppblbridge.h

#pragma once

using namespace System;

namespace cppblbridge {

public ref class Class1

{

// TODO: Add your methods for this class here.

};

}

Before we will modify it for our purposes, we need to make sure that we reference our BL project. We can do that by opening project dependencies and checking our BL project:

And the last preparation step is to add the dependency to our BL DLL in Additional Dependencies:

Now we can modify our files to connect to the C++.

So, the header:

// cppblbridge.h

#pragma once

#include "../cppbusinesslogic/cppbusinesslogic.h"

using namespace System;

namespace cppblbridge {

public ref class CppBLBridge

{

private:

Ccppbusinesslogic* m_cppBL;

public:

CppBLBridge();

~CppBLBridge();

int getTestValue();

};

}

And the source file:

// This is the main DLL file.

#include "stdafx.h"

#include "cppblbridge.h"

using namespace cppblbridge;

CppBLBridge::CppBLBridge()

{

m_cppBL = new Ccppbusinesslogic();

}

CppBLBridge::~CppBLBridge()

{

delete m_cppBL;

m_cppBL = 0;

}

int CppBLBridge::getTestValue()

{

return m_cppBL->getTestValue();

}

Note that Intellisense doesn't work at all in C++/CLI. Microsoft removed the support from Visual Studio 2010, even though in VS2008 it worked.

After we have done all this, we will be able to build our two projects. However, we need a way to see that our connection works. This we will do in the WPF that we will create in the following step.

Step 3. Creating the WPF project

Now we need to create our C# WPF project. Click on the solution and go add a new project:

In this project we will add a simple label and a button. I didn't spent much time on the design, because I don't find it useful for this post, so just drag&drop. Also, I added a button click event handler, and when it will be executed, we will get our value from C++.

Now how do we connect our WPF app to the bridge you will ask?

First of all we need to add a reference to our bridge project:

After this, I modified the MainWindow.xaml.cs file to contain the following code:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

using cppblbridge;

namespace TestApp

{

/// <summary>

/// Interaction logic for MainWindow.xaml

/// </summary>

public partial class MainWindow : Window

{

CppBLBridge cppBLBridge;

public MainWindow()

{

InitializeComponent();

cppBLBridge = new CppBLBridge();

}

private void button1_Click(object sender, RoutedEventArgs e)

{

label1.Content = cppBLBridge.getTestValue();

}

}

}


Note that intellisense works like marvel in here, in comparison with the C++/CLI project.

If we try to build our project, we will succeed, BUT, if we try to start our WPF project (and don't forget to make it a startup project), we will get something like this:

This is, however, the result of the simple fact that in the output folder there is not present the business logic dll cppbusinesslogic.dll. We can copy it by hand there, but I think this is very annoying. Here we have two options:

  1. Change the output folder of our WPF project to be the same as the one for the other two projects

  2. Use a post-build event that will copy automatically using a .bat file the required dll.

If the point 1 is simple, I would like to show the second one.

We will create a bat file in the folder of the WPF project with the following contents:

@ECHO OFF

SET CONFIG_PATH=%CD%

CD..

CD..

CD..

SET DLLS_SOURCE_LOCATION=%CD%\Debug

SET DLLS_TARGET_LOCATION=%CONFIG_PATH%


echo Dll's copy from : %DLLS_SOURCE_LOCATION%

echo Dll's copy to : %DLLS_TARGET_LOCATION%


set DEBUG_DLLS_LIST=(cppbusinesslogic.dll)

for %%i in %DEBUG_DLLS_LIST% do @xcopy "%DLLS_SOURCE_LOCATION%\%%i" "%DLLS_TARGET_LOCATION%" /d /y


set RELEASE_DLLS_LIST=(cppbusinesslogic.dll)

for %%i in %RELEASE_DLLS_LIST% do @xcopy "%DLLS_SOURCE_LOCATION%\%%i" "%DLLS_TARGET_LOCATION%" /d /y

This bat file will allow us to copy our cppbusinesslogic.dll to the destination folder of the WPF project.

Now, we will add the execution of this bat to the post build event actions of our WPF project.

Now, if we build our solution, everything will be automatic, and if we will run our WPF application, we will get our window started like this:

After pressing the Button, we will get:

Which is that what we needed. Everything works, the job is done!

Here is a link to GitHub:

GitHub