├── ServiceDemo ├── .nuke ├── src │ └── ServiceDemo.Api │ │ ├── Scripts │ │ └── -.- │ │ ├── appsettings.json │ │ ├── WeatherForecast.cs │ │ ├── ServiceDemo.Api.csproj │ │ ├── appsettings.Development.LinuxOsx.json │ │ ├── appsettings.Development.Windows.json │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── Controllers │ │ └── WeatherForecastController.cs │ │ ├── Startup.cs │ │ └── Program.cs ├── terraform │ ├── provider.tf │ ├── data.tf │ ├── backend.tf │ ├── variables.tf │ └── main.tf ├── build.cmd ├── .gitignore ├── tests │ └── ServiceDemo.Api.Tests │ │ ├── ThisTestShould.cs │ │ └── ServiceDemo.Api.Tests.csproj ├── build │ ├── .editorconfig │ ├── _build.csproj │ ├── _build.csproj.DotSettings │ └── Build.cs ├── .github │ └── workflows │ │ └── ServiceDemoPipeline.yml ├── GitVersion.yml ├── initializeGitRepo.sh ├── docs │ └── 0000-template.md ├── build.sh ├── .template.config │ └── template.json ├── build.ps1 └── ServiceDemo.sln ├── .gitignore ├── GitVersion.yml ├── packageSettings.csproj ├── .github └── workflows │ └── PackageDotnetTemplate.yml └── README.md /ServiceDemo/.nuke: -------------------------------------------------------------------------------- 1 | ServiceDemo.sln -------------------------------------------------------------------------------- /ServiceDemo/src/ServiceDemo.Api/Scripts/-.-: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ServiceDemo/terraform/provider.tf: -------------------------------------------------------------------------------- 1 | # Configure the Azure Provider 2 | provider "azurerm" { 3 | version = "~>2.0" 4 | 5 | features {} 6 | } -------------------------------------------------------------------------------- /ServiceDemo/build.cmd: -------------------------------------------------------------------------------- 1 | :; set -eo pipefail 2 | :; ./build.sh "$@" 3 | :; exit $? 4 | 5 | @ECHO OFF 6 | powershell -ExecutionPolicy ByPass -NoProfile %0\..\build.ps1 %* 7 | -------------------------------------------------------------------------------- /ServiceDemo/terraform/data.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_app_service_plan" "plan" { 2 | name = "TemplateDemo-ASP" 3 | resource_group_name = "TemplateDemoRG" 4 | } 5 | 6 | data "azurerm_subscription" "sub" { 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | .nuget/ 4 | _ReSharper.* 5 | packages/ 6 | artifacts/ 7 | *.user 8 | *.suo 9 | *.userprefs 10 | *DS_Store 11 | *.sln.ide 12 | .vs/ 13 | .vscode/ 14 | .tmp/ 15 | .idea/ 16 | .terraform/ -------------------------------------------------------------------------------- /ServiceDemo/.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | .nuget/ 4 | _ReSharper.* 5 | packages/ 6 | artifacts/ 7 | *.user 8 | *.suo 9 | *.userprefs 10 | *DS_Store 11 | *.sln.ide 12 | .vs/ 13 | .vscode/ 14 | .tmp/ 15 | .idea/ 16 | .terraform/ -------------------------------------------------------------------------------- /ServiceDemo/terraform/backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "azurerm" { 3 | resource_group_name = "TemplateDemoRG" 4 | storage_account_name = "templatedemostate" 5 | container_name = "states" 6 | key = "servicedemo.tfstate" 7 | } 8 | } -------------------------------------------------------------------------------- /ServiceDemo/tests/ServiceDemo.Api.Tests/ThisTestShould.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace ServiceDemo.Api.Tests 4 | { 5 | [TestFixture] 6 | public class ThisTestShould 7 | { 8 | [Test] 9 | public void AlwaysPass() 10 | { 11 | Assert.That(1, Is.EqualTo(1)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ServiceDemo/src/ServiceDemo.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Debug", 5 | "Override": { 6 | "Microsoft": "Error", 7 | "System": "Error" 8 | } 9 | }, 10 | "Properties": { 11 | "ApplicationName": "ServiceDemo.Api" 12 | } 13 | }, 14 | "AllowedHosts": "*" 15 | } 16 | -------------------------------------------------------------------------------- /ServiceDemo/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | // ***** Default Variables ***** 2 | 3 | variable "prefix" { 4 | description = "Custom prefix for the application services" 5 | type = string 6 | default = "service" 7 | } 8 | 9 | variable "location" { 10 | description = "The Azure Region in which the resource will be created." 11 | type = string 12 | default = "East US" 13 | } -------------------------------------------------------------------------------- /ServiceDemo/src/ServiceDemo.Api/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ServiceDemo.Api 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ServiceDemo/build/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_style_qualification_for_field = false:warning 3 | dotnet_style_qualification_for_property = false:warning 4 | dotnet_style_qualification_for_method = false:warning 5 | dotnet_style_qualification_for_event = false:warning 6 | dotnet_style_require_accessibility_modifiers = never:warning 7 | 8 | csharp_style_expression_bodied_methods = true:silent 9 | csharp_style_expression_bodied_properties = true:warning 10 | csharp_style_expression_bodied_indexers = true:warning 11 | csharp_style_expression_bodied_accessors = true:warning 12 | -------------------------------------------------------------------------------- /ServiceDemo/tests/ServiceDemo.Api.Tests/ServiceDemo.Api.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ServiceDemo/build/_build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.0 6 | 7 | CS0649;CS0169 8 | .. 9 | .. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ServiceDemo/src/ServiceDemo.Api/ServiceDemo.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ServiceDemo/src/ServiceDemo.Api/appsettings.Development.LinuxOsx.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Debug", 5 | "Override": { 6 | "Microsoft": "Error", 7 | "System": "Error" 8 | } 9 | }, 10 | "WriteTo": [ 11 | { 12 | "Name": "File", 13 | "Args": { 14 | "path": "/var/log/ServiceDemo", 15 | "rollingInterval": "Day", 16 | "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}) {Message}{NewLine}{Exception}" 17 | } 18 | }, 19 | { 20 | "Name": "Console", 21 | "Args": { 22 | "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}) {Message}{NewLine}{Exception}" 23 | } 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /ServiceDemo/src/ServiceDemo.Api/appsettings.Development.Windows.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Debug", 5 | "Override": { 6 | "Microsoft": "Error", 7 | "System": "Error" 8 | } 9 | }, 10 | "WriteTo": [ 11 | { 12 | "Name": "File", 13 | "Args": { 14 | "path": "c:\\logs\\ServiceDemo\\log.txt", 15 | "rollingInterval": "Day", 16 | "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}) {Message}{NewLine}{Exception}" 17 | } 18 | }, 19 | { 20 | "Name": "Console", 21 | "Args": { 22 | "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}) {Message}{NewLine}{Exception}" 23 | } 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /ServiceDemo/.github/workflows/ServiceDemoPipeline.yml: -------------------------------------------------------------------------------- 1 | name: ServiceDemo Pipeline 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Fetch all history for all tags and branches 17 | run: git fetch --prune --unshallow 18 | - name: Nuke restore + build + test + publish 19 | run: ./build.sh --configuration Release -target compile+test+publish 20 | - name: Run Azure webapp deploy action using publish profile credentials 21 | uses: azure/webapps-deploy@v2 22 | with: 23 | app-name: service-servicedemo-api 24 | publish-profile: ${{ secrets.azureWebAppPublishProfile }} 25 | package: './artifacts/ServiceDemo.Api' -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: Mainline 2 | branches: 3 | main: 4 | tag: '' 5 | increment: Patch 6 | prevent-increment-of-merged-branch-version: true 7 | track-merge-target: false 8 | regex: ^main$ 9 | source-branches: 10 | - develop 11 | - release 12 | tracks-release-branches: false 13 | is-release-branch: false 14 | is-mainline: true 15 | pre-release-weight: 55000 16 | release: 17 | source-branches: 18 | - develop 19 | - main 20 | - support 21 | - release 22 | feature: 23 | source-branches: 24 | - develop 25 | - main 26 | - release 27 | - feature 28 | - support 29 | - hotfix 30 | pull-request: 31 | source-branches: 32 | - develop 33 | - main 34 | - release 35 | - feature 36 | - support 37 | - hotfix 38 | hotfix: 39 | source-branches: 40 | - develop 41 | - main 42 | - support 43 | support: 44 | source-branches: 45 | - main -------------------------------------------------------------------------------- /ServiceDemo/GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: Mainline 2 | branches: 3 | main: 4 | tag: '' 5 | increment: Patch 6 | prevent-increment-of-merged-branch-version: true 7 | track-merge-target: false 8 | regex: ^main$ 9 | source-branches: 10 | - develop 11 | - release 12 | tracks-release-branches: false 13 | is-release-branch: false 14 | is-mainline: true 15 | pre-release-weight: 55000 16 | release: 17 | source-branches: 18 | - develop 19 | - main 20 | - support 21 | - release 22 | feature: 23 | source-branches: 24 | - develop 25 | - main 26 | - release 27 | - feature 28 | - support 29 | - hotfix 30 | pull-request: 31 | source-branches: 32 | - develop 33 | - main 34 | - release 35 | - feature 36 | - support 37 | - hotfix 38 | hotfix: 39 | source-branches: 40 | - develop 41 | - main 42 | - support 43 | support: 44 | source-branches: 45 | - main -------------------------------------------------------------------------------- /ServiceDemo/src/ServiceDemo.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:9981", 8 | "sslPort": 44312 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "weatherforecast", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "ServiceDemo.Api": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "weatherforecast", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packageSettings.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Template 5 | Dotnet.Template.Demo 6 | Dotnet.Template.Demo 7 | Olga Nelioubov 8 | Responsible for packaging the Dotnet Demo Template 9 | dotnet-new;template;buffalowebdevdemo 10 | netcoreapp3.1 11 | 12 | true 13 | false 14 | content 15 | true 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ServiceDemo/initializeGitRepo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Creating new local repository..." 4 | if ! git status > /dev/null 2>&1 5 | then 6 | git init 7 | else 8 | echo "Local repository already exists." 9 | fi 10 | 11 | echo -e "\nCreating [main] branch..." 12 | if git checkout -b main 13 | then 14 | echo -e "\nMaking initial commit..." 15 | git add . 16 | git commit -m "initial commit" 17 | git branch -M main 18 | else 19 | echo "Please see the following log for commit details." 20 | git log 21 | fi 22 | 23 | echo -e "\nSetting up remote repo..." 24 | if gh help > /dev/null 2>&1 25 | then 26 | tryCreateRemote=$(gh repo create ServiceDemo --public --confirm 2>/dev/null) 27 | if [ $? -eq 0 ] 28 | then 29 | echo "Created new repository $tryCreateRemote and added as remote." 30 | echo "If you're deploying to cloud resources, make sure to set those resources up before you push [main] to [origin]." 31 | else 32 | echo "Remote repository already exists." 33 | fi 34 | else 35 | echo "Please install GitHub CLI. For more info, visit https://cli.github.com/." 36 | fi -------------------------------------------------------------------------------- /.github/workflows/PackageDotnetTemplate.yml: -------------------------------------------------------------------------------- 1 | name: Template Packaging Pipeline 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | publish: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Fetch all history for all tags and branches 17 | run: git fetch --prune --unshallow 18 | - name: Setup .NET Core 19 | uses: actions/setup-dotnet@v1 20 | with: 21 | dotnet-version: 3.1.101 22 | - name: Install GitVersion 23 | uses: gittools/actions/gitversion/setup@v0.9.2 24 | with: 25 | versionSpec: '5.2.x' 26 | - name: Use GitVersion 27 | id: gitversion 28 | uses: gittools/actions/gitversion/execute@v0.9.2 29 | - name: Pack the template 30 | run: dotnet pack ./packageSettings.csproj -o ./artifacts/ /p:PackageVersion=${{ steps.gitversion.outputs.nuGetVersionV2 }} /p:RepositoryUrl=https://github.com/onelioubov/DotnetTemplateDemo 31 | - name: Push package to Artifactory 32 | run: dotnet nuget push ./artifacts/*.nupkg -k ${{ secrets.Artifactory }} -s https://onelioubov.jfrog.io/artifactory/api/nuget/v3/nuget-local --no-symbols true -------------------------------------------------------------------------------- /ServiceDemo/docs/0000-template.md: -------------------------------------------------------------------------------- 1 | # 0000. Template 2 | 3 | Date: [yyyy-MM-dd] 4 | Modified: [yyyy-MM-dd] 5 | 6 | ## Status 7 | 8 | [Proposed, Accepted, Superseded by 0000] 9 | 10 | [A decision may be "proposed" if the project stakeholders haven't agreed with it yet, or "accepted" once it is agreed. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement.] 11 | 12 | ## Context 13 | 14 | [This section describes the forces at play, including technological, political, social, and project local. These forces are probably in tension, and should be called out as such. The language in this section is value-neutral. It is simply describing facts.] 15 | 16 | ## Decision 17 | 18 | [This section describes our response to these forces. It is stated in full sentences, with active voice. "We will ..."] 19 | 20 | ## Consequences 21 | 22 | [This section describes the resulting context, after applying the decision. All consequences should be listed here, not just the "positive" ones. A particular decision may have positive, negative, and neutral consequences, but all of them affect the team and project in the future.] 23 | 24 | [More Info: [http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions)] -------------------------------------------------------------------------------- /ServiceDemo/src/ServiceDemo.Api/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace ServiceDemo.Api.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private static readonly string[] Summaries = new[] 15 | { 16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 17 | }; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public WeatherForecastController(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | [HttpGet] 27 | public IEnumerable Get() 28 | { 29 | var rng = new Random(); 30 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 31 | { 32 | Date = DateTime.Now.AddDays(index), 33 | TemperatureC = rng.Next(-20, 55), 34 | Summary = Summaries[rng.Next(Summaries.Length)] 35 | }) 36 | .ToArray(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ServiceDemo/src/ServiceDemo.Api/Startup.cs: -------------------------------------------------------------------------------- 1 | using HealthChecks.UI.Client; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Diagnostics.HealthChecks; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | 9 | namespace ServiceDemo.Api 10 | { 11 | public class Startup 12 | { 13 | private readonly IConfiguration _configuration; 14 | public Startup(IConfiguration configuration) 15 | { 16 | _configuration = configuration; 17 | } 18 | 19 | // This method gets called by the runtime. Use this method to add services to the container. 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddApplicationInsightsTelemetry(); 23 | services.AddControllers(); 24 | services.AddHealthChecks(); 25 | 26 | // Register Swagger services 27 | services.AddSwaggerDocument(); 28 | } 29 | 30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 32 | { 33 | if (env.IsDevelopment()) 34 | { 35 | app.UseDeveloperExceptionPage(); 36 | } 37 | 38 | app.UseHttpsRedirection(); 39 | 40 | app.UseRouting(); 41 | 42 | app.UseAuthorization(); 43 | 44 | // Register Swagger generator and UI middleware 45 | app.UseOpenApi(); 46 | app.UseSwaggerUi3(); 47 | 48 | app.UseEndpoints(endpoints => 49 | { 50 | endpoints.MapControllers(); 51 | endpoints.MapHealthChecks("/api/healthcheck", new HealthCheckOptions() 52 | { 53 | ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse 54 | }); 55 | }); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ServiceDemo/terraform/main.tf: -------------------------------------------------------------------------------- 1 | # Local variables for resource tagging 2 | locals { 3 | common_tags = { 4 | "Created On" = formatdate("DD MMM YYYY hh:mm ZZZ",timestamp()) 5 | } 6 | } 7 | 8 | resource "azurerm_resource_group" "servicedemorg" { 9 | name = "ServiceDemoRG" 10 | location = var.location 11 | } 12 | 13 | # create application insights for servicedemo_api 14 | resource "azurerm_application_insights" "servicedemo_api_insights" { 15 | name = "${var.prefix}-servicedemo-api" 16 | location = var.location 17 | resource_group_name = azurerm_resource_group.servicedemorg.name 18 | application_type = "web" 19 | retention_in_days = 90 20 | } 21 | 22 | # create resource for servicedemo_api 23 | resource "azurerm_app_service" "servicedemo_api" { 24 | name = "${var.prefix}-servicedemo-api" 25 | location = var.location 26 | resource_group_name = azurerm_resource_group.servicedemorg.name 27 | app_service_plan_id = data.azurerm_app_service_plan.plan.id 28 | 29 | client_affinity_enabled = false 30 | 31 | site_config { 32 | default_documents = ["index.htm", "index.html", "hostingstart.html", "Default.htm", "Default.html"] 33 | health_check_path = "/api/healthcheck" 34 | linux_fx_version = "DOTNETCORE|3.1" 35 | } 36 | 37 | app_settings = { 38 | APPINSIGHTS_INSTRUMENTATIONKEY = join("", azurerm_application_insights.servicedemo_api_insights.*.instrumentation_key) 39 | WEBSITE_RUN_FROM_PACKAGE = "1" 40 | SCM_DO_BUILD_DURING_DEPLOYMENT = false 41 | ASPNETCORE_ENVIRONMENT = "Production" 42 | } 43 | 44 | lifecycle { 45 | ignore_changes = [tags] 46 | } 47 | tags = local.common_tags 48 | 49 | provisioner "local-exec" { 50 | command = "printf \"\\n\\nAdd the following publish profile as a new secret called azureWebAppPublishProfile in the GitHub repo prior to deploying to the new Azure Web App:\\n\\n$(az webapp deployment list-publishing-profiles --name ${azurerm_app_service.servicedemo_api.name} --resource-group ${azurerm_resource_group.servicedemorg.name} --subscription \"${data.azurerm_subscription.sub.display_name}\" --xml | sed 's/\\\\//g' | sed 's/^.\\(.*\\).$/\\1/')\\n\\n\"" 51 | } 52 | } -------------------------------------------------------------------------------- /ServiceDemo/src/ServiceDemo.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.Hosting; 9 | using Serilog; 10 | 11 | namespace ServiceDemo.Api 12 | { 13 | public class Program 14 | { 15 | private static IConfiguration _configuration; 16 | public static void Main(string[] args) 17 | { 18 | try 19 | { 20 | var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); 21 | var platform = env.Equals("Development") ? 22 | (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 23 | ".Windows" : ".LinuxOsx") 24 | : ""; 25 | _configuration = new ConfigurationBuilder() 26 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 27 | .AddJsonFile( 28 | $"appsettings.{env ?? "Production"}{platform}.json", 29 | optional: true, reloadOnChange: true) 30 | .AddEnvironmentVariables() 31 | .AddCommandLine(args) 32 | .Build(); 33 | 34 | Log.Logger = new LoggerConfiguration() 35 | .ReadFrom.Configuration(_configuration) 36 | .CreateLogger() 37 | .ForContext(); 38 | 39 | Log.Information("Starting web host"); 40 | 41 | CreateHostBuilder(args).Build().Run(); 42 | } 43 | catch (Exception exception) 44 | { 45 | Log.Fatal(exception, "Site terminated"); 46 | } 47 | finally 48 | { 49 | Log.Information("Ending web host"); 50 | 51 | Log.CloseAndFlush(); 52 | } 53 | } 54 | 55 | public static IHostBuilder CreateHostBuilder(string[] args) => 56 | Host.CreateDefaultBuilder(args) 57 | .ConfigureWebHostDefaults(webBuilder => 58 | { 59 | webBuilder 60 | .UseConfiguration(_configuration) 61 | .UseSerilog() 62 | .UseStartup(); 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ServiceDemo/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo $(bash --version 2>&1 | head -n 1) 4 | 5 | set -eo pipefail 6 | SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 7 | 8 | ########################################################################### 9 | # CONFIGURATION 10 | ########################################################################### 11 | 12 | BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj" 13 | TEMP_DIRECTORY="$SCRIPT_DIR//.tmp" 14 | 15 | DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" 16 | DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh" 17 | DOTNET_CHANNEL="Current" 18 | 19 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 20 | export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 21 | 22 | ########################################################################### 23 | # EXECUTION 24 | ########################################################################### 25 | 26 | function FirstJsonValue { 27 | perl -nle 'print $1 if m{"'$1'": "([^"]+)",?}' <<< ${@:2} 28 | } 29 | 30 | # If global.json exists, load expected version 31 | if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then 32 | DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE")) 33 | if [[ "$DOTNET_VERSION" == "" ]]; then 34 | unset DOTNET_VERSION 35 | fi 36 | fi 37 | 38 | # If dotnet is installed locally, and expected version is not set or installation matches the expected version 39 | if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version 2>&1) == "$DOTNET_VERSION") ]]; then 40 | export DOTNET_EXE="$(command -v dotnet)" 41 | else 42 | DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" 43 | export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" 44 | 45 | # Download install script 46 | DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" 47 | mkdir -p "$TEMP_DIRECTORY" 48 | curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" 49 | chmod +x "$DOTNET_INSTALL_FILE" 50 | 51 | # Install by channel or version 52 | if [[ -z ${DOTNET_VERSION+x} ]]; then 53 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path 54 | else 55 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path 56 | fi 57 | fi 58 | 59 | echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" 60 | 61 | "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false -nologo -clp:NoSummary --verbosity quiet 62 | "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" 63 | -------------------------------------------------------------------------------- /ServiceDemo/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "Olga Nelioubov", 4 | "classifications": [ 5 | "Api", 6 | "Code", 7 | "Reference" 8 | ], 9 | "identity": "Dotnet.Template.Demo", 10 | "name": "Demo Template", 11 | "shortName": "demo-template", 12 | "tags": { 13 | "language": "C#", 14 | "type": "item" 15 | }, 16 | "sourceName": "ServiceDemo", 17 | "symbols": { 18 | "lowerCaseServiceName": { 19 | "type": "generated", 20 | "generator": "casing", 21 | "parameters": { 22 | "source": "name", 23 | "toLower": true 24 | }, 25 | "replaces": "servicedemo" 26 | }, 27 | "useAutomation": { 28 | "type": "parameter", 29 | "datatype": "bool", 30 | "description": "Use this to enable/disable Nuke, GitHub Actions, and Terraform", 31 | "defaultValue": "true" 32 | } 33 | }, 34 | "sources":[{ 35 | "modifiers": [ 36 | { 37 | "exclude": [ 38 | "**/[Bb]in/**", 39 | "**/[Oo]bj/**", 40 | ".template.config/**/*", 41 | "**/*.filelist", 42 | "**/*.user", 43 | "**/*.lock.json", 44 | "**/.vs/**", 45 | "**/.vscode/**", 46 | "**/.git/**", 47 | "packageSettings.csproj", 48 | "**/.tmp/**", 49 | ".idea/**", 50 | "**/artifacts/**", 51 | "**/.terraform/**" 52 | ] 53 | }, 54 | { 55 | "exclude": [ 56 | "**/.nuke", 57 | "**/build*", 58 | "**/build/**", 59 | "**/.github/**", 60 | "**/terraform/**" 61 | ], 62 | "condition": "(!useAutomation)" 63 | } 64 | ] 65 | }], 66 | "postActions": [ 67 | { 68 | "condition": "(OS != \"Windows_NT\")", 69 | "description": "Make scripts executable", 70 | "manualInstructions": [{ "text": "Run 'chmod +x *.sh'" }], 71 | "actionId": "CB9A6CF3-4F5C-4860-B9D2-03A574959774", 72 | "args": { 73 | "+x": "*.sh" 74 | }, 75 | "continueOnError": true 76 | }, 77 | { 78 | "actionId": "3A7C4B45-1F5D-4A30-959A-51B88E82B5D2", 79 | "description": "Sets up the local and remote git repo and creates an initial commit to [main] by calling initializeGitRepo.sh", 80 | "args": { 81 | "executable": "initializeGitRepo.sh", 82 | "args": "", 83 | "redirectStandardOutput": "false" 84 | }, 85 | "manualInstructions": [{ 86 | "text": "Run 'initializeGitRepo.sh'" 87 | }], 88 | "continueOnError": false 89 | } 90 | ], 91 | "placeholderFilename": "-.-" 92 | } -------------------------------------------------------------------------------- /ServiceDemo/build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 4 | [string[]]$BuildArguments 5 | ) 6 | 7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" 8 | 9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } 10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 11 | 12 | ########################################################################### 13 | # CONFIGURATION 14 | ########################################################################### 15 | 16 | $BuildProjectFile = "$PSScriptRoot\build\_build.csproj" 17 | $TempDirectory = "$PSScriptRoot\\.tmp" 18 | 19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json" 20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" 21 | $DotNetChannel = "Current" 22 | 23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 25 | 26 | ########################################################################### 27 | # EXECUTION 28 | ########################################################################### 29 | 30 | function ExecSafe([scriptblock] $cmd) { 31 | & $cmd 32 | if ($LASTEXITCODE) { exit $LASTEXITCODE } 33 | } 34 | 35 | # If global.json exists, load expected version 36 | if (Test-Path $DotNetGlobalFile) { 37 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) 38 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { 39 | $DotNetVersion = $DotNetGlobal.sdk.version 40 | } 41 | } 42 | 43 | # If dotnet is installed locally, and expected version is not set or installation matches the expected version 44 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` 45 | (!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) { 46 | $env:DOTNET_EXE = (Get-Command "dotnet").Path 47 | } 48 | else { 49 | $DotNetDirectory = "$TempDirectory\dotnet-win" 50 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" 51 | 52 | # Download install script 53 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" 54 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null 55 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 56 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) 57 | 58 | # Install by channel or version 59 | if (!(Test-Path variable:DotNetVersion)) { 60 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } 61 | } else { 62 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } 63 | } 64 | } 65 | 66 | Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" 67 | 68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false -nologo -clp:NoSummary --verbosity quiet } 69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } 70 | -------------------------------------------------------------------------------- /ServiceDemo/ServiceDemo.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDemo.Api", "src\ServiceDemo.Api\ServiceDemo.Api.csproj", "{77E611A3-9A32-4A7E-A9E9-9B130A249D5A}" 7 | EndProject 8 | #if (useAutomation) 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{9B21D099-26CB-4D25-8C5E-F9ED07A0C18B}" 10 | EndProject 11 | #endif 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDemo.Api.Tests", "tests\ServiceDemo.Api.Tests\ServiceDemo.Api.Tests.csproj", "{BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Debug|x64 = Debug|x64 18 | Debug|x86 = Debug|x86 19 | Release|Any CPU = Release|Any CPU 20 | Release|x64 = Release|x64 21 | Release|x86 = Release|x86 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {9B21D099-26CB-4D25-8C5E-F9ED07A0C18B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {9B21D099-26CB-4D25-8C5E-F9ED07A0C18B}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Debug|x64.ActiveCfg = Debug|Any CPU 32 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Debug|x64.Build.0 = Debug|Any CPU 33 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Debug|x86.ActiveCfg = Debug|Any CPU 34 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Debug|x86.Build.0 = Debug|Any CPU 35 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Release|x64.ActiveCfg = Release|Any CPU 38 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Release|x64.Build.0 = Release|Any CPU 39 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Release|x86.ActiveCfg = Release|Any CPU 40 | {77E611A3-9A32-4A7E-A9E9-9B130A249D5A}.Release|x86.Build.0 = Release|Any CPU 41 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Debug|x64.ActiveCfg = Debug|Any CPU 44 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Debug|x64.Build.0 = Debug|Any CPU 45 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Debug|x86.ActiveCfg = Debug|Any CPU 46 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Debug|x86.Build.0 = Debug|Any CPU 47 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Release|x64.ActiveCfg = Release|Any CPU 50 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Release|x64.Build.0 = Release|Any CPU 51 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Release|x86.ActiveCfg = Release|Any CPU 52 | {BF8ED7F0-02FB-4145-9C7C-4AD832B937BE}.Release|x86.Build.0 = Release|Any CPU 53 | EndGlobalSection 54 | EndGlobal 55 | -------------------------------------------------------------------------------- /ServiceDemo/build/_build.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | DO_NOT_SHOW 3 | DO_NOT_SHOW 4 | DO_NOT_SHOW 5 | DO_NOT_SHOW 6 | Implicit 7 | Implicit 8 | ExpressionBody 9 | 0 10 | NEXT_LINE 11 | True 12 | False 13 | 120 14 | IF_OWNER_IS_SINGLE_LINE 15 | WRAP_IF_LONG 16 | False 17 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 18 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 19 | True 20 | True 21 | True 22 | True 23 | True 24 | True 25 | True 26 | True 27 | True 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DotnetTemplateDemo 2 | This is a demo of how custom .NET templates can be used to quickly create a functioning and deployable API. This includes: 3 | * The creation of a local and remote Git repository on GitHub during template-invocation. 4 | * Requires [GitHub CLI](https://cli.github.com/) configured for your GitHub account. 5 | * Currently only works on Linux and MacOS, but I have plans to make it friendly to Windows as well (unless you have WSL, in which case, you should be good). 6 | 7 | * A [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) module to create a Resource Group, Web App, and Application Insights resources within Microsoft Azure. 8 | * Please note that this assumes you have the following cloud resources for this to be deployed: 9 | - Azure Subscription. 10 | - Azure Storage Account with a container for the Terraform state files. 11 | - Azure App Service Plan to deploy the Web App to. 12 | 13 | The reason these resources need to be pre-created is because I'm assuming you would be running multiple web apps out of a single App Service Plan (that is managed outside of the actual repo containing your API code). If that's not the case, an App Service Plan resource can always be added to the provided Terraform template. 14 | * When you're ready to create your resources, make sure you're logged into your account via the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) and set the Subscription you want to use; then, just run a `terraform init` and `terraform apply` to get the resources created. 15 | * A build pipeline to compile and test your code (and generate a client when ready) using [Nuke](http://www.nuke.build/docs/getting-started/setup.html). 16 | * A GitHub Action to build and release your API to the resources created by Terraform. This runs automatically on push to the remote [main] branch. 17 | * Please note that there is still a manual step to include the publish profile from the newly-created Azure Web App (you should be able to get it as an output when you create the web app via Terraform) as a [GitHub repo secret](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets) named azureWebAppPublishProfile. This needs to be done before you push to [main]. 18 | * A GitHub Action to package the template itself and push it to a NuGet feed for further distribution. 19 | 20 | #### Some Caveats 21 | - Ultimately, this template has a few hard-coded values, such as ones for the NuGet feed and the pre-created resources in Azure (the App Service Plan and Storage Account names). This is for the purposes of showing how it can be tailored to your org (including naming conventions and some standard variables used throughout the company), where the "org" in this specific case is my GitHub account, NuGet feed, and Azure Subscription. 22 | 23 | - This is not for general use as it will not work for you out of the box (unless you actually change the aforementioned hard-coded values to your own). 24 | 25 | - This is for the purposes of demonstrating how custom .NET templates can be used and is not a statement on how the code within the template should actually be structured (even though _some_ of my opinions do apply here). There are probably a few things in here that you don't want to do in a live application. However, there are some resources below pertaining to the items I've implemented in this repo; they're still good to have in your toolbox, even if the demo implementation of them does not necessarily show their best usage. 26 | 27 | # Resources 28 | ## General .NET Tempate 29 | [Microsoft documentation for .NET Tempates](https://docs.microsoft.com/en-us/dotnet/core/tools/custom-templates) 30 | [Microsoft custom template tutorial](https://docs.microsoft.com/en-us/dotnet/core/tutorials/cli-templates-create-item-template) 31 | [.NET Template Parameter Generators](https://github.com/dotnet/templating/wiki/Available-Parameter-Generators) 32 | [Post Action Registry](https://github.com/dotnet/templating/wiki/Post-Action-Registry) 33 | [Testing Your Templates](https://github.com/dotnet/templating/wiki/Testing-your-templates) 34 | [Useful Microsoft blog post](https://devblogs.microsoft.com/dotnet/how-to-create-your-own-templates-for-dotnet-new/) 35 | 36 | ## Miscellaneous Resources 37 | [Recommended .NET project structure](https://gist.github.com/davidfowl/ed7564297c61fe9ab814) 38 | [Microsoft doc for NSwag Swagger specification](https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-nswag?view=aspnetcore-3.1&tabs=netcore-cli) 39 | [Microsoft doc for healthchecks](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/monitor-app-health) 40 | [Build automation with Nuke](http://www.nuke.build/) 41 | [GitVersion configuration](https://gitversion.readthedocs.io/en/latest/input/docs/configuration/) 42 | [Serilog for logging](https://serilog.net/) 43 | [Using appsettings.json for Serilog configuration](https://github.com/serilog/serilog-settings-configuration) 44 | [Deploying Azure App Service with GitHub Actions](https://docs.microsoft.com/en-us/azure/app-service/deploy-github-actions) 45 | [Documenting Architecture Decisions](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions) 46 | [Another ADR Resource](https://github.com/joelparkerhenderson/architecture_decision_record) 47 | [GitHub CLI](https://cli.github.com/) 48 | 49 | If you have any questions about this repo, feel free to reach out! -------------------------------------------------------------------------------- /ServiceDemo/build/Build.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Nuke.Common; 4 | using Nuke.Common.Execution; 5 | using Nuke.Common.Git; 6 | using Nuke.Common.IO; 7 | using Nuke.Common.ProjectModel; 8 | using Nuke.Common.Tooling; 9 | using Nuke.Common.Tools.Docker; 10 | using Nuke.Common.Tools.DotNet; 11 | using Nuke.Common.Tools.GitVersion; 12 | using Nuke.Common.Tools.NSwag; 13 | using Nuke.Common.Utilities.Collections; 14 | using static Nuke.Common.EnvironmentInfo; 15 | using static Nuke.Common.IO.FileSystemTasks; 16 | using static Nuke.Common.IO.PathConstruction; 17 | using static Nuke.Common.Tools.Docker.DockerTasks; 18 | using static Nuke.Common.Tools.DotNet.DotNetTasks; 19 | using static Nuke.Common.Tools.NSwag.NSwagTasks; 20 | 21 | [CheckBuildProjectConfigurations] 22 | [UnsetVisualStudioEnvironmentVariables] 23 | class Build : NukeBuild 24 | { 25 | public static int Main () => Execute(x => x.Compile); 26 | 27 | [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] 28 | readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; 29 | 30 | [Solution] readonly Solution Solution; 31 | [GitRepository] readonly GitRepository GitRepository; 32 | [GitVersion] readonly GitVersion GitVersion; 33 | 34 | AbsolutePath SourceDirectory => RootDirectory / "src"; 35 | AbsolutePath TestsDirectory => RootDirectory / "tests"; 36 | AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; 37 | 38 | Target Clean => _ => _ 39 | .Before(Restore) 40 | .Executes(() => 41 | { 42 | SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); 43 | TestsDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); 44 | EnsureCleanDirectory(ArtifactsDirectory); 45 | }); 46 | 47 | Target Restore => _ => _ 48 | .Executes(() => 49 | { 50 | DotNetRestore(s => s 51 | .SetProjectFile(Solution)); 52 | }); 53 | 54 | Target Compile => _ => _ 55 | .DependsOn(Restore) 56 | .Executes(() => 57 | { 58 | DotNetBuild(s => s 59 | .SetProjectFile(Solution) 60 | .SetConfiguration(Configuration) 61 | .SetAssemblyVersion(GitVersion.AssemblySemVer) 62 | .SetFileVersion(GitVersion.AssemblySemFileVer) 63 | .SetInformationalVersion(GitVersion.InformationalVersion) 64 | .EnableNoRestore()); 65 | }); 66 | 67 | Target Test => _ => _ 68 | .After(Compile) 69 | .Executes(() => 70 | { 71 | var testProjects = Solution.AllProjects.Where(project => 72 | project.Path.ToString().Contains(TestsDirectory.ToString())); 73 | DotNetTest(t => t 74 | .EnableNoBuild() 75 | .SetConfiguration(Configuration) 76 | .SetWorkingDirectory(TestsDirectory) 77 | .CombineWith(testProjects, (x, p) => x 78 | .SetProjectFile(p) 79 | .SetLogger("nunit;LogFilePath=TestResults/" + p.Name + "-Result.xml"))); 80 | }); 81 | 82 | Target GenerateClient => _ => _ 83 | .DependsOn(Compile) 84 | .Executes(() => 85 | { 86 | var clientDir = SourceDirectory; 87 | var clientProjDir = clientDir / "ServiceDemo.Client"; 88 | EnsureCleanDirectory(clientProjDir); 89 | 90 | var openApiPath = clientDir / "ServiceDemo.json"; 91 | 92 | NSwagAspNetCoreToOpenApi(x => x 93 | .SetNSwagRuntime("NetCore31") 94 | .SetAssembly(SourceDirectory / "ServiceDemo.Api" / "bin" / Configuration.ToString() / "netcoreapp3.1" / "ServiceDemo.Api.dll") 95 | .SetDocumentName("v1") 96 | .EnableUseDocumentProvider() 97 | .SetOutputType(SchemaType.OpenApi3) 98 | .SetOutput(openApiPath) 99 | ); 100 | 101 | NSwagSwaggerToCSharpClient(x => x 102 | .SetNSwagRuntime("NetCore31") 103 | .SetInput(openApiPath) 104 | .SetOutput(clientProjDir / "ServiceDemo.Client.cs") 105 | .SetNamespace("ServiceDemo.Clients") 106 | .SetGenerateClientInterfaces(true) 107 | .SetGenerateExceptionClasses(true) 108 | .SetExceptionClass("{controller}ClientException") 109 | ); 110 | 111 | var version = GitRepository.Branch.Equals("main", StringComparison.OrdinalIgnoreCase) ? GitVersion.MajorMinorPatch : GitVersion.NuGetVersionV2; 112 | 113 | DotNet($"new classlib -o {clientProjDir}", workingDirectory: clientProjDir); 114 | DeleteFile(clientProjDir / "Class1.cs"); 115 | DotNet($"add package Newtonsoft.Json", workingDirectory: clientProjDir); 116 | DotNet("add package System.ComponentModel.Annotations", workingDirectory: clientProjDir); 117 | 118 | DotNetPack(x => x 119 | .SetProject(clientProjDir) 120 | .SetOutputDirectory(ArtifactsDirectory) 121 | .SetConfiguration(Configuration) 122 | .SetVersion(version) 123 | .SetIncludeSymbols(true) 124 | ); 125 | }); 126 | Target Publish => _ => _ 127 | .After(Test) 128 | .Executes(() => 129 | { 130 | var projectSolution = Solution.AllProjects.Where(p => 131 | !p.Name.Contains("Tests") 132 | && !p.Name.Contains("build") 133 | && p.Is(ProjectType.CSharpProject)); 134 | 135 | DotNetPublish(s => s 136 | .SetConfiguration(Configuration) 137 | .SetAssemblyVersion(GitVersion.AssemblySemVer) 138 | .SetFileVersion(GitVersion.AssemblySemFileVer) 139 | .SetInformationalVersion(GitVersion.InformationalVersion) 140 | .EnableNoRestore() 141 | .CombineWith(projectSolution, (x, p) => x 142 | .SetProject(p) 143 | .SetOutput(ArtifactsDirectory / p.Name))); 144 | }); 145 | } 146 | --------------------------------------------------------------------------------