In my blog, I've discussed several issues that come up again and again when working with ESAPI scripts (e.g., class library settings, patient search). I've shared with you possible solutions to these issues, and I've often made the code available on my GitHub page.
That's great, but it means you have to copy the code every time the issue comes up in a different script. And if I improve on the solution, you'll have to copy and paste it again.
Therefore, to make it easier for you (and me) to reuse common solutions, I've created a toolkit—ESAPI Essentials—that includes a lot of the functionality I've discussed in my blog. ESAPI Essentials is available via NuGet, and the code is open-source (https://github.com/redcurry/EsapiEssentials).
ESAPI Essentials is a work in progress, so expect to see new functionality in the future. As of March 2019, it includes the following features (available only for ESAPI 13.6):
Plugin runner for launching plugin scripts from Visual Studio
Base class to support asynchronous calls to ESAPI in standalone apps
"Smart" search for patients in standalone apps
Launcher to run standalone apps from Eclipse
Class library settings for plugin scripts
Samples showing how to use some of the features above
To use ESAPI Essentials in your project, search for "esapiessentials" in the NuGet Package Manager in Visual Studio. Most of the functionality described below is in the EsapiEssentials package. The plugin runner itself is in the EsapiEssentials.PluginRunner package.
This new plugin runner is largely based on EsapiPlugInRunner, but there are a few key improvements. For instance, I've made it much easier to setup your script to work with the runner. Instead of having to create a new Run method with the script context parameters, you now derive from ScriptBase or ScriptBaseWithWindow and override the Run method:
public class Script : ScriptBase
public override void Run(PluginScriptContext context)
// Your code here
The PluginScriptContext class contains all of the original properties from ScriptContext (EsapiPlugInRunner allowed only a subset of these). The sample in the Samples/Plugin folder shows how to put everything together to make it work.
I've discussed how to run ESAPI from a separate thread in the blog post Create ESAPI Scripts That Don’t Freeze the UI. But that discussion applied to plugin scripts, not standalone apps.
For standalone apps, I've created a new class (available in ESAPI Essentials) called EsapiServiceBase. The idea is that, in your standalone app, you would create an EsapiService class and derive it from EsapiServiceBase.
EsapiServiceBase automatically creates a new thread for you, which you can use to make ESAPI calls. You do that by calling the RunAsync method (provided by EsapiServiceBase) and passing it the function you want to run in the ESAPI thread.
For example, let's say that your EsapiService provides a GetStructureSetIdsAsync method that returns the IDs of the structure sets of the currently opened patient. Using the RunAsync method, it could be implemented as follows:
public Task<string> GetStructureSetIdsAsync() =>
RunAsync(patient => patient.StructureSets.Select(ss => ss.Id).ToArray());
Note that the RunAsync method provides the currently opened patient to the function you want to call (in this case, it's a lambda expression).
It's also important to note that anything inside the RunAsync method can access ESAPI directly, but nothing outside of it can. This means that you can't return ESAPI objects (e.g., PlanSetup, StructureSet) because they would be accessed outside of the ESAPI thread.
Instead, you need to return basic types (like the string array above) or custom types that wrap specific data you want from ESAPI. For example, you could create a Plan class that only has the prescribed dose (if that's what your application needs).
This may seem like a limitation, but I actually recommend that your application doesn't depend on ESAPI directly (even if you weren't using EsapiServiceBase). You don't want your application to depend on ESAPI because it makes it less robust to future changes in ESAPI. It also makes your application more difficult to test (see my blog post Use the Facade Pattern When Working With ESAPI).
I discussed "smart" search in the blog post Create a “Smart Search” Control for Standalone Apps. The implementation in ESAPI Essentials makes some improvements, and there's an example of how to integrate it into your own EsapiService.
Standalone App Launcher
In a blog post I talked about An Easy Way to Launch Stand-Alone Apps from Eclipse, but one drawback is that the context is lost (you have no information about the opened patient, current plan, etc.)
In ESAPI Essentials, the standalone runner captures the context and passes it to your standalone application via command-line arguments. You can then parse these arguments and construct a new script context using the StandaloneScriptContext class:
// The ESAPI Application object must be created before the StandaloneScriptContext
_app = Application.CreateApplication(null, null);
// The StandaloneScriptContext requires the command-line arguments
// and the Application object (returns null if there are no command-line arguments)
_scriptContext = StandaloneScriptContext.From(Environment.GetCommandLineArgs(), _app);
You're then free to use the script context object as if you were working with a plugin script. Of course, because you're actually working with a standalone app, you can close the patient and open a new one.
Class Library Settings
This is an implementation of AssemblySettings, which I discussed in the blog post How to Use Settings for a Class Library. The only difference is that this version is generalized to work on any class library.
In the Future
There are several features I'd like to add in the near future:
Of course, since ESAPI Essentials is an open-source project, pull requests are welcome with additional features. If you have features to recommend that you'd like implemented, feel free to add a suggestion as a comment below.