“Object reference not set to an instance of an object.”
at ServiceStack.Script.SharpPages.GetPage(String pathInfo) in C:\BuildAgent\work\3481147c480f4a2f\src\ServiceStack.Common\Script\SharpPages.cs:line 167\r\n at ServiceStack.Script.ScriptContext.GetPage(String virtualPath) in C:\BuildAgent\work\3481147c480f4a2f\src\ServiceStack.Common\Script\ScriptContext.cs:line 211\r\n
After doing:
var pageResult = new PageResult(context.GetPage(templateName)) { Model = model };
Of course error message not ideal, however most likely I’ve done something wrong with setup.
I am (framework built in) dependency injecting context into a class after setting up in .NET 6 minimal hosting like this:
//...earlier services...
builder.Services.AddSingleton(redisMqServer.MessageFactory);
builder.Services.AddHostedService<ProcessQueue>();
//ServiceStack appHost and ScriptContext
var appHost = new ProcessQueueAppHost
{
AppSettings = new NetCoreAppSettings(builder.Configuration) // Use **appsettings.json** and config sources
};
var context = new ScriptContext
{
VirtualFiles = appHost.VirtualFiles,
ScriptBlocks =
{
new EmailRootUrlForImgScriptBlock(),
new EmailPrefsLinkScriptBlock()
},
ScriptMethods = { new MyScriptMethods() }
}.Init();
builder.Services.AddSingleton<ScriptContext>(context);
//Build
_host = builder.Build();
_host.UseServiceStack(appHost);
redisMqServer.RegisterHandler<PasswordResetEmailInternalMsg>(appHost.ServiceController.ExecuteMessage);
//...etc..
//.Start() is called in ProcessQueue.cs
await _host.StartAsync();
Console.WriteLine("ProgramToProcessQueue running - until crashed.");
while (_running)
{
Thread.Sleep(10000);
}
Thread.Sleep(5000); // Give remaining services a chance to finish.
}
…this is a ‘Queue Processor’ app accepting redis MQ based messages and running as an AppService WebJob.
You should isolate the test into just the #Script that’s failing in isolation, i.e. without Redis MQ.
BTW you shouldn’t be accessing anything on the AppHost before it’s initialized, e.g. VirtualFiles = appHost.VirtualFiles.
This could be related to the issue since the Exception is an error trying to access the VirtualFiles:
Since you already have SharpPagesFeature configured:
Plugins.Add(new SharpPagesFeature());
Which is itself a ScriptContext which I’d suggest you use instead which is automatically configured with the AppHost.
Alternatively create the ScriptContext where you use it, not in Program.cs before anything is properly initialized. ScriptContext is a lightweight sandbox that’s better to be configured around the task you need to use it for.
You will need to open the test project under tests folder and run the single test to push a message into redis (“localhost:6379?db=1”)
Run the EE.ProcessQueue project to get to the error.
I thought that would be good to give full context end to end. Anyway your answer has helped, but have two questions below.
I do not think “create the ScriptContext where you use it” is good for me as I have a lot of different email templates with common ScriptBlocks which is why I was trying to inject ScriptContext and use a resolved single instance that has access to the virtual files and ScriptBlocks, I can see now as you say that won’t work by using AppHost before it’s initialized. If tried to do builder.Services.AddSingleton(context) after builder.Build() then that would surely fail too.
I had put in Plugins.Add(new SharpPagesFeature()); in an attempt to get the virtual files to work. I do now see that the doco says “SharpPagesFeature is a subclass of ScriptContext” over here: https://sharpscript.net/docs/script-pages
So just to confirm - you are saying that SharpPagesFeature will have the Virtual Files ready to access?
I see I can adjust my instantiation of SharpPagesFeature to include my common ScriptBlocks.
…so…
How do I resolve the ‘SharpPagesFeature’ as a ScriptContext?
It’s a lightweight sandbox, creating the rendering context to render each template within is exactly a use-case it’s designed for, as it does in the Email Templates example .
SharpPagesFeatureis a ScriptContext, so you can just fetch the plugin:
But the preferred API would be to access it from IAppHost.ScriptContext which returns SharpPagesFeature if configured, otherwise returns a DefaultScriptContext if not:
var context = HostContext.AppHost.ScriptContext;
Although in a Service you’d typically only use ISharpPages dependency or IRequest.GetPage() extension method to resolve and render #Script pages, see examples in:
It does seem wrong to use SharpPagesFeature when I only need the more core functionality that comes with ScriptContext.
So (correct me if I am wrong) if I want to use vanilla ScriptContext and drop the SharpPagesFeature, just comes down to this:
I must be willing to create a ScriptContext for each of my ServiceStack.Service that sends email(s), so that I can access the base.VirtualFiles as per TechStacks example. Even if I will normally be passing in the exact same ScriptBlocks.
I can just introduce my own little abstract class that inherits from ServiceStack.Service and gives me a little helper method to create the ScriptContext if the repetition bothers me.
SharpPagesFeature is basically a ScriptContext that’s binded to the AppHost, it’s your choice whether you use it or not, but it’s basically similar to your initial singleton ScriptContext approach, except properly initialized with the AppHost.
It’s normal C# so can use whichever DRY techniques you prefer, personally inheritance is my least favorite option, IMO extension methods achieve more readable DRY code with fewer abstractions.