Wednesday, May 13, 2009

CodeConsole Web Part for SharePoint

About the CodeConsole Web Part
Download Source Code + WSP

This is a web part I've found myself wanting several times, when I've been either too lazy to compile and deploy code, or haven't had the development tools available on the target SharePoint box.

In short, the CodeConsole Web Part ..

  • Executes C# code, entered into a text area, on the server. The code can access the SharePoint object model, and other .NET / ASP.NET objects.
  • Displays output from the execution in an output text area.
  • Uses jQuery to make asynchronous requests to a custom .asmx service on the server.
  • Uses JSON to serialize / deserialize data, should more complex structures be required by someone who decides to use it.
A Screen Shot of the Web Part in action:

CodeConsole Screen Shot
Click the image for a larger version
Update: Uploaded a demonstration screen capture video, in case a screen shot isn't enough. Check it out.

In this shot, the input frame is white, and the output frame is black. The code passed through the input box will have the following using directives available to it by default:
  • using System;
  • using System.IO;
  • using System.Text;
  • using Microsoft.SharePoint;
This can easily be extended in the attached code, otherwise you'll have to use fully qualified names in the code.

For output redirection, the code entered will have an object called 'output' available. This is a System.IO.TextWriter, meaning it will have such functions as Write and WriteLine. The above example screen shot uses this output object, so turn to that if you're confused.

Installing the web part is pretty straight forward, but will (due to the jQuery / JSON use) involve a few manual config steps. See my previous blog post for more information on that. Within the source code package, there's a folder called "wsp", within which you'll find a precompiled solution file, which can be deployed to a working SharePoint 3.0 portal.

About the implementation

The code isn't complex at all, and I'm sure many would point out that I could have done things a little bit differently here and there (such as rewriting how the web part is displayed, and taking optional configuration input to decide whether or not to link the json / jquery javascripts - which may not be necessary, if they've already been imported by another webpart, or in the master page).

Essential files all reside in the TEMPLATE\FEATURES\CodeConsoleWP folder, where:

CodeConsole.cs is the web part's source file. This emits all ui and javascript, including references to jQuery and JSON. Most essential of which is the following javascript. Yes, it's simple. Feel free to extend it. I'm sure intellisense would be a nice addition ;)


$(function(e){
var input = $("#CodeConsole_input");
var output = $("#CodeConsole_output");
$("#CodeConsole_execute").click(function(be) {
output.text("Compiling, please wait.");
var code = {'code' : input.text()};
var jsonStr = JSON.stringify(code);
$.ajax({
type: 'POST',
url: '/_vti_bin/CodeConsoleSVC.asmx/ExecuteCode',
data: jsonStr,
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(msg) { output.text(msg.d); },
error: function(xhr, msg) { output.text("Ajax Error:\n" + msg + "\n" + xhr.responseText); }
});
});
});


CodeConsoleSVC.cs is the web service source file, which basically looks like:


namespace CodeConsoleWP
{
using System;
using System.IO;
using System.Web.Script.Services;
using System.Web.Services;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class CodeConsoleSVC : WebService
{
[WebMethod]
public string ExecuteCode(string code)
{
using (TextWriter stringWriter = new StringWriter())
{
try
{
Dynamic.ExecuteCode(code, stringWriter,
new[] {"System", "System.Text", "System.IO", "Microsoft.SharePoint"});
return stringWriter.ToString();
}
catch (Exception ex)
{
return "Error:\n" + ex.Message;
}
}
}
}
}


Most interesting in the above web service, which will be deployed to the /_vti_bin/, along with other SharePoint services, is the call to Dynamic.ExecuteCode. This is a very simple class I typed up, which generates an assembly in-memory, referencing all assemblies of the hosting assembly (the web part, meaning SharePoint assemblies and such will be referenced), wrapping the code, executes it and returns an optional return value.

Dynamic.cs


internal static class Dynamic
{
public static object ExecuteCode(string code, TextWriter output, string[] namespaces)
{
using (CodeDomProvider provider = CodeDomProvider.CreateProvider("C#"))
{
var parameters = new CompilerParameters
{
GenerateExecutable = false,
IncludeDebugInformation = false,
MainClass = "temp",
GenerateInMemory = true
};

foreach (AssemblyName refasm in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
{
parameters.ReferencedAssemblies.Add(Assembly.Load(refasm).Location);
}
string usingDirectives = "";
foreach (string ns in namespaces)
{
usingDirectives += "using " + ns + ";";
}
CompilerResults results = provider.CompileAssemblyFromSource(
parameters,
@"namespace Dynamic {" +
usingDirectives +
@"public class Code { public object Run(System.IO.TextWriter output) { " +
code +
"; return null; } } }");

if (results.Errors.Count > 0)
{
var sb = new StringBuilder();
foreach (CompilerError error in results.Errors)
{
sb.AppendLine(String.Format("{0},{1}: {2}", error.Line, error.Column, error.ErrorText));
}
throw new CompileException(sb.ToString());
}
else
{
object obj = results.CompiledAssembly.CreateInstance("Dynamic.Code");
object ret = obj.GetType().InvokeMember("Run", BindingFlags.InvokeMethod, null, obj,
new object[] {output});
return ret;
}
}
}

public class CompileException : Exception
{
public CompileException(string s)
: base(s)
{
}
}
}


Compiling and installing

Upon build, the source will be compiled, and put along with the service endpoint in a SharePoint solution file (wsp). This is taken care of by the build scripts in the DeploymentFiles folder. This file will end up in the CodeConsoleWP\wsp\ folder. To install the web part, just deploy this solution to the SharePoint server. Be sure to follow the instructions in my previous post, to add json/ajax support to SharePoint, though - otherwise the service call will fail miserably.

If you've got any questions, drop me a note in the comments here, on twitter, or by email.

Download Source Code + WSP

5 comments:

Sandeep K Nahta said...

I was wondering of a situation where a client can really use it ? any idea

Einar Otto Stangvik said...

If you by 'client' mean site user; he can't. Unless he's a site administrator. And, even then, this shouldn't be installed in production environments.

It's a tool for development / test environments only; meant to provide easy access to various meta data and behind-the-scenes stuff you'd normally deploy temporary applications / web apps to access.

Bulgom said...

Thank you for sharing your great work. This webpart would be very useful for me to quickly test simple scripts. Unfortunately, in spite of the config in 12/Isapi and in the application web.config, i still can't execute any command. The output box shows "Error : Request failed" ("Echec de la demande" in french) even for a simple "output.Write("Hello");".
Have you any solution ?

Einar Otto Stangvik said...

@Bulgom,

If you downloaded an older version of the linked code; that would install into the local bin folder of the web app, an approach which will only work if you've enabled full trust in the web.config.

The latest version (uploaded yesterday) deploys to the global assembly cache, and should resolve the issue. Did that help?

I'll admit I should / could have spent more time configuring the CAS policies, and thus avoiding the GAC, but the end result will be the same. And my recommendation still stands: don't deploy this to portals with end users on it :)

Bulgom said...

Thank you for your response. The last version works great. It will simplify a lot my work (on my dev box).