display build version on web page - continuous-integration

I am developing a web site using asp.net core.
And I publish it with Visual Studio or/and VSTS.
I want to display some information about which build it is on the web page.
(something like rev 2016.9.20.4002)
How can I do that?

You can track it with build number.
Go to your VSTS site and create build definition
Select General tab, specify build number format, for example: $(date:yyyyMMdd)$(rev:.r)
(Optional) Select Triggers tab, check Continuous integration (CI) and configure filters if you want queue build for each check-in.
Configure other settings (e.g. steps/task in Build tab)
After build complete, go to the summary of that build definition (click build definition title hyperlink to go to summary page), the result will be like this:
Steps to display build number to your website:
Install Replace Tokens extension to you VSTS
Edit your build definition to add Replace token task
Specify target files and root directory (for asp.net core app, you can specify **\appsettings.json)
Select Variable tab and add a new variable. Save your build definition
Edit appsettings.json file of your asp.net project. Sample code:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication1-ab933d83-8f4b-4024-9f3c-1aef5339a8f3;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"CodeVersion": {
"Num": "#{MyBuildNumber}#"
}
}
Add logical to your asp.net project to read appsettings.json to get specific value and display in the page.
Check in your code and queue build.

After a day of research, finally found/created a better option than using any random app (Replace Token) from Marketplace.
The option I am talking is already available in VSTS, Azure CLI task.
Here are the stpes:
Add setting BUILD_NUMBER with initial value of 1.0 in appsettings.json
Read appsettings.json in your app and display it. I am sure you all are smart enough to figure out how to use appsettings to display Build Number on your WebApplication.
In Azure Portal, similarly create an App Setting named BUILD_NUMBER with initial value of 1.0 in Azure Application settings section under App Services for your App.
In VSTS, In your Release definition, add a task Azure CLI.
Populate required fields such as Azure Subscription, Script Location with Inline script and last but most important Inline Script with following CLI command
az webapp config appsettings set -n iCoreTestApi -g ArchitectsSandbox -s Dev --settings BUILD_NUMBER=$(Build.BuildNumber)
Command explanation:
iCoreTestApi should be replaced by your real WebApp or Api name in Azure
ArchitectsSandbox should be replaced by your resource group in Azure
Dev is the slot name, you may or may not have it.
Rest of the command remains same.
Once you will queue new build, after successful completion of the deployment, you can see app settings section on Azure is updated with new BUILD_NUMBER.
Let me know if you still have any question.

Do you mean something like this:
In project.json:
{
"title": "Your Application name",
"version": "2016.9.20.4002",
"copyright": "Your Company 2016",
"description": "Awesome ASP.Net Core Application",
"dependencies": {
//rest of project.json
You can then create a property in your view model or model such as:
public static string Version
{
get
{
var assembly = Assembly.GetExecutingAssembly();
var fileVersion = GetCustomAttribute<AssemblyFileVersionAttribute>(assembly);
return fileVersion?.Version;
}
}
In your view:
#model Namespace.CustomViewModel
<!--Other HTML Code-->
<span id="applicationVersion">#CustomViewModel.Version</span>

Looks like ApplicationEnvironment class is what you need:
var appEnv = new Microsoft.Extensions.PlatformAbstractions.ApplicationEnvironment();
string version = appEnv.ApplicationVersion;
Also
How can I auto-increment an MVC 6 version number? may be also interesting to you, but keep in mind, that IApplicationEnvironment has been removed.

Just as an alternative option, you could read the time that the assembly was created and display it in a version format. Every time the assembly is rebuilt, this value would change to the time it was created.
(adapted from this answer for .Net Core)
public static class AppInfo
{
private static Lazy<string> buildVersion =
new Lazy<string>(() => GetBuildVersion(Assembly.GetEntryAssembly()));
public static string BuildVersion { get; } = buildVersion.Value;
private static string GetBuildVersion(Assembly assembly)
{
var filePath = assembly.Location;
const int c_PeHeaderOffset = 60;
const int c_LinkerTimestampOffset = 8;
var buffer = new byte[2048];
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
stream.Read(buffer, 0, 2048);
var offset = BitConverter.ToInt32(buffer, c_PeHeaderOffset);
var secondsSince1970 = BitConverter.ToInt32(buffer, offset + c_LinkerTimestampOffset);
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var linkTimeUtc = epoch.AddSeconds(secondsSince1970);
var localTime = TimeZoneInfo.ConvertTime(linkTimeUtc, TimeZoneInfo.Local);
var minutesFromMidnight = localTime.Minute + localTime.Hour * 60;
return localTime.ToString("yyyy.M.dd.") + minutesFromMidnight;
}
}
Then just reference it in Razor as:
#AppInfo.BuildVersion

It may be not exactly what requested but was quite efficient in my situation. If docker is used to build and deploy web apps, consider adding this step to your CI build script before docker build command (bash):
echo ENV BUILD_NUMBER=$(Build.BuildNumber) >> Dockerfile
In the app code just use BUILD_NUMBER environment variable.
The advantage of this method is that build number value becomes metadata of the docker image just like labels but you can also access it from the inside your app.

Related

How to automatically update the UI after user updates a userProperty in Google Apps Script Workspace Add-ons for Google Sheets?

I want to automagically update the UI with a new user setting after the user updates that setting by submitting a user input from.
Currently, I am attempting to use the updateCard() method from the CardService as shown in the below code. The docs are here but they do not contain any example code.
I expect that after the user provides the input and submits it, the current card will be replaced by an updated card that will contain the new setting.
However, what’s actually happening is that the card I expect to update is not updating automatically. To see the change, the user has to manually refresh the app homepage. After, and only after, a manual refresh, does the homepage card update with the new setting.
How do I update the homepage automatically without requiring a manual refresh?
Code.gs
const changedProperty = PropertiesService.getUserProperties().getProperty( MY_SETTING );
const newNavigation = CardService.newNavigation();
const cardPoppedToRoot = newNavigation.popToRoot();
const homepageCard = getCardFromUiConfig( HOMEPAGE_UI_CONFIG, );
const updatedCard = cardPoppedToRoot.updateCard( homepageCard, );
return updatedCard;
I also tried the following code per this answer and the results are exactly the same as with the above code.
Code.gs
return CardService.newActionResponseBuilder()
.setNavigation(
CardService.newNavigation()
.popToRoot()
.updateCard( homepageCard, )
).build();
When I try to configure my appsscript.json file as shown in the answer as follows:
appsscript.json
"homepageTrigger": {
"runFunction": "onHomepage"
},
"contextualTriggers":[
{
"unconditional":{},
"onTriggerFunction": "onHomepage"
}
]
I get the following error:
"appsscript.json" has errors: Invalid manifest: unknown fields: [addOns.common.contextualTriggers]
I think that that is only possible for Gmail add-ons.
contextualTriggers can't be child of common.
From https://developers.google.com/apps-script/manifest/addons#common (links not included):
Common
The manifest configuration for parameters that are common for every host application. Some values defined here are used as a default when specific values for a particular host are omitted.
{
"homepageTrigger": {
object (HomepageTrigger)
},
"layoutProperties": {
object (LayoutProperties)
},
"logoUrl": string,
"name": string,
"openLinkUrlPrefixes": [
string
],
"universalActions": [
{
object (UniversalAction)
}
],
"useLocaleFromApp": boolean
}
AFAIK contextualTriggers can only be used with Gmail add-ons. From https://developers.google.com/apps-script/manifest/gmail-addons (links not included):
Gmail
The Google Workspace add-on manifest configuration for Gmail extensions. See Extending Gmail with Google Workspace add-ons for more information.
{
"authorizationCheckFunction": string,
"composeTrigger": {
object (ComposeTrigger)
},
"contextualTriggers": [
{
object (ContextualTrigger)
}
],
"homepageTrigger": {
object (HomepageTrigger)
}
}
Related
How to fully refresh Google Addon Card (Google Sheets) which includes a drop down menu populated using sheet data when data changes?

Configuration doesn't pick connection string from secrets.json file in ASP NET Core MVC 6.0 lts

I created ASP NET Core MVC 6 lts app with individual user accounts with Visual Studio 2022 preview 6.0. I moved connection string from appsettings.json to secrets.json. I start the app with IIS Express and apply migrations from exception page that opens. It works.
But when I try to apply additional migrations from Package Manager Console I get an error "connectionString can't be null".
Add services to the container. This is from Program.cs:
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
This is appsettings.json, connection string value removed:
"ConnectionStrings": {
"DefaultConnection": ""
},
This is Secrets.json:
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=DESKTOP-HIPPIDT;Initial Catalog=KehittajaLocalDb;Integrated Security=True;MultipleActiveResultSets=True"
}
}
In .NET 6,
You do not need to specify AddUserSecrets<>.
As mentioned here ,
WebApplication.CreateBuilder initializes a new instance of the
WebApplicationBuilder class with preconfigured defaults. The
initialized WebApplicationBuilder (builder) provides default
configuration and calls AddUserSecrets when the EnvironmentName is
Development
All you need to do is get the connection string from the builder.Configuration like below:
var dbConnectionString = builder.Configuration["ConnectionStrings:DevelopmentConnection"];
Then, you can specify the connection with AddDbContext<>:
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(dbConnectionString);
});
I added this to the Program.cs, now it works as expected. I don't know how come it worked when migrations were applied from exception page "Apply Migrations" button anyway.
// Add services to the container
if(builder.Enviroment.IsDevelopment())
{
builder.Configuration.AddUserSecrets<Program>();
}
..other code

Properly implement In-App Updates in App Center?

I am reading this documentation/article from Microsoft on how to Distribute Mobile apps with app center. The problem is I really don't understand how to implement this. I have a app on app center (Android) I want to implement mandatory update so that I can eliminate the bugs of the previous version. I tried to distribute the app with mandatory update enabled and it is not working. How can I fix this?
https://learn.microsoft.com/en-us/appcenter/distribution/
Here is what I did I added this code on my App.xaml.cs (XAMARIN FORMS PROJECT):
protected override void OnStart ()
{
AppCenter.Start("android={Secret Code};", typeof(Analytics), typeof(Crashes), typeof(Distribute));
Analytics.SetEnabledAsync(true);
Distribute.SetEnabledAsync(true);
Distribute.ReleaseAvailable = OnReleaseAvailable;
}
bool OnReleaseAvailable(ReleaseDetails releaseDetails)
{
string versionName = releaseDetails.ShortVersion;
string versionCodeOrBuildNumber = releaseDetails.Version;
string releaseNotes = releaseDetails.ReleaseNotes;
Uri releaseNotesUrl = releaseDetails.ReleaseNotesUrl;
var title = "Version " + versionName + " available!";
Task answer;
if (releaseDetails.MandatoryUpdate)
{
answer = Current.MainPage.DisplayAlert(title, releaseNotes, "Download and Install");
}
else
{
answer = Current.MainPage.DisplayAlert(title, releaseNotes, "Download and Install", "Ask Later");
}
answer.ContinueWith((task) =>
{
if (releaseDetails.MandatoryUpdate || (task as Task<bool>).Result)
{
Distribute.NotifyUpdateAction(UpdateAction.Update);
}
else
{
Distribute.NotifyUpdateAction(UpdateAction.Postpone);
}
});
return true;
}
And here is what I added on my MainActivity.cs(ANDROID PROJECT):
AppCenter.Start("{Secret Code}", typeof(Analytics), typeof(Crashes), typeof(Distribute));
Looking at this App Center documentation here for Xamarin Forms -
You can customize the default update dialog's appearance by implementing the ReleaseAvailable callback. You need to register the callback before calling AppCenter.Start
It looks like you need to swap your current ordering to get in-app updates working.
There could be a lot of different reasons as to why they are not working. As you can see in the Notes here and here,
Did your testers download the app from the default browser?
Are cookies enabled for the browser in their settings?
Another important point you'll read in the links, is that the feature is only available for listed distribution group users. It is not for all your members. You could use a simple version checker for your purpose instead or you could use a plugin.

Auto-updates to Electron

I'm looking to deploy an auto-update feature to an Electron installation that I have, however I am finding it difficult to find any resources on the web.
I've built a self contained application using Adobe Air before and it seemed to be a lot easier writing update code that effectively checked a url and automatically downloaded and installed the update across Windows and MAC OSX.
I am currently using the electron-boilerplate for ease of build.
I have a few questions:
How do I debug the auto update feature? Do I setup a local connection and test through that using a local Node server or can I use any web server?
In terms of signing the application I am only looking to run apps on MAC OSX and particularly Windows. Do I have to sign the applications in order to run auto-updates? (I managed to do this with Adobe Air using a local certificate.
Are there any good resources that detail how to implement the auto-update feature? As I'm having difficulty finding some good documentation on how to do this.
I am also new to Electron but I think there is no simple auto-update from electron-boilerplate (which I also use). Electron's auto-updater uses Squirrel.Windows installer which you also need to implement into your solution in order to use it.
I am currently trying to use this:
https://www.npmjs.com/package/electron-installer-squirrel-windows
And more info can be found here:
https://github.com/atom/electron/blob/master/docs/api/auto-updater.md
https://github.com/squirrel/squirrel.windows
EDIT: I just opened the project to try it for a while and it looks it works. Its pretty straightforward. These are pieces from my gulpfile.
In current configuration, I use electron-packager to create a package.
var packager = require('electron-packager')
var createPackage = function () {
var deferred = Q.defer();
packager({
//OPTIONS
}, function done(err, appPath) {
if (err) {
gulpUtil.log(err);
}
deferred.resolve();
});
return deferred.promise;
};
Then I create an installer with electron-installer-squirrel-windows.
var squirrelBuilder = require('electron-installer-squirrel-windows');
var createInstaller = function () {
var deferred = Q.defer();
squirrelBuilder({
// OPTIONS
}, function (err) {
if (err)
gulpUtil.log(err);
deferred.resolve();
});
return deferred.promise;
}
Also you need to add some code for the Squirrel to your electron background/main code. I used a template electron-squirrel-startup.
if(require('electron-squirrel-startup')) return;
The whole thing is described on the electron-installer-squirrel-windows npm documentation mentioned above. Looks like the bit of documentation is enough to make it start.
Now I am working on with electron branding through Squirrel and with creating appropriate gulp scripts for automation.
You could also use standard Electron's autoUpdater module on OS X and my simple port of it for Windows: https://www.npmjs.com/package/electron-windows-updater
I followed this tutorial and got it working with my electron app although it needs to be signed to work so you would need:
certificateFile: './path/to/cert.pfx'
In the task config.
and:
"build": {
"win": {
"certificateFile": "./path/to/cert.pfx",
"certificatePassword": "password"
}
},
In the package.json
Are there any good resources that detail how to implement the auto-update feature? As I'm having difficulty finding some good documentation on how to do this.
You don't have to implement it by yourself. You can use the provided autoUpdater by Electron and just set a feedUrl. You need a server that provides the update information compliant to the Squirrel protocol.
There are a couple of self-hosted ones (https://electronjs.org/docs/tutorial/updates#deploying-an-update-server) or a hosted service like https://www.update.rocks
Question 1:
I use Postman to validate that my auto-update server URLs return the response I am expecting. When I know that the URLs provide the expected results, I know I can use those URLs within the Electron's Auto Updater of my Application.
Example of testing Mac endpoint with Postman:
Request:
https://my-server.com/api/macupdates/checkforupdate.php?appversion=1.0.5&cpuarchitecture=x64
JSON Response when there is an update available:
{
"url": "https:/my-server.com/updates/darwin/x64/my-electron=app-x64-1.1.0.zip",
"name": "1.1.0",
"pub_date": "2021-07-03T15:17:12+00:00"
}
Question 2:
Yes, your Electron App must be code signed to use the auto-update feature on Mac. On Windows I'm not sure because my Windows Electron app is code signed and I did not try without it. Though it is recommended that you sign your app even if the auto-update could work without it (not only for security reasons but mainly because otherwise your users will get scary danger warnings from Windows when they install your app for the first time and they might just delete it right away).
Question 3:
For good documentation, you should start with the official Electron Auto Updater documentation, as of 2021-07-07 it is really good.
The hard part, is figuring out how to make things work for Mac. For Windows it's a matter of minutes and you are done. In fact...
For Windows auto-update, it is easy to setup - you just have to put the RELEASES and nupkg files on a server and then use that URL as the FeedURL within your Electron App's autoUpdater. So if your app's update files are located at https://my-server.com/updates/win32/x64/ - you would point the Electron Auto Updater to that URL, that's it.
For Mac auto-update, you need to manually specify the absolute URL of the latest Electron App .zip file to the Electron autoUpdater. So, in order to make the Mac autoUpdater work, you will need to have a way to get a JSON response in a very specific format. Sadly, you can't just put your Electron App's files on your server and expect it to work with Mac just like that. Instead, the autoUpdater needs a URL that will return the aforementioned JSON response. So to do that, you need to pass Electron's Auto Updater feedURL the URL that will be able to return this expected kind of JSON response.
The way you achieve this can be anything but I use PHP just because that's the server I already paid for.
So in summary, with Mac, even if your files are located at https://my-server.com/updates/darwin/x64/ - you will not provide that URL to Electron's Auto Updater FeedURL. Instead will provide another URL which returns the expected JSON response.
Here's an example of my main.js file for the Electron main process of my App:
// main.js (Electron main process)
function registerAutoUpdater() {
const appVersion = app.getVersion();
const os = require('os');
const cpuArchitecture = os.arch();
const domain = 'https://my-server.com';
const windowsURL = `${domain}/updates/win32/x64`;
const macURL = `${domain}/api/macupdates/checkforupdate.php?appversion=${appVersion}&cpuarchitecture=${cpuArchitecture}`;
//init the autoUpdater with proper update feed URL
const autoUpdateURL = `${isMac ? macURL : windowsURL}`;
autoUpdater.setFeedURL({url: autoUpdateURL});
log.info('Registered autoUpdateURL = ' + (isMac ? 'macURL' : 'windowsURL'));
//initial checkForUpdates
autoUpdater.checkForUpdates();
//Automatic 2-hours interval loop checkForUpdates
setInterval(() => {
autoUpdater.checkForUpdates();
}, 7200000);
}
And here's an example of the checkforupdate.php file that returns the expected JSON response back to the Electron Auto Updater:
<?php
//FD Electron App Mac auto update API endpoint.
// The way Squirrel.Mac works is by checking a given API endpoint to see if there is a new version.
// If there is no new version, the endpoint should return HTTP 204. If there is a new version,
// however, it will expect a HTTP 200 JSON-formatted response, containing a url to a .zip file:
// https://github.com/Squirrel/Squirrel.Mac#server-support
$clientAppVersion = $_GET["appversion"] ?? null;
if (!isValidVersionString($clientAppVersion)) {
http_response_code(204);
exit();
}
$clientCpuArchitecture = $_GET["cpuarchitecture"] ?? null;
$latestVersionInfo = getLatestVersionInfo($clientAppVersion, $clientCpuArchitecture);
if (!isset($latestVersionInfo["versionNumber"])) {
http_response_code(204);
exit();
}
// Real logic starts here when basics did not fail
$isUpdateVailable = isUpdateAvailable($clientAppVersion, $latestVersionInfo["versionNumber"]);
if ($isUpdateVailable) {
http_response_code(200);
header('Content-Type: application/json;charset=utf-8');
$jsonResponse = array(
"url" => $latestVersionInfo["directZipFileURL"],
"name" => $latestVersionInfo["versionNumber"],
"pub_date" => date('c', $latestVersionInfo["createdAtUnixTimeStamp"]),
);
echo json_encode($jsonResponse);
} else {
//no update: must respond with a status code of 204 No Content.
http_response_code(204);
}
exit();
// End of execution.
// Everything bellow here are function declarations.
function getLatestVersionInfo($clientAppVersion, $clientCpuArchitecture): array {
// override path if client requests an arm64 build
if ($clientCpuArchitecture === 'arm64') {
$directory = "../../updates/darwin/arm64/";
$baseUrl = "https://my-server.com/updates/darwin/arm64/";
} else if (!$clientCpuArchitecture || $clientCpuArchitecture === 'x64') {
$directory = "../../updates/darwin/";
$baseUrl = "https://my-server.com/updates/darwin/";
}
// default name with version 0.0.0 avoids failing
$latestVersionFileName = "Finance D - Tenue de livres-darwin-x64-0.0.0.zip";
$arrayOfFiles = scandir($directory);
foreach ($arrayOfFiles as $file) {
if (is_file($directory . $file)) {
$serverFileVersion = getVersionNumberFromFileName($file);
if (isVersionNumberGreater($serverFileVersion, $clientAppVersion)) {
$latestVersionFileName = $file;
}
}
}
return array(
"versionNumber" => getVersionNumberFromFileName($latestVersionFileName),
"directZipFileURL" => $baseUrl . rawurlencode($latestVersionFileName),
"createdAtUnixTimeStamp" => filemtime(realpath($directory . $latestVersionFileName))
);
}
function isUpdateAvailable($clientVersion, $serverVersion): bool {
return
isValidVersionString($clientVersion) &&
isValidVersionString($serverVersion) &&
isVersionNumberGreater($serverVersion, $clientVersion);
}
function getVersionNumberFromFileName($fileName) {
// extract the version number with regEx replacement
return preg_replace("/Finance D - Tenue de livres-darwin-(x64|arm64)-|\.zip/", "", $fileName);
}
function removeAllNonDigits($semanticVersionString) {
// use regex replacement to keep only numeric values in the semantic version string
return preg_replace("/\D+/", "", $semanticVersionString);
}
function isVersionNumberGreater($serverFileVersion, $clientFileVersion): bool {
// receives two semantic versions (1.0.4) and compares their numeric value (104)
// true when server version is greater than client version (105 > 104)
return removeAllNonDigits($serverFileVersion) > removeAllNonDigits($clientFileVersion);
}
function isValidVersionString($versionString) {
// true when matches semantic version numbering: 0.0.0
return preg_match("/\d\.\d\.\d/", $versionString);
}

DomainProjectPicker class is obsolete in VSTS 2010?

What is the alternative for DomainProjectPicker if I want to select a server plus its projects? I am aware of a new class called TeamProjectPicker, but that doesn't help me. Anyone know how to select the server from this type of dialog?
Thanks,TS.
As far as I can figure it out it's more or less the same as the DomainProjectPicker.
Here's a code sample of how I was working with it:
if (tpp.ShowDialog() == DialogResult.OK)
{
try
{
//here you get the TfsTeamProjectCollection (the TeamFoundationServer class is also obsolete)
TfsTeamProjectCollection tfsProj = tpp.SelectedTeamProjectCollection;
//here you authenticate
tfsProj.Authenticate();
}
etc...
You can use the TeamProjectPicker class from Microsoft.TeamFoundation.Client.dll. There is a great blog post that describes how to wrangle the dialog: Using the TeamProjectPicker API in TFS 2010
Here's the code sample for selecting multiple team projects:
Application.EnableVisualStyles(); // Makes it look nicer from a console app.
//"using" pattern is recommended as the picker needs to be disposed of
using (TeamProjectPicker tpp = new TeamProjectPicker(TeamProjectPickerMode.MultiProject, false))
{
DialogResult result = tpp.ShowDialog();
if (result == DialogResult.OK)
{
System.Console.WriteLine("Selected Team Project Collection Uri: " + tpp.SelectedTeamProjectCollection.Uri);
System.Console.WriteLine("Selected Projects:");
foreach(ProjectInfo projectInfo in tpp.SelectedProjects)
{
System.Console.WriteLine(projectInfo.Name);
}
}
}
If you don't care about the project and only want the user to be able to select a server and collection, use TeamProjectPickerMode.NoProject in the constructor.

Resources