Подтвердить что ты не робот

Могу ли я использовать глобальную бритву @helper вне App_Code?

Вопрос прост, как указано в названии: существует ли способ использования помощников бритвы вне "App_Code"?

Пример (файл HtmlEx.cshtml):

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

Я спрашиваю об этом, потому что у меня нет ничего другого, чтобы добавить App_Code; Я хочу структурировать свой проект несколько иначе.

Спасибо.

ОБНОВЛЕНИЕ: Я не хочу никаких других типов расширений. Меня интересуют только чистые бритвенные помощники, о которых говорит Скотт: http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx

4b9b3361

Ответ 1

Вопрос прост, как указано в названии: есть ли способ с помощниками бритвы вне "App_Code"?

Нет, нет.

Ответ 2

Никогда не говори никогда...

Первый метод: (для использования в проекте веб-приложения)

Просто добавьте событие предварительной сборки, чтобы скопировать файл в папку App_Code.

(Но поскольку файл, вероятно, должен быть включен в проект, вы можете добавить пустой файл с тем же именем в каталог App_Code, а затем обновить его.)

(Обратите внимание, что даже если вы поместите файл из папки App_code, вы не получите intellisense до первого времени, так что это в любом случае не имеет значения.)

Метод второй: (для использования в библиотеке классов, в которой проект запуска является веб-приложением)

В библиотеке классов папка App_Code не является чем-то особенным, поэтому для того, чтобы иметь вспомогательную страницу глобальную, мы должны переопределить код бритвы, поскольку она жестко закодирована для создания глобальных помощников только для кода в App_code.

Кроме того, код бритвы разработан таким образом, что для глобальных помощников он создает пространство имен на основе полного пути, что вам, вероятно, неинтересно.

В конце концов, мы остаемся с проблемой, что нет доступного intellisense, поэтому, чтобы избежать всех этих проблем, я написал следующий код, предполагая, что:

  • Ваши файлы .cshtml(или vbhtml) копируются в каталог вывода окончательных проектов
  • Вы добавляете файл .cs(или .vb) с тем же именем, что и имя файла глобальных помощников, и устанавливаете его действие сборки для "компиляции" (этот файл будет автогенерироваться при запуске, чтобы обеспечить intellisense).
  • Вам необходимо зарегистрировать PreApplicationStartupClass в файле AssemblyInfo.cs
  • Вы должны заменить метод PreApplicationStartupCode.Start(), чтобы предоставить относительный путь к вашей глобальной странице помощников в папке Bin в порядке зависимости (то есть, если один из глобальных вспомогательных файлов использует помощники в другом файл, то он должен быть указан после него).
  • В классе CustomRazorCodeHost вам необходимо выбрать правильный метод PostProcessGeneratedCode(), который подходит для установленной версии MVC.

Вот код (но нужно добавить соответствующие инструкции "using" ):

[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 будет иметь ошибки компиляции), однако у визуальной студии, по-видимому, есть проблемы, чтобы выявить проблему.

Также иногда скомпилированный код из последней сборки (скомпилированный из файла .cs) иногда может конфликтовать с недавно обновленным файлом .cshtml.

Поэтому я бы рекомендовал добавить событие pre-build для обрезания файла

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

вы можете сделать более сложным и сделать это, только если файл .cshtml был изменен (и это также относится к коду, который я написал выше).

Ответ 3

Используйте расширение Razor Generator в представлении с помощью помощников внутри, и вы создадите код для представления перед временем компиляции. Сгенерированный код просмотра является частью вашего проекта и компилируется в сборку, поэтому вы можете поместить файл вида в любом месте и использовать помощники где угодно, даже с unit test.

Ответ 4

Конечно, вы можете поместить их в любом месте своего кода или структуры проекта. в файле, где вы создаете своего помощника, обязательно включите его с помощью System.Web.Mvc.

то обычно обычно расширяют класс Помощника следующим образом:

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