📜 ⬆️ ⬇️

Plugin system on ASP.NET. Idea development


Good day, dear readers. In this article I will tell you about the development of a plug-in system for projects written in ASP.NET MVC. In the previous article I described the basics of creating a system that allows you to divide its parts into separate plugins.

Writing a continuation pushed me to numerous questions that I get from readers. Continued under the cut.

Introduction


I received a large number of questions on the described system from users of the habr. It is very pleasant that many people found my article interesting and useful. Most often I am asked about the further development of the system. Due to some changes in my life, I had to move a bit away from this topic. But given the interest in the topic, today I will try to share with you my experience of implementation and the problems that I encountered while working with the system.

Like the previous article, this one is written for newbies who may find it difficult to understand all the subtleties. I will describe everything as simple as possible (in some places, experienced programmers may begin to shower me with tomatoes for simplifications), so I apologize to those who are particularly sensitive to the reader's account for forgiveness.
')

Problems and limitations in the first version


The system I described earlier has certain advantages when working on large projects. But, as with everything, it also has its drawbacks. During the implementation of the system in work projects, I encountered several unpleasant moments:

  1. Compiled plug-in files are blocked after downloading, which does not allow updates from the "on the fly".
  2. It is necessary to constantly keep track of the plug-in versions in the folder so that the old versions are not loaded.
  3. The need to connect third-party libraries to the root project to ensure the work of plug-ins.

This is not a complete list of problems that I encountered when working. But these problems are basic and because of them the development can turn into a very unpleasant act (I shed so much pain because of them and shed tears of blood).

The list of problems is determined, we will solve them.

Blocking plugins


In the first version of the system there is a very big problem - blocking plug-in files. And this problem arises very acutely if the plugin is connected to a “combat” project, the stopping of which is extremely undesirable. To update plugins, it was necessary to somehow stop the site and download updated versions of the system components.

This problem results from one line of code:

var currentAssambly = AppDomain.CurrentDomain.Load(assembly); 

Let me explain what happens here, if there is no experience with application domains. First, let's look at MSDN for the definition of the application domain class.
Represents an application domain, which is the isolated environment in which applications run.

For simpler understanding, imagine that a domain is a separate process in the system in which the application runs. Thus, from the line of code above, we can understand that the plug-in file is “loaded” into the process of the main site. From this it follows that the file will be blocked as long as the application domain exists (or the site is running).

To solve the blocking problem, there are several generally accepted solutions:

  1. Upload the plugin file to your own domain.
  2. Call the overloaded .Load (byte []) method for the domain. This method allows you to connect the assembly of a copy of its contents from the byte array.
  3. Enable shadow copy for the application domain and load plugins with the static Assembly.Load () method

The first method is bad in that it generates separate processes for plugins and isolates (in fact, if the domain is correctly configured, this is solved, but it requires writing a large amount of additional code) the downloadable plugin from the main site and its libraries. So we will not use it.

The second method does not suit us, since in this case, as well as in the first, the plugin is isolated from the libraries. And MSDN itself says that the .Load method (byte []) is auxiliary and is used only if there is no possibility to call the static Assembly.Load () method. In our case, nothing prevents us from using the static method.

But the third way we will adopt. It requires a minimum amount of code and is simple to be embedded into a plugin manager already written.

This code will be added.
 string cachePath = @"C:\Temp"; if (!System.IO.Directory.Exists(cachePath)) System.IO.Directory.CreateDirectory(cachePath, new System.Security.AccessControl.DirectorySecurity()); AppDomain.CurrentDomain.SetCachePath(cachePath); //Set shadowcopy to prevent locking plugins AppDomain.CurrentDomain.SetShadowCopyPath(AppDomain.CurrentDomain.BaseDirectory); AppDomain.CurrentDomain.SetShadowCopyFiles(); 


I will explain a little. When using Shadow Copy, the application domain before loading the plug-in (or another connected library) makes a copy of it into the specified folder and works with this copy, making it possible to manipulate the source file.

In the above code, we specified the path to the folder where copies of the loaded plugins will be stored. Do not forget that the folder must be available for writing. Otherwise, we will get an exception at the start.

After the application is loaded into the domain of the plugin, it can no longer be removed or replaced. Thus, even though the plug-in files are available for rewriting on the fly, there is no need to restart.

Plugin version tracking


This problem may arise only from a limited number of developers (I do not exclude that this circle is limited to me alone), but earlier I could mistakenly upload the updated version of the plugin to the wrong folder or simply by changing the file name to upload two versions at once.

The curvature of the hands and no fraud. On the other hand, being able to store several versions of the plugin on the server at once may be useful. For example, if in the new version there is a critical failure, and the programmer is sick. The situation is, of course, fictional and fantastic, but you never know. Therefore, we will add version tracking. This is done quite simply. Passing through all the plug-in files by reflection we get the version and add it to the list of plug-ins. If there is already such a plugin in the list, perform a version comparison. If the found file has a higher version, replace it.

Code example
 ... IList<System.Reflection.AssemblyName> assemblies = new List<AssemblyName>(); foreach (var dll in libs) { ... //     if (!assemblies.Any(o => o.Name == dll.Name)) assemblies.Add(dll); //Replace assembly with higher version //  ,       else if (assemblies.Any(o => o.Name == dll.Name && o.Version < dll.Version)) { assemblies.Remove(assemblies.FirstOrDefault(o => o.Name == dll.Name)); assemblies.Add(dll); } ... 


Connection of third-party libraries


This problem caused me the most pain and suffering. How many times have I come across a situation where I don’t see a new plugin in the system. And only debag showed an error loading the connected library. Yes, and litter the base project is not comme il faut. Therefore, it was decided to create a kind of library repository in which the connected libraries would be added. But there is one nuance that is worth paying close attention to here. If you update the library version in one of the plug-ins, and the other version has the old version, we will get an error. But this is a matter of attentiveness. After a couple of updates, this feature is postponed in the head and then the problems disappear.

In this case, to solve the problem, we only need to add one to the line of the config (main project) the following:

  <probing privatePath="bin;plugins;plugins/SharedLibs" /> 

I created the SharedLibs directory. But the name, as you know, can be anything. Thus, we tell the main project that it should search for libraries in the places indicated in the parameter. The rest of the dependency resolution magic will do for us .NET

Afterword


So, we have done the second iteration on the development of a plug-in system for ASP.NET MVC sites. The main problems that caused difficulties in using the system have been resolved. At this stage of the development of the idea, the system is already used in production projects and shows its viability.

The article turned out small because it is a kind of addition to the previous one.
Now I work on automatic replacement of the updated plug-ins "on the fly". But the current implementation is very unstable and it will be indecent to post it.

I also hope that at least a little satisfied the interest of all who wrote to me. I am pleased to answer your questions.

Repository on github.com

PS Friends, your constructive criticism is very important. Express your opinion in the comments.
PPS Time is extremely short and not enough for the implementation of all ideas and ideas. If there are enthusiasts and interested people, join the project on github. We will develop the system together.

Source: https://habr.com/ru/post/357684/


All Articles