Lock JSON File for Reading and Writing - c#

Hi I have an app that uses a json file to monitor run intervals of emails. I use it as a simple way to store the times I run an email notification. But I've run into an issue where the user can run concurrent instances of this app. What happens then is that two instances can read that json file and if they both read that an email is not sent, both instances will send an email leading to duplicate emails.
This is how the code is (sampled down for replicability)
public class SummaryEmailStatus
{
public DateTime DateSent { get; set; }
public string ToolType { get; set; }
public bool IsSent { get; set; }
}
public static class JsonUtil
{
public static Dictionary<string, SummaryEmailStatus> EmailStatusKVP = new Dictionary<string, SummaryEmailStatus>();
public static bool CreateEmailItemJasonFile(string jsonFileName)
{
var summCfgEmailStatus = new SummaryEmailStatus
{
DateSent = DateTime.Parse("2022-01-01"),
ToolType = "CIM",
IsSent = false
};
EmailStatusKVP.Add(SummaryEmailJsonObjs.ConfigError, summCfgEmailStatus);
string json = JsonConvert.SerializeObject(EmailStatusKVP, Formatting.Indented);
File.WriteAllText(jsonFileName, json);
}
public static Dictionary<string, SummaryEmailStatus> ReadEmailItemJsonFromFile(string jsonFileName)
{
string json = File.ReadAllText(jsonFileName);
EmailStatusKVP = JsonConvert.DeserializeObject<Dictionary<string, SummaryEmailStatus>>(json);
return EmailStatusKVP;
}
public static void WriteSummEmailStatusJsonToFile(string summaryEmailType, SummaryEmailStatus emailItem)
{
//string json = JsonConvert.SerializeObject(emailItem, Formatting.Indented);
EmailStatusKVP[summaryEmailType] = emailItem;
string json = JsonConvert.SerializeObject(EmailStatusKVP, Formatting.Indented);
File.WriteAllText(ParserConfigFilesConstants.SummaryEmailJsonFilePath, json);
}
}
The issue is I am using File.WritallText and ReadAllText. Is there a way to do what I am doing but in a way that locks the file each time the CreateEmailItemJasonFile or ReadEmailItemJsonFromFile or WriteSummEmailStatusJsonToFile is called?
I want only one instance for the console application to use this file. If the other instance tries to use it, it should get some "being used by another program" exception.
I saw this solution How to lock a file with C#? but with how new I am to C# I am not sure how to use it for my own needs.
I also thought about using a lock object around my File.Write and File.Read sections but I was under the impression that would only work if its another thread within the console application instance:
lock (fileReadLock)
{
string json = File.ReadAllText(jsonFileName);
}

I fixed it by using FileStream:
For reading I used:
FileStream fileStream = new FileStream(jsonFileName, FileMode.Open, FileAccess.Read, FileShare.None);
using (StreamReader reader = new StreamReader(fileStream))
{
json = reader.ReadToEnd();
}
And for Writing I used:
FileStream fs = new FileStream(ParserConfigFilesConstants.SummaryEmailJsonFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
using (StreamWriter writer = new StreamWriter(fs))
{
writer.WriteLine(json);
writer.Flush();
}
This allows me to lock the file out whenever my app is using the file.

Related

Serializing and deserializing a collection of objects

I'm trying to create an Rssfeed reader which saves info about a podcast to a JSON file and I'm having trouble serializing and deserializing to that file.
I realize that there are other threads regarding this subject, but I cannot grasp or comprehend how to apply it to my code or the reasoning behind it.
So I have a bit of code that creates a file if it doesn't exist and writes JSON data to it which looks like:
public void SaveFile(Podcast podcast)
{
try
{
JsonSerializer serializer = new JsonSerializer();
if(!File.Exists(#"C: \Users\Kasper\Desktop\Projektuppgift\Projektuppgift - Delkurs2\Projektet\Projektet\bin\Debug\podcasts.json"))
{
string json = JsonConvert.SerializeObject( new { Podcast = podcast });
StreamWriter sw = File.CreateText(#"C:\Users\Kasper\Desktop\Projektuppgift\Projektuppgift-Delkurs2\Projektet\Projektet\bin\Debug\podcasts.json");
using (JsonWriter writer = new JsonTextWriter(sw))
{
serializer.Serialize(writer, json);
}
}
else
{
var filepath = #"C:\Users\Kasper\Desktop\Projektuppgift\Projektuppgift-Delkurs2\Projektet\Projektet\bin\Debug\podcasts.json";
var jsonData = File.ReadAllText(filepath);
var podcasts = JsonConvert.DeserializeObject<List<Podcast>>(jsonData) ?? new List<Podcast>();
podcasts.Add(podcast);
jsonData = JsonConvert.SerializeObject(new {PodcastList = podcasts });
File.WriteAllText(filepath, jsonData);
}
}
catch (Exception ex)
{
Console.WriteLine("IO Exception ", ex.Message);
}
}
What I can't get to work is to deserialize from this file and add an object to it. Is there an easier way to add more data to the JSON file or am I missing something?
The Podcast class looks like this:
public class Podcast
{
public string url { get; set; }
public string name { get; set; }
public int updateInterval { get; set; }
public string category { get; set; }
//public Category category = new Category();
public List<Episode> episodes { get; set; }
public Podcast(string url, string name, Category category, List<Episode> episodes, int updateInterval)
{
this.url = url;
this.name = name;
this.category = category.name;
this.episodes = episodes;
this.updateInterval = updateInterval;
}
public Podcast(Podcast p)
{
this.url = p.url;
this.name = p.name;
this.category = p.category;
this.episodes = p.episodes;
this.updateInterval = p.updateInterval;
}
}
There appear to be a couple of issues here:
You are checking for the existence of a different file than the one you are reading/writing. The former filename has extra spaces in it. The best way to avoid this problem is to use a variable to contain the filename rather than hardcoding it in three separate places.
You are inconsistent about the JSON format you are writing and reading:
When you first create the file (in the first branch), you are writing a JSON object that contains a property Podcast which then contains a single podcast.
When you attempt to read the JSON file, you are treating the entire JSON as a list of podcasts.
After tacking the new podcast onto the list, you are writing the JSON as a single object containing a PodcastList property, which then contains the list.
You need to use a consistent JSON format. I would recommend breaking your code into smaller methods to read and write the podcasts.json file like this so that it is easier to reason about:
public static List<Podcast> ReadPodcastsFromFile(string filepath)
{
if (!File.Exists(filepath)) return new List<Podcast>();
string json = File.ReadAllText(filepath);
return JsonConvert.DeserializeObject<List<Podcast>>(json);
}
public static void WritePodcastsToFile(List<Podcast> podcasts, string filepath)
{
string json = JsonConvert.SerializeObject(podcasts);
// This will overwrite the file if it exists, or create a new one if it doesn't
File.WriteAllText(filepath, json);
}
Then, you can simplify your SaveFile method down to this (I would be tempted to rename it to SavePodcast):
public void SaveFile(Podcast podcast)
{
var filepath = #"C:\Users\Kasper\Desktop\Projektuppgift\Projektuppgift-Delkurs2\Projektet\Projektet\bin\Debug\podcasts.json";
List<Podcast> podcasts = ReadPodcastsFromFile(filepath);
podcasts.Add(podcast);
WritePodcastsToFile(podcasts, filepath);
}
Notice I've also removed the exception handling from SaveFile. You should move that up to wherever SaveFile is called, so that you can take appropriate action at that point if an exception is thrown, e.g.:
try
{
SaveFile(podcast);
}
catch (Exception ex)
{
// Show a message to the user indicating that the file did not save
}
I'm just still learning c# but it might be that you deserialise into a list of podcasts and when you serialise you're serliasing into an object type.

Windows 8 phone Saving and loading a list of Objects

I would like to save and load my History list filled with History Entry objects. I am trying to do this through Isolated Storage, so that when the user opens and closes the app none of their browsing history is lost. It is saved which can be loaded once the app is clicked. I have had a look around and saw this question on stackoverflow, and I have tried to follow it but came across so errors. Isolated Storage & Saving Multiple Objects.
Here is the code
The HistoryEntry class
public string URL { get; set; }
public string timestamp { get; set; }
public string date { get; set; }
The MainPage code:
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using System.IO;
List<HistoryEntry> urls = new List<HistoryEntry>();
public HistoryEntry selectedHistory;
public MainPage()
{
InitializeComponent();
Deserialize<>(urls, ???);
}
void Browser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
HistoryEntry urlObj = new HistoryEntry();
urlObj.URL = url;
urlObj.timestamp = DateTime.Now.ToString("HH:mm");
urlObj.date = url.Remove(url.LastIndexOf('.'));
urls.Add(urlObj);
textBox1.Text = url;
listBox.ItemsSource = null;
listBox.ItemsSource = urls;
Serialize(urlObj, urls);
}
private static void Serialize(string fileName, object source)
{
var userStore = IsolatedStorageFile.GetUserStoreForApplication();
using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Create, userStore))
{
XmlSerializer serializer = new XmlSerializer(source.GetType());
serializer.Serialize(stream, source);
}
}
public static void Deserialize<T>(ObservableCollection<T> list, string filename)
{
list = new ObservableCollection<T>();
var userStore = IsolatedStorageFile.GetUserStoreForApplication();
if (userStore.FileExists(filename))
{
using (var stream = new IsolatedStorageFileStream(filename, FileMode.Open, userStore))
{
XmlSerializer serializer = new XmlSerializer(list.GetType());
var items = (ObservableCollection<T>)serializer.Deserialize(stream);
foreach (T item in items)
{
list.Add(item);
}
}
}
}
Serialize has some invalid arguments which is the same with when De serialize is called. What are the appropriate values to be sent to the method, and will this successfully save and load the history objects.
Thank you in advance :)
If you need any more details please comment and I will be happy to explain in further detail :)
Did you try this way using MemoryStream which it did work for Win 8 :
To Serialize:
MemoryStream sessionData = new MemoryStream();
DataContractSerializer serializer = new
DataContractSerializer(typeof(ObservableCollection<NewsByTag>));
serializer.WriteObject(sessionData, data);
StorageFile file = await ApplicationData.Current.LocalFolder
.CreateFileAsync(sFileName);
using (Stream fileStream = await file.OpenStreamForWriteAsync())
{
sessionData.Seek(0, SeekOrigin.Begin);
await sessionData.CopyToAsync(fileStream);
await fileStream.FlushAsync();
}
To Deserialize it back:
StorageFile file = await ApplicationData.Current.LocalFolder.
GetFileAsync(sFileName);
using (IInputStream inStream = await file.OpenSequentialReadAsync())
{
DataContractSerializer serializer =
new DataContractSerializer(typeof(ObservableCollection<NewsByTag>));
var data = (ObservableCollection<NewsByTag>)serializer
.ReadObject(inStream.AsStreamForRead());
}
Hope it helps!

Taglib-sharp: how to use the IFileAbstraction to allow reading metadata from stream?

I'm trying to read the metadata of a mp3 file stored in IsolatedStorage using TagLib.
I know TagLib normally only take a file path as input but as WP uses a sandbox environment I need to use a stream.
Following this tutorial (http://www.geekchamp.com/articles/reading-and-writing-metadata-tags-with-taglib) I created a iFileAbstraction interface:
public class SimpleFile
{
public SimpleFile(string Name, Stream Stream)
{
this.Name = Name;
this.Stream = Stream;
}
public string Name { get; set; }
public Stream Stream { get; set; }
}
public class SimpleFileAbstraction : TagLib.File.IFileAbstraction
{
private SimpleFile file;
public SimpleFileAbstraction(SimpleFile file)
{
this.file = file;
}
public string Name
{
get { return file.Name; }
}
public System.IO.Stream ReadStream
{
get { return file.Stream; }
}
public System.IO.Stream WriteStream
{
get { return file.Stream; }
}
public void CloseStream(System.IO.Stream stream)
{
stream.Position = 0;
}
}
Normally I would now be able to do this:
using (IsolatedStorageFileStream filestream = new IsolatedStorageFileStream(name, FileMode.OpenOrCreate, FileAccess.ReadWrite, store))
{
filestream.Write(data, 0, data.Length);
// read id3 tags and add
SimpleFile newfile = new SimpleFile(name, filestream);
TagLib.Tag tags = TagLib.File.Create(newfile);
}
The problem is that TagLib.File.Create still doesn't want to accept the SimpleFile object.
How do I make this work?
Your code doesn't compile because TagLib.File.Create wants IFileAbstraction on input, and you're giving it SimpleFile instance which doesn't implement the interface. Here's one way to fix:
// read id3 tags and add
SimpleFile file1 = new SimpleFile( name, filestream );
SimpleFileAbstraction file2 = new SimpleFileAbstraction( file1 );
TagLib.Tag tags = TagLib.File.Create( file2 );
Don't ask me why we need SimpleFile class instead of passing name and stream into SimpleFileAbstraction - it was in your sample.

CsvHelper not writing anything to memory stream

I have the following method:
public byte[] WriteCsvWithHeaderToMemory<T>(IEnumerable<T> records) where T : class
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords<T>(records);
return memoryStream.ToArray();
}
}
Which is being called with a list of objects - eventually from a database, but since something is not working I'm just populating a static collection. The objects being passed are as follows:
using CsvHelper.Configuration;
namespace Application.Models.ViewModels
{
public class Model
{
[CsvField(Name = "Field 1", Ignore = false)]
public string Field1 { get; set; }
[CsvField(Name = "Statistic 1", Ignore = false)]
public int Stat1{ get; set; }
[CsvField(Name = "Statistic 2", Ignore = false)]
public int Stat2{ get; set; }
[CsvField(Name = "Statistic 3", Ignore = false)]
public int Stat3{ get; set; }
[CsvField(Name = "Statistic 4", Ignore = false)]
public int Stat4{ get; set; }
}
}
What I'm trying to do is write a collection to a csv for download in an MVC application. Every time I try to write to the method though, the MemoryStream is coming back with zero length and nothing being passed to it. I've used this before, but for some reason it's just not working - I'm somewhat confused. Can anyone point out to me what I've done wrong here?
Cheers
You already have a using block which is great. That will flush your writer for you. You can just change your code slightly for it to work.
using (var memoryStream = new MemoryStream())
{
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords<T>(records);
} // StreamWriter gets flushed here.
return memoryStream.ToArray();
}
If you turn AutoFlush on, you need to be careful. This will flush after every write. If your stream is a network stream and over the wire, it will be very slow.
Put csvWriter.Flush(); before you return to flush the writer/stream.
EDIT: Per Jack's response. It should be the stream that gets flushed, not the csvWriter. streamWriter.Flush();. Leaving original solution, but adding this correction.
EDIT 2: My preferred answer is: https://stackoverflow.com/a/22997765/1795053 Let the using statements do the heavy lifting for you
Putting all these together (and the comments for corrections), including resetting the memory stream position, the final solution for me was;
using (MemoryStream ms = new MemoryStream())
{
using (TextWriter tw = new StreamWriter(ms))
using (CsvWriter csv = new CsvWriter(tw, CultureInfo.InvariantCulture))
{
csv.WriteRecords(errors); // Converts error records to CSV
tw.Flush(); // flush the buffered text to stream
ms.Seek(0, SeekOrigin.Begin); // reset stream position
Attachment a = new Attachment(ms, "errors.csv"); // Create attachment from the stream
// I sent an email here with the csv attached.
}
}
In case the helps someone else!
There is no flush in csvWriter, the flush is in the streamWriter. When called
csvWriter.Dispose();
it will flush the stream.
Another approach is to set
streamWriter.AutoFlush = true;
which will automatically flush the stream every time.
Here is working example:
void Main()
{
var records = new List<dynamic>{
new { Id = 1, Name = "one" },
new { Id = 2, Name = "two" },
};
Console.WriteLine(records.ToCsv());
}
public static class Extensions {
public static string ToCsv<T>(this IEnumerable<T> collection)
{
using (var memoryStream = new MemoryStream())
{
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(collection);
} // StreamWriter gets flushed here.
return Encoding.ASCII.GetString(memoryStream.ToArray());
}
}
}
Based on this answer.
using CsvHelper;
public class TwentyFoursStock
{
[Name("sellerSku")]
public string ProductSellerSku { get; set; }
[Name("shippingPoint")]
public string ProductShippingPoint { get; set; }
}
using (var writer = new StreamWriter("file.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(TwentyFoursStock);
}

silverlight, Save load, IsolatedStorageFile and IsolatedStorageFileStream. Exceptions

Windows Phone 7 App
The Goal of the application is a simple To Do list.
I have a class 'toditem' i add those objects to the Items object.
it seems to me I'm doing something really complicated and most likely no clean or decent code
But i have some serious problems with "IsolatedStorageFile"
public class ToDoItem
{
public string ToDoName { get; set; } // Add controle's enz.
public string ToDoDescription { get; set; }
internal Priority PriortiySelection { get; set; }
...
}
Items class (basicly a wrapper clas so i can acces it)
public class Items
{
public static List<ToDoItem> Itemslist = new List<ToDoItem>();
public static List<ToDoItem> GetList()
static methods here..
}
The code Belows returns the following exceptions :
"Attempt to access the method failed:
System.Io.streamreader..ctor
(System.String)"
and afterwards i get
Operation not permitted on IsolatedStorageFileSTream
if (store.FileExists(#"items.std"))
{
ToDoItem item = new ToDoItem();
try
{
IsolatedStorageFileStream save = new IsolatedStorageFileStream(#"items.std", FileMode.Open, store);
BinaryReader reader = new BinaryReader(save);
}
catch (Exception exc)
{
MessageBox.Show(exc.Message);
}
in public partial class NewToDo : PhoneApplicationPage
i added the following method. wich returns the above exceptions again i only assume that its allowd for some reason or i make some huge mistakes.
private void saveItem(ToDoItem toDoItem)
{
try
{
using (StreamWriter sw = new StreamWriter(store.OpenFile(#"items.std", FileMode.Append)))
{
sw.WriteLine(toDoItem.ToDoName);
sw.WriteLine(toDoItem.ToDoDescription);
sw.WriteLine(toDoItem.PriortiySelection.ToString());
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
Should u need more information I'm always glad to provide it, I'm currently a student at a Belgium college second year and I'm playing around with windows phone7 apps.
The following will read the contents of a file from isolated storage
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
if (!store.FileExists(VIEW_MODEL_STORAGE_FILE))
{
return result;
}
using (var isfs = new IsolatedStorageFileStream(VIEW_MODEL_STORAGE_FILE, FileMode.Open, store))
{
using (var sr = new StreamReader(isfs))
{
string lineOfData;
while ((lineOfData = sr.ReadLine()) != null)
{
result += lineOfData;
}
}
}
}
The example builds a string of data (result). This is actually a serialized object which is actually a collection of other objects. This can then be deserialized back to the collection. This is probably preferable to what you were trying to do with writing out properties to a file one at a time.
Here's how to write the file:
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
using (var isfs = new IsolatedStorageFileStream(VIEW_MODEL_STORAGE_FILE, FileMode.Create, store))
{
using (var sw = new StreamWriter(isfs))
{
sw.Write(serializedCollectionObject);
sw.Close();
}
}
}
Is it possible you're not disposing all your disposable objects and encountering a problem when you try to access a resource for a second time because it's still in use?
The using statement is a good way to handle this easily, more on that here.
Dispose with Using
A bit more background on the topic here where Jm47 was getting the same error message for this reason.
Problem opening a stream to an isolatedstorage image already the source on an image?

Categories

Resources