7

I have a problem that has tormented me for several days.

As I do in a plugin in ASP .NET Core MVC to bring me views with the plugin

I have this situation

solution:EVS

  • Controllers
    • ...
  • Views
    • ...
  • Plugins
    • this folder contain a plugins dll
  • other folders ...
  • IPlugin.cs
  • Program.cs
  • Startup.cs

file IPlugin.cs

...
using McMaster.NETCore.Plugins;

namespace EVS
{
    public interface IPlugin
    {
        string Name { get; }
        void Do();
        void BootReg();
        void BootExecute();
        void PluginConfigure(IApplicationBuilder app, IHostingEnvironment env);
        void PluginConfigureServices(IServiceCollection services);
    }

    public class PluginsManager
    {
        public List<PluginLoader> PluginLoaders;

        public Dictionary<string, IPlugin> Plugins;
        public Dictionary<string, Assembly> Views;
        public List<String> dllFileNames;

        public PluginsManager()
        {
            PluginLoaders = new List<PluginLoader>();
            Plugins = new Dictionary<string, IPlugin>();
            dllFileNames = new List<string>();
            string PloginDirectory= Path.Combine(AppContext.BaseDirectory, "Plugins");
            if (Directory.Exists(PloginDirectory))
                dllFileNames.AddRange(Directory.GetFiles(PloginDirectory+"\\", "*.dll"));

            foreach (string dllFile in dllFileNames)
            {
                if (!dllFile.Contains(".Views.dll"))
                {
                    var loader = PluginLoader.CreateFromAssemblyFile(dllFile,sharedTypes: new[] { typeof(IPlugin) });
                    PluginLoaders.Add(loader);               
                }
                else
                {
                    // 

                    //
                }   
            }
            foreach (var loader in PluginLoaders)
            {
                foreach (IPlugin plugin in loader.LoadDefaultAssembly().GetTypes().Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract).Select((x)=> (IPlugin)Activator.CreateInstance(x)))
                    if (!Plugins.ContainsKey(plugin.Name))
                        Plugins.Add(plugin.Name, plugin);
            }
        }
    }
}

file Program.cs

namespace EVS
{
    public class Program
    {
        public static void Main(string[] args)
        {
            foreach (IPlugin plugin in Global.Static.PluginsManager.Plugins.Select((x) => x.Value))
                plugin.BootReg();           

            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

file Startup.cs

namespace EVS
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });


            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            foreach (IPlugin plugin in Global.Static.PluginsManager.Plugins.Select((x) => x.Value))
            {
                plugin.PluginConfigureServices(services);
            }                
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();
            app.UseCookiePolicy();

            foreach (IPlugin plugin in Global.Static.PluginsManager.Plugins.Select((x) => x.Value))
            {
                plugin.PluginConfigure(app,env);
            }

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

now let's take an example of plugins

solution:EVS.TestPlugin

  • Controllers
    • TestPluginController.cs
  • Views
    • test.cshtml
  • TestPlugin.cs

file: TestPlugin.cs

namespace EVS.TestPlugin
{
    internal class TestPlugin : IPlugin
    {
        public string Name
        {
            get
            {
                return "TestPlugin";
            }
        }

        public void BootReg()
        {
            ...
        }

        public void BootExecute()
        {
            ...
        }

        public void Do()
        {

        }


        public void PluginConfigure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc(routes =>
            {
                routes.MapRoute("TestPlugin", "TestPlugin/", new { controller = "TestPlugin", action = "index" });
                routes.MapRoute("TestPlugint1", "TestPlugin/t1", new { controller = "TestPlugin", action = "t1" });
            });
        }

        public void PluginConfigureServices(IServiceCollection services)
        {

            services.AddMvc().AddApplicationPart(typeof(TestPluginController).GetTypeInfo().Assembly).AddControllersAsServices();
        }

    }
}

file: Controllers/TestPluginController.cs

namespace EVS.TestPlugin.Controllers
{
    public class TestPluginController : Controller
    {
        public IActionResult Index()
        {
            return Content("<h1>TestPluginController</h1>");
        }

        public IActionResult t1()
        {
            return View("test");
        }
    }
}

file : Views/test.cshtml

@{
    Layout = null;
}

`.cshtml` test view

Plugins are not included in the solution, but they are loaded dynamically depending whether they are in a folder solemnly for them.

The problem: I can accordingly add controllers as (EVS.TestPlugin.PluginConfigureServices (...)) and MapRoute (EVS.TestPlugin.PluginConfigure (...)) However, how could I also add the context of the views? Therefore it would be on: ~/PluginName/Views/ViewName.cshatml

EDIT

I found this (link), but it's not really what I need. It partially solve the problem, because it only work if the view are compiled.

EDIT 2

Momentarily I solved it adding the views reference on to the plugins csproj, as follows:

<ItemGroup>
    <EmbeddedResource Include="Views\**\*.cshtml"/>
    <Content Remove="Views\**\*.cshtml" />
</ItemGroup>
<PropertyGroup>
    <RazorCompileOnBuild>false</RazorCompileOnBuild>
</PropertyGroup>

On the source project:

// p is a Instance of plugin
foreach(IPlugin p in Plugins.Select((x) => x.Value))
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.FileProviders.Add(
                    new EmbeddedFileProvider(p.GetType().GetTypeInfo().Assembly));
    });

This does not solve my problem because the .cshtml files come in clear in the plugin file, what I need is to be able to add the views from the assembly pluginname.views.dll or in some other way, the important that the views are compiled

EDIT 3

Good news, using ILSpy I parsed a PluginName.Views.dll file and I discovered that it implements RazorPage<object>

With the following code I verified that I could initialize all the caches that cono RazorPage<object> which allows me to have the instances of the objects that create the view

foreach (string dllview in Views.Select((x) => x.Key))
{
    PluginLoader loader = PluginLoader.CreateFromAssemblyFile(dllview, sharedTypes: new[] { typeof(RazorPage<object>) });
    foreach (RazorPage<object> RazorP in loader.LoadDefaultAssembly().GetTypes().Where(t => typeof(RazorPage<object>).IsAssignableFrom(t)).Select((x) => (RazorPage<object>)Activator.CreateInstance(x)))
    {
        // i need a code for add a RazorPagein the in RazorViewEngine, or anywhere else that allows register in the context

        System.Diagnostics.Debug.WriteLine(RazorP.GetType().ToString());
        /*  - output 
            AspNetCore.Views_test
            AspNetCore.Views_TestFolder_htmlpage

            as you can see is the folder structure

            - folder tree:
            Project plugin folder
                Views
                    test.cshtml
                    TestFolder
                        htmlpage.cshtml

        */
    }
}

Solved

thanks to all I solved, there seems to be a problem with

var assembly = ...;
services.AddMvc()
    .AddApplicationPart(assembly)

someone forgot to even put the razzor part factory

CompiledRazorAssemblyApplicationPartFactory

I solved that

services.AddMvc().ConfigureApplicationPartManager(apm =>
    {
    foreach (var b in new CompiledRazorAssemblyApplicationPartFactory().GetApplicationParts(AssemblyLoadContext.Default.LoadFromAssemblyPath(".../ViewAssembypath/file.Views.dll")))
        apm.ApplicationParts.Add(b);
    });

in addition I published a library that does everything efficiently

NETCore.Mvc.PluginsManager

4
  • Gianfrancesco Aurecchia great work! I was building something similar, tried your library and replaced my code with yours. Commented Dec 15, 2018 at 21:30
  • Did you find a clever way to debug plugins? Commented Dec 15, 2018 at 21:53
  • what do you mean by debugging it? check when starting the blugin if it is really comapatibile and do not break anything? Commented Dec 17, 2018 at 13:31
  • Please move your solution to an answer of its own, thank you. Commented Dec 31, 2018 at 18:55

2 Answers 2

2

thanks to all I solved, there seems to be a problem with

var assembly = ...;
services.AddMvc()
    .AddApplicationPart(assembly)

someone forgot to even put the razzor part factory

CompiledRazorAssemblyApplicationPartFactory

I solved that

services.AddMvc().ConfigureApplicationPartManager(apm =>
    {
    foreach (var b in new CompiledRazorAssemblyApplicationPartFactory().GetApplicationParts(AssemblyLoadContext.Default.LoadFromAssemblyPath(".../ViewAssembypath/file.Views.dll")))
        apm.ApplicationParts.Add(b);
    });

in addition I published a library that does everything efficiently

NETCore.Mvc.PluginsManager

Sign up to request clarification or add additional context in comments.

Comments

0

You can add custom search paths by creating a file provider and adding it in your StartUp.ConfigureServices() method, like this :

IFileProvider myFileProvider = new PhysicalFileProvider("C:/Some/File/Location");
services.AddMvc().AddRazorOptions(opt =>
                        {
                            opt.FileProviders.Add(myFileProvider);
                        });

4 Comments

Yeah it does, I use it myself in a very similar system. :) - It'll look in that provider with the usual search pattern, So you should have a "Views" folder inside that file provider ...
I'm sorry I expressed myself badly, the views must be in the *.views.dll assembly not positioned in a folder.
@GianfrancescoAurecchia Ah, I see - you have them as embedded resources in the DLL - I'd suggest having a look at the IFileProvider interface, It's quite thin, and would be easy to implement a version that loads from embedded resources instead. You might be able to adjust this implementation to your needs - github.com/aspnet/FileSystem/blob/…
thanks i took a look but it doesn't do for this case since the views are compiled

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.