Opening a Memory Mapped File causes FileNotFoundException when deployed in IIS - asp.net

Following the code example from this website, I created a windows console application that creates a mapped memory file:
using (var file = MemoryMappedFile.CreateNew("myFile", 24))
{
var bytes = new byte[24];
for (var i = 0; i < bytes.Length; i++)
bytes[i] = (byte)(65 + i);
using (var writer = file.CreateViewAccessor(0, bytes.Length))
{
writer.WriteArray<byte>(0, bytes, 0, bytes.Length);
}
Console.WriteLine("Run memory mapped file reader before exit");
Console.WriteLine("Press any key to exit ...");
Console.ReadLine();
}
in a new asp.net web application I read the MMF using the code:
protected void Page_Load(object sender, EventArgs e)
{
string sOutput = "";
using (var file = MemoryMappedFile.OpenExisting("myFile"))
{
using (var reader = file.CreateViewAccessor(0, 24))
{
var bytes = new byte[24];
reader.ReadArray<byte>(0, bytes, 0, bytes.Length);
for (var i = 0; i < bytes.Length; i++)
sOutput += (char)bytes[i] + " ";
}
}
Response.Write(sOutput);
}
In the development IIS Express the file is read as expected and the page displays the data from the mapped memory file.
However, When deployed to IIS I get the exception System.IO.FileNotFoundException at the locationMemoryMappedFile.OpenExisting("myFile")
I tried changing the app pool identity to the same as the one running the MMF console application but this does didn't work.

Add "Global\\" prefix to the mapName. That is the only way it worked for me.
When we try to access shared memory created in the first process it runs in a different security context and shared memory objects are not visible unless they are created in the global namespace.
This is the code that worked for me. I used CreateOrOpen in a WinForm application and I used OpenExisting in an IIS process:
mSec = new MemoryMappedFileSecurity();
mSec.AddAccessRule(new AccessRule<MemoryMappedFileRights>(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MemoryMappedFileRights.FullControl, AccessControlType.Allow));
mmf = MemoryMappedFile.CreateOrOpen("Global\\\MmfName", 100, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, mSec, HandleInheritability.Inheritable);
mmf = MemoryMappedFile.OpenExisting("Global\\\MmfName");

If Inter Process Communication (IPC) is what you are trying to achieve, you could use WCF with a Named Pipe Binding. You gain a lot of simplicity at the cost of some performance.

For non physical file, there are two cases,
File is deleted after your process exits.
IIS does not run in same user as your console process, IIS runs in little restricted mode, so unless you create File with added security to allow process to access your service.
You will have to use http://msdn.microsoft.com/en-us/library/dd267529(v=vs.110).aspx this constructor with following security.
MemoryMappedFileSecurity mSec = new MemoryMappedFileSecurity ();
mSec.AddAccessRule(new AccessRule<MemoryMappedFileRights>(new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MemoryMappedFileRights .FullControl, AccessControlType.Allow));
And then try to access it from IIS with same security.

Related

Async await to save file causes "Process cannot access file because it is being used by another process" error

I have the following code to save an excel file on the server and then read its content:
if (file.Length > 0)
{
string path = _hostingEnvironment.ContentRootPath + "/CSV-import-students/";
FileStream fs = new FileStream(Path.Combine(path, file.FileName), FileMode.Create);
await file.CopyToAsync(fs);
FileInfo fileUploaded = new FileInfo(Path.Combine(path, file.FileName));
using (ExcelPackage package = new ExcelPackage(fileUploaded))
{
StringBuilder sb = new StringBuilder();
ExcelWorksheet worksheet = package.Workbook.Worksheets[0];
int rowCount = worksheet.Dimension.Rows;
int ColCount = worksheet.Dimension.Columns;
bool bHeaderRow = true;
}
The file is saved fine on the server. But, then when I try to access it, I receive "Process cannot access file because it is being used by another process" error. How can I prevent this error? Any ideas?
Almost invariably, when newing up a class that implements IDisposable (such as FileStream), you should do so with a using statement:
using (var fs = new FileStream(Path.Combine(path, file.FileName), FileMode.Create))
{
await file.CopyToAsync(fs);
}
This will automatically dispose of the resource when the using statement goes out of scope, and in the case of FileStream, will flush the write and close the file. That's the source of your issue, so this will solve your problem.
However, you might also need to contend with concurrency. It's possible for two requests to be processed simultaneously that both need to work with the same file. You should plan for concurrency, by catching file access violation exceptions and responding via a retry policy. The Polly exception handling library can help here.

ASP.NET IIS File create permission while using Thread

I am trying to create a file in my ASP.NET application (IIS 7.5). The file write is done in a separate thread and is giving access not available error.
What kind of permission does the directory need? I have tried giving full access to IIS_IUSRS and IUSR. But this did not work. Everything works okay in my local machine, but once deployed on the server, I get access error.
string filePath = MapPath("c:\FilePath\");
PrintFile printFile = new PrintFile();
Thread printFileThread = new Thread(delegate()
{
printFile.PrintFile(filePath);
});
printFileThread.Start();
public void PrintFile(string filePath)
{
if (Directory.Exists(filePath) == false)
{
Directory.CreateDirectory(filePath);
}
FileStream fs = new FileStream(filePath + "NewFile.pdf", FileMode.Create);
}

Accessing SSRS server report from local application

I have deployed my SSRS reports in the server. Is it possible for me to access that report from my local web application. I have given the server's credentials in the web.config. But still its not displaying the report and it shows some error like Cannot create a connection to data source 'DataSource1'. (rsErrorOpeningConnection).
When I hosted the same application in the server it is working absolutely fine.
Can anyone tell me why am not able to access the reports from my local system?
This is not my code, but ideally is all you have to do. I remember using it successfully in one of previous projects some time back
private void ShowReport()
{
try
{
string urlReportServer = "http://sqlDBServer//Reportserver";
rptViewer.ProcessingMode = ProcessingMode.Remote; // ProcessingMode will be Either Remote or Local
rptViewer.ServerReport.ReportServerUrl = new Uri(urlReportServer); //Set the ReportServer Url
rptViewer.ServerReport.ReportPath = "/ReportName"; //Passing the Report Path
//Creating an ArrayList for combine the Parameters which will be passed into SSRS Report
ArrayList reportParam = new ArrayList();
reportParam = ReportDefaultPatam();
ReportParameter[] param = new ReportParameter[reportParam.Count];
for (int k = 0; k < reportParam.Count; k++)
{
param[k] = (ReportParameter)reportParam[k];
}
// pass crendentitilas
//rptViewer.ServerReport.ReportServerCredentials =
// new ReportServerCredentials("uName", "PassWORD", "doMain");
//pass parmeters to report
rptViewer.ServerReport.SetParameters(param); //Set Report Parameters
rptViewer.ServerReport.Refresh();
}
catch (Exception ex)
{
throw ex;
}
}
Ref: http://www.codeproject.com/Articles/675762/Call-SSRS-Reports-by-using-Csharp

Clearing temp ASP.NET files on application restart

We're dynamically loading assemblies at startup and adding them as a reference:
BuildManager.AddReferencedAssembly(assembly);
The application supports installing new plugins at runtime. Following an install/uninstall action we are restarting the web application. I've tried:
HostingEnvironment.InitiateShutdown();
and
System.Web.HttpRuntime.UnloadAppDomain();
However, the new version of a plugin is not loaded - I believe this is due to how ASP.NET is aggressively caching referenced assemblies - especially ASP.NET MVC controllers.
In production this shouldn't be a problem since the plugin assembly version would be incremented each time. However, in development this is more of an issue since we don't wish to change the version number every time we make a slight change to a plugin.
How can we force the clearing of temp asp.net files, either programatically or using a post build event?
One solution is to "touch" global.asax but this seems a bit hacky to me.
I've used following piece of code to reset the application pool on demand. (Just connect this to a Controller Action).
Note : Since it's the application pool, you might want to check the impact to any other apps running on the same app pool.
public class IisManager
{
public static string GetCurrentApplicationPoolId()
{
// Application is not hosted on IIS
if (!AppDomain.CurrentDomain.FriendlyName.StartsWith("/LM/"))
return string.Empty;
// Application hosted on IIS that doesn't support App Pools, like 5.1
else if (!DirectoryEntry.Exists("IIS://Localhost/W3SVC/AppPools"))
return string.Empty;
string virtualDirPath = AppDomain.CurrentDomain.FriendlyName;
virtualDirPath = virtualDirPath.Substring(4);
int index = virtualDirPath.Length + 1;
index = virtualDirPath.LastIndexOf("-", index - 1, index - 1);
index = virtualDirPath.LastIndexOf("-", index - 1, index - 1);
virtualDirPath = "IIS://localhost/" + virtualDirPath.Remove(index);
var virtualDirEntry = new DirectoryEntry(virtualDirPath);
return virtualDirEntry.Properties["AppPoolId"].Value.ToString();
}
public static void RecycleApplicationPool(string appPoolId)
{
string appPoolPath = "IIS://localhost/W3SVC/AppPools/" + appPoolId;
var appPoolEntry = new DirectoryEntry(appPoolPath);
appPoolEntry.Invoke("Recycle");
}
public static void RecycleApplicationPool(string appPoolId, string username, string password)
{
string appPoolPath = "IIS://localhost/W3SVC/AppPools/" + appPoolId;
var appPoolEntry = new DirectoryEntry(appPoolPath, username, password);
appPoolEntry.Invoke("Recycle");
}
}
The overridden method is to cater for instances where you want to explicitly pass a user with Admin rights on the machine/server which hosts IIS instance.
And the controller action could be something like;
public string ResetAppPool()
{
var appPoolId = IisManager.GetCurrentApplicationPoolId();
if (appPoolId.Equals(string.Empty))
return "Application is not running inside an App Pool"; //May be not IIS 6 onwards
try
{
IisManager.RecycleApplicationPool(appPoolId); //Can only be used by Admin users
return string.Format("App pool {0} recycled successfully", appPoolId);
}
catch (Exception ex)
{
Logger.Error("Failed to recycle app pool : " + ex.StackTrace);
return string.Format("App pool {0} recycle failed", appPoolId);
}
}

ASP.NET- using System.IO.File.Delete() to delete file(s) from directory inside wwwroot?

I have a ASP.NET SOAP web service whose web method creates a PDF file, writes it to the "Download" directory of the applicaton, and returns the URL to the user. Code:
//Create the map images (MapPrinter) and insert them on the PDF (PagePrinter).
MemoryStream mstream = null;
FileStream fs = null;
try
{
//Create the memorystream storing the pdf created.
mstream = pgPrinter.GenerateMapImage();
//Convert the memorystream to an array of bytes.
byte[] byteArray = mstream.ToArray();
//return byteArray;
//Save PDF file to site's Download folder with a unique name.
System.Text.StringBuilder sb = new System.Text.StringBuilder(Global.PhysicalDownloadPath);
sb.Append("\\");
string fileName = Guid.NewGuid().ToString() + ".pdf";
sb.Append(fileName);
string filePath = sb.ToString();
fs = new FileStream(filePath, FileMode.CreateNew);
fs.Write(byteArray, 0, byteArray.Length);
string requestURI = this.Context.Request.Url.AbsoluteUri;
string virtPath = requestURI.Remove(requestURI.IndexOf("Service.asmx")) + "Download/" + fileName;
return virtPath;
}
catch (Exception ex)
{
throw new Exception("An error has occurred creating the map pdf.", ex);
}
finally
{
if (mstream != null) mstream.Close();
if (fs != null) fs.Close();
//Clean up resources
if (pgPrinter != null) pgPrinter.Dispose();
}
Then in the Global.asax file of the web service, I set up a Timer in the Application_Start event listener. In the Timer's ElapsedEvent listener I look for any files in the Download directory that are older than the Timer interval (for testing = 1 min., for deployment ~20 min.) and delete them. Code:
//Interval to check for old files (milliseconds), also set to delete files older than now minus this interval.
private static double deleteTimeInterval;
private static System.Timers.Timer timer;
//Physical path to Download folder. Everything in this folder will be checked for deletion.
public static string PhysicalDownloadPath;
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
deleteTimeInterval = Convert.ToDouble(System.Configuration.ConfigurationManager.AppSettings["FileDeleteInterval"]);
//Create timer with interval (milliseconds) whose elapse event will trigger the delete of old files
//in the Download directory.
timer = new System.Timers.Timer(deleteTimeInterval);
timer.Enabled = true;
timer.AutoReset = true;
timer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimedEvent);
PhysicalDownloadPath = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath + "Download";
}
private static void OnTimedEvent(object source, System.Timers.ElapsedEventArgs e)
{
//Delete the files older than the time interval in the Download folder.
var folder = new System.IO.DirectoryInfo(PhysicalDownloadPath);
System.IO.FileInfo[] files = folder.GetFiles();
foreach (var file in files)
{
if (file.CreationTime < DateTime.Now.AddMilliseconds(-deleteTimeInterval))
{
string path = PhysicalDownloadPath + "\\" + file.Name;
System.IO.File.Delete(path);
}
}
}
This works perfectly, with one exception. When I publish the web service application to inetpub\wwwroot (Windows 7, IIS7) it does not delete the old files in the Download directory. The app works perfect when I publish to IIS from a physical directory not in wwwroot. Obviously, it seems IIS places some sort of lock on files in the web root. I have tested impersonating an admin user to run the app and it still does not work. Any tips on how to circumvent the lock programmatically when in wwwroot? The client will probably want the app published to the root directory.
Your problem may be related to the fact that IIS reloads the Web Service Application if the directory or files contained in the main folder changes.
Try creating / deleting files in a temporary folder which is outside the root folder of your application (be aware of permissions on the folder to allow IIS to read/write files).
Instead of writing directly to the file system, why not use isolated storage?
http://msdn.microsoft.com/en-us/library/system.io.isolatedstorage.isolatedstorage.aspx
This should solve any location or permission based issues that you are having
I forgot to come back and answer my question.
I had to give the IIS_IUSRS group Modify permissions to the directory where I was reading/writing files.
Thanks to all those who answered.

Resources