This version of the Ed-Fi Dashboards is no longer supported. See the Ed-Fi Technology Version Index for a link to the latest version.

 

Plugin Architecture - Hello World (Sample Plugin)

Previous Version

This is a previous version of the Ed-Fi Dashboards. Visit the Ed-Fi Tech Docs home page for a link to the current version, or the Ed-Fi Technology Version Index for links to all versions. 

This walkthrough is a simple Hello World, useful for experiencing and understanding the plugin architecture essentials. We will create a plugin to show a view at the “Admin Area” that will display the words (wait for it) “Hello World.” The code for this walkthrough can be found in the Ed-Fi-Samples Repository ~\Sample Plugins/EdFi.Dashboards.Plugins.HelloWorld.

Hello World: Creating the Plugin

  1. Create a new empty MVC project using the Razor View engine and name it "EdFi.Dashboards.Plugins.HelloWorld.Web".




  2. Clean up the project by deleting any default folders and files (e.g., App_Start, Content, Global.asax) with the exception of Properties\AssemblyInfo.cs. You won’t be needing these because the plugin will use the code in the context of the EdFi.Dashboards.Presentation.Web project.
  3. 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 
  4. Create the following folder structure starting at the root of the Hello World project: ~\Areas\Admin\Controllers\ and ~\Areas\Admin\Views\.
  5. Create a controller under the ~\Areas\Admin\Controllers\ folder and name it HelloWorldController.

    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(); 
    	  } 
     } 
    }
  6. 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. 
  7. 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.
  8. It is also very important to wrap the content in the @section ContentPlaceHolder1 { } Razor directive because this is expected by the dashboard’s main layouts and corresponding area layouts.

    @{ 
    // 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> 
    }
  9. 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:

    PM> Install-Package Castle.Windsor -Version 2.5.1.0
  10. Create a marker interface so that you can reference the current plugin’s assembly (i.e., ~\Marker_EdFi_Dashboards_Plugins_HelloWorld_Web.cs).

    namespace EdFi.Dashboards.Plugins.HelloWorld.Web 
    {
    	/// <summary> 
    	/// Marker interface - Needed for the CastleWindsor installer 
    	/// </summary> 
    	public interface Marker_EdFi_Dashboards_Plugins_HelloWorld_Web 
    	{ 
    	} 
    }

Finally, 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 EdFi.Dashboards.Presentation.Core to inherit from. You will be using the WebDefaultConvetionInstaller for this plugin.

 

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using EdFi.Dashboards.Presentation.Core.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_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>()); 
		} 
	} 
}

A common oversight when creating a new 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.

Integrating the Plugin into the Dashboard UI

We'll finish this example by integrating the new plugin with the Dashboard UI. We'll do that by adding a new tab to the admin interface UI. In the source code, the UI tab is also called the "menu," so we'll use that terminology here. To enable the plugin to appear in the menu, you have to implement the IPluginManifest interface. The plugin manifest contains the following 3 properties:

  • The Name of the plugin.
  • The Version.
  • A collection of the Menu Items that can be added to the different areas that the dashboard has to implement the IPluginManifest interface.

Specific detail about the manifest follows.

  1. In the root of your plugin project, add a PluginManifest class that implements IPluginManifest.

    using EdFi.Dashboards.Core.Providers.Context;
    using EdFi.Dashboards.Resource.Models.Common;
    using EdFi.Dashboards.Resources.Models.Plugins;
    using EdFi.Dashboards.Resources.Navigation;
    using EdFi.Dashboards.Resources.Plugins;
    using System.Collections.Generic;
     
    namespace EdFi.Dashboards.Plugins.HelloWorld.Web 
    { 
    	/// <summary> 
    	/// Manifest class for this plugin. 
    	/// </summary> 
    	public class PluginManifest : IPluginManifest
  2. In the constructor of PluginManifest pass in an IAdminAreaLinks object and assign it to a private member variable. The IAdminAreaLinks represent existing menus that you will be extending.

    private readonly IAdminAreaLinks _adminAreaLinks;
    	/// <summary>
    	/// Constructor 
    	/// </summary> 
    	/// <param name="adminAreaLinks">Since our plugin is under the Admin area it requires the AdminAreaLinks to be able to add a new menu.</param> 
    	public PluginManifest(IAdminAreaLinks adminAreaLinks) 
    	{ 
    		this._adminAreaLinks = adminAreaLinks; 
    	}
  3. Implement the Name and Version properties of the class. It is recommended that you use reflection to return the Name and Version of the assembly represented in the Properties\AssemblyInfo.cs file.

    /// <summary> 
    /// Name of the plugin 
    /// </summary> 
    public string Name 
    { 
    	get { return this.GetType().Assembly.GetName().Name; } 
    } 
    /// <summary> 
    /// Version of the plugin 
    /// </summary> 
    public string Version 
    { 
    	get { return this.GetType().Assembly.GetName().Version.ToString(); } 
    }
  4. Implement the IEnumerable<PluginMenu> property. The implementation ensures that the request context has a valid LocalEducationAgencyId. Then, you assign your PluginMenu to the Admin area and provide the menu text and target URL to navigate to when the menu is clicked in the UI. Using the built-in helper method Resource of the IAdminAreaLinks, passing in the string “HelloWorld” will resolve to ~Admin/HelloWorld.

    /// <summary> 
    /// Menu definition for the plugin
    /// </summary>
    public IEnumerable<PluginMenu> PluginMenus 
    { 
    	get { return GetPluginMenus(); } 
    } 
    private IEnumerable<PluginMenu> GetPluginMenus() 
    { 
    	var requestContext = EdFiDashboardContext.Current; 
    	if (requestContext.LocalEducationAgencyId.HasValue) 
    	{ 
    		return new List<PluginMenu> 
    		{ 
    			new PluginMenu{ 
    			//This corresponds to the Area where the menu is to be added
    			Area = "Admin",
    			ResourceModels = new List<ResourceModel> 
    			{ 
    				new ResourceModel 
    				{ 
    					// the text to display on the menu
    					Text = "Sample Plugin", 
    					// the URL to resolve when clickin on the menu.
    					// the link.Resource() registration method is a helper method provided to easily construct the proper url (~/Admin/HelloWorld)
    					Url = _adminAreaLinks.Resource(requestContext.LocalEducationAgencyId.Value, "HelloWorld"),
    				}
    			} 
    		} 
    	}; 
    } return new List<PluginMenu>();} 
    }}
  5. 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 EdFi.Dashboards.Presentation.Core to inherit from. This example uses the WebDefaultConvetionInstaller

  6. Register the plugin manifest implementation in the Installer located in ~/Utilities/CastleWindsor/Installer.cs. The figure below shows the code for registering the plugin manifest implementation.

    using Castle.MicroKernel.Registration;
    using Castle.MicroKernel.SubSystems.Configuration;
    using Castle.Windsor;
    using EdFi.Dashboards.Presentation.Core.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_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>()); 
    		} 
    	} 
    }
  7. Build the Visual Studio Solution. 

  8. Copy the compiled assembly and .pdb file to the Plugins folder for the dashboard and perform an iisreset, if necessary.

  9. Navigate to the dashboard and login as and admin. On a development machine, you can use KurtMcCarthy with anything as the password (except an empty string or the text “wrong-password”). This is an account that maps to an IT Administrator for Grand Bend ISD in the sample dataset provided. Then, replace the route with “~Admin/HelloWorld” and your plugin view should show Hello World on your browser.