Pergunta

A pergunta é simples, conforme indicado no título:Existe uma maneira de ter ajudantes de barbear fora do 'App_Code'?

Exemplo (arquivo HtmlEx.cshtml):

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

Pergunto isso porque não tenho mais nada para colocar no App_Code;Quero estruturar meu projeto um pouco diferente.

Obrigado.

ATUALIZAR: Não quero nenhum outro tipo de extensão.Estou interessado apenas em ajudantes de navalha pura, como Scott está falando aqui: http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx

Foi útil?

Solução

A pergunta é simples, conforme indicado no título:Existe uma maneira de ter ajudantes de barbear fora de 'App_code'?

Não, não há.

Outras dicas

Nunca diga nunca...

Método um:(Para uso em um projeto de aplicativo da web)

Basta adicionar um evento de pré-construção para copiar seu arquivo para a pasta App_Code.

(Mas como o arquivo provavelmente deve ser incluído no projeto, você pode adicionar um arquivo vazio com o mesmo nome ao diretório App_Code e, em seguida, fazer com que o evento build o atualize.)

(Observe que, mesmo se você colocar o arquivo originalmente na pasta App_code, você não obterá o intellisense até a primeira compilação, portanto, de qualquer maneira, não faz diferença.)

Método dois:(para uso em uma biblioteca de classes, na qual o projeto de inicialização é uma aplicação web)

Em uma biblioteca de classes, a pasta App_Code não é nada especial, portanto, para podermos ter a página auxiliar global, temos que substituir o código razor, pois ele é codificado para criar auxiliares globais apenas para o código na pasta App_code.

Além disso, o código razor é projetado para que, para ajudantes globais, ele crie um namespace baseado no caminho completo, algo que provavelmente não lhe interessa.

Afinal continuamos com um problema, que não existe intellisense disponível, então para evitar todos esses problemas, escrevi o seguinte código, assumindo que:

  1. Seus arquivos .cshtml (ou vbhtml) estão sendo copiados para o diretório de saída do projeto final
  2. Você adiciona um arquivo .cs (ou .vb) com o mesmo nome do nome do arquivo auxiliar global e define sua ação de construção como "compilar" (este arquivo será gerado automaticamente na inicialização para fornecer intellisense)
  3. Você deve registrar o PreApplicationStartupClass no arquivo AssemblyInfo.cs
  4. Você deve substituir no método PreApplicationStartupCode.Start(), para fornecer o caminho relativo para sua página de ajudantes globais na pasta Bin, na ordem de dependência (ou seja,se um dos arquivos auxiliares globais usar auxiliares no outro arquivo, ele deverá ser listado depois dele).
  5. Na classe CustomRazorCodeHost você deve escolher o método correto de PostProcessGeneratedCode() que seja apropriado para a versão MVC instalada

Aqui está o código (mas será necessário adicionar as instruções "using" apropriadas):

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

Mas observe que se houver um erro de sintaxe no arquivo .cshtml, você terá problemas para compilar na próxima vez (pois o arquivo .cs gerado terá erros de compilação), porém o visual studio aparentemente tem problemas para identificar o problema.

Além disso, às vezes, o código compilado da última compilação (compilado do arquivo .cs) pode entrar em conflito com o arquivo .cshtml recém-atualizado.

Portanto, eu recomendaria adicionar um evento de pré-construção para truncar o arquivo

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

você pode ser mais sofisticado e fazê-lo somente se o arquivo .cshtml tiver sido alterado (e isso também se aplica ao código que escrevi acima).

Use o Gerador de navalha extensão em uma visualização com ajudantes internos e você gerará o código para a visualização antes do tempo de compilação.O código de visualização gerado faz parte do seu projeto e é compilado no assembly, para que você possa colocar o arquivo de visualização em qualquer lugar e usar os auxiliares em qualquer lugar, até mesmo em um teste de unidade.

claro, você pode colocá-los em qualquer lugar do seu código ou estrutura do projeto.no arquivo onde você cria seu auxiliar, certifique-se de incluir using System.Web.Mvc.

então, normalmente, estenda a classe Helper assim:

namespace System.Web.Mvc
{
    static class HtmlHelperExtensions
    {
        public static IHtmlString MyNewHelper(this HtmlHelper helper, string someParam)
        {
            // do something
        }
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top