使用RazorEngine Roslyn代码时,ViewBase<T>带参构造函数实例化问题
解决RazorEngine Roslyn模式下带构造参数ViewBase的实例化问题
嘿,我之前在项目里也碰到过一模一样的问题——RazorEngine默认会尝试用无参构造函数实例化模板的基类,但咱们的ViewBase<T>偏偏需要构造参数,直接跑就报错。折腾了好一阵,总结了几个可行的方案,你可以根据自己的项目情况选:
方案一:自定义IActivator接管实例创建
这个方法最简单,直接替换RazorEngine默认的实例激活器,让它在创建模板类的时候帮我们传入基类需要的构造参数。
首先实现自己的IActivator:
public class ViewBaseActivator : IActivator { // 假设你用依赖注入容器来获取构造参数所需的服务,这里换成你实际的服务获取逻辑 private readonly IServiceProvider _serviceProvider; public ViewBaseActivator(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public object CreateInstance(Type type, params object[] args) { // 检查当前类型是否继承自ViewBase<T> var baseType = type.BaseType; if (baseType != null && baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(ViewBase<>)) { // 获取基类的构造函数参数,然后从服务容器中获取对应的实例 var ctorParameters = baseType.GetConstructors()[0].GetParameters() .Select(param => _serviceProvider.GetRequiredService(param.ParameterType)) .ToArray(); // 用带参数的构造函数创建模板实例 return Activator.CreateInstance(type, ctorParameters); } // 非ViewBase<T>子类用默认逻辑创建 return Activator.CreateInstance(type, args); } }
然后在配置RazorEngine服务的时候注册这个激活器:
var config = new TemplateServiceConfiguration { Activator = new ViewBaseActivator(yourServiceProvider), // 传入你的服务容器实例 // 其他配置项,比如启用Roslyn: Language = Language.CSharp, CompilerServiceFactory = new RoslynCompilerServiceFactory() }; using (var razorService = RazorEngineService.Create(config)) { // 正常解析模板 var result = razorService.RunCompile(templateContent, "templateKey", modelType, modelData); }
方案二:自定义模板代码生成器,修改构造函数逻辑
如果需要更精细地控制模板类的生成代码,可以重写RazorEngine的代码生成器,让它直接生成带构造参数的模板类,这样激活的时候就自然能传入参数了。
实现自定义代码生成器:
public class ViewBaseCodeGenerator : TemplateCSharpCodeGenerator { public ViewBaseCodeGenerator(IRazorEngineConfiguration config, Type templateType, string className, string rootNamespace) : base(config, templateType, className, rootNamespace) { } protected override void GenerateConstructor(CodeGeneratorContext context) { // 获取ViewBase<T>的构造函数参数(假设你的基类只有一个构造函数) var baseConstructor = typeof(ViewBase<>).MakeGenericType(ModelType).GetConstructors()[0]; var baseParams = baseConstructor.GetParameters(); // 给模板类创建带参数的构造函数 var constructor = new CodeConstructor { Attributes = MemberAttributes.Public }; // 添加构造参数,并调用基类构造函数 foreach (var param in baseParams) { constructor.Parameters.Add(new CodeParameterDeclarationExpression(param.ParameterType, param.Name)); constructor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression(param.Name)); } // 将构造函数添加到生成的模板类中 context.GeneratedType.Members.Add(constructor); } }
然后注册这个代码生成器到配置里:
var config = new TemplateServiceConfiguration { CodeGeneratorFactory = (cfg, templateType, className, rootNamespace) => new ViewBaseCodeGenerator(cfg, templateType, className, rootNamespace), Language = Language.CSharp, CompilerServiceFactory = new RoslynCompilerServiceFactory() }; using (var razorService = RazorEngineService.Create(config)) { // 此时生成的模板类会带有对应构造参数,你在创建实例时需要传入这些参数 // 或者结合上面的IActivator来自动注入 }
方案三:使用ITemplateFactory完全控制模板实例化
如果上面的方法都不满足需求,还可以实现ITemplateFactory,完全接管模板的创建过程:
public class ViewBaseTemplateFactory : ITemplateFactory { private readonly IServiceProvider _serviceProvider; public ViewBaseTemplateFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public ITemplate CreateTemplate(Type templateType, object model) { // 根据模板类型和模型,创建ViewBase<T>实例并传入构造参数 var baseType = templateType.BaseType; if (baseType != null && baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(ViewBase<>)) { var ctorParams = baseType.GetConstructors()[0].GetParameters() .Select(p => _serviceProvider.GetRequiredService(p.ParameterType)) .ToArray(); return (ITemplate)Activator.CreateInstance(templateType, ctorParams); } return (ITemplate)Activator.CreateInstance(templateType); } }
注册方式:
var config = new TemplateServiceConfiguration { TemplateFactory = new ViewBaseTemplateFactory(yourServiceProvider), Language = Language.CSharp, CompilerServiceFactory = new RoslynCompilerServiceFactory() };
这几个方案里,我个人最常用的是方案一,因为它侵入性最低,不需要修改模板生成的代码,只需要替换激活逻辑就行。如果你的ViewBase<T>构造参数比较复杂,或者需要和依赖注入深度集成,这个方案最省心。
内容的提问来源于stack exchange,提问作者Stuart.Sklinar
相关产品推荐
相关产品推荐

