Ensure 2 methods succeed else roll back - c#

Can anyone please tell me whether this is possible.
I have some code that allows a user to upload/change their image, before the change takes place I delete the default/old image from disk before uploading new image.
Problem is if something goes wrong either with the delete or upload, how can I roll back both so that the original image is returned.
I thought I could use tranactionscope, but either I'm not using it correctly or it not applicable for this case.
All the examples I have found involve using 2 call to database, but my code only involves one call and that's to update.
//TODO check transactionscope works ok
using (var tran = new TransactionScope())
{
//Delete old image before updating new image
//123 bogus number to throw error
var deleteOldImage = _igmpfu.DisplayProfileDetailsForUpdate("123")
.FirstOrDefault();
if (Convert.ToString(deleteOldImage) !=
"5bb188f0-2508-4cbd-b83d-9a5fe5914a1b.png")
{
DeleteOldImage(deleteOldImage);
}
//Insert new image
var imageGuid = imageId + ".png";
bool imageUrl = _iuma.UpdateAvatar(cookieId, imageGuid);
if (imageUrl)
{
TempData["Message"] = "Image updated";
return RedirectToAction("Index", "Members");
}
tran.Complete();
}
Any assistance in helping a newbie would be appreciated
//------------------------
I have been looking at the computer to long, all I had to do was
var deleteOldImage = _igmpfu.DisplayProfileDetailsForUpdate("123").FirstOrDefault();
if (deleteOldImage != null)
{
code here for writing to disk
}
I have spent ages trying to work this out and thats all I had to do :-(
Thanks everyone for their replies.

The code you have would only work if the classes you are using know how to enlist in the transaction.
The main issue you will encounter when dealing with files is that the file system is difficult to deal with transactionally. The approach I would use is this:
Save the new file on disk with a different filename.
Update the database with the new filename.
If the db update was successful, delete the old file from disk, if not delete the new file from disk.

Related

Attached images in Folder or Database?

I'm currently working on a .NET (core 3.1) website project and I am a little stuck on how to handle images and as I could not find a proper response for my case, here it is.
I'm working on a reports system where the user should be allowed to create a report and attach images if necessary. My question is, should I store the images in a database or a folder? The images will not contain "National security threats" but I guess they could be of a private nature.
Is it a good practice to store them on a Database?
I found it a bit messy the procedure to store them:
public async Task<IActionResult> Create(IFormFile image)
{
if (ModelState.IsValid)
{
byte[] p1 = null; //As I understand, it should be store as byte[]
using (var fs1 = image.OpenReadStream())
using (var ms1 = new MemoryStream())
{
fs1.CopyTo(ms1);
p1 = ms1.ToArray();
}
Image img = new Image(); //This is my Image model
img.Img = p1; //The property .IMG is of type "varbinary" on the DB.
_imagesDB.Images.Add(img); //My context
await _imagesDB.SaveChangesAsync();
return RedirectToAction(nameof(Index)); //if everything went well go back to index-
}
return View(report);
}
This is more or less ok (I guess) but I was not able to read it back from the database and send it to the View for showing.
Any ideas on how to read back the images from my context and, specially, how to send it from the controller to the View?
Thanks in advance.-
Alvaro.
There are pros and cons of both methods of storing files. It's convenient to have your files where your data is - however it takes a toll on the database side.
Text (the file path) in the database is only a few thousand bytes max (varchar data type, not the text data type in SQL), while a file can be enormous.
Imagine you wanted to query 1,000,000 users (hypothetically) - you would also be querying 1,000,000 files. That an enormous amount of data. Storing text (the file path) is minimal and a query could retrieve 1,000,000 rows of text rather quickly.
This can slow down your web app by causing longer load times due to your queries. I've had this issue personally and had to actually make a lazy load workaround to speed up the app.
Also, you have to consider the backup/restore process for your database. The larger the database then the longer your backup/restore times will take - and databases only grow. I heard a story about a company who backed up their database nightly, but their backup time took longer than a day due to files in their database. They weren't even done with the backup the day prior when the next backup started.
There are other factors to consider but those few alone are significant considerations.
In regards to the C# view/controller process...
Files are stored as bytes in a database (varbinary). You'll have to query the data and store them in a byte[] just like you are now and convert it to a file.
Here's a simplified snippet of one of my controllers in my .NET Core 3.1 web app.
This was only to download 1 PDF file - you will have to change it for your needs of course.
public async Task<IActionResult> Download(string docId, string docSource)
{
// Some kinda of validation...
if (!string.IsNullOrEmpty(docId))
{
// These are my query parameters (I'm using Dapper)
var p = new
{
docId,
docSource // This is just a parameter for my specific query
};
// Query the database for the document
// DocumentModel doc = some kinda of async query using
// the p variables as parameters
// I cut this part out since your database methods may be different
try
{
// Return the file
return File(doc.Content, "application/pdf", doc.LeafName);
}
catch
{
// You'll probably want to pass some kind of error message to your view
return View();
}
}
return View();
}
The doc.Content are the bytes and the doc.LeafName is just the name of the document.
You can also pass the file back to your View by setting properties on it's ViewModel/Model.
return View(new YourViewModel
{
SomeViewModelProperty = someProp,
Documents = documents
});
If you use a file server that's accessible to your API or web app then I believe you can retrieve the file directly from there.

Caching posted data and fall-backs

I'm currently working on a project that has an external site posting xml data to a specified url on our site. My initial thoughts were to first of all save the xml data to a physical file on our server as a backup. I then insert the data into the cache and from then on, all requests for the data will be made to the cache instead of the physical file.
At the moment I have the following:
[HttpPost]
public void MyHandler()
{
// filePath = path to my xml file
// Delete the previous file
if (File.Exists(filePath))
File.Delete(filePath));
using (Stream output = File.OpenWrite(filePath))
using (Stream input = request.InputStream)
{
input.CopyTo(output);
}
// Deserialize and save the data to the cache
var xml = new XmlTextReader(filePath);
var serializer = new XmlSerializer(typeof(MyClass));
var myClass = (MyClass)serializer.Deserialize(xml);
HttpContext.Current.Cache.Insert(myKey,
myClass,
null,
myTimespan,
Cache.NoSlidingExpiration,
CacheItemPriority.Default, null);
}
The issue I have is that I'm always getting exceptions thrown because the file that I'm saving to 'is in use' when I try a second post to update the data.
A colleague suggested using a Mutex class just before I left work on the Friday so I wonder if that is the correct approach here?
Basically I'm just trying to sanity check that this is a good way of managing the data? I can see there's clearly an issue with how I'm writing the data to a file but aside from this, does my approach make sense?
Thanks

WP8: Losing data or access to XML file in isolated storage

I am creating a WP8 shopping list app that stores user created lists(in my code, each shopping list is defined as a ListObj that I defined). I would like to save the lists created by users through an .xml file. As long as I continue to add to this list of ListObj's, I seem to have no issues. But I start to experience trouble when I want to remove a ListObj from my list. When I reopen my app after I removed something from my list of ListObj's and attempt to load my list upon start, I enter this try/catch block:
try
{
using (IsolatedStorageFile appStorage =
IsolatedStorageFile.GetUserStoreForApplication())
{
if (appStorage.FileExists("rootList.xml"))
{
using (IsolatedStorageFileStream isStream =
appStorage.OpenFile("rootList.xml",
FileMode.Open, FileAccess.Read))
{
XmlSerializer serializer = new XmlSerializer(typeof(List<ListObj>));
rootList = (List<ListObj>)serializer.Deserialize(isStream);
}
}
else
{
rootList = new List<ListObj>();
Debug.WriteLine("rootList not found.");
}
}
}
catch
{
///Uhhh....
}
However, my program executes the catch statement in which nothing happens obviously. I am unsure of what to execute in this catch block in order to diagnose my problem. I think I am losing access to the app's Isolated storage but again, I am unsure as to how to proceed. Any ideas?
So I ended up finding a solution to my problem. It turns out that the way I modified my .xml file caused an error in which it can no longer be read. I solved this by completely overwriting my file with the modified data rather than trying to change the existing data.

WinRT create a file if it doesn't exist and know that the file was created

I would like to open a file, and if it does not exist create it, similar to this question
The catch is that if the file was newly created I perform some additional initialization logic.
I can use
await folder.CreateFileAsync(fileName, CreationCollisionOption.OpenIfExists)
to open the file if it already exists, but how can I tell if the result of this operation was a newly created file or just opening an existing file? Is there a best practice here or am I stuck looking at the file create date or file size?
Ideally the code would look something like this:
var file = await folder.CreateFileAsync(fileName, CreationCollsionOption.OpenIfExists);
//How can I reliably tell if the file was new?
if (fileIsNew)
await InitializeFile(file);
With using the CreationCollsionOption.OpenIfExists there is no way to tell what's happened other than looking the CreatedDate or the Size of the file.
So I think you are better if you use the GetFileAsync (like in the linked question) and do your initilaize logic in the catch FileNotFoundException branch, because there is no File.Exists in WinRt
However there is a third solution where you enumerate the files in the directory and check for existence by hand:
var files = await folder.GetFilesAsync(CommonFileQuery.OrderByName);
var file = files.FirstOrDefault(x => x.Name == fileName);
if (file == null)
{
file = await GetFilesAsync(fileName);
await InitializeFile(file);
}
Note: in this case potentially you have to prepare to handle race conditions becase it can happen while you are processing the file list somebody creates the file before you (that's why there is no File.Exists method exists)
I had a similar situation where I wanted to determine if an app was running for the first time. Here's how I approached it.
try
{
await ApplicationData.Current.LocalFolder.GetFileAsync(FirstRunFile);
}
catch (FileNotFoundException)
{
isFirstRun = true;
}
if (isFirstRun) {
// Perform necessary initialization here
await ApplicationData.Current.LocalFolder.CreateFileAsync(FirstRunFile);
}

GDI+ A generic error ... Attempting to save file

I've been learning quite a bit from everyone here, however I've come across a problem which I'm having trouble finding an answer to. While attempting to save an image which has been uploaded using AsyncFileUpload I'm receiving the following error: A generic error occurred in GDI+.
I've done quite a bit of Googling and it seems that the most likely issue is permissions related. I've tried allowing write/modify for Network and Network_Serice. Later attempted allowing it for the environment user and finally tested it with "Everyone" - unfortunately it was still not working.
One possible issue is the fact that I'm relying on the built-in development server. I'm still fairly new to it all, so not too sure if there are any limitations in this area or if I'm simply overlooking something obvious.
Code:
try
{
//Attempt to convert file to image
uploadedImage = System.Drawing.Image.FromStream(avatarUpload.PostedFile.InputStream);
//Save image
if (uploadedImage != null)
{
//Create vars
String savePath = Server.MapPath("~/Images/Avatars/");
//Save image
uploadedImage.Save(savePath, ImageFormat.Jpeg); !!! Exception raised here
//Dispose of image
uploadedImage.Dispose();
}
}
catch
{
//Create error message
createMessage("Please ensure you select an image i.e. .bmp, .png, .jpg.", "error");
}
If anyone has any ideas or needs any more info please let me know.
Change
String savePath = Server.MapPath("~/Images/Avatars/");
To
String savePath = Server.MapPath("~/Images/Avatars/myImage.jpg");

Categories