Asp.net mvc 3 我可以在App_代码之外有一个global razor@helper吗?

Asp.net mvc 3 我可以在App_代码之外有一个global razor@helper吗?,asp.net-mvc-3,razor,helper,app-code,Asp.net Mvc 3,Razor,Helper,App Code,问题很简单,正如标题中所述:有没有一种方法可以让剃须刀助手在“应用程序代码”之外工作 示例(HtmlEx.cshtml文件): @helper脚本(字符串文件名,UrlHelper url) { } 我这样问是因为我真的没有任何东西可以放在App_代码中;我想我的项目结构有点不同 谢谢 更新:我不想要任何其他类型的扩展。我只对Scott在这里谈到的纯razor助手感兴趣:当然,您可以将它们放在代码或项目结构中的任何位置。在创建助手的文件中,请确保包含using System.Web.Mvc 然

问题很简单,正如标题中所述:有没有一种方法可以让剃须刀助手在“应用程序代码”之外工作

示例(HtmlEx.cshtml文件):

@helper脚本(字符串文件名,UrlHelper url)
{
}
我这样问是因为我真的没有任何东西可以放在App_代码中;我想我的项目结构有点不同

谢谢


更新:我不想要任何其他类型的扩展。我只对Scott在这里谈到的纯razor助手感兴趣:

当然,您可以将它们放在代码或项目结构中的任何位置。在创建助手的文件中,请确保包含using System.Web.Mvc

然后按如下方式扩展Helper类:

namespace System.Web.Mvc
{
    static class HtmlHelperExtensions
    {
        public static IHtmlString MyNewHelper(this HtmlHelper helper, string someParam)
        {
            // do something
        }
    }
}
问题很简单,如标题所述:有没有一种方法 在“应用程序代码”之外有剃须刀助手吗


不,没有。

在包含帮助程序的视图上使用扩展,您将在编译之前为该视图生成代码。生成的视图代码是项目的一部分,并编译到程序集中,因此您可以将视图文件放置在任何位置,并在任何位置使用帮助程序,甚至可以从单元测试开始

永远不要说不…

方法一:(用于web应用程序项目)

只需添加一个预构建事件即可将文件复制到App_Code文件夹中

(但由于该文件可能必须包含在项目中,因此可以向App_Code dir添加一个同名的空文件,然后使用build事件对其进行更新。)

(请注意,即使您最初将文件放在App_code文件夹中,在第一次构建之前,您也不会获得intellisense,因此无论如何都没有区别。)

方法二:(用于类库,其中启动项目是web应用程序)

在类库中,App_代码文件夹没有什么特殊之处,因此为了能够使helper页面成为全局的,我们必须重写razor代码,因为它是硬编码的,只为App_代码文件夹中的代码生成全局helper

此外,razor代码的设计使全局助手能够基于完整路径创建名称空间,这可能是您不感兴趣的

毕竟我们仍然存在一个问题,即没有可用的intellisense,因此为了避免所有这些问题,我编写了以下代码,假设:

  • 您的.cshtml(或vbhtml)文件将被复制到最终项目输出目录
  • 您添加了一个与全局助手文件名同名的.cs(或.vb)文件,并将其生成操作设置为“编译”(该文件将在启动时自动生成,以提供intellisense)
  • 您必须在AssemblyInfo.cs文件中注册PreApplicationStartupClass
  • 您必须在PreApplicationStartupCode.Start()方法中进行替换,以按照依赖关系的顺序提供Bin文件夹中全局帮助程序页的相对路径(即,如果其中一个全局帮助程序文件使用另一个文件中的帮助程序,则应在其后面列出)
  • 在CustomRazorCodeHost类中,您必须选择适合所安装MVC版本的PostProcessGeneratedCode()的正确方法
  • 以下是代码(但必须添加适当的“using”语句):


    只有在.cshtml文件已更改的情况下(这也适用于我上面编写的代码),您才能进行更复杂的操作。

    对不起,我需要的是razor助手,而不是任何其他类型的扩展名(HtmlHelper等)。。。请检查:简单有效的答案。。。谢谢我想微软的人不应该混淆这些概念。我真的不认为在MVC项目中使用App_代码有什么意义。他们强制我们将全局razor视图放在那里,而我甚至不能将扩展放在里面()。奇怪!我相信把它放在App_代码文件夹中的原因是,这是在整个项目中获得intellisense的唯一方法。当它在App_代码中时,它会将它们创建为静态方法,并将它们正确地连接起来,以便能够使用所有相关的上下文,而这些上下文通常是实例+1。你肯定应该为此获得所有优点,但是,从方便的角度来看,我不太确定,虽然我怀疑它在标准web应用程序中是否有用,但它显然是在库项目中使用全局剃须刀帮助器的唯一方法。如果您使用的是
    “~/”
    样式的Url,甚至
    @Url.Content(…)
    这不管用,而且每次都要重新编译才能看到更改,但对于大多数帮助者来说,这可能没什么,只是开发过程中的一个难题
    namespace System.Web.Mvc
    {
        static class HtmlHelperExtensions
        {
            public static IHtmlString MyNewHelper(this HtmlHelper helper, string someParam)
            {
                // do something
            }
        }
    }
    
    [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));
        }
    } 
    
    echo. > $(ProjectDir)\Path\to\.cs\file