I have created mvc application.. In that i have write code for update web.config.i.e
try
{
//// Helps to open the Root level web.config file.
Configuration webConfigApp = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
AppSettingsSection objAppsettings = (AppSettingsSection)webConfigApp.GetSection("appSettings");
//Edit
if (objAppsettings != null)
{
////Modifying the AppKey from AppValue to AppValue1
objAppsettings.Settings["DaysToKeepJob"].Value = model.Keepjobsfolders.ToString();
objAppsettings.Settings["DeleteLocalStoreStudies"].Value = model.DeleteLocalStoreStudies.ToString();
objAppsettings.Settings["ManualBurnerNoOfCopies"].Value = model.ManualNumberofCopies.ToString();
objAppsettings.Settings["DefaultBurner"].Value = model.DefaultBurner.ToString();
objAppsettings.Settings["AutoBurnerNoOfCopies"].Value = model.AutoNumberofCopies.ToString();
objAppsettings.Settings["SmartDisk"].Value = model.chkSMARTDisk.ToString();
objAppsettings.Settings["ForcetodefaultTransfer"].Value = model.chkKeepjobsfolders.ToString();
////Save the Modified settings of AppSettings.
webConfigApp.Save();
}
if (diskType.Title != null)
{
var res = DiskTypeSave(diskType);
}
return Json(new { Status = "Success", Msg = "Rules Saved Successfully." }, JsonRequestBehavior.AllowGet);
}
but after this line webConfigApp.Save(); my session i.e Session["UserAccessRights"] get null . i know because of updating web.config it get null.
Please suggest to maintain session even web.config updates
Every time you change the web.config the application is restarted in IIS.
You can do one of the things below :
Use another settings file, a xml, json, etc. to store those values
Use the database to store those settings per user
Use SQL server for saving sessions or a state server, not in-memory (default)
For this you first have to keep Sessions copy into local variable and then assign again. But for this you have to keep Session.SessionId copy also, because on new session it sets again.
Related
THE GOAL
I'm trying to have a class library shared between an ASP.NET Core web app and other projects/solutions and the class should be able to interact with whatever database is being used by the calling process/environment.
THE PROBLEM
I have a class library that is throwing up a weird error when used in my ASP.NET Core 3.1 web app. The class library is actually shared between the front end (the website) and the backend app that takes care of some recurring, heavy load processes. I'm using EF Core with both front and back ends and the database is on Azure, not my local machine. Yet, when the web app tries to do some work I am getting the following error:
An attempt to attach an auto-named database for file C:...\bin\Debug\netcoreapp3.1\aspnetdb.mdf failed. A database with the same name exists, or specified file cannot be opened, or it is located on UNC share.
This doesn't make sense to me at all since the DB is on Azure. Also, calling the same exact method in the library using the backend app doesn't throw up this error. The connection string is stored in appsettings.json for the website and app.config for the backend.
This is block of code that is throwing the error, but again this is only happening on the ASP.NET Core project on the SaveChanges() call:
public static void AddLogEvent(int Severity, DateTime EventTime, string EventType, string User, string Message)
{
DBEntities context = new DBEntities();
DbSet<LogEvent> dbSet = context.Set<LogEvent>();
LogEvent NewRecord = new LogEvent();
NewRecord.Id = Guid.NewGuid();
NewRecord.Severity = Severity;
NewRecord.EventTime = EventTime;
NewRecord.EventType = EventType;
NewRecord.User = User;
NewRecord.Message = Message;
dbSet.Add(NewRecord);
context.SaveChanges();
}
Within DBEntities, I am overriding the OnConfiguring() method to ensure proper connection for whatever environment is making the call as such:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
bool FoundValidConnection = false;
if (ConfigurationManager.ConnectionStrings.Count > 0)
{
foreach (ConnectionStringSettings connstr in ConfigurationManager.ConnectionStrings)
{
if (FoundValidConnection == false)
{
if (string.Compare(connstr.Name.Trim().ToUpper(), "DefaultConnection".ToUpper()) == 0)
{
optionsBuilder.UseSqlServer(connstr.ConnectionString);
FoundValidConnection = true;
break;
}
}
}
foreach (ConnectionStringSettings connstr in ConfigurationManager.ConnectionStrings)
{
if (FoundValidConnection == false)
{
if (string.Compare(connstr.Name.Trim().ToUpper(), "DBEntities".ToUpper()) == 0)
{
optionsBuilder.UseSqlServer(connstr.ConnectionString);
FoundValidConnection = true;
break;
}
}
}
if (FoundValidConnection == false)
{
//if still haven't found one of the expected connection string names, then take whatever the first one is.
optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings[0].ConnectionString);
FoundValidConnection = true;
}
}
else
{
//nothing to do. there are no connection strings in the ConfigurationManager
}
}
}
Lastly, when I step through debugging on the website, I can see that the only connection string located in ConfigurationManager.ConnectionStrings is 1 with a name of LocalSqlServer and a connection string set to:
data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true
Which seems to be my local testing SQLExpress instance but is not used anywhere in the web app. All references or connection strings to the local testing database in SQLExpress have been removed so I am confused as to how this is showing up and the one in appsettings.json is being ignored. I also don't understand how optionsBuilder.IsConfigured is returning FALSE on the web app. I expected that context to already be configured.
I ended up changing the last if statement to the following:
if (FoundValidConnection == false)
{
try
{
//read from appsettings.json directly instead
string connString = new ConfigurationBuilder().SetBasePath(System.IO.Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build().GetSection("ConnectionStrings").GetSection("DefaultConnection").Value;
optionsBuilder.UseSqlServer(connString);
FoundValidConnection = true;
}
catch (System.Exception ex)
{
}
}
As Ivan noted in the comments above, ConfigurationManager from System.Configuration only works with app.config, which is XML based. ASP.NET Core utilizes appsettings.json which is, obviously, JSON based. So the solution was to modify the last check to be such that if the app.config checks fail and produce nothing then we assume the code is being called by something using appsettings.json and that one line uses ConfigurationBuilder to get the app's directory, build the configuration based on that file and get the expected connection by it's section and name.
References to Microsoft.Extensions.Configuration.FileExtensions and Microsoft.Extensions.Configuration.Json were required for SetBasePath() and AddJsonFile(), respectively.
The try/catch block is needed in the event there is some unforeseen error and the appsettings/json doesn't contain the expected section/name. Although, an error would eventually get thrown up somewhere when trying to interact with the database if nothing was set.
I have a custom settings file (Settings.xml) where I specify all that I need. When changeing that file while running the website the site, of course, restart.
Settings.xml is read into an object that have the same structure as the xml and then I work with the Settings object
private static readonly XDocument Settings = XDocument.Load(AppDomain.CurrentDomain.BaseDirectory + "\\Settings.xml");
The site runs in IIS 8.5.
Is it possible to update Settings.xml without forceing the website to restart?
Is it the IIS that automatically restart?
You can read your setting on every file change like this:
private static DateTime? _lastSettingRead;
private static XDocument _cahedSettings;
private static XDocument Settings
{
get
{
var settingsPath = AppDomain.CurrentDomain.BaseDirectory + "\\Settings.xml";
//Get last change Settings file datetime
var lastSettingChange = System.IO.File.GetLastWriteTime(settingsPath);
//If we read first time or file changed since last read
if (!_lastSettingRead.HasValue || lastSettingChange > _lastSettingRead)
{
_lastSettingRead = lastSettingChange; //change read date
_cahedSettings = XDocument.Load(settingsPath); //load settings to field
}
return _cahedSettings; //return cached settings from field
}
}
I am working on an application which is deployed to a TEST and then a LIVE webserver.
I want the class library I am working on to use the correct service endpoint when it is deployed.
Currently the code is as follows;
var data = new SettingsViewModel()
{
ServiceURI = Constants.LIVE_ENDPOINT_SERVICE_ADDRESS,
AutoSync = Constants.DEFAULT_AUTO_SYNC,
AppDataFolder = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ROOT_FOLDER, Constants.DATA_FOLDER),
MapKey = Constants.BASIC_MAP_KEY,
Logging = false
};
#if DEBUG
data.ServiceURI = Constants.DEV_ENDPOINT_SERVICE_ADDRESS;
#endif
As you can see, this can only pick up the DEV or the LIVE endpoints. This code cannot distinguish whether the webserver is LIVE or TEST
I thought about setting up an App.Config file and get the correct Endpoint from there. But when I create a new item, the Config template is not listed. So how do I do this?
For now I could propose this solution :
public static class Constants
{
public static string GetEndPoint()
{
// Debugging purpose
if (System.Diagnose.Debug.IsAttached)
{
return DEV_ENDPOINT_SERVICE_ADDRESS;
}
else if ( Environment.MachineName == "Test Server" ) // You need to know your test server machine name at hand.
{
return "Return test Server endpoint"
}
else
{
return "Return live server endpoint";
}
}
}
You can used it in your SettingsViewModel like this:
var data = new SettingsViewModel()
{
ServiceURI = Constants.GetEndPoint(),
AutoSync = Constants.DEFAULT_AUTO_SYNC,
AppDataFolder = Path.Combine(ApplicationData.Current.LocalFolder.Path, Constants.ROOT_FOLDER, Constants.DATA_FOLDER),
MapKey = Constants.BASIC_MAP_KEY,
Logging = false
};
The drawback for this solution is, if you change your test server you need to change is manually in your code.
Having done some research I realise I need to clarify something. The application I am working on is a Windows RT application and this does not allow config files. The solution I am meant to use is to use local settings, but these do not reference an external file like an App.Config. If I want to change the location of an EndPoint then I am going to have to specify where that is in the code.
I've written a custom action for an installer project that does the following:
Checks existing websites to see if any exist with the same name put
in by the user.
Creates the website in IIS if it doesn't exist.
Creates an application pool.
Assigns the application pool to the created website.
When it comes to assigning the application pool I get and error:
The configuration object is read only, because it has been committed
by a call to ServerManager.CommitChanges(). If write access is
required, use ServerManager to get a new reference.
This baffles me as it seems to suggest that I can't assign the newly created application pool with the ServerManager.CommitChanges() call. However, everything else works fine using this, which I wouldn't expect if this was an issue.
Here is my code:
I have a ServerManager instance created like so:
private ServerManager mgr = new ServerManager();
In my Install method I do the following:
Site site = CreateWebsite();
if (site != null)
{
CreateApplicationPool();
AssignAppPool(site);
}
Check existing websites - done in OnBeforeInstall method
private Site CheckWebsites()
{
SiteCollection sites = null;
Site site = null;
try
{
sites = mgr.Sites;
foreach (Site s in sites)
{
if (!string.IsNullOrEmpty(s.Name))
{
if (string.Compare(s.Name, targetSite, true) == 0) site = s;
}
}
}
catch{}
return site;
}
CreateWebSite method:
private Site CreateWebsite()
{
Site site = CheckWebsites();
if (site == null)
{
SiteCollection sites = mgr.Sites;
int port;
Int32.TryParse(targetPort, out port);
site = sites.Add(targetSite, targetDirectory, port);
mgr.CommitChanges();
}
else
{
//TO DO - if website already exists edit settings
}
return site;
}
Create App Pool
//non-relevant code...
ApplicationPool NewPool = mgr.ApplicationPools.Add(ApplicationPool);
NewPool.AutoStart = true;
NewPool.ManagedRuntimeVersion = "4.0";
NewPool.ManagedPipelineMode = ManagedPipelineMode.Classic;
mgr.CommitChanges();
Assign App Pool
private void AssignAppPool(Site site)
{
site.ApplicationDefaults.ApplicationPoolName = ApplicationPool; //ERRORS HERE
mgr.CommitChanges();
}
I can't see why a site could be created, an app pool created but then not assigned. Help.
I finally realised that the 'configuration object' referred to in the error was the 'site'. Seems obvious now, but basically I needed to re-get the site to then assign the app pool to it. I think this is allow the previous changes to take place and then pick them up. So I altered my code by removing the need to pass the Site into private void AssignAppPool() and just getting the site again like this:
Site site = mgr.Sites["TestWebApp"];
How can I modify / manipulate the web.config programmatically with C# ? Can I use a configuration object, and, if yes, how can I load the web.config into a configuration object ? I would like to have a full example changing the connection string. After the modification the web.config should be written back to the harddisk.
Here it is some code:
var configuration = WebConfigurationManager.OpenWebConfiguration("~");
var section = (ConnectionStringsSection)configuration.GetSection("connectionStrings");
section.ConnectionStrings["MyConnectionString"].ConnectionString = "Data Source=...";
configuration.Save();
See more examples in this article, you may need to take a look to impersonation.
Configuration config = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
ConnectionStringsSection section = config.GetSection("connectionStrings") as ConnectionStringsSection;
//section.SectionInformation.UnprotectSection();
section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
config.Save();
Since web.config file is xml file you can open web.config using xmldocument class. Get the node from that xml file that you want to update and then save xml file.
here is URL that explains in more detail how you can update web.config file programmatically.
http://patelshailesh.com/index.php/update-web-config-programmatically
Note: if you make any changes to web.config, ASP.NET detects that changes and it will reload your application(recycle application pool) and effect of that is data kept in Session, Application, and Cache will be lost (assuming session state is InProc and not using a state server or database).
This is a method that I use to update AppSettings, works for both web and desktop applications. If you need to edit connectionStrings you can get that value from System.Configuration.ConnectionStringSettings config = configFile.ConnectionStrings.ConnectionStrings["YourConnectionStringName"]; and then set a new value with config.ConnectionString = "your connection string";. Note that if you have any comments in the connectionStrings section in Web.Config these will be removed.
private void UpdateAppSettings(string key, string value)
{
System.Configuration.Configuration configFile = null;
if (System.Web.HttpContext.Current != null)
{
configFile =
System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
}
else
{
configFile =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
}
var settings = configFile.AppSettings.Settings;
if (settings[key] == null)
{
settings.Add(key, value);
}
else
{
settings[key].Value = value;
}
configFile.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection(configFile.AppSettings.SectionInformation.Name);
}