I develop asp.net web forms application in VS2012 a I have one project with Web.App and one project is a Class Library.
In a class library i reading configuration from web.config (in a project is a lot of app settings like path for menu file, log file etc...) and in this assembly i have a class for caching and logging events to file.
This is a part of cache class:
public static class MyCache
{
public const String CACHE_MENU_USER = "CACHE_MENU_USER_{0}";
public const String CACHE_LOG_FILE_PATH = "LOG_FILE_PATH";
public static void AddToCache(String cacheKey, Object value, DateTime absoluteExpiration, bool loggToFile = true)
{
HttpContext.Current.Cache.Add(cacheKey,
value,
null,
absoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.High,
(k, v, r) => //key, value, reason of remove from cache
{
String reasonOfRemove = String.Empty;
switch (r)
{
case CacheItemRemovedReason.DependencyChanged:
reasonOfRemove = String.Format("CacheItemRemovedReason.DependencyChanged");
break;
case CacheItemRemovedReason.Expired:
reasonOfRemove = String.Format("CacheItemRemovedReason.Expired");
break;
case CacheItemRemovedReason.Removed:
reasonOfRemove = String.Format("CacheItemRemovedReason.Removed");
break;
case CacheItemRemovedReason.Underused:
reasonOfRemove = String.Format("CacheItemRemovedReason.Underused");
break;
}
AppConfiguration.Logger.WriteMessage(String.Format("Key {0} was removed for reason: {1}", cacheKey, reasonOfRemove));
});
if (loggToFile)
AppConfiguration.Logger.WriteMessage(String.Format("Insert cache key {0} from now to: {1}", cacheKey, absoluteExpiration));
}
}
AppConfiguration is a static class with static properties with values from web.config.
Logger is a instance of Logger class.
In global.asax is a piece of code:
protected void Application_Start(object sender, EventArgs e)
{
AppConfiguration.ReadConfiguration();
AppConfiguration.Logger.WriteMessage("Start app");
}
protected void Application_End(object sender, EventArgs e)
{
AppConfiguration.Logger.WriteMessage("End app");
}
Problem is, that App_Start is called on every request a inserted value to cache is immediately removed...
this is a part of log file:
10.8.2013 17:14:22: Start app.
10.8.2013 17:14:23: Insert cache key CACHE_MENU_USER_DEMO\Administrator from now to: 8/17/2013 5:14:23 PM
10.8.2013 17:14:23: Key CACHE_MENU_USER_DEMO\Administrator was removed for reason: CacheItemRemovedReason.Removed
10.8.2013 17:14:23: End app.
a this is repeated with every request.
I was trying start application in IIS Express, i trying start application in server (IIS7, Windows server 2008 R2) and it is still the same...
Any idea what is wrong with cache and global.asax?
Thanks
EDIT:
*Problem solved. More is in comments from FlopScientist*
Related
I am totally new to Wix and wix#, I am trying to update the app.config after the installation is finished.
I am able to achieve it by using the Wix Util extension util:XmlFile, but I want it to be done by wix# CustomDialog UI.
Below is the code which I had tried
var project = new ManagedProject("MyProduct",
new Dir(#"%ProgramFiles%\My Company\My Product",
new File("Program.cs"),
new File(#"myPath\App.config")),
new ElevatedManagedAction(CustomActions.OnInstall, Return.check, When.After, Step.InstallFiles, Condition.NOT_Installed)
{
UsesProperties = "CONFIG_FILE=[INSTALLDIR]App.config"
});
project.Load += Msi_Load;
project.BeforeInstall += Msi_BeforeInstall;
project.AfterInstall += Msi_AfterInstall;
Created a CustomDialog and set its value to a session variable after next
void next_Click(object sender, EventArgs e)
{
MsiRuntime.Session["NAME"] = name.Text;
Shell.GoNext();
}
I am able to retrieve session value in Msi_BeforeInstall but here app.config path is getting null as it is not copied to the INSTALLDIR and when I tried to perform it on Msi_AfterInstall here I am not getting the session variable property
I also tried to do it by CustomAction after installation
[CustomAction]
public static ActionResult OnInstall(Session session)
{
return session.HandleErrors(() =>
{
string configFile = session.Property("INSTALLDIR") + "App.config";
string userName = session.Property("NAME");
UpdateAsAppConfig(configFile, userName);
});
}
static public void UpdateAsAppConfig(string configFile,string name)
{
var config = ConfigurationManager.OpenMappedExeConfiguration(new ExeConfigurationFileMap { ExeConfigFilename = configFile }, ConfigurationUserLevel.None);
config.AppSettings.Settings["MyName"].Value = name;
config.Save();
}
But not getting the session variable property.
I am really new to it, any help would be appreciated. Please help me if I am doing it wrong or how can I update my app.config after installation.
Thanks.
I know your problem, AfterInstall event works with dead session it's unaccessable at this moment. If you need some properties in AfterInstall moment you can do use SetupEventArgs.Data property:
private void OnBeforeInstall(SetupEventArgs arguments)
{
//....
arguments.Data["MyName"] = arguments.Session["MyName"];
}
private void OnAfterInstall(SetupEventArgs arguments)
{
var propertyValue = arguments.Data["MyName"];
}
Also Data property can be used in your UI forms shown AFTER ProgressBarForm. Hope it help to you, let me know your feedback.
I would like to reproduce the behavior of Visual Studio which informs you when a project file is touched externally and proposes to reload it!
Due to the requirements, I believe reactive is a great match to solve that problem.
I am using a modified reactive FileSystemWatcher described in this post: http://www.jaylee.org/post/2012/08/26/An-update-to-matthieumezil-Rx-and-the-FileSystemWatcher.aspx
public class FileWatcher
{
private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public static IObservable<FileChanged> ObserveFolderChanges(string path, string filter, TimeSpan throttle, Predicate<string> isPartOfProject)
{
return Observable.Create<FileChanged>(
observer =>
{
var fileSystemWatcher = new FileSystemWatcher(path, filter) { EnableRaisingEvents = true, IncludeSubdirectories = true };
var sources = new[]
{
Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Created")
.Where(IsMaybeAProjectFile)
.Select(ev => new FileChanged(ev.EventArgs.FullPath, FileChangeTypes.Added, SourceChangeTypes.FileSystem)),
Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Deleted")
.Where(IsMaybeAProjectFile)
.Select(ev => new FileChanged(ev.EventArgs.FullPath, FileChangeTypes.Deleted, SourceChangeTypes.FileSystem))
};
return sources.Merge()
.Throttle(throttle)
.Do(changed =>
{
if (Logger.IsDebugEnabled)
{
Logger.Debug($"FileWatcher event [{changed.FileChangeType}] {changed.FullPath}");
}
})
.Finally(() => fileSystemWatcher.Dispose())
.Subscribe(observer);
}
);
}
private static bool IsMaybeAProjectFile(EventPattern<FileSystemEventArgs> ev)
{
return ev.EventArgs.FullPath.EndsWith(".zip") || ev.EventArgs.FullPath.EndsWith(".skye");
}
}
public class FileChanged
{
public string FullPath { get; }
public FileChangeTypes FileChangeType { get; }
public SourceChangeTypes SourceChangeType { get; }
public FileChanged(string fullPath, FileChangeTypes fileChangeType, SourceChangeTypes sourceChangeType)
{
FullPath = fullPath;
FileChangeType = fileChangeType;
SourceChangeType = sourceChangeType;
}
}
[Flags]
public enum FileChangeTypes
{
Added = 1,
Deleted = 2
}
[Flags]
public enum SourceChangeTypes
{
FileSystem = 1,
Project = 2
}
Now in my application I created an event
private ProjectChangedEventHandler ProjectChanged { get; set; }
private void OnProjectChanged(FileChanged fileChanged)
{
ProjectChanged?.Invoke(this, fileChanged);
}
public delegate void ProjectChangedEventHandler(object sender, FileChanged fileChanged);
Which is used like this when I delete or a add a file from the project
OnProjectChanged(new FileChanged(archive.Filename, FileChangeTypes.Deleted, SourceChangeTypes.Project));
OnProjectChanged(new FileChanged(archive.Filename, FileChangeTypes.Added, SourceChangeTypes.Project));
Now I can start to leverage those two streams and with a join (which needs fine tuning for the left and right duration selector) I am able to detect which file was modified by my application:
private void ObserveProjectModifications(string projectFilePath)
{
_observeFolderChanges = FileWatcher.ObserveFolderChanges(Path.GetDirectoryName(projectFilePath), "*.*", TimeSpan.FromMilliseconds(500), IsPartOfProject);
_observeProjectChanges = Observable.FromEventPattern<ProjectChangedEventHandler, FileChanged>(h => ProjectChanged += h, h => ProjectChanged -= h).Select(pattern => pattern.EventArgs);
_changes = _observeProjectChanges.Join(_observeFolderChanges, _ => Observable.Never<Unit>(), _ => Observable.Never<Unit>(), ResultSelector).Where(changed => IsPartOfProject(changed.FullPath));
}
private FileChanged ResultSelector(FileChanged fileChanged, FileChanged projectChanged)
{
if (Logger.IsDebugEnabled)
{
Logger.Debug($"ResultSelector File [{fileChanged.FileChangeType}] {fileChanged.FullPath} # Project [{projectChanged.FileChangeType}] {projectChanged.FullPath}");
}
if (fileChanged.FullPath == projectChanged.FullPath)
{
if (fileChanged.FileChangeType == projectChanged.FileChangeType)
{
if (fileChanged.SourceChangeType != projectChanged.SourceChangeType)
{
return projectChanged;
}
return fileChanged;
}
return fileChanged;
}
return fileChanged;
}
private bool IsPartOfProject(string fullPath)
{
if (_projectFileManager.ProjectFilePath.Equals(fullPath)) return true;
return _archives.Values.Any(a => a.Filename.Equals(fullPath));
}
My issue is that I also want to know that a file was modified externally! Any idea would be really helpful! Thanks
Unfortunatelly the FileSystemWatcher doesn't provide information which process has modified the file, so you are bit out of luck there. There are few possibilities that I can think of:
Ignore flag - When your application is doing a change you can set a flag and ignore the events when the flag is set. This is the simplest way, but you might miss some external change if it happens concurrently when the flag is set and also it gets even more complicated due to throttling you have.
Tagging the file - whenever you do a change to the file you generate a guid (or similar) which you will use to tag the file. And then whenever the file change is fired, you check the file property (can be stored either as real filesystem file property - similar for example to jpeg metadata you see in details in file explorer, there are more ways to set such file property) and then if the tag is different from what you have or is missing then you know it is external - there you need to also take care due to throttling and the tag being outdated etc
Minifilter file system driver - This would be the cleanest solution and probably is very close to what Visual studio is using - just a guess though. It is basically a universal windows driver that monitors any I/O change. Microsoft has created reference implementation called minispy, which is small tool to monitor and log any I/O and transaction activity that occurs in the system. You don't have to implement the driver yourself as there is already a 3rd party FileSystemWatcher implemented using this approach on github. That file system watcher provides information which process has modified the file. The only problem here is that the driver itself needs to be installed, before it can be used, so you need admin privileged installer of sort.
At the moment that's all I can think of.
I'm trying to make a service that creates a text file, as a tutorial project.
However when I debug it, I get The process cannot access the file C:\myfilepath.OnStart.txt because it is being used by another process.
I'm expecting it to keep creating the file in a format like OnStart(n).txt
public void OnDebug()
{
OnStart(null);
}
protected override void OnStart(string[] args)
{
Timer t = new Timer(WriteTxt, null, 0, 5000);
}
public static void WriteTxt(Object i)
{
System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "OnStart.txt");
}
When creating the file you're mistakenly leaving it open which is why you can't access it next time and receive an error. You must call .Dispose() after you've created it to let go of the reference to the file like this:
File.Create(AppDomain.CurrentDomain.BaseDirectory + "OnStart.txt").Dispose();
If you want to keep creating files then they'll need a different name each time. You could keep a global variable to keep track of this or possibly pass a value into the Write method.
Global variable method
// Keeps track of the last file number with this global variable
private int fileCount;
public void OnDebug()
{
OnStart(null);
}
protected override void OnStart(string[] args)
{
fileCount = 0; // Or whatever you want to start with
Timer t = new Timer(WriteTxt, null, 0, 5000);
}
public static void WriteTxt(Object i)
{
// Creates the file name eg: OnStart1.txt
var fileName = string.Format("OnStart{0}.txt", fileCount);
// Use Path.Combine to make paths
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
// Create the file
File.Create(filePath).Dispose();
// Adds 1 to the value
fileCount++;
}
Results:
OnStart0.txt
OnStart1.txt
OnStart2.txt
...
...
OnStart4999.txt
I'm trying to implement what I'd call a "one time cache" for specific files in my ASP.NET 3.5 site. Upon request, the file is downloaded from a remote file server onto the web server, then served up by my page. The problem arises when I go to delete it. If I delete the file in the OnUnload method, the client doesn't have time to retrieve it. Is there some way of detecting the download of a file and deleting it immediately after it is first accessed? My code is like this:
private bool _deleteFlag = false;
private string _deleteFile = string.Empty;
protected void Page_Load(object sender, EventArgs e) {
if (!string.IsNullOrEmpty(Request.QueryString["file"])) {
_deleteFlag = Request.QueryString["file"].Contains(#"cache/");
_deleteFile = Request.QueryString["file"];
}
}
protected override void OnUnload(EventArgs e) {
if (_deleteFlag) {
// Can't delete here; the client is still trying to retrieve it.
System.IO.File.Delete(Server.MapPath(_deleteFile));
}
base.OnUnload(e);
}
I'm leaning towards writing an IHttpHandler that overrides the file request, but that feels like overkill.
Try this:
When you add your file, insert it into the HttpRuntimeCache and set the Key to the FileName and set the TimeOut to say 10 seconds. Then on the CacheItemRemoved callback, delete the file. It's not exact, but it should roughly meet your use case.
public static object LoadFile()
{
var filename = ParseFileNameFromHttpRequest();
var a = loadFileSomeHow(filename);
HttpRuntime.Cache.Insert(
filename ,
a,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
new TimeSpan(0, 0, 0,10,0,0),
CacheItemPriority.Default,
new CacheItemRemovedCallback(DeleteFile));
return a;
}
public void DeleteFile(String key, object value,
CacheItemRemovedReason removedReason)
{
File.Delete(key);
}
For more info: http://msdn.microsoft.com/en-us/library/vstudio/7kxdx246(v=vs.100).aspx
I like to Update keys/Values defined in AppSettings section of Web.config at runtime. however I DO NOT want to actually save them to Web.config file.
I have a huge web application that have consists of many modules, DLLs and source code files. A bunch of critical information ranged from database configuration, encryption keys, username and passwords for webservices are saved in AppSettings section of the web.config file. Recent project requirement needs me to move these values out of web.config and keep in a secure storage.
I already secured these values in an external location and I can read them back when application starts.
here is the sample code.
Global.asax
public class Global: System.Web.HttpApplication {
protected void Application_Start(object sender, EventArgs e) {
Dictionary<string, string> secureConfig = new Dictionary<string,string>{};
// --------------------------------------------------------------------
// Here I read and decrypt keys and add them to secureConfig dictionary
// To test assume the following line is a key stored in secure sotrage.
//secureConfig = SecureConfig.LoadConfig();
secureConfig.Add("ACriticalKey","VeryCriticalValue");
// --------------------------------------------------------------------
foreach (KeyValuePair<string, string> item in secureConfig) {
ConfigurationManager.AppSettings.Add(item.Key, item.Value);
}
}
}
As you may noticed it is not feasible to change references to AppSettings in a massive code created by multiple programming teams to read their settings from my secureConfig dictionary and on the other hand I should not save these values in web.config file which is available to web administrators and operators, system admins and cloud admins.
To Make programmers life easier, I want to let them add their values to AppSettings section of web.config during development, but they will be removed from there and put to secure storage later during deployment, however these values should be available to program transparently as they are still in AppSettings section.
Question: how can I add values to AppSettings at runtime so program can read them using ConfigurationManager.AppSettings["ACriticalKey"] to get "VeryCriticalValue" without saving them in Web.Config?
Please note: ConfigurationManager.AppSettings.Add(item.Key, item.Value); gives me ConfigurationErrorsException with message The configuration is read only.
Please note: Preferably some settings should be able to stay in AppSettings as before
I know this is an old question, but I ran into the same problem and I found that Set works in the same way as Add, and does not throw an exception, so just replace Add with Set, like so:
ConfigurationManager.AppSettings.Set(item.Key, item.Value);
You need to make use of WebConfigurationManager.OpenWebConfiguration()
Configuration config = WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath);
config.AppSettings.Settings.Remove("Variable");
config.AppSettings.Settings.Add("Variable", "valyue");
config.Save();
Perhaps this link will help. It references 2.0 but I believe the method is still valid in 4.0.
Also, the SO question on the same/similar topic here may be of interest.
Also, modifying the web.config at runtime should cause an application pool recycle each time. Not trying to tell you how to suck eggs, just thought I'd note it for anyone's prospective interest...Thx.
Thanks to nkvu which directed me to a his first link which in turn sent me to Williarob's post "Override Configuration Manager" I managed to find a solution to my question.
The mentioned blog post covers how to read settings from another XML file and it works with both windowed applications and web applications (with a little modification in config file name and path). Although this blog written on 2010 it is still working fine with .NET4 without problem.
However as I was going to read my configuration from a secure device, I simplified the class and here is how to use the classes provided by Williarob
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Internal;
using System.Linq;
using System.Reflection;
namespace Williablog.Core.Configuration {
public sealed class ConfigSystem: IInternalConfigSystem {
private static IInternalConfigSystem clientConfigSystem;
private object appsettings;
private object connectionStrings;
/// <summary>
/// Re-initializes the ConfigurationManager, allowing us to merge in the settings from Core.Config
/// </summary>
public static void Install() {
FieldInfo[] fiStateValues = null;
Type tInitState = typeof(System.Configuration.ConfigurationManager).GetNestedType("InitState", BindingFlags.NonPublic);
if (null != tInitState) {
fiStateValues = tInitState.GetFields();
}
FieldInfo fiInit = typeof(System.Configuration.ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static);
FieldInfo fiSystem = typeof(System.Configuration.ConfigurationManager).GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static);
if (fiInit != null && fiSystem != null && null != fiStateValues) {
fiInit.SetValue(null, fiStateValues[1].GetValue(null));
fiSystem.SetValue(null, null);
}
ConfigSystem confSys = new ConfigSystem();
Type configFactoryType = Type.GetType("System.Configuration.Internal.InternalConfigSettingsFactory, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
IInternalConfigSettingsFactory configSettingsFactory = (IInternalConfigSettingsFactory) Activator.CreateInstance(configFactoryType, true);
configSettingsFactory.SetConfigurationSystem(confSys, false);
Type clientConfigSystemType = Type.GetType("System.Configuration.ClientConfigurationSystem, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
clientConfigSystem = (IInternalConfigSystem) Activator.CreateInstance(clientConfigSystemType, true);
}
#region IInternalConfigSystem Members
public object GetSection(string configKey) {
// get the section from the default location (web.config or app.config)
object section = clientConfigSystem.GetSection(configKey);
switch (configKey) {
case "appSettings":
// Return cached version if exists
if (this.appsettings != null) {
return this.appsettings;
}
// create a new collection because the underlying collection is read-only
var cfg = new NameValueCollection();
// If an AppSettings section exists in Web.config, read and add values from it
if (section is NameValueCollection) {
NameValueCollection localSettings = (NameValueCollection) section;
foreach (string key in localSettings) {
cfg.Add(key, localSettings[key]);
}
}
// --------------------------------------------------------------------
// Here I read and decrypt keys and add them to secureConfig dictionary
// To test assume the following line is a key stored in secure sotrage.
//secureConfig = SecureConfig.LoadConfig();
secureConfig.Add("ACriticalKey", "VeryCriticalValue");
// --------------------------------------------------------------------
foreach (KeyValuePair<string, string> item in secureConfig) {
if (cfg.AllKeys.Contains(item.Key)) {
cfg[item.Key] = item.Value;
} else {
cfg.Add(item.Key, item.Value);
}
}
// --------------------------------------------------------------------
// Cach the settings for future use
this.appsettings = cfg;
// return the merged version of the items from secure storage and appsettings
section = this.appsettings;
break;
case "connectionStrings":
// Return cached version if exists
if (this.connectionStrings != null) {
return this.connectionStrings;
}
// create a new collection because the underlying collection is read-only
ConnectionStringsSection connectionStringsSection = new ConnectionStringsSection();
// copy the existing connection strings into the new collection
foreach (ConnectionStringSettings connectionStringSetting in ((ConnectionStringsSection) section).ConnectionStrings) {
connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
}
// --------------------------------------------------------------------
// Again Load connection strings from secure storage and merge like below
// connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
// --------------------------------------------------------------------
// Cach the settings for future use
this.connectionStrings = connectionStringsSection;
// return the merged version of the items from secure storage and appsettings
section = this.connectionStrings;
break;
}
return section;
}
public void RefreshConfig(string sectionName) {
if (sectionName == "appSettings") {
this.appsettings = null;
}
if (sectionName == "connectionStrings") {
this.connectionStrings = null;
}
clientConfigSystem.RefreshConfig(sectionName);
}
public bool SupportsUserConfig { get { return clientConfigSystem.SupportsUserConfig; } }
#endregion
}
}
To install this (or original version of configuration override) add following line to
your Global. class (Global.asax.cs) in Application_Start
Williablog.Core.Configuration.ConfigSystem .Install();
like below:
public class Global: System.Web.HttpApplication {
//...
#region protected void Application_Start(...)
protected void Application_Start(object sender, EventArgs e) {
Williablog.Core.Configuration.ConfigSystem .Install();
//...
}
#endregion
//...
}