我可以在app_code之外有一个全球剃刀@helper吗?
-
11-12-2019 - |
题
问题简单,如标题说:有没有办法在'app_code'之外拥有剃刀助手?
示例(htmlex.chtml文件):
@helper Script(string fileName, UrlHelper url)
{
<script src="@url.Content("~/Scripts/" + fileName)" type="text/javascript"></script>
}
.
我问这个是因为我真的没有别的什么来放入app_code;我想构建我的项目有点不同。
谢谢。
更新:我不想要任何其他类型的扩展。我对只有斯科特的纯剃刀助手感兴趣,因为斯科特在这里说: http://weblogs.asp.net/scottgu/12/2011/05/12/Asp-Net-mvc-3-and-the-助手语法内-Razor.aspx
解决方案
问题简单,标题说明:有没有办法 在'app_code'之外的剃刀助手?
否,没有。
其他提示
永远不会说永远...
方法一个:(用于Web应用程序项目)
只添加一个预构建事件以将文件复制到App_code文件夹中。
(但由于文件必须包含在项目中,您可以将具有相同名称的空文件添加到App_code DIR中,然后将构建事件更新为。)
(注意,即使您在App_code文件夹中将文件放在App_code文件夹中,您将无法获得IntelliSense直到第一次建设,因此它无论如何都没有区别。)
方法二:(用于类库,启动项目是Web应用程序)
在一个类库中app_code文件夹不是什么特殊的,所以为了能够拥有辅助页面全局,我们必须覆盖剃刀代码,因为它很难编码,只需在代码中制作全球助手app_code文件夹。
更重要的是剃刀代码被设计成使其为全局帮助者创建一个名称空间,这是一个基于完整路径的命名空间,您可能不感兴趣的东西。
毕竟我们留下了一个问题,没有可用的IntelliSense,因此要避免所有这些问题,我已经写了以下代码,假设
- 您的.cshtml(或vbhtml)文件被复制到最终项目输出目录
- 您将使用与全局帮助程序文件名相同的名称添加.cs(或.vb)文件,并将其设置为“编译”构建操作,(此文件将在启动时自动化以提供IntelliSense)
- 您必须在AssemblyInfo.cs文件中注册预先保释原装司法函数
- 您必须在preApplicationstartupcode.start()方法中替换,以便在依赖顺序中为bin文件夹中的全局帮助程序页面提供相对路径(即,如果其中一个全局帮助文件使用其他辅助程序文件然后它应该在它之后列出。
- 在customrazorcodehost类中,您必须选择适合安装MVC版本的PostProcessGeneratedCode()的正确方法
这是代码(但是一个将必须添加适当的“使用”语句):
.[EditorBrowsable(EditorBrowsableState.Never)] public static class PreApplicationStartCode { private static bool _startWasCalled; public static void Start() { // Even though ASP.NET will only call each PreAppStart once, we sometimes internally call one PreAppStart from // another PreAppStart to ensure that things get initialized in the right order. ASP.NET does not guarantee the // order so we have to guard against multiple calls. // All Start calls are made on same thread, so no lock needed here. if (_startWasCalled) { return; } _startWasCalled = true; //Add here the the global helpers based on dependency //also note that each global helper should have a .cs file in the project with the same name CustomRazorHelperBuildProvider bp = new CustomRazorHelperBuildProvider(); bp.VirtualPath = "~/Bin/path/to/helpers/file/Helpers.cshtml"; bp.GenerateCodeAndCompile(); bp = new CustomRazorHelperBuildProvider(); bp.VirtualPath = "~/Bin/path/to/helpers/file/DepndentHelpers.cshtml"; bp.GenerateCodeAndCompile(); } } public class CustomRazorHelperBuildProvider :RazorBuildProvider { static List<string> GeneratedAssemblyReferences = new List<string>(); public new string VirtualPath { get; set; } protected override System.Web.WebPages.Razor.WebPageRazorHost CreateHost() { return new CustomCodeRazorHost(VirtualPath); } private WebPageRazorHost _host; internal WebPageRazorHost Host { get { if (_host == null) { _host = CreateHost(); } return _host; } } private CodeCompileUnit _generatedCode = null; internal CodeCompileUnit GeneratedCode { get { if (_generatedCode == null) { EnsureGeneratedCode(); } return _generatedCode; } } private CodeDomProvider _provider = null; internal CodeDomProvider Provider { get { if(_provider == null) { _provider = GetProvider(); } return _provider; } } private void EnsureGeneratedCode() { RazorTemplateEngine engine = new RazorTemplateEngine(Host); GeneratorResults results = null; using (TextReader reader = OpenReader(VirtualPath)) { results = engine.GenerateCode(reader, className: null, rootNamespace: null, sourceFileName: Host.PhysicalPath); } if (!results.Success) { RazorError error = results.ParserErrors.Last(); throw new HttpParseException(error.Message + Environment.NewLine, null, VirtualPath, null, error.Location.LineIndex + 1); } _generatedCode = results.GeneratedCode; } private CodeDomProvider GetProvider() { CompilerType compilerType = GetDefaultCompilerTypeForLanguage(Host.CodeLanguage.LanguageName); CodeDomProvider provider = CreateCodeDomProviderWithPropertyOptions(compilerType.CodeDomProviderType); return provider; } /// <summary> /// Generates the c# (or vb.net) code, for the intellisense to work /// </summary> public void GenerateCode() { //Remember that if there is a razor error, then the next time the project will not compile at all, because the generated .cs file will also have the error! //The solution is to add a pre-build event to truncate the file, but not remove it!, also note that the pre-build event will not work in time if the .cs file is open in the VS editor! string filePath = VirtualPath.Replace("/", "\\").Replace("~\\Bin", "").Replace("\\Debug", "").Replace("\\Release", ""); filePath = filePath.Remove(filePath.Length - 4); //filePath = filePath.Insert(filePath.LastIndexOf("\\"), "\\HelperAutoGeneratedCode"); Assembly curAssem = Assembly.GetExecutingAssembly(); filePath = HttpRuntime.AppDomainAppPath + "\\..\\" + curAssem.GetName().Name + filePath; using (FileStream fs = new FileStream(filePath, FileMode.Truncate)) { using (StreamWriter sw = new StreamWriter(fs)) { Provider.GenerateCodeFromCompileUnit(GeneratedCode, sw, null); sw.Flush(); sw.Close(); } fs.Close(); } //We need to replace the type of the helpers from "HelperResult" to object, otherwise the intellisense will complain that "it can't convert from HelperResult to object" string text = File.ReadAllText(filePath); text = text.Replace("public static System.Web.WebPages.HelperResult ", "public static object "); File.WriteAllText(filePath, text); } public void GenerateCodeAndCompile() { GenerateCode(); Compile(); } /// <summary> /// Compiles the helper pages for use at runtime /// </summary> /// <returns>Compiler Result</returns> public CompilerResults Compile() { Assembly assem = Assembly.GetExecutingAssembly(); AssemblyName[] references = assem.GetReferencedAssemblies(); List<string> referenceNames = references.Select(r => Assembly.ReflectionOnlyLoad(r.FullName).Location).ToList(); referenceNames.Add(assem.Location); //Add here references that are not included in the project, but are needed for the generated assembly, you can see this through the results.Errors referenceNames.Add((typeof(WebMatrix.Data.ConnectionEventArgs).Assembly.Location)); referenceNames.Add((typeof(WebMatrix.WebData.SimpleRoleProvider).Assembly.Location)); if (GeneratedAssemblyReferences != null && GeneratedAssemblyReferences.Count > 0) { referenceNames.AddRange(GeneratedAssemblyReferences); } CompilerResults results = Provider.CompileAssemblyFromDom(new CompilerParameters(referenceNames.ToArray()), new CodeCompileUnit[] { GeneratedCode }); if (results.Errors.HasErrors) { IEnumerator en = results.Errors.GetEnumerator(); en.MoveNext(); CompilerError error = en.Current as CompilerError; throw new HttpParseException(error.ErrorText + Environment.NewLine, null, VirtualPath, null, error.Line); } Assembly assemblyRef = GetGeneratedType(results).Assembly; GeneratedAssemblyReferences.Add(assemblyRef.Location); //So that any subsequent helper page that is dependent on it will have it as a reference //We need to make it available for Razor, so it will work with reguler razor pages at runtime RazorBuildProvider.CodeGenerationStarted += new EventHandler((sender, args) => (sender as RazorBuildProvider).AssemblyBuilder.AddCodeCompileUnit(this, GeneratedCode)); return results; } private static CodeDomProvider CreateCodeDomProviderWithPropertyOptions(Type codeDomProviderType) { // The following resembles the code in System.CodeDom.CompilerInfo.CreateProvider // Make a copy to avoid modifying the original. var originalProviderOptions = GetProviderOptions(codeDomProviderType); IDictionary<string, string> providerOptions = null; if (originalProviderOptions != null) { providerOptions = new Dictionary<string, string>(originalProviderOptions); } AssemblyName[] references = Assembly.GetExecutingAssembly().GetReferencedAssemblies(); foreach (AssemblyName reference in references) { if (reference.Name == "mscorlib") { providerOptions["CompilerVersion"] = "v" + reference.Version.Major + "." + reference.Version.Minor; break; } } if (providerOptions != null && providerOptions.Count > 0) { ConstructorInfo ci = codeDomProviderType.GetConstructor(new Type[] { typeof(IDictionary<string, string>) }); CodeDomProvider provider = null; if (ci != null) { // First, obtain the language for the given codedom provider type. CodeDomProvider defaultProvider = (CodeDomProvider)Activator.CreateInstance(codeDomProviderType); string extension = defaultProvider.FileExtension; // Then, use the new createProvider API to create an instance. provider = CodeDomProvider.CreateProvider(extension, providerOptions); } return provider; } return null; } internal static IDictionary<string, string> GetProviderOptions(Type codeDomProviderType) { // Using reflection to get the property for the time being. // This could simply return CompilerInfo.PropertyOptions if it goes public in future. CodeDomProvider provider = (CodeDomProvider)Activator.CreateInstance(codeDomProviderType); string extension = provider.FileExtension; if (CodeDomProvider.IsDefinedExtension(extension)) { CompilerInfo ci = CodeDomProvider.GetCompilerInfo(CodeDomProvider.GetLanguageFromExtension(extension)); PropertyInfo pi = ci.GetType().GetProperty("ProviderOptions", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance); if (pi != null) return (IDictionary<string, string>)pi.GetValue(ci, null); return null; } return null; } } public class CustomCodeRazorHost : WebPageRazorHost { internal const string ApplicationInstancePropertyName = "ApplicationInstance"; internal const string ContextPropertyName = "Context"; internal const string WebDefaultNamespace = "ASP"; private static readonly string _helperPageBaseType = typeof(HelperPage).FullName; public CustomCodeRazorHost(string virtualPath) : base(virtualPath) { DefaultBaseClass = _helperPageBaseType; DefaultNamespace = WebDefaultNamespace; DefaultDebugCompilation = false; StaticHelpers = true; } //Version for MVC 3 public override void PostProcessGeneratedCode(CodeCompileUnit codeCompileUnit, CodeNamespace generatedNamespace, CodeTypeDeclaration generatedClass, CodeMemberMethod executeMethod) { // Add additional global imports generatedNamespace.Imports.AddRange(GetGlobalImports().Select(s => new CodeNamespaceImport(s)).ToArray()); // Create ApplicationInstance property CodeMemberProperty prop = new CodeMemberProperty() { Name = ApplicationInstancePropertyName, Type = new CodeTypeReference(typeof(HttpApplication).FullName), HasGet = true, HasSet = false, Attributes = MemberAttributes.Family | MemberAttributes.Final }; prop.GetStatements.Add( new CodeMethodReturnStatement( new CodeCastExpression( new CodeTypeReference(typeof(HttpApplication).FullName), new CodePropertyReferenceExpression( new CodePropertyReferenceExpression( null, ContextPropertyName), ApplicationInstancePropertyName)))); generatedClass.Members.Insert(0, prop); // Yank out the execute method (ignored in Razor Web Code pages) generatedClass.Members.Remove(executeMethod); // Make ApplicationInstance static CodeMemberProperty appInstanceProperty = generatedClass.Members .OfType<CodeMemberProperty>() .Where(p => ApplicationInstancePropertyName .Equals(p.Name)) .SingleOrDefault(); if (appInstanceProperty != null) { appInstanceProperty.Attributes |= MemberAttributes.Static; } } //Version for MVC 4 public override void PostProcessGeneratedCode(CodeGeneratorContext context) { // Add additional global imports context.Namespace.Imports.AddRange(GetGlobalImports().Select(s => new CodeNamespaceImport(s)).ToArray()); // Create ApplicationInstance property CodeMemberProperty prop = new CodeMemberProperty() { Name = ApplicationInstancePropertyName, Type = new CodeTypeReference(typeof(HttpApplication).FullName), HasGet = true, HasSet = false, Attributes = MemberAttributes.Family | MemberAttributes.Final }; prop.GetStatements.Add( new CodeMethodReturnStatement( new CodeCastExpression( new CodeTypeReference(typeof(HttpApplication).FullName), new CodePropertyReferenceExpression( new CodePropertyReferenceExpression( null, ContextPropertyName), ApplicationInstancePropertyName)))); context.GeneratedClass.Members.Insert(0, prop); // Yank out the execute method (ignored in Razor Web Code pages) context.GeneratedClass.Members.Remove(context.TargetMethod); // Make ApplicationInstance static CodeMemberProperty appInstanceProperty = context.GeneratedClass.Members .OfType<CodeMemberProperty>() .Where(p => ApplicationInstancePropertyName .Equals(p.Name)) .SingleOrDefault(); if (appInstanceProperty != null) { appInstanceProperty.Attributes |= MemberAttributes.Static; } } protected override string GetClassName(string virtualPath) { return ParserHelpers.SanitizeClassName(Path.GetFileNameWithoutExtension(virtualPath)); } }
但请注意,如果在.cshtml文件中存在语法错误,则下次有问题要编译(因为生成的.cs文件将具有编译错误),但Visual Studio显然存在问题以确定问题。
有时,来自最后一个构建的编译代码(从.cs文件编译),有时可以与新更新的.cshtml文件冲突。
因此,我建议添加预构建事件以截断文件
.echo. > $(ProjectDir)\Path\to\.cs\file
如果已更改.cshtml文件(而且这也适用于上面写的代码),则才能更复杂并执行此操作。
使用 razor generator 在与助手里面的视图上的扩展,您将为您生成代码在编译之前查看。生成的视图代码是项目的一部分,并将其编译到程序集中,因此您可以在任何位置放置视图文件并在任何位置使用帮助者,即使是一个单元测试。
当然,您可以将它们放在代码或项目结构中的任何位置。在您创建帮助程序的文件中,务必包含使用System.Web.mvc。
然后刚刚延长这样的辅助类:
namespace System.Web.Mvc
{
static class HtmlHelperExtensions
{
public static IHtmlString MyNewHelper(this HtmlHelper helper, string someParam)
{
// do something
}
}
}
.