문제

질문은 제목에 명시된대로 간단합니다. "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가 없으므로 이러한 모든 문제를 피하기 위해 다음 코드를 작성했습니다.

  1. .cshtml (또는 VBHTML) 파일이 최종 프로젝트 출력 디렉토리에 복사됩니다
  2. 전역 도우미 파일 이름과 동일한 이름으로 .cs (또는 .vb) 파일을 추가하고 "컴파일"에 빌드 조치를 설정합니다 (이 파일은 IntelliSense를 제공하기 위해 시작시 자동으로 발생합니다)
  3. AssemblyInfo.cs 파일 에 PreApplicationStartupClass를 등록해야합니다.
  4. PreApplicationStartupCode.start () 메서드를 교체하여 Bin 폴더의 글로벌 도우미 페이지의 상대 경로를 종속성 순서대로 제공해야합니다 (즉, 글로벌 도우미 파일 중 하나가 다른 헬퍼 파일 중 하나 인 경우 파일은 그런 다음 이후에 나열되어야합니다).
  5. 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
        }
    }
}
.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top