I am using TuesPechkin in one of Service Fabric services to generate PDF files from HTML. This is working fine on my local development computer, but as soon as I deploy my application to an Azure cluster and run the same code I get the following error message:
Unable to load DLL 'wkhtmltox.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)
I think this might be because TuesPechkin relies on the Visual C++ 2013 runtime. Is this supported in a Azure cluster? Or is there something else going wrong?
My code for generating PDF's looks like this:
private static readonly IConverter _converter = new ThreadSafeConverter(
new PdfToolset(
new Win64EmbeddedDeployment(
new AssemblyFolderDeployment())));
public Task<byte[]> GeneratePdfFromHtml(string title, string html)
{
try
{
var document = new HtmlToPdfDocument
{
GlobalSettings = {
ProduceOutline = true,
DocumentTitle = title,
PaperSize = PaperKind.A4, // Implicit conversion to PechkinPaperSize
Margins = {
All = 1.375,
Unit = Unit.Centimeters
}
},
Objects = {
new ObjectSettings { HtmlText = html }
}
};
byte[] result = _converter.Convert(document);
return Task.FromResult(result);
}
catch(Exception e)
{
throw e;
}
}
AssemblyFolderDeployment.cs
public class AssemblyFolderDeployment : IDeployment
{
public string Path
{
get
{
return System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Wkhtml");
}
}
}
Related
I developed a command line app in C# that runs some macros in MS ACCESS but I am getting the following error in the production environment:
System.Runtime.InteropServices.COMException (0x800A09B6): You canĀ“t carry out this action at the present time.
at Microsoft.Office.Interop.Access.DoCmd.RunMacro(Object MacroName, Object RepeatCount, Object RepeatExpression)
When I run the application again it works fine.
This is the code:
public void RunMacros()
{
Application access = null;
_runningMacros = true;
CloseMessageBox("Microsoft Access"); // Thread to close MsgBoxes
try
{
access = new Application();
access.AutomationSecurity = Microsoft.Office.Core.MsoAutomationSecurity.msoAutomationSecurityLow;
var databases = MdbSettings.Config.Databases;
for (var mdbIndex = 0; mdbIndex < databases.Count; mdbIndex++)
{
if (databases[mdbIndex].Type == ReportType)
{
var mdbFile = databases[mdbIndex].File;
if (mdbFile.StartsWith("OcnTs*", StringComparison.InvariantCultureIgnoreCase))
{
mdbFile = mdbFile.Replace("*", Settings.DateReplaceName);
}
var mdbFullPath = GetFileFullPath(mdbFile, MdbPath);
access.OpenCurrentDatabase(mdbFullPath, Settings.IsDbExclusive);
for (var macrosIndex = 0; macrosIndex < databases[mdbIndex].Macros.Count; macrosIndex++)
{
var macro = databases[mdbIndex].Macros[macrosIndex].Name;
var message = string.Format("{0}, macro: {1}", Path.GetFileName(mdbFullPath), macro);
Log.Write(message);
access.DoCmd.RunMacro(macro);
}
access.CloseCurrentDatabase();
}
}
_runningMacros = false;
access.Quit(Microsoft.Office.Interop.Access.AcQuitOption.acQuitSaveNone);
Marshal.ReleaseComObject(access);
access = null;
}
catch (Exception ex)
{
Log.Write(ex.ToString());
try
{
Marshal.ReleaseComObject(access);
}
catch (Exception e)
{
Log.Write("Error realeasing Access application: " + e.ToString(), Log.Severity.Warning);
}
throw;
}
}
Can someone help me to fix the error?
EDITED:
- The error only occurs in production environment
- The production environment has installed MS Access 2010
- The development environment has installed MS Access 2016
- Every macro run between 6 and 20 queries
- The error does not always occur in the same macro
I read Excel files using OpenXml. all work fine but if the spreadsheet contains one cell that has an address mail and after it a space and another word, such as:
abc#abc.com abc
It throws an exception immediately at the opening of the spreadsheet:
var _doc = SpreadsheetDocument.Open(_filePath, false);
exception:
DocumentFormat.OpenXml.Packaging.OpenXmlPackageException
Additional information:
Invalid Hyperlink: Malformed URI is embedded as a
hyperlink in the document.
There is an open issue on the OpenXml forum related to this problem: Malformed Hyperlink causes exception
In the post they talk about encountering this issue with a malformed "mailto:" hyperlink within a Word document.
They propose a work-around here: Workaround for malformed hyperlink exception
The workaround is essentially a small console application which locates the invalid URL and replaces it with a hard-coded value; here is the code snippet from their sample that does the replacement; you could augment this code to attempt to correct the passed brokenUri:
private static Uri FixUri(string brokenUri)
{
return new Uri("http://broken-link/");
}
The problem I had was actually with an Excel document (like you) and it had to do with a malformed http URL; I was pleasantly surprised to find that their code worked just fine with my Excel file.
Here is the entire work-around source code, just in case one of these links goes away in the future:
void Main(string[] args)
{
var fileName = #"C:\temp\corrupt.xlsx";
var newFileName = #"c:\temp\Fixed.xlsx";
var newFileInfo = new FileInfo(newFileName);
if (newFileInfo.Exists)
newFileInfo.Delete();
File.Copy(fileName, newFileName);
WordprocessingDocument wDoc;
try
{
using (wDoc = WordprocessingDocument.Open(newFileName, true))
{
ProcessDocument(wDoc);
}
}
catch (OpenXmlPackageException e)
{
e.Dump();
if (e.ToString().Contains("The specified package is not valid."))
{
using (FileStream fs = new FileStream(newFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
UriFixer.FixInvalidUri(fs, brokenUri => FixUri(brokenUri));
}
}
}
}
private static Uri FixUri(string brokenUri)
{
brokenUri.Dump();
return new Uri("http://broken-link/");
}
private static void ProcessDocument(WordprocessingDocument wDoc)
{
var elementCount = wDoc.MainDocumentPart.Document.Descendants().Count();
Console.WriteLine(elementCount);
}
}
public static class UriFixer
{
public static void FixInvalidUri(Stream fs, Func<string, Uri> invalidUriHandler)
{
XNamespace relNs = "http://schemas.openxmlformats.org/package/2006/relationships";
using (ZipArchive za = new ZipArchive(fs, ZipArchiveMode.Update))
{
foreach (var entry in za.Entries.ToList())
{
if (!entry.Name.EndsWith(".rels"))
continue;
bool replaceEntry = false;
XDocument entryXDoc = null;
using (var entryStream = entry.Open())
{
try
{
entryXDoc = XDocument.Load(entryStream);
if (entryXDoc.Root != null && entryXDoc.Root.Name.Namespace == relNs)
{
var urisToCheck = entryXDoc
.Descendants(relNs + "Relationship")
.Where(r => r.Attribute("TargetMode") != null && (string)r.Attribute("TargetMode") == "External");
foreach (var rel in urisToCheck)
{
var target = (string)rel.Attribute("Target");
if (target != null)
{
try
{
Uri uri = new Uri(target);
}
catch (UriFormatException)
{
Uri newUri = invalidUriHandler(target);
rel.Attribute("Target").Value = newUri.ToString();
replaceEntry = true;
}
}
}
}
}
catch (XmlException)
{
continue;
}
}
if (replaceEntry)
{
var fullName = entry.FullName;
entry.Delete();
var newEntry = za.CreateEntry(fullName);
using (StreamWriter writer = new StreamWriter(newEntry.Open()))
using (XmlWriter xmlWriter = XmlWriter.Create(writer))
{
entryXDoc.WriteTo(xmlWriter);
}
}
}
}
}
The fix by #RMD works great. I've been using it for years. But there is a new fix.
You can see the fix here in the changelog for issue #793
Upgrade OpenXML to 2.12.0.
Right click solution and select Manage NuGet Packages.
Implement the fix
It is helpful to have a unit test. Create an excel file with a bad email address like test#gmail,com. (Note the comma instead of the dot).
Make sure the stream you open and the call to SpreadsheetDocument.Open allows Read AND Write.
You need to implement a RelationshipErrorHandlerFactory and use it in the options when you open. Here is the code I used:
public class UriRelationshipErrorHandler : RelationshipErrorHandler
{
public override string Rewrite(Uri partUri, string id, string uri)
{
return "https://broken-link";
}
}
Then you need to use it when you open the document like this:
var openSettings = new OpenSettings
{
RelationshipErrorHandlerFactory = package =>
{
return new UriRelationshipErrorHandler();
}
};
using var document = SpreadsheetDocument.Open(stream, true, openSettings);
One of the nice things about this solution is that it does not require you to create a temporary "fixed" version of your file and it is far less code.
Unfortunately solution where you have to open file as zip and replace broken hyperlink would not help me.
I just was wondering how it is posible that it works fine when your target framework is 4.0 even if your only installed .Net Framework has version 4.7.2.
I have found out that there is private static field inside System.UriParser that selects version of URI's RFC specification. So it is possible to set it to V2 as it is set for .net 4.0 and lower versions of .Net Framework. Only problem that it is private static readonly.
Maybe someone will want to set it globally for whole application. But I wrote UriQuirksVersionPatcher that will update this version and restore it back in Dispose method. It is obviously not thread-safe but it is acceptable for my purpose.
using System;
using System.Diagnostics;
using System.Reflection;
namespace BarCap.RiskServices.RateSubmissions.Utility
{
#if (NET20 || NET35 || NET40)
public class UriQuirksVersionPatcher : IDisposable
{
public void Dispose()
{
}
}
#else
public class UriQuirksVersionPatcher : IDisposable
{
private const string _quirksVersionFieldName = "s_QuirksVersion"; //See Source\ndp\fx\src\net\System\_UriSyntax.cs in NexFX sources
private const string _uriQuirksVersionEnumName = "UriQuirksVersion";
/// <code>
/// private enum UriQuirksVersion
/// {
/// V1 = 1, // RFC 1738 - Not supported
/// V2 = 2, // RFC 2396
/// V3 = 3, // RFC 3986, 3987
/// }
/// </code>
private const string _oldQuirksVersion = "V2";
private static readonly Lazy<FieldInfo> _targetFieldInfo;
private static readonly Lazy<int?> _patchValue;
private readonly int _oldValue;
private readonly bool _isEnabled;
static UriQuirksVersionPatcher()
{
var targetType = typeof(UriParser);
_targetFieldInfo = new Lazy<FieldInfo>(() => targetType.GetField(_quirksVersionFieldName, BindingFlags.Static | BindingFlags.NonPublic));
_patchValue = new Lazy<int?>(() => GetUriQuirksVersion(targetType));
}
public UriQuirksVersionPatcher()
{
int? patchValue = _patchValue.Value;
_isEnabled = patchValue.HasValue;
if (!_isEnabled) //Disabled if it failed to get enum value
{
return;
}
int originalValue = QuirksVersion;
_isEnabled = originalValue != patchValue;
if (!_isEnabled) //Disabled if value is proper
{
return;
}
_oldValue = originalValue;
QuirksVersion = patchValue.Value;
}
private int QuirksVersion
{
get
{
return (int)_targetFieldInfo.Value.GetValue(null);
}
set
{
_targetFieldInfo.Value.SetValue(null, value);
}
}
private static int? GetUriQuirksVersion(Type targetType)
{
int? result = null;
try
{
result = (int)targetType.GetNestedType(_uriQuirksVersionEnumName, BindingFlags.Static | BindingFlags.NonPublic)
.GetField(_oldQuirksVersion, BindingFlags.Static | BindingFlags.Public)
.GetValue(null);
}
catch
{
#if DEBUG
Debug.WriteLine("ERROR: Failed to find UriQuirksVersion.V2 enum member.");
throw;
#endif
}
return result;
}
public void Dispose()
{
if (_isEnabled)
{
QuirksVersion = _oldValue;
}
}
}
#endif
}
Usage:
using(new UriQuirksVersionPatcher())
{
using(var document = SpreadsheetDocument.Open(fullPath, false))
{
//.....
}
}
P.S. Later I found that someone already implemented this pathcher: https://github.com/google/google-api-dotnet-client/blob/master/Src/Support/Google.Apis.Core/Util/UriPatcher.cs
I haven't use OpenXml but if there's no specific reason for using it then I highly recommend LinqToExcel from LinqToExcel. Example of code is here:
var sheet = new ExcelQueryFactory("filePath");
var allRows = from r in sheet.Worksheet() select r;
foreach (var r in allRows) {
var cella = r["Header"].ToString();
}
I have an RDL file designed in ReportBuilder 2. I need to show it in my C# application. I know there is an RdlViewer example at gotreportviewer.com but it is not what I really want (For example it can not handle parameters with multiple values). Instead, I'm curious about the way ReportBuilder itself shows report previews. Looking at its installation directory, I found several interesting DLLs. Using ILSpy, I saw that inside MSReportBuilder.exe, there is a method called "StartPreview()" which is called when you the "Run" button in ReportBuilder. Here is the code:
private void StartPreview()
{
try
{
if (!this.m_layoutEditor.AtBookmark("ReportPreview"))
{
using (MemoryStream memoryStream = new MemoryStream())
{
this.m_layoutEditor.CommitPendingChanges(false);
this.m_preview.Reset();
Report report;
if (this.ActiveServer != null)
{
this.m_preview.ProcessingMode = ProcessingMode.Remote;
report = this.m_preview.ServerReport;
string text = this.ActiveServer.ServerUrl;
if (this.ActiveServer.IsInSharePointMode)
{
text = Util.RemoveTrailingSlash(text) + "/_vti_bin/reportserver";
}
AuthenticationInfo authenticationInfo = this.ActiveServer.GetAuthenticationInfo();
if (authenticationInfo.IsFormsAuth)
{
this.m_preview.ServerReport.ReportServerCredentials.SetFormsCredentials(authenticationInfo.AuthCookie, authenticationInfo.UserName, authenticationInfo.Password, authenticationInfo.Domain);
}
this.m_preview.ServerReport.ReportServerUrl = new Uri(text);
using (this.m_layoutEditor.UndoManager.BeginTempGroup())
{
Report rdlObject = this.m_layoutEditor.Report.GetRdlObject();
if (this.CurrentReportInfo.IsServerReport)
{
this.m_reportProject.ResolveServerReferences(rdlObject);
}
RdlSerializer rdlSerializer = LayoutEditor.CreateSerializer();
rdlSerializer.Serialize(memoryStream, rdlObject);
}
memoryStream.Position = 0L;
this.m_preview.ServerReport.LoadReportDefinition(memoryStream);
DataSourceCredentials[] storedCredentialsForCurrentReport = this.GetStoredCredentialsForCurrentReport();
if (storedCredentialsForCurrentReport.Length > 0)
{
this.m_preview.ServerReport.SetDataSourceCredentials(storedCredentialsForCurrentReport);
}
}
else
{
this.m_preview.ProcessingMode = ProcessingMode.Local;
report = this.m_preview.LocalReport;
using (this.m_layoutEditor.UndoManager.BeginTempGroup())
{
Report rdlObject2 = this.m_layoutEditor.Report.GetRdlObject();
this.m_reportProject.FixUpDataSourceCredentialsForLocalPreview(rdlObject2);
RdlSerializer rdlSerializer2 = LayoutEditor.CreateSerializer();
rdlSerializer2.Serialize(memoryStream, rdlObject2);
}
memoryStream.Position = 0L;
this.m_preview.LocalReport.LoadReportDefinition(memoryStream);
DataSourceCredentials[] storedCredentialsForCurrentReport2 = this.GetStoredCredentialsForCurrentReport();
if (storedCredentialsForCurrentReport2.Length > 0)
{
bool flag;
this.m_preview.LocalReport.GetDataSources(out flag);
if (!flag)
{
this.m_preview.LocalReport.SetDataSourceCredentials(storedCredentialsForCurrentReport2);
}
}
}
report.DisplayName = this.CurrentReportInfo.ReportName;
this.m_preview.RefreshReport();
this.m_layoutEditor.SetBookmark("ReportPreview");
}
}
}
catch (Exception ex)
{
string message;
this.m_reportProject.ParseException(ex, out message);
ErrorDialog.Show(this, Strings.Error_Preview, message, ex);
this.SetDisplayMode(DisplayMode.Design);
}
}
Interesting point is that the code for server-side processing is very clear. But I can't understand how the client-side processing (my need) works! Can you help me do the same thing in my application?
I'm using VS 2010 / .Net 4.0
I ran into a strange problem last week. A call to new XMLSerializer(typeof(MyType)) crashed with an ExternalException, telling me that csc.exe could not be executed.
After some investigation I found that this exception only occurs if the process environment size reaches a "critical" limit. I created a little sample application to verify that reason.
namespace EnvironmentTester
{
public class Program
{
private static void Main(string[] args)
{
FillProcessEnvironmentBlock(false);
SerializeDataObject();
}
private static void SerializeDataObject()
{
var dto = new DataObject {Name = "MyDto"};
try
{
var xmlSerializer = new XmlSerializer(dto.GetType()); // throws exception
xmlSerializer.Serialize(TextWriter.Null, dto);
Console.WriteLine("No exception occured.");
}
catch(Exception e)
{
Console.WriteLine("Exception occured : " + e.GetType());
}
Console.ReadKey();
}
private static void FillProcessEnvironmentBlock(bool fillToMax)
{
var currentEnvVarIndex = 0;
var environmentSize = GetEnvironmentSize();
int criticalEnvironmentSize = fillToMax ? 30692 : 30691;
while (environmentSize < criticalEnvironmentSize)
{
var envVarName = "Env" + currentEnvVarIndex;
var envVarValueLength = (criticalEnvironmentSize - environmentSize - envVarName.Length - 2) % 32000;
Environment.SetEnvironmentVariable(envVarName, new string('a', envVarValueLength));
currentEnvVarIndex++;
environmentSize = GetEnvironmentSize();
}
}
private static int GetEnvironmentSize()
{
var envVars = Environment.GetEnvironmentVariables();
int environmentSize = 0;
foreach (string envKey in envVars.Keys)
{
environmentSize += envKey.Length;
}
foreach (string envVar in envVars.Values)
{
environmentSize += envVar.Length;
}
environmentSize += 2*envVars.Keys.Count; // add the '=' and the '\0'
return environmentSize;
}
public class DataObject
{
[XmlAttribute("ObjectName")]
public string Name { get; set; }
}
}
}
If FillProcessEnvironmentBlock is called with parameter false, the critical size is not reached, if it's called with true, the ExternalException is thrown. I tested it on two different WindowsXP 32bit SP2 machines, with the same result.
I know that csc.exe is called to create a temporary assembly used to read/write the xml file. But I don't know why the call to csc.exe fails if the process environment is too large.
Does anyone know the reason for this exception? And how can I work around it (if I don't want to write my own xml serialization)? Are there any other known problems causeed by a process environment that's too large?
You can precompile serializers using Sgen tool or MSBuild Sgen task
I'm trying to convert a ML.NET app from win console to a UWP and I'm not loading the files into my ML pipeline. I'm getting an File Not Found error.
here is my code:
public static double ProcessDataBtn_Click(float tempOPS)
{
double rpg = 0;
var dataset = GetDataPathByDatasetName("OPSData.csv");
var testDataset = GetDataPathByDatasetName("OPSData-test.csv");
var pipeline = new LearningPipeline
{
new TextLoader(dataset).CreateFrom<OPSData>(useHeader: true, separator: ','),
new ColumnConcatenator("Features", "OPS"),
new GeneralizedAdditiveModelRegressor()
};
var model = pipeline.Train<OPSData, OPSPrediction>();
model.WriteAsync(GetModelFilePath("model.zip"));
Here is the get file code:
public static string GetDataPathByDatasetName(string datasetName)
{
var appPath = Path.GetDirectoryName(Environment.GetCommandLineArgs().First());
var parentDir = Directory.GetParent(appPath).Parent.Parent.Parent.Parent;
var datasetPath = Path.Combine(parentDir.FullName, "datasets", datasetName);
return datasetPath;
}
public static string GetModelFilePath(string fileName)
{
var appPath = Path.GetDirectoryName(Environment.GetCommandLineArgs().First());
var parentDir = Directory.GetParent(appPath).Parent.Parent.Parent.Parent;
var fileDir = Path.Combine(parentDir.FullName, "models");
if (!Directory.Exists(fileDir))
{
Directory.CreateDirectory(fileDir);
}
var filePath = Path.Combine(parentDir.FullName, "models", fileName);
return filePath;
}
And here are my objects.
public class OPSData
{
[Column("0")]
public float OPS;
[Column("1", name: "Label")]
public float RunsPerGame;
}
public class OPSPrediction
{
[ColumnName("Score")]
public float PredictedRPG;
}
I'getting the error on the following line:
var model = pipeline.Train();
Not the answer you were hoping for, but this is a known bug with newer versions of ML.NET:
https://github.com/dotnet/corefx/issues/33434
As a workaround for this bug, you'd have to stay with version 0.6.0 for now until this has been addressed.
Unfortunately, there is another bug you will likely hit if you try to release the app via Microsoft Store: https://github.com/dotnet/machinelearning/issues/1736 (you will see the bug in release builds, not in debug builds)