.Net Core 動態注冊 Controller_01


聲明:本文借鑒蔣金楠先生的博客: https://www.cnblogs.com/lonelyxmas/p/12656993.html

如何動態 的注冊Controller,大概思路是 使用  Roslyn解析並編譯代碼生成dll,利用IActionDescriptorProvider 接口,將生成好的ControllerActionDescriptor添加到ActionDescriptorCollection 集合中

動態生成 assembly 接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace DynamicRegister1
{
    public interface ICompiler
    {
        /// <summary>
        /// 動態生成 Assembly,使用 Roslyn 來生成
        /// </summary>
        /// <param name="text">需要動態編譯的代碼</param>
        /// <param name="assemblies"> 生成assembly索要依賴的程序集</param>
        /// <returns></returns>
        Assembly Compile(string text, string assemblyName = null, params Assembly[] assemblies);
        /// <summary>
        /// 動態生成 Assembly,並保存到文件夾,使用 Roslyn 來生成
        /// </summary>
        /// <param name="text">需要動態編譯的代碼</param>
        /// <param name="assemblies">生成assembly索要依賴的程序集</param>
        /// <returns></returns>
        string DynamicGenerateDllToFile(string text, string assemblyName = null, params Assembly[] assemblies);
    }
}

 2  對上面接口簡單的實現

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace DynamicRegister1
{
    public class CompilerService : ICompiler
    {
        public Assembly Compile(string text, string assemblyName = null, params Assembly[] assemblies)
        {
            //引入相關的動態編譯包 
            //Microsoft.CodeAnalysis.CSharp;   
            // 加載相關的引用文件
            var references = assemblies.Select(i => MetadataReference.CreateFromFile(i.Location));
            // 類型為 csharp 動態鏈接庫
            var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
            // 將文本生成解析樹
            var syntaxTrees = new SyntaxTree[] { CSharpSyntaxTree.ParseText(text) };
            // 程序集名稱,如果未空,生成新的程序集名稱
            if (assemblyName == null)
                assemblyName = "_" + Guid.NewGuid().ToString("D");
            var compilation = CSharpCompilation.Create(assemblyName, syntaxTrees, references, options);
            // 通過流對象生成相關的程序集
            using MemoryStream stream = new MemoryStream();
            //編譯
            var result = compilation.Emit(stream);
            if (result.Success)
            {
                //  編譯成功 返回 assembly
                stream.Seek(0, SeekOrigin.Begin);
                return Assembly.Load(stream.ToArray());
            }
            else
            {
                StringBuilder stringBuilder = new StringBuilder();
                // 獲取異常
                foreach (Diagnostic codeIssue in result.Diagnostics)
                {
                    stringBuilder.Append($"ID: {codeIssue.Id}," +
                                 $" Message: {codeIssue.GetMessage()}," +
                                 $"Location:{codeIssue.Location.GetLineSpan()}," +
                                 $"Severity:{codeIssue.Severity}---");
                
                    // AppRuntimes.Instance.Loger.Error("自動編譯代碼出現異常," + msg);                   
                }
                throw new Exception(stringBuilder.ToString());
            }

        }

        public string DynamicGenerateDllToFile(string text, string assemblyName = null,params Assembly[] assemblies)
        {

            var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
            var syntaxTrees = new SyntaxTree[] { CSharpSyntaxTree.ParseText(text) };
            if (assemblyName == null)
                assemblyName = "_" + Guid.NewGuid().ToString("D");
            var references = assemblies.Select(i => MetadataReference.CreateFromFile(i.Location));
            var compilation = CSharpCompilation.Create(assemblyName, syntaxTrees, references, options);
            var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Proxy");
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            var apiRemoteProxyDllFile = Path.Combine(path,
                assemblyName + DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".dll");
            var result = compilation.Emit(apiRemoteProxyDllFile);
            if (result.Success)
            {
                return apiRemoteProxyDllFile;
            }
            else
            {
                StringBuilder stringBuilder = new StringBuilder();
                // 獲取異常
                foreach (Diagnostic codeIssue in result.Diagnostics)
                {
                    stringBuilder.Append($"ID: {codeIssue.Id}," +
                                 $" Message: {codeIssue.GetMessage()}," +
                                 $"Location:{codeIssue.Location.GetLineSpan()}," +
                                 $"Severity:{codeIssue.Severity}---");

                    // AppRuntimes.Instance.Loger.Error("自動編譯代碼出現異常," + msg);                   
                }
                throw new Exception(stringBuilder.ToString());
            }
        }
    }
}

//   從生成號的 assembly 中加載 controller

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace DynamicRegister1
{
    /*由於針對MVC應用的請求總是指向某一個Action,
     * 所以MVC框架提供的路由整合機制體現在為每一個Action創建一個或者多個終結點
     * (同一個Action方法可以注冊多個路由)。
     * 針對Action方法的路由終結點是根據描述Action方法的ActionDescriptor對象構建而成的。
     * 至於ActionDescriptor對象,則是通過注冊的一組IActionDescriptorProvider對象來提供的,
     * 那么我們的問題就迎刃而解:通過注冊自定義的IActionDescriptorProvider從動態定義的Controller
     * 類型中解析出合法的Action方法,並創建對應的ActionDescriptor對象*/
    public class OwnActionProvider : IActionDescriptorProvider
    {

        private readonly IServiceProvider _serviceProvider;
        private readonly ICompiler _compiler;
        public OwnActionProvider(IServiceProvider serviceProvider, ICompiler compiler)
        {
            _serviceProvider = serviceProvider;
            _compiler = compiler;
            _actions = new List<ControllerActionDescriptor>();
        }
        public int Order => -1000;

        /// <summary>
        /// 當IChangeToken 發生變化,會執行下面兩個監聽方法
        /// </summary>
        /// <param name="context"></param>
        public void OnProvidersExecuted(ActionDescriptorProviderContext context)
        {
            // 添加action ,添加完成之后,會將   context.Results中的action更新到 ActionDescriptorCollection,見下面源代碼
            foreach (var action in _actions)
            {
                context.Results.Add(action);
            }
        }


        public void OnProvidersExecuting(ActionDescriptorProviderContext context)
        {

        }
        private readonly List<ControllerActionDescriptor> _actions;

        /// <summary>
        /// 創建 ApplicationModel
        /// 那么ActionDescriptor如何創建呢?我們能想到簡單的方式是調用如下這個Build方法。
        /// 針對該方法的調用存在兩個問題:
        /// 第一ControllerActionDescriptorBuilder是一個內部(internal)類型,
        /// 我們指定以反射的方式調用這個方法,
        /// 第二,這個方法接受一個類型為ApplicationModel的參數。
        /// </summary>
        /// <param name="controllerTypes"></param>
        /// <returns></returns>
        ApplicationModel CreateApplicationModel(IEnumerable<Type> controllerTypes)
        {
            //  var s = Microsoft.AspNetCore.Mvc.ApplicationModels.AttributeRouteModel;
            // 因為ApplicationModelFactory 是內建類型,只能通過反射去獲取對象 創建 ApplicationModel
            var assembly = Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Mvc.Core"));
            var typeName = "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelFactory";
            var factoryType = assembly.GetTypes().Single(it => it.FullName == typeName);
            //IServiceProvider通過GetService(Type serviceType)拿到實例
            var factory = _serviceProvider.GetService(factoryType);
            var method = factoryType.GetMethod("CreateApplicationModel");
            var typeInfos = controllerTypes.Select(it => it.GetTypeInfo());
            //通過反射創建 ApplicationModel
            return (ApplicationModel)method.Invoke(factory, new object[] { typeInfos });
        }

        /// <summary>
        ///  生成 ControllerActionDescriptor
        /// </summary>
        /// <param name="sourceCode"></param>
        /// <returns></returns>
        IEnumerable<ControllerActionDescriptor> CreateActionDescrptors(string sourceCode)
        {
            Func<Type, Boolean> IsController = new Func<Type, bool>(typeInfo =>
            {
                if (!typeInfo.IsClass) return false;
                if (typeInfo.IsAbstract) return false;
                if (!typeInfo.IsPublic) return false;
                if (typeInfo.ContainsGenericParameters) return false;
                if (typeInfo.IsDefined(typeof(NonControllerAttribute))) return false;
                if (!typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && !typeInfo.IsDefined(typeof(ControllerAttribute))) return false;
                return true;
            });
            //  生成assemble
            var assembly = _compiler.Compile(sourceCode, null,
                Assembly.Load(new AssemblyName("System.Runtime")),
                typeof(object).Assembly,
                typeof(ControllerBase).Assembly,
                typeof(Controller).Assembly);
            //獲取程序集中 類型為控制器的類型
            var controllerTypes = assembly.GetTypes().Where(it => IsController(it));
            // 創建  ApplicationModel,
            var applicationModel = CreateApplicationModel(controllerTypes);

            assembly = Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Mvc.Core"));
            var typeName = "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerActionDescriptorBuilder";
            // 反射獲取 ControllerActionDescriptorBuilder 對象
            var controllerBuilderType = assembly.GetTypes().Single(it => it.FullName == typeName);
            // 反射獲取Build 方法
            var buildMethod = controllerBuilderType.GetMethod("Build", BindingFlags.Static | BindingFlags.Public);
            // 創建 ControllerActionDescriptor 對象
            return (IEnumerable<ControllerActionDescriptor>)buildMethod.Invoke(null, new object[] { applicationModel });
        }

        public void AddControllers(string sourceCode) => _actions.AddRange(CreateActionDescrptors(sourceCode));
        /// <summary>
        /// 刪除注冊的controller
        /// </summary>
        /// <param name="controllerName"></param>
        /// <param name="actionName"></param>
        public void RemoveControllers(string controllerName, string actionName)
        {
            for (int i = 0; i < _actions.Count(); i++)
            {
                if (_actions[i].ActionName == actionName && _actions[i].ControllerName == controllerName)
                    _actions.RemoveAt(i);
            }
        }
        public IEnumerable<ControllerActionDescriptor> ControllerActionDescriptor
        {
            get => _actions;
        }
    }
    /// <summary>
    /// DynamicActionProvider 解決了將提供的源代碼向對應ActionDescriptor列表的轉換,
    /// 但是MVC默認情況下對提供的ActionDescriptor對象進行了緩存。如果框架能夠使用新的ActionDescriptor對象,
    /// 需要告訴它當前應用提供的ActionDescriptor列表發生了改變,
    /// 而這可以利用自定義的IActionDescriptorChangeProvider來實現。
    /// 為此我們定義了如下這個DynamicChangeTokenProvider類型,
    /// 該類型實現了IActionDescriptorChangeProvider接口,
    /// 並利用GetChangeToken方法返回IChangeToken對象通知MVC框架當前的ActionDescriptor已經發生改變。
    /// 從實現實現代碼可以看出,當我們調用NotifyChanges方法的時候,狀態改變通知會被發出去。
    /// </summary>

    public class OwnChangeTokenProvider : IActionDescriptorChangeProvider
    {
        private CancellationTokenSource _source;
        private CancellationChangeToken _token;
        public OwnChangeTokenProvider()
        {
            _source = new CancellationTokenSource();
            _token = new CancellationChangeToken(_source.Token);
        }
        /// <summary>
        /// 當token 發生變化時,會調用  IActionDescriptorProvider 
        /// 中的OnProvidersExecuted,OnProvidersExecuting兩個監聽方法
        /// </summary>
        /// <returns></returns>
        public IChangeToken GetChangeToken() => _token;
        public void NotifyChanges()
        {
            //  給_source 賦值,並獲取舊值
            var old = Interlocked.Exchange(ref _source, new CancellationTokenSource());
            _token = new CancellationChangeToken(_source.Token);
            // 取消舊的注冊
            old.Cancel();
        }
    }
    #region  以下是更新ActionDescriptorCollection的源代碼 

    public class ActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider
    {
        private readonly IActionDescriptorProvider[] _actionDescriptorProviders;
        private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders;
        private ActionDescriptorCollection _collection;
        private int _version = -1;

        /// <summary>
        /// Initializes a new instance of the <see cref="ActionDescriptorCollectionProvider" /> class.
        /// </summary>
        /// <param name="actionDescriptorProviders">The sequence of <see cref="IActionDescriptorProvider"/>.</param>
        /// <param name="actionDescriptorChangeProviders">The sequence of <see cref="IActionDescriptorChangeProvider"/>.</param>
        public ActionDescriptorCollectionProvider(
            IEnumerable<IActionDescriptorProvider> actionDescriptorProviders,
            IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders)
        {
            _actionDescriptorProviders = actionDescriptorProviders
                .OrderBy(p => p.Order)
                .ToArray();

            _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray();

            ChangeToken.OnChange(
                GetCompositeChangeToken,
                UpdateCollection);
        }
        private IChangeToken GetCompositeChangeToken()
        {
            if (_actionDescriptorChangeProviders.Length == 1)
            {
                return _actionDescriptorChangeProviders[0].GetChangeToken();
            }

            var changeTokens = new IChangeToken[_actionDescriptorChangeProviders.Length];
            for (var i = 0; i < _actionDescriptorChangeProviders.Length; i++)
            {
                changeTokens[i] = _actionDescriptorChangeProviders[i].GetChangeToken();
            }

            return new CompositeChangeToken(changeTokens);
        }

        /// <summary>
        /// Returns a cached collection of <see cref="ActionDescriptor" />.
        /// </summary>
        public ActionDescriptorCollection ActionDescriptors
        {
            get
            {
                if (_collection == null)
                {
                    UpdateCollection();
                }

                return _collection;
            }
        }

        private void UpdateCollection()
        {
            var context = new ActionDescriptorProviderContext();

            for (var i = 0; i < _actionDescriptorProviders.Length; i++)
            {
                _actionDescriptorProviders[i].OnProvidersExecuting(context);
            }

            for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--)
            {
                _actionDescriptorProviders[i].OnProvidersExecuted(context);
            }

            //   返回 ActionDescriptorCollection
            _collection = new ActionDescriptorCollection(
                new ReadOnlyCollection<ActionDescriptor>(context.Results),
                Interlocked.Increment(ref _version)); // 原子操作
        }
    }

    #endregion

}

   效果:

 

  

訪問注冊的方法

 

二   實現方式2 

 參考:https://stackoverflow.com/questions/46156649/asp-net-core-register-controller-at-runtime

ApplicationPart 是用來管理運行時中加載的 dll 的,

只要能把帶有Controller的dll 加載到ApplicationParts,刷新一下相關的 runtime 就能實現了吧。有了思路,就先看看 .NET Core MVC 源碼吧,從里面找找看有沒有相關的 Controller 緩存的集合,看能不能動態加載進去。 

 

  public IActionResult Index(string source,
        [FromServices] ApplicationPartManager manager,
        [FromServices] ICompiler compiler,
        [FromServices] OwnChangeTokenProvider tokenProvider)
        {
            try
            {
                manager.ApplicationParts.Add(new AssemblyPart(compiler.Compile(source,null, Assembly.Load(new AssemblyName("System.Runtime")),
                    typeof(object).Assembly,
                    typeof(ControllerBase).Assembly,
                    typeof(Controller).Assembly)));
                tokenProvider.NotifyChanges();
                return Content("OK");
            }
            catch (Exception ex)
            {
                return Content(ex.Message);
            }
        }

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM