Execute Console Application with parameters from MVC 5 Controller - c#

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.

Related

Running console command from inside MVC controller not getting output

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

How do I execute and return the results of a python script in c#?

How do I execute and return the results of a python script in c#?
I am trying to run a python script from my controller.
I have python.exe setup in a virtual environment folder created with the virtualenv command.
So just for testing purposes at the moment I would like to just return resulting string from my phython script:
# myscript.py
print "test"
And display that in a view in my asp.net mvc app.
I got the run_cmd function from a related stackoverflow question.
I've tried adding the -i option to force interactive mode and calling process.WaitForExit() with no luck.
namespace NpApp.Controllers
{
public class HomeController : Controller
{
public ActionResult Index(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
ViewBag.textResult = run_cmd("-i C:/path/to/virtualenv/myscript.py", "Some Input");
return View();
}
private string run_cmd(string cmd, string args)
{
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"C:/path/to/virtualenv/Scripts/python.exe";
start.CreateNoWindow = true;
start.Arguments = string.Format("{0} {1}", cmd, args);
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
//Console.Write(result);
process.WaitForExit();
return result;
}
}
}
}
}
It seems like myscript.py never even runs. But I get no errors, just a blank variable in my view.
Edit:
I had tried to simplify the above stuff because I thought it would be easier to explain and get an answer. Eventually I do need to use a package called "nameparser" and store the result of passed name argument into a database. But if I can just get the run_cmd to return a string I think I can take care of the rest of it. This is why I think the rest api and IronPython mentioned in the comments may not work for me here.
Ok, I figured out what the issue was thanks to some leads from the comments. Mainly it was the spaces in the path to the python.exe and the myscript.py. Turns out I didn't need -i or process.WaitForExit(). I just moved the python virtual environment into a path without spaces and everything started working. Also made sure that the myscript.py file was executable.
This was really helpful:
string stderr = process.StandardError.ReadToEnd();
string stdout = process.StandardOutput.ReadToEnd();
Debug.WriteLine("STDERR: " + stderr);
Debug.WriteLine("STDOUT: " + stdout);
That shows the python errors and output in the Output pane in Visual Studio.

How to combine address for web or file system?

In C# I must create a method that receives as parameters webPath or fileSystemPath plus a file name. Consider that this method is going to be called from a asp.net and also from a windows form project.
This are the cases I assume that could be, and the code I wrote so far:
string webPath1 = "//someaddress";
string webPath2 = "//someaddress/";
string fsPath1 = #"\\somefolder";
string fsPath2 = #"\\somefolder\";
string filename = "somefilename.txt";
string WebPathFileName1 = System.IO.Path.Combine(webPath1, filename);
string WebPathFileName2 = System.IO.Path.Combine(webPath2, filename);
string s1 = Path.GetFullPath(WebPathFileName1);
string FsPathFileName1 = System.IO.Path.Combine(fsPath1, filename);
string FsPathFileName2 = System.IO.Path.Combine(fsPath2, filename);
string s2 = Path.GetFullPath(FsPathFileName1);
If you test the code you will see that WebPathFileName1 returns "//someaddress\\somefilename.txt" but I should response with "//someaddress//somefilename.txt".
The input path could end or not with \
What other methods could I use to combine paths? Thanks.
I may come with more details from my project leader as I know. The idea is that, as I said, this method will be called from 2 kind of projects. So it should compose a web path or a files system path.

Telerik asp.net MVC Fileupload control

I am using Telerik asp.net MVC 3 file control in my Razor view (Catalog/Product View) like this:
#(Html.Telerik().Upload()
.Name("orderImageAtachment")
.Async(async => async.Save("Save", "Catalog").AutoUpload(true))
.ClientEvents(events => events
.OnSuccess("ItemImageOnSuccess")
.OnError("ItemImageOnError")
)
)
I have created an ActionResult like this:
public ActionResult Save(IEnumerable<HttpPostedFileBase> orderImageAtachment, string CompID)
{
// The Name of the Upload component is "attachments"
foreach (var file in orderImageAtachment)
{
// Some browsers send file names with full path. This needs to be stripped.
var fileName = Path.GetFileName(file.FileName);
var physicalPath = Path.Combine(Server.MapPath("~/Content/Docs"), fileName);
// The files are not actually saved in this demo
file.SaveAs(physicalPath);
}
// Return an empty string to signify success
return Content("");
}
and client side functions like this:
function onSuccess(e) {
// Array with information about the uploaded files
var files = e.files;
if (e.operation == "upload") {
alert("Successfully uploaded " + files.length + " files");
}
}
function onError(e) {
alert('Error in file upload');
// Array with information about the uploaded files
var files = e.files;
if (e.operation == "upload") {
alert("Failed to uploaded " + files.length + " files");
}
// Suppress the default error message
e.preventDefault();
}
I get select button which opens browse window. But clicking it does nothing.... I am not sure whats wrong. Do I need to add something in web.config? Please suggest.
I'm a little confused at which point its not working, but I'm assuming its not hitting the action in your controller. I'd make sure you are trying a fairly small file, the default limit is 4mb.
Also it looks like the signature of your Save Action does not match the route you are giving it in the upload's async.Save(...). I'm not sure it will matter since its a string, but you might try removing the Save actions's CompID parameter (doesn't look like its used in the snippet at least).
I'd try using fiddler or the developer tools in whichever browser you are using to see if u are getting a 404 error by chance.

C# Problem Reading Console Output to string

i want to launch ffmpeg from my app and retrive all console output that ffmpeg produces. Thing seems obvious, i followed many forum threads/articles like this one but i have problem, though i follow all information included there I seem to end up in dead end.
String that should contain output from ffmpeg is always empty. I've tried to see where is the problem so i made simple c# console application that only lists all execution parameters that are passed to ffmpeg, just to check if problem is caused by ffmpeg itself. In that case everything work as expected.
I also did preview console window of my app. When i launch ffmpeg i see all the output in console but the function that should recieve that output for further processing reports that string was empty. When my param-listing app is launched the only thing I see is the expected report from function that gets output.
So my question is what to do to get ffmpeg output as i intended at first place.
Thanks in advance
MTH
This is a long shot, but have you tried redirecting StandardError too?
Here is a part of my ffmpeg wrapper class, in particular showing how to collect the output and errors from ffmpeg.
I have put the Process in the GetVideoDuration() function just so you can see everything in the one place.
Setup:
My ffmpeg is on the desktop, ffPath is used to point to it.
namespace ChildTools.Tools
{
public class FFMpegWrapper
{
//path to ffmpeg (I HATE!!! MS special folders)
string ffPath = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ffmpeg.exe";
//outputLines receives each line of output, only if they are not zero length
List<string> outputLines = new List<string>();
//In GetVideoDuration I only want the one line of output and in text form.
//To get the whole output just remove the filter I use (my search for 'Duration') and either return the List<>
//Or joint the strings from List<> (you could have used StringBuilder, but I find a List<> handier.
public string GetVideoDuration(FileInfo fi)
{
outputLines.Clear();
//I only use the information flag in this function
string strCommand = string.Concat(" -i \"", fi.FullName, "\"");
//Point ffPath to my ffmpeg
string ffPath = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ffmpeg.exe";
Process processFfmpeg = new Process();
processFfmpeg.StartInfo.Arguments = strCommand;
processFfmpeg.StartInfo.FileName = ffPath;
//I have to say that I struggled for a while with the order that I setup the process.
//But this order below I know to work
processFfmpeg.StartInfo.UseShellExecute = false;
processFfmpeg.StartInfo.RedirectStandardOutput = true;
processFfmpeg.StartInfo.RedirectStandardError = true;
processFfmpeg.StartInfo.CreateNoWindow = true;
processFfmpeg.ErrorDataReceived += processFfmpeg_OutData;
processFfmpeg.OutputDataReceived += processFfmpeg_OutData;
processFfmpeg.EnableRaisingEvents = true;
processFfmpeg.Start();
processFfmpeg.BeginOutputReadLine();
processFfmpeg.BeginErrorReadLine();
processFfmpeg.WaitForExit();
//I filter the lines because I only want 'Duration' this time
string oStr = "";
foreach (string str in outputLines)
{
if (str.Contains("Duration"))
{
oStr = str;
}
}
//return a single string with the duration line
return oStr;
}
private void processFfmpeg_OutData(object sender, DataReceivedEventArgs e)
{
//The data we want is in e.Data, you must be careful of null strings
string strMessage = e.Data;
if outputLines != null && strMessage != null && strMessage.Length > 0)
{
outputLines.Add(string.Concat( strMessage,"\n"));
//Try a Console output here to see all of the output. Particularly
//useful when you are examining the packets and working out timeframes
//Console.WriteLine(strMessage);
}
}
}
}

Categories