我有一个使用显示模板生成HTML的实用工具方法:
public static MvcHtmlString MyMethod(this HtmlHelper html)
{
var model = new Model();
var viewDataContainer = new ViewDataContainer<INode>(model);
var modelHtmlHelper = new HtmlHelper<INode>(html.ViewContext,
viewDataContainer);
return modelHtmlHelper.DisplayFor(node => node, "TemplateName");
}
我正在尝试编写测试来验证其行为。到目前为止,我想出了:
public class when_extension_method_is_used
{
static MvcHtmlString output;
Because of = () =>
{
var httpContext = new Mock<HttpContextBase>();
httpContext.SetupGet(hc => hc.Items).Returns(new ListDictionary());
var routeData = new RouteData();
routeData.Values.Add("controller", "Test");
var viewContext = new ViewContext
{
RouteData = routeData,
HttpContext = httpContext.Object,
ViewData = new ViewDataDictionary()
};
var viewDataContainer = new ViewPage();
var htmlHelper = new HtmlHelper(viewContext, viewDataContainer);
output = htmlHelper.MyMethod();
};
It should_just_work =
() => output.ToString().ShouldEqual("<blink></blink>");
}
}
这不起作用。我得到NullReferenceException
at:
at System.Web.Compilation.BuildManager.GetVPathBuildResultFromCacheInternal(VirtualPath virtualPath, Boolean ensureIsUpToDate)
at System.Web.Compilation.BuildManager.GetVPathBuildResultInternal(VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile, Boolean throwIfNotFound, Boolean ensureIsUpToDate)
at System.Web.Compilation.BuildManager.GetVPathBuildResultWithNoAssert(HttpContext context, VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile, Boolean throwIfNotFound, Boolean ensureIsUpToDate)
at System.Web.Compilation.BuildManager.GetObjectFactory(String virtualPath, Boolean throwIfNotFound)
at System.Web.Mvc.BuildManagerWrapper.System.Web.Mvc.IBuildManager.FileExists(String virtualPath) in BuildManagerWrapper.cs: line 8
at System.Web.Mvc.BuildManagerViewEngine.FileExists(ControllerContext controllerContext, String virtualPath) in BuildManagerViewEngine.cs: line 42
at System.Web.Mvc.VirtualPathProviderViewEngine.GetPathFromGeneralName(ControllerContext controllerContext, List`1 locations, String name, String controllerName, String areaName, String cacheKey, String[]& searchedLocations) in VirtualPathProviderViewEngine.cs: line 180
at System.Web.Mvc.VirtualPathProviderViewEngine.GetPath(ControllerContext controllerContext, String[] locations, String[] areaLocations, String locationsPropertyName, String name, String controllerName, String cacheKeyPrefix, Boolean useCache, String[]& searchedLocations) in VirtualPathProviderViewEngine.cs: line 167
at System.Web.Mvc.VirtualPathProviderViewEngine.FindPartialView(ControllerContext controllerContext, String partialViewName, Boolean useCache) in VirtualPathProviderViewEngine.cs: line 113
at System.Web.Mvc.ViewEngineCollection.<>c__DisplayClass8.<FindPartialView>b__7(IViewEngine e) in ViewEngineCollection.cs: line 97
at System.Web.Mvc.ViewEngineCollection.Find(Func`2 lookup, Boolean trackSearchedPaths) in ViewEngineCollection.cs: line 66
at System.Web.Mvc.ViewEngineCollection.Find(Func`2 cacheLocator, Func`2 locator) in ViewEngineCollection.cs: line 48
at System.Web.Mvc.ViewEngineCollection.FindPartialView(ControllerContext controllerContext, String partialViewName) in ViewEngineCollection.cs: line 96
at System.Web.Mvc.Html.TemplateHelpers.ExecuteTemplate(HtmlHelper html, ViewDataDictionary viewData, String templateName, DataBoundControlMode mode, GetViewNamesDelegate getViewNames, GetDefaultActionsDelegate getDefaultActions) in TemplateHelpers.cs: line 66
at System.Web.Mvc.Html.TemplateHelpers.TemplateHelper(HtmlHelper html, ModelMetadata metadata, String htmlFieldName, String templateName, DataBoundControlMode mode, Object additionalViewData, ExecuteTemplateDelegate executeTemplate) in TemplateHelpers.cs: line 239
at System.Web.Mvc.Html.TemplateHelpers.TemplateHelper(HtmlHelper html, ModelMetadata metadata, String htmlFieldName, String templateName, DataBoundControlMode mode, Object additionalViewData) in TemplateHelpers.cs: line 192
at System.Web.Mvc.Html.TemplateHelpers.TemplateFor(HtmlHelper`1 html, Expression`1 expression, String templateName, String htmlFieldName, DataBoundControlMode mode, Object additionalViewData, TemplateHelperDelegate templateHelper) in TemplateHelpers.cs: line 181
at System.Web.Mvc.Html.TemplateHelpers.TemplateFor(HtmlHelper`1 html, Expression`1 expression, String templateName, String htmlFieldName, DataBoundControlMode mode, Object additionalViewData) in TemplateHelpers.cs: line 174
at System.Web.Mvc.Html.DisplayExtensions.DisplayFor(HtmlHelper`1 html, Expression`1 expression, String templateName) in DisplayExtensions.cs: line 43
例外的来源是System.Web.VirtualPath.GetCacheKey()
:
public string GetCacheKey()
{
// VirtualPathProvider property is null
return HostingEnvironment.VirtualPathProvider.GetCacheKey(this);
}
HostingEnvironment.VirtualPathProvider
?答案 0 :(得分:1)
创建了一种解决方法。它很丑陋,违反了最佳做法,但有效。
1)将扩展性挂钩添加到扩展方法类:
public static class MyExtensionMethods
{
static MyExtensionMethods()
{
Renderer = (html, model) =>
{
// this is the default implementation that will be used by MVC runtime
var viewDataContainer = new ViewDataContainer<INode>(model);
var modelHtmlHelper = new HtmlHelper<INode>(html.ViewContext, viewDataContainer);
return modelHtmlHelper.DisplayFor(node => node, "TemplateName");
};
}
public static Func<HtmlHelper, INode, MvcHtmlString> Renderer { get; set; }
public static MvcHtmlString Menu(this HtmlHelper html)
{
var model = new Model();
return Renderer(html, model);
}
}
2)使用自托管Razor引擎执行模板:
using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web.Razor;
using Microsoft.CSharp;
namespace YourNamespace.Specifications.SpecUtils
{
public sealed class InMemoryRazorEngine
{
public static ExecutionResult Execute<TModel>(string razorTemplate, TModel model, params Assembly[] referenceAssemblies)
{
var razorEngineHost = new RazorEngineHost(new CSharpRazorCodeLanguage());
razorEngineHost.DefaultNamespace = "RazorOutput";
razorEngineHost.DefaultClassName = "Template";
razorEngineHost.NamespaceImports.Add("System");
razorEngineHost.DefaultBaseClass = typeof(RazorTemplateBase<TModel>).FullName;
var razorTemplateEngine = new RazorTemplateEngine(razorEngineHost);
using (var template = new StringReader(razorTemplate))
{
var generatorResult = razorTemplateEngine.GenerateCode(template);
var compilerParameters = new CompilerParameters();
compilerParameters.GenerateInMemory = true;
compilerParameters.ReferencedAssemblies.Add(typeof(InMemoryRazorEngine).Assembly.Location);
if (referenceAssemblies != null)
{
foreach (var referenceAssembly in referenceAssemblies)
{
compilerParameters.ReferencedAssemblies.Add(referenceAssembly.Location);
}
}
var codeProvider = new CSharpCodeProvider();
var compilerResult = codeProvider.CompileAssemblyFromDom(compilerParameters, generatorResult.GeneratedCode);
var compiledTemplateType = compilerResult.CompiledAssembly.GetExportedTypes().Single();
var compiledTemplate = Activator.CreateInstance(compiledTemplateType);
var modelProperty = compiledTemplateType.GetProperty("Model");
modelProperty.SetValue(compiledTemplate, model, null);
var executeMethod = compiledTemplateType.GetMethod("Execute");
executeMethod.Invoke(compiledTemplate, null);
var builderProperty = compiledTemplateType.GetProperty("OutputBuilder");
var outputBuilder = (StringBuilder)builderProperty.GetValue(compiledTemplate, null);
var runtimeResult = outputBuilder.ToString();
return new ExecutionResult(generatorResult, compilerResult, runtimeResult);
}
}
#region Nested type: ExecutionResult
public sealed class ExecutionResult
{
public ExecutionResult(GeneratorResults generatorResult, CompilerResults compilerResult, string runtimeResult)
{
GeneratorResult = generatorResult;
CompilerResult = compilerResult;
RuntimeResult = runtimeResult;
}
public GeneratorResults GeneratorResult { get; private set; }
public CompilerResults CompilerResult { get; private set; }
public string RuntimeResult { get; private set; }
}
#endregion
#region Nested type: RazorTemplateBase
public abstract class RazorTemplateBase<TModel>
{
protected RazorTemplateBase()
{
OutputBuilder = new StringBuilder();
}
public TModel Model { get; set; }
public StringBuilder OutputBuilder { get; private set; }
public abstract void Execute();
public virtual void Write(object value)
{
OutputBuilder.Append(value);
}
public virtual void WriteLiteral(object value)
{
OutputBuilder.Append(value);
}
}
#endregion
}
}
3)在测试中覆盖默认扩展方法Renderer
public class when_extension_method_is_used
{
static MvcHtmlString output;
Because of = () =>
{
var htmlHelper = new HtmlHelper(new ViewContext(), new ViewPage());
MyExtensionMethods.Renderer = (html, model) =>
{
const string template = "<blink>@Model</blink>";
var executionResult = InMemoryRazorEngine.Execute(template, model);
return new MvcHtmlString(executionResult.RuntimeResult);
};
output = htmlHelper.MyMethod();
};
It should_just_work =
() => output.ToString().ShouldEqual("<blink>make UX experts cry!</blink>");
}
}
备注:强>
RazorTemplateBase