Skip to content

Commit

Permalink
Merge pull request #314 from nventive/dev/arde/update-feature-branch-…
Browse files Browse the repository at this point in the history
…from-main

chore: merge main into feature/screen-reader-support
  • Loading branch information
Arieldelossantos authored May 16, 2023
2 parents bfb25af + 1ce9e47 commit 228e1c5
Show file tree
Hide file tree
Showing 34 changed files with 812 additions and 211 deletions.
14 changes: 7 additions & 7 deletions build/variables.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ variables:
# Make sure you have the following secured files in your Azure pipeline library.
#
# Android
InternalKeystore: com.nventive.internal.applicationtemplate.jks # This is the internal keystore used for internal builds.
GooglePlayKeystore: com.nventive.applicationtemplate.jks # This is the official keystore used for Google Play.
InternalKeystore: com.nventive.internal.applicationtemplate.jks # This should be the internal keystore used for internal builds.
GooglePlayKeystore: com.nventive.applicationtemplate.jks # This should be the official keystore used for Google Play.
# iOS
InternalProvisioningProfile: com.nventive.internal.applicationtemplate.mobileprovision # This is the internal provisioning profile for internal builds.
InternalCertificate: nventive_enterprise.p12 # This is the certificate from the entreprise account used to sign internal builds.
AppStoreProvisioningProfile: com.nventive.applicationtemplate.mobileprovision # This is the official provisioning profile for the AppStore.
AppStoreCertificate: nventive.p12 # This is the official certificate used to sign AppStore builds.
InternalProvisioningProfile: com.nventive.applicationtemplate.mobileprovision # This should be the internal provisioning profile for internal builds.
InternalCertificate: nventive.p12 # This should be the certificate from the nventive Apple account used to sign internal builds.
AppStoreProvisioningProfile: com.nventive.applicationtemplate.mobileprovision # This should be the client provisioning profile for the AppStore (Production distribution).
AppStoreCertificate: nventive.p12 # This should be the client production certificate used to sign AppStore builds.

# Prerequisites - Service connections
# Make sure you have the following service connections in your Azure pipeline library.
Expand Down Expand Up @@ -77,7 +77,7 @@ variables:
macOSPoolName: 'macOS'

# Versions to use
XAMARIN_IOS_VERSION: 15.10
XAMARIN_IOS_VERSION: 16.1.1

# Name of the folder where the artefacts will be placed. Variable used in build and release phases.
# We make seperate folders so that releases can each download only the folder they need.
Expand Down
10 changes: 5 additions & 5 deletions doc/Environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ By default, the template offers the following runtime environments, defined by t

You can add / remove runtime environments by simply adding or removing appsettings files (e.g. `appsettings.myenvironment.json` will create a new runtime environment named `myenvironment`).

This is configured inside the [AppSettingsConfiguration.cs](../src/app/ApplicationTemplate.Shared/Configuration/AppSettingsConfiguration.cs) file.
This is governed by `IEnvironmentManager` and configured inside the [ConfigurationConfiguration.cs](../src/app/ApplicationTemplate.Presentation/Configuration/ConfigurationConfiguration.cs) file.

- The default runtime environment is set based on a compile-time directive (e.g. production).

- You can get the current environment using `AppSettingsConfiguration.AppEnvironment.GetCurrent`.
- You can get the current environment using `IEnvironmentManager.Current`.

- You can get all the possible environments using `AppSettingsConfiguration.AppEnvironment.GetAll`.
- You can get all the possible environments using `IEnvironmentManager.AvailableEnvironments`.

- You can set the current environment using `AppSettingsConfiguration.AppEnvironment.SetCurrent`. If the environment doesn't exist, you will get an exception.
- You can set the current environment using `IEnvironmentManager.Override`. If the environment doesn't exist, you will get an exception.

- The current environment is persisted into a file that is processed at startup; this allows the environment to affect your IoC and be faster than accessing any settings service (e.g. no IoC, no deserialization, etc.).
- When using `EnvironmentManager` (the default implementation of `IEnvironmentManager`), the current environment is persisted into a file that is processed at startup; this allows the environment to affect your IoC and be faster than accessing any settings service (e.g. no IoC, no deserialization, etc.).

- The current environment is also used when configuring the `IHostBuilder`. For example, you can do the following:
```csharp
Expand Down
2 changes: 1 addition & 1 deletion src/app/ApplicationTemplate.Mobile/iOS/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<key>CFBundleDisplayName</key>
<string>ApplicationTemplate</string>
<key>CFBundleIdentifier</key>
<string>com.nventive.internal.applicationtemplate</string>
<string>com.nventive.applicationtemplate</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="ByteSize" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
Expand All @@ -17,57 +18,65 @@ namespace ApplicationTemplate;
public static class ConfigurationConfiguration
{
public const string AppSettingsFileName = "appsettings";

//-:cnd:noEmit
#if PRODUCTION
//+:cnd:noEmit
public const string DefaultEnvironment = "PRODUCTION";
//-:cnd:noEmit
#elif DEBUG
//+:cnd:noEmit
public const string DefaultEnvironment = "DEVELOPMENT";
//-:cnd:noEmit
#else
//+:cnd:noEmit
public const string DefaultEnvironment = "STAGING";
//-:cnd:noEmit
#endif
//+:cnd:noEmit
public const string AppSettingsFileNameWithExtensions = AppSettingsFileName + ".json";
public const string AppSettingsOverrideFileNameWithExtension = AppSettingsFileName + ".override.json";

/// <summary>
/// Populates the <see cref="IConfiguration"/> of the host using appsettings.json files.
/// </summary>
/// <param name="hostBuilder">The host builder.</param>
/// <param name="folderPath">The folder path pointing to override files.</param>
public static IHostBuilder AddConfiguration(this IHostBuilder hostBuilder, string folderPath)
/// <param name="environmentManager">The environment manager.</param>
public static IHostBuilder AddConfiguration(this IHostBuilder hostBuilder, string folderPath, IEnvironmentManager environmentManager)
{
if (hostBuilder is null)
{
throw new ArgumentNullException(nameof(hostBuilder));
}

return hostBuilder
.AddConfiguration()
.AddConfiguration(environmentManager)
.ConfigureHostConfiguration(b => b
// Readonly configuration (from code, not from any file)
.AddReadOnlyConfiguration(folderPath, environmentManager)

// appsettings.json
.AddBaseConfiguration()

// appsettings.{staging, production, etc.}.json
.AddEnvironmentConfiguration(folderPath)
.AddEnvironmentConfiguration(environmentManager)

// appsettings.override.json
.AddUserOverrideConfiguration(folderPath)
);
}

/// <summary>
/// Adds the read-only configuration source containing the values that cannot come from appsettings.json files.
/// </summary>
/// <param name="configurationBuilder">The configuration builder.</param>
/// <param name="folderPath">The folder containing the configuration override files.</param>
private static IConfigurationBuilder AddReadOnlyConfiguration(this IConfigurationBuilder configurationBuilder, string folderPath, IEnvironmentManager environmentManager)
{
return configurationBuilder.AddInMemoryCollection(GetCodeConfiguration(folderPath));

IEnumerable<KeyValuePair<string, string>> GetCodeConfiguration(string folderPath)
{
var prefix = ApplicationTemplateConfigurationExtensions.DefaultOptionsName<ReadOnlyConfigurationOptions>() + ":";

yield return new KeyValuePair<string, string>(prefix + nameof(ReadOnlyConfigurationOptions.ConfigurationOverrideFolderPath), folderPath);
yield return new KeyValuePair<string, string>(prefix + nameof(ReadOnlyConfigurationOptions.DefaultEnvironment), environmentManager.Default);
}
}

/// <summary>
/// Adds the base configuration source.
/// </summary>
/// <param name="configurationBuilder">The configuration builder.</param>
private static IConfigurationBuilder AddBaseConfiguration(this IConfigurationBuilder configurationBuilder)
{
var generalAppSettingsFileName = $"{AppSettingsFileName}.json";
var generalAppSettings = AppSettings.GetAll().SingleOrDefault(s => s.FileName.EndsWith(generalAppSettingsFileName, StringComparison.OrdinalIgnoreCase));
var generalAppSettingsFileName = AppSettingsFileNameWithExtensions;
var generalAppSettings = AppSettingsFile.GetAll().SingleOrDefault(s => s.FileName.EndsWith(generalAppSettingsFileName, StringComparison.OrdinalIgnoreCase));

if (generalAppSettings != null)
{
Expand All @@ -82,12 +91,11 @@ private static IConfigurationBuilder AddBaseConfiguration(this IConfigurationBui
/// The environment can be overriden by the user.
/// </summary>
/// <param name="configurationBuilder">The configuration builder.</param>
/// <param name="folderPath">The folder path indicating where the override file should be when the user overrode the environment.</param>
private static IConfigurationBuilder AddEnvironmentConfiguration(this IConfigurationBuilder configurationBuilder, string folderPath)
private static IConfigurationBuilder AddEnvironmentConfiguration(this IConfigurationBuilder configurationBuilder, IEnvironmentManager environmentManager)
{
var currentEnvironment = AppEnvironment.GetCurrent(folderPath);
var currentEnvironment = environmentManager.Current;
var environmentAppSettingsFileName = $"{AppSettingsFileName}.{currentEnvironment}.json";
var environmentAppSettings = AppSettings.GetAll().SingleOrDefault(s => s.FileName.EndsWith(environmentAppSettingsFileName, StringComparison.OrdinalIgnoreCase));
var environmentAppSettings = AppSettingsFile.GetAll().SingleOrDefault(s => s.FileName.EndsWith(environmentAppSettingsFileName, StringComparison.OrdinalIgnoreCase));

if (environmentAppSettings != null)
{
Expand All @@ -104,111 +112,35 @@ private static IConfigurationBuilder AddEnvironmentConfiguration(this IConfigura
/// <param name="folderPath">The folder path indicating where the override file should be.</param>
private static IConfigurationBuilder AddUserOverrideConfiguration(this IConfigurationBuilder configurationBuilder, string folderPath)
{
return configurationBuilder.Add(new WritableJsonConfigurationSource(Path.Combine(folderPath, AppSettingsFileName + ".override.json")));
return configurationBuilder.Add(new WritableJsonConfigurationSource(Path.Combine(folderPath, AppSettingsOverrideFileNameWithExtension)));
}

/// <summary>
/// Registers the <see cref="IConfiguration"/> as a singleton.
/// </summary>
/// <param name="hostBuilder">The host builder.</param>
private static IHostBuilder AddConfiguration(this IHostBuilder hostBuilder)
private static IHostBuilder AddConfiguration(this IHostBuilder hostBuilder, IEnvironmentManager environmentManager)
{
if (hostBuilder is null)
{
throw new ArgumentNullException(nameof(hostBuilder));
}

return hostBuilder.ConfigureServices((ctx, s) =>
s.AddSingleton(a => ctx.Configuration)
return hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) => serviceCollection
.AddSingleton(serviceProvider => hostBuilderContext.Configuration)
.AddSingleton(environmentManager)
.BindOptionsToConfiguration<ReadOnlyConfigurationOptions>(hostBuilderContext.Configuration)
);
}

public class AppEnvironment
public class AppSettingsFile
{
private static string _currentEnvironment;
private static string _currentFolderPath;

/// <summary>
/// Gets the current environment.
/// </summary>
/// <param name="folderPath">The path to the directory containing the environment override file.</param>
/// <returns>The current environment.</returns>
public static string GetCurrent(string folderPath)
{
if (_currentEnvironment == null)
{
_currentFolderPath = folderPath;
var filePath = GetSettingFilePath(folderPath);

_currentEnvironment = File.Exists(filePath)
? File.ReadAllText(filePath)
: DefaultEnvironment;
}

return _currentEnvironment.ToUpperInvariant();
}

/// <summary>
/// Sets the current environment to <paramref name="environment"/>.
/// </summary>
/// <param name="environment">The environment override.</param>
public static void SetCurrent(string environment)
{
if (environment == null)
{
throw new ArgumentNullException(nameof(environment));
}

environment = environment.ToUpperInvariant();

var availableEnvironments = GetAll();

if (!availableEnvironments.Contains(environment))
{
throw new InvalidOperationException($"Environment '{environment}' is unknown and cannot be set.");
}

using (var writer = File.CreateText(GetSettingFilePath(_currentFolderPath)))
{
writer.Write(environment);
}

_currentEnvironment = environment;
}

public static string[] GetAll()
{
var environmentsFromAppSettings = AppSettings
.GetAll()
.Select(s => s.Environment)
.Where(s => !string.IsNullOrEmpty(s))
.Select(s => s.ToUpperInvariant())
.OrderBy(name => name)
.ToArray();

if (environmentsFromAppSettings.Length == 0)
{
// Return the default environment if there are no environment specific appsettings.
return new string[] { DefaultEnvironment };
}

return environmentsFromAppSettings;
}

private static string GetSettingFilePath(string folderPath)
{
return Path.Combine(folderPath, "environment");
}
}

public class AppSettings
{
private static AppSettings[] _appSettings;
private static AppSettingsFile[] _appSettingsFiles;

private readonly Assembly _assembly;
private readonly Lazy<string> _environment;

public AppSettings(string fileName, Assembly assembly)
public AppSettingsFile(string fileName, Assembly assembly)
{
FileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
_assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
Expand All @@ -217,6 +149,10 @@ public AppSettings(string fileName, Assembly assembly)

public string FileName { get; }

/// <summary>
/// Gets the environment from the file name (like "production" in "appsettings.production.json").
/// </summary>
/// <returns>The environment string if available or null otherwise.</returns>
public string Environment => _environment.Value;

private string GetEnvironment()
Expand Down Expand Up @@ -246,21 +182,20 @@ public Stream GetContent()
}
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Not available for desktop.")]
public static AppSettings[] GetAll()
public static AppSettingsFile[] GetAll()
{
if (_appSettings == null)
if (_appSettingsFiles == null)
{
var executingAssembly = Assembly.GetExecutingAssembly();

_appSettings = executingAssembly
_appSettingsFiles = executingAssembly
.GetManifestResourceNames()
.Where(fileName => fileName.ToUpperInvariant().Contains(AppSettingsFileName.ToUpperInvariant()))
.Select(fileName => new AppSettings(fileName, executingAssembly))
.Select(fileName => new AppSettingsFile(fileName, executingAssembly))
.ToArray();
}

return _appSettings;
return _appSettingsFiles;
}
}
}
Loading

0 comments on commit 228e1c5

Please sign in to comment.