Monday, March 26, 2012

Javascript from within .NET


Target:
In this post we’ll discuss how to out of curiosity run easily complex Javascript functionality in C# using WebBrowser control (however without using WinForms).

Background:
We can achieve our target via building Web application where one of the web pages will contain JavaScript code. JavaScript method can then be invoked and its results can be returned to server (for instance with simple JQuery).
However, we don’t want to build Web application just to execute some single JavaScript function (but with important output). 

So is it possible to run JavaScript in C# without heavy artillery?

After quick googling, we’ve found couples of tools, most prominent and free are Jint and Javascript.Net.
Both open sources are pretty easy to use in case we want to run simple JavaScript code.
But what happens when JavaScript resides at some 3rd party website and obfuscated?
JavaScript interpreter is not enough in such case, we need browser capabilities in order to load 3rd party obfuscated JavaScript module and run its methods.
With a little help from Google and combining suggestions and tricks from different sources, we compiled a nice solution.

1.Creation of static HTML page
First of all, we created a static HTMLpage HTMLPage1.html with all the relevant scripts definitions. In following example, we’ve created a sample page with 2 references:
1. Reference to obfuscated online 3rd party JavaScript Api which contain class ApiClass with asynchronous function DoSomething().
2. Reference to custom JavaScript file that we created and it is situated at our output directory
In the body of the page, we wrapped calls to both our and external JavaScript API methods with simple functions that receives a list of parameters (in the examples below only one parameter, but it can be freely extended to more) and a callback (will be discussed later)
<html>
  <head>                          
    <script type="text/javascript" src="myJsClass.js"></script>
    <script type="text/javascript" src="http://someObfuscatedApi"></script>               
    <title>Hi</title>
  </head>

  <body> 
    <script type="text/javascript">
        function CallJsMethod(jsParameter, callback) {
            someClass = new someObfuscatedAPI.ApiClass();
            return someClass.DoSomething(jsParameter,function(result){
                callback(result);           
            });
        }

        function CallMyJsMethod(jsParameter, callback) {
            someOtherClass = new myJsClass.MyApiClass();       
            var result = someOtherClass.DoSomethingElse(jsParameter);           
            callback(result);           
        }          
    </script>
   </body>
</html>

2.Running WebBrowser control
Next step is to create and to execute the WebBrowser control in our module.
WebBrowser is a Windows.Forms control, as such it should be executed as Single-Threaded Apartment (STA) process.
In order to start using JavaScript it should be fully loaded in a browser (and we know that from a DocumentCompleted event of the browser).
The following code will do the trick:
public class JsExecuter
{               
    private string m_JsMethod;
    private object[] m_JsParameters;

    public void ExecuteJavaScript(string jsMethod, object[] jsParameters)
    {
       m_JsMethod = jsMethod;                
       m_JsParameters = jsParameters;              
       runBrowserThread();
    }

    private void runBrowserThread()
    {                
       var th = new Thread(() =>
       {
          using (WebBrowser webBrowser = new WebBrowser()
          {
             InitBrowser(webBrowser);
             Application.Run();
          }
        });
        th.SetApartmentState(ApartmentState.STA);
        th.Start();               
    } 

    //load our html page and so all the referenced scripts inside
    private void InitBrowser(WebBrowser webBrowser)
    {
       webBrowser.DocumentCompleted += browser_DocumentCompleted;                
       string curDir = Directory.GetCurrentDirectory();
       webBrowser.Url = new Uri(String.Format("file:///{0}/HTMLPage1.html",curDir));
    }

    private void browser_DocumentCompleted (object sender, WebBrowserDocumentCompletedEventArgs e)
    {
       WebBrowser br = sender as WebBrowser;
       br.Document.InvokeScript(m_JsMethod,m_JsParameters);                     
     }             
}

3.Calling the module and using the callback
Now we know how to create a WebBrowser control and make the call to JavaScript methods from it.The last thing that is left to do is to explain how to use the callbacks.
 
public delegate void ResultArrivedHandler(string results);

// in order to allow communication from JavaScript 
//[ComVisible(true)] should be defined
[ComVisible(true)]
public class CallBack
{
      public ResultArrivedHandler ResultArrived { get; set; }    

      //indicates default member for the object. It allows us to pass  
      //CallBack instance directly to JavaScript, and so JavaScript will
      //invoke its default member
      [DispId(0)]
      public void Call(string result)
      {
         Application.ExitThread();

         if (ResultArrived != null)
         {
            ResultArrived(result);
         }
      }
}

In the code above we defined a class named CallBack. An instance of this class will be passed as a callback to our JavaScript wrapper methods (see Creation of static Html page section).

When a result arrives from JavaScript, the ResultArrived event will be raised to notify our module’s clients.

4.Sample client code:

class Program
{
  static void Main(string[] args)
  { 
    CallBack cb = new CallBack(); 
    cb.ResultArrived += new ResultArrivedHandler(x =>Console.WriteLine(x));
    JsExecuter gs = new JsExecuter();

    // last parameter in parameter list will be callback object
    gs.ExecuteJavaScript("CallJsMethod", new object[] { "some parameter", cb }); 
  }
}
Here we created a CallBack object and subscribed to its ResultArrived event (just prints the results to Output).
Then, we just called to our module. Notice how the parameters and especially CallBack instance were passed (the CallBack instance is the last parameter !).

Epilogue:
That’s all! Simple and elegant solution for pretty tricky problem.
But there is still one big con for this solution and it’s of course the use of the WebBrowser control.
Yes, memory leak issues are still there and weren’t solved (online discussion thread: http://social.msdn.microsoft.com/Forums/en-US/ieextensiondevelopment/thread/88c21427-e765-46e8-833d-6021ef79e0c8).
It’s not a big deal if your application doesn’t run a lot of JavaScript methods.
However if it runs for hours and performs thousands calls to a module above then depending on your machine it can hang or stuck.

12 comments:

  1. Knowledge about the latest and vital technology would increase one's self esteem to the core at the time of lagging confidence. The content presented here is quite resembling the same. You have done a great job by sharing this in here.

    Best JAVA Training institute in Chennai | Best JAVA Training in Chennai | Hadoop training in chennai

    ReplyDelete
  2. Great post you could have completed the following. I'm truly happy to learn to read this kind of. This can be a very informative subject matter that you're picked. keep writing guerrilla marketing strategies

    ReplyDelete
  3. The blog gave me an idea to integrate java script with dot net. Thanks for sharing it
    Dot Net Training in Chennai

    ReplyDelete
  4. These are incredible as tester arranger and can hold back check whether the destruct couple of unencumbered message that is seized from the bed work. Java

    ReplyDelete
  5. Learning about energy and how it can be best utilized for betterment of mankind. Indeed most of the industries associated with the energy usage have started understanding about electrical power systems and engineering.
    view our floodspro site

    ReplyDelete
  6. This is the awesome post and I have huge information from your creative blog. The admin presented the content is very interesting and also comprehensive. Thank you for your sharing, Keep updating...
    Oracle Training in Chennai
    Oracle Training institute in chennai
    Tableau Training in Chennai
    Spark Training in Chennai
    Unix Training in Chennai
    Power BI Training in Chennai
    Oracle DBA Training in Chennai
    Oracle Training in Chennai
    Oracle Training institute in chennai

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. Thanks for sharing Blog . Keep sharing ! Java Course in Pune

    ReplyDelete
  10. Very nice blog , thanks for sharing Power bi Training in Pune

    ReplyDelete