diff --git a/.gitignore b/.gitignore index f5581bd..56f0c3c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ +tools/ # Visual Studio 2015 cache/options directory .vs/ diff --git a/README.md b/README.md index cc2f816..abcf45e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # Random Quotes Fun random quotes web app. App is built using Microsoft's ASP.NET core framework and it's very simple. + +## Build + +There is a Cake script for building the Random Quotes application in the root folder. + +The Octo .NET Core CLI extension must be installed prior to running the build, using the command line + +```shell +dotnet tool install Octopus.DotNet.Cli --tool-path .\tools\ +``` + +Running locally will place the resulting Zip package into a folder called `LocalPackages`, as a sibling to the repo's folder. \ No newline at end of file diff --git a/RandomQuotes/AppSettings.cs b/RandomQuotes/AppSettings.cs index 6667074..4b4034d 100644 --- a/RandomQuotes/AppSettings.cs +++ b/RandomQuotes/AppSettings.cs @@ -4,5 +4,13 @@ public class AppSettings { public string AppVersion { get; set; } public string EnvironmentName { get; set; } + public string TenantName { get; set; } + public QuoteRecord[] CustomQuotes { get; set; } + } + + public class QuoteRecord + { + public string Quote { get; set; } + public string Author { get; set; } } } diff --git a/RandomQuotes/Startup.cs b/RandomQuotes/Startup.cs index 70788a0..c0d9b5b 100644 --- a/RandomQuotes/Startup.cs +++ b/RandomQuotes/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using RandomQuotes.Models; namespace RandomQuotes @@ -53,10 +54,21 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); - var quoteFilePath = Path.Combine(Directory.GetCurrentDirectory(), $"wwwroot{Path.DirectorySeparatorChar}data{Path.DirectorySeparatorChar}quotes.txt"); - Quote.Quotes = File.Exists(quoteFilePath) ? File.ReadAllLines(quoteFilePath).Select(System.Net.WebUtility.HtmlDecode).ToList() : new List(); - var authorFilePath = Path.Combine(Directory.GetCurrentDirectory(), $"wwwroot{Path.DirectorySeparatorChar}data{Path.DirectorySeparatorChar}authors.txt"); - Quote.Authors = File.Exists(authorFilePath) ? File.ReadAllLines(authorFilePath).Select(System.Net.WebUtility.HtmlDecode).ToList() : new List(); + var service = app.ApplicationServices.GetService(typeof(IOptions)); + var settings = ((IOptions)service).Value; + + if (settings.CustomQuotes != null && settings.CustomQuotes.Any()) + { + Quote.Quotes = settings.CustomQuotes.Select(x => x.Quote).ToList(); + Quote.Authors = settings.CustomQuotes.Select(x => x.Author).ToList(); + } + else + { + var quoteFilePath = Path.Combine(Directory.GetCurrentDirectory(), $"wwwroot{Path.DirectorySeparatorChar}data{Path.DirectorySeparatorChar}quotes.txt"); + Quote.Quotes = File.Exists(quoteFilePath) ? File.ReadAllLines(quoteFilePath).Select(System.Net.WebUtility.HtmlDecode).ToList() : new List(); + var authorFilePath = Path.Combine(Directory.GetCurrentDirectory(), $"wwwroot{Path.DirectorySeparatorChar}data{Path.DirectorySeparatorChar}authors.txt"); + Quote.Authors = File.Exists(authorFilePath) ? File.ReadAllLines(authorFilePath).Select(System.Net.WebUtility.HtmlDecode).ToList() : new List(); + } } } } \ No newline at end of file diff --git a/RandomQuotes/Views/Shared/_Layout.cshtml b/RandomQuotes/Views/Shared/_Layout.cshtml index 08985c3..6530f99 100644 --- a/RandomQuotes/Views/Shared/_Layout.cshtml +++ b/RandomQuotes/Views/Shared/_Layout.cshtml @@ -1,12 +1,19 @@ @using Microsoft.Extensions.Options; @inject IOptions Settings +@{ + var title = "Random Quotes"; + if (!string.IsNullOrWhiteSpace(Settings.Value.TenantName)) + { + title += $" - {Settings.Value.TenantName}"; + } +} - @ViewData["Title"] - Random Quotes + @title @@ -30,7 +37,7 @@ - Random Quotes + @title @@ -39,7 +46,7 @@ @RenderBody()
diff --git a/RandomQuotes/appsettings.json b/RandomQuotes/appsettings.json index 4bbfe1c..e2c18bc 100644 --- a/RandomQuotes/appsettings.json +++ b/RandomQuotes/appsettings.json @@ -7,6 +7,7 @@ }, "AppSettings": { "AppVersion": "0.0.0", - "EnvironmentName": "DEV" + "EnvironmentName": "DEV", + "CustomQuotes": [] } } diff --git a/build.cake b/build.cake new file mode 100644 index 0000000..abe4f50 --- /dev/null +++ b/build.cake @@ -0,0 +1,118 @@ +#tool "nuget:?package=GitVersion.CommandLine&prerelease" + +using Path = System.IO.Path; +using IO = System.IO; +using Cake.Common.Tools; + +var target = Argument("target", "Default"); +var configuration = Argument("configuration", "Release"); +var octopusServer = Argument("octopusServer", ""); +var octopusApikey = Argument("octopusApiKey", ""); + +var isLocalBuild = string.IsNullOrWhiteSpace(octopusServer); + +var packageId = "RandomQuotes"; + +var publishDir = "./publish"; +var artifactsDir = "./artifacts"; +var localPackagesDir = "../LocalPackages"; + +var gitVersionInfo = GitVersion(new GitVersionSettings { + OutputType = GitVersionOutput.Json +}); + +var nugetVersion = gitVersionInfo.NuGetVersion; + +Setup(context => +{ + if(BuildSystem.IsRunningOnTeamCity) + BuildSystem.TeamCity.SetBuildNumber(gitVersionInfo.NuGetVersion); + + Information("Building v{0}", nugetVersion); +}); + +Teardown(context => +{ + Information("Finished running tasks."); +}); + +Task("__Default") + .IsDependentOn("__Clean") + .IsDependentOn("__Restore") + .IsDependentOn("__Build") + .IsDependentOn("__Publish") + .IsDependentOn("__Pack") + .IsDependentOn("__Push"); + +Task("__Clean") + .Does(() => +{ + CleanDirectory(artifactsDir); + CleanDirectory(publishDir); + CleanDirectories("./**/bin"); + CleanDirectories("./**/obj"); +}); + +Task("__Restore") + .Does(() => DotNetCoreRestore(".", new DotNetCoreRestoreSettings + { + ArgumentCustomization = args => args.Append($"/p:Version={nugetVersion}") + }) +); + +Task("__Build") + .Does(() => +{ + DotNetCoreBuild(".", new DotNetCoreBuildSettings + { + Configuration = configuration, + ArgumentCustomization = args => args.Append($"/p:Version={nugetVersion}") + }); +}); + +Task("__Publish") + .Does(() => +{ + DotNetCorePublish(".", new DotNetCorePublishSettings + { + Framework = "netcoreapp2.0", + Configuration = configuration, + OutputDirectory = publishDir, + ArgumentCustomization = args => args.Append($"--no-build") + }); +}); + +Task("__Pack") + .Does(() => { + + OctoPack(packageId, new OctopusPackSettings { + BasePath = publishDir, + Format = OctopusPackFormat.Zip, + Version = nugetVersion, + OutFolder = artifactsDir, + ToolPath = IsRunningOnUnix() ? Context.Tools.Resolve("dotnet-octo") : Context.Tools.Resolve("dotnet-octo.exe") + }); +}); + +Task("__Push") + .Does(() => { + + var packageFile = $"{artifactsDir}\\{packageId}.{nugetVersion}.zip"; + + if (!isLocalBuild) + { + OctoPush(octopusServer, octopusApikey, packageFile, new OctopusPushSettings { + ToolPath = IsRunningOnUnix() ? Context.Tools.Resolve("dotnet-octo") : Context.Tools.Resolve("dotnet-octo.exe") + }); + } + else + { + CreateDirectory(localPackagesDir); + CopyFileToDirectory(packageFile, localPackagesDir); + } +}); + +Task("Default") + .IsDependentOn("__Default"); + +RunTarget(target); diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..6e41618 --- /dev/null +++ b/build.cmd @@ -0,0 +1,24 @@ +@ECHO OFF +REM see http://joshua.poehls.me/powershell-batch-file-wrapper/ + +SET SCRIPTNAME=%~d0%~p0%~n0.ps1 +SET ARGS=%* +IF [%ARGS%] NEQ [] GOTO ESCAPE_ARGS + +:POWERSHELL +PowerShell.exe -NoProfile -NonInteractive -NoLogo -ExecutionPolicy Unrestricted -Command "& { $ErrorActionPreference = 'Stop'; & '%SCRIPTNAME%' @args; EXIT $LASTEXITCODE }" %ARGS% +EXIT /B %ERRORLEVEL% + +:ESCAPE_ARGS +SET ARGS=%ARGS:"=\"% +SET ARGS=%ARGS:`=``% +SET ARGS=%ARGS:'=`'% +SET ARGS=%ARGS:$=`$% +SET ARGS=%ARGS:{=`}% +SET ARGS=%ARGS:}=`}% +SET ARGS=%ARGS:(=`(% +SET ARGS=%ARGS:)=`)% +SET ARGS=%ARGS:,=`,% +SET ARGS=%ARGS:^%=% + +GOTO POWERSHELL \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..40a1956 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,189 @@ +########################################################################## +# This is the Cake bootstrapper script for PowerShell. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER Experimental +Tells Cake to use the latest Roslyn release. +.PARAMETER WhatIf +Performs a dry run of the build script. +No tasks will be executed. +.PARAMETER Mono +Tells Cake to use the Mono scripting engine. +.PARAMETER SkipToolPackageRestore +Skips restoring of packages. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +http://cakebuild.net + +#> + +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target = "Default", + [ValidateSet("Release", "Debug")] + [string]$Configuration = "Release", + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity = "Verbose", + [switch]$Experimental = $true, + [Alias("DryRun","Noop")] + [switch]$WhatIf, + [switch]$Mono, + [switch]$SkipToolPackageRestore, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null +function MD5HashFile([string] $filePath) +{ + if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) + { + return $null + } + + [System.IO.Stream] $file = $null; + [System.Security.Cryptography.MD5] $md5 = $null; + try + { + $md5 = [System.Security.Cryptography.MD5]::Create() + $file = [System.IO.File]::OpenRead($filePath) + return [System.BitConverter]::ToString($md5.ComputeHash($file)) + } + finally + { + if ($file -ne $null) + { + $file.Dispose() + } + } +} + +Write-Host "Preparing to run build script..." + +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" +$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" +$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" + +# Should we use mono? +$UseMono = ""; +if($Mono.IsPresent) { + Write-Verbose -Message "Using the Mono based scripting engine." + $UseMono = "-mono" +} + +# Should we use the new Roslyn? +$UseExperimental = ""; +if($Experimental.IsPresent -and !($Mono.IsPresent)) { + Write-Verbose -Message "Using experimental version of Roslyn." + $UseExperimental = "-experimental" +} + +# Is this a dry run? +$UseDryRun = ""; +if($WhatIf.IsPresent) { + $UseDryRun = "-dryrun" +} + +# Make sure tools folder exists +if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { + Write-Verbose -Message "Creating tools directory..." + New-Item -Path $TOOLS_DIR -Type directory | out-null +} + +# Make sure that packages.config exist. +if (!(Test-Path $PACKAGES_CONFIG)) { + Write-Verbose -Message "Downloading packages.config..." + try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { + Throw "Could not download packages.config." + } +} + +# Try find NuGet.exe in path if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Trying to find nuget.exe in PATH..." + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } + $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 + if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { + Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." + $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName + } +} + +# Try download NuGet.exe if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Downloading NuGet.exe..." + try { + (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) + } catch { + Throw "Could not download NuGet.exe." + } +} + +# Save nuget.exe path to environment to be available to child processed +$ENV:NUGET_EXE = $NUGET_EXE + +# Restore tools from NuGet? +if(-Not $SkipToolPackageRestore.IsPresent) { + Push-Location + Set-Location $TOOLS_DIR + + # Check for changes in packages.config and remove installed tools if true. + [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) + if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or + ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { + Write-Verbose -Message "Missing or changed package.config hash..." + Remove-Item * -Recurse -Exclude packages.config,nuget.exe + } + + Write-Verbose -Message "Restoring tools from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet tools." + } + else + { + $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" + } + Write-Verbose -Message ($NuGetOutput | out-string) + Pop-Location +} + +# Make sure that Cake has been installed. +if (!(Test-Path $CAKE_EXE)) { + Throw "Could not find Cake.exe at $CAKE_EXE" +} + +# Start Cake +Write-Host "Running build script..." +Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" +exit $LASTEXITCODE \ No newline at end of file diff --git a/gitversion.yml b/gitversion.yml new file mode 100644 index 0000000..c1a0dc7 --- /dev/null +++ b/gitversion.yml @@ -0,0 +1 @@ +mode: ContinuousDeployment \ No newline at end of file