Pregunta

La pregunta es simple como se indicó en el título: ¿Hay una forma de tener ayudantes de afeitar fuera de 'App_code'?

Ejemplo (archivo HTMLEX.CSHTML):

@helper Script(string fileName, UrlHelper url)
{
<script src="@url.Content("~/Scripts/" + fileName)" type="text/javascript"></script> 
}

pregunto esto porque realmente no tengo nada más que poner en app_code;Quiero estructurar mi proyecto un poco diferente.

gracias.

Actualizar: No quiero ningún otro tipo de extensiones.Estoy interesado en solo ayudantes de afeitar puros, ya que Scott está hablando de aquí: http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-Helper-Sintaxax-Dentro-Razor.aspx

¿Fue útil?

Solución

La pregunta es simple como se indica en el título: ¿Hay alguna forma de ¿Tener ayudantes de afeitar fuera de 'App_code'?

no, no hay.

Otros consejos

nunca digas nunca ...

Método uno: (para uso en un proyecto de aplicación web)

Solo agregue un evento de pre-construcción para copiar su archivo en la carpeta APP_CODE.

(Pero como el archivo probablemente debe incluirse en el proyecto, puede agregar un archivo vacío con el mismo nombre al app_code dir, y luego tener el evento de compilación para actualizarlo).

(Nota, que incluso si coloca el archivo originalmente en la carpeta APP_CODE, no obtendrá Intellisense hasta la primera vez que se construya, por lo que de todos modos no es una diferencia).

Método dos: (para uso en una biblioteca de clases, en la que el proyecto de inicio es una aplicación web)

En una biblioteca de clases, la carpeta App_code no es nada especial, por lo que para poder tener la página de la Ayuda Global, tenemos que anular el Código de Razor, ya que está codificado para hacer que los ayudantes globales solo para el código en el APP_CODE FOLDER.

Además, el código de afeitar está diseñado para que para los ayudantes globales crea un espacio de nombres en función del camino completo, algo que probablemente no está interesado.

Después de todo, permanecemos con un problema, que no hay un intellisense disponible, por lo tanto, para evitar todos estos problemas, he escrito el siguiente código, asumiendo que:

  1. Sus archivos .cshtml (o VBHTML) se están copiando en el directorio de salida de proyectos finales
  2. Agrega un archivo .cs (o .vb) con el mismo nombre que el nombre de Ayudentes Globales, y configure su acción de compilación para "compilar", (este archivo se realizará automoderado al inicio de INTELLISENSE)
  3. Tienes que registrar la preapplicationStartupClass en el archivo ensedtingInfo.cs
  4. Tienes que reemplazar en el método PROPICPICATIONSTARTUPCODE.START (), para dar la ruta relativa a su página de Ayudantes Global en la carpeta Bin, en el orden de la dependencia (es decir, si uno de los archivos de Helper Global usa ayudantes en el otro Archivo, entonces debe aparecer en la lista después de ella).
  5. En la clase CustomrazorcodeHost tiene que elegir el método correcto de código posprocesario () que sea apropiado para la versión MVC instalada

    Aquí está el código (pero uno tendrá que agregar las declaraciones "usando" apropiadas):

    [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));
        }
    } 
    

    Pero tenga en cuenta que si hay un error de sintaxis en el archivo .cshtml, tendrá problemas para compilar la próxima vez (ya que el archivo .CS generado tendrá errores de compilación), sin embargo, Visual Studio aparentemente tiene problemas para identificar el problema.

    También a veces el código compilado de la última compilación (compilado desde el archivo .CS), a veces puede entrar en conflicto con el archivo .cshtml recién actualizado.

    Por lo tanto, recomendaría agregar un evento de pre-construcción para truncar el archivo

    echo. > $(ProjectDir)\Path\to\.cs\file
    

    Puede ir más sofisticado y hacerlo solo si el archivo .cshtml ha sido cambiado (y esto también se aplica al código que he escrito arriba).

Use la generador de afeitadora extensión en una vista con ayudantes dentro y generará el código para elVer antes de la hora de compilación.El código de visión generado forma parte de su proyecto y se compila en el ensamblaje, por lo que puede colocar el archivo de vista en cualquier lugar y usar los ayudantes en cualquier lugar, incluso desde una prueba de unidad.

Claro, puede ponerlos en cualquier lugar de su código o estructura de proyectos.En el archivo donde cree su ayudante, asegúrese de incluir usando SYSTEM.WEB.MVC.

Luego, solo normalmente extiende la clase Helper como esta:

namespace System.Web.Mvc
{
    static class HtmlHelperExtensions
    {
        public static IHtmlString MyNewHelper(this HtmlHelper helper, string someParam)
        {
            // do something
        }
    }
}

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top