标题中所述的问题很简单:有没有办法在'App_Code'之外使用剃刀助手?
示例(HtmlEx.cshtml文件):
@helper Script(string fileName, UrlHelper url)
{
<script src="@url.Content("~/Scripts/" + fileName)" type="text/javascript"></script>
}
我问这个因为我在App_Code中没有任何其他内容;我想把我的项目结构有点不同。
感谢。
更新:我不想要任何其他类型的扩展程序。斯科特在这里谈到的只有纯粹的剃刀助手:http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx
答案 0 :(得分:18)
标题中所述的问题很简单:有没有办法 在App_Code&#39;?
之外有剃刀助手
不,没有。
答案 1 :(得分:6)
永不言败......
方法一:(用于Web应用程序项目)
只需添加预构建事件即可将文件复制到App_Code文件夹中。
(但由于文件必须包含在项目中,您可以向App_Code目录添加一个具有相同名称的空文件,然后使用build事件来更新它。)
(注意,即使您最初将文件放在App_code文件夹中,也不会在第一次构建之前获得智能感知,因此无论如何都不会产生差异。)
方法二:(用于类库,其中启动项目是Web应用程序)
在类库中,App_Code文件夹并不特别,所以为了能够将帮助页面设置为全局,我们必须覆盖剃刀代码,因为它是硬编码的,只为代码中的代码创建全局帮助程序。 App_code文件夹。
此外,剃刀代码的设计使得对于全局帮助程序,它会根据完整路径创建名称空间,这可能是您不感兴趣的。
毕竟我们仍然遇到问题,没有可用的智能感知,所以为了避免所有这些问题,我编写了以下代码,假设:
这是代码(但必须添加适当的“using”语句):
[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文件被更改时才能这样做(这也适用于我上面编写的代码)。
答案 2 :(得分:4)
在包含帮助程序的视图上使用Razor Generator扩展名,您将在编译之前生成视图的代码。生成的视图代码是项目的一部分并编译到程序集中,因此您可以将视图文件放在任何位置,并在任何地方使用帮助程序,甚至可以从单元测试中使用。
答案 3 :(得分:-1)
当然,您可以将它们放在代码或项目结构中的任何位置。在您创建帮助程序的文件中,请确保使用System.Web.Mvc。
然后通常像这样扩展Helper类:
namespace System.Web.Mvc
{
static class HtmlHelperExtensions
{
public static IHtmlString MyNewHelper(this HtmlHelper helper, string someParam)
{
// do something
}
}
}