Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Overview


This documentation provides sample implementations of the plugin architecture for Ed-Fi Dashboards. The intent is to provide detailed information about the creation of plugins and the complete interaction with data, services, and views while documenting the overall plugin architecture and major conventions so that an accomplished developer in the technology can successfully create plugins.

Audience

The primary audience for this document are developers implementing plugins for dashboards powered by Ed-Fi.

Prerequisites

The Ed-Fi platform architecture uses a collection of design patterns, architectural best-practices, conventions, and databases. In order to understand the code base and be able to write a plugin, it is important to understand these patterns and techniques. It is recommended that a developer intending to implement a plugin has knowledge and understanding of the dashboard’s extension mechanisms and has completed the dashboard setup guide. Setup information is available in the Ed-Fi Dashboards Getting Started guide and detail about the dashboard architecture can be found in the Ed-Fi Dashboard Developers' Guide.

Basic Guidelines for Implementing a Plugin

There are a few basic guidelines that are very important and should be followed
when followed when developing a plugin:

  1. All plugins should be mutually exclusive and self-contained. Plugins are intended to be new functionality that gets added to the dashboards.
  2. Plugins follow the basic Ed-Fi conventions for extensions. See "Extension Framework Guide /wiki/spaces/TT/pages/18645095.
  3. Plugins should use the convention-based project names starting always with EdFi.Dashboards.Plugins.[PluginName].[ProjectType]. For example: EdFi.Dashboards.Plugins.[PluginName].Resources,
    EdFi.Dashboards.Plugins.[PluginName].Data.
  4. A new plugin should always be developed under the current EdFi.Dashboards.Plugins.Web version. This means that if this project is MVC 3 with .Net Framework 4.0, you should use MVC 3 with .Net Framework 4.0 or the plugin will not work.
  5. JavaScript and CSS files need to be declared as Embedded Resources.

Technical Overview

The plugin architecture hooks into the existing dashboard dashboards by leveraging the CastleWindsor Inversion of Control (IoC) container. When the container is created, on Application_Start, it looks in the ~/Plugins
directory for appropriately named DLLs. If it finds any plugins, it then uses reflection to create instances of classes that implement the IWindsorInstaller interface. The IWindsorInstaller classes are responsible for wiring up the dependency injection for the plugin.

Once the IoC container has been initialized, the dashboard initializes the MVC framework. During this initialization, the dashboard will again look in the ~/Plugins directory for appropriately named DLLs. If it finds any plugins, it then registers all of the custom routes, controllers, and views. If the plugin has embedded JavaScript or CSS files, it creates the Cassette bundles for those embedded resources.

At this point, the plugin is available to be consumed. 

Walkthrough 1: Hello World (Sample Plugin)

Objective: The first plugin will be simple. You will create a plugin to show a view at the “Admin Area” that will display the words “Hello World”. The code for this walkthrough can be found on our Ed-Fi-Samples Repository ~\Sample Plugins/EdFi.Dashboards.Plugins.HelloWorld

Creating the Plugin

 

Create a new empty MVC project utilizing the Razor View engine and name it: EdFi.Dashboards.Plugins.HelloWorld.Web
  • Clean up the project by deleting any default folders and files (App_Start, Content, Global.asax, etc.) with the exception of Properties\AssemblyInfo.cs. You won’t be needing these because the EdFi.Dashboards.Presentation.Web project already provides what is needed.
  • Add the following references to the project: 
    1. EdFi.Dashboards.Core 
    2. EdFi.Dashboards.Presentation.Core
    3. EdFi.Dashboards.Resources 
    4. EdFi.Dashboards.Resources.Models 
    5. EdFi.Dashboards.Reosource.Models.Common 
  • Create the following folder structure starting at the root of the Hello World project: “~\Areas\Admin\Controllers\” and “~\Areas\Admin\Views\”.
  • Create a controller under the “~\Areas\Admin\Controllers\” folder and name it HelloWorldController.

    Code Block
    languagec#
    using System.Web.Mvc; namespace EdFi.Dashboards.Plugins.HelloWorld.Web.Areas.Admin.Controllers { 
    /// <summary> 
    /// Controller for the plugin's view. 
    /// The folder structure uses ASP.NET MVC conventions of Areas. An area is effectively an MVC structure inside an application. 
    /// Your plugin should either reside inside of an existing Area (see EdFi.Dashboards.Presentation.Web) 
    /// or be sure to contain code that registers the area (see EdFi.Dashboards.Presentation.Core) 
    /// </summary>
    public class HelloWorldController : Controller { 
    	/// <summary> 
    	/// Controller constructor
    	/// </summary> 
      public HelloWorldController() { } 
    	/// <summary> 
    	/// The Get method for the controller. 
    	/// </summary> 
    	/// <param name="localEducationAgencyId">Since our plugin resides within the admin area it requires a LEA id as a       parameter.</param> 
    	public ActionResult Get(int localEducationAgencyId) { 
    		return View(); 
    	} 
      } 
    }
  • If Visual Studio created a default Index action method, replace it with the standard Get action method. It is very important for security reasons to add the localEducationAgencyId to the signature of the Get method. 
  • Create the corresponding Get view “~\Areas\Admin\Views\HelloWorld\Get.cshtml”. Make sure you set the custom tool property of the view to “RazorGenerator” so it generates a precompiled version of the view that will get embedded in the .dll.
  • It is very important to wrap the content in the @section ContentPlaceHolder1 { } razor directive because the dashboard’s main layouts and corresponding area layouts expect this.

    Code Block
    languagexml
    @{ 
    // The view for our plugin. This file must have the CustomTool property set to RazorGenerator which is a Visual Studio Extension. 
    // ContentPlaceHolderHead and ContentPlaceHolder1 are view sections defined by the dashboard rendering engine.   
          ViewBag.Title = "Hello World"; 
    }
    @section ContentPlaceHolderHead { 
      <title>Hello World</title> 
    } 
    @section ContentPlaceHolder1 { 
      <h1>Hello World</h1> 
    }
  • Add a reference to Castle.Windsor. It is recommended that you use the same version of CastleWindsor that the dashboard is using. In the Package Manager Console, type the following command:
  • Create a marker interface so that you can reference the current plugin’s assembly: “~\Marker

    Plugin walk-through samples:

    Child pages (Children Display)

    Troubleshooting

    Security Exception

    Error message: You do not have access to: [Resource].

    Resolution: Make sure the methods on your controller and service have the required parameters in the signature. For example, if writing a plugin at the StudentSchool Level, make sure you have the SchoolId and the StudenUSI. You can also ensure that your service method is attributed with the CanBeAuthorizedBy and required claims:

    Code Block
    languagec#
    [CanBeAuthorizedBy(EdFiClaimTypes.ViewAllStudents, EdFiClaimTypes.ViewMyStudents)]

    The Dashboard Doesn’t Find My View

    Error message: System.InvalidOperationException: The view “Get” or its master was not found or no view engine supports the searched locations. The following locations were searched: [Locations]

    Resolution: Make sure the namespaces are correct. EdFi.Dashboards.Plugins.[PluginName].Web.Areas.[area].Views.[controllerName].Get. Also make sure that you have enabled Razor Generator on the view.

    Controller Not Found Error

    Error  message: ControllType return null while opening the sample plugin page in Admin login.

    Resolution: Check the PluginHelper.GetPluginInstallers() which is called by InversionOfControlContainerFactory.GetInstallers() to ensure that the Plugin's installers are getting passed to the call to Castle.Windsor.Install(params IWindsorInstaller[] installers) which will trigger Castle Windsor to call 
    EdFi.Dashboards.Plugins.HelloWorld.Web.Utilities.CastleWindsor.Installer.Install which will call 
    EdFi.Dashboards.Presentation.Core.Plugins.Utilities.CastleWindsor.WebDefaultConventionInstaller<Marker_EdFi_Dashboards_Plugins_HelloWorld_

    Web.cs”Create a “~\Utilities\CastleWindsor\Installer.cs” file in the specified path. This is needed to tell the IoC container to register the plugin’s web artifacts like controllers. There are 4 default installers provided in

    Web>.Install(IWindsorContainer container, IConfigurationStore store) which will call
    Castle.Windsor.WindsorContainer.Install(params IWindsorInstaller[] installers) passing in the 
    EdFi.Dashboards.Presentation

    .Core to inherit from. You will be using the WebDefaultConvetionInstaller for this plugin.

     

    Code Block
    languagec#
    using Castle.MicroKernel.Registration; using Castle.MicroKernel.SubSystems.Configuration; using Castle.Windsor; using

    .Architecture.CastleWindsor.ControllerInstaller<Marker_EdFi_Dashboards_Plugins_HelloWorld_Web> which will trigger Castle Windsor to call
    EdFi.Dashboards.Presentation.

    Core

    Architecture.

    Plugins.Utilities.

    CastleWindsor

    ; using EdFi

    .

    Dashboards.Resources.Plugins;   namespace EdFi.Dashboards.Plugins.HelloWorld.Web.Utilities.CastleWindsor  {  /// <summary>  /// Installer class for this plugin. /// This is needed to resolve the CastleWindsor IoC Container and hook in this plugin with the main site.  /// </summary>  public class Installer : WebDefaultConventionInstaller<Marker

    .ControllerInstaller<Marker_EdFi_Dashboards_Plugins_HelloWorld_

    Web>  {  public override void Install(IWindsorContainer container, IConfigurationStore store)  {  base.Install(container, store); //This registers our PluginManifest class as able to fullfill the IPluginManifest interface with the CastleWindsor IoC  //The IPluginManifest is what will add a new menu(tab) to an existing section.  container.Register(Component .For<IPluginManifest>() .ImplementedBy<PluginManifest>());  }  }  } InfoNOTE: A common mistake when creating a view is to forget to set the custom tool on the view to be RazorGenerator. This tool will generate a .cs version of the view that will be compiled and embedded into the .dll.

    Web>.Install which uses reflection to find and register the Plugin Controller Types with Castle Windsor

    Include Page
    Developers' Guide
    Developers' Guide