Problems rendering image from database byte field using WebImage - c#

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;
}
}
}

Related

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";
}

How to Get email id using asp.net mvc4 openid api [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
Looking to get the email id along with the openid while
Using the Internet template for MVC4
This is available for auth using google but not for facebook
wondering how to get/request the email id in the extraData dictionary
Looking at the code in AspNetWebStack project at http://aspnetwebstack.codeplex.com/, it looks like
OAuthWebSecurity.RegisterFacebookClient()
makes use of FacebookClient in DotNetOpenAuth.AspNet.dll hosted at https://github.com/AArnott/dotnetopenid
and the code in FacebookClient.GetUserData() has
var userData = new NameValueCollection();
userData.AddItemIfNotEmpty("id", graphData.Id);
userData.AddItemIfNotEmpty("username", graphData.Email);
userData.AddItemIfNotEmpty("name", graphData.Name);
userData.AddItemIfNotEmpty("link", graphData.Link == null ? null : graphData.Link.AbsoluteUri);
userData.AddItemIfNotEmpty("gender", graphData.Gender);
userData.AddItemIfNotEmpty("birthday", graphData.Birthday);
return userData;
which should return the email-id in username but it's not being returned
any help is appreciated
thanks
The provided Facebook OAuth client will not let you get anything beyond the default info. To get anything else, you need to be able to change the value of the scope parameter, something the included client doesn't allow. So, to get around this and still use the other boilerplate code the Internet template provides, you need to implement a custom OAuth client that follows the same pattern.
Since the entire ASP.NET source is open source, as is the OAuth library DotNetOpenAuth, you can actually look into the OAuth library and see exactly how the Facebook provider is built. Using that, I was able to come up with this:
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Web;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.AspNet;
using DotNetOpenAuth.AspNet.Clients;
using Validation;
using Newtonsoft.Json;
namespace OAuthProviders
{
/// <summary>
/// The facebook client.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Facebook", Justification = "Brand name")]
public sealed class FacebookScopedClient : OAuth2Client
{
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://graph.facebook.com/oauth/access_token";
/// <summary>
/// The _app id.
/// </summary>
private readonly string appId;
/// <summary>
/// The _app secret.
/// </summary>
private readonly string appSecret;
private readonly string scope;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="FacebookScopedClient"/> class.
/// </summary>
/// <param name="appId">
/// The app id.
/// </param>
/// <param name="appSecret">
/// The app secret.
/// </param>
/// <param name="scope">
/// The scope (requested Facebook permissions).
/// </param>
public FacebookScopedClient(string appId, string appSecret, string scope)
: base("facebook")
{
Requires.NotNullOrEmpty(appId, "appId");
Requires.NotNullOrEmpty(appSecret, "appSecret");
Requires.NotNullOrEmpty(scope, "scope");
this.appId = appId;
this.appSecret = appSecret;
this.scope = scope;
}
#endregion
#region Methods
/// <summary>
/// The get service login url.
/// </summary>
/// <param name="returnUrl">
/// The return url.
/// </param>
/// <returns>An absolute URI.</returns>
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
// Note: Facebook doesn't like us to url-encode the redirect_uri value
var builder = new UriBuilder(AuthorizationEndpoint);
builder.AppendQueryArgument("client_id", this.appId);
builder.AppendQueryArgument("redirect_uri", returnUrl.AbsoluteUri);
builder.AppendQueryArgument("scope", this.scope);
return builder.Uri;
}
/// <summary>
/// The get user data.
/// </summary>
/// <param name="accessToken">
/// The access token.
/// </param>
/// <returns>A dictionary of profile data.</returns>
protected override IDictionary<string, string> GetUserData(string accessToken)
{
FacebookGraphData graphData;
var request =
WebRequest.Create("https://graph.facebook.com/me?access_token=" + UriDataStringRFC3986(accessToken));
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
graphData = OAuthJsonHelper.Deserialize<FacebookGraphData>(responseStream);
}
}
// this dictionary must contains
var userData = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(graphData.Id)) { userData.Add("id", graphData.Id); }
if (!string.IsNullOrEmpty(graphData.Email)) { userData.Add("username", graphData.Email); }
if (!string.IsNullOrEmpty(graphData.Name)) { userData.Add("name", graphData.Name); }
if (graphData.Link != null && !string.IsNullOrEmpty(graphData.Link.AbsoluteUri)) { userData.Add("link", graphData.Link == null ? null : graphData.Link.AbsoluteUri); }
if (!string.IsNullOrEmpty(graphData.Gender)) { userData.Add("gender", graphData.Gender); }
if (!string.IsNullOrEmpty(graphData.Birthday)) { userData.Add("birthday", graphData.Birthday); }
return userData;
}
/// <summary>
/// Obtains an access token given an authorization code and callback URL.
/// </summary>
/// <param name="returnUrl">
/// The return url.
/// </param>
/// <param name="authorizationCode">
/// The authorization code.
/// </param>
/// <returns>
/// The access token.
/// </returns>
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
// Note: Facebook doesn't like us to url-encode the redirect_uri value
var builder = new UriBuilder(TokenEndpoint);
builder.AppendQueryArgument("client_id", this.appId);
builder.AppendQueryArgument("redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri));
builder.AppendQueryArgument("client_secret", this.appSecret);
builder.AppendQueryArgument("code", authorizationCode);
builder.AppendQueryArgument("scope", this.scope);
using (WebClient client = new WebClient())
{
string data = client.DownloadString(builder.Uri);
if (string.IsNullOrEmpty(data))
{
return null;
}
var parsedQueryString = HttpUtility.ParseQueryString(data);
return parsedQueryString["access_token"];
}
}
/// <summary>
/// Converts any % encoded values in the URL to uppercase.
/// </summary>
/// <param name="url">The URL string to normalize</param>
/// <returns>The normalized url</returns>
/// <example>NormalizeHexEncoding("Login.aspx?ReturnUrl=%2fAccount%2fManage.aspx") returns "Login.aspx?ReturnUrl=%2FAccount%2FManage.aspx"</example>
/// <remarks>
/// There is an issue in Facebook whereby it will rejects the redirect_uri value if
/// the url contains lowercase % encoded values.
/// </remarks>
private static string NormalizeHexEncoding(string url)
{
var chars = url.ToCharArray();
for (int i = 0; i < chars.Length - 2; i++)
{
if (chars[i] == '%')
{
chars[i + 1] = char.ToUpperInvariant(chars[i + 1]);
chars[i + 2] = char.ToUpperInvariant(chars[i + 2]);
i += 2;
}
}
return new string(chars);
}
/// <summary>
/// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986.
/// </summary>
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
internal static string UriDataStringRFC3986(string value)
{
// Start with RFC 2396 escaping by calling the .NET method to do the work.
// This MAY sometimes exhibit RFC 3986 behavior (according to the documentation).
// If it does, the escaping we do that follows it will be a no-op since the
// characters we search for to replace can't possibly exist in the string.
var escaped = new StringBuilder(Uri.EscapeDataString(value));
// Upgrade the escaping to RFC 3986, if necessary.
for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
{
escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
}
// Return the fully-RFC3986-escaped string.
return escaped.ToString();
}
#endregion
}
}
There are a few dependent libraries required to make this work as-is, all of which are available on NuGet. You already have DotNetOpenAuth; http://nuget.org/packages/Validation/ is another. The OAuthJsonHelper is a copy of an internal class used by DotNetOpenAuth - to get this provider to work I had to re-implement it in my own namespace:
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using Validation;
namespace OAuthProviders
{
/// <summary>
/// The JSON helper.
/// </summary>
internal static class OAuthJsonHelper
{
#region Public Methods and Operators
/// <summary>
/// The deserialize.
/// </summary>
/// <param name="stream">
/// The stream.
/// </param>
/// <typeparam name="T">The type of the value to deserialize.</typeparam>
/// <returns>
/// The deserialized value.
/// </returns>
public static T Deserialize<T>(Stream stream) where T : class
{
Requires.NotNull(stream, "stream");
var serializer = new DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(stream);
}
#endregion
}
}
This is all provided as-is, with no guarantee that it will work via copy/paste - it's up to you to figure out how to integrate it into your project.

Sending Data to WEB API function from Code behind C#

I have created Web API Services.
What I am trying to do is to call that API function, namely SaveSession(string data), from code behind button click, and to pass the entire form collection data as parameter in string format.
When I call the api's uri through webclient.uploadstring(url,string);
It is calling the web service but the parameter is not getting passed.
Pls help.
Instead of using a controller action method that accepts a string, you can instead read the form-url-encoded content posted to the controller.
In the example below, the SessionController exposes a SaveSession() method that accepts a POST of form-url-encoded content and then reads the session data as a collection of KeyValuePair instances.
The example client builds a list of key-value pairs and then uses a HttpClient instance to post the FormUrlEncodedContent to the web API controller method.
Example SessionController:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
namespace MvcApplication1.Controllers
{
public class SessionController : ApiController
{
/// <summary>
/// Saves specified the session data.
/// </summary>
/// <returns>
/// A <see cref="HttpResponseMessage"/> that represents the response to the requested operation.
/// </returns>
[HttpPost()]
public HttpResponseMessage SaveSession()
{
// Ensure content is form-url-encoded
if(!IsFormUrlEncodedContent(this.Request.Content))
{
return this.Request.CreateResponse(HttpStatusCode.BadRequest);
}
// Read content as a collection of key value pairs
foreach (var parameter in ReadAsFormUrlEncodedContentAsync(this.Request.Content).Result)
{
var key = parameter.Key;
var value = parameter.Value;
if(!String.IsNullOrEmpty(key))
{
// Do some work to persist session data here
}
}
return this.Request.CreateResponse(HttpStatusCode.OK);
}
/// <summary>
/// Determines whether the specified content is form URL encoded content.
/// </summary>
/// <param name="content">
/// The type that the <see cref="IsFormUrlEncodedContent(HttpContent)"/> method operates on.
/// </param>
/// <returns>
/// <see langword="true"/> if the specified content is form URL encoded content; otherwise, <see langword="false"/>.
/// </returns>
public static bool IsFormUrlEncodedContent(HttpContent content)
{
if (content == null || content.Headers == null)
{
return false;
}
return String.Equals(
content.Headers.ContentType.MediaType,
FormUrlEncodedMediaTypeFormatter.DefaultMediaType.MediaType,
StringComparison.OrdinalIgnoreCase
);
}
/// <summary>
/// Write the HTTP content to a collection of name/value pairs as an asynchronous operation.
/// </summary>
/// <param name="content">
/// The type that the <see cref="ReadAsFormUrlEncodedContentAsync(HttpContent)"/> method operates on.
/// </param>
/// <returns>The <see cref="Task"/> object representing the asynchronous operation.</returns>
/// <remarks>
/// The <see cref="ReadAsFormUrlEncodedContentAsync(HttpContent, CancellationToken)"/> method
/// uses the <see cref="Encoding.UTF8"/> format (or the character encoding of the document, if specified)
/// to parse the content. URL-encoded characters are decoded and multiple occurrences of the same form
/// parameter are listed as a single entry with a comma separating each value.
/// </remarks>
public static Task<IEnumerable<KeyValuePair<string, string>>> ReadAsFormUrlEncodedContentAsync(HttpContent content)
{
return ReadAsFormUrlEncodedContentAsync(content, CancellationToken.None);
}
/// <summary>
/// Write the HTTP content to a collection of name/value pairs as an asynchronous operation.
/// </summary>
/// <param name="content">
/// The type that the <see cref="ReadAsFormUrlEncodedContentAsync(HttpContent, CancellationToken)"/> method operates on.
/// </param>
/// <param name="cancellationToken">
/// The cancellation token used to propagate notification that the operation should be canceled.
/// </param>
/// <returns>The <see cref="Task"/> object representing the asynchronous operation.</returns>
/// <remarks>
/// The <see cref="ReadAsFormUrlEncodedContentAsync(HttpContent, CancellationToken)"/> method
/// uses the <see cref="Encoding.UTF8"/> format (or the character encoding of the document, if specified)
/// to parse the content. URL-encoded characters are decoded and multiple occurrences of the same form
/// parameter are listed as a single entry with a comma separating each value.
/// </remarks>
public static Task<IEnumerable<KeyValuePair<string, string>>> ReadAsFormUrlEncodedContentAsync(HttpContent content, CancellationToken cancellationToken)
{
return Task.Factory.StartNew<IEnumerable<KeyValuePair<string, string>>>(
(object state) =>
{
var result = new List<KeyValuePair<string, string>>();
var httpContent = state as HttpContent;
if (httpContent != null)
{
var encoding = Encoding.UTF8;
var charSet = httpContent.Headers.ContentType.CharSet;
if (!String.IsNullOrEmpty(charSet))
{
try
{
encoding = Encoding.GetEncoding(charSet);
}
catch (ArgumentException)
{
encoding = Encoding.UTF8;
}
}
NameValueCollection parameters = null;
using (var reader = new StreamReader(httpContent.ReadAsStreamAsync().Result, encoding))
{
parameters = HttpUtility.ParseQueryString(reader.ReadToEnd(), encoding);
}
if (parameters != null)
{
foreach(var key in parameters.AllKeys)
{
result.Add(
new KeyValuePair<string, string>(key, parameters[key])
);
}
}
}
return result;
},
content,
cancellationToken
);
}
}
}
Example Calling Client:
using System;
using System.Collections.Generic;
using System.Net.Http;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
using (var client = new HttpClient())
{
// Initialize HTTP client
client.BaseAddress = new Uri("http://localhost:26242/api/", UriKind.Absolute);
client.Timeout = TimeSpan.FromSeconds(10);
// Build session data to send
var values = new List<KeyValuePair<string, string>>();
values.Add(new KeyValuePair<string, string>("Item1", "Value1"));
values.Add(new KeyValuePair<string, string>("Item2", "Value2"));
values.Add(new KeyValuePair<string, string>("Item3", "Value3"));
// Send session data via POST using form-url-encoded content
using (var content = new FormUrlEncodedContent(values))
{
using (var response = client.PostAsync("session", content).Result)
{
Console.WriteLine(response.StatusCode);
}
}
}
}
}
}
I typically have the IsFormUrlEncodedContent and ReadAsFormUrlEncodedContentAsync methods as extension methods on HttpContent but placed them in the controller for the purpose of this example.

Check last modified date of file in 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.

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