Razor 2.0 模板引擎,支持布局






4.86/5 (38投票s)
Razor 2.0 模板引擎,可在 MVC 之外工作,并支持布局(母版页)和 _ViewStart,就像 ASP.NET MVC 一样。
介绍
我已将代码库迁移到 GitHub。如需获取最新更新,请访问 GitHub 上的 RazorMachine。
还有一个 RazorMachine NuGet 安装程序包。要使用 NuGet 安装 RazorMachine,请在程序包管理器控制台中运行以下命令:
PM> Install-Package RazorMachine
我假设您了解 ASP.NET Razor 视图引擎及其独立用于生成任何文本内容的能力。否则您为何在此阅读本文?因此,我将不会(过多地)介绍所有有趣的 Razor 功能以及 Razor 的工作原理。相反,我将介绍一种在 MVC 之外使用 Razor 视图引擎的布局(母版页)的方法。目前,您会发现许多框架在 MVC 环境之外提供 Razor 模板执行,例如CodePlex 上的 Razor TemplateEngine。我没有发现任何一个框架以简单的方式支持布局。而且其中没有一个使用 Razor 2.0。
在本文中,我提到“MVC”时,指的是 ASP.NET MVC 框架,而不是 MVC 本身。我提到“内容”时,指的是模板内容及其嵌入的脚本、标记等。
这个小型框架提供了一个基于 Razor 2.0(MVC 4 的一部分)的模板引擎,它支持布局和其他功能,其方式与 MVC 支持这些功能的方式大致相同。当您想在 MVC 环境之外使用 Razor 渲染报告、电子邮件、源代码、HTML 或其他任何内容时,该框架将为您提供帮助,尤其是在需要将特定内容与布局合并时。我尽可能地遵循 MVC 的模板位置、命名和模板查找约定,以便 MVC 开发人员能够尽可能轻松地开始使用此模板引擎。该实现“感觉”像 MVC,并且默认配置允许您在编辑 Razor 模板时在 Visual Studio 中使用智能感知。由于此框架使用 Razor 2.0(MVC 4 的一部分),因此它提供了 Razor 2.0 的功能,例如根运算符替换(“~/”)和条件 HTML 属性,即当相应值评估为 null 或空时不会呈现的属性。
在 HTML 方面,我实现了 Razor 2.0 提供的基本 HTML 功能,并实现了可选的 HTML 编码。通过“可选”,我的意思是 HTML 编码可以完全关闭,这对于代码生成器等非常方便。
我没有包含额外的 HTML 帮助器功能。但您可以通过继承默认模板基类并自行添加这些功能。
使用代码
为了获得一个整体的认识,我认为最好的方法是直接深入一些示例。您也可以在源代码(项目 Xipton.Razor.Example)中找到这些示例。
当您开始使用此框架时,最重要的类是 RazorMachine
。以下示例(也包含在示例项目中)使用 RazorMachine
的本地实例。请注意,通常出于性能原因,在任何应用程序中,您都会创建一个 RazorMachine
的单例实例(因为内部实例绑定的 JIT 创建类型缓存)并使用该特定实例进行所有模板执行。
示例 1 - 执行模板
RazorMachine rm = new RazorMachine();
ITemplate template = rm.ExecuteContent("Razor says: Hello @Model.FirstName @Model.LastName",
new {FirstName="John", LastName="Smith"});
Console.WriteLine(template.Result);
示例 2 - 使用布局执行模板
RazorMachine rm = new RazorMachine();
rm.RegisterTemplate("~/shared/_layout.cshtml",
"BEGIN TEMPLATE \r\n @RenderBody() \r\nEND TEMPLATE");
ITemplate template = rm.ExecuteContent("@{Layout=\"_layout\";} Razor says: Hello @Model.FirstName @Model.LastName",
new {FirstName="John", LastName="Smith"});
Console.WriteLine(template); // template.ToString() evaluates to template.Result
示例 3 - 使用布局和 _viewStart 执行模板
RazorMachine rm = new RazorMachine();
rm.RegisterTemplate("~/shared/_layout.cshtml","BEGIN TEMPLATE \r\n @RenderBody() \r\nEND TEMPLATE");
rm.RegisterTemplate("~/_viewstart.cshtml","@{Layout=\"_layout\";}");
ITemplate template = rm.ExecuteContent("Razor says: Hello @Model.FirstName @Model.LastName",
new {FirstName="John", LastName="Smith"});
Console.WriteLine(template); // same result as example 2
示例 4 - 使用布局和 _viewStart 通过虚拟路径执行模板
RazorMachine rm = new RazorMachine();
rm.RegisterTemplate("~/shared/_layout.cshtml","BEGIN TEMPLATE \r\n @RenderBody() \r\nEND TEMPLATE");
rm.RegisterTemplate("~/_viewstart.cshtml","@{Layout=\"_layout\";}");
rm.RegisterTemplate("~/simpleTemplate.cshtml","Razor says: Hello @Model.FirstName @Model.LastName");
ITemplate template = rm.ExecuteUrl("~/simpleTemplate.cshtml",
new {FirstName="John", LastName="Smith"});
Console.WriteLine(template); // same result as example 2
示例 5 - 使用 ViewBag 将信息从任何地方(包括布局)返回给调用者
rm.RegisterTemplate("~/shared/_layout.cshtml", "@{ViewBag.PiValue=3.1415927;}");
rm.RegisterTemplate("~/_viewstart.cshtml", "@{Layout=\"_layout\";}");
ITemplate template = rm.ExecuteContent("Anything");
Console.WriteLine(template.ViewBag.PiValue); // => writes 3.1415927
示例 6 - 将信息从任何地方添加到预定义的 ViewBag 并将其返回给调用者
RazorMachine rm = new RazorMachine();
rm.RegisterTemplate("~/shared/_layout.cshtml", "@{ViewBag.Values.Add(3.1415927);}");
rm.RegisterTemplate("~/_viewstart.cshtml", "@{Layout=\"_layout\";}");
ITemplate template = rm.ExecuteContent("Anything",
viewbag:new {Values = new List<double>{0,1,2});
Console.WriteLine(template.ViewBag.Values[3]); // => writes 3.1415927
示例 7 - 显示生成的代码
RazorMachine rm = new RazorMachine(includeGeneratedSourceCode:true);
rm.RegisterTemplate("~/shared/_layout.cshtml",
"BEGIN TEMPLATE \r\n @RenderBody() \r\nEND TEMPLATE");
rm.RegisterTemplate("~/_viewstart.cshtml",
"@{Layout=\"_layout\";}");
ITemplate template = rm.ExecuteContent("Razor says: Hello @Model.FirstName @Model.LastName",
new { FirstName = "John", LastName = "Smith" });
Console.WriteLine(template); // writes output result
Console.WriteLine(template.GeneratedSourceCode); // writes generated source for template
Console.WriteLine(template.Childs[0].GeneratedSourceCode); // writes generated source for layout
示例 8 - HTML 编码
RazorMachine rm = new RazorMachine();
// not encoded since all output is literal content. Literal content is never encoded.
Console.WriteLine(rm.ExecuteContent("Tom & Jerry").Result);
// encoded since the content is written as a string value
// and by default HtmlEncode is on
Console.WriteLine(rm.ExecuteContent("@Model.Text",
new {Text="Tom & Jerry"}).Result);
// not encoded since content is a written as a raw string
Console.WriteLine(rm.ExecuteContent("@Raw(Model.Text)",
new { Text = "Tom & Jerry" }).Result);
// not encoded since HtmlEncoding is turend off in code
Console.WriteLine(rm.ExecuteContent("@{HtmlEncode=false;} @Model.Text",
new { Text = "Tom & Jerry" }).Result);
rm = new RazorMachine(htmlEncode: false);
// not encoded since now html encoding if off by default, still you can set it on in code
Console.WriteLine(rm.ExecuteContent("@Model.Text",
new { Text = "Tom & Jerry" }).Result);
示例 9 - 根运算符直接解析
RazorMachine rm = new RazorMachine(rootOperatorPath:"/MyAppName");
rm.RegisterTemplate("~/MyTemplate",
"<a href='~/SomeLink'>Some Link</a>");
var template = rm.ExecuteUrl("/MyAppName/MyTemplate");
// same result as:
template = rm.ExecuteUrl("~/MyTemplate");
Console.WriteLine(template); // writes: <a href=/MyAppName/SomeLink>Some Link</a>
示例 10 - Razor 2:值为 null 的属性不被渲染
RazorMachine rm = new RazorMachine();
var template = rm.ExecuteContent(@"<a href='~/SomeLink'
data-brand='@Model.Brand'
data-not-rendered='@Model.NullValue'>Some Link</a>", new {Brand="Toyota",NullValue=(string)null});
Console.WriteLine(template); // writes: <a href=/SomeLink data-brand='Toyota'>Some Link</a>
假设您了解 MVC,我认为示例相当直观。如您所见,大多数示例都“感觉”像 MVC。
深入研究
基本上,RazorMachine
将虚拟路径作为模板定位器。您仍然可以直接执行模板内容,如示例所示。RazorMachine
上的模板执行方法如下所示:
public virtual ITemplate ExecuteUrl(
string templateVirtualPath, // any virtual path as the resource locator,
// absolute or with virtual root operator
object model = null, // though optional its presence is obvious
object viewbag = null, // you may pass an initialized ViewBag
bool skipLayout = false, // you may force to skip any possibly layout
bool throwExceptionOnVirtualPathNotFound = true) // if false then null is returned
// if the virtual path was not found
public virtual ITemplate ExecuteContent(
string templateContent, // template script itself, internally managed by a generated url
object model = null,
object viewbag = null,
bool skipLayout = false)
RazorMachine
上有一个额外的便利执行方法,名为 Execute
,它会将执行转发给 ExecuteUrl
或 ExecuteContent
。其签名如下:
public virtual ITemplate Execute(
string templateVirtualPathOrContent, // if it starts with a '/' or a '~' => it is a path
object model = null,
object viewbag = null,
bool skipLayout = false,
bool throwExceptionOnVirtualPathNotFound = true) // only holds with a path argument
执行方法返回一个 ITemplate
实例。该接口如下所示:
public interface ITemplate {
#region MVC alike
string Layout { get; set; }
dynamic Model { get; }
dynamic ViewBag { get; }
// Returns the Parents's result as a LiteralString which should not be encoded again.
LiteralString RenderBody();
// Returns the given virtual path result as a LiteralString which should not be encoded again.
// You may skip the child's layout using the parameter skipLayout which can be handy
// when rendering template partials (controls)
LiteralString RenderPage(string virtualPath, object model = null, bool skipLayout = false);
// Returns any Parents's section as a LiteralString which should not be encoded again.
LiteralString RenderSection(string sectionName, bool required = false);
// returns true if any parent defines a section by the given sectionName
bool IsSectionDefined(string sectionName);
#endregion
// If set true then the Write method HTML encodes the output.
// If set false never any HTML encoding is performed (at this template)
// The default setting can be configured.
bool HtmlEncode { get; set; }
// If generated source code must be included (needs to be configured)
// then that source code can be accessed by this property
// for debug purposes
string GeneratedSourceCode { get; }
// If this template is rendered by another template
// (this instance is a layout or control) then that other template is
// registered as the Parent and can be accessed during execution time
ITemplate Parent { get; }
// Any layout and all controls are registered as childs
// The child list should not be accessed during
// template execution, but only after complete execution, because the child list
// is built during excution
IList<ITemplate> Childs { get; }
// Returns the Root parent, or this instance if no Parent exists.
ITemplate RootOrSelf { get; }
// VirtualLocation is the virtual path without the template name
// and can be used as a starting point for building virtual pathes
VirtualPathBuilder VirtualLocation { get; }
// The actual virtual path that resolved this template
string VirtualPath { get; }
// Context gives access to the template factory and to the razor configuration
RazorContext RazorContext { get; }
// The rendered result
String Result { get; }
// Writes output to the output StringBuilder. It encodes value.ToString()
// if HtmlEncode is true, unless value is a LiteralString instance.
void Write(object value);
// Writes output to the output StringBuilder, never encoded
void WriteLiteral(object value);
// Ensures the value to be written as a raw string.
// Since it returns a (literal) string type you can invoke it
// as @Raw("a & b"), so that you do not need a code block
LiteralString Raw(string value);
}
请注意,Xipton.Razor.LiteralString
表示一个不应被再次编码的字符串(与 MvcHtmlString 类似)。
在内部,该框架依赖于内容提供程序,这些提供程序能够通过虚拟路径解析单个模板内容(即模板脚本)。您为每个单独的模板资源类型初始化内容提供程序。此框架包含文件、嵌入资源和内存内容的内容提供程序。只要它实现了 IContentProvider
,您就可以添加任何其他自定义内容提供程序。
在内容提供程序之上是 ContentManager
。内容管理器决定哪个内容提供程序将响应任何虚拟请求。您可以将 ContentManager
视为一个路由器。但由于其主要任务是组装内容并将其提供给 TemplateFactory
,因此我将其命名为 ContentManager
。
ContentManager
将组装好的模板脚本提供给 TemplateFactory
。模板工厂的任务是创建模板实例。它嵌入了 Razor 和 CodeDom 编译器,并内部管理将模板脚本编译为 .Net 类型的整个过程。它应用 JIT 编译。因此,任何第一次模板请求都会导致脚本编译成模板类型。任何后续相同的请求都会重用编译后的模板类型。任何请求始终返回一个新的模板实例。
以下类图显示了最重要的类和方法。
使用模板文件
为了简单起见,本文顶部的示例 1-10 将模板注册到内存中。RazorMachine
上的便利方法 RegisterTemplate
将模板注册转发给 MemoryContentProvider
。
很可能您想在应用程序文件夹(或子文件夹 ./Views,这是 MVC 标准)中创建模板文件。在您的 Visual Studio 项目中,它可能看起来像这样:
现在您可以通过虚拟路径“~/Reports/Products.cshtml”(或“~/Reports/Products”,因为您始终可以省略默认扩展名)来执行 Products.cshtml。
RazorMachine rm = new RazorMachine();
ITemplate template = rm.ExecuteUrl("~/Reports/Products", new MyProductList());
FileContentProvider
会根据给定的虚拟路径查找文件,并最终将相应的内容提供给模板工厂。请注意,您**必须**将所有模板属性字段“复制到输出目录”设置为“始终复制”或“仅当较新副本时复制”。配置
框架如何初始化和配置?直截了当地说,默认配置如下:
<configuration>
<configSections>
<section name="xipton.razor.config"
type="Xipton.Razor.Config.XmlConfigurationSection, Xipton.Razor" />
</configSections>
<xipton.razor.config>
<xipton.razor>
<rootOperator path="/" /> <!-- same as application name at Asp.Net -->
<!-- if includeGeneratedSourceCode==true the GeneratedSourceCode can be found at ITemplate.GeneratedSourceCode -->
<!-- if htmlEncode==true all strings written by the template method Write(string) are HTML encoded by default.
Note: WriteLiteral(string) will never encode any output -->
<!-- language is C#, VB, or a customized language type name -->
<templates
baseType="Xipton.Razor.TemplateBase"
language="C#"
defaultExtension=".cshtml"
autoIncludeName="_ViewStart"
sharedLocation="~/Shared"
includeGeneratedSourceCode="false"
htmlEncode="true"
/>
<references>
<clear/>
<add reference="mscorlib.dll"/>
<add reference="system.dll"/>
<add reference="system.core.dll"/>
<add reference="microsoft.csharp.dll"/>
<!-- all *.dll assembly references at the execution context are referenced by the compiled template assemblies -->
<add reference="*.dll"/>
<!-- same for all *.exe assemblies -->
<add reference="*.exe"/>
</references>
<namespaces>
<clear/>
<add namespace="System"/>
<add namespace="System.Collections"/>
<add namespace="System.Collections.Generic"/>
<add namespace="System.Dynamic"/>
<add namespace="System.IO"/>
<add namespace="System.Linq"/>
<add namespace="Xipton.Razor.Extension"/>
</namespaces>
<contentProviders>
<clear/>
<!-- by default the file content provider is added -->
<add type="Xipton.Razor.Core.ContentProvider.FileContentProvider" rootFolder="./Views"/>
</contentProviders>
</xipton.razor>
</xipton.razor.config>
</configuration>
关于配置的一些说明
- 如果您根本不进行任何配置,则上述配置代表实际配置。
- 您不需要配置所有设置。任何省略的配置设置都将设置为上述相应的默认设置。
- 如果您不在 references、namespaces 或 contentProviders 中添加 <clear/> 元素,那么您相应的配置设置将添加到默认配置中。
<xipton.razor> <namespaces> <add namespace="MyApp.Utils"/> </namespaces> </xipton.razor>
现在,“MyApp.Utils”命名空间已添加到默认命名空间中,而不是替换默认命名空间。 - 您可以像上面一样将配置包含在您的 app.config 中。您也可以从单独的配置文件名或 XML 字符串加载配置。然后节点 <xipton.razor> 必须是根节点。查看
RazorMachine
初始化程序以了解其工作原理。 - 您还可以先加载默认配置(不进行任何显式配置或从您的 app.config 加载配置节),然后传递可选参数来覆盖(配置的)默认设置,例如:
var RazorMachine = new RazorMachine(includeGeneratedSourceCode:true);
- 我选择不使用所有 Microsoft 配置类来解耦框架的配置,以获得更大的配置灵活性。通过实现 XmlConfigurationSection,框架的配置可以与您的 app.config 集成,如本节顶部所示。
模板引用
您可以在 references add 元素中指定“*.dll”和“*.exe”模式。这意味着分别将当前应用程序域执行上下文中加载的所有 DLL 程序集和 EXE 程序集添加到编译模板的程序集引用中。在查找执行上下文中的所有已加载程序集之前,首先确保应用程序的 bin 文件夹中的所有程序集都已加载到执行上下文中。
public static AppDomain EnsureBinAssembliesLoaded(this AppDomain domain) {
if (_binAssembliesLoadedBefore.ContainsKey(domain.FriendlyName))
return domain;
var binFolder = !string.IsNullOrEmpty(domain.RelativeSearchPath)
? Path.Combine(domain.BaseDirectory, domain.RelativeSearchPath)
: domain.BaseDirectory;
Directory.GetFiles(binFolder, "*.dll")
.Union(Directory.GetFiles(binFolder, "*.exe"))
.ToList()
.ForEach(domain.EnsureAssemblyIsLoaded);
_binAssembliesLoadedBefore[domain.FriendlyName] = true;
return domain;
}
private static void EnsureAssemblyIsLoaded(this AppDomain domain, string assemblyFileName) {
var assemblyName = AssemblyName.GetAssemblyName(assemblyFileName);
if (!domain.GetAssemblies().Any(a => AssemblyName.ReferenceMatchesDefinition(assemblyName, a.GetName()))) {
domain.Load(assemblyName);
}
}
我在版本 2.2 中添加了此预加载行为。因此,您不再需要为 GAC 程序集设置“复制本地”为“True”(尽管这样做也没问题)。否则,请确保相应的 GAC 程序集在执行第一个模板之前已加载到执行上下文中。引用此类程序集中的任何类型都足以将该程序集加载到执行上下文中。
内容提供者
您可以配置一个或多个内容提供程序。这些内容提供程序可以添加到您的代码库中,也可以进行配置。只要它实现了 IContentProvider
,您就可以添加任何自定义内容提供程序。
内容提供程序根据请求,为任何特定资源将模板内容(即模板脚本)提供给内容管理器。内容提供程序必须能够将虚拟路径转换为特定的资源名称、文件名或其他内容。并且它必须通过该特定资源名称提供内容。此外,内容提供程序必须通过引发事件来发布任何模板更改。
内容提供程序通过将相应的配置元素传递给初始化程序 InitFromConfig(XElement element)
来进行初始化。
IContentProvider
接口如下所示:
public interface IContentProvider
{
event EventHandler<ContentModifiedArgs> ContentModified;
string TryGetResourceName(string virtualPath);
string TryGetContent(string resourceName);
IContentProvider InitFromConfig(XElement configuredAddElement);
}
您可以按如下方式配置包含的内容提供程序:<add type="Xipton.Razor.Core.ContentProvider.MemoryContentProvider"/>
<add type="Xipton.Razor.Core.ContentProvider.FileContentProvider"
rootFolder="./Views"/>
<add type="Xipton.Razor.Core.ContentProvider.EmbeddedResourceContentProvider"
resourceAssembly="Xipton.Razor.UnitTest.dll"
rootNameSpace="Xipton.Razor.UnitTest.Views"/>
正如您可能预期的那样,您可以为您的特定提供程序配置定义任何其他属性。整个 XElement 节点 <add type="..." [otherAttributes] />
将传递给提供程序的初始化程序。
该框架使用复合模式将任何 IContentProvider
调用转发到内容提供程序列表。任何第一个具有有效结果(非 null 结果)的转发调用将决定最终结果。此时,进一步转发将停止,并返回获得的结果。因此,内容提供程序的顺序很重要,因为第一个具有有效结果的内容提供程序将决定最终结果,从而覆盖任何后续内容提供程序的结果。
rm.RegisterTemplate("~/_ViewStart.cshtml","... [any content] ..."); // will automatically clear the type cache since it is an autoInclude script
因为调用 RegisterTemplate("~/_ViewStart.cshtml","...[any content]...")
将模板注册到 MemoryContentProvider
,并且该提供程序位于 FileContentProvider
之上,所以任何执行请求都将加载内存中的 _ViewStart,而不是文件中的 _ViewStart,从而覆盖相应的文件内容。这种行为提供了优先处理内容的可能性,从而允许额外的(手动)配置,例如更改布局设置(在 _ViewStart 中)。或者,您可以创建作为嵌入资源的默认模板,同时允许通过创建具有相同相应虚拟路径的文件的来“覆盖”模板。
@model 指令
该框架能够处理 @model 指令(VB 中的 ModelType)。因此,我不得不稍微扩展 C# 和 VB 的 Razor 解析器。您可以在Xipton.Razor.Core.Generator
中找到相应的代码库。因为该框架支持 @model 指令,所以您可以将其与 Visual Studio 的智能感知一起使用。当然,您也可以使用 @inherits 指令,因为该指令本身就是 Razor 的一部分。智能感知。
要使 Razor 智能感知在非 MVC 项目中工作,首先请确保您满足 MVC 在文件扩展名方面的约定,即 C# 为 .cshtml,VB 为 .vbhtml。接下来,将名为 web.config 的配置文件(包含在 Xipton.Razor 项目中)复制到您的非 MVC 模板项目中。您必须将其命名为“web.config”,否则将不起作用。将 web.config 文件放在 app.config 文件旁边。现在您的智能感知应该可以在 Visual Studio 中工作了。请阅读包含的 web.config 中的注释,了解如何针对引用 Xipton.Razor.csproj 项目或引用已签名的 Xipton.Razor.dll 的不同场景配置智能感知。
如果您在 MVC 项目中使用 Xipton.Razor,则需要在您的 MVC app.config 中包含 Xipton.Razor.dll 程序集引用。在您的模板中,您必须使用 @inherits 指令,而不是 @model 指令。有关详细信息,请参阅 web.config 中的注释。
创建自己的自定义模板基类
您可以继承包含的 TemplateBase
并将您的自定义子类配置为新的默认模板基类。为了使其正常工作,您需要创建两个类。现在,我们将您的新基类命名为 MyTemplateBase
。
首先,您必须让 MyTemplateBase
直接继承自 TemplateBase
。在此类中尽可能多地添加非类型化模型实现。
其次,您必须创建另一个名为 MyTemplateBase<TModel>
(与基类同名)的子类,该子类继承 MyTemplateBase
并实现 ITemplateBase<TModel>
。所以您的代码将如下所示:
public class MyTemplateBase : TemplateBase
{
// your base implementation goed here
}
public class MyTemplateBase<TModel> : MyTemplateBase, ITemplate<TModel> {
#region Implementation of ITemplate<out TModel>
public new TModel Model{
get { return (TModel)base.Model; }
}
#endregion
// your typed implementation goed here
}
现在您可以将 typeof(MyTemplateBase)
配置为您的默认模板基类。
错误报告
我特别关注了与解析错误、编译错误和运行时绑定错误相关的错误报告,并试图使这些错误报告尽可能清晰。错误消息尽可能包含资源名称和相应的代码行。您将收到用于解析、编译和运行时绑定的单独异常类型。
性能
第一次运行模板和再次运行同一模板之间存在相当大的性能差异。我测试了 200 个模板,每个模板写入一个 GUID 字符串。第一次运行这些模板大约需要 10 秒。再次运行相同的模板则不到 1 毫秒,速度快了 10000 倍!
因此,由于需要多个编译步骤,首次执行请求时的模板编译速度相当慢。一旦您的模板编译完成,性能就非常好。
最后
通过本文,我将对我的 RazorMachine 进行一个全面的概述,让您有机会决定 RazorMachine 是否对您有价值。我跳过了很多细节。要了解详细信息,您应该查看完整的源代码。我还添加了一些单元测试。尽情享用!
历史
- 2012/07/17 - 首次发布
- 2012/08/21 - v2.0
- 升级到已发布的 Razor 2(已发布的 MVC 4 的一部分)
- 将 TemplateProvider 重命名为 ContentManager
- 在 RazorMachine 中添加了名为 Execute 的方法,用于执行内容和 URL。
- 修复了使用锚点时的 URL 问题
- 修复了同时安装了 MVC 3 和 MVC 4 的系统上的智能感知问题
- 其他小改进
- 2012/09/27 - v2.1:修复了解析不同场景的引用的错误
- 2012/10/01 - v2.1a:内容更新和代码库注释更新
- 2012/10/24 - v2.2
- 改进了引用解析。不再需要为 GAC 引用设置“复制本地”为 True
- 重构了 RazorConfig 类
- 移除了 TemplateBase 中的 br-field(可以在 _viewStart 中声明)
- 源代码中的其他小改动,例如警告抑制
- 2012/10/31 - v2.3
- 改进了 EmbeddedResourceContentProvider 中的程序集加载
- 将代码库迁移到 GitHub
- 2012/11/17 - v2.4
- bug修复:属性中的空格现在得以保留
- bug修复:现在可以处理嵌套的匿名类型
- 2013/01/25 - v2.5
- 添加了对 @helper 的支持
- 任何未托管的 dll 都被忽略,不再抛出异常
- 配置改进