Build approach for creating ClickOnce packages for multiple environments - wpf

Here is my scenario:
I have a WPF application which I am delivering via ClickOnce. The application has multiple environments for multiple clients (currently 9 but expecting to double that in the near future).
The process I currently use is (basically):
Token replace parts of the app.config
Token replace parts of the WiX file used in the generation of the MSI installer (including the signing certificate and thumbprint)
Build the solution
Create a Client/Environment specific installer
Repeat for each client/environment combination
This has the benefit of meaning that to install the application it is a simple case of running the required installer. However, the downside is that if (when) I need to create a new environment, I have to re-run the whole build process with a new set of configuration parameters.
How can I make this all better?
My latest thought is that I split out my build process to just create the binaries. Then have a separate packaging process that that pulled in the appropriate binaries, patched configs, (re)signed manifests using MAGE etc.
This will have the continued benefit of "build once, deploy multiple times", whilst ensuring that if new environments were required they could be repackaged without rebuilding the binaries.
Does this sound like a sensible approach?
Does anyone have any guidance for such a scenario?
Thanks

We had a similar scenario, with a WPF ClickOnce application used in multiple environments, where the only thing in app.config is a connectionstring.
To get around the fact that you can not change the configuration file within the clickonce package without having a build process building one package for each client/environment we came up with a solution that lets you place an app.config file in the server deployment folder and let the application access that at runtime.
To do that, we created a static class that initializes in app.xaml.cs OnStartup event.
public static class DbConnectionString
{
public static string ConnectionString { get; private set; }
public static string ActivationPath { get; private set; }
public static void Init()
{
string dbContext = "myDbContext";
string configFile = "App.config";
ConnectionString = "";
ActivationPath = "";
ActivationArguments actArg = AppDomain.CurrentDomain.SetupInformation.ActivationArguments;
if (actArg != null)
{
if (actArg.ActivationData != null)
{
try
{
var actData = actArg.ActivationData[0];
var activationPath = Path.GetDirectoryName(new Uri(actData).LocalPath);
var map = new System.Configuration.ExeConfigurationFileMap();
map.ExeConfigFilename = Path.Combine(activationPath, configFile);
var config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
var connectionStringSection = config.ConnectionStrings;
ConnectionString = connectionStringSection.ConnectionStrings[dbContext].ConnectionString;
ActivationPath = activationPath;
}
catch (Exception)
{
ConnectionString = "";
ActivationPath = "";
}
}
}
}
}
In the Project settings under Publish/Options/Manifests tick the "Allow URL parameters to be passed to application"
I then use the ConnectionString property of the static class where I need a connection string. It will not be set unless you deploy the app as online only, so we default to the app.config within the package for dev/testing.
It is a bit convoluted, but works well, and you only have to publish your app once and provide an app.config for each installation that does not change between builds.
It also sets the property ActivationPath which is the path to the clickonce server install directory.

That sounds like a step in the right direction, and is similar to what I've been doing for a WPF application for a couple of years, and it has worked well.
We build the solution with Team City and then have multiple after build steps which handle the ClickOnce publishing, one step for each configuration. Each configuration involves kicking off an MSBuild file which uses Mage.exe. It copies the solution output files to a temporary directory and then performs numerous replacements on files such as the App.config and runs various custom MSBuild tasks.
The MSBuild project file contains base settings and environment overrides for things like the ClickOnce download URL. We also have to do some hacky replacements on the generated manifest itself (and then re-sign it) for things like marking particular files as data, or not.

Related

dotnet ef database update on ASP.NETCORE2.1 project with MSSQLSERVER - Format of the initialization string does not conform to specification ..index 0

As the title says, I am trying to use dotnet ef database update from the command line and getting the error Format of the initialization string does not conform to specification starting at index 0.
From searching around this site and the internet, everything points to the connection string being wrong, but the connection string is working fine to compile and run the application. I am trying to add Identity to the project so I can have users with passwords, and am trying to follow the Deep Dive tutorial on pluralsight, but when it gets to this part, the code fails.
My connection string in appsettings.json is
"ConnectionStrings": {
"DefaultConnection": "Server=PTI-VWS12-002;Database=EPDM_TestVault;Trusted_Connection=true;MultipleActiveResultSets=true;"
},
The code in my Startup.cs is:
var migrationAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), sql=> sql.MigrationsAssembly(migrationAssembly)));
though i've also tried it without the migration assembly as well. I'm really not sure what could be wrong with my connection string.
EDIT: My constructor:
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) { Configuration = configuration; }
And my constructor has the default for ASP.NET CORE 2.1
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>();
EDIT 2: Solved.
I'm still not sure what I did wrong in my project, but i got the Identity tables to generate using the official Asp.NET sample project library over here https://github.com/aspnet/Docs. Using the exact migration file from the IdentityDemo, and plopping in my connection string from above, I was able to create the Identity tables in my database.
You first need to configure IConfiguration using IConfigurationBuilder in Constructor of startup or in Program.cs before Kestrel Server startup.
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
_configuration = builder.Build();
I'm still not sure what I did wrong in my project, but i got the Identity tables to generate using the official Asp.NET sample project library over here https://github.com/aspnet/Docs. Using the exact migration file from the IdentityDemo, and plopping in my connection string from above, I was able to create the Identity tables in my database.
EDIT: it still doesn't run in context of my program.

How can you access Azure Web App AppSettings from a React app at run time

I have a React app created with create-react-app which I have deployed to an Azure Web App. There is no back-end: the site is purely 'static'.
In the production environment, there are a number of keys to API services and other secrets that I need to keep secure, but which the client app needs to be able to read.
In React there's a mechanism for accessing environment-specific information using .env files, such as .env.production, but this is not suitable for keeping secrets, as environment variables mentioned in the code are substituted with the actual value from the .env file during the build process, and are consequently visible to anyone looking at the JavaScript in their browser.
Setting the values of the AppSettings can be done on the Azure Portal (or via suitable scripting in the CI/CD pipeline), but how can I read the AppSettings values at runtime?
A number of StackOverflow questions have been asked about this, but none of the answers address the fundamental question properly, or seem to miss the point. For example, here and here.
First off, this topic is puzzling lot of developers around the globe and it should be addressed properly in Aspnet Core. One viable option would be to set up Server-Side Rendering, but there are some of us who wouldn't benefit from it. Also, there are no proper examples for doing it for ReactJS + Redux in Aspnet Core world.
My solution is to go for a InMemoryFileProvider, see https://github.com/dazinator/Dazinator.AspNet.Extensions.FileProviders for details about the NuGet package.
To start using one, in my Configure() I'll do a:
configuredSpaFileProvider = CreateFileProvider(env);
app.UseSpaStaticFiles(new StaticFileOptions
{
FileProvider = configuredSpaFileProvider
});
My CreateFileProvider() contains an instantiation of InMemoryFileProvider-class, which can be populated with additional fake "files". Pre-existing static file on filesystem will be served with a PhysicalFileProvider.
To allow access to index.html, CSS, JavaScript and all possible static files, do what a DefaultSpaStaticFileProvider would do:
var defaultPath = "build";
if (env.IsDevelopment())
defaultPath = "public";
var absoluteRootPath = System.IO.Path.Combine(
env.ContentRootPath,
$"{ReactJSdirectory}/{defaultPath}");
var physicalFileProvider = new PhysicalFileProvider(absoluteRootPath);
Then create a provider and virtual file like this:
var inMemoryProvider = new InMemoryFileProvider();
inMemoryProvider.Directory.AddFile("/", new StringFileInfo(configJsContent, "app.config.js"));
Finally glue them together, prioritizing on the virtual files:
return new CompositeFileProvider(inMemoryProvider, physicalFileProvider);
Now that a dynamic JavaScript-file exists, it needs to be accessed on client-code. There are options for this, but I'll simply do a:
<script src="/app.config.js" async defer></script>
in my index.html. The trick is to construct a string with suitable JavaScript-parseable content setting up variables and storing them into window-object and accessing them later on a client.
Now my 12 factor application is fully compliant with third factor "Store config in the environment".

setup file is not working fine on client machines

I created a windows form application and my application will read the data from the two xml files in which these files are stored in application bin\debugger folder ,these files will change while running the application and also my form is using the windows media player and some other xml files which are stored in the system drives.
I created a setup file like below procedure
right click on Solution > Add > New Project >setup name
in Application folder I added two xml files and while adding the primary output of application i got a pop up box saying that it depends on the wmp.dll i added that one also.
created a shortcut for primary output ,cut and pasted into user's desktop and next in User Program i created a new folder in that folder i created a shortcut for primary output of an application and named as a setup file next I builded the setup file and after wards and I installed the setup in developed code machine its working fine but when i install the setup file on another computer it is not working please tell me what will be the reason, i'm struggling for this since from 3 days
Note: I added the system drives files in that locations before application running on the client computer
public partial class Form1 : Form
{
XmlDocument SysDetails = new XmlDocument();
XmlDocument UsrDetails = new XmlDocument();
Public Form()
{
string filePath1 = Application.StartupPath;
string org1 = filePath1+ "\\UserFirstDetails.xml";
UsrDetails.Load(org1);
XmlNode NdeFirst= UsrDetails.SelectSingleNode("UserFirstDetails/IsFirstTime");
FirstTime = NdeFirst.InnerText;
if (FirstTime == "True")
{
NdeFirst.InnerText = "False";
XmlNode productnum = UsrDetails.SelectSingleNode("UserFirstDetails/ProductOrderNum");
productnum.InnerText = ProductNo;
UsrDetails.Save(org1);//here i'm getting an exception ,please help me
in setup project it is points to C:\Program Files\Default Company Name\Setup Project, How to make C:\Project Files folder ReadOnly attribute to false
}
}
}
Based on the code sample and the Access Denied error...
Programs cannot write to the ProgramFiles directory unless they are elevated to admin level. So there are two solutions:
There are directories for user data, such as Application Data Folder, User's Application Data Folder etc, and that's where data files should be created and updated, and installed to, not the Program Files directory.
If your program does a bunch of things that require admin privilege and you decide that users need to have admin privilege to run it, then give it an elevation manifest so there will be an elevation dialog when the program starts up.
There's an article here:
http://www.codeproject.com/Articles/17968/Making-Your-Application-UAC-Aware

How to change Play 2 Framework DB configuration at runtime?

We are using Play 2.1.1 and its built-in JPA integration (JPA.em()
etc).
How can we dynamically change the db.pass property? Play.application().configuration() seems
to be immutable as of Play 2.1. (or we're at least not aware of the mutators)
If we are able to change db.pass, how can we reload the DB configuration so that JPA.em() returns an EntityManager using the new password?
What we are trying to avoid is having to recreate the EntityManager using
EntityManagerFactory. We want to continue to let Play manage that in
the JPA helper class.
Background
The system has a default DB configuration for running locally. When deployed to a server, the DB password is dynamically set on the running application using the following script:
#!/bin/bash
stty -echo
read -p "Password: " PASS
stty echo
curl -k https://127.0.0.1:8443/someUrl/pwd --data "password=$PASS"
The application receives this data and then recreates the Hibernate
SessionFactory. Our new Play app will be required to do something
similar.
The key is to use the ConfigFactory to create a new Config entry. This new Config contains an entry for password with the value coming from your http call to your password service.
A new Configuration is created using the new Config, which in turn falls back to the original Config from the original Configuration.
Basically the new password entry supersedes the original.
It sound long winded when you say it, but the code is pretty readable.
public class Global extends GlobalSettings {
// inject http client to make call for password
#Override
public Configuration onLoadConfig(Configuration configuration, File file, ClassLoader classLoader) {
final Config config = ConfigFactory.parseString(String.format("db.default.user=%s", callPasswordService()));
return new Configuration(config.withFallback(configuration.getWrappedConfiguration().underlying()));
}
}
To answer my own question, at first we solved the problem of updating the immutable configuration at runtime by overriding Configuration.onLoadConfig with the following:
If configuration indicates that production.level is PROD
Read the password from stdin
Create a new configuration by converting the old one to a map and building a new one with ConfigFactory.parseMap, with the new parameter as well
Return super.onLoadConfig
However, this still didn't address that the problem of reloading the DB configuration. In the end, my colleague created a Play! plugin which essentially a copy of some JPA classes with the added capability of being reloaded with a Map of configuration properties.
Update
The "hook" is the additional static method which the plugin adds to the JPA class (e.g. reloadWithProperties). This method creates a new data source which is then rebound in JNDI.

Lync 2010 Plugin - Silverlight WPF with elevated permissions

I'm developing a CWE (Conversation Extensibility Window) with WPF and Silverlight 4 on Visual Studio 2010, for Lync 2010.
My application reads a list of .xml from the root directory of the application and into a string array.
I've this code:
bool featureComplete = Application.Current.HasElevatedPermissions;
if (featureComplete)
{
List<string> files = new List<string>(Directory.EnumerateFiles(textBox1.Text, "*.*"));
mensajes.Content = files.Count;
}
else
{
mensajes.Content = "no trust";
}
In the event handler of a button event. With any path (my documents, root, application, etc) the application says "no trust".
I change the properties on the build to Out-of-browser settings and also check "require elevated trust" but nothing happens.
I tried and tried looking for an answer on google and msdn but I could not find a solution.
So there's a checklist or step list to make a trusted CWE on Lync2010 with silverlight? I forgot something?
Please remember: this is not a usual web application, its a lync 2010 app.
ALSO: I can do it with "debug mode" with special folders like this tutorial: http://www.silverlight.net/learn/graphics/file-and-local-data/local-file-access
and it works, but when I run it under lync 2010 it says "access denied" for that folder.
There is the code that works only on debug as an application, and not works like lync applet:
List<string> picsinfolder = new List<string>();
var pics = Directory.EnumerateFiles
(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures));
foreach (var item in pics)
{
picsinfolder.Add(item);
}
listBox1.ItemsSource = picsinfolder;
Thanks a lot in advance.
PD: Work-arounds that fix the issue can be accepted as an answer
PD2: No, signing the xap doesn't work.
The work around I'm using and works is open an XML, reading, copy into Isolated Storage, read it again from there. Everytime I need to change the file, I read it and copy again on Isolated Storage

Resources