2012-05-19 9 views
20

La domanda è semplice come indicato nel titolo: esiste un modo per avere aiutanti del rasoio al di fuori di "App_Code"?Posso avere un rasoio globale @helper al di fuori di App_Code?

Esempio (HtmlEx.cshtml file):

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

chiedo questo perché in realtà non hanno niente altro da mettere in App_Code; Voglio strutturare il mio progetto in modo un po 'diverso.

Grazie.

UPDATE: Non voglio altri tipi di estensioni. Mi interessano solo i puri aiutanti del rasoio di cui Scott sta parlando qui: http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx

+0

Penso che i ragazzi di Microsoft non dovrebbero confondere i concetti. Non vedo davvero il punto di avere App_Code in un progetto MVC. Ci impongono di mettere lì le viste del rasoio globale, e invece non posso nemmeno inserire estensioni all'interno (http://stackoverflow.com/questions/3686906/the-call-is-ambiguous-between-the-following-method-or-properties -in-asp-net-mVC). Strano! – Learner

+0

Credo che il motivo di metterlo nella cartella App_Code, è perché questo è l'unico modo per ottenere intellisense sull'intero progetto –

+0

anche quando è in App_Code li crea come metodi statici e li aggancia correttamente per poterli usare tutti i contesti rilevanti in cui sono normalmente istanza –

risposta

17

La domanda è semplice, come indicato nel titolo: C'è un modo di avere aiutanti rasoio al di fuori del 'App_Code'?

No, non c'è.

+0

Risposta semplice ed efficace ... Grazie ... Lo manterrà aperto un po 'più a lungo per vedere se qualcuno conosce ogni tipo di soluzione alternativa. – Learner

+0

Una risposta semplice, efficace e sfortunata. – Dan

-1

sicuro, li puoi mettere ovunque nel tuo codice o nella struttura del progetto. nel file in cui crei il tuo helper assicurati di includere System.Web.Mvc.

poi basta normalmente estendere la classe Helper in questo modo:

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

scusate, ma voglio aiutanti per i rasoio e non altri tipi di estensioni (HtmlHelper e simili) ... per favore controllate questo: http://weblogs.asp.net/scottgu/archive/ 2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx – Learner

4

Utilizzare l'estensione Razor Generator su una vista con gli helper interni e si genera il codice per la visualizzazione prima della compilazione. Il codice di visualizzazione generato è parte del tuo progetto e viene compilato nell'assieme, quindi puoi posizionare il file di visualizzazione ovunque e utilizzare gli helper ovunque, anche da un test di unità.

+0

Grazie per questo aggiornamento ... lo proverò ad un certo punto – Learner

+0

questo funziona solo per viste veramente semplici, se stai usando gli URL di stile '" ~/"' o anche '@ Url.Content (...) 'non funzionerà –

+0

in più devi ricompilare ogni volta per vedere le modifiche, ma probabilmente è ok per la maggior parte degli helper - solo un fastidio durante lo sviluppo –

6

Never Never Say ...

Un metodo: (Per l'utilizzo in un progetto di applicazione Web)

Basta aggiungere un evento di pre-compilazione per copiare il file nella cartella App_Code.

(Ma dal momento che il file deve probabilmente essere inclusi nel progetto, è possibile aggiungere un file vuoto con lo stesso nome della directory App_Code, e quindi avere l'evento build per aggiornarlo.)

(Nota, anche se metti il ​​file originariamente nella cartella App_code non otterrai intellence fino alla prima creazione, quindi non è una differenza.)

Metodo 2: (da utilizzare in una libreria di classi, in cui il progetto di avvio è un'applicazione web)

In una libreria di classi l'App_ La cartella Codice non è nulla di speciale, quindi per poter avere la pagina di supporto globale, dobbiamo scavalcare il codice del rasoio, poiché è codificato in modo rigido per rendere gli helper globali solo per il codice nella cartella App_code.

Inoltre, il codice del rasoio è progettato in modo che per gli helper globali crei uno spazio dei nomi basato sul percorso completo, cosa che probabilmente non ti interessa.

Dopo tutto quello che rimane con un problema, che non c'è intellisense a disposizione, in modo da evitare tutti questi problemi, ho scritto il seguente codice, assumendo che: i file

  1. tuo .cshtml (o vbhtml) vengono copiati nella directory di output dei progetti finali
  2. Aggiungete un file .cs (o .vb) con lo stesso nome del nome file globale dei helper e impostate la sua azione build su "compilazione", (questo file verrà generato automaticamente a avvio per fornire intellisense)
  3. È necessario registrare PreApplicationStartupClass nel file AssemblyInfo.cs
  4. È necessario sostituire il metodo PreApplicationStartupCode.Start() per fornire il percorso relativo alla pagina globale dei helper nella cartella Bin, nell'ordine di dipendenza (ad es. se uno dei file helper globali utilizza gli helper nell'altro file, dovrebbe essere elencato dopo di esso).
  5. Nella classe CustomRazorCodeHost devi scegliere il metodo corretto di PostProcessGeneratedCode(), che è appropriato per la versione MVC installata

Ecco il codice (ma si dovrà aggiungere l'appropriato "con" dichiarazioni) :

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

Ma nota che se c'è un errore di sintassi nel file .cshtml, si avranno problemi a compilare la prossima volta (come il cs file generato avrà errori di compilazione), tuttavia lo studio visivo apparentemente ha problemi per individuare il problema.

Anche a volte il codice compilato dall'ultima build (compilato dal file .cs), può a volte entrare in conflitto con il file .cshtml appena aggiornato.

Quindi mi sento di raccomandare di aggiungere un evento di pre-compilazione per troncare il file

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

si può andare più sofisticato e di farlo solo se il file è stato modificato .cshtml (e questo vale anche per il codice che ho scritto sopra).

+0

+1 ti meriti sicuramente tutti i meriti per questo, ma dal punto di vista della convenienza Non sono sicuro che sia – Learner

+1

Benché dubiti che sia utile in un'applicazione web standard, è apparentemente l'unico modo per utilizzare helper del rasoio globale in un progetto di libreria –

Problemi correlati