Using RazorEngine with TextWriter - razorengine

I want to use RazorEngine to generate some html files. It's easy to generate strings first, then write them to files. But if the generated strings are too large, that will cause memory issues.
So I wonder is there a non-cached way to use RazorEngine, like using StreamWriter as its output rather than a string.
I google this for a while, but with no luck.
I think use a custom base template should be the right way, but the documents are so few(even out of date) on the offcial homepage of RazorEngine.
Any hint will be helpful!

OK. I figured it out.
Create a class that inherits TemplateBase<T>, and take a TextWrite parameter in the constructor.
public class TextWriterTemplate<T> : TemplateBase<T>
{
private readonly TextWriter _tw;
public TextWriterTemplate(TextWriter tw)
{
_tw = tw;
}
// override Write and WriteLiteral methods, write text using the TextWriter.
public override void Write(object value)
{
_tw.Write(value);
}
public override void WriteLiteral(string literal)
{
_tw.Write(literal);
}
}
Then use the template as this:
private static void Main(string[] args)
{
using (var sw = new StreamWriter(#"output.txt"))
{
var config = new FluentTemplateServiceConfiguration(c =>
c.WithBaseTemplateType(typeof(TextWriterTemplate<>))
.ActivateUsing(context => (ITemplate)Activator.CreateInstance(context.TemplateType, sw))
);
using (var service = new TemplateService(config))
{
service.Parse("Hello #Model.Name", new {Name = "Waku"}, null, null);
}
}
}
The content of output.txt should be Hello WAKU.

Related

Injecting dynamic content into an Orchard page body

Is it possible to inject dynamic content into an Orchard page body? I would like to inject the id of the current logged in user into links.
My initial thought was to use token replacement, so entering would replace [memberid] with the logged in user id at runtime. Is there a way I can intercept the response and run some custom code before it is sent back to the client?
This question is the same, however the answer is very specific to their issue.
Tokens might be the proper way to go, however I haven't digged into that yet. But if you want to change the response you can use response filters.
I used it to minify resulting HTML in this module https://gallery.orchardproject.net/List/Modules/Orchard.Module.JadeX.HtmlMarkupMinifier
Here's the code that should do the trick or at least give you an idea.
using System.Globalization;
using System.IO;
using System.Text;
using System.Web.Mvc;
using Orchard;
using Orchard.Mvc.Filters;
using Orchard.UI.Admin;
public class TokenReplacementFilter : FilterProvider, IActionFilter
{
private readonly WorkContext _workContext;
public TokenReplacementFilter(IWorkContextAccessor workContextAccessor)
{
_workContext = workContextAccessor.GetContext();
}
public void OnActionExecuting(ActionExecutingContext filterContext) {
// Only apply the token replacement if logged in and not in the Orchard admin area
if (filterContext.HttpContext.Response.Filter == null || _workContext.CurrentUser == null || AdminFilter.IsApplied(filterContext.RequestContext))
return;
filterContext.HttpContext.Response.Filter = new TokenReplacementStream(filterContext.HttpContext.Response.Filter, filterContext.HttpContext.Response.Output.Encoding, _workContext);
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
internal class TokenReplacementStream : MemoryStream
{
private readonly Stream _stream;
private readonly Encoding _encoding;
private string _html;
private readonly WorkContext _workContext;
public TokenReplacementStream(Stream filter, Encoding encoding, WorkContext workContext)
{
_stream = filter;
_encoding = encoding;
_workContext = workContext;
}
public override void Write(byte[] buffer, int offset, int count)
{
_html += _encoding.GetString(buffer);
}
public override void Flush()
{
if (_html != null) {
_html = _html.Replace("[memberid]", _workContext.CurrentUser.Id.ToString(CultureInfo.InvariantCulture));
_stream.Write(_encoding.GetBytes(_html), 0, _encoding.GetByteCount(_html));
}
}
}

NLog MemoryTarget maximum size

Since I have a global exception handler that reports uncaught errors via e-mail, next step is to add some context to it by having some 10-20 last lines of log that are collected.
So I am using MemoryTarget like so:
MemoryTarget _logTarget;
_logTarget = new MemoryTarget();
_logTarget.Layout = "${longdate}|${level:uppercase=true}|${logger}|${message}${exception}";
LoggingRule loggingRule = new LoggingRule("*", LogLevel.Debug, _logTarget);
LogManager.Configuration.AddTarget("exceptionMemory", _logTarget);
LogManager.Configuration.LoggingRules.Add(loggingRule);
LogManager.Configuration.Reload();
Apps containing this should run forever, and if I leave logs in memory, unchecked, I'll have neatly designed memory leak.
How to address this? How to truncate MemoryTarget.Logs to have at most say 100 lines?
Your best bet is probably to write your own MemoryTarget... Something like this (untested) should work.
namespace NLog.Targets
{
using System.Collections.Generic;
[Target("LimitedMemory")]
public sealed class LimitedMemoryTarget : TargetWithLayout
{
private Queue<string> logs = new Queue<string>();
public LimitedMemoryTarget()
{
this.Logs = new List<string>();
}
public IEnumerable<string> Logs
{
get { return logs; }
private set { logs = value; }
}
[DefaultValue(100)]
public int Limit { get; set; }
protected override void Write(LogEventInfo logEvent)
{
string msg = this.Layout.Render(logEvent);
logs.Enqueue(msg);
if (logs.Count > Limit)
{
logs.Dequeue();
}
}
}
}
This example is based on the NLog MemoryTarget, the source code for which you can find here:
https://github.com/NLog/NLog
NLog docs are here:
http://nlog-project.org/documentation/v2.0.1/
I didn't see anything like you are asking about in either location.

How to write an NLog target using Signalr

I'm trying to write a target for NLog to send messages out to connected clients using SignalR.
Here's what I have now. What I'm wondering is should I be using resolving the ConnectionManager like this -or- somehow obtain a reference to the hub (SignalrTargetHub) and call a SendMessage method on it?
Are there performance ramifications for either?
[Target("Signalr")]
public class SignalrTarget:TargetWithLayout
{
public SignalR.IConnectionManager ConnectionManager { get; set; }
public SignalrTarget()
{
ConnectionManager = AspNetHost.DependencyResolver.Resolve<IConnectionManager>();
}
protected override void Write(NLog.LogEventInfo logEvent)
{
dynamic clients = GetClients();
var logEventObject = new
{
Message = this.Layout.Render(logEvent),
Level = logEvent.Level.Name,
TimeStamp = logEvent.TimeStamp.ToString("yyyy-MM-dd HH:mm:ss.fff")
};
clients.onLoggedEvent(logEventObject);
}
private dynamic GetClients()
{
return ConnectionManager.GetClients<SignalrTargetHub>();
}
}
I ended up with the basic the same basic structure that I started with. Just a few tweaks to get the information I needed.
Added exception details.
Html encoded the final message.
[Target("Signalr")]
public class SignalrTarget:TargetWithLayout
{
protected override void Write(NLog.LogEventInfo logEvent)
{
var sb = new System.Text.StringBuilder();
sb.Append(this.Layout.Render(logEvent));
if (logEvent.Exception != null)
sb.AppendLine().Append(logEvent.Exception.ToString());
var message = HttpUtility.HtmlEncode(sb.ToString());
var logEventObject = new
{
Message = message,
Logger = logEvent.LoggerName,
Level = logEvent.Level.Name,
TimeStamp = logEvent.TimeStamp.ToString("HH:mm:ss.fff")
};
GetClients().onLoggedEvent(logEventObject);
}
private dynamic GetClients()
{
return AspNetHost.DependencyResolver.Resolve<IConnectionManager>().GetClients<SignalrTargetHub>();
}
}
In my simple testing it's working well. Still remains to be seen if this adds any significant load when under stress.

Playframework Excel file generation

I've installed excel module in order to generate reports from datas recorded by my application into database.
It works fine : i can create report simply by clicking on a link into my main page and render into excel template.
But i'd rather generate excel file periodically (using a job) and save it into a shared folder, and that without any human action (so not by clicking on a link).
It's like I want to trigger the associated controller to render into my template automatically.
Does anyone got any tips on it for me?
So the problem is you can't pass some parameters into the job, or...?
Using something like this just doesn't work?
#On("0 45 4-23 ? * MON-FRI")
public class ExcelJob extends Job {
public void doJob() {
// generate excel
}
}
I wrote my own Excel generator using JExcel, and I use it for scheduled generation without a problem. It also doesn't require a template, because the report structure is derived from annotations. This is roughly 20 lines of code - you may want to try it for yourself.
This is really rough and lacks good user feedback, but gives you the idea...
Excel generator - not Play-specific in any way
public class ExcelGenerator
{
public void generateReport(Function successCallback,
Function failureCallback)
{
try
{
byte[] report = // generate your report somehow
successCallback.execute(report);
}
catch (Exception e)
{
failureCallback.execute(e.getMessage());
}
}
}
A function interface for callbacks (very basic)
public interface Function
{
public void execute(Object... args);
}
Your Play controller
public class MyController extends Controller
{
public static void index()
{
render();
}
public static void createReport()
{
Function failureCallback = new Function()
{
public void execute(Object... args)
{
flash.error(args[0]);
indxe();
}
};
Function successCallback = new Function()
{
public void execute(Object... args)
{
renderBinary((byte[])args[0]);
}
};
ExcelGenerator excelGenerator = new ExcelGenerator();
excelGenerator.generateReport(successCallback,
failureCallback);
}
}
Finally, re-use the ExcelGenerator from your job
public class MyJob extends Job
{
public void doJob()
{
Function failureCallback = new Function()
{
public void execute(Object... args)
{
Logger.error(args[0]);
}
}
Function successCallback = new Function()
{
public void execute(Object... args)
{
byte[] report = (byte[])args[0];
// write report to disk
}
}
ExcelGenerator excelGenerator = new ExcelGenerator();
excelGenerator.generateReport(successCallback,
failureCallback);
}
}
You'll still need to write your own report generator, or refactor the existing excel module to provide what you need.
So if you want to run and manage several jobs you can do something like this
for (int i = 0; i < 10; i++) {
SendingMessageJob sendingMessageJob = new SendingMessageJob();
promises.add(sendingMessageJob.now());
}
boolean allDone = false;
while (!allDone) {
allDone = true;
for (F.Promise promise : promises) {
if (!promise.isDone()) {
allDone = false;
break;
}
}
}
// when arrive here all jobs have finished their process
You can check the Play documentation, specifically the section on jobs, where you'll see examples on how to create automatically triggered methods. This should solve your issue.
EDIT (update on comment):
You can manually trigger a job, do this:
new MyExcelGeneratorJob().doJob();
Thing is, Play is stateless, so the job should use data from the database. Instead of trying to pass parameters from your request into the Job (won't work) try to store that data in a staging area in the database that the job loads and processes to generate the excel.

How to pass parameters to a CodeActivity in a NativeActivity code sequence

I'm trying to get windows workflows working, and I've become a little stumped.
I've gotten a single workflow working, but now I am trying to do something a little more complex: start a workflow, where each activity itself contains a workflow. (Picture something like the main program starts the activities "Input, logic, and output", and then each of those have additional activities like "prompt user, get input, etc.")
I've had it working fine, with the example from here (http://msdn.microsoft.com/en-us/magazine/gg535667.aspx), when I am not passing any parameters from the main program to the activites. My question is, how exactly does the 'Variables' and 'metadata.SetVariablesCollection' work in the NativeActivity, and how to I get the parameters to the low level activities?
This is what I am currently trying:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Collections.ObjectModel;
using System.Activities.Statements;
namespace Project1
{
internal class MainProgram
{
internal static void Main(string[] args)
{
try
{
var act = new SimpleSequence();
act.Activities.Add((Activity)(new WriteSomeText()));
act.Activities.Add((Activity)(new WriteSomeText()));
act.Activities.Add((Activity)(new WriteSomeText()));
act.Variables.Add(new Variable<string> ("stringArg", "TEXT"));
WorkflowInvoker.Invoke(act);
}
catch (Exception ex)
{
System.Console.WriteLine("EXCEPTION: {0}", ex);
}
}
public class WriteSomeText : CodeActivity
{
[RequiredArgument]
public InArgument<string> stringArg { get; set; }
protected override void Execute(CodeActivityContext context)
{
string output = context.GetValue(stringArg);
System.Console.WriteLine(output);
}
}
public class SimpleSequence : NativeActivity
{
Collection<Activity> activities;
Collection<Variable> variables;
Variable<int> current = new Variable<int> { Default = 0 };
public Collection<Activity> Activities
{
get
{
if (this.activities == null)
this.activities = new Collection<Activity>();
return this.activities;
}
set
{
this.activities = value;
}
}
public Collection<Variable> Variables
{
get
{
if (this.variables == null)
this.variables = new Collection<Variable>();
return this.variables;
}
set
{
this.variables = value;
}
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
metadata.SetChildrenCollection(this.activities);
metadata.SetVariablesCollection(this.variables);
metadata.AddImplementationVariable(this.current);
}
protected override void Execute(NativeActivityContext context)
{
if (this.Activities.Count > 0)
context.ScheduleActivity(this.Activities[0], onChildComplete);
}
void onChildComplete(NativeActivityContext context, ActivityInstance completed)
{
int currentExecutingActivity = this.current.Get(context);
int next = currentExecutingActivity + 1;
if (next < this.Activities.Count)
{
context.ScheduleActivity(this.Activities[next], this.onChildComplete);
this.current.Set(context, next);
}
}
}
}
}
This ends up throwing the following exception:
EXCEPTION: System.Activities.InvalidWorkflowException: The following errors were encountered while processing the workflow tree:
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
at System.Activities.Validation.ActivityValidationServices.ThrowIfViolationsExist(IList`1 validationErrors)
at System.Activities.Hosting.WorkflowInstance.ValidateWorkflow(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.Hosting.WorkflowInstance.RegisterExtensionManager(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.WorkflowApplication.EnsureInitialized()
at System.Activities.WorkflowApplication.RunInstance(WorkflowApplication instance)
at System.Activities.WorkflowApplication.Invoke(Activity activity, IDictionary`2 inputs, WorkflowInstanceExtensionManager extensions, TimeSpan timeout)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow)
at Project1.MainProgram.Main(String[] args) in c:\users\user\documents\visual studio 2010\Projects\ModelingProject1\Project1\MainProgram.cs:line 25
I know, I only pass 1 parameter, but the exception still says that I am missing 3 parameters. I am missing something as to how to do this properly.
You're correctly declaring stringArg as an InArgument but you're not passing any value to it when calling it inside SimpleSequence.
You can pass something using the constructor, while constructing the all activity itself, like this:
public class WriteSomeText : CodeActivity
{
[RequiredArgument]
public InArgument<string> stringArg { get; set; }
public WriteSomeText(string stringArg)
{
this.stringArg = stringArg;
}
protected override void Execute(CodeActivityContext context
{
string output = context.GetValue(stringArg);
System.Console.WriteLine(output);
}
}
// Calling the activity like this:
internal static void Main(string[] args)
{
var act = new SimpleSequence()
{
Activities =
{
new WriteSomeText("hello"),
new WriteSomeText("world"),
new WriteSomeText("!")
}
};
WorkflowInvoker.Invoke(act);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
Also notice that is a best practice to use the constructor to initialize collections:
public SimpleSequence()
{
activities = new Collection<Activity>();
variables = new Collection<Variable>();
}
This way is even more intuitive to initialize the activity:
var act = new SimpleSequence()
{
Activities =
{
new WriteSomeText("hello"),
new WriteSomeText("world"),
new WriteSomeText("!")
},
Variables =
{
new Variable<int>("myNewIntVar", 10),
// ....
}
};
EDIT:
There are a couple of other ways to approach the problem. This is your best friend while starting in the WF4 world.
Check WF\Basic\CustomActivities\Code-Bodied for a little push with this particular case.

Resources