Rendering an email throws a TemplateCompilationException using RazorEngine 3 in a non-MVC project - razor

I am trying to render emails in a windows service host.
I use RazorEngine 3 forked by coxp which has support for Razor 2.
https://github.com/coxp/RazorEngine/tree/release-3.0/src
This works fine for a couple of emailtemplates but there is one causing me problems.
#model string
Click here to enter a new password for your account.
This throws a CompilationException: The name 'WriteAttribute' does not exist in the current context. So passing in a string as model and putting it in the href-attribute causes problems.
I can make it work by changing this line by:
#Raw(string.Format("Klik hier.", #Model))
but this makes the template very unreadable and harder to pass along to a marketing department for further styling.
I like to add that referencing the RazorEngine by using a Nuget package is not a solution since it is based on Razor 1 and somewhere along the process the DLL for system.web.razor gets replaced by version 2 which breaks any code using RazorEngine. It seems more interesting to use Razor 2 to benefit from the new features and to be up to date.
Any suggestions on how to fix this would be great. Sharing your experiences is also very welcome.
UPDATE 1
It seems like calling SetTemplateBaseType might help, but this method does not exist anymore, so I wonder how to be able to bind the templatebasetype?
//Missing method in the new RazorEngine build from coxp.
Razor.SetTemplateBaseType(typeof(HtmlTemplateBase<>));

I use Windsor to inject the template service rather than using the Razor object. Here is a simplified part of the code that shows how to set the base template type.
private static ITemplateService CreateTemplateService()
{
var config = new TemplateServiceConfiguration
{
BaseTemplateType = typeof (HtmlTemplateBase<>),
};
return new TemplateService(config);
}

RazorEngine 3.1.0
Little bit modified example based on coxp answer without the injection:
private static bool _razorInitialized;
private static void InitializeRazor()
{
if (_razorInitialized) return;
_razorInitialized = true;
Razor.SetTemplateService(CreateTemplateService());
}
private static ITemplateService CreateTemplateService()
{
var config = new TemplateServiceConfiguration
{
BaseTemplateType = typeof (HtmlTemplateBase<>),
};
return new TemplateService(config);
}
public static string ParseTemplate(string name, object model)
{
InitializeRazor();
var appFileName = "~/EmailTemplates/" + name + ".cshtml";
var template = File.ReadAllText(HttpContext.Current.Server.MapPath(appFileName));
return RazorEngine.Razor.Parse(template, model);
}

Related

Razor engine cant find view

I'm trying to render a HTML from a view without using a web request. I need the HTML as a string, internally, I do not wish to serve it.
The viewEngine.FindView() returns a viewEnineResult that shows no view was found. It shows to search locations where it looked they look like this:
/Views//PDFOperationsReportView.cshtml
/Views/Shared/PDFOperationsReportView.cshtml
(Observe the double forward slash in the first line)
File structure (I placed it into a HTML snippet cause I couldn't manage to format the text properly in this editor)
Project
Folder
Subfolder
CodeFile.cs
Views
PDFOperationsReportView.cshtml
The code:
var viewName = "PDFOperationsReportView";
var actionContext = GetActionContext();
var viewEngineResult = _viewEngine.FindView(actionContext, viewName, false);
if (!viewEngineResult.Success)
{
throw new InvalidOperationException(string.Format("Couldn't find view '{0}'", viewName));
}
var view = viewEngineResult.View;
I had the same issue. I found the answer here: GitHub aspnet/Mvc Issue #4936
Basically, use GetView instead of FindView, like this:
var viewResult = razorViewEngine.GetView(viewName, viewName, false);
Your viewName needs to be a full path for this to work. For example:
/Views/Shared/PDFOperationsReportView.cshtml
~/Pages/Shared/_Article.cshtml
~/Areas/CM/Pages/_Article.cshtml
We have a helper method defined to render optional views which may or may not exist:
public static Task RenderPartialAsyncIfExists(this IHtmlHelper htmlHelper, ICompositeViewEngine engine, string partialViewName, object model)
{
if (engine.GetView(partialViewName, partialViewName, false).Success)
{
return htmlHelper.RenderPartialAsync(partialViewName, model);
}
return Task.CompletedTask;
}
It's used on view pages like:
#inject ICompositeViewEngine Engine
...
#{ await Html.RenderPartialAsyncIfExists(Engine, $"~/Views/Shared/_navigationAdmin.cshtml"); }
This works find locally (IIS Express) but for some reason was failing when deployed to IIS.
In my case, there was something wrong with the .csproj file, where the view in question was removed but then re-added as an embedded resource:
<ItemGroup>
<Content Remove="Views\Shared\_navigationAdmin.cshtml" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Views\Shared\_navigationAdmin.cshtml" />
</ItemGroup>
Removing those two sections from the .csproj fixed the problem in IIS.
This is using (EOL) AspNet Core 2.2

How do I allow an MIME extension map in ASP.NET vNext?

Background
I have a piece of LESS code that needs to be compiled at runtime with Less.js -- it calculates some things via JavaScript -- so I can't use the task runner, etc.
In my index.html, I have:
<head>
...
<link rel="stylesheet/less" href="assets/less/DynamicHeight.less" />
...
<script type="text/javascript" src="lib/less/less.js"></script>
...
</head>
Problem
Less.js appears unable to find the file:
And when I try to access the file directly, I see:
Question
How can I add the configuration that will allow this less file to be downloaded? Am I still able to use web.config files with vNext, or do I need to do something with config.json instead?
Lead 1: Should I use Owin?
Thinking this might be the right path but I'm pretty unfamiliar.
I see a number of tutorials out there, such as K. Scott Allen's, which reference code such as:
public void Configuration(IAppBuilder app)
{
var options = new StaticFileOptions
{
ContentTypeProvider = new FileExtensionContentTypeProvider()
};
((FileExtensionContentTypeProvider)options.ContentTypeProvider).Mappings.Add(
new KeyValuePair<string, string>(".less", "text/css"));
app.UseStaticFiles(options);
}
However, it appears that in its current version, asp.net is looking for a signature of Configure(IApplicationBuilder app) instead.
The IApplicationBuilder class doesn't have a method along the lines of UseStaticFiles -- it only has a signature of IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware).
I have a feeling that this is likely the right path to solve the issue -- I just can't find out how to propertly configure the IAppliationBuilder to map the MIME extension.
Okay, I believe I figured it out.
Step 1: Add the appropriate library for static files
In ASP.NET vNext, this is Microsoft.Aspnet.StaticFiles.
In your project.json file, add the following under "dependencies":
"Microsoft.AspNet.StaticFiles": "1.0.0-beta2"
This adds the static middleware method that you can use later.
Step 2: Configure the app to use Static Files
Add the using statement at the top:
using Microsoft.AspNet.StaticFiles;
At this point, the app.UseStaticFiles method will be available, so your Configure method can look as follows:
public void Configure(IApplicationBuilder app)
{
var options = new StaticFileOptions
{
ContentTypeProvider = new FileExtensionContentTypeProvider()
};
((FileExtensionContentTypeProvider)options.ContentTypeProvider).Mappings.Add(
new KeyValuePair<string, string>(".less", "text/css"));
app.UseStaticFiles(options);
}
And voila! I get text when browsing to .less files, and no more error is appearing from LessJS.
In .NET Core 1.0.1, SeanKileen answer is still good. The following is a simple code rewrite:
public void Configure(IApplicationBuilder app, ...)
var contentTypeProvider = new FileExtensionContentTypeProvider();
contentTypeProvider.Mappings[".map"] = "application/javascript";
contentTypeProvider.Mappings[".less"] = "text/css";
app.UseStaticFiles(new StaticFileOptions()
{
ContentTypeProvider = contentTypeProvider
});
The above code EXTENDS the default mapping list (see the source), which already has ~370 mappings.
Avoid using the FileExtensionContentTypeProvider constructor overload that takes a dictionary (as suggested by JHo) if you want those 370 default mappings.
SeanKilleen's answer is right on, and still works ASP.NET Core RC1. My only improvement is to write the exact same code using collection initializers to make it cleaner.
app.UseStaticFiles(new StaticFileOptions
{
ContentTypeProvider = new FileExtensionContentTypeProvider(new Dictionary<string, string>
{
{ ".less", "text/css" },
{ ".babylon", "text/json" },
// ....
})
});

Best Way to keep Settings for a WinRT App?

I'm working on a WinRT app that's actually also a game. I need to keep different information such as audio settings or player statistics somewhere in sort of a file or somehow. If it's a file, just write settings in or... ? I have an idea but I think is way too rudimentary... What is the best approach to obtain this?
Any help or suggestions are greatly appreciated!
Here are some ways to save Data in a WinRT app, the method with Settings in the name is probably what you are looking for!- just added the other ones as well,- you also can serialize data if you want to. This is working code- but don't forget to add error handling etc. It's a simple demo code :)
As for settings, you can save simple settings as key and values, and for more complex settings you can use a container. I've provided both examples here =)
public class StorageExamples
{
public async Task<string> ReadTextFileAsync(string path)
{
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.GetFileAsync(path);
return await FileIO.ReadTextAsync(file);
}
public async void WriteTotextFileAsync(string fileName, string contents)
{
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(file, contents);
}
public void SaveSettings(string key, string contents)
{
ApplicationData.Current.LocalSettings.Values[key] = contents;
}
public string LoadSettings(string key)
{
var settings = ApplicationData.Current.LocalSettings;
return settings.Values[key].ToString();
}
public void SaveSettingsInContainer(string user, string key, string contents)
{
var localSetting = ApplicationData.Current.LocalSettings;
localSetting.CreateContainer(user, ApplicationDataCreateDisposition.Always);
if (localSetting.Containers.ContainsKey(user))
{
localSetting.Containers[user].Values[key] = contents;
}
}
}
The MSDN has an article on using app settings in Windows Store apps.
The Windows.UI.ApplicationSettings namespace contains all the classes you need.
Provides classes that allow developers to define the app settings that appear in the settings pane of the Windows shell. The settings pane provides a consistent place for users to access app settings.
Basically these classes let you store application settings and hook them into the standard place for all application settings. Your users don't have to learn anything new, the settings will be in the expected place.

Is it possible to set breakpoints in razor views with servicestack?

I am trying out the new razor view stuff in service stack, and I have this view:
#inherits ServiceStack.Razor.ViewPage<ServiceStackRazorCrud.Api.UserPageResourceResponse>
#{
var m = Model; // <-- I have a breakpoint in this line.
var req = Request;
var res = Response;
}
When I set a breakpoints and run the application (console application) I can see that the view is compiled but the debugger does not break when I request the view in the browser. I assume that this is because the views are compiled dynamically at application start or something like that. Is it possible somehow to get the breakpoints to work?
AFAIK it's not possible to debug views this way (currently using 3.9.43, later version I believe has better diagnostics for compilation errors).
Try and keep view code simple, restricted to simple loops/rendering and using extension methods on DTO's for any complex logic/processing, which do allow debugging. You might also consider utilizing logging, or a simple Debug extension method:
using ServiceStack.Html;
public static class HtmlHelperExtensions
{
public static bool IsDebug(this HtmlHelper htmlHelper)
{
#if DEBUG
return true;
#else
return false;
#endif
}
}
#using ServiceStack.Text
#inherits ServiceStack.Razor.ViewPage<ServiceStackRazorCrud.Api.UserPageResourceResponse>
#{
var m = Model;
}
#if (this.Html.IsDebug())
{
<div class="debug">#(this.Model == null ? "m == null" : Model.Dump())</div>
}

How does one cshtml call a function in another cshtml?

In webmatrix I have created a DAL.cshtml in App_Data with lots of public static functions.
I want to call them from a different .cshtml page in a different folder.
Presently I get
Compiler Error Message: CS0103: The name DAL does not exist in the current context
(note I'm a beginner following a blog about moving the data access to a single location)
In my DAL.cshtml an example is:
public static void AddProfile (dynamic Profile)
{
var sql = "INSERT INTO profile (ProfileDescription, ProfileType) " +
"VALUES (#0, #1)";
PinwheelDB.Execute(sql, Profile.ProfileDescription, Profile.ProfileType);
var Profile.ProfileID = PinwheelDB.GetLastInsertId();
}
In my 'calling' .cshtml I have:
dynamic Profile = new ExpandoObject();
Profile.ProfileDescription = Request.Form["txtChildFirstName"];
Profile.ProfileType = 1;
functions.DAL.AddProfile(Profile);
var vProfileID = Profile.ProfileID;
Once I moved the DAL.cshtml into a folder called App_Code instead of App_Data (Thank-you Darin) it just worked.
Please note that the code I have ended up with is as follows (in case it helps another beginner)........
DAL.cshtml now in a folder called App_Code I have:
#functions
{
public static void AddProfile (dynamic Profile)
{
var sql = "......"
PinwheelDB.Execute(sql, .......);
}
The calling .cshtml
remains as I first quoted
If anyone knows why this is I'd like to understand.
You could put commonly used methods in classes in the App_Code folder. For example you could have a ~/App_Code/DAL.cs:
public class DAL
{
public static void AddProfile(dynamic profile)
{
...
}
}
and in your calling Razor page:
#{
var profile = ...
DAL.AddProfile(profile);
}