I'm trying to upload a file asynchronously to my web server ( ASP.NET MVC). Specifically to my web api endpoint. I followed several tutorials but I keep getting an error of null parameters.
It happens when I subclass MultipartFormDataStreamProvider. The method GetLocalFileName, gets called first correctly, with the proper HttpContentHeaders, but then gets called a second time with null headers.
The file is successfully saved with the filename I set, but as I get an exception, I am not able to know what's the name generated.
However if I just use the base MultipartFormDataStreamProvider class, the file is saved perfectly, with no errors, but I can't control the filename in this case.
This is the api method:
[HttpPost]
public async Task<FilesUploadResult> Save()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
var ret = new FilesUploadResult(HttpStatusCode.UnsupportedMediaType, null);
ret.Message = "Unsupported Media Type. Request must contain multipart/form-data form.";
return ret;
}
var path = UploadsFolderPath;
// to-do: this works but for some reason makes a call with empty headers.
var provider = new SimpleMultipartFormDataStreamProvider(path);
// this provider saves the file with an arbritrary name
//var provider = new MultipartFormDataStreamProvider(path);
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider as MultipartFormDataStreamProvider);
var files = from f in provider.FileData
let info = new FileInfo(f.LocalFileName)
select new FileUploadDescription(info.Name, path + "\\" + info.Name, info.Length, HttpStatusCode.Created);
var ret = new FilesUploadResult(HttpStatusCode.OK, files);
return ret;
}
catch (Exception ex)
{
var ret = new FilesUploadResult(HttpStatusCode.InternalServerError, null);
ret.Message = ex.Message;
return ret;
}
}
This is the code for my SimpleMultipartFormDataStreamProvider:
public class SimpleMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
public SimpleMultipartFormDataStreamProvider(string path) : base(path) { }
public override string GetLocalFileName(HttpContentHeaders headers)
{
// make sure the headers are valid
if (headers == null)
{
throw new ArgumentNullException("headers");
}
// filename
var filename = CleanString(headers.ContentDisposition.FileName); ;
// create the local file name
var localFileName = string.Format("_{0}_{1}", GetRandomName(), filename);
//this is here because Chrome submits files in quotation marks which get treated as part of the filename and get escaped
return localFileName.Replace("\"", string.Empty);
}
private string GetRandomName()
{
return GuidHelper.ShortUniqueId(16);
}
private static string CleanString(string str)
{
if (string.IsNullOrWhiteSpace(str))
return string.Empty;
var sb = new StringBuilder();
foreach (char c in str)
{
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.' || c == '_')
{
sb.Append(c);
}
}
return sb.ToString();
}
}
Can anybody tell me why I am getting the GetLocalFileName called twice and the 2nd time with empty headers?
Even i had encountered such behavior in MVC4 it submits 2 files one of which is empty, so better would be to have a check whether the file has any content and then proceed with saving the file.
There can be multiple approaches for this:
One of them would be to get the list of all the files from Request.Files (this is an array of strings )
foreach (string fileName in Request.Files)
{
HttpPostedFileBase file = Request.Files[fileName];
//Save file content goes here
if (file != null && file.ContentLength > 0)
{
//logic to save the content
}
}
GetLocalFileName is called once for each file in the multipart request. If you you send two files in the same request GetLocalFileName will be invoked twice. I think you should check how are you invoking the service and use fiddler or something similar to see if you really are sending two files. If you send several parts in the same request try to specify the ContentType of each part.
I figured it out by myself.
The issue was that the class I implemented to subclass the MultipartDataStreamProvider was in a different project.
That project had references to .Net .Http assemblies that were different to the ones currently in the Web Api project.
Only thing I did was to move that CustomMultipartDataStreamProvider class to the same project, and everything worked perfectly.
Related
I am trying to work through a school assignment that has us use a C# program to parse data from a CSV file and add it to a table in a local database. When I try to run the program though, the method I am using fails to parse any of the data into the object.
Here is the method I am using:
//Parse CSV line
public bool ParseCSVline(string aLine)
{
try
{
string[] fields = aLine.Split(',');
this.Item_ID = int.Parse(fields[0]);
this.Invent_id = int.Parse(fields[1]);
this.Itemsize = fields[2];
this.Color = fields[3];
this.Curr_price = decimal.Parse(fields[4]);
this.Qoh = int.Parse(fields[5]);
return true; //if everything parsed, return true
}
catch (Exception ex)
{
Console.Write("Failed to Parse");
return false; //if a parse failed, return false
}
When running the program the method keeps throwing the Exception instead of actually parsing the data. For clarity, here is the section in the Main program that is calling everything:
/Step 2 - Open input file
//Set where the file comes from
string filepath = #"C:\Users\Karlore\Documents\School\SAI-430\";
string filename = #"NewInventory.csv";
//Open reader
StreamReader theFile = new StreamReader(filepath + filename);
//Step 3 - Create an object to use
Item theItem = new Item();
//Step 4 - Loop through file and add to database
while (theFile.Peek() >= 0)
{
//Get one line and parse it inside the object
theItem.ParseCSVline(filename);
//Check to see if item is already there
if (theItem.IsInDatabase(connection))
{
continue;
}
else
{
//Add the new item to the database if it wasn’t already there
theItem.AddRow(connection);
}
} //end of while loop
If anyone can point out where I may have made an error, or point me in the right direction I would appreciate it.
Replace the line:
theItem.ParseCSVline(filename);
by:
theItem.ParseCSVline(theFile.ReadLine());
When i use OpenFileDialog to Open file, of course i need to get the file directory and its name to load the file.(to load xml, to access the file i need full path.)
opd is OpenFileDialog
if (opd.ShowDialog() == true)
{
var names = opd.FileNames;
foreach (string name in names)
{
LoadFile(Path.Combine(Path.GetDirectoryName(name), name));
}
}
my question is How Path.GetDirectoryName take the path of the File by Just taking the string ?
Path.GetDirectoryName(name)
name is Just string and this method takes its directory by just taking string? . there can be thousands of files with same name inside computer.
ShortQuestion:
where is opd refrenced?
Edit:
i thought opd.FileNames just takes name of the files.(because of methods name)
and Also i found something interesting.
LoadFile(Path.Combine(Path.GetDirectoryName(name), name));
this works fine Because Path.Combine will just skip the same part of string.
Ex:
string name = #"C:\Users\Default\xml.xml";
string getDirNameResault= Path.GetDirectoryName(name);// this will be C:\Users\Default
So Path.Combine will be
Path.Combine(#"C:\Users\Default", #"C:\Users\Default\xml.xml)
witch returns "C:\Users\Default\xml.xml" !
Path.GetDirectoryName splits the string you already have (from opd) by slash / or \ and then returns everything except last part.
Complete source code of the function in .NET Core foundational libraries (called CoreFX) you can find here: https://github.com/dotnet/corefx/blob/41e203011152581a6c65bb81ac44ec037140c1bb/src/System.Runtime.Extensions/src/System/IO/Path.cs#L151
Code of the implementation:
// Returns the directory path of a file path. This method effectively
// removes the last element of the given file path, i.e. it returns a
// string consisting of all characters up to but not including the last
// backslash ("\") in the file path. The returned value is null if the file
// path is null or if the file path denotes a root (such as "\", "C:", or
// "\\server\share").
//
public static String GetDirectoryName(String path)
{
if (path != null)
{
CheckInvalidPathChars(path);
#if FEATURE_LEGACYNETCF
if (!CompatibilitySwitches.IsAppEarlierThanWindowsPhone8)
{
#endif
string normalizedPath = NormalizePath(path, false);
// If there are no permissions for PathDiscovery to this path, we should NOT expand the short paths
// as this would leak information about paths to which the user would not have access to.
if (path.Length > 0)
{
try
{
// If we were passed in a path with \\?\ we need to remove it as FileIOPermission does not like it.
string tempPath = Path.RemoveLongPathPrefix(path);
// FileIOPermission cannot handle paths that contain ? or *
// So we only pass to FileIOPermission the text up to them.
int pos = 0;
while (pos < tempPath.Length && (tempPath[pos] != '?' && tempPath[pos] != '*'))
pos++;
// GetFullPath will Demand that we have the PathDiscovery FileIOPermission and thus throw
// SecurityException if we don't.
// While we don't use the result of this call we are using it as a consistent way of
// doing the security checks.
if (pos > 0)
Path.GetFullPath(tempPath.Substring(0, pos));
}
catch (SecurityException)
{
// If the user did not have permissions to the path, make sure that we don't leak expanded short paths
// Only re-normalize if the original path had a ~ in it.
if (path.IndexOf("~", StringComparison.Ordinal) != -1)
{
normalizedPath = NormalizePath(path, /*fullCheck*/ false, /*expandShortPaths*/ false);
}
}
catch (PathTooLongException) { }
catch (NotSupportedException) { } // Security can throw this on "c:\foo:"
catch (IOException) { }
catch (ArgumentException) { } // The normalizePath with fullCheck will throw this for file: and http:
}
path = normalizedPath;
#if FEATURE_LEGACYNETCF
}
#endif
int root = GetRootLength(path);
int i = path.Length;
if (i > root)
{
i = path.Length;
if (i == root) return null;
while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar) ;
String dir = path.Substring(0, i);
#if FEATURE_LEGACYNETCF
if (CompatibilitySwitches.IsAppEarlierThanWindowsPhone8)
{
if (dir.Length >= MAX_PATH - 1)
throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
}
#endif
return dir;
}
}
return null;
}
For the complete source code of the function (in Mono) see here: https://github.com/mono/mono/blob/master/mcs/class/corlib/System.IO/Path.cs#L199
name is Just string and this method takes its directory by just taking string? . there can be thousands of files with same name inside computer.
name contains the full path, Path.GetDirectoryName() just strips everything after the last directory separator, Path.Combine(Path.GetDirectoryName(name), name) will not do anything useful:
If path2 includes a root, path2 is returned.
Just use name directly.
I am creating a Windows Forms Application with c# that can open up a file on startup if it is passed an argument containing the path to the file.
However, it doesn't accurately determine if arguments have been passed. Even when I pass no arguments, it still tries to open up a file.
Here's my code:
string[] args = Environment.GetCommandLineArgs();
if (args == null || args.Length == 0)
{
}
else
{
try
{
ListData ld = new LmReader.LmReader().readLmList(args[0]);
listItemsList.Items.Clear();
foreach (ListItemList li in ld.lil)
{
ListViewItem lvi = new ListViewItem(li.text);
lvi.Font = li.itemFont;
listItemsList.Items.Add(lvi);
}
filenameOpen = selectOpenLocation.FileName;
this.Text = "List Maker - " + Path.GetFileNameWithoutExtension(args[0]);
}
catch (Exception ex)
{
new Error().doError("Your list could not be opened.", ex);
}
}
What am I doing wrong?
Environment.GetCommandLineArgs() always at least returns one argument which is the excecutable file name and then contains the arguments you might have passed in.
So that's why your if condition nether matches
See documentation
From the docs:
The first element in the array contains the file name of the executing
program
Therefore
args.Length == 0
should be
args.Length <= 1
in your case.
I was attempting to use this Blog to pass an argument to an offline click-once application. Near the bottom of the blog there are example programs to download. I downloaded and published the program "TestRunningWithArgs" the C# version.
Now I am trying to pass an argument to the application which is not working. It keeps saying no "No arguments passed in".
I am trying to use the following code to pass an argument:
shellexecute("File Path\My Applicaiton.application","MyArgument")
This code runs the application but the sample application states it did not receive an argument. The language is visual basic (maybe 4 or 6?).
The code below works and allows internet explorer to run and a specific file be opened from sharepoint and I was wondering if there is a difference that I am missing.
shellexecute("C:\Program Files\Internet Explorer\iexplore.exe",FileLocation)
The only one I see is that the click once is a .application file vs a .exe and is there a way to get this to work?
Below is the code for the click once application, just copied from the supplied example program on the blog
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
GetArgsToShow();
}
/// Get the arguments and show them on the screen.
private void GetArgsToShow()
{
// Get the ActivationArguments from the SetupInformation property of the domain.
string[] activationData =
AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData;
if (activationData != null && activationData.Length > 0)
{
//querystring starts with ?; file association starts with "file:"
if (activationData.Length == 1 && activationData[0].Substring(0, 1) == "?")
{
ProcessQueryString(activationData);
}
else if (activationData.Length == 1 && activationData[0].Length >= 5 && activationData[0].Substring(0,5).ToLower() == #"file:")
{
ProcessFileAssociation(activationData);
}
else
{
ProcessCSVParameters(activationData);
}
}
else
{
if (activationData == null)
{
lstArgs.Items.Add("No arguments passed in.");
}
else
{
lstArgs.Items.Add(String.Format("Number of args = {0}", activationData.Length));
}
}
}
/// Convert a query string into Name/Value pairs and process it.
private void ProcessQueryString(string[] activationData)
{
NameValueCollection nvc =
System.Web.HttpUtility.ParseQueryString(activationData[0]);
//Get all the keys in the collection, then pull the values for each of them.
//I'm only passing each key once, with one value.
string[] theKeys = nvc.AllKeys;
foreach (string theKey in theKeys)
{
string[] theValue = nvc.GetValues(theKey);
lstArgs.Items.Add(string.Format("Key = {0}, Value = {1}", theKey, theValue[0]));
}
}
/// Process what you would get if you set up a file association,
/// and the user double-clicked on one of the associated file.
private void ProcessFileAssociation(string[] activationData)
{
//This is what you get when you set up a file association and the user double-clicks
// on an associated file.
Uri uri = new Uri(activationData[0]);
lstArgs.Items.Add(uri.LocalPath.ToString());
}
/// Process a comma-delimited string of values. Not: can't have spaces or double-quotes in the string,
/// it will only read the first argument.
private void ProcessCSVParameters(string[] activationData)
{
//I have to say here that I've only ever seen 1 entry passed in activationData,
// but I'm checking for multiples just in case.
//This takes each entry and splits it by comma and separates them into separate entries.
char[] myComma = { ',' };
foreach (string arg in activationData)
{
string[] myList = activationData[0].Split(myComma, StringSplitOptions.RemoveEmptyEntries);
foreach (string oneItem in myList)
lstArgs.Items.Add(oneItem);
}
}
}
EDIT
Well I tried accessing the .exe located in "C:\Users\userName\AppData\Local\Apps\2.0\5G9CGPWV.6O3\X7YPB07N.2Q2\test..tion_0000000000000000_0001.0000_03232931d88a66c9\" which did not work.
Found the file you have to call to send an argument.
shellexecute("C:\Users\%USERNAME%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\(Publisher Name)\My Application.appref-ms", Arguement)
The publisher name is specified in visual studio Publish Tab, options and then Publisher Name. This is also the Start Menu Folder where your program can be launched from.
So the title might be bit misleading, but what I wanted to accomplish is reading an array of files and then combine them into one, which is where I am now.
The problem is that I have a catch that looks for the exception "FileNotFoundException", when this is called I want to continue my try statement (Using "continue") but let the user know that the file is missing.
My setup is a class that is called from a form (It's in the form where the error should show up)
I thought about creating an event that can be registered from my form, but is that the right way?
public void MergeClientFiles(string directory)
{
// Find all clients
Array clients = Enum.GetValues(typeof(Clients));
// Create a new array of files
string[] files = new string[clients.Length];
// Combine the clients with the .txt extension
for (int i = 0; i < clients.Length; i++)
files[i] = clients.GetValue(i) + ".txt";
// Merge the files into directory
using (var output = File.Create(directory))
{
foreach (var file in files)
{
try
{
using (var input = File.OpenRead(file))
{
input.CopyTo(output);
}
}
catch (FileNotFoundException)
{
// Its here I want to send the error to the form
continue;
}
}
}
}
You want the method to do its job and report user about problems, right?
Then Oded has suggested right thing. With small modification, the code could look like this:
public List<string> MergeClientFiles( string path )
{
// Find all clients
Array clients = Enum.GetValues( typeof( Clients ) );
// Create a new array of files
string[] files = new string[clients.Length];
// Combine the clients with the .txt extension
for( int i = 0; i < clients.Length; i++ )
files[i] = clients.GetValue( i ) + ".txt";
List<string> errors = new List<string>();
// Merge the files into AllClientData
using( var output = File.Create( path ) ) {
foreach( var file in files ) {
try {
using( var input = File.OpenRead( file ) ) {
input.CopyTo( output );
}
}
catch( FileNotFoundException ) {
errors.Add( file );
}
}
}
return errors;
}
Then, in caller you just check if MergeClientFiles returns non-empty collection.
You can collect the exceptions into a List<FileNotFoundException> and at the end of iteration, if the list is not empty, throw a custom exception assigning this list to a corresponding member.
This will allow any code calling the above to catch your custom exception, iterate over the FileNotFoundExceptions and notify the user.
you could define a delegate that you pass as an argument of your method.
public delegate void FileNotFoundCallback(string file);
public void MergeClientFiles(string directory, FileNotFoundCallback callback)
{
// Find all clients
Array clients = Enum.GetValues(typeof(Clients));
// Create a new array of files
string[] files = new string[clients.Length];
// Combine the clients with the .txt extension
for (int i = 0; i < clients.Length; i++)
files[i] = clients.GetValue(i) + ".txt";
// Merge the files into directory
using (var output = File.Create(directory))
{
foreach (var file in files)
{
try
{
using (var input = File.OpenRead(file))
{
input.CopyTo(output);
}
}
catch (FileNotFoundException)
{
// Its here I want to send the error to the form
callback( file );
continue;
}
}
}
}
Rather than catching FileNotFoundExceptions you should actively check if the file exists and then don't try to open it if it doesn't.
You can change the method to return a list of merged files, a list of missing files, or a list of all files along with an indicator if they were merged or missing. Returning a single list gives the caller the option to process the missing files all at once and know how many were missing, instead of one-by-one as would be the case with an event or callback.
For some inspiration, have a look at the documentation for the new parallel constructs in c#, such as Parallel.For and the Reactive Framework (rx).
In the first, exceptions are collected in an AggregateException, in Rx exceptions are communicated via a callback interface.
I think I prefer the approach used in Parallel.For, but choose what fits your scenario best.