Check last modified date of file in C# - c#

I'm looking to find out a way of seeing when a file was last modified in C#. I have full access to the file.

System.IO.File.GetLastWriteTime is what you need.

You simply want the File.GetLastWriteTime static method.
Example:
var lastModified = System.IO.File.GetLastWriteTime("C:\foo.bar");
Console.WriteLine(lastModified.ToString("dd/MM/yy HH:mm:ss"));
Note however that in the rare case the last-modified time is not updated by the system when writing to the file (this can happen intentionally as an optimisation for high-frequency writing, e.g. logging, or as a bug), then this approach will fail, and you will instead need to subscribe to file write notifications from the system, constantly listening.

Be aware that the function File.GetLastWriteTime does not always work as expected, the values are sometimes not instantaneously updated by the OS. You may get an old Timestamp, even if the file has been modified right before.
The behaviour may vary between OS versions. For example, this unit test worked well every time on my developer machine, but it always fails on our build server.
[TestMethod]
public void TestLastModifiedTimeStamps()
{
var tempFile = Path.GetTempFileName();
var lastModified = File.GetLastWriteTime(tempFile);
using (new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
}
Assert.AreNotEqual(lastModified, File.GetLastWriteTime(tempFile));
}
See File.GetLastWriteTime seems to be returning 'out of date' value
Your options:
a) live with the occasional omissions.
b) Build up an active component realising the observer pattern (eg. a tcp server client structure), communicating the changes directly instead of writing / reading files. Fast and flexible, but another dependency and a possible point of failure (and some work, of course).
c) Ensure the signalling process by replacing the content of a dedicated signal file that other processes regularly read. It´s not that smart as it´s a polling procedure and has a greater overhead than calling File.GetLastWriteTime, but if not checking the content from too many places too often, it will do the work.
/// <summary>
/// type to set signals or check for them using a central file
/// </summary>
public class FileSignal
{
/// <summary>
/// path to the central file for signal control
/// </summary>
public string FilePath { get; private set; }
/// <summary>
/// numbers of retries when not able to retrieve (exclusive) file access
/// </summary>
public int MaxCollisions { get; private set; }
/// <summary>
/// timespan to wait until next try
/// </summary>
public TimeSpan SleepOnCollisionInterval { get; private set; }
/// <summary>
/// Timestamp of the last signal
/// </summary>
public DateTime LastSignal { get; private set; }
/// <summary>
/// constructor
/// </summary>
/// <param name="filePath">path to the central file for signal control</param>
/// <param name="maxCollisions">numbers of retries when not able to retrieve (exclusive) file access</param>
/// <param name="sleepOnCollisionInterval">timespan to wait until next try </param>
public FileSignal(string filePath, int maxCollisions, TimeSpan sleepOnCollisionInterval)
{
FilePath = filePath;
MaxCollisions = maxCollisions;
SleepOnCollisionInterval = sleepOnCollisionInterval;
LastSignal = GetSignalTimeStamp();
}
/// <summary>
/// constructor using a default value of 50 ms for sleepOnCollisionInterval
/// </summary>
/// <param name="filePath">path to the central file for signal control</param>
/// <param name="maxCollisions">numbers of retries when not able to retrieve (exclusive) file access</param>
public FileSignal(string filePath, int maxCollisions): this (filePath, maxCollisions, TimeSpan.FromMilliseconds(50))
{
}
/// <summary>
/// constructor using a default value of 50 ms for sleepOnCollisionInterval and a default value of 10 for maxCollisions
/// </summary>
/// <param name="filePath">path to the central file for signal control</param>
public FileSignal(string filePath) : this(filePath, 10)
{
}
private Stream GetFileStream(FileAccess fileAccess)
{
var i = 0;
while (true)
{
try
{
return new FileStream(FilePath, FileMode.Create, fileAccess, FileShare.None);
}
catch (Exception e)
{
i++;
if (i >= MaxCollisions)
{
throw e;
}
Thread.Sleep(SleepOnCollisionInterval);
};
};
}
private DateTime GetSignalTimeStamp()
{
if (!File.Exists(FilePath))
{
return DateTime.MinValue;
}
using (var stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None))
{
if(stream.Length == 0)
{
return DateTime.MinValue;
}
using (var reader = new BinaryReader(stream))
{
return DateTime.FromBinary(reader.ReadInt64());
};
}
}
/// <summary>
/// overwrites the existing central file and writes the current time into it.
/// </summary>
public void Signal()
{
LastSignal = DateTime.Now;
using (var stream = new FileStream(FilePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
using (var writer = new BinaryWriter(stream))
{
writer.Write(LastSignal.ToBinary());
}
}
}
/// <summary>
/// returns true if the file signal has changed, otherwise false.
/// </summary>
public bool CheckIfSignalled()
{
var signal = GetSignalTimeStamp();
var signalTimestampChanged = LastSignal != signal;
LastSignal = signal;
return signalTimestampChanged;
}
}
Some tests for it:
[TestMethod]
public void TestSignal()
{
var fileSignal = new FileSignal(Path.GetTempFileName());
var fileSignal2 = new FileSignal(fileSignal.FilePath);
Assert.IsFalse(fileSignal.CheckIfSignalled());
Assert.IsFalse(fileSignal2.CheckIfSignalled());
Assert.AreEqual(fileSignal.LastSignal, fileSignal2.LastSignal);
fileSignal.Signal();
Assert.IsFalse(fileSignal.CheckIfSignalled());
Assert.AreNotEqual(fileSignal.LastSignal, fileSignal2.LastSignal);
Assert.IsTrue(fileSignal2.CheckIfSignalled());
Assert.AreEqual(fileSignal.LastSignal, fileSignal2.LastSignal);
Assert.IsFalse(fileSignal2.CheckIfSignalled());
}

Just use File.GetLastWriteTime. There's a sample on that page showing how to use it.

The actual real last modified DateTime is in Microsoft.VisualBasic.FileSystem.FileDateTime(pathString).
LastWriteTime will give you a false positive if the file has been copied from its original location.
See https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.filesystem.filedatetime?view=netframework-4.8
Also: I have noticed that some files transferred between computers can differ in their modified DateTime by a second or so. NFI why. I added a 5-second fudge-factor to my check to account for this discrepancy.

Related

SQL Server CLR function aggregation of sorted string

In order to get a sorted aggregated string, I wrote the CLR function below. However, it always returns empty instead of what I expected, just like "001, 002, 003". I tried to debug the CLR function in visual studio 2017, but threw the error message
The operation could not be completed. Unspecified error
Code:
[Serializable]
[SqlUserDefinedAggregate(
Format.UserDefined, //use clr serialization to serialize the intermediate result
Name = "CLRSortedCssvAgg", //aggregate name on sql
IsInvariantToNulls = true, //optimizer property
IsInvariantToDuplicates = false, //optimizer property
IsInvariantToOrder = false, //optimizer property
IsNullIfEmpty = false, //optimizer property
MaxByteSize = -1) //maximum size in bytes of persisted value
]
public class SortedCssvConcatenateAgg : IBinarySerialize
{
/// <summary>
/// The variable that holds all the strings to be aggregated.
/// </summary>
List<string> aggregationList;
StringBuilder accumulator;
/// <summary>
/// Separator between concatenated values.
/// </summary>
const string CommaSpaceSeparator = ", ";
/// <summary>
/// Initialize the internal data structures.
/// </summary>
public void Init()
{
accumulator = new StringBuilder();
aggregationList = new List<string>();
}
/// <summary>
/// Accumulate the next value, not if the value is null or empty.
/// </summary>
public void Accumulate(SqlString value)
{
if (value.IsNull || String.IsNullOrEmpty(value.Value))
{
return;
}
aggregationList.Add(value.Value);
}
/// <summary>
/// Merge the partially computed aggregate with this aggregate.
/// </summary>
/// <param name="other"></param>
public void Merge(SortedCssvConcatenateAgg other)
{
aggregationList.AddRange(other.aggregationList);
}
/// <summary>
/// Called at the end of aggregation, to return the results of the aggregation.
/// </summary>
/// <returns></returns>
public SqlString Terminate()
{
if (aggregationList != null && aggregationList.Count > 0)
{
aggregationList.Sort();
accumulator.Append(string.Join(CommaSpaceSeparator, aggregationList));
aggregationList.Clear();
}
return new SqlString(accumulator.ToString());
}
public void Read(BinaryReader r)
{
accumulator = new StringBuilder(r.ReadString());
}
public void Write(BinaryWriter w)
{
w.Write(accumulator.ToString());
}
}
You are close. Just need a few minor adjustments. Do the following and it will work (I tested it):
Remove all references to accumulator. It is not used.
Replace the Terminate(), Read(), and Write() methods with the following:
public SqlString Terminate()
{
string _Aggregation = null;
if (aggregationList != null && aggregationList.Count > 0)
{
aggregationList.Sort();
_Aggregation = string.Join(CommaSpaceSeparator, aggregationList);
}
return new SqlString(_Aggregation);
}
public void Read(BinaryReader r)
{
int _Count = r.ReadInt32();
aggregationList = new List<string>(_Count);
for (int _Index = 0; _Index < _Count; _Index++)
{
aggregationList.Add(r.ReadString());
}
}
public void Write(BinaryWriter w)
{
w.Write(aggregationList.Count);
foreach (string _Item in aggregationList)
{
w.Write(_Item);
}
}
That said, I'm not sure if this approach is faster or slower than the FOR XML approach, but a UDA certainly makes for a more readable query, especially if you need multiple aggregations.
Still, I should mention that starting in SQL Server 2017, this became a built-in function: STRING_AGG (which allows for sorting via the WITHIN GROUP (ORDER BY ... ) clause).
In your Accumulate and Merge, you're dealing with your aggregationList; in Read and Write you're dealing with accumulator. You should pick one or the other for all of them and use it. As I understand it, Read and Write are used when the engine needs to persist temporary results to a work table. For your case, when it does that, it's persisting only your empty StringBuilder.

How to convert voice speech in format .wav and to use it

I am developing a bot which will receive voice ( from facebook channel ) and will convert it in .wav .
I'm using a sample of : How can a bot receive a voice file from Facebook Messenger (MP4) and convert it to a format that is recognized by speech engines like Bing or Google?
I had a problem during converting i think .
Here is my code :
messagescontroller.cs :
namespace SpeechToText.Controllers
{
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Connector;
using Services;
using System.Web.Configuration;
using System.IO;
[BotAuthentication]
public class MessagesController : ApiController
{
private readonly MicrosoftCognitiveSpeechService speechService = new MicrosoftCognitiveSpeechService();
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
string message = string.Empty;
IncomingFacebookVoiceMessage voice = null;
try
{
voice = new IncomingFacebookVoiceMessage(activity);
//await SendReply(activity, "The type of your voice file is " + voice.ContentType);
}
catch
{
message = "Send me a voice message instead!"; // no voice file found
}
try
{
if (voice != null)
{
//Download original MP4 voice message
voice.DownloadFile();
var mp4 = voice.GetLocalPathAndFileName();
//Convert MP4 to WAV
// var wavFolder = Utils.GetHomeFolder() + WebConfigurationManager.AppSettings["WAVFilesFolder"];
var wavFolder = "C:" + #"\" + "Teste" ;
var converter = new AudioFileFormatConverter(mp4, wavFolder);
var wav = converter.ConvertMP4ToWAV();
//Convert .WAV file to text
var bing = new MicrosoftCognitiveSpeechService(); //gets the path + filename
// var text = await bing.GetTextFromAudioAsync(wav); //takes path+filename to WAV file, returns text
// convert string to stream
byte[] data = File.ReadAllBytes(wav);
//byte[] byteArray = Encoding.ASCII.GetBytes(contents);
MemoryStream stream = new MemoryStream(data);
var text = await this.speechService.GetTextFromAudioAsync(stream);
if (string.IsNullOrWhiteSpace(text))
{
message = "Looks like you didn't say anything.";
}
else
{
message = text;
}
//Clean up files from disk
voice.RemoveFromDisk();
converter.RemoveTargetFileFromDisk();
}
}
catch (Exception ex)
{
message = "Woah! " + ex.Message.Trim().Trim('.') + "!";
}
Activity reply = activity.CreateReply(message);
await connector.Conversations.ReplyToActivityAsync(reply);
}
else
{
await this.HandleSystemMessage(activity);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
/// <summary>
/// Gets the JwT token of the bot.
/// </summary>
/// <param name="connector"></param>
/// <returns>JwT token of the bot</returns>
/// <summary>
/// Handles the system activity.
/// </summary>
/// <param name="activity">The activity.</param>
/// <returns>Activity</returns>
private async Task<Activity> HandleSystemMessage(Activity activity)
{
switch (activity.Type)
{
case ActivityTypes.DeleteUserData:
// Implement user deletion here
// If we handle user deletion, return a real message
break;
case ActivityTypes.ConversationUpdate:
// Greet the user the first time the bot is added to a conversation.
if (activity.MembersAdded.Any(m => m.Id == activity.Recipient.Id))
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
var response = activity.CreateReply();
response.Text = "Hi! I am SpeechToText Bot. I can understand the content of any audio and convert it to text. Try sending me a wav file.";
await connector.Conversations.ReplyToActivityAsync(response);
}
break;
case ActivityTypes.ContactRelationUpdate:
// Handle add/remove from contact lists
break;
case ActivityTypes.Typing:
// Handle knowing that the user is typing
break;
case ActivityTypes.Ping:
break;
}
return null;
}
}
}
-------------------- IncomingFacebookVoiceMessage.cs
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Configuration;
namespace SpeechToText
{
/// <summary>
/// Represents an incoming voice message from facebook messenger,
/// where the voice data is in an MP4 file (the message contains a link to
download it).
/// </summary>
public class IncomingFacebookVoiceMessage
{
#region Properties
/// <summary>
/// URL of the MP4 file sent by user and stored on facebook's servers.
/// </summary>
public Uri MP4FileUrl { get; private set; }
/// <summary>
/// Local filename of the MP4 file after it has been downloaded from Facebook.
/// </summary>
private string MP4LocalFileName { get; set; }
/// <summary>
/// Path to the folder on local disk containing the downloaded voice messages from Facebook.
/// This is configured in Web.config using the FacebookDownloadedVoiceMessagesFolder key.
/// The path in the Web.config will be relative to the site's root folder.
/// </summary>
public string VoiceMessageFolder { get; private set; }
/// <summary>
/// Content-type of the attachment (for debugging - it's not always MP4).
/// </summary>
public string ContentType { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Constructor that uses an MP4 file link that is
/// received in activity.Attachments by bot framework.
/// </summary>
/// <param name="MP4FileUrl">URL of the MP4 file sent by user and stored on facebook's servers.</param>
public IncomingFacebookVoiceMessage(string MP4FileUrl)
{
if (string.IsNullOrWhiteSpace(MP4FileUrl))
{
throw new Exception("The MP4 file URL was empty.");
}
this.MP4FileUrl = new Uri(MP4FileUrl);
this.VoiceMessageFolder = GetVoiceMessagesFolderFromWebConfig();
}
/// <summary>
/// A shortcut constructor that extracts the URL of the MP4 voice message file
/// from the Activity object received by the controller in the Bot Framework.
/// </summary>
/// <param name="activity">The Activity object that contains an attachment of type video/mp4. If no attachment, throws an exception.</param>
public IncomingFacebookVoiceMessage(IMessageActivity activity)
{
var mp4Attachment = activity.Attachments?.FirstOrDefault(a => a.ContentType.Equals("video/mp4") || a.ContentType.Contains("audio") || a.ContentType.Contains("video"));
if (mp4Attachment == null)
{
throw new Exception("The message didn't have a voice attachment.");
}
else
{
this.MP4FileUrl = new Uri(mp4Attachment.ContentUrl);
this.VoiceMessageFolder = GetVoiceMessagesFolderFromWebConfig();
this.ContentType = mp4Attachment.ContentType; //for debugging. Different devices send different content-types, e.g. audio/aac and video/mp4
}
}
#endregion
#region Public methods
/// <summary>
/// Downloads the MP4 file containing the voice message from Facebook.
/// </summary>
/// <returns>The filename (without path) of the MP4 file stored on local disk.</returns>
public string DownloadFile()
{
var filename = GetRandomFileName();
var filenameWithPath = VoiceMessageFolder + #"\" + filename;
//if folder doesn't exist, create it
if (!Directory.Exists(VoiceMessageFolder))
{
Directory.CreateDirectory(VoiceMessageFolder);
}
using (var client = new WebClient())
{
client.DownloadFile(this.MP4FileUrl, filenameWithPath);
}
MP4LocalFileName = filename;
return filename;
}
/// <summary>
/// Removes the downloaded MP4 file from the local disk to clean up space.
/// </summary>
/// <returns>True if successfully removed, false otherwise.</returns>
public bool RemoveFromDisk()
{
try
{
File.Delete(GetLocalPathAndFileName());
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Returns the full local path and filename to the downloaded MP4 voice message.
/// </summary>
/// <returns>E.g. D:\home\site\wwwroot\abc.mp4</returns>
public string GetLocalPathAndFileName()
{
if (string.IsNullOrWhiteSpace(MP4LocalFileName))
{
throw new Exception("The voice message has not been downloaded yet.");
}
return VoiceMessageFolder + #"\" + MP4LocalFileName;
}
#endregion
#region Private methods
/// <summary>
/// Reads Web.config and returns the path to the folder which will store downloaded messages.
/// The folder in the config must be relative to the site's root.
/// </summary>
/// <returns>Full path to the folder that will be used to store MP4 voice messages.</returns>
private string GetVoiceMessagesFolderFromWebConfig()
{
//return Utils.GetHomeFolder() + WebConfigurationManager.AppSettings["FacebookDownloadedVoiceMessagesFolder"];
return "C:" + #"\" + "Teste" ;
}
/// <summary>
/// Generates a random filename using a new GUID.
/// </summary>
/// <returns>A random file name in the format "msg-GUID.mp4".</returns>
private string GetRandomFileName()
{
return "msg-" + Guid.NewGuid() + ".mp4";
}
#endregion
}
}
---------------AudioFileFormatConverter.cs
using MediaToolkit;
using MediaToolkit.Model;
using System;
using System.IO;
using System.Web.Configuration;
namespace SpeechToText
{
/// <summary>
/// Converts audio files between various formats using the open-source
FFMPEG software.
/// </summary>
public class AudioFileFormatConverter
{
#region Properties
/// <summary>
/// Path + filename to the source file.
/// </summary>
public string SourceFileNameAndPath { get; private set; }
/// <summary>
/// Path + filename to the file which is the result of the conversion.
/// </summary>
public string ConvertedFileNameAndPath { get; private set; }
/// <summary>
/// The folder where the converted files will be stored.
/// </summary>
public string TargetPath { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="sourceFileNameAndPath">Filename and path to the source
file.</param>
/// <param name="targetPath">The folder where the converted files will
be stored.</param>
public AudioFileFormatConverter(string sourceFileNameAndPath, string
targetPath)
{
if (string.IsNullOrWhiteSpace(sourceFileNameAndPath))
{
throw new Exception("Empty source filename.");
}
else if (string.IsNullOrWhiteSpace(targetPath))
{
throw new Exception("Empty target path.");
}
else
{
this.SourceFileNameAndPath = sourceFileNameAndPath;
this.TargetPath = targetPath;
//create folder if it's not there
if (!Directory.Exists(TargetPath))
{
Directory.CreateDirectory(TargetPath);
}
}
}
#endregion
#region Public methods
/// <summary>
/// Converts a source MP4 file to a target WAV file and returns the path
and filename of the converted file.
/// </summary>
/// <returns>The path and filename of the converted file.</returns>
public string ConvertMP4ToWAV()
{
ConvertedFileNameAndPath = TargetPath + #"\" +
Path.GetFileNameWithoutExtension(SourceFileNameAndPath) + ".wav";
//use the same
file name as original, but different folder and extension
var inputFile = new MediaFile { Filename = SourceFileNameAndPath };
var outputFile = new MediaFile { Filename = ConvertedFileNameAndPath
};
using (var engine = new Engine(GetFFMPEGBinaryPath()))
{
engine.Convert(inputFile, outputFile);
}
return ConvertedFileNameAndPath;
}
/// <summary>
/// Removes the converted file from disk to free up space.
/// Doesn't remove the source file.
/// </summary>
/// <returns>True if file deleted successfully, false if cannot delete,
exception if filename is empty.</returns>
public bool RemoveTargetFileFromDisk()
{
if (string.IsNullOrWhiteSpace(ConvertedFileNameAndPath))
{
throw new Exception("The file has not been converted yet, so it cannot be deleted.");
}
try
{
File.Delete(ConvertedFileNameAndPath);
return true;
}
catch
{
return false;
}
}
#endregion
#region Private methods
/// <summary>
/// Reads the config to determine the location of ffmpeg.exe.
/// </summary>
/// <returns>The full path and filename to the ffmpeg.exe program.
</returns>
public string GetFFMPEGBinaryPath()
{
// return Utils.GetHomeFolder() +
WebConfigurationManager.AppSettings["FFMPEGBinaryLocation"];
return "D:" + #"\" + "Teste";
}
#endregion
}
}
Here is my output message :
Can you please help me how to resolve the problem...where i should store the audio register in facebook messenger ?
I think all you are missing is ffmpeg.exe in the GetFFMPEGBinaryPath() method. Once I added that to the end of the path, the code you have functions as expected.
public string GetFFMPEGBinaryPath()
{
return "D:" + #"\Teste\ffmpeg-20170921-183fd30-win64-static\bin\ffmpeg.exe";
}

Problems rendering image from database byte field using WebImage

I have an asp.net mvc 5 app with a database that stores photos. I'm trying to read the photo and resize it for display in a Staff profile.
I'm fairly new to both asp.net mvc and c#
I setup the following controller but am not getting an image display when I use a link to the controller in an img tag.
Any help would be appreciated.
public ActionResult Index(int id)
{
Staff staff = db.StaffList.Find(id);
if (staff.Photo != null)
{
var img = new WebImage(staff.Photo);
img.Resize(100, 100, true, true);
var imgBytes = img.GetBytes();
return File(imgBytes, "image/" + img.ImageFormat);
}
else
{
return null;
}
}
Looking around it seems there's a lot of dissatisfaction with the WebImage class and it has a few prominent bugs. I settled on using a nuget package called ImageProcessor rather than trying to write my own. This seems fairly inefficient to me but I don't have a better answer right now and this isn't heavily used so I'm going with this and just moving on.
Posting it here in case anyone else is struggling with something similar.
public ActionResult Index(int id, int? height, int? width)
{
int h = (height ?? 325);
int w = (width ?? 325);
Staff staff = db.StaffList.Find(id);
if (staff == null)
{
return new HttpNotFoundResult();
}
if (staff.Photo != null)
{
Size size = new Size(w, h);
byte[] rawImg;
using (MemoryStream inStream = new MemoryStream(staff.Photo))
{
using (MemoryStream outStream = new MemoryStream())
{
using (ImageFactory imageFactory = new ImageFactory())
{
imageFactory.Load(inStream)
.Constrain(size)
.Format(format)
.Save(outStream);
}
rawImg = outStream.ToArray();
}
}
return new FileContentResult(rawImg, "image/jpeg");
}
else
{
return null;
}
}
I'm going to answer this using ImageProcessor since your own answer uses the library. Disclaimer. I'm also the author of the library.
You're really not best using an ActionResult for processing images as it will be horribly inefficient. It run's far too late in the pipeline and you will have no caching.
You're much better off installing the Imageprocessor.Web package and implementing your own version of the IImageService interface to serve your images from the database. (On that note storing images in a db unless you are using blob storage is never wise either)
Implementing the IImageService interface allows you to utilise the url api within the library with a specific prefix to identify which image service to use. For example, remote image requests are prefixed with remote.axd to tell IageProcessor.Web to excute the RemoteImageService implementation.
This has the benefit of caching your images so subsequent requests are returned from a cache rather than being reprocessed upon each request.
Here is the full implementation of the LocalFileImageService which is the default service for the library. This should be able to serve as a guide for how to implement your own service.
namespace ImageProcessor.Web.Services
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Web;
using ImageProcessor.Web.Helpers;
/// <summary>
/// The local file image service for retrieving images from the
/// file system.
/// </summary>
public class LocalFileImageService : IImageService
{
/// <summary>
/// The prefix for the given implementation.
/// </summary>
private string prefix = string.Empty;
/// <summary>
/// Gets or sets the prefix for the given implementation.
/// <remarks>
/// This value is used as a prefix for any image requests
/// that should use this service.
/// </remarks>
/// </summary>
public string Prefix
{
get
{
return this.prefix;
}
set
{
this.prefix = value;
}
}
/// <summary>
/// Gets a value indicating whether the image service
/// requests files from
/// the locally based file system.
/// </summary>
public bool IsFileLocalService
{
get
{
return true;
}
}
/// <summary>
/// Gets or sets any additional settings required by the service.
/// </summary>
public Dictionary<string, string> Settings { get; set; }
/// <summary>
/// Gets or sets the white list of <see cref="System.Uri"/>.
/// </summary>
public Uri[] WhiteList { get; set; }
/// <summary>
/// Gets a value indicating whether the current request
/// passes sanitizing rules.
/// </summary>
/// <param name="path">
/// The image path.
/// </param>
/// <returns>
/// <c>True</c> if the request is valid; otherwise, <c>False</c>.
/// </returns>
public bool IsValidRequest(string path)
{
return ImageHelpers.IsValidImageExtension(path);
}
/// <summary>
/// Gets the image using the given identifier.
/// </summary>
/// <param name="id">
/// The value identifying the image to fetch.
/// </param>
/// <returns>
/// The <see cref="System.Byte"/> array containing the image data.
/// </returns>
public async Task<byte[]> GetImage(object id)
{
string path = id.ToString();
byte[] buffer;
// Check to see if the file exists.
// ReSharper disable once AssignNullToNotNullAttribute
FileInfo fileInfo = new FileInfo(path);
if (!fileInfo.Exists)
{
throw new HttpException(404, "No image exists at " + path);
}
using (FileStream file = new FileStream(path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
4096,
true))
{
buffer = new byte[file.Length];
await file.ReadAsync(buffer, 0, (int)file.Length);
}
return buffer;
}
}
}

Dynamic Windows form - list with XML

I am creating a dynamic form where if I click on "Add", a new panel appears with a set of buttons.
In this panel, I would like to add a list which will remember how many buttons were created. Thus I thought realize a list, but I want to increment again even though that we restart the console.
May be there is a tip to put it in a XMLfile. In this way my strategy can be renamed with the max of what is present in the CSV, but I do not know how to record and how to increment...
Any idea?
public class SerialStrategyFuture
{
public string StrategyName { get; set; }
public string NumStrategy { get; set; }
}
public void CreateStrategyFuture()
{
ConsoleStrategyItem strategyItemFuture = new ConsoleStrategyItem();
strategyItemFuture.Location = new Point(3, 3);
futureContainer.Height += 85;
futureContainer.Controls.Add(strategyItemFuture);
SerialStrategyFuture strategyFuture = new SerialStrategyFuture();
strategyFuture.StrategyName = "Strat Future ";
strategyFuture.NumStrategy = "How to increment it ???";
XmlSerializer serializer = new XmlSerializer(typeof(SerialStrategyFuture));
TextWriter textWriter = new StreamWriter(#"C:\Users\...");
serializer.Serialize(textWriter,strategyFuture);
textWriter.Close();
ConsoleStrategyItem.Instance.txtStrategyName.Text = "Strat Future 1 ";
}
I am not sure if you can serialize the utureContainer.Controls List. Threfore, I would use this way:
Declare a List ouf your buttons:
List controlList = new List();
each time your users creates a new button:
controlList.add(strategyItemFuture);
Serialize the List when your program closes or another opportune moment:
Deserialize it when your program starts.
build a foreach loop which pulls the buttons from the deserialized List.
Here are the two methods I use to serialize / deserialize
/// <summary>
/// Serializes a file to a compressed XML file. If an error occurs, the exception is NOT caught.
/// </summary>
/// <typeparam name="T">The Type</typeparam>
/// <param name="obj">The object.</param>
/// <param name="fileName">Name of the file.</param>
public static void SerializeToXML<T>(T obj, string fileName)
{
try
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (FileStream fs = new FileStream(fileName, FileMode.Create))
{
using (GZipStream compressor = new GZipStream(fs, CompressionMode.Compress))
{
serializer.Serialize(compressor, obj);
}
}
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Deserializes an object from XML.
/// </summary>
/// <typeparam name="T">The object</typeparam>
/// <param name="file">The file.</param>
/// <returns>
/// The deserialized object
/// </returns>
public static T DeserializeFromXml<T>(string file)
{
T result;
XmlSerializer ser = new XmlSerializer(typeof(T));
using (FileStream fs = new FileStream(file, FileMode.Open))
{
using (GZipStream decompressor = new GZipStream(fs, CompressionMode.Decompress))
{
result = (T)ser.Deserialize(decompressor);
return result;
}
}
}
}
Usage:
SerializeToXML(controlList , yourPath);
this.controlList = DeserializeFromXml<List<ConsoleStrategyItem>>(yourPath);
I am doing a little bit more than just storing the number of buttons, but this has the advantage that you can store more data when your buttons have more logic.

Custom path of the user.config

I manage my application-settings using the setting-designer in VS2008.
"The exact path of the user.config
files looks something like this:"
<Profile Directory>\<Company Name>\
<App Name>_<Evidence Type>_<Evidence Hash>\<Version>\user.config
Is there to a way to customize this path? I would prefer something like this:
<Profile Directory>\<Company Name>\
<App Name>\<Version>\user.config
I noticed that white-spaces were replaced by underscores in the "Company Name" in the new created folder ("Test Company" --> "Test_Company"). I really wish to turn off this behavior.
You know, I could write a new XML-based setting-handler, but I would like to use the setting-designer.
It hasn't been easy to find good info on implementing a custom settings provider so I'm including a complete implementation below (bottom.) The format of the user.config file is retained, as well as the functionality within the .settings designer. I'm certain there are parts that can be cleaned up a bit, so don't hassle me :)
Like others, I wanted to change the location of the user.config file and still get the fun and fanciness of working with the .settings files in the designer, including creating default values for new installations. Importantly, our app also already has other saved settings objects at a path (appData\local\etc) in which we have already decided, and we didn't want artifacts in multiple locations.
The code is much longer than I'd like it to be, but there is no SHORT answer that I could find. Though it seems somewhat painful just to be able to control the path, creating a custom settings provider in itself is still pretty powerful. One could alter the follwing implementation to store the data just about anywhere including a custom encrypted file, database, or interact with a web serivice.
From what I've read, Microsoft does not intend on making the path to the config file configurable. I'll take their word for it when they say allowing that would be scary. See (this) post. Alas, if you want to do it yourself you must implement your own SettingsProvider.
Here goes..
Add a reference in your project to System.Configuration, you'll need it to implement the SettingsProvider.
Easy bit...Create a class which implements SettingsProvider, use ctrl+. to help you out.
class CustomSettingsProvider : SettingsProvider
Another easy bit...Go to the code behind of your .settings file (there is a button in the designer) and decorate the class to point it to your implementation. This must be done to override the built in functionality, but it does not change how the designer works.(sorry the formatting here is weird)
[System.Configuration.SettingsProvider(typeof(YourCompany.YourProduct.CustomSettingsProvider))]
public sealed partial class Settings
{
//bla bla bla
}
Getting the path: There is a property called "SettingsKey" (e.g. Properties.Settings.Default.SettingsKey) I used this to store the path. I made the following property.
/// <summary>
/// The key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = #"C:\temp\user.config";</c>
/// </summary>
private string UserConfigPath
{
get
{
return Properties.Settings.Default.SettingsKey;
}
}
Storing the settings values. I chose to use a dictionary. This will get used extensively in a bit. I created a struct as a helper.
/// <summary>
/// In memory storage of the settings values
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }
/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
internal string name;
internal string serializeAs;
internal string value;
}
The magic. You must override 2 methods, GetPropertyValues and SetPropertyValues. GetPropertyValues recieves as a parameter what you see in the designer, you have to opportunity to update the values and return a new collection. SetPropertyValues is called when the user saves any changes to the values made at runtime, this is where I update the dictionary and write out the file.
/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
//load the file
if (!_loaded)
{
_loaded = true;
LoadValuesFromFile();
}
//collection that will be returned.
SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();
//iterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
foreach (SettingsProperty setting in collection)
{
SettingsPropertyValue value = new SettingsPropertyValue(setting);
value.IsDirty = false;
//need the type of the value for the strong typing
var t = Type.GetType(setting.PropertyType.FullName);
if (SettingsDictionary.ContainsKey(setting.Name))
{
value.SerializedValue = SettingsDictionary[setting.Name].value;
value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
}
else //use defaults in the case where there are no settings yet
{
value.SerializedValue = setting.DefaultValue;
value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
}
values.Add(value);
}
return values;
}
/// <summary>
/// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
//grab the values from the collection parameter and update the values in our dictionary.
foreach (SettingsPropertyValue value in collection)
{
var setting = new SettingStruct()
{
value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
name = value.Name,
serializeAs = value.Property.SerializeAs.ToString()
};
if (!SettingsDictionary.ContainsKey(value.Name))
{
SettingsDictionary.Add(value.Name, setting);
}
else
{
SettingsDictionary[value.Name] = setting;
}
}
//now that our local dictionary is up-to-date, save it to disk.
SaveValuesToFile();
}
Complete solution. So here's the entire class which includes the constructor, Initialize, and helper methods. Feel free to cut/paste slice and dice.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.IO;
namespace YourCompany.YourProduct
{
class CustomSettingsProvider : SettingsProvider
{
const string NAME = "name";
const string SERIALIZE_AS = "serializeAs";
const string CONFIG = "configuration";
const string USER_SETTINGS = "userSettings";
const string SETTING = "setting";
/// <summary>
/// Loads the file into memory.
/// </summary>
public CustomSettingsProvider()
{
SettingsDictionary = new Dictionary<string, SettingStruct>();
}
/// <summary>
/// Override.
/// </summary>
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(ApplicationName, config);
}
/// <summary>
/// Override.
/// </summary>
public override string ApplicationName
{
get
{
return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
}
set
{
//do nothing
}
}
/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
//load the file
if (!_loaded)
{
_loaded = true;
LoadValuesFromFile();
}
//collection that will be returned.
SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();
//itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
foreach (SettingsProperty setting in collection)
{
SettingsPropertyValue value = new SettingsPropertyValue(setting);
value.IsDirty = false;
//need the type of the value for the strong typing
var t = Type.GetType(setting.PropertyType.FullName);
if (SettingsDictionary.ContainsKey(setting.Name))
{
value.SerializedValue = SettingsDictionary[setting.Name].value;
value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
}
else //use defaults in the case where there are no settings yet
{
value.SerializedValue = setting.DefaultValue;
value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
}
values.Add(value);
}
return values;
}
/// <summary>
/// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
//grab the values from the collection parameter and update the values in our dictionary.
foreach (SettingsPropertyValue value in collection)
{
var setting = new SettingStruct()
{
value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
name = value.Name,
serializeAs = value.Property.SerializeAs.ToString()
};
if (!SettingsDictionary.ContainsKey(value.Name))
{
SettingsDictionary.Add(value.Name, setting);
}
else
{
SettingsDictionary[value.Name] = setting;
}
}
//now that our local dictionary is up-to-date, save it to disk.
SaveValuesToFile();
}
/// <summary>
/// Loads the values of the file into memory.
/// </summary>
private void LoadValuesFromFile()
{
if (!File.Exists(UserConfigPath))
{
//if the config file is not where it's supposed to be create a new one.
CreateEmptyConfig();
}
//load the xml
var configXml = XDocument.Load(UserConfigPath);
//get all of the <setting name="..." serializeAs="..."> elements.
var settingElements = configXml.Element(CONFIG).Element(USER_SETTINGS).Element(typeof(Properties.Settings).FullName).Elements(SETTING);
//iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
//using "String" as default serializeAs...just in case, no real good reason.
foreach (var element in settingElements)
{
var newSetting = new SettingStruct()
{
name = element.Attribute(NAME) == null ? String.Empty : element.Attribute(NAME).Value,
serializeAs = element.Attribute(SERIALIZE_AS) == null ? "String" : element.Attribute(SERIALIZE_AS).Value,
value = element.Value ?? String.Empty
};
SettingsDictionary.Add(element.Attribute(NAME).Value, newSetting);
}
}
/// <summary>
/// Creates an empty user.config file...looks like the one MS creates.
/// This could be overkill a simple key/value pairing would probably do.
/// </summary>
private void CreateEmptyConfig()
{
var doc = new XDocument();
var declaration = new XDeclaration("1.0", "utf-8", "true");
var config = new XElement(CONFIG);
var userSettings = new XElement(USER_SETTINGS);
var group = new XElement(typeof(Properties.Settings).FullName);
userSettings.Add(group);
config.Add(userSettings);
doc.Add(config);
doc.Declaration = declaration;
doc.Save(UserConfigPath);
}
/// <summary>
/// Saves the in memory dictionary to the user config file
/// </summary>
private void SaveValuesToFile()
{
//load the current xml from the file.
var import = XDocument.Load(UserConfigPath);
//get the settings group (e.g. <Company.Project.Desktop.Settings>)
var settingsSection = import.Element(CONFIG).Element(USER_SETTINGS).Element(typeof(Properties.Settings).FullName);
//iterate though the dictionary, either updating the value or adding the new setting.
foreach (var entry in SettingsDictionary)
{
var setting = settingsSection.Elements().FirstOrDefault(e => e.Attribute(NAME).Value == entry.Key);
if (setting == null) //this can happen if a new setting is added via the .settings designer.
{
var newSetting = new XElement(SETTING);
newSetting.Add(new XAttribute(NAME, entry.Value.name));
newSetting.Add(new XAttribute(SERIALIZE_AS, entry.Value.serializeAs));
newSetting.Value = (entry.Value.value ?? String.Empty);
settingsSection.Add(newSetting);
}
else //update the value if it exists.
{
setting.Value = (entry.Value.value ?? String.Empty);
}
}
import.Save(UserConfigPath);
}
/// <summary>
/// The setting key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = #"C:\temp\user.config";</c>
/// </summary>
private string UserConfigPath
{
get
{
return Properties.Settings.Default.SettingsKey;
}
}
/// <summary>
/// In memory storage of the settings values
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }
/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
internal string name;
internal string serializeAs;
internal string value;
}
bool _loaded;
}
}
You would have to implement your own SettingsProvider to customize the path.
See this Client Settings FAQ
Q: Why is the path so obscure? Is there any way to change/customize it?
A: The path construction algorithm has to meet certain rigorous requirements in terms of security, isolation and robustness. While we tried to make the path as easily discoverable as possible by making use of friendly, application supplied strings, it is not possible to keep the path totally simple without running into issues like collisions with other apps, spoofing etc.
The LocalFileSettingsProvider does not provide a way to change the files in which settings are stored. Note that the provider itself doesn't determine the config file locations in the first place - it is the configuration system. If you need to store the settings in a different location for some reason, the recommended way is to write your own SettingsProvider. This is fairly simple to implement and you can find samples in the .NET 2.0 SDK that show how to do this. Keep in mind however that you may run into the same isolation issues mentioned above .
Here is an easier, briefer alternative to creating a custom settings class: change your app's evidence so that the "url" part is a constant rather than being based on the location of executable. To do this, you need to modify the default AppDomain when the program starts. There are two parts: setting app.config to use your AppDomainManager, and creating an AppDomainManager and HostSecurityManager to customize the Url evidence. Sounds complicated but it's much simpler than creating a custom settings class. This only applies to unsigned assemblies. If you have a signed assembly, it's going to use that evidence instead of the Url. But the good news there is your path will always be constant anyway (as long as the signing key doesn't change).
You can copy the code below and just replace the YourAppName bits.
DefaultAppDomainManager.cs:
using System;
using System.Security;
using System.Security.Policy;
namespace YourAppName
{
/// <summary>
/// A least-evil (?) way of customizing the default location of the application's user.config files.
/// </summary>
public class CustomEvidenceHostSecurityManager : HostSecurityManager
{
public override HostSecurityManagerOptions Flags
{
get
{
return HostSecurityManagerOptions.HostAssemblyEvidence;
}
}
public override Evidence ProvideAssemblyEvidence(System.Reflection.Assembly loadedAssembly, Evidence inputEvidence)
{
if (!loadedAssembly.Location.EndsWith("YourAppName.exe"))
return base.ProvideAssemblyEvidence(loadedAssembly, inputEvidence);
// override the full Url used in Evidence to just "YourAppName.exe" so it remains the same no matter where the exe is located
var zoneEvidence = inputEvidence.GetHostEvidence<Zone>();
return new Evidence(new EvidenceBase[] { zoneEvidence, new Url("YourAppName.exe") }, null);
}
}
public class DefaultAppDomainManager : AppDomainManager
{
private CustomEvidenceHostSecurityManager hostSecurityManager;
public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
{
base.InitializeNewDomain(appDomainInfo);
hostSecurityManager = new CustomEvidenceHostSecurityManager();
}
public override HostSecurityManager HostSecurityManager
{
get
{
return hostSecurityManager;
}
}
}
}
app.config excerpt:
<runtime>
<appDomainManagerType value="YourAppName.DefaultAppDomainManager" />
<appDomainManagerAssembly value="DefaultAppDomainManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</runtime>
Building on Chucks excellent answer:
Implement a new partial class based on Settings.Designer.cs, so the Settings Designer does not wipe out the attribute when changes are made:
namespace Worker.Properties
{
[System.Configuration.SettingsProvider(
typeof(SettingsProviders.DllFileSettingsProvider<Settings>))]
internal sealed partial class Settings
{
}
}
I created the following class that can be put in an external project. It allows the config to be project.dll.config. By Inheriting and Overriding ConfigPath allows any path you like. I also added support for reading System.Collections.Specialized.StringCollection. This version is for Application settings.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.IO;
//http://stackoverflow.com/questions/2265271/custom-path-of-the-user-config
namespace SettingsProviders
{
public class DllFileSettingsProvider<Properties_Settings> : SettingsProvider where Properties_Settings : new()
{
const string NAME = "name";
const string SERIALIZE_AS = "serializeAs";
const string CONFIG = "configuration";
const string APPLICATION_SETTINGS = "applicationSettings";
const string SETTING = "setting";
/// <summary>
/// Loads the file into memory.
/// </summary>
public DllFileSettingsProvider()
{
SettingsDictionary = new Dictionary<string, SettingStruct>();
}
/// <summary>
/// Override.
/// </summary>
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(ApplicationName, config);
}
/// <summary>
/// Override.
/// </summary>
public override string ApplicationName
{
get
{
return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
}
set
{
//do nothing
}
}
/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
//load the file
if (!_loaded)
{
_loaded = true;
LoadValuesFromFile();
}
//collection that will be returned.
SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();
//itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
foreach (SettingsProperty setting in collection)
{
SettingsPropertyValue value = new SettingsPropertyValue(setting);
value.IsDirty = false;
//need the type of the value for the strong typing
var t = Type.GetType(setting.PropertyType.FullName);
if (setting.PropertyType == typeof(System.Collections.Specialized.StringCollection))
{
var xml = SettingsDictionary[setting.Name].value;
var stringReader = new System.IO.StringReader(xml);
var xmlreader = System.Xml.XmlReader.Create(stringReader);
var ser = new System.Xml.Serialization.XmlSerializer(typeof(System.Collections.Specialized.StringCollection));
var obj = ser.Deserialize(xmlreader);
var col = (System.Collections.Specialized.StringCollection)obj;
value.PropertyValue = col;
}
else if (SettingsDictionary.ContainsKey(setting.Name))
{
value.SerializedValue = SettingsDictionary[setting.Name].value;
value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
}
else //use defaults in the case where there are no settings yet
{
value.SerializedValue = setting.DefaultValue;
value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
}
values.Add(value);
}
return values;
}
/// <summary>
/// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
//grab the values from the collection parameter and update the values in our dictionary.
foreach (SettingsPropertyValue value in collection)
{
var setting = new SettingStruct()
{
value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
name = value.Name,
serializeAs = value.Property.SerializeAs.ToString()
};
if (!SettingsDictionary.ContainsKey(value.Name))
{
SettingsDictionary.Add(value.Name, setting);
}
else
{
SettingsDictionary[value.Name] = setting;
}
}
//now that our local dictionary is up-to-date, save it to disk.
SaveValuesToFile();
}
/// <summary>
/// Loads the values of the file into memory.
/// </summary>
private void LoadValuesFromFile()
{
if (!File.Exists(ConfigPath))
{
//if the config file is not where it's supposed to be create a new one.
throw new Exception("Config file not found: " + ConfigPath);
}
//load the xml
var configXml = XDocument.Load(ConfigPath);
//get all of the <setting name="..." serializeAs="..."> elements.
var settingElements = configXml.Element(CONFIG).Element(APPLICATION_SETTINGS).Element(typeof(Properties_Settings).FullName).Elements(SETTING);
//iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
//using "String" as default serializeAs...just in case, no real good reason.
foreach (var element in settingElements)
{
var newSetting = new SettingStruct()
{
name = element.Attribute(NAME) == null ? String.Empty : element.Attribute(NAME).Value,
serializeAs = element.Attribute(SERIALIZE_AS) == null ? "String" : element.Attribute(SERIALIZE_AS).Value ,
value = element.Value ?? String.Empty
};
if (newSetting.serializeAs == "Xml")
{
var e = (XElement)element.Nodes().First();
newSetting.value = e.LastNode.ToString() ?? String.Empty;
};
SettingsDictionary.Add(element.Attribute(NAME).Value, newSetting);
}
}
/// <summary>
/// Creates an empty user.config file...looks like the one MS creates.
/// This could be overkill a simple key/value pairing would probably do.
/// </summary>
private void CreateEmptyConfig()
{
var doc = new XDocument();
var declaration = new XDeclaration("1.0", "utf-8", "true");
var config = new XElement(CONFIG);
var userSettings = new XElement(APPLICATION_SETTINGS);
var group = new XElement(typeof(Properties_Settings).FullName);
userSettings.Add(group);
config.Add(userSettings);
doc.Add(config);
doc.Declaration = declaration;
doc.Save(ConfigPath);
}
/// <summary>
/// Saves the in memory dictionary to the user config file
/// </summary>
private void SaveValuesToFile()
{
//load the current xml from the file.
var import = XDocument.Load(ConfigPath);
//get the settings group (e.g. <Company.Project.Desktop.Settings>)
var settingsSection = import.Element(CONFIG).Element(APPLICATION_SETTINGS).Element(typeof(Properties_Settings).FullName);
//iterate though the dictionary, either updating the value or adding the new setting.
foreach (var entry in SettingsDictionary)
{
var setting = settingsSection.Elements().FirstOrDefault(e => e.Attribute(NAME).Value == entry.Key);
if (setting == null) //this can happen if a new setting is added via the .settings designer.
{
var newSetting = new XElement(SETTING);
newSetting.Add(new XAttribute(NAME, entry.Value.name));
newSetting.Add(new XAttribute(SERIALIZE_AS, entry.Value.serializeAs));
newSetting.Value = (entry.Value.value ?? String.Empty);
settingsSection.Add(newSetting);
}
else //update the value if it exists.
{
setting.Value = (entry.Value.value ?? String.Empty);
}
}
import.Save(ConfigPath);
}
/// <summary>
/// The setting key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = #"C:\temp\user.config";</c>
/// </summary>
public virtual string ConfigPath
{
get
{
var name = new Properties_Settings().GetType().Module.Name + ".config";
if (System.IO.File.Exists(name)==false)
{
System.Diagnostics.Trace.WriteLine("config file NOT found:" + name);
}
System.Diagnostics.Trace.WriteLine("config file found:" + name);
return name;
// return Properties.Settings.Default.SettingsKey;
}
}
/// <summary>
/// In memory storage of the settings values
/// </summary>
internal Dictionary<string, SettingStruct> SettingsDictionary { get; set; }
/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
internal string name;
internal string serializeAs;
internal string value;
}
bool _loaded;
}
}
Make sure dependent projects include the project.dll.config file by add a post-build event:
copy $(SolutionDir)Worker\$(OutDir)Worker.dll.config $(TargetDir) /y

Categories

Resources