Skip to content

Instantly share code, notes, and snippets.

@mbcrawfo
Created December 18, 2015 04:20
Show Gist options
  • Save mbcrawfo/2269f5a1a402792ff2d8 to your computer and use it in GitHub Desktop.
Save mbcrawfo/2269f5a1a402792ff2d8 to your computer and use it in GitHub Desktop.
Layouts for Postal outside of ASP.Net
// Temporary implementation until this PR is integrated into Postal:
// https://github.com/andrewdavey/postal/pull/117
internal class FileSystemWithLayoutsRazorView
: IView
{
private readonly IRazorEngineService m_razorService;
private readonly string m_cacheName;
/// <summary>
/// Creates a new <see cref="FileSystemRazorView"/> using the
/// given view filename.
/// </summary>
/// <param name="razorService">
/// The RazorEngine IRazorEngineService to use to render the view
/// </param>
/// <param name="filename">
/// The filename of the view.
/// </param>
public FileSystemWithLayoutsRazorView(IRazorEngineService razorService,
string filename)
{
m_razorService = razorService;
m_cacheName = filename;
}
/// <summary>
/// Renders the view into the given <see cref="TextWriter"/>.
/// </summary>
/// <param name="viewContext">
/// The <see cref="ViewContext"/> that contains the view data
/// model.
/// </param>
/// <param name="writer">
/// The <see cref="TextWriter"/> used to write the rendered output.
/// </param>
public void Render(ViewContext viewContext, TextWriter writer)
{
m_razorService.RunCompile(m_cacheName, writer, null,
viewContext.ViewData.Model);
}
}
/// <summary>
/// A Razor view engine configured to work outside of ASP.Net.
/// </summary>
internal class FileSystemWithLayoutsRazorViewEngine
: IViewEngine
{
private readonly string m_viewPathRoot;
private readonly IRazorEngineService m_razorService;
/// <summary>
/// Create the view engine.
///
/// Namespaces and libraries to be used during Razor compilation
/// are loaded from the Web.config in the root of the email
/// view directory.
/// </summary>
/// <param name="viewPathRoot"></param>
public FileSystemWithLayoutsRazorViewEngine(string viewPathRoot)
{
m_viewPathRoot = viewPathRoot;
var razorConfig = new TemplateServiceConfiguration
{
TemplateManager = new DelegateTemplateManager(ResolveTemplate),
ReferenceResolver = new ReferenceResolver()
};
var webConfigPath = Path.Combine(viewPathRoot, "Web.config");
if (File.Exists(webConfigPath))
{
var xml = XDocument.Parse(File.ReadAllText(webConfigPath));
Debug.Assert(xml.Root != null);
var namespaces = xml.Root.Descendants("namespaces")
.SelectMany(n => n.Elements("add"))
.Select(e => e.Attribute("namespace").Value);
foreach (var ns in namespaces)
{
razorConfig.Namespaces.Add(ns);
}
var libraries = xml.Root.Descendants("libraries")
.SelectMany(e => e.Elements("add"))
.Select(e => e.Attribute("dll").Value);
((ReferenceResolver) razorConfig.ReferenceResolver)
.AddLibraries(libraries);
}
m_razorService = RazorEngineService.Create(razorConfig);
}
/// <summary>
/// Tries to find a razor view (.cshtml or .vbhtml files).
/// </summary>
public ViewEngineResult FindPartialView(
ControllerContext controllerContext, string partialViewName,
bool useCache)
{
IEnumerable<string> searchedPaths;
var existingPath = ResolveTemplatePath(partialViewName,
out searchedPaths);
if (existingPath != null)
{
return new ViewEngineResult(
new FileSystemWithLayoutsRazorView(m_razorService, existingPath),
this);
}
return new ViewEngineResult(searchedPaths);
}
/// <summary>
/// Tries to find a razor view (.cshtml or .vbhtml files).
/// </summary>
public ViewEngineResult FindView(ControllerContext controllerContext,
string viewName, string masterName, bool useCache)
{
return FindPartialView(controllerContext, viewName, useCache);
}
/// <summary>
/// Does nothing.
/// </summary>
public void ReleaseView(ControllerContext controllerContext,
IView view)
{
// Nothing to do here - FileSystemRazorView does not need disposing.
}
private string GetViewFullPath(string path)
{
return Path.Combine(m_viewPathRoot, path);
}
private string ResolveTemplate(string viewName)
{
var path = ResolveTemplatePath(viewName);
if (path == null)
{
return null;
}
return File.ReadAllText(path);
}
private string ResolveTemplatePath(string viewName)
{
IEnumerable<string> searchedPaths;
var existingPath = ResolveTemplatePath(viewName, out searchedPaths);
return existingPath;
}
private string ResolveTemplatePath(string viewName,
out IEnumerable<string> searchedPaths)
{
var possibleFilenames = new List<string>();
var needsExtension =
!viewName.EndsWith(".cshtml",
StringComparison.OrdinalIgnoreCase) &&
!viewName.EndsWith(".vbhtml", StringComparison.OrdinalIgnoreCase);
if (needsExtension)
{
possibleFilenames.Add(viewName + ".cshtml");
possibleFilenames.Add(viewName + ".vbhtml");
}
else
{
possibleFilenames.Add(viewName);
}
var possibleFullPaths = possibleFilenames
.Select(GetViewFullPath)
.ToArray();
var existingPath = possibleFullPaths.FirstOrDefault(File.Exists);
searchedPaths = possibleFullPaths;
return existingPath;
}
}
internal class HttpContextSafeViewEngineCollection
: ViewEngineCollection
{
public HttpContextSafeViewEngineCollection()
{
// Horrible reflection based hack to pass in custom dependency
// resolver. This is needed otherwise ViewEngineCollection uses
// global Autofac MVC resolver, which requires a HttpContext.Current,
// which doesn't work from background thread.
// http://discuss.hangfire.io/t/hangfire-job-throws-autofac-exception-even-though-job-class-is-not-managed-by-autofac/488/5
var resolverField = typeof(ViewEngineCollection).GetField(
"_dependencyResolver",
BindingFlags.NonPublic | BindingFlags.Instance);
Debug.Assert(resolverField != null);
var resolver = new DefaultResolver();
resolverField.SetValue(this, resolver);
}
private class DefaultResolver
: IDependencyResolver
{
public object GetService(Type serviceType)
{
return null;
}
public IEnumerable<object> GetServices(Type serviceType)
{
return Enumerable.Empty<object>();
}
}
}
/// <summary>
/// Resolves library references for the RazorEngine compiler.
/// </summary>
internal class ReferenceResolver
: IReferenceResolver
{
private string[] m_libraries;
/// <summary>
/// Create a resolver to handle a list of library references.
/// </summary>
/// <param name="libraries"></param>
public ReferenceResolver(params string[] libraries)
{
if (libraries == null)
throw new ArgumentNullException("libraries");
m_libraries = libraries;
}
/// <summary>
/// Add libraries to the resolver.
/// </summary>
/// <param name="libraries"></param>
public void AddLibraries(IEnumerable<string> libraries)
{
m_libraries = m_libraries.Union(libraries).ToArray();
}
public IEnumerable<CompilerReference> GetReferences(
TypeContext context,
IEnumerable<CompilerReference> includeAssemblies = null)
{
// get the paths of all currently loaded assemblies
var loadedAssemblies =
(new UseCurrentAssembliesReferenceResolver())
.GetReferences(context, includeAssemblies)
.Select(r => r.GetFile())
.ToList();
return m_libraries
.Select(lib => CompilerReference.From(
FindLoadedAssembly(loadedAssemblies, lib)));
}
/// <summary>
/// Given a list of loaded assembly file references, finds and
/// returns the path matching the requested assembly name.
/// </summary>
/// <param name="references"></param>
/// <param name="name"></param>
/// <returns></returns>
private string FindLoadedAssembly(
IEnumerable<string> references, string name)
{
return references.First(r =>
{
var filename = Path.GetFileName(r);
Debug.Assert(filename != null);
return filename.Equals(name,
StringComparison.CurrentCultureIgnoreCase);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment