Development
Kind time of the 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 plug-ins.
Numerous questions that I receive from the readers pushed me to write the continuation. Continuation under the cut.
Introduction
I received a lot of questions on the described system from users of the hub. It is very pleasant that many of my articles were interesting and useful. Most often I am asked about the further development of the system. In connection with some changes in life, I had to move away from this topic a little. But taking into account the interest in the topic, today I will try to share with you my experience of implementation and the problems that I encountered when working with the system.
Like the previous article, this is written for beginners, who may find it difficult to understand all the subtleties. I will describe everything in the simplest possible language (in some places, experienced programmers can start throwing me tomatoes for simplicity), so I apologize to readers who are particularly sensitive to the presentation of forgiveness.
Problems and limitations in the first version
The system I described earlier has certain advantages when working on large projects. But, as in everything, it also has its drawbacks. During the introduction of the system into working projects, I encountered several unpleasant moments:
- The files of the compiled plugins are blocked after loading, which does not allow updating from the “on the fly.”
- You need to constantly monitor the version of the plugins in the folder so that older versions are not loaded.
- 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 while working. But these problems are basic and because of them development can turn into a very unpleasant action (how much pain I experienced because of them and bloody tears shed not count).
The list of problems is defined, we will solve them.
Blocking of plug-ins
In the first version of the system there is a very big problem – blocking plug-in files. And this problem arises very sharply if the plug-in is connected to a “combat” project, the stop of which is extremely undesirable. Updating the plug-ins required somehow to stop the site and download the updated versions of the system components.
This problem follows from a single line of code:
var currentAssambly = AppDomain.CurrentDomain.Load (assembly);
I’ll explain what happens when there is no experience with application domains. First, look in MSDN for the definition of the application domain class.
Represents an application domain that is an isolated environment in which applications run.
For a simpler understanding, imagine that a domain is a separate process in the system in which the application is running. Thus, from the line of code above, we can understand that the “plugin” of the plugin file is in the process of the main site. It follows that the file will be blocked as long as the application domain exists (or the site is started).
There are several generally accepted solutions for solving the blocking problem:
- Upload the plug-in file into your own domain.
- Call the overloaded .Load (byte []) method for the domain. This method allows you to connect the assembly to a copy of its contents from the byte array.
- Enable shadow copy for the application domain and load the plug-ins with the static method Assembly.Load ()
The first way is bad that it generates separate processes for plug-ins and isolates (in fact, with proper domain configuration this is solved, but requires writing a lot of additional code) the downloadable plug-in from the main site and its libraries. So we will not use it.
The second way will not work for us, because in this case, as well as in the first one, the plugin is isolated from the libraries. And MSDN itself says that the .Load (byte []) method is auxiliary and is used only if there is no way to call the static Assembly.Load () method. In our case, nothing prevents using the static method.
But we will take the third method for service. It requires a minimum amount of code and is easy to embed in the already written plug-in manager.
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 ();
A little explain. When using Shadow Copy, the application domain before downloading the plug-in (or another connected library) makes its copy to the specified folder and works with this copy, giving the ability to manipulate the source file.
In the above code, we specified the path to the folder where the copies of the downloaded plug-ins will be stored. Do not forget that the folder must be writable. Otherwise, at startup, we will get an exception.
After loading the application plugin into the domain, it is impossible to remove or replace it. Thus, although the plugin files are available for dubbing “on the fly”, the need for restart does not disappear.
Tracking versions of plugins
This problem may arise only for a limited number of developers (I do not exclude that this circle is limited to me by one), but earlier I could by mistake unload the updated version of the plug-in in the wrong folder, or simply change the file name to put two versions at once.
Curvature of hands and no fraud. On the other hand, being able to store multiple versions of the plug-in on the server can be useful. For example, if a critical failure occurs in the new version, and the programmer is sick. The situation, of course, is fictional and fantastic, but it is not enough. Therefore, we will add version tracking. This is done quite simply. Passing through all plug-in files by reflection, we get the version and add it to the list of plug-ins. If the list already has such a plug-in, we perform a version comparison. If the file found has the older version, replace it.
...
IList assemblies = new List ();
Foreach (var dll in libs)
{
...
// Check the presence of the plugin in the list
If (! Assemblies.Any (o => o.Name == dll.Name))
Assemblies.Add (dll);
// Replace assembly with higher version
// If there is a plugin, compare the versions and add a newer one
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);
}
...
Connecting third-party libraries
This problem caused me the most pain and suffering. How many times have I come across a situation where I do not see a new plugin in the system. And only showed the error loading the connected library. Yes, and clog the basic project is not comme il faut. Therefore, it was decided to create a kind of library repository, in which connected libraries would be built. But there is one nuance, on which here it is worth paying close attention. If you update the version of the library in one of the plug-ins, and in others the old version remains – we get an error. But this is a matter of attentiveness. After a couple of updates in the head this feature is postponed and further problems disappear.
In this case, to solve the problem, we need only add one to the line of the config (main project):
I created the SharedLibs directory. But the name, as you understand, can be anything. So we tell the main project that it should look for libraries in the places specified in the parameter. All the rest of the magic of dependency resolution for us will do. NET
Afterword
So, we made the second iteration of developing a plug-in system for sites on ASP.NET MVC. The main problems that caused difficulties in using the system were solved. At this stage of the idea’s development the system is already used in production projects and shows its viability.
The article turned out to be small because it is a kind of complement to the previous one.
Now I’m working on automatic replacement of updated plug-ins “on the fly.” But the current implementation is very unstable and it will be indecent to spread it.
I also hope that at least a little satisfied the interest of all who wrote to me. With pleasure I will answer your questions.
Repository on github.com
P.S. Friends, your constructive criticism is very important. Express your opinion in the comments.
P.P.S. Time is very short and is not enough to realize all ideas and ideas. If there are enthusiasts and interested people, join the project on github. We will develop the system together.