Могу ли я иметь глобальный бритвой @helper за пределами app_code?
-
11-12-2019 - |
Вопрос
Вопрос прост, как указано в заголовке: Есть ли способ иметь бритвой помощников за пределами «App_Code»?
Пример (файл htmlex.cshtml):
@helper Script(string fileName, UrlHelper url)
{
<script src="@url.Content("~/Scripts/" + fileName)" type="text/javascript"></script>
}
.
Я спрашиваю это, потому что мне на самом деле нет ничего, чтобы поставить в App_Code;Я хочу структурировать свой проект немного по-другому.
спасибо.
<Сильное> Обновление: Я не хочу никаких других типов расширений.Меня интересует только чистые бритвы, так как Скотт говорит здесь: http://weblogs.ass.asp.net/scottgu/Archive/2011/05/12/asp-net-mvc-3- и-HELPER-SYNTAX-INTER-RAZOR.ASPX
Решение
Вопрос прост, как указано в заголовке: есть ли способ Наличие бритвых помощников за пределами 'app_code'?
Нет, нет.
Другие советы
Никогда не говори никогда ...
Просто добавьте событие предварительного сборки, чтобы скопировать файл в папку App_Code.
(но поскольку файл, вероятно, должен быть включен в проект, вы можете добавить пустой файл с тем же именем на app_code dir, а затем иметь событие сборки для его обновления.)
(Обратите внимание, что даже если вы поместите файл изначально в папку App_Code, вы не будете получать Intellisense до первого строяния, поэтому это все равно не разница.)
В библиотеке классов папка App_Code не является чем-то особенным, поэтому для того, чтобы иметь возможность иметь Page Helper Global, мы должны переопределить код бритва, так как жестко закодировано, чтобы сделать глобальные помощники только для кода в Папка App_Code.
Далее, кроме того, что код бритва разработан так, чтобы для глобальных помощников он создает пространство имен на основе полного пути, то, что вам, вероятно, не заинтересован.
Ведь мы остаемся с проблемой, что нет в наличии IntelliSense, поэтому чтобы избежать всех этих проблем, я написал следующий код, предполагая, что:
- Ваш файлы .cshtml (или vbhtml) скопированы в каталог выходных проектов
- Вы добавляете файл .cs (или .vb) с тем же именем, что и имя файла Global Meampers, и установить его действие для «Compile», (этот файл будет автогенерирован при запуске, чтобы обеспечить Intellisense)
- Вы должны зарегистрировать PREAPLINGATIONSTARTUPCASS в файле AssockInfo.cs
- Вы должны заменить в методе PREAPSINTATIONSTARTUPCODE.START (), чтобы дать относительный путь к странице глобальных помощников в папке Bin, в порядке зависимости (т. Е. Если один из файлов Global Helper использует помощники в другом Затем его следует перечислить после него).
- в классе CustomRazorcodehost вы должны выбрать правильный метод PostProcessGenedCodeCode (), который подходит для версии MVC, установленной
Вот код (но нужно добавить соответствующие «с использованием» отчетов):
.[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. Поэтому я бы порекомендовал добавить событие предварительного сборки для усечения файла
.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
}
}
}
.