Running console command from inside MVC controller not getting output - c#

We have an MVC web app that allows downloading dynamically generated PDF reports. I am trying to allow viewing the report in the browser, and because of browser compatibility issues, we can't use a JS PDF viewer, so am working on a controller action that generated the PDF using existing code, then converts it to HTML using a third party program and returns the HTML.
The third party program, pdf2htmlEX, is used via a command line interface, but when I try to invoke the program to convert the PDF to HTML nothing happens. I do not receive an error, but no HTML file is generated.
I first tried just a single line to start the conversion Process.Start("commands here"), but when that didn't work I tried a more advanced way to start the process and allow capturing the StdOut found on this answer: How To: Execute command line in C#, get STD OUT results, but I don't seem to be getting any output either. I am not familiar with invoking command line programs using c#, so I am not sure if I am making a simple mistake. My current controller action looks like this:
public ActionResult GetReportPdfAsHtml(int userId, ReportType reportType, int page = 1)
{
// get pdf
var pdfService = new PdfServiceClient();
var getPdfResponse = pdfService.GetPdfForUser(new GetPdfForUserRequest {
UserId = userId,
ReportType = reportType,
BaseUri = Request.Url.Host
});
pdfService.Close();
// save pdf to temp location
var folderRoot = Server.MapPath("~");
var location = Path.Combine(folderRoot, "pdfTemp");
var outputDir = $"{location}\\output";
var fileName = $"{userId}_{reportType}";
Directory.CreateDirectory(outputDir);
var file = $"{location}\\{fileName}.pdf";
//IOFile is alias of system.IO.File to avoid collision with the 'File' Method already on the controller
IOFile.WriteAllBytes(file, getPdfResponse.Pdf);
//********************************************************************
//***** Works fine up to here, PDF is successfully generated and saved
//********************************************************************
// Convert pdf above to html
var arguments = $"{file} --dest-dir {outputDir} -f {page} -l {page}";
// Start the child process.
var p = new Process {
StartInfo = {
UseShellExecute = false,
RedirectStandardOutput = true,
FileName = Server.MapPath("~\\pdf2htmlEX.exe"),
Arguments = arguments
}
};
p.Start();
// Read the output stream first and then wait.
var output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
// Function continues and returns fine, but MVC then errors because the
// file isn't created so the path below doesn't exist
return File($"{outputDir}\\{fileName}.html", "text/html");
}
Update: I have tried running the command in a cmd console and it works fine. However when I try and run it via the Process.Start() method, i get following output from Pdf2htmlEX:
>temporary dir: [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244
>Preprocessing: 0/1
>Preprocessing: 1/1
>Add new temporary file: [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244/__css
>Add new temporary file: [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244/__outline
>Add new temporary file: [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244/__pages
>Working: 0/1
>Install font 1: (14 0) SUBSET+LatoLightItalic
>Add new temporary file: [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244/f1.ttf
>Embed font: [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244/f1.ttf 1
>Add new temporary file: [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244/__raw_font_1.ttf
>em size: 2000
>Add new temporary file: [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244/f1.map
>Missing space width in font 1: set to 0.5
>space width: 0.5
>Add new temporary file: [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244/__tmp_font1.ttf
>Add new temporary file: [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244/__tmp_font2.ttf
>Internal Error: Attempt to output 2147483647 into a 16-bit field. It will be truncated and the file may not be useful.
>Internal Error: File Offset wrong for ttf table (name-data), -1 expected 150
>Save Failed
>Cannot save font to [Redacted]\pdfTemp\temp/pdf2htmlEX-a46244/__tmp_font1.ttf

Related

Can I store an Ini file in a Resources file?

I have a Windows Forms application, .Net Framework 4.6.1, and I want to store some DB connection data in an Ini file.
I then wanted to store it in the Resources file of the project (so I don't have to copy/paste the file in the Debug and Release folder manually, etc.) as a normal file, but when I tried to compile the program and read the Ini data with ini-parser, the following exception showed up: System.ArgumentException: 'Invalid characters in path access'.
I'm using Properties.Resources where I read the Ini file, so I guessed there would be no problem with the path. Could it be a problem with the Ini file itself?
The content of the Ini file is the following:
[Db]
host = (anIP)
port = (aPort)
db = (aDbName)
user = (aDbUser)
password = (aDbUserPwd)
And my method for reading the data:
public static void ParseIniData()
{
var parser = new FileIniDataParser();
IniData data = parser.ReadFile(Properties.Resources.dbc);
mysqlHost = data["Db"]["host"];
mysqlPort = data["Db"]["port"];
mysqlDb = data["Db"]["db"];
mysqlUser = data["Db"]["user"];
mysqlPwd = data["Db"]["password"];
}
I finally could do it using what #KlausGütter told me in the comments (thanks!).
Instead of using the FileIniDataParser you have to use the StreamIniDataParser, and get the Stream with Assembly.GetManifestResourceStream.
I found this a bit tricky, because using this method you need to set the Build Action in the file you want to read to Embedded Resource.
This file is then added as an embedded resource in compile time and you can retrieve its stream.
So my method ended up the following way:
public static void ParseIniData()
{
var parser = new StreamIniDataParser();
dbcReader = new StreamReader(_Assembly.GetManifestResourceStream("NewsEditor.Resources.dbc.ini"));
IniData data = parser.ReadData(dbcReader);
mysqlHost = data["Db"]["host"];
mysqlPort = data["Db"]["port"];
mysqlDb = data["Db"]["db"];
mysqlUser = data["Db"]["user"];
mysqlPwd = data["Db"]["password"];
}
where _Assembly is a private static attribute: private static Assembly _Assembly = Assembly.GetExecutingAssembly();. This gets you the assembly that's being executed when running the code (you could also use this code directly in the method, but I used the Assembly on another method in my class, so I decided to set an attribute... DRY I guess).

C# Directory.GetCurrentDirectory()

I have windows form app with the following part of code when the Form Loads
public MonitorMail()
{
InitializeComponent();
pathfile = Directory.GetCurrentDirectory();
pathfile = pathfile + #"\Log\Configuration.txt";
var Lista = LoadConfigFile.LoadConfig(pathfile);
if (Lista.Count > 0)
{
SwithMailText.Text = Lista[0];
Excel_Textbox.Text = Lista[1];
LogFileText.Text = Lista[2];
MailServerText.Text = Lista[3];
FromText.Text = Lista[4];
SslText.Text = Lista[5];
UserText.Text = Lista[6];
}
}
As you can see in this code i declare a List named as "Lista" which List takes the records of the Configuration file and fill some textboxes with the data of that Configuration file.
My problem is the following: when I run my program inside in Visual Studio, it loads the records correctly in those textboxes.
When I run my program runs outside of Visual Studio, it also loads the records correctly
BUT
When I try run my program from the command prompt (because this how it should be run) like MonitorMail.exe the program runs but does not show the data in the textboxes.
After trying to understand why is this happening I noticed that is has something to do with
pathfile = Directory.GetCurrentDirectory();
I concluded to that because I changed the pathfile to pathfile="complete path of the Configuration.txt" so when I hit it from cmd works as it should be.
Any idea why Directory.GetCurrentDirectory(); affects cmd? Or is something am I missing?
You wrote in the comments: "i need for every PC to get current directory that my .exe is", but that is not what Directory.GetCurrentDirectory() does...
You need
string myPath = System.Reflection.Assembly.GetEntryAssembly().Location;
instead. That gives you the full path including the file name. You can take the Location's Directory if that is what you need.

Execute Console Application with parameters from MVC 5 Controller

I have a MVC 5 controller and a C# console application executed like this:
lp c:\excel.xls /xls
I need to execute this line after I uploaded an XLS file using a Form:
[HttpPost, ValidateAntiForgeryToken]
public virtual JsonResult UploadXLS(HttpPostedFileBase XLSFile)
{
var uploadDir = Server.MapPath("~/App_Data/");
if (XLSFile != null)
{
var originalFileExtension = Path.GetExtension(XLSFile.FileName);
var fileName = Guid.NewGuid().ToString() + originalFileExtension;
var filePath = Path.Combine(uploadDir, fileName);
XLSFilePartners.SaveAs(filePath);
// EXECUTE THE CONSOLE PROJECT HERE
return Json("Uploaded!", "text/html");
}
return Json("No File!", "text/html");
}
To run a program, you can use Process.Start. You will need to supply the path to the executable and the parameters:
Process.Start("lp.exe", "c:\\excel.xls /xls");
If your command line arguments contain spaces (like the file path), you will need to enclose them in quotation marks (and escape those, since it is a string). Like this:
"\"c:\\path with spaces\\excel.xls\" /xls"
Note that this will only start the process - it doesn't wait until it's finished. If you need that, look at Process.WaitForExit.
For more info look at the MSDN page.

Docusign Retrieve via ProcessBuilder

I'm attempting to build a java process to execute the docusign retrieve product via the command line. I've written the process to execute based on a given property file.
buildRoot = isWindowsOs() ? "C:" + "\\Program Files (x86)\\DocuSign, Inc\\Retrieve 3.2" : "\\Program Files (x86)\\DocuSign, Inc\\Retrieve 3.2" ;
String[] command = new String [2];
command[0] = "\""+buildRoot+ "\\" + docuSignAppName+"\"";
logger.info(command[0].toString());
//ADDED FOR EXPLANATION - "C:\Program Files (x86)\DocuSign, Inc\Retrieve 3.2\DocuSignRetrieve.exe"
command[1] = arguments;
logger.info(command[1].toString());
ProcessBuilder processBuilder = new ProcessBuilder(command);
logger.info("ProcessBuilder starting directory" +processBuilder.directory());
processBuilder.redirectErrorStream(true);
p = processBuilder.start();
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
stdout = new BufferedReader(isr);
Once I pass in the built out string of parameters the executed code looks like the sample provided but always results in the error back to the screen "Missing "accountid" parameter".
The parameter list looks like the following.
/endpoint "Demo"
/userid "REMOVED"
/password "REMOVED"
/accountid "REMOVED"
/span "-1"
/spanfilter "Completed"
/statusfilter "Completed"
/fieldlog "LIST OF FIELDS"
/nstyle "EnvelopeID"
/save "MergedPdfWithoutCert"
/dir "D:\DocuSignStore"
/includeheaders "true"
Any help or assistance would be appreciated.
The solution was found in a StackOverflow discussion regarding common issues with the ProcessBuilder.
My problem was that I expected by changing the putting in the full path, that I could run the executable. For the reason I'm not sure right now, that wasn't working as expected. The solution was to run the CMD command which exists on the %PATH% on any windows OS.
String[] command = new String [2];
command[0] = "\""+buildRoot+ "\\" + docuSignAppName+"\"";
logger.info(command[0].toString());
//ADDED FOR EXPLANATION - "C:\Program Files (x86)\DocuSign, Inc\Retrieve 3.2\DocuSignRetrieve.exe"
command[1] = arguments;
logger.info(command[1].toString());
//This starts a new command prompt
ProcessBuilder processBuilder = new ProcessBuilder("cmd","/c","DocusignRetreive.exe);
//This sets the directory to run the command prompt from
File newLoc = new File("C:/Program Files (x86)/DocuSign, Inc/Retrieve 3.2");
processBuilder.directory(newLoc);
logger.info("ProcessBuilder starting directory" +processBuilder.directory());
processBuilder.redirectErrorStream(true);
/*When the process builder starts the prompt looks like
*C:\Program Files (x86)\DocuSign, Inc\Retrieve 3.2
*Now DocusignRetrieve.exe is an executable in the directory to be run
*/
p = processBuilder.start();

IManExt ImportCmd trouble

I have been writing a small application using C# to copy a document into an individuals 'My Documents' folder on our DMS server.
I've beased the code around the listing provided in the 'WorkSite SDK 8: Utilize the IMANEXT2Lib.IManRefileCmd to File New Document Folders' blog.
Using this code in a WinForm application I have no problems copying the file from the source folder into the users DMS 'My Documents' folder.
However if I use the code in a command line application/.dll or any other type of application (other than WinForm) during the copy process I receive the error messages;
1.
Error occurred when try to log the event!
IManExt: Error occurred when try to log the event!
Access is denied.
2.
The document was imported to the database, but could not be added to
the folder.
IManExt: The document was imported to the database, but could not be
added to the folder.
IManExt.LogRuleEventsCmd.1: Error occurred when try to log the event!
IManExt.LogRuleEventsCmd.1: Access is denied.
Error occurred when try to log the event!
-%-
Does anyone know why I'd receiving the 'Access Denied' error messages when using a non-WinForms application to copy documents?
What would I need to do to get around this issue?
Any help would be amazing!
Code in place:
public void moveToDMS(String servName, String dBName, String foldName)
{
const string SERVERNAME = servName; //Server name
const string DATABASENAME = dBName; //Database name
const string FOLDERNAME = foldName; //Matter alias of workspace
IManDMS dms = new ManDMSClass();
IManSession sess = dms.Sessions.Add(SERVERNAME);
sess.TrustedLogin();
//Get destination database.
IManDatabase db = sess.Databases.ItemByName(DATABASENAME);
//Get destination folder by folder and owner name.
IManFolderSearchParameters fparms = dms.CreateFolderSearchParameters();
fparms.Add(imFolderAttributeID.imFolderOwner, sess.UserID);
fparms.Add(imFolderAttributeID.imFolderName, FOLDERNAME);
//Build a database list in which to search.
ManStrings dblist = new ManStringsClass();
dblist.Add(db.Name);
IManFolders results = sess.WorkArea.SearchFolders(dblist, fparms);
if (results.Empty == true)
{
//No results returned based on the search criteria.
Console.WriteLine("NO RESULTS FOUND!");
}
IManDocumentFolder fldr = null;
if (results.Empty == false)
{
//Assuming there is only one workspace returned from the results.
fldr = (IManDocumentFolder)results.ItemByIndex(1);
}
if (fldr != null)
{
// Import file path
string docPath = #"C:\Temp\";
string docName = "MyWord.doc";
// Create an instance of the ContextItems Collection Object.
ContextItems context = new ContextItemsClass();
// Invoke ImportCmd to import a new document to WorkSite database.
ImportCmd impCmd = new ImportCmdClass();
// The WorkSite object you pass in can be a database, session, or folder.
// Depends on in where you want the imported doc to be stored.
context.Add("IManDestinationObject", fldr); //The destination folder.
// Filename set here is used for easy example, a string variable is normally used here
context.Add("IManExt.Import.FileName", docPath + docName);
// Document Author
context.Add("IManExt.Import.DocAuthor", sess.UserID); //Example of a application type.
// Document Class
context.Add("IManExt.Import.DocClass", "BLANK"); //Example of a document class.
//context.Add("IManExt.Import.DocClass", "DOC"); //Example of a document class.
// Document Description (optional)
context.Add("IManExt.Import.DocDescription", docName); //Using file path as example of a description.
// Skip UI
context.Add("IManExt.NewProfile.ProfileNoUI", true);
impCmd.Initialize(context);
impCmd.Update();
if (impCmd.Status == (int)CommandStatus.nrActiveCommand)
{
impCmd.Execute();
bool brefresh = (bool)context.Item("IManExt.Refresh");
if (brefresh == true)
{
//Succeeded in importing a document to WorkSite
IManDocument doc = (IManDocument)context.Item("ImportedDocument");
//Succeeded in filing the new folder under the folder.
Console.WriteLine("New document number, " + doc.Number + ", is successfully filed to " + fldr.Name + " folder.");
}
}
}
}
Just in case this helps someone else.
It seems my issue was the result of a threading issue.
I noticed the C# winform apps I had created were automatically set to run on a single 'ApartmentState' thread ([STAThread]).
Whereas the console applications & class library thread state and management hadn't been defined within the project and was being handled with the default .NET config.
To get this to work: In the console application, I just added the [STAThread] tag on the line above my Main method call.
In the class library, I defined a thread for the function referencing the IMANxxx.dll and set ApartmentState e.g.
Thread t = new Thread(new ThreadStart(PerformSearchAndMove));
t.SetApartmentState(ApartmentState.STA);
t.Start();
In both cases ensuring single 'ApartmentState' thread was implemented set would resolve the issue.

Categories