app_code 외부의 글로벌 Razor @Helper가 있습니까?
-
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에 넣을 것을 정말로 가지고 있지 않기 때문에 이것을 묻습니다.나는 프로젝트를 조금 다르게 구조화하고 싶습니다.
감사합니다.
업데이트 : 다른 유형의 확장을 원하지 않습니다.나는 Scott가 여기에 대해 말하고있는 순수한 면도기 헬퍼에만 관심이 있습니다. http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-헬퍼 - 구문 - in-razor.aspx
해결책
질문은 제목에 명시된대로 간단합니다. 면도기 도우미가 'app_code'외부를 가지고 있습니까?
아니오, 그렇지 않습니다.
다른 팁
결코 말하지 마라 ...
방법 1 : (웹 응용 프로그램 프로젝트에서 사용하기 위해)
파일을 app_code 폴더에 복사하는 사전 빌드 이벤트를 추가하십시오.
(그러나 파일이 프로젝트에 포함되어야하므로 APP_CODE DIR에 이름이 같은 빈 파일을 추가 한 다음 빌드 이벤트를 업데이트 할 수 있습니다.)
(원래 파일을 APP_CODE 폴더에 넣으려면 먼저 IntelliSense를 얻지 못할 경우에도 먼저 차이가 없으므로 intellisense를 얻지 못합니다.)
방법 2 : (시작 프로젝트가 웹 응용 프로그램 인 클래스 라이브러리에서 사용)
클래스 라이브러리에서 APP_CODE 폴더는 특별한 것이 아니므로 도우미 페이지 글로벌을 가질 수 있으려면 Razor 코드를 무시해야합니다. app_code 폴더.
더 자세히 면도기 코드는 전체 경로를 기반으로 네임 스페이스를 만듭니다.
우리는 문제가있는 이후에, intelliisense가 없으므로 이러한 모든 문제를 피하기 위해 다음 코드를 작성했습니다.
- .cshtml (또는 VBHTML) 파일이 최종 프로젝트 출력 디렉토리에 복사됩니다
- 전역 도우미 파일 이름과 동일한 이름으로 .cs (또는 .vb) 파일을 추가하고 "컴파일"에 빌드 조치를 설정합니다 (이 파일은 IntelliSense를 제공하기 위해 시작시 자동으로 발생합니다)
- AssemblyInfo.cs 파일 에 PreApplicationStartupClass를 등록해야합니다.
- PreApplicationStartupCode.start () 메서드를 교체하여 Bin 폴더의 글로벌 도우미 페이지의 상대 경로를 종속성 순서대로 제공해야합니다 (즉, 글로벌 도우미 파일 중 하나가 다른 헬퍼 파일 중 하나 인 경우 파일은 그런 다음 이후에 나열되어야합니다).
- CustomRazorCodeHost 클래스에서 MVC 버전이 설치된 에 적합한 PostProcessGeneratedCode ()의 올바른 방법을 선택해야합니다.
여기에 코드가 있습니다 (그러나 "적절한"명령문을 사용하여 적절한 "을 추가해야합니다).
.[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 파일이 컴파일 오류가 컴파일 될 수 있으므로) Visual Studio는 분명히 문제점을 정확하게 문제가 발생하는 데 문제가 있습니다.
또한 마지막 빌드 (.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
}
}
}
.