NVelocity not finding the template - asp.net

I'm having some difficulty with using NVelocity in an ASP.NET MVC application. I'm using it as a way of generating emails.
As far as I can make out the details I'm passing are all correct, but it fails to load the template.
Here is the code:
private const string defaultTemplatePath = "Views\\EmailTemplates\\";
...
velocityEngine = new VelocityEngine();
basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, defaultTemplatePath);
ExtendedProperties properties = new ExtendedProperties();
properties.Add(RuntimeConstants.RESOURCE_LOADER, "file");
properties.Add(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, basePath);
velocityEngine.Init(properties);
The basePath is the correct directory, I've pasted the value into explorer to ensure it is correct.
if (!velocityEngine.TemplateExists(name))
throw new InvalidOperationException(string.Format("Could not find a template named '{0}'", name));
Template result = velocityEngine.GetTemplate(name);
'name' above is a valid filename in the folder defined as basePath above. However, TemplateExists returns false. If I comment that conditional out and let it fail on the GetTemplate method call the stack trace looks like this:
at NVelocity.Runtime.Resource.ResourceManagerImpl.LoadResource(String resourceName, ResourceType resourceType, String encoding)
at NVelocity.Runtime.Resource.ResourceManagerImpl.GetResource(String resourceName, ResourceType resourceType, String encoding)
at NVelocity.Runtime.RuntimeInstance.GetTemplate(String name, String encoding)
at NVelocity.Runtime.RuntimeInstance.GetTemplate(String name)
at NVelocity.App.VelocityEngine.GetTemplate(String name)
...
I'm now at a bit of an impasse. I feel that the answer is blindingly obvious, but I just can't seem to see it at the moment.

Have you considered using Castle's NVelocityTemplateEngine?
Download from the "TemplateEngine Component 1.1 - September 29th, 2009" section and reference the following assemblies:
using Castle.Components.Common.TemplateEngine.NVelocityTemplateEngine;
using Castle.Components.Common.TemplateEngine;
Then you can simply call:
using (var writer = new StringWriter())
{
_templateEngine.Process(data, string.Empty, writer, _templateContents);
return writer.ToString();
}
Where:
_templateEngine is your NVelocityTemplateEngine
data is your Dictionary of information (I'm using a Dictionary to enable me to access objects by a key ($objectKeyName) in my template.
_templateContents is the actual template string itself.
I hope this is of help to you!
Just to add, you'll want to put that into a static method returning a string of course!

Had this issue recently - NVelocity needs to be initialised with the location of the template files. In this case mergeValues is an anonymous type so in my template I can just refer to $Values.SomeItem:
private string Merge(Object mergeValues)
{
var velocity = new VelocityEngine();
var props = new ExtendedProperties();
props.AddProperty("file.resource.loader.path", #"D:\Path\To\Templates");
velocity.Init(props);
var template = velocity.GetTemplate("MailTemplate.vm");
var context = new VelocityContext();
context.Put("Values", mergeValues);
using (var writer = new StringWriter())
{
template.Merge(context, writer);
return writer.ToString();
}
}

Try setting the file.resource.loader.path
http://weblogs.asp.net/george_v_reilly/archive/2007/03/06/img-srchttpwwwcodegenerationnetlogosnveloc.aspx

Okay - So I'm managed to get something working but it is a bit of a hack and isn't anywhere near a solution that I want, but it got something working.
Basically, I manually load in the template into a string then pass that string to the velocityEngine.Evaluate() method which writes the result into the the given StringWriter. The side effect of this is that the #parse instructions in the template don't work because it still cannot find the files.
using (StringWriter writer = new StringWriter())
{
velocityEngine.Evaluate(context, writer, templateName, template);
return writer.ToString();
}
In the code above templateName is irrelevant as it isn't used. template is the string that contains the entire template that has been pre-loaded from disk.
I'd still appreciate any better solutions as I really don't like this.

The tests are the ultimate authority:
http://fisheye2.atlassian.com/browse/castleproject/NVelocity/trunk/src/NVelocity.Tests/Test/ParserTest.cs?r=6005#l122
Or you could use the TemplateEngine component which is a thin wrapper around NVelocity that makes things easier.

Related

scraping html without htmlagilitypack

Due to the limitation of the system, i am not allowed to use htmlagilitypack as i dont have the rights to refer the library. So i can only use native asp.net programming language to parse page.
e.g. i want to scrap this page https://sg.linkedin.com/job/google/jobs/ to get the list of google jobs ( just an example, i am not really planning to get this list but my own company's) , i see they are under how can i extra these jobs description and name.
My current codes are
System.Net.WebClient client = new System.Net.WebClient();
try{
System.IO.Stream myStream = client.OpenRead("https://sg.linkedin.com/job/google/jobs/");
System.IO.StreamReader sr = new System.IO.StreamReader(myStream);
string htmlContent = sr.ReadToEnd();
//do not know how to carry on
}catch(Exception e){
Response.Write(e.Message);
}
how can i carry on?
You can fetch that page and use a regular expression to isolate the useful parts. If you get real lucky, you may have a valid XML file:
var html = new WebClient().DownloadString("https://sg.linkedin.com/job/google/jobs/");
var jobs = new XmlDocument();
jobs.LoadXml(Regex.Replace(Regex.Match(html,
#"<ul class=""jobs"">[\s\S]*?</ul>").Value,
#"itemscope | itemprop="".*?""", "")); // clean invalid attributes
foreach (XmlElement job in jobs.SelectNodes("//li[#class='job']"))
{
Console.WriteLine(job.SelectSingleNode(".//a[#class='company']").InnerText);
Console.WriteLine(job.SelectSingleNode(".//h2/a").InnerText);
Console.WriteLine(job.SelectSingleNode(".//p[#class='abstract']").InnerText);
Console.WriteLine();
}

Dynamic sitemap, database driven

I've been struggling with this for a couple of days now. Can't find any good example, or an example that I understand.
Background:
I own a small blog platform for user to blog.
Each user gets their own subdomain and for now there is no sitemap available. Not good.
I want to create some kind of dynamic sitemap, where all sitemapnodes is retreived from the database. The sitemap will be used only for the search engine spiders.
System: ASP.NET, mySQL.
The sitemap is pure XML. So I need in some way to create an ASPX file that return xml-data instead of html.
And I need to somehow redirect the web.sitemap to that dynamic file.
I have never worked with XML, and I dont know how to create a file that creates XML data. So i dont even know what to search for.
I don't want any static sitemap file to be stored on the server. Everything should be created on the fly.
So. Please. If you can give me some advise about XML, any example on the internet, or just what to search for.
My main questions:
1.
How to create XML output from aspx file?
2.
How do I "inform" the system, and search engine crawlers that the file to crawl is "/sitemap.aspx"
ThankS!
I looked into MvcSiteMapProvider.MVC5 and I could not get it to work. First of all it modified my Web.config to the point that my css and js files were getting a 404 not found when running my web app.
With the time I spent getting MvcSiteMapProvider to work I could have just wrote my own.
So... here is my own dumbed down version of generating a sitemap xml.
The only thing is you have to specify your routes manually. I haven't added reflection yet to go through each controller and pull out each action.
The data-driven piece works very well though.
In your Home controller add the action Sitemap and the private helper methods.
GetRouteUrls is the manually added controller/action routes.
GetDynamicUrls builds the data-driven Urls. In my example I have a LiquidsController and a Details(string id) action.
public ActionResult Sitemap()
{
var xml = new XDocument(
new XDeclaration("1.0", "utf-8", null),
new XElement("urlset",
new XAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
, GetRouteUrls()
, GetDynamicUrls()
)
);
return new XmlActionResult(xml);
}
private List<XElement> GetDynamicUrls()
{
var result = new List<XElement>();
using (var db = new ApplicationDbContext())
{
var liquids = db.Liquids.ToList();
foreach (var liquid in liquids)
{
result.Add(LocUrl("Liquids", "Details", liquid.FriendlyId));
}
}
return result;
}
private List<XElement> GetRouteUrls()
{
var result = new List<XElement>();
result.Add(LocUrl("Account", "Register"));
result.Add(LocUrl("Account", "Login"));
result.Add(LocUrl("Home", "Index"));
result.Add(LocUrl("Home", "About"));
result.Add(LocUrl("Home", "Contact"));
result.Add(LocUrl("Home", "TermsOfService"));
result.Add(LocUrl("Home", "PrivacyStatement"));
result.Add(LocUrl("Liquids", "Index"));
result.Add(LocUrl("Vendors", "Index"));
result.Add(LocUrl("Hardware", "Index"));
return result;
}
private XElement LocUrl(string controller, string action, string id = null)
{
if (!string.IsNullOrEmpty(id))
action = string.Format("{0}/{1}", action, id);
var baseUri = string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Content("~"));
return new XElement("url",
new XElement("loc", string.Format("{0}{1}/{2}", baseUri, controller, action))
);
}
I then added a route so I could access the sitemap doing /sitemap
routes.MapRoute(name: "sitemap", url: "sitemap", defaults: new {controller = "Home", action = "Sitemap"});
The XmlActionResult return type can be found here:
Return XML from a controller's action in as an ActionResult?

ASP.NET localization

When localizing an ASP.NET app (MVC or webforms, does't matter), how do you handle HTML strings in your resource file? In particular, how do you handle something like a paragraph with an embedded dynamic link? My strategy so far has been to use some sort of placeholder for the href attribute value and replace it at runtime with the actual URL, but this seems hokey at best.
As an example, suppose my copy is:
Thank you for registering. Click
here
to update your preferences.
To login and begin using the app, click
here.
Using MVC (Razor), what could be a simple:
<p>#Resources.Strings.ThankYouMessage</p>
now turns into
<p>#Resources.Strings.ThankYouMessage
.Replace("{prefs_url}", Url.Action("Preferences", "User"))
.Replace("{login_url}", Url.Action("Login", "User"))</p>
It's not horrible, but I guess I'm just wondering if there's a better way?
There isn't really a better way, beyond some syntax and performance tweaks. For example, you might add a cache layer so that you aren't doing these string operations for every request. Something like this:
<p>#Resources.LocalizedStrings.ThankYouMessage</p>
which calls a function perhaps like this:
Localize("ThankYouMessage", Resources.Strings.ThankYouMessage)
which does a hashtable lookup by resource + culture:
//use Hashtable instead of Dictionary<> because DictionaryBase is not thread safe.
private static System.Collections.Hashtable _cache =
System.Collections.Hashtable.Synchronized(new Hashtable());
public static string Localize(string resourceName, string resourceContent) {
string cultureName = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
if (string.IsNullOrEmpty(resourceName))
throw new ArgumentException("'resourceName' is null or empty.");
string cacheKey = resourceName + "/" + cultureName;
object o = _cache[cacheKey];
if (null == o) { //first generation; add it to the cache.
_cache[cacheKey] = o = ReplaceTokensWithValues(resourceContent);
}
return o as string;
}
Notice the call to ReplaceTokensWithValues(). That is the function that contains all the "not horrible" string-replacement fiffery:
public static string ReplaceTokensWithValues(string s) {
return s.Replace("{prefs_url}", Url.Action("Preferences", "User"))
.Replace("{login_url}", Url.Action("Login", "User")
.Replace("{any_other_stuff}", "random stuff");
}
By using a caching approach as above, ReplaceTokensWithValues() is only called once per culture, per resource for the lifetime of the application--instead of once per resource call. The difference may be on the order of 100 vs. 1,000,000.

File not found error with FileStreamResult controller action

I have a controller action declared as follows:
[Authorize(Order = 0, Roles = "Requester,Controller,Installer")]
public FileStreamResult ExportJobCards()
The body of this method builds a collection of CSV lines, and attempts to return them as a file as follows:
using (var sw = new StreamWriter(new MemoryStream()))
{
foreach (var line in lines)
{
sw.WriteLine(line);
}
return new FileStreamResult(sw.BaseStream, "text/csv");
}
When I request this action using the following action link...
Html.ActionLink("Export to Excel", "ExportJobCards")
...the export method executes properly, i.e. all the required CSV data is present in the lines collection in the above code, but I get a File Not Found error rendered as the end result.
EDIT:
In agreement with Tommy's observation, I moved the return out of the using, and I now get a file, but the file is empty. The new code that actually produces a file, ableit empty, is:
var sw = new StreamWriter(new MemoryStream());
foreach (var line in lines)
{
sw.WriteLine(line);
}
sw.Flush();
return new FileStreamResult(sw.BaseStream, "text/csv");
With your current setup, the Using statement is disposing of the StringWriter before the return can complete, which is resulting in the null reference/file not found error. Remove the using statement or set the StringWriter to another variable before you exit out and you should be good to go on getting rid of the File Not Found error.
A thought on your second issue now, looking into memorystreams as filestream results, you may need to change your return to this
sw.BaseStream.seek(0, SeekOrigin.Begin)
return new FileStreamResult(sw.BaseStream, "text/csv");
as the pointer is still at the end of the stream when you return.
It throws that error because you're not giving it a file stream. What you want is the FileContentResult into which you can pass arbitrary content. This content needs to be a byte array of your content, probably easiest to:
use a stringbuilder rather than a streamwriter
get your string from the builder
use the static method System.Text.UnicodeEncoding.Unicode.GetBytes(string) to get the byte array
Give the byte array to FileContentResult
As you have to write this code anyway the easiest thing to do would be to create a new FileStringResult that inherits from the base FileResult that can take in a string or stringbuilder. Override WriteFile(HttpResponseBase response) to do the string to byte[] conversion and push that into the response. Take a look at the FileStreamResult class from the MVC sources, it's very small and easy to do.

NVelocity -- #parse with embedded resources

I'm generating emails based off embedded NVelocity templates and would like to do something with dynamically included sections. So my embedded resources are something like this:
DigestMail.vm
_Document.vm
_ActionItem.vm
_Event.vm
My email routine will get a list of objects and will pass each of these along with the proper view to DigestMail.vm:
public struct ItemAndView
{
public string View;
public object Item;
}
private void GenerateWeeklyEmail(INewItems[] newestItems)
{
IList<ItemAndView> itemAndViews = new List<ItemAndView>();
foreach (var item in newestItems)
{
itemAndViews.Add(new ItemAndView
{
View = string.Format("MyAssembly.MailTemplates._{0}.vm", item.GetType().Name),
Item = item
});
}
var context = new Dictionary<string, object>();
context["Recipient"] = _user;
context["Items"] = itemAndViews;
string mailBody = _templater.Merge("MyAssembly.MailTemplates.DigestMail.vm", context);
}
And in my DigestMail.vm template I've got something like this:
#foreach($Item in $Items)
====================================================================
#parse($Item.viewname)
#end
But it's unable to #parse when given the path to an embedded resource like this. Is there any way I can tell it to parse each of these embedded templates?
Hey Jake, is .viewname a property? I'm not seeing you setting it in your code, how about you use the following:
#foreach($Item in $Items)
====================================================================
$Item.viewname
#end
I don't know why you're parsing the $Item.viename rather than just using the above? I'm suggesting this as I've just never needed to parse anything!
Please refer to this post where we've discussed the generation of templates.
Hope this helps!

Resources