这是我的第一篇文章,如果问题需要一些修改,请道歉。我已经把这个问题尽可能地煮了,但是这里有很多组件,所以这个帖子非常庞大......
我们的ASP.NET MVC站点在Azure上部署为应用服务。我正在使用API控制器方法来生成存在于同一站点上的页面的PDF。为此,控制器创建一个PhantomJS进程,等待成功,并返回它创建的文件的内容。这一切都很好,但之后在网站上的几个视图产生这样的错误:
Server Error in '/' Application.
目录'D:\ home \ site \ wwwroot \ Views \ Location'不存在。无法开始监视文件更改。
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Web.HttpException: Directory 'D:\home\site\wwwroot\Views\Location' does not exist. Failed to start monitoring file changes.
一段时间后,错误发生了变化:
Server Error in '/' Application.
未找到视图“LocationList”或其主节点,或者视图引擎不支持搜索的位置。搜索了以下位置: 〜/查看/位置/ LocationList.aspx 〜/查看/位置/ LocationList.ascx 〜/查看/共享/ LocationList.aspx 〜/查看/共享/ LocationList.ascx 〜/查看/位置/ LocationList.cshtml 〜/查看/位置/ LocationList.vbhtml 〜/查看/共享/ LocationList.cshtml 〜/查看/共享/ LocationList.vbhtml
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The view 'LocationList' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Location/LocationList.aspx
~/Views/Location/LocationList.ascx
~/Views/Shared/LocationList.aspx
~/Views/Shared/LocationList.ascx
~/Views/Location/LocationList.cshtml
~/Views/Location/LocationList.vbhtml
~/Views/Shared/LocationList.cshtml
~/Views/Shared/LocationList.vbhtml
这仅适用于尚未编译的视图或之前未访问过的任何其他文件。解决此问题的唯一方法是手动停止和启动Web应用程序。我可以确认所有进程都不会发生这种情况(运行“echo.exe”而不是“phantomjs.exe”不会导致损坏的行为)。
我查看了我能想到的所有日志,但没有找到任何与众不同的东西。我最好的猜测是,一个过程被强行或意外终止,但至于什么和为什么,我不知道。也许有一些我不知道的关键日志?
这是相关的c#代码:
private static async Task<int> ExecuteSimpleAsync(string workingDir, double? timeout,
string command, params string[] parameters)
{
var paramStr = string.Join(" ", parameters.Select(x => x == null ? "" : $"\"{x}\"").ToList());
var processInfo = new ProcessStartInfo(command, paramStr) {
WorkingDirectory = workingDir,
UseShellExecute = false,
CreateNoWindow = true,
};
Process process = null;
int exitCode = -1;
using (process = new Process() { StartInfo = processInfo }) {
process.Start();
await process.WaitForExitAsync(timeout); // simple extension function to check for 'Process.HasExited' periodically
exitCode = process.ExitCode;
}
return exitCode;
}
private static async Task<byte[]> GetFileContents(string filePath) {
byte[] bytes = null;
using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read)) {
bytes = new byte[file.Length];
await file.ReadAsync(bytes, 0, (int) file.Length);
}
return bytes;
}
public static async Task<byte[]> RenderPdfAsync(
string cookiesB64, string localUrl, string baseFilename, double? timeout = 60)
{
....
// filesPath: (directory for temporary output)
// timeout: 60.000 (60 seconds)
// PhantomJSExePath: (absolute path containing 'phantomjs.exe')
// scriptFile: "rasterize_simple.js"
// requestUrl: "TestReport/ForUserAndTestPdf/1002/10"
// outputFile: "phantomjs-output-<timestamp>.pdf"
// cookiesB64: (base64-encoded authentication cookies passed to request in PhantomJS)
var exitCode = await ExecuteSimpleAsync(filesPath, timeout, PhantomJSExePath + @"\phantomjs.exe",
scriptFile, requestUrl, outputFile, cookiesB64);
if (exitCode != 0)
return null;
return await GetFileContents(outputFile);
}
[Authorize]
[HttpGet]
[Route("TestReport/ForUserAndTestPdf/{userId}/{testId}")]
public async Task<HttpResponseMessage> ForUserAndTestPdfAsync(int userId, int testId) {
// produce a slightly-modified version of the current URL:
// /TestReport/ForUserAndTest/<userid>/<testid>
// => /TestReport/ForUserAndTestPdf/<userid>/<testid>?print=true
var url = Request.RequestUri.GetLocalPathWithParams("print=true").Replace("ForUserAndTest", "ForUserAndTestPdf");
// get the cookies used in the current request and convert to a base64-encoded JSON object
var cookiesB64 = Request.GetCookiesJsonB64();
var bytes = await PhantomJSHelpers.RenderPdfAsync(cookiesB64, url, "phantomjs-output", 60);
var message = new HttpResponseMessage(HttpStatusCode.OK);
message.Content = new StreamContent(new MemoryStream(bytes));
message.Content.Headers.ContentLength = bytes.Length;
message.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return message;
}
这是PhantomJS使用的“rasterize_simple.js”脚本的相关部分,没有页面大小,cookie等的设置:
page.open(address, function(status) {
page.render(outputFilename);
phantom.exit(0);
});
所有这些的预期结果是它生成的PDF文件,并且对此API方法的所有后续调用(具有不同的参数)都可以完美地工作。然而,副作用是一个完全破碎的网站:(
这里的任何帮助将不胜感激!
我担心你的ASP.NET应用程序的功能在Azure WebApp中无法正常工作,比如分叉一个进程来运行PhantomJS并生成一个PDF文件,因为有很多限制不允许这样做,请参考到Kudu wiki页面Azure Web App sandbox
了解更多。
这是我认为你有的一些限制。
解决方案是在Azure VM上部署您的应用程序,而不是WebApp。
发布我自己的答案,因为Peter Pan's answer指出了我正确的方向,但我发现了一个不同的解决方案。看来问题是由写入沙箱中的受保护区域(D:\ home中的任何内容)引起的。从Path.GetTempPath()
运行PhantomJS并在那里编写文件似乎完全解决了这个问题。
这并不能解释究竟发生了什么,但至少问题已经解决了。