Script Packs

glennblock edited this page Dec 4, 2014 · 21 revisions

Script Packs allow you to bootstrap the environment for new scripts, further reducing the amount of code necessary to take advantage of your favorite C# frameworks. For more background on why Script Packs were created / needed, see [here] (http://codebetter.com/glennblock/2013/05/14/scripting-ease-with-script-packs/).

Features

  1. Are deployed as NuGet packages which are automatically discovered.
  2. Provide a more script friendly API for working with a framework.
  3. Can inject assembly references into the consuming script.
  4. Can inject using statements for common namespaces.
  5. Can can bring other NuGet package references.
  6. Can remove the need for boilerplate code by setting up the environment for you, like initializing an a web host.
  7. Can depend on services provided by other NuGet packages.
  8. Can depend on each other.

Using a Script Pack

To use a Script Pack, install it from NuGet and use the scriptcs Require<T> mechanism to import it into your script. E.g.

  1. Create a folder: mkdir scriptpacks101

  2. Navigate to the folder: cd scriptpacks101

  3. Install the ScriptCs.Adder script pack: scriptcs -install ScriptCs.Adder

  4. Create a script and save it as adder.csx:

     var adder = Require<Adder>();
     var answer = adder.Add(1, 2);
     Console.WriteLine(answer);
    
  5. Execute your script: scriptcs adder.csx

Authoring a Script Pack

  1. Create a new C# DLL project.
  2. Add a reference to the ScriptCs.Contracts NuGet package.
  3. Create the Script Pack Context by implementing IScriptPackContext. This class should have methods which will surface to the consumer of the Script Pack in their script. Context classes should always be designed in a lazy fashion. This means that they should not do any work on construction. They should wait until the consumer accesses one of the members. This type will be what the consumer provides as T to Require<T>() to load the pack, so choose the name carefully. For example in the [Web API Script Pack] (https://github.com/scriptcs-contrib/scriptcs-webapi), the Context is called just WebApi to allow the consumer to do Require<WebApi>(). A common convention the community has adopted is to add the suffix Pack, i.e. ScriptCs.Nancy uses NancyPack for the context.
  4. Create the Script Pack itself. The easiest way to do this is by creating a class called ScriptPack that implements ScriptPack<T> where T is the context class. In the pack you can override the Initialize method and add logic to add imports or assembly references. You do this by accessing the members of the provided IScriptPackSession object. Don't worry about naming collisions, as the class will never be used directly by a consumer.
  5. Publish the package. The common convention here is the package name should be ScriptCs.xxx i.e. ScriptCs.Nancy.
  6. Another common convention is for the repo to use scriptcs-xxx as the name.

Example

Below you can see how to implement the simple Adder example shown earlier. In this case there are no using statements or references so the pack itself is empty.

public class Adder : IScriptPackContext {
  public int Add(int first, int second) {
    return first + second
  }
}

public class ScriptPack : ScriptPack<Adder> {
}

Adding imports and references.

The Web API Script Pack brings in additional assembly references and imports. Here is a snippet showing how it does this:

void IScriptPack.Initialize(IScriptPackSession session)
{
    session.AddReference("System.Net.Http");
    var namespaces = new[]
        {
            "System.Web.Http",
            "System.Web.Http.Routing",
            "System.Net.Http",
            "System.Net.Http.Headers",
            "Owin"
        }.ToList();

    namespaces.ForEach(session.ImportNamespace);
}

As you can see, it is adding an assembly reference to System.Net.Http as well as importing common namespaces that will be needed by consumers. It is important that all namespaces for any types are exposed via the members of the Context class are added. For example, the Web API pack has a Configure method which accepts an HttpConfiguration param, thus the System.Web.Http namespace must be added, or the consumer calling it will fail unless they have added their own using statement.

Exporting and Importing services

Script Packs are discovered and instantiated using [MEF] (http://msdn.microsoft.com/en-us/library/dd460648%28v=vs.110%29.aspx). The IScriptPack interface which all packs implement is annotated with an [InheritedExport] attribute making all packs MEF parts which export IScriptPack

This means that Script Packs can also import other contracts either from within the Script Pack or from other Script Packs. scriptcs also provides some common services that can also be injected. Beyond that, by injecting services you can make your packs easier to test, by allowing those services to be mockable.

To allow services to be injected, you use property or constructor injection. Constructor Injection is the most common / preferred way so that will be covered here.

  1. Add a constructor to your IScriptPack
  2. Add an [ImportingConstructor] attribute to that constructor.
  3. Add params to the constructor for each MEF contract that you want to import. Annotate those params with an [ImportMany] attribute if the param is a collection of imports otherwise no attribute is needed.
  4. Make sure to save references to the imported contracts within class members so that they can be accessed later.
  5. Make sure any parts that are being imported either are annotated with an [Export] attribute, custom Export attribute, or that they implement an interface that is annotated with [InheritedExport]

Example

The Web API Script Pack injects services in its constructor:

[ImportingConstructor]
public ScriptPack(ILog logger, IControllerTypeManager typeManager)
{
  _logger = logger;
  _typeManager = typeManager;
}

ILog is a logging service provided by the scriptcs host itself to allow writing to the scriptcs log infrastructure. IControllerTypeManager however is a part that is exported within the script pack.

[Export(typeof(IControllerTypeManager))]
public class ControllerTypeManager : IControllerTypeManager
{
  ...
}

Though in this example, the IControllerTypeManager export is within the Script Pack itself, it does not have to be. You can have parts provided in extension NuGet packages which are installed along with the pack they are extending. scriptcs will automatically discover them and make them available as exports.

For the list of script packs, see the Script Packs master list.

For more information about authoring script packs, also see Authoring-script-packs.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.