How can I just make a function call, without URL, and without HTTP, to a simple ASP.NET file, and capture the byte stream it generated?
More background information,
You can run an ASPX page without IIS, without an HTTP message, if you build a host for the ASPNET runtime.
Example:
public class MyAspNetHost : System.MarshalByRefObject
{
public void ProcessRequest(string page)
{
var request = new System.Web.Hosting.SimpleWorkerRequest
(page, // the page being requested
null, // query - none in this case
System.Console.Out // output - any TextWriter will do
);
// this will emit the page output to Console.Out
System.Web.HttpRuntime.ProcessRequest(request);
}
public AppDomain GetAppDomain()
{
return System.Threading.Thread.GetDomain();
}
}
public class Example
{
public void Run(IEnumerable<String> pages)
{
// ASPNET looks for assemblies - including the assembbly
// that contains any custom ASPNET host - in the bin\
// subdirectory of the physical directory that backs the
// ASPNET Host. Because we are going to use the current
// working directory as the physical backing directory for
// the ASPNET host, we need to ensure there's a bin
// subdirectory present.
bool cleanBin = false;
if (!Directory.Exists("bin"))
{
cleanBin = true;
Directory.CreateDirectory("bin");
}
// Now, ensure that the assembly containing the custom host is
// present in that bin directory. The assembly containing the
// custom host is actually *this* assembly.
var a = System.Reflection.Assembly.GetExecutingAssembly();
string destfile= Path.Combine("bin", Path.GetFileName(a.Location));
File.Copy(a.Location, destfile, true);
host =
(MyAspNetHost) System.Web.Hosting.ApplicationHost.CreateApplicationHost
( typeof(MyAspNetHost),
"/foo", // virtual dir - can be anything
System.IO.Directory.GetCurrentDirectory() // physical dir
);
// process each page
foreach (string page in pages)
host.ProcessRequest(page);
}
}
If you want to clean up that bin directory, you have to get the AppDomain to unload first. You can do that, like this:
private ManualResetEvent aspNetHostIsUnloaded;
private void HostedDomainHasBeenUnloaded(object source, System.EventArgs e)
{
// cannot clean bin dir here. The AppDomain is not yet gone.
aspNetHostIsUnloaded.Set();
}
private Run(IEnumerable<String> pages)
{
try
{
....code from above ....
}
finally
{
if (host!= null)
{
aspNetHostIsUnloaded = new ManualResetEvent(false);
host.GetAppDomain().DomainUnload += this.HostedDomainHasBeenUnloaded;
AppDomain.Unload(host.GetAppDomain());
// wait for it to unload
aspNetHostIsUnloaded.WaitOne();
// optionally remove the bin directory
if (cleanBin)
{
Directory.Delete("bin", true);
}
aspNetHostIsUnloaded.Close();
}
}
}
This makes sense for testing ASPX pages, and that sort of thing. But I'm not so sure this is the right thing, for your scenario. There are more direct ways to generate text files. But, it may be right for you. If you really like the template engine idea, hosting ASPNET may be just the thing for you.
In your case, you would want to modify the custom Host so that the output for each page goes to a StringWriter instead of Console.Out, and then you could do Grep (or more likely a search with Regex) on that output. You might also want to modify it to accept all the input data as a querystring. You'd need to format the page request to do that.
EDIT: There's a good article on MSDN Magazine on this technique of hosting the ASPNET runtime. From December 2004.
EDIT2: There's a simpler way to manage the bin directory. Just create a symbolic link named bin, pointing to ".". Then, you can remove the symlink after the call to AppDomain.Unload(), without waiting. Looks like this:
public void Run(string[] pages)
{
bool cleanBin = false;
MyAspNetHost host = null;
try
{
// This creates a symlink.
// ASPNET always looks for a bin\ directory for the privateBinPath of the AppDomain.
// This will create the bin dir, pointing to the current dir.
if (!Directory.Exists("bin"))
{
cleanBin = true;
CreateSymbolicLink("bin", ".", 1);
}
host =
(MyAspNetHost) System.Web.Hosting.ApplicationHost.CreateApplicationHost
( typeof(MyAspNetHost),
"/foo", // virtual dir - can be anything
System.IO.Directory.GetCurrentDirectory() // physical dir
);
foreach (string page in pages)
host.ProcessRequest(page);
}
finally
{
// tell the host to unload
if (host!= null)
{
AppDomain.Unload(host.GetAppDomain());
if (cleanBin)
{
// remove symlink - can do without waiting for AppDomain unload
Directory.Delete("bin");
}
}
}
}
This eliminates the need for the ManualResetEvent, copying files, synchronization, etc. It assumes the assembly for the custom ASPNet Host as well as all the assemblies required by the ASPX pages you run are contained in the current working directory.
This sounds like a very similar issue which is generating HTML emails on a server. There are some answers here that do that (for MVC):
ASP.NET MVC: How to send an html email using a controller?
You can proceed in a similar fashion for non-MVC by loading and rendering a control (ASCX) to a file.