I'm working on a simple portfolio project. I would like to show images on a webpage that logged in users can edit. My problem is in the [HttpPost] Edit, more specifically this part:
if (ModelState.IsValid)
{
//updating current info
inDb = ModelFactory<ArtSCEn>.GetModel(db, artSCEn.ArtSCEnID);
inDb.LastModified = DateTime.Now;
inDb.TechUsed = artSCEn.TechUsed;
inDb.DateOfCreation = artSCEn.DateOfCreation;
inDb.Description = artSCEn.Description;
inDb.ArtSC.LastModified = DateTime.Now;
//validating img
if (Validator.ValidateImage(img))
{
inDb.ImageString = Image.JsonSerialzeImage(img);
}
else
{
//return to the UI becuase we NEED a valid pic
return View(artSCEn);
}
db.Entry(inDb).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
//[PROBLEMATIC PART STARTS HERE]
//updating the pic on the server
//getting the string info
string userArtImgFolder = Server.MapPath($"~/Content/Images/Artistic/{inDb.ArtSC.PersonID}");
string imgNameOnServer = Path.Combine(
userArtImgFolder,
$"{inDb.ArtSC.PersonID}_{inDb.ArtSC.ArtSCID}_{inDb.ArtSCEnID}{Path.GetExtension(img.FileName)}");
//deleting previous pic
System.IO.File.Delete(imgNameOnServer);
//creating a new pic
Image.ResizePropotionatelyAndSave(img, Path.Combine(
userArtImgFolder,
$"{inDb.ArtSC.PersonID}_{inDb.ArtSC.ArtSCID}_{inDb.ArtSCEnID}{Path.GetExtension(img.FileName)}"));
return RedirectToAction("Edit", "Art", new { id = inDb.ArtSCID });
}
When I get back the new picture and I want to delete the previous, System.IO.File.Delete() always triggers an exception that it cannot access the resource, because someone else is holding onto it. Any idea what that might be?
Maybe it's something simple, I'm new to ASP, but just can't figure it out.
UPDATE
Following on the suggestions in the comments section, I checked the processes with a tool called Process Monitor and it seems that indeed IIS is locking the resource:
This one appears 2 more times in the logs, by the way.
Judging by the fact that the operation is CreateFileMapping, I guess it has to do with either Server.MapPath() or Path.Combine(), however, the Server is an IDisposable (being derived from Controller), so can that be the one I should deal with?
Also, the resource I'm trying to delete is an image used on the website, which might be a problem, but that section of the website is not shown during this process.
I found the solution building on the comment of #Diablo.
The IIS was indeed holding on to the resource, but Server.MapPath() or any of that code had nothing to do with it: it was the Edit view my page returning the data to. With the help of this SO answer, it turns out I was careless with a BitMap that I used without a using statement in the view to get some image stats. I updated the helper function with the following code:
public static float GetImageWidthFromPath(string imgAbsolutPath, int offset)
{
float width = 0;
using (Bitmap b = new Bitmap(imgAbsolutPath))
{
width = b.Width - offset;
}
return width;
}
Now IIS does not hold on to the resource and I can delete the file.
Related
Before anyone points it out, yes, I know there have been similar questions asked before. I've already tried solutions from them and they do not work, which is why I'm asking this question.
For reference, I'm using Android API 30.
So I'm performing a query on all audio files on the device using MediaStore. As of now I'm able to properly access artist/album/song IDs, names, etc. and use them elsewhere in my app. The one thing I'm unable to get though is the album art.
This is what my code looks like:
string[] columns = {
MediaStore.Audio.Media.InterfaceConsts.IsMusic,
MediaStore.Audio.Media.InterfaceConsts.Data,
MediaStore.Audio.Media.InterfaceConsts.Title,
MediaStore.Audio.Media.InterfaceConsts.CdTrackNumber,
MediaStore.Audio.Media.InterfaceConsts.Duration,
MediaStore.Audio.Media.InterfaceConsts.Id,
MediaStore.Audio.Media.InterfaceConsts.Album,
MediaStore.Audio.Media.InterfaceConsts.AlbumId,
MediaStore.Audio.Media.InterfaceConsts.Artist,
MediaStore.Audio.Media.InterfaceConsts.ArtistId,
};
ICursor? cursor = context.ContentResolver?.Query(MediaStore.Audio.Media.ExternalContentUri!, columns, null, null, null);
if (cursor is null)
{
return;
}
while (cursor.MoveToNext())
{
if (cursor.GetString(0) is not "1")
{
continue;
}
string trackPath = cursor.GetString(1)!;
string trackTitle = cursor.GetString(2)!;
int trackIndex = cursor.GetInt(3);
uint trackDuration = (uint)(cursor.GetFloat(4) / 1000);
long trackId = cursor.GetLong(5);
string albumTitle = cursor.GetString(6)!;
long albumId = cursor.GetLong(7);
string artistName = cursor.GetString(8)!;
long artistId = cursor.GetLong(9);
I initially tried the obvious method, which was adding MediaStore.Audio.Media.InterfaceConsts.AlbumArt to columns and getting the data from that column. But just adding that causes the application to freeze. Logging shows that adding it causes the method to freeze midway and not do anything. So this solution is out.
I then tried MediaMetadataRetriever, like this:
MediaMetadataRetriever retriever = new();
retriever.SetDataSource(trackPath);
byte[] artwork = retriever.GetEmbeddedPicture()!;
Bitmap albumArt = BitmapFactory.DecodeByteArray(artwork, 0, artwork.Length)!;
However, this also fails. I get an error message in the logs saying that MediaMetadataRetriever was unable to set the data source to that source.
So I figured maybe my data source was wrong. After doing some digging around I tried using a different path:
Uri contentUri = Uri.Parse("content://media/external/audio/albumart")!;
Uri albumArtUri = ContentUris.WithAppendedId(contentUri, albumId);
MediaMetadataRetriever retriever = new();
retriever.SetDataSource(albumArtUri.Path);
...
This also does not work. Neither does using MediaStore.Audio.Albums.ExternalContentUri.
I even tried opening a ParcelFileDescriptor from those URIs. Also doesn't work.
Does anyone know of a way that would definitely work? Most of the answers on StackOverflow seem to be quite dated, so they possibly don't apply to API 30 anymore. But I don't know what I'm doing wrong since the documentation isn't very detailed.
I am trying to observe if a screenshot is taken while using my App on Iphone. If a screenshot is taken while using my App, I would like that screenshot to be deleted.
I also understand that during deletion, user needs to give permission for deletion.
I used an Observer method successfully to check if a screenshot is taken while using my app.
I am stuck at a point where I need to access that screenshot and delete it, of course with user permission.
```public override void OnActivated(UIApplication application)
{
try
{
// Start observing screenshot notification
if (_screenshotNotification == null)
{
_screenshotNotification = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.UserDidTakeScreenshotNotification,
(NSNotification n) => {
Console.WriteLine("UserTookScreenshot");
var photosOptions = new PHFetchOptions();
photosOptions.SortDescriptors = new NSSortDescriptor[] { new
NSSortDescriptor("creationDate", false) };
photosOptions.FetchLimit = 1;
var photo = PHAsset.FetchAssets(photosOptions);
Console.WriteLine(photo);
var filePath = photo.Path;
System.IO.File.Delete(filePath);
n.Dispose();
}
);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
}```
I know the above code does not work with deleting the current screenshot taken while using my App. It gives a general idea on what I want to achieve.
How can I delete the screenshot taken while using my APP from Iphone instantly (with user permission)? I would also like to know if it is possible.
Yes it is possible, you obviously have to properly manage the permissions for the image. In order to do this, first you have to add an observer to detect the screenshot as shown here
First: declare an NSObject variable on your AppDelegate. In the example below I added _screenshotNotification.
Second: On the AppDelegate’s OnActivated (app moving to active state), add code to start observing the UIApplication.UserDidTakeScreenshotNotification and then do something once the notification is posted.
Third: On the AppDelegate’s OnResignActivation (app moving to inactive state), add code to remove the observer.
Then you have to actually find the file & delete, so you in the page you are trying to do it, just add the Foundation & Photos using statements and then try this. (I converted the Swift from here but haven't tested it)
var fetchOptions = new PHFetchOptions();
fetchOptions.SortDescriptors[0] = new Foundation.NSSortDescriptor("creationDate", true);
var fetchResult = PHAsset.FetchAssets(PHAssetMediaType.Image, fetchOptions);
if (fetchResult != null)
{
var lastAsset = (fetchResult.LastObject as PHAsset);
var arrayToDelete = new PHAsset[1] { lastAsset };
PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() => { PHAssetChangeRequest.DeleteAssets(arrayToDelete); },
async (bool success, NSError errorMessage) => { }); //Handle Completion Here Appropriately
}
I don't think it is possible.
From iOS 11, when you take a screenshot, the snap minimizes itself in the bottom left corner of the screen. From here, save or delete the screenshot is decided by the user.(as the image below)
You can read more information form this article: how-take-screenshot-iphone-or-ipad-ios-11
Here comes the problem how do you know whether user has save the screenshot or not?
If the user saved, you can delete the screenShot form the photoLibrary.
While if the user dose not save the screenshot, what your deleted is not the screenshot.
Try this:
func didTakeScreenshot() {
self.perform(#selector(deleteAppScreenShot), with: nil, afterDelay: 1, inModes: [])
}
#objc func deleteAppScreenShot() {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors?[0] = Foundation.NSSortDescriptor(key: "creationDate", ascending: true)
let fetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
guard let lastAsset = fetchResult.lastObject else { return }
PHPhotoLibrary.shared().performChanges {
PHAssetChangeRequest.deleteAssets([lastAsset] as NSFastEnumeration)
} completionHandler: { (success, errorMessage) in
if !success, let errorMessage = errorMessage {
print(errorMessage.localizedDescription)
}
}
}
I can't sort this weird issue out and I have tried anything and everything I can think of.
I got 5 pages, everyone of them passing variables with navigation this way:
Pass:
NavigationSerice.Navigate(new Uri("/myPage.xaml?key=" + myVariable, UriKind.Relative));
Retrieve:
If (NavigationContext.QueryString.ContainsKey(myKey))
{
String retrievedVariable = NavigationContext.QueryString["myKey"].toString();
}
I open a list on many pages and one of the pages automatically deletes an item from the list actualProject (actualProject is a variable for a string list). Then, when I go so far back that I reach a specific page - the app throws an exception. Why? I have no idea.
The code that deletes the item:
// Remove the active subject from the availible subjects
unlinkedSubjects.Remove(actualSubject);
unlinkedsubjectsListBox.ItemsSource = null;
unlinkedsubjectsListBox.ItemsSource = unlinkedSubjects;
Then the page that throws the exception's OnNavigatedTo event:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (NavigationContext.QueryString.ContainsKey("key"))
{
actualProject = NavigationContext.QueryString["key"];
try
{
//Read subjectList from IsolatedStorage
subjectList = readSetting(actualProject) != null ? (List<String>)readSetting(actualProject) : new List<String>();
//Put the subjectList into the subjectListBox
subjectListBox.ItemsSource = subjectList;
//Set the subjectsPageTitle to the "actualProject" value, to display the name of the current open project at the top of the screen
subjectsPageTitle.Text = actualProject;
}
catch (Exception)
{
if (language.Equals("en."))
{
// Language is set to english
MessageBox.Show("Couldn't open the project, please try again or please report the error to Accelerated Code - details on the about page");
}
else if (language.Equals("no."))
{
// Language is set to norwegian
MessageBox.Show("Kunne ikke åpne prosjektet, vennligst prøv igjen eller rapporter problemet til Accelerated Code - du finner detaljer på om-siden");
}
}
}
}
Exception:
_exception {System.ArgumentException: Value does not fall within the expected range.} System.Exception {System.ArgumentException}
My theory:
The app kind of loads the currently opened and modified List. Is that possible? No idea.
So there are a number of ways to pass data between pages.
The way you have chosen is the least suggested.
You can use the PhoneApplicationService.Current dictionary but this is messy also if you have a ton of variables, doesn't persist after app shut down and could be simplified.
I wrote a free DLL that kept this exact scenario in mind called EZ_iso.
You can find it here
Basically what you would do to use it is this.
[DataContractAttribute]
public class YourPageVars{
[DataMember]
public Boolean Value1 = false;
[DataMember]
public String Value2 = "And so on";
[DataMember]
public List<String> MultipleValues;
}
Once you have your class setup you can pass it easily between pages
YourPageVars vars = new YourPageVars { /*Set all your values*/ };
//Now we save it
EZ_iso.IsolatedStorageAccess.SaveFile("PageVars",vars);
That's it! Now you can navigate and retrieve the file.
YourPageVars vars = (YourPageVars)EZ_iso.IsolatedStorageAccess.GetFile("PageVars",typeof(YorPageVars));
This is nice because you can use it for more than navigation. You can use it for anything that would require Isolated storage. This data is serialized to the device now so even if the app shuts down it will remain. You can of course always delete the file if you choose as well.
Please make sure to refer to the documentation for any exceptions you have. If you still need help feel free to hit me up on twitter #Anth0nyRussell or amr#AnthonyRussell.info
I seem to be having a timing issue when renaming images and then re displaying them. In my code I use System.IO.File.Move twice to rename some images in a directory. Then later I try to retrieve a list of files in the directory, but when I do so I get some file names that existed after the first rename, and some that existed after the 2nd rename. How do I ensure I get only file names that exist after the 2nd rename? I have contemplated putting in a Thread.Sleep(), but that feels like a hack. In case it helps, I'm using MVC3.
public ActionResult UpdateImages ()
{
foreach (file in directory)
System.IO.File.Move("oldname", "newname");
foreach (file in directory)
System.IO.File.Move("oldname", "newname");
return RedirectToAction("Images", "Manager", new { id = Id });
}
public ViewResult Images(int id)
{
var di = new DirectoryInfo(Server.MapPath("something")));
var files = di.GetFileSystemInfos("*-glr.jpg");
var orderedFiles = files.OrderBy(f => f.Name);
var images = new List<string>();
images.AddRange(orderedFiles.Select(fileSystemInfo => fileSystemInfo.Name));
ViewData["Images"] = images;
return View();
}
edit
I wish I could remove my own question. It seems I have solved this and the answer isn't even related to the information I provided in the question.
It seems that I ended up sending both a Get and a Post to the server. The Post kicked off the work, but the response from the post got aborted since the Get was also fired. Since the Get finished quickly, it would catch the system in an in-between state.
The offending line of code was a anchor element that had both an href and a javascript click handler (through jQuery) attached to it.
Small problem here with an MVC app that I'm not sure how to figure out a way around.
Basically, I'm adding additional functionality to a system that was originally created by someone else (c#). For a reporting system, the results were only ever displayed on screen. Now I am building in the functionality to allow the user the ability to download their report as an Excel document.
So basically, I have a view that displays the date ranges, and some other search refinement options to the user. I have introduced a radio button that if selected will download the report as opposed to displaying it on screen.
Here are my three actions within the ReportController:
public ActionResult Index()
{
return View();
}
public ActionResult ProductReport(AdminReportRequest reportRequest, FormCollection formVariables)
{
AdminEngine re = new AdminEngine();
if (!reportRequest.Download)
{
AdminReport report = re.GetCompleteAdminReport(reportRequest);
return View(report);
}
Stream ExcelReport = re.GetExcelAdminReport(reportRequest);
TempData["excelReport"] = ExcelReport;
return RedirectToAction("ExcelProductReport");
}
public FileResult ExcelReport()
{
var ExcelReport = TempData["excelReport"] as Stream;
return new FileStreamResult(ExcelReport, "application/ms-excel")
{
FileDownloadName = "Report" + DateTime.Now.ToString("MMMM d, yyy") + ".xls"
};
}
I've debugged through the AdminEngine, and everything looks fine. However, in the ExcelReport action, when it comes to returning the file - it doesn't. What I see is a lot of characters on screen (in the 'panelReport' div - see below), mixed in with what would be the data in the excel file.
I think I have established that the reason it is being displayed on screen is as a result of some code
that was written in the Index view:
<% using (Ajax.BeginForm("ProductReport", "Report", null,
new AjaxOptions
{
UpdateTargetId = "panelReport",
InsertionMode = InsertionMode.Replace,
OnSuccess = "pageLoaded",
OnBegin = "pageLoading",
OnFailure = "pageFailed",
LoadingElementId = ""
},
new { id = "SearchForm" })) %>
As you can see, the Ajax.BeginForm statement states that it should update to the panelReport div - which is what it's doing (through the Product Report partial view). While this is perfect for when the reports need to be displayed on screen, it is obviously not going to work with an excel file.
Is there a way of working around this issue without changing the existing code too much?
Here is the class where I do the workings for the excel file in case it's required to shed light on the situation:
Report Class:
public Stream GetExcelAdminReport(AdminReportRequest reportRequest)
{
AdminReport report = new AdminReport();
string dateRange = null;
List<ProductSale> productSales = GetSortedListOfProducts(reportRequest, out dateRange);
report.DateRange = dateRange;
if (productSales.Count > 0)
{
report.HasData = true;
CustomisedSalesReport CustSalesRep = new CustomisedSalesReport();
Stream SalesReport = CustSalesRep.GenerateCustomisedSalesFile(productSales);
return SalesReport;
}
}
Workings Class:
public class CustomisedSalesReport
{
public Stream GenerateCustomisedSalesFile(List<ProductSale> productSales)
{
MemoryStream ms = new MemoryStream();
HSSFWorkbook templateWorkbook = new HSSFWorkbook();
HSSFSheet sheet = templateWorkbook.CreateSheet("Sales Report");
//Workings
templateWorkbook.Write(ms);
ms.Position = 0;
return ms;
}
}
The problem is pretty obvious that you are using an Ajax Form to download a file. On top you are using the built-in Microsoft Ajax libraries, which seem to be not intelligent enough.
I can provide 2 solutions:
The easiest solution (which I have used in the past) is that instead of streaming a file yourself, create an excel file and save it on the server and then send the download link to the user. It won't require a lot of change to the code.
You could handle the OnSubmit event of the AjaxForm, see if you it's a file to download. If yes, then make a full postback request (using $.post()). This way the browser will automatically pop-up the dialog asking for where to download.
Hope it makes sense.