Breaking Post! Utilizing Python Server in a C#.NET Application

Integration is the key
I always enthusiast about integration between different programming language and technology

Breaking Post? Yeah, the same like it is in Breaking News : an interesting event occur during main events... Laughing

While I work on series of articles that talk about developing Flask application on Openshift, I also in the middle of developing a C#.NET Point of Sales application for a Malaysia based company. In the process of coding its admin dashboard, I feel like wanting to use a web view for it. Traditionally, a GUI Desktop application developer like me will just drag and drop GUI element on a dashboard and then code its interaction. But as I about to doing it, an idea flashes my mind : why don't I use Bootstrap + Python for this part? I can leverage Bootstrap cool element design by simply reusing its plethora of example laying around in the web and have access to Python power to serve the dynamic content of the web. The idea seems promising...

 

 

The result is not bad : 

The result of integrating C# and Python powered internal web 

A C#.NET POS application showing off its Admin Dashboard, which is a Python powered Bootstrap internal website

I am sure you can easily recognize Bootstrap Top Fixed Navigation Bar and other elements there. You can also clearly see that the application data (company name, address and user login) was mixed to form the dashboard page. And clicking that "Click here" button will bring a Session Property dialog box coming internally from the application itself. How can we achieve this?

.NET come with a WebBrowser control which you can use as an embedded web browser displaying any web resources : local HTML file, local server address or any internet location that you wish to display in your application. With this ability, we can move further : why not shipped our application along with a Python web server that get displayed in a WebBrowser control embedded in our Main Window? To display our .NET application data in the browser, we can simply pass its data whether using URL variable or Post method to the WebBrowser control. Neat, right? 

Creating C# Windows Forms Application

To show how it is done, let's begin by creating a C#.NET desktop application using any .NET version. Here, I am using Visual Studio.NET 2012.

Create new Windows Forms project in Visual Studio 2012
Create new Windows Forms project in Visual Studio 2012

From the toolbox, find web browser control, drag and drop it to our main form.

Web browser control
Web browser control use in a Windows Form.

VS.NET will automatically set its Dock property to Fill. That's good.

Creating Python Flask Application 

For the Python application, I am going to use the same Flask application that we already have in Part III of our article series, with code changes in main.py file shown below :

from flask import Flask, render_template, request
import json
application = Flask(__name__)
@application.route('/', methods=['GET', 'POST'])
def index():	
   appdata = {}
   for field in request.form:
      appdata = json.loads(field)
return render_template('water/index.html', appdata = appdata)
if __name__ == '__main__':
   application.run(debug=True, host="0.0.0.0", port=7465)

If you compare this file with the one in Part III, you can see that here we add argument in def index decorator to make index() method accessible using POST method. Having this ability, we can pass any JSON data from our C#.NET application to our Python application with ease. If we only use GET method (the default method for any route method), we will have to send and modify tedious variables to be part of URL being call. Not a pleasant experience.

We also import json object and call its loads method to deserialized any JSON object being passed in POST data, and stored it in appdata variable. Lastly, we render a Jinja template and pass appdata variable in it. 

To use appdata variable in Jinja template, we simply use it this way :

{{ appdata.company_name }}
{{ appdata.company_address }}

Let's go back to our C# application and see how do we pass a JSON object using POST method

Sending Json Object in POST data from C# Application

First we must create C# class that resemble a JSON object we wish to send 

class JSONdata
{
public string username;
public string company_name;
public string company_address;
}

Next we use Newtonsoft JSON serializer to serialize our JSON Object to string and convert it to byte array.

String postdata = JsonConvert.SerializeObject(jsdata);
System.Text.Encoding a = System.Text.Encoding.UTF8;
data = a.GetBytes(postdata);

All is done. Now we can send this Json object to our Python Flask server using WebBrowser navigate method

webBrowser1.Navigate("http://localhost:7465", "_self", data, "Content-Type: application/x-www-form-urlencoded" + Environment.NewLine);

Responding To Internal URL

As you know that the web we have is an internal web server, of course it will include many links/buttons that should respond specifically to our application, such as opening another Form. How do we do that?

Fortunately WebBrowser control has navigating event that occur before a navigation to another URL is in place. We can override this event and modify it to suit our need, seen below :

private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
   string url = e.Url.ToString();
   if (url.StartsWith("app:"))
   {
      string context = url.Split(':')[1];
      if (context.Equals("company_profile"))
      {
         MessageBox.Show("You have a great company there!");
      }
      e.Cancel = true;
   }
}

The rule is quite simple : all internal links that specific to our application must have app: in the beginning of its URL. Using a plain if statement, we can act according to its context data, and cancel the navigation process. Of course, you can make the code cooler by introducing register action mechanism for specific context. Here is a screenshot when user click Company Profile link:

Mixing C# and Python never seems this cool...
Mixing C# and Python never feel this cool... Cool

Packaging Python and Running It!

In my previous post I already talk about setting up local Python environment for Windows and OSX. Prepare that first if you haven't done so, but skip the part that talk about virtualenvironment. We don't need it. We just have to copy  C:\Python27 (or any version that you use) to our Python project folder (replacing env folder). But make sure that all python package you are about to use already available in Lib\site-packages.  

Here is my arrangement of our application, complete with a Python 2.7 installation :

Our Python server, complete with Python 2.7 installation
Our Python server complete with Python 2.7 installation

Make sure that this server directory resides in the same directory as our application executable. To run it, we can use System.Diagnostics.Process to automatically start/stop our Python server in the constructor of our main form.

...
Process process;
public Main()
{            
  InitializeComponent();
  ProcessStartInfo pi = new ProcessStartInfo();
  pi.FileName = Application.StartupPath + "\\server\\Python27\\pythonw.exe";
  pi.Arguments = Application.StartupPath + "\\server\\wsgi\\main.py";
  pi.CreateNoWindow = true;
  process = Process.Start(pi);
  timer1.Interval = 2000;
  timer1.Enabled = true;
}

We use pythonw.exe binary, in order for the startup server process to not create and display console window. We also wait for 2 seconds before starting our first navigate using WebBrowser control. Later, we can kill pythonw.exe process already created using process.kill method. I do this in Form disposing event seen below :

protected override void Dispose(bool disposing)
{
   if (disposing && (components != null))
   {
      components.Dispose();
   }
   base.Dispose(disposing);
   if (!process.HasExited)
   {
      process.Kill();
   }
}

But it seems sometimes pythonw.exe process didn't clearly removed from memory. It may be a specific Windows issue and may already been fixed in the latest Python release. Have a check on it!

 

Conclusion and Github Repo

"Isn't it inefficient including a full Python installation to each desktop installation?", you may asked. Maybe. But the functionality and capability offered by including a full fledged Python server in our Desktop application bring many new and interesting possibilities. This should be a great things for many system integrator. Such as me. For example, I am thinking of coding the data access layer as a Python Flask + SQLAlchemy internal server, using JSON as the communication mechanism. Using this technique, this desktop application can act as a fully functional server for local clients wishing to access the same data. Many more!

If you want a complete Github Repository for this application, you can fork it here. I didn't include the Python server there, so you have to copy it to the same directory where the *.exe binary will be generated. Thinking further for the installation process when you distribute your application, I think you can use NSIS installer (or any setup installer that you choose) to copy Python27 folder to the same folder where you application installed. I have try to use .NET for this actually, but it's not an efficient approach, as I have to manually set all Python files using Copy if newer attribute.

 

Have fun with amazing world of Python!

Stay tuned! 




Leave comments

authimage
  • Hi Rick.

    Nice to know that we tackle the same problem using similar way. Did you use JSON to for the data transfer part?

    Actually, I have use this technique in 2003 using a Visual C++ 6.0 application. But I didn't shipped the application with any web server there. I just use a plain, dumb (but working) technique to manually search and replace certain tags in the template with data coming from C++ application.

    Now using a real dynamic web, the capability of this feature is getting cooler.

    • Eko
  • In our company we did something similar to enhance the php web app with the data from Python api server.

    • Rick

Copyright(c) 2014 - PythonBlogs.com
By using this website, you signify your acceptance of Terms and Conditions and Privacy Policy
All rights reserved