wkhtmltopdf fails on Azure Website - azure

I'm using the https://github.com/codaxy/wkhtmltopdf wrapper to create a pdf from a web page on my website (I pass in an absolute url e.g. http://mywebsite.azurewebsites.net/PageToRender.aspx It works fine in dev and on another shared hosting account but when I deploy to an Azure website it fails and all I get is a ThreadAbortException.
Is it possible to use wkhtmltopdf on azure, and if so, what am I doing wrong?
UPDATE:
This simple example just using Process.Start also doesn't work. It just hangs when run on Azure but works fine on other servers.
string exePath = System.Web.HttpContext.Current.Server.MapPath("\\App_Data\\PdfGenerator\\wkhtmltopdf.exe");
string htmlPath = System.Web.HttpContext.Current.Server.MapPath("\\App_Data\\PdfGenerator\\Test.html");
string pdfPath = System.Web.HttpContext.Current.Server.MapPath("\\App_Data\\PdfGenerator\\Test.pdf");
StringBuilder error = new StringBuilder();
using (var process = new Process())
{
using (Stream fs = new FileStream(pdfPath, FileMode.Create))
{
process.StartInfo.FileName = exePath;
process.StartInfo.Arguments = string.Format("{0} -", htmlPath);
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.Start();
while (!process.HasExited)
{
process.StandardOutput.BaseStream.CopyTo(fs);
}
process.WaitForExit();
}
}

Check out this SO question regarding a similar issue. This guy seems to have gotten it to work. RotativaPDF is built on top of wkhtmltopdf hence the connection. I am in the process of trying it myself on our Azure site - I will post in the near future with my results.
Azure and Rotativa PDF print for ASP.NET MVC3

Related

How to provision Branding files using SharePoint Hosted App in SharePoint Online/Office 365?

I am looking for SharePoint Hosted App Solution which will provision Branding files (JS/CSS/Images) into SharePoint Online/Office 365 environment.
I got a very good article to achive this and tried to implement the same as shown in below link: http://www.sharepointnutsandbolts.com/2013/05/sp2013-host-web-apps-provisioning-files.html
This solution is not working for me and while execution of app, I am getting below error:
Failed to provision file into host web. Error: Unexpected response data from server. Here is the code which is giving me error:
// utility method for uploading files to host web..
uploadFileToHostWebViaCSOM = function (serverRelativeUrl, filename, contents) {
var createInfo = new SP.FileCreationInformation();
createInfo.set_content(new SP.Base64EncodedByteArray());
for (var i = 0; i < contents.length; i++) {
createInfo.get_content().append(contents.charCodeAt(i));
}
createInfo.set_overwrite(true);
createInfo.set_url(filename);
var files = hostWebContext.get_web().getFolderByServerRelativeUrl(serverRelativeUrl).get_files();
hostWebContext.load(files);
files.add(createInfo);
hostWebContext.executeQueryAsync(onProvisionFileSuccess, onProvisionFileFail);
}
Please suggest me, what can be the issue in this code? Or else suggest me another way/reference in which I can Create a SharePoint-Hosted App to provision Branding Files.
Thanks in Advance!
I would use a different method to access host web context as follows:
//first get app context, you will need it.
var currentcontext = new SP.ClientContext.get_current();
//then get host web context
var hostUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
var hostcontext = new SP.AppContextSite(currentcontext, hostUrl);
function getQueryStringParameter(param) {
var params = document.URL.split("?")[1].split("&");
var strParams = "";
for (var i = 0; i < params.length; i = i + 1) {
var singleParam = params[i].split("=");
if (singleParam[0] == param) {
return singleParam[1];
}
}
}
Here are some references:
https://sharepoint.stackexchange.com/questions/122083/sharepoint-2013-app-create-list-in-host-web
https://blog.appliedis.com/2012/12/19/sharepoint-2013-apps-accessing-data-in-the-host-web-in-a-sharepoint-hosted-app/
http://www.mavention.com/blog/sharePoint-app-reading-data-from-host-web
http://www.sharepointnadeem.com/2013/12/sharepoint-2013-apps-access-data-in.html
Additionally, here is an example of how to deploy a master page, however as you might notice during your testing the method used to get host web context is not working as displayed in the video and you should use the one I described before.
https://www.youtube.com/watch?v=wtQKjsjs55I
Finally, here is a an example of how to deploy branding files through a Console Application using CSOM, if you are smart enough you will be able to convert this into JSOM.
https://channel9.msdn.com/Blogs/Office-365-Dev/Applying-Branding-to-SharePoint-Sites-with-an-App-for-SharePoint-Office-365-Developer-Patterns-and-P

Can't access to Azure Storage folder using Lucene.Net

We decided to implement a search functionality in our API which is developed in ServiceStack, we decided to use Lucene.Net since we heard it was a great indexer to make searches.
We created a worker role whose job is to create the indexes in a Azure Storage folder, we guided ourselves using Leon Cullen's tutorial. We use the AzureDirectory library specified in that post, so we could use the latest Azure SDK.
Then in our API project we added the references for Lucene.Net and AzureDirectory too, our endpoint ended up looking like this:
public object Post(SearchIndex request)
{
List<Product> products = new List<Product>();
var pageSize = -1;
var totalpages = -1;
int.TryParse(ConfigurationManager.AppSettings["PageSize"], out pageSize);
if (request.Page.Equals(0))
{
request.Page = 1;
}
// Get Azure settings
AzureDirectory azureDirectory ;
try
{
// This is the line where we get the Access denied exception thrown at us
azureDirectory = new AzureDirectory(Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(ConfigurationManager.AppSettings["ConnectionStringAzureSearch"]), "indexsearch");
IndexSearcher searcher;
using (new AutoStopWatch("Creating searcher"))
{
searcher = new IndexSearcher(azureDirectory);
}
using (new AutoStopWatch(string.Format("Search for {0}", request.SearchString)))
{
string[] searchfields = new string[] { "Id", "Name", "Description" };
var hits = searcher.Search(QueryMaker(request.SearchString, searchfields), request.Page * pageSize);
int count = hits.ScoreDocs.Count();
float temp_totalpages = 0;
temp_totalpages = (float)hits.ScoreDocs.Count() / (float)pageSize;
if (temp_totalpages > (int)temp_totalpages)
{
totalpages = (int)temp_totalpages + 1;
}
else
{
totalpages = (int)temp_totalpages;
}
foreach (ScoreDoc match in hits.ScoreDocs)
{
Document doc = searcher.Doc(match.Doc);
int producId = int.Parse(doc.Get("Id"));
Product product = Db.Select<Product>("Id={0}", producId).FirstOrDefault();
products.Add(product);
}
}
return new SearchIndexResult { result = products.Skip((int)((request.Page - 1) * 10)).Take(pageSize).ToList(), PageSize = pageSize, TotalPages = totalpages };
}
catch (Exception e)
{
return new HttpResult(HttpStatusCode.NoContent, "azureDirectory. Parameter: " + request.SearchString + ". e: " + e.Message);
}
}
If we run this locally it works as expected, returning us the results we were expecting. But when we published our API to Azure and tried to access to the search endpoint we received an 403 error message with the message 'Access to the path "D:/AzureDirectory" is denied".
We're confused as to why is trying to access to such folder at all, the name of the folder is wrong and I think it's trying to access a local route, we really don't know why does it work fine locally but once it's deployed to Azure it stops working.
The worker role runs without a problems, but it's the API side that cannot access to the folder in Azure Storage. Are we missing some important step in the configuration? The tutorial we followed wasn't very clear for beginners using Lucene.Net or Azure Storage so we fear we might have missed an important step. We've checked our connection strings and everything seems ok though.
As for reference:
https://github.com/azure-contrib/AzureDirectory/blob/master/AzureDirectory/AzureDirectory.cs
when you do this
azureDirectory = new AzureDirectory(Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(ConfigurationManager.AppSettings["ConnectionStringAzureSearch"]), "indexsearch");
This executes
var cachePath = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "AzureDirectory");
var azureDir = new DirectoryInfo(cachePath);
if (!azureDir.Exists)
azureDir.Create();
var catalogPath = Path.Combine(cachePath, _containerName);
var catalogDir = new DirectoryInfo(catalogPath);
if (!catalogDir.Exists)
catalogDir.Create();
_cacheDirectory = FSDirectory.Open(catalogPath);
So simple solution for you might be to have that directory on site root
DirectoryInfo info = new DirectoryInfo(HostingEnvironment.MapPath("~/"));
azureDirectory = new AzureDirectory(storageAccount, containerName, new SimpleFSDirectory(info), true);
I got it to work.
I just got the latest version of AzureDirectory from GitHub.
Got the latest nuGet packages for Azure Storage etc.
Recreated the index.
In addition to #brykneval answer, I tried his solution but last parameter bool compressBlob = false which he set to true made my local debug fail with 404 exception from AzureDirectory library and when I published to Azure web app, it had exception with message: System.IO.InvalidDataException: Block length does not match with its complement.
I removed last parameter from constructor and everything works like a charm. Hope this helps anyone.

Upload a file to a document library in SharePoint 2010 programmatically in client-server application

I am using below code to upload the file in SharePoint 2010 Library
String fileToUpload = #"C:\YourFile.txt";
String sharePointSite = "http://yoursite.com/sites/Research/";
String documentLibraryName = "Shared Documents";
using (SPSite oSite = new SPSite(sharePointSite))
{
using (SPWeb oWeb = oSite.OpenWeb())
{
if (!System.IO.File.Exists(fileToUpload))
throw new FileNotFoundException("File not found.", fileToUpload);
SPFolder myLibrary = oWeb.Folders[documentLibraryName];
// Prepare to upload
Boolean replaceExistingFiles = true;
String fileName = System.IO.Path.GetFileName(fileToUpload);
FileStream fileStream = File.OpenRead(fileToUpload);
// Upload document
SPFile spfile = myLibrary.Files.Add(fileName, fileStream, replaceExistingFiles);
// Commit
myLibrary.Update();
}
}
This worked well through my machine. But when I deploy it on server and used the below snippet to upload file in library from my machine, it gives error. It is not getting the file location (C:\YourFile.txt) from local(client) machine.
When you run on the server your code runs under a different account (apppool identity) which does not have the permission to read C drive.
I dont know why would you want to read and upload a file from the same server, looks like you are simply testing Sharepoint Object Model then it is ok
If you are expecting some other app or service to keep an updated file for Sharepoint , it should be moved to the web directory i.e \wwwroot\wss\VirtualDirectories\80 and then use your code to read and update your doc lib (myLibrary) as you are doing.
Are you running this in a console app or "in SharePoint"?
Could it be that the account running the code doesnt have read permissions in C:\?

Headless browsers and Windows Azure Websites

I´m trying to use a headless browser for crawling purposes to add SEO features in a open source project i´m developing.
The project sample site is deployed via Azure Websites.
I tried several ways to get the task working using different solutions like Selenium .NET (PhantomJSDriver, HTMLUnitDriver, ...) or even standalone PhantomJs .exe file.
I´m using a headless browser because the site is based in DurandalJS, so it needs to execute scripts and wait for a condition to be true in order to return the generated HTML. For this reason, can´t use things like WebClient/WebResponse classes or HTMLAgilityPack which use to work just fine for non-javascript sites.
All the above methods works in my devbox localhost environment but the problem comes when uploading the site to Azure Websites. When using standalone phantomjs the site freezes when accessing the url endpoint and after a while return a HTTP 502 error. In case of using Selenium Webdriver i´m getting a
OpenQA.Selenium.WebDriverException: Unexpected error. System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:XXXX
I think the problem is with running .exe files in Azure and not with the code. I know it´s possible to run .exe files in Azure CloudServices via WebRole/WebWorkers but need to stay in Azure Websites for keep things simple.
It´s possible to run a headless browser in Azure Websites? Anyone have experience with this type of situation?
My code for the standalone PhantomJS solution is
//ASP MVC ActionResult
public ActionResult GetHTML(string url)
{
string appRoot = Server.MapPath("~/");
var startInfo = new ProcessStartInfo
{
Arguments = String.Format("{0} {1}", Path.Combine(appRoot, "Scripts\\seo\\renderHTML.js"), url),
FileName = Path.Combine(appRoot, "bin\\phantomjs.exe"),
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
StandardOutputEncoding = System.Text.Encoding.UTF8
};
var p = new Process();
p.StartInfo = startInfo;
p.Start();
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
ViewData["result"] = output;
return View();
}
// PhantomJS script
var resourceWait = 300,
maxRenderWait = 10000;
var page = require('webpage').create(),
system = require('system'),
count = 0,
forcedRenderTimeout,
renderTimeout;
page.viewportSize = { width: 1280, height: 1024 };
function doRender() {
console.log(page.content);
phantom.exit();
}
page.onResourceRequested = function (req) {
count += 1;
//console.log('> ' + req.id + ' - ' + req.url);
clearTimeout(renderTimeout);
};
page.onResourceReceived = function (res) {
if (!res.stage || res.stage === 'end') {
count -= 1;
//console.log(res.id + ' ' + res.status + ' - ' + res.url);
if (count === 0) {
renderTimeout = setTimeout(doRender, resourceWait);
}
}
};
page.open(system.args[1], function (status) {
if (status !== "success") {
//console.log('Unable to load url');
phantom.exit();
} else {
forcedRenderTimeout = setTimeout(function () {
//console.log(count);
doRender();
}, maxRenderWait);
}
});
and for the Selenium option
public ActionResult GetHTML(string url)
{
using (IWebDriver driver = new PhantomJSDriver())
{
driver.Navigate().GoToUrl(url);
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(30));
IWebElement myDynamicElement = wait.Until<IWebElement>((d) =>
{
return d.FindElement(By.CssSelector("#compositionComplete"));
});
var content = driver.PageSource;
driver.Quit();
return Content(content);
}
}
Thanks!!
You cannot execute exe files in the shared website environment, either you have to use the web services or you have to set up a proper (azure) virtual machine.
The free shared website service is really basic, and won't cut it when you need more advanced functionality.
See this question and accepted answer for a more elaborated answer: Can we run windowservice or EXE in Azure website or in Virtual Machine?
I am not sure about shared and basic website environment but i am successfully run ffmpeg.exe from standart website environment. Despite that still phantomjs and even chromedriver itself is not working.
However i am able run Firefox driver successfully. In order to do that
I copied latest firefox directory from my local to website and below code worked well.
var binary = new FirefoxBinary("/websitefolder/blabla/firefox.exe");
var driver = new FirefoxDriver(binary, new FirefoxProfile());
driver.Navigate().GoToUrl("http://www.google.com");

How can I get the WebRole site root path from RoleEntryPoint.OnStart()?

As part of starting up a WebRole on Windows Azure I would like to access files on the website being started and I would like to do this in RoleEntryPoint.OnStart(). This will for instance enable me to influence ASP.NET config before the ASP.NET AppDomain is loaded.
When running locally with Azure SDK 1.3 and VS2010 the sample code below do the trick, but the code has the stench of hack around it and it does not do the trick when deploying to Azure.
XNamespace srvDefNs = "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition";
DirectoryInfo di = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
string roleRoot = di.Parent.Parent.FullName;
XDocument roleModel = XDocument.Load(Path.Combine(roleRoot, "RoleModel.xml"));
var propertyElements = roleModel.Descendants(srvDefNs + "Property");
XElement sitePhysicalPathPropertyElement = propertyElements.Attributes("name").Where(nameAttr => nameAttr.Value == "SitePhysicalPath").Single().Parent;
string pathToWebsite = sitePhysicalPathPropertyElement.Attribute("value").Value;
How can I get the WebRole site root path from RoleEntryPoint.OnStart() in a way that work in both dev and on Azure?
This seem to work in both dev and on Windows Azure:
private IEnumerable<string> WebSiteDirectories
{
get
{
string roleRootDir = Environment.GetEnvironmentVariable("RdRoleRoot");
string appRootDir = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
XDocument roleModelDoc = XDocument.Load(Path.Combine(roleRootDir, "RoleModel.xml"));
var siteElements = roleModelDoc.Root.Element(_roleModelNs + "Sites").Elements(_roleModelNs + "Site");
return
from siteElement in siteElements
where siteElement.Attribute("name") != null
&& siteElement.Attribute("name").Value == "Web"
&& siteElement.Attribute("physicalDirectory") != null
select Path.Combine(appRootDir, siteElement.Attribute("physicalDirectory").Value);
}
}
If anyone use this to manipulate files in the ASP.NET app, you should know that the files written by RoleEntryPoint.OnStart() will have ACL settings that prevent the ASP.NET application from updating them.
If you need to write to such files from ASP.NET this code show how you can change file permissions so this is possible:
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
IdentityReference act = sid.Translate(typeof(NTAccount));
FileSecurity sec = File.GetAccessControl(testFilePath);
sec.AddAccessRule(new FileSystemAccessRule(act, FileSystemRights.FullControl, AccessControlType.Allow));
File.SetAccessControl(testFilePath, sec);
Take a look at:
Environment.GetEnvironmentVariable("RoleRoot")
Does that give you what you're looking for?

Resources