zoukankan      html  css  js  c++  java
  • .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);
                }
            }
    

      

  • 相关阅读:
    org.springframework.beans.BeanUtils属性赋值 Date类型处理转换为LocalDateTime, Date不能直接赋值给LocalDateTime
    python rabbitmq官方文档demo
    rabbitmq安装
    python 文件查找及截取字符串 (替换,分割) demo
    python pika rabbitmq demo
    python xlrd excel读取操作
    python pymysql 数据库查询操作
    GO语言学入门学习,学习资料推荐
    linux安装uwsgi错误:gcc returned 1 exit status error: lto-wrapper failed collect2:
    anconda的使用以及在conda环境中使用pip和conda使用安装依赖的区别和注意事项
  • 原文地址:https://www.cnblogs.com/hnzheng/p/12713023.html
Copyright © 2011-2022 走看看