在asp.net mvc的开发场景中,我们可能会遇到需要根据配置或者运行时参数动态生成Controller的情况,比如多租户场景下不同租户需要不同的业务接口,手动编写所有Controller显然不现实,这时候就可以通过动态编译的方式实现Controller的自动生成。

动态编译生成Controller的核心思路
动态编译生成Controller的整体流程可以分为四个步骤:首先构建符合Controller规范的C#代码文本,然后使用CSharpCodeProvider进行编译得到程序集,接着将程序集加载到当前应用域中,最后让asp.net mvc的Controller工厂能够识别到新生成的Controller。下面我们逐步展开说明。
第一步:构建Controller代码文本
动态生成的Controller需要符合asp.net mvc的Controller规范,首先需要继承Controller类,并且方法需要标记对应的路由特性或者遵循默认的路由规则。我们可以先定义代码模板,再替换其中的动态内容。
以下是一个简单的Controller代码模板示例:
using System;
using System.Web.Mvc;
namespace DynamicControllers
{
public class {ControllerName}Controller : Controller
{
// 动态生成的示例方法
public ActionResult {ActionName}()
{
ViewBag.Message = "这是动态生成的{ControllerName}Controller的{ActionName}方法";
return View();
}
}
}
第二步:使用CSharpCodeProvider编译代码
CSharpCodeProvider是.NET Framework提供的用于动态编译C#代码的类,我们需要配置编译参数,指定引用的程序集,然后执行编译操作。
编译的核心代码示例如下:
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Web;
using Microsoft.CSharp;
public class ControllerCompiler
{
/// <summary>
/// 编译Controller代码并返回程序集路径
/// </summary>
/// <param name="controllerCode">Controller的C#代码文本</param>
/// <param name="outputAssemblyPath">生成的程序集保存路径</param>
/// <returns>编译是否成功</returns>
public static bool CompileController(string controllerCode, string outputAssemblyPath)
{
// 创建C#代码编译提供者
using (CSharpCodeProvider provider = new CSharpCodeProvider())
{
// 配置编译参数
CompilerParameters parameters = new CompilerParameters();
// 添加需要引用的程序集
parameters.ReferencedAssemblies.Add(typeof(System.Web.Mvc.Controller).Assembly.Location);
parameters.ReferencedAssemblies.Add(typeof(System.Object).Assembly.Location);
parameters.ReferencedAssemblies.Add(typeof(System.Web.HttpContext).Assembly.Location);
// 设置生成程序集的路径
parameters.OutputAssembly = outputAssemblyPath;
// 生成可执行文件为false,生成类库
parameters.GenerateExecutable = false;
// 生成调试信息为false
parameters.IncludeDebugInformation = false;
// 执行编译
CompilerResults results = provider.CompileAssemblyFromSource(parameters, controllerCode);
// 检查编译是否有错误
if (results.Errors.HasErrors)
{
foreach (CompilerError error in results.Errors)
{
// 这里可以将错误记录到日志中
System.Diagnostics.Debug.WriteLine($"编译错误:{error.ErrorText},行号:{error.Line}");
}
return false;
}
return true;
}
}
}
第三步:加载编译后的程序集
编译完成后会生成一个dll程序集,我们需要将其加载到当前的应用域中,这样asp.net mvc才能识别到其中的Controller类型。
加载程序集的代码示例:
using System;
using System.IO;
using System.Reflection;
public class AssemblyLoader
{
/// <summary>
/// 加载指定路径的程序集
/// </summary>
/// <param name="assemblyPath">程序集路径</param>
/// <returns>加载的程序集</returns>
public static Assembly LoadAssembly(string assemblyPath)
{
if (!File.Exists(assemblyPath))
{
throw new FileNotFoundException($"程序集文件不存在:{assemblyPath}");
}
// 加载程序集到当前应用域
return Assembly.LoadFrom(assemblyPath);
}
}
第四步:注册动态生成的Controller到Mvc框架
asp.net mvc默认通过Controller工厂查找Controller类型,动态加载的程序集默认可能不会被扫描到,我们需要自定义Controller工厂或者在应用启动时注册程序集。
以下是自定义Controller工厂的示例,让框架能够识别动态生成的Controller:
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Reflection;
public class DynamicControllerFactory : DefaultControllerFactory
{
private static readonly List<Assembly> _dynamicAssemblies = new List<Assembly>();
/// <summary>
/// 注册动态生成的程序集
/// </summary>
/// <param name="assembly">动态程序集</param>
public static void RegisterDynamicAssembly(Assembly assembly)
{
if (!_dynamicAssemblies.Contains(assembly))
{
_dynamicAssemblies.Add(assembly);
}
}
protected override Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName)
{
// 先尝试从默认的程序集中查找Controller
Type controllerType = base.GetControllerType(requestContext, controllerName);
if (controllerType != null)
{
return controllerType;
}
// 如果没找到,从动态注册的程序集中查找
foreach (var assembly in _dynamicAssemblies)
{
// 拼接Controller的完整名称,假设命名空间是DynamicControllers
string fullControllerName = $"DynamicControllers.{controllerName}Controller";
Type type = assembly.GetType(fullControllerName);
if (type != null && typeof(Controller).IsAssignableFrom(type))
{
return type;
}
}
return null;
}
}
完整调用示例
我们可以将上面的步骤组合起来,实现一个完整的动态生成Controller的调用示例:
using System;
using System.IO;
using System.Reflection;
using System.Web.Mvc;
public class DynamicControllerGenerator
{
public static void GenerateAndRegisterController()
{
// 1. 构建Controller代码
string controllerName = "Test";
string actionName = "Index";
string controllerCode = $@"
using System;
using System.Web.Mvc;
namespace DynamicControllers
{{
public class {controllerName}Controller : Controller
{{
public ActionResult {actionName}()
{{
ViewBag.Message = ""这是动态生成的{controllerName}Controller的{actionName}方法"";
return View();
}}
}}
}}
";
// 2. 编译代码
string assemblyDir = AppDomain.CurrentDomain.BaseDirectory + "DynamicAssemblies";
if (!Directory.Exists(assemblyDir))
{
Directory.CreateDirectory(assemblyDir);
}
string assemblyPath = Path.Combine(assemblyDir, $"{controllerName}Controller.dll");
bool compileSuccess = ControllerCompiler.CompileController(controllerCode, assemblyPath);
if (!compileSuccess)
{
throw new Exception("Controller编译失败");
}
// 3. 加载程序集
Assembly dynamicAssembly = AssemblyLoader.LoadAssembly(assemblyPath);
// 4. 注册到Controller工厂
DynamicControllerFactory.RegisterDynamicAssembly(dynamicAssembly);
// 注意:需要在Global.asax中注册自定义的Controller工厂
// ControllerBuilder.Current.SetControllerFactory(new DynamicControllerFactory());
}
}
注意事项
- 动态编译生成的程序集默认会保存在磁盘上,需要注意文件权限问题,确保应用有读写对应目录的权限。
- 编译时需要引用所有Controller依赖的程序集,否则会出现编译错误。
- 动态生成的Controller如果需要访问视图,需要提前准备好对应的视图文件,或者直接在方法中返回JsonResult等不需要视图的结果。
- 如果动态生成的Controller需要更新,需要先卸载对应的程序集,但是.NET中程序集不能单独卸载,只能卸载整个应用域,所以如果需要频繁更新,建议单独创建应用域来加载动态程序集。
asp.net_mvc动态编译Controller生成CSharpCodeProvider修改时间:2026-07-07 06:27:36