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.
Related
I work with C# in Visual Studio 2019. My database is in SQLite using Dapper.
Here is what I am struggling with.
I have 2 tables in my database that are connected.
The parent, tbClient. And the child table, tbProject.
tbProject has a field to ClientId.
I use a ComboBox to WireUpp the Data from the database to my form. I have a form to Client, and a form for the Project, in this form I chose a CLient in a ComboBox, and save its ID in my tbProjet.
The idea is simple, but I am struggling because I am using an example that was made in Windows (WPF), and my application is in Windows Forms. I noticed that the Properties of the ComboBox are not the same, then I am having some trouble accessing the correct Project field when I want to open the Project of a specific Client.
Let´s show how it's done in the WPF app and in my WinForm app. I think is going to be more clear to get some help.
The codes of the Project Form are below: the first is the WPF app, and the second one is the WinForm where I was not able to make work yet.
WPF Application:
// WPF Application:
namespace MyApp.Controls
{
public ProjectControls()
{
InitializeComponent();
InitializeClientList();
WireUpDropDowns();
}
private void WireUpDropDowns()
{
clientDropDown.ItemsSource = clients;
clientDropDown.DisplayMemberPath = "Name";
clientDropDown.SelectedValuePath = "Id";
projectDropDown.ItemsSource = projects;
projectDropDown.DisplayMemberPath = "DisplayValue";
projectDropDown.SelectedValuePath = "Id";
}
private void InitializeClientList()
{
string sql = "select * from Client order by Name";
var clientList = SqliteDataAccess.LoadData<ClientModel>(sql, new Dictionary<string, object>());
clientList.ForEach(x => clients.Add(x));
}
private void clientDropDown_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
LoadProjectDropDown();
}
private void LoadProjectDropDown()
{
string sql = "select * from tbProject where ClientId = #ClientId";
Dictionary<string, object> parameters = new Dictionary<string, object>
{
{ "#ClientId", clientDropDown.SelectedValue }
};
var records = SqliteDataAccess.LoadData<ProjectsModel>(sql, parameters);
projects.Clear();
records.ForEach(x => projects.Add(x));
}
}
Windows Form Application:
// Windows Forms Application:
namespace MyApp.Controls
{
public ProjectControls()
{
InitializeComponent();
InitializeClientList();
WireUpDropDowns();
}
private void WireUpDropDowns()
{
clientDropDown.DataSource = null;
clientDropDown.DataSource = clients;
clientDropDown.DisplayMember= "Name";
clientDropDown.ValueMember = "Id";
projectDropDown.DataSource = null;
projectDropDown.DataSource= projects;
projectDropDown.DisplayMember = "DisplayValue";
projectDropDown.ValueMember= "Id";
}
private void InitializeClientList()
{
string sql = "select * from Client order by Name";
var clientList = SqliteDataAccess.LoadData<ClientModel>(sql, new Dictionary<string, object>());
clientList.ForEach(x => clients.Add(x));
}
private void clientDropDown_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
LoadProjectDropDown();
}
private void LoadProjectDropDown()
{
string sql = "select * from tbProject where ClientId = #ClientId";
Dictionary<string, object> parameters = new Dictionary<string, object>
{
{ "#ClientId", clientDropDown.SelectedValue }
// --> I think the problem is here, I am passing an object to the database where ClientId is an integer type. I tried to use SelectedIndex instead, but with this property, I do not get the correct Project from the table
};
var records = SqliteDataAccess.LoadData<ProjectsModel>(sql, parameters);
projects.Clear();
records.ForEach(x => projects.Add(x));
}
}
In the Windows Form Application I get this Error Message from my AccesDataBase Routine:
System.NotSupportedException: 'The member ClientId of type AppLibrary.Models.ClientModel cannot be used as a parameter value'
So I think the basic question here is Am I using the ComboBOx Properties correct? What I am missing?
Thank you in advance for any help received.
Verônica.
I found out about my mistake.
The property of the Combobox is correct.
I was passing the wrong parameter to the ClientDropDown.SelectedValue in other parts of the code that I haven´t shared here.
I was trying to select a specific client in the ClientDropDown through code but I was passing SelectedIndex to the SelectedValue.
I used the Breakpoints and was able to find the error, and now is working with this code that I share here in the question, it is correct.
I am creating a form-application using C#. This application should create certain files, and these files need to have a incrementing number. Therefor I thought I could store a Counter in app.config and increment it everytime a file is created.
I used wrote this code :
System.Configuration.Configuration _Config = null;
public Form1()
{
InitializeComponent();
_Config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
}
private void btnNew_Click(object sender, EventArgs e)
{
int lCount;
if (int.TryParse(ConfigurationManager.AppSettings["count"], out lCount))
{
_Config.AppSettings.Settings["count"].Value = lCount++.ToString();
_Config.Save(ConfigurationSaveMode.Modified);
MessageBox.Show(ConfigurationManager.AppSettings["count"].ToString());
}
else
{
throw new Exception("Count in Config file is no int");
}
}
It's not updating the value. Do I need to reload the config or does it not make any sense to store counter value in app.config anyways?
Thanks
you need to use lCount++; before toString();, otherwise the count always save the same value, you can try to add ConfigurationManager.RefreshSection method when you save your new data.
Here is a sample for your question.
int i = 0;
Console.WriteLine("i++:" + i++.ToString()); // will get 0
i = 0;
Console.WriteLine("(++i):"+(++i).ToString()); // will get 1
c# online sample
or you can use (++lCount).ToString() instead of lCount++.ToString()
_Config.AppSettings.Settings["count"].Value = (++lCount).ToString();
the code looks like this.
System.Configuration.Configuration _Config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
int lCount;
if (int.TryParse(ConfigurationManager.AppSettings["count"], out lCount))
{
lCount++;
_Config.AppSettings.Settings["count"].Value = lCount.ToString();
_Config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection(_Config.AppSettings.SectionInformation.Name);
}
Note:
I will suggest you save the counter in DB instead of App.config.
I have properly overwrite commit in InstallerSetup.cs I do not wish to write the user entered value to app.config but rather I want to pass the string Context.Parameters["TESTPARAMETER"]; to another class in form1.cs on load function. I tried string test = InstallerSetup.Context.Parameters["TESTPARAMETER"];
but getting InstallerSetup.Context is null. Please Help.
InstallerSetup.cs
public static string SQLSERVERNAME = "";
public static string HMSTENANTDB;
public static string SQLLOGIN;
public static string SQLPASSWORD;
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
try
{
SQLSERVERNAME = Context.Parameters["SQLSERVERNAME"];
HMSTENANTDB = Context.Parameters["HMSTENANTDB"];
SQLLOGIN = Context.Parameters["SQLLOGIN"];
SQLPASSWORD = Context.Parameters["SQLPASSWORD"];
}
catch (Exception e)
{
MessageBox.Show("Failed to update the application configuration file : " + e.Message);
base.Rollback(savedState);
}
}
from1.cs
InstallerSetup InsSetup = new InstallerSetup();
string Vsqlserver = InsSetup.Installers.Count.ToString();
string Vtenant = "";
if (InsSetup.Context != null)
{
Vtenant = InsSetup.Context.Parameters["HMSTENANTDB"];
}
else
{
Vtenant = "context is null";
}
As far as I can tell, the issue is that the property values are not being passed into the custom action. That would be the most obvious explanation. A commentfrom the poster says:
"passed those parameters to the custom action...................................... SQLSERVERNAME = Context.Parameters["SQLSERVERNAME"];
etc...
//................there is only these 4 lines in my custom actions"
which is essentially repeating the code that was previously posted.
This is NOT passing the values into the custom action. This is retrieving values which must already have been passed into the custom action.
Assuming that the custom action has been correctly added to (typically) the install nod of the custom action, and also assuming that the property names are in a TextBoxes dialog in the install, the values must be passed in to the custom action via the CustomActionData settings. To use one example, the CustomActionData setting must be something like:
/SQLSERVERNAME=[SQLSERVERNAME]
or /SQLSERVERNAME=[EDITA1] if EDIOTA1 is being used because that's the default property name.
However there is no reference to the TextBoxes (or any other) install dialog in the original question, so it's not really clear where the value of (say) SQLSERVERNAME is supposed to come from. It may be passed in on the msiexec command line, for example.
This question already has answers here:
How can I save application settings in a Windows Forms application?
(14 answers)
Closed 7 years ago.
what is the best way to save configuration data in c# application?
note that those data maybe changed dynamically.
as i know, ConfigurationManager class can be used. but i heard that this is not good way to do that.
A simple way is to use a config data object, save it as xml file with the name of the application in the local Folder and on startup read it back.
Here is an example to store the position and size of a form.
The config dataobject is strongly typed and easy to use:
[Serializable()]
public class CConfigDO
{
private System.Drawing.Point m_oStartPos;
private System.Drawing.Size m_oStartSize;
public System.Drawing.Point StartPos
{
get { return m_oStartPos; }
set { m_oStartPos = value; }
}
public System.Drawing.Size StartSize
{
get { return m_oStartSize; }
set { m_oStartSize = value; }
}
}
A manager class for saving and loading:
public class CConfigMng
{
private string m_sConfigFileName = System.IO.Path.GetFileNameWithoutExtension(System.Windows.Forms.Application.ExecutablePath) + ".xml";
private CConfigDO m_oConfig = new CConfigDO();
public CConfigDO Config
{
get { return m_oConfig; }
set { m_oConfig = value; }
}
// Load configfile
public void LoadConfig()
{
if (System.IO.File.Exists(m_sConfigFileName))
{
System.IO.StreamReader srReader = System.IO.File.OpenText(m_sConfigFileName);
Type tType = m_oConfig.GetType();
System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
object oData = xsSerializer.Deserialize(srReader);
m_oConfig = (CConfigDO)oData;
srReader.Close();
}
}
// Save configfile
public void SaveConfig()
{
System.IO.StreamWriter swWriter = System.IO.File.CreateText(m_sConfigFileName);
Type tType = m_oConfig.GetType();
if (tType.IsSerializable)
{
System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
xsSerializer.Serialize(swWriter, m_oConfig);
swWriter.Close();
}
}
}
Now you can use it in your form in the load and close events:
private void Form1_Load(object sender, EventArgs e)
{
// Load config
oConfigMng.LoadConfig();
if (oConfigMng.Config.StartPos.X != 0 || oConfigMng.Config.StartPos.Y != 0)
{
Location = oConfigMng.Config.StartPos;
Size = oConfigMng.Config.StartSize;
}
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// Save config
oConfigMng.Config.StartPos = Location;
oConfigMng.Config.StartSize = Size;
oConfigMng.SaveConfig();
}
And the produced xml file is also readable:
<?xml version="1.0" encoding="utf-8"?>
<CConfigDO xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<StartPos>
<X>70</X>
<Y>278</Y>
</StartPos>
<StartSize>
<Width>253</Width>
<Height>229</Height>
</StartSize>
</CConfigDO>
Very simple, or what so you think?
Use the built-in settings mechanism. But leave the configuration manager alone and use your settings like
Properties.Settings.Default.x
There is nothing wrong in using the ConfigurationManager (web.config/app.config).
For different types of configuration data, I recommend the following;
Never-changing read-only configuration data
Hardcode in application or use (assembly-level) attributes. This
requires a recompile/re-installation of app when configuration-data
changes.
Almost never-changing read-only configuration data
Use the <appSettings>, <connectionStrings> or other sections in web.config/app.config (Often requires a restart of app when changed, especially when configuration-data is read only during startup)
Server-side writable configuration data maintained in application
Use a database or a file in C:\ProgramData\<name of your company>\<name of your app>
Client-side writeable configuration data maintained in application (desktop-apps)
Use a file in C:\Users\<clients login name>\AppData\Local\<name of your company>\<name of your app>\. If you need configuration data to be available on other computers (using the same AD domain), use the LocalRoaming folder under AppData instead (don't place too much configuration data in files placed here, tho because they need to be transferred between computers)
Background: I'm using the HTML 5 Offline App Cache and dynamically building the manifest file. Basically, the manifest file needs to list each of the static files that your page will request. Works great when the files are actually static, but I'm using Bundling and Minification in System.Web.Optimization, so my files are not static.
When in the DEBUG symbol is loaded (i.e. debugging in VS) then the actual physical files are called from the MVC View. However, when in Release mode, it calls a virtual file that could look something like this: /bundles/scripts/jquery?v=FVs3ACwOLIVInrAl5sdzR2jrCDmVOWFbZMY6g6Q0ulE1
So my question: How can I get that URL in the code to add it to the offline app manifest?
I've tried:
var paths = new List<string>()
{
"~/bundles/styles/common",
"~/bundles/styles/common1024",
"~/bundles/styles/common768",
"~/bundles/styles/common480",
"~/bundles/styles/frontend",
"~/bundles/scripts/jquery",
"~/bundles/scripts/common",
"~/bundles/scripts/frontend"
};
var bundleTable = BundleTable.Bundles;
foreach (var bundle in bundleTable.Where(b => paths.Contains(b.Path)))
{
var bundleContext = new BundleContext(this.HttpContext, bundleTable, bundle.Path);
IEnumerable<BundleFile> files = bundle.GenerateBundleResponse(bundleContext).Files;
foreach (var file in files)
{
var filePath = file.IncludedVirtualPath.TrimStart(new[] { '~' });
sb.AppendFormat(formatFullDomain, filePath);
}
}
As well as replacing GenerateBundleResponse() with EnumerateFiles(), but it just always returns the original file paths.
I'm open to alternative implementation suggestions as well. Thanks.
UPDATE: (7/7/14 13:45)
As well as the answer below I also added this Bundles Registry class to keep a list of the required static files so that it works in debug mode in all browsers. (See comments below)
public class Registry
{
public bool Debug = false;
public Registry()
{
SetDebug();
}
[Conditional("DEBUG")]
private void SetDebug()
{
Debug = true;
}
public IEnumerable<string> CommonScripts
{
get
{
if (Debug)
{
return new string[]{
"/scripts/common/jquery.validate.js",
"/scripts/common/jquery.validate.unobtrusive.js",
"/scripts/common/knockout-3.1.0.debug.js",
"/scripts/common/jquery.timepicker.js",
"/scripts/common/datepicker.js",
"/scripts/common/utils.js",
"/scripts/common/jquery.minicolors.js",
"/scripts/common/chosen.jquery.custom.js"
};
}
else
{
return new string[]{
"/scripts/common/commonbundle.js"
};
}
}
}
}
I'm by no means happy with this solution. Please make suggestions if you can improve on this.
I can suggest an alternative from this blog post create your own token.
In summary the author suggests using web essentials to create the bundled file and then creating a razor helper to generate the token, in this case based on the last changed date and time.
public static class StaticFile
{
public static string Version(string rootRelativePath)
{
if (HttpRuntime.Cache[rootRelativePath] == null)
{
var absolutePath = HostingEnvironment.MapPath(rootRelativePath);
var lastChangedDateTime = File.GetLastWriteTime(absolutePath);
if (rootRelativePath.StartsWith("~"))
{
rootRelativePath = rootRelativePath.Substring(1);
}
var versionedUrl = rootRelativePath + "?v=" + lastChangedDateTime.Ticks;
HttpRuntime.Cache.Insert(rootRelativePath, versionedUrl, new CacheDependency(absolutePath));
}
return HttpRuntime.Cache[rootRelativePath] as string;
}
}
Then you can reference the bundled file like so...
#section scripts {
<script src="#StaticFile.Version("~/Scripts/app/myAppBundle.min.js")"></script>}
Then you have control of the token and can do what you want with it.