Using asp.net MVC engine to develop plug-in system

Time:2021-10-4

1、 Foreword

The plug-in system in my mind should be like NOP (more awesome, such as orchard and OSGi. Net). Each plug-in module is not just a pile of DLLs that implement a business interface, and then called by reflection or IOC technology, but a complete MVC applet. I can control the installation and disabling of plug-ins in the background. The directory structure is as follows:

After generation, it is placed in the plugins folder under the root directory of the site, and each plug-in has a subfolder

Plugins/Sms.AliYun/

Plugins/Sms.ManDao/

I am a lazy person with obsessive-compulsive disorder. I don’t want to copy the generated DLL file to the bin directory.

2、 Problems to be solved

1. By default, the asp.net engine will only load the DLLs in the “bin” folder, and the plug-in files we want are scattered in various subdirectories under the plugins directory.

2. How to handle when a model is used in a view? By default, razorviewengine uses buildmanager to compile views into dynamic assemblies, and then uses activator.createinstance to instantiate newly compiled objects. When using plug-in DLL, the current AppDomain does not know how to resolve this view that references the model because it does not exist in “bin” or GAC. To make matters worse, you won’t receive any error messages telling you why it doesn’t work or where the problem is. Instead, he will tell you that the file cannot be found in the view directory.

3. A plug-in is running under the site. Directly overwriting the DLL of the plug-in will tell you that the DLL is currently in use and cannot be overwritten.

4. How to load the view file if it is not placed in the view directory of the site.

3、 Net 4.0 makes all this possible

A new feature of net4.0 is the ability to execute code before application initialization (preapplicationstartmethodattribute). This feature enables applications to_ Some work can be done before star. For example, we can tell our MVC plug-in where to put the DLL of the system and do preloading before the application starts. About several new features of. Net, I have written a blog to introduce them. Click me., Some bloggers have written about preapplicationstartmethodattribute. Please click me. ABP’s startup module should also be implemented using the feature principle of preapplicationstartmethodattribute. I haven’t seen whether this is the case.

4、 Solution

1. Modify the web.config directory of the primary site so that the runtime can load files from other directories in addition to the bin directory


 <runtime>
 <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <probing privatePath="Plugins/temp/" />
 </assemblyBinding>
 </runtime>

2. Develop a simple plug-in management class, which is used in application_ Before start, copy the DLLs in each subdirectory of plugins to the folder specified in step 1. In order to make the demo as simple as possible, there is no detection of duplicate DLLs (for example, the EF assembly is referenced in the plug-in, and the master site also refers to it. If the EF DLL already exists in the site bin directory, it is not necessary to copy the DLL in the plug-in to the dynamic assembly directory set above)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Compilation;
using System.Web.Hosting;
[assembly: PreApplicationStartMethod(typeof(Plugins.Core.PreApplicationInit), "Initialize")]
namespace Plugins.Core
{
 public class PreApplicationInit
 {

  static PreApplicationInit()
  {
   PluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins"));
   ShadowCopyFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/plugins/temp"));
  }

  /// <summary>
  ///Directory information of plug-in
  /// </summary>
  private static readonly DirectoryInfo PluginFolder;

  /// <summary>
  ///The DLL directory specified when the program should run
  /// </summary>
  private static readonly DirectoryInfo ShadowCopyFolder;

  public static void Initialize()
  {
   Directory.CreateDirectory(ShadowCopyFolder.FullName);
   //Empty the files in the running directory of the plug-in DLL
   foreach (var f in ShadowCopyFolder.GetFiles("*.dll", SearchOption.AllDirectories))
   {
    f.Delete();
   }
   foreach (var plug in PluginFolder.GetFiles("*.dll", SearchOption.AllDirectories).Where(i=>i.Directory.Parent.Name== "plugins"))
   {
    File.Copy(plug.FullName, Path.Combine(ShadowCopyFolder.FullName, plug.Name), true);
   }
   foreach (var a in
    ShadowCopyFolder
    .GetFiles("*.dll", SearchOption.AllDirectories)
    .Select(x => AssemblyName.GetAssemblyName(x.FullName))
    .Select(x => Assembly.Load(x.FullName)))
   {
    BuildManager.AddReferencedAssembly(a);
   }

  }
 }
}

3. How can the view engine find our view? The answer is to rewrite the method of razorviewengine. I adopt the method that the Convention is greater than the configuration (assuming that our plug-in project namespace is plugins.apps.sms, the default controller namespace is plugins.apps.sms.controllers, and the folder after plug-in generation must be / plugins / plugins. Apps. SMS /), By analyzing the current controller, you can know the view directory location of the current plug-in

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.WebPages.Razor;

namespace Plugins.Web
{
 public class CustomerViewEngine : RazorViewEngine
 {

  /// <summary>
  ///Define the address where the view page is located.
  /// </summary>
  private string[] _viewLocationFormats = new[]
  {
   "~/Views/Parts/{0}.cshtml",
   "~/Plugins/{pluginFolder}/Views/{1}/{0}.cshtml",
   "~/Plugins/{pluginFolder}/Views/Shared/{0}.cshtml",
   "~/Views/{1}/{0}.cshtml",
   "~/Views/Shared/{0}.cshtml",
  };
  public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
  {
   string ns = controllerContext.Controller.GetType().Namespace;
   string controller = controllerContext.Controller.GetType().Name.Replace("Controller", "");
   //Description is the controller in the plug-in, and the view directory needs to be handled separately
   if (ns.ToLower().Contains("plugins"))
   {
    var pluginsFolder = ns.ToLower().Replace(".controllers", "");
    ViewLocationFormats = ReplacePlaceholder(pluginsFolder);
   }
   return base.FindView(controllerContext, viewName, masterName, useCache);
  }
  /// <summary>
  ///Replace pluginfolder placeholder
  /// </summary>
  /// <param name="folderName"></param>
  private string[] ReplacePlaceholder(string folderName)
  {
   string[] tempArray = new string[_viewLocationFormats.Length];
   if (_viewLocationFormats != null)
   {
    for (int i = 0; i < _viewLocationFormats.Length; i++)
    {
     tempArray[i] = _viewLocationFormats[i].Replace("{pluginFolder}", folderName);
    }
   }
   return tempArray;
  }
 }
}

Then specify the razor engine in the global.asax of the primary site as the one we rewritten

4. Start making a plug-in directory, which is not much different from the MVC project we usually establish, but some settings need to be made during release.

. the generation path should be written according to the agreement in Article 3, otherwise the view file will not be found

The web.config and. Cshtml files in the. View directory should be copied to the generation directory (right click in the file)

3. Set the generation attribute in the reference project. If it already exists under the main program, set “copy to output directory” to none. Otherwise, an error will occur when copying to the dynamic bin directory. You can modify the type in step 2 and add the file comparison function. Only if there is nothing in the bin directory can it be copied to the dynamic bin directory.

4. The generated directory structure is as follows:

5. After running, everything is normal, the controller in the plug-in works normally, and there is no problem that the model is referenced in the view

At this point, even if the core part of a plug-in system is completed, you can continue to expand and add the discovery, installation and uninstall functions of plug-ins. These are Pediatrics compared with the core functions. In the future, I will publish an article on plug-in system based on ABP framework. If you are interested, prepare the small bench and buy melon seeds and peanuts:)

5、 Source code

Download plugins link: https://pan.baidu.com/s/1nvmbL81 Password: 85v1

The above is the whole content of this article. I hope it will be helpful to your study, and I hope you can support developpaer.