It's Alive! Alive!: Apps That Create and Execute Other Apps

A few weeks ago we were talking to an iKnode user which was trying to write a code generator. While trying to help solve the problem, we came up with a solution that ended up being a really cool feature. Apps that Write other Apps.

We were already writing a service to allow iKnode Apps to call others, but to Create, Compile and Publish Applications from an iKnode Application was an amazing idea which would give iKnode Apps a lot more power and flexibility. We went ahead to spec, design and implement the feature. And today, we unveil the Frankestein Feature as we like to call it.

The iKnode engine is already implemented as a set of Services which are only accessible from the inside of our network. When an iKnode application runs, it is running inside our network, so it made sense to just allow iKnode Apps to just call our services. We created a service in the assembly iKnode.Applications (which is used by ALL iKnode Applications) to simplify the call to our internal service, allowing the caller to Save, Publish and Execute iKnode Applications.

Programming Interface

The Service is called “iKnode.Applications.ApplicationService” (docs) and handles “Application” (docs) objects. The Application class represents an application for the service. Let’s look at the structure:

Application Class

The ApplicationService is the class that allows the application to perform operations in the iKnode engine. The structure is defined below:

Application Service Class

The important operations for the Application Service are:

Save

Allows the Application to be Saved. There is not compilation or validation of code. A ‘Saved’ application is considered in Draft mode, which means it cannot be executed. Use this method to store partial or drafts of an application.

C# Code
1
2
3
Guid userId = new Guid("YOUR_USERID");
ApplicationService svc = new ApplicationService(userId);
svc.Save(app);

Publish

Allows the Application to published. The Application is first saved, then compiled and then published so that it can be executed. If there is any compilation errors, and exception will be generated. A successfully published Application can be Executed.

C# Code
1
2
3
Guid userId = new Guid("YOUR_USERID");
ApplicationService svc = new ApplicationService(userId);
svc.Publish(app);

Execute

Executes the Application and returns the application result if any.

C# Code
1
2
3
Guid userId = new Guid("YOUR_USERID");
ApplicationService svc = new ApplicationService(userId);
svc.Execute(appName, appMethod, methodParamsInJson));

Remove

Removes an existing application even if the application has been published. If removal is successful the Application will not longer be available.

C# Code
1
2
3
Guid userId = new Guid("YOUR_USERID");
ApplicationService svc = new ApplicationService(userId);
svc.Remove(app);

GetById

Returns the Application Information by using the Application Identifier. It will return null if not found.

C# Code
1
2
3
4
Guid userId = new Guid("YOUR_USERID");
Guid appId = new Guid("APPID");
ApplicationService svc = new ApplicationService(userId);
svc.GetById(appId);

GetByName

Returns the Application Information by using the Application Name. It will return null if not found.

C# Code
1
2
3
Guid userId = new Guid("YOUR_USERID");
ApplicationService svc = new ApplicationService(userId);
svc.GetByName(appName);

Example

We have built an example that covers all of the operations. Throughout this example we will class this Application “The Host”. The host’s main purpose is to create the Guest Application if it doesn’t exist, and then run it. But if the application already exists then it sohuld only execute it.

The code for the Host is shown below:

C# Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using System;
using System.Text;
using iKnode.Applications;
using System.Collections.Generic;

namespace Applications
{
    /// <summary>
    /// Defines the Application Service Test.
    /// </summary>
    [Application]
    public class ApplicationServiceTest
    {
        /// <summary>
        /// User Identifier.
        /// </summary>
        private static readonly Guid UserId = new Guid("YOUR_USERID");

        /// <summary>
        /// Tests the Application Service.
        /// </summary>
        /// <returns>Trace Information.</returns>
        public string TestAppService()
        {
            string appName = "GuestApp";
            StringBuilder trace = new StringBuilder();

            ApplicationService svc = new ApplicationService(UserId);
            Application app = svc.GetByName(appName);

            if(app == null) {
                trace.AppendLine("App was not found!");
                Guid id = this.CreateApplication(appName);
                trace.AppendLine("App '"+appName+"' created with Id - "+id);
            } else {
                trace.AppendLine("App '"+appName+"' found!");
            }

            trace.AppendLine("Application Executed: "+ svc.Execute(appName, "Sum", "{ a:'10', b:'20'}"));

            return trace.ToString();
        }

        /// <summary>
        /// Creates an application with the selected name.
        /// </summary>
        /// <param name="appName">Application Name</param>
        /// <returns>Application Identifier of the created application.</returns>
        private Guid CreateApplication(string appName)
        {
            string content = @"using System;
using iKnode.Applications;

[Application]
public class GuestApp
{        
    public Double Sum(Double a, Double b)
    {
        return a + b;
    }
}";

            List<string> dependencies = new List<string>();

            Application app = new Application(appName, "Test Application", content, "1.0", dependencies);

            ApplicationService svc = new ApplicationService(UserId);

            // Saves the App but no publish. Cannot be executed.
            if(!svc.Save(app)) {
                return Guid.Empty;
            }

            // Saves, Compiles and Publishes. If successful, then app can be executed.
            if(!svc.Publish(app)) {
                return Guid.Empty;
            }

            return app.Id;
        }
    }
}

What the Host application does is create another application named “Test” which performs a “Sum” operation. We will refer to the generated application as the Guest. The code this new Application will have is:

C# Code
1
2
3
4
5
6
7
8
9
10
using iKnode.Applications;

[Application]
public class GuestApp
{
  public Double Sum(Double a, Double b)
  {
      return a + b;
  }
}

Let’s test the Host application. Before executing the Host, this is how the Application Library looks like:

If we execute the application the first time, this is what we will see:

As we can see the application was not found by the Host application and created it before executing it. If we run it a second time, this is what we’ll see:

As we can see the guest application was found, so it was just executed. If we go to the Application Library we’ll see the GuestApp, just like any other application.

We hope you enjoy this new feature, we had a lot of fun writing it and coming up with applications to use it. Try it and let us know what you think.