Asp.net mvc 3 我可以在App_代码之外有一个global razor@helper吗?
问题很简单,正如标题中所述:有没有一种方法可以让剃须刀助手在“应用程序代码”之外工作 示例(HtmlEx.cshtml文件):Asp.net mvc 3 我可以在App_代码之外有一个global razor@helper吗?,asp.net-mvc-3,razor,helper,app-code,Asp.net Mvc 3,Razor,Helper,App Code,问题很简单,正如标题中所述:有没有一种方法可以让剃须刀助手在“应用程序代码”之外工作 示例(HtmlEx.cshtml文件): @helper脚本(字符串文件名,UrlHelper url) { } 我这样问是因为我真的没有任何东西可以放在App_代码中;我想我的项目结构有点不同 谢谢 更新:我不想要任何其他类型的扩展。我只对Scott在这里谈到的纯razor助手感兴趣:当然,您可以将它们放在代码或项目结构中的任何位置。在创建助手的文件中,请确保包含using System.Web.Mvc 然
@helper脚本(字符串文件名,UrlHelper url)
{
}
我这样问是因为我真的没有任何东西可以放在App_代码中;我想我的项目结构有点不同
谢谢
更新:我不想要任何其他类型的扩展。我只对Scott在这里谈到的纯razor助手感兴趣:当然,您可以将它们放在代码或项目结构中的任何位置。在创建助手的文件中,请确保包含using System.Web.Mvc 然后按如下方式扩展Helper类:
namespace System.Web.Mvc
{
static class HtmlHelperExtensions
{
public static IHtmlString MyNewHelper(this HtmlHelper helper, string someParam)
{
// do something
}
}
}
问题很简单,如标题所述:有没有一种方法
在“应用程序代码”之外有剃须刀助手吗
不,没有。在包含帮助程序的视图上使用扩展,您将在编译之前为该视图生成代码。生成的视图代码是项目的一部分,并编译到程序集中,因此您可以将视图文件放置在任何位置,并在任何位置使用帮助程序,甚至可以从单元测试开始 永远不要说不… 方法一:(用于web应用程序项目) 只需添加一个预构建事件即可将文件复制到App_Code文件夹中 (但由于该文件可能必须包含在项目中,因此可以向App_Code dir添加一个同名的空文件,然后使用build事件对其进行更新。) (请注意,即使您最初将文件放在App_code文件夹中,在第一次构建之前,您也不会获得intellisense,因此无论如何都没有区别。) 方法二:(用于类库,其中启动项目是web应用程序) 在类库中,App_代码文件夹没有什么特殊之处,因此为了能够使helper页面成为全局的,我们必须重写razor代码,因为它是硬编码的,只为App_代码文件夹中的代码生成全局helper 此外,razor代码的设计使全局助手能够基于完整路径创建名称空间,这可能是您不感兴趣的 毕竟我们仍然存在一个问题,即没有可用的intellisense,因此为了避免所有这些问题,我编写了以下代码,假设:
只有在.cshtml文件已更改的情况下(这也适用于我上面编写的代码),您才能进行更复杂的操作。对不起,我需要的是razor助手,而不是任何其他类型的扩展名(HtmlHelper等)。。。请检查:简单有效的答案。。。谢谢我想微软的人不应该混淆这些概念。我真的不认为在MVC项目中使用App_代码有什么意义。他们强制我们将全局razor视图放在那里,而我甚至不能将扩展放在里面()。奇怪!我相信把它放在App_代码文件夹中的原因是,这是在整个项目中获得intellisense的唯一方法。当它在App_代码中时,它会将它们创建为静态方法,并将它们正确地连接起来,以便能够使用所有相关的上下文,而这些上下文通常是实例+1。你肯定应该为此获得所有优点,但是,从方便的角度来看,我不太确定,虽然我怀疑它在标准web应用程序中是否有用,但它显然是在库项目中使用全局剃须刀帮助器的唯一方法。如果您使用的是
“~/”
样式的Url,甚至@Url.Content(…)
这不管用,而且每次都要重新编译才能看到更改,但对于大多数帮助者来说,这可能没什么,只是开发过程中的一个难题
namespace System.Web.Mvc
{
static class HtmlHelperExtensions
{
public static IHtmlString MyNewHelper(this HtmlHelper helper, string someParam)
{
// do something
}
}
}
[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));
}
}
echo. > $(ProjectDir)\Path\to\.cs\file