├── global.json ├── README.md ├── Dockerfile ├── src └── WebApi │ ├── appsettings.test.json │ ├── Models │ ├── MyDbContext.cs │ └── Person.cs │ ├── appsettings.json │ ├── web.config │ ├── Program.cs │ ├── Properties │ ├── launchSettings.json │ └── PublishProfiles │ │ ├── aspcore-circleci-azure - Web Deploy-publish.ps1 │ │ └── publish-module.psm1 │ ├── Controllers │ ├── ValuesController.cs │ └── PeopleController.cs │ ├── WebApi.xproj │ ├── Migrations │ ├── MyDbContextModelSnapshot.cs │ ├── 20161116235219_InitialCreate.cs │ └── 20161116235219_InitialCreate.Designer.cs │ ├── Startup.cs │ └── project.json ├── test └── WebApi.IntegrationTests │ ├── appsettings.test.json │ ├── Configuration │ ├── BaseTestCollection.cs │ ├── BaseIntegrationTest.cs │ └── BaseTestFixture.cs │ ├── appsettings.json │ ├── project.json │ ├── Properties │ └── AssemblyInfo.cs │ ├── WebApi.IntegrationTests.xproj │ └── Controllers │ ├── ValuesControllerIntegrationTest.cs │ └── PeopleControllerIntegrationTest.cs ├── script ├── restore ├── build ├── package ├── publish-azure ├── publish-dockerhub └── test ├── web.azure.config ├── circle.yml ├── AspnetCoreCircleCiAzure.sln ├── .gitattributes └── .gitignore /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "src", "test" ], 3 | "sdk": { 4 | "version": "1.0.0-preview2-003131" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aspcore-circleci-azure 2 | Building an asp.net core api with integration tests on circleCi and deploying to azure 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore 2 | WORKDIR /app 3 | COPY src/WebApi/bin/Release/netcoreapp1.1/publish/ . 4 | ENTRYPOINT ["dotnet", "WebApi.dll"] -------------------------------------------------------------------------------- /src/WebApi/appsettings.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=127.0.0.1;Database=AspnetCoreCircleCiAzure_Test;User Id=sa;Password=Xpto@1502;MultipleActiveResultSets=true" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/WebApi.IntegrationTests/appsettings.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=127.0.0.1;Database=AspCoreCircleCiAzure_Test;User Id=sa;Password=Xpto@1502;MultipleActiveResultSets=true" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /script/restore: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # script/builds - builds the release 4 | 5 | # exit on sub-module failure 6 | set -e 7 | 8 | cd "$(dirname "$0")/.." 9 | 10 | # ------ 11 | 12 | echo "Restoring..." 13 | 14 | dotnet restore 15 | 16 | echo "Done restoring!" 17 | 18 | # ------ 19 | -------------------------------------------------------------------------------- /test/WebApi.IntegrationTests/Configuration/BaseTestCollection.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace WebApi.IntegrationTests.Configuration 4 | { 5 | [CollectionDefinition("Base collection")] 6 | public abstract class BaseTestCollection : ICollectionFixture 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /script/build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # script/builds - builds the release 4 | 5 | # exit on sub-module failure 6 | set -e 7 | 8 | cd "$(dirname "$0")/.." 9 | 10 | # ------ 11 | 12 | echo "Building..." 13 | 14 | cd src/WebApi/ 15 | rm -rf bin/Release 16 | dotnet publish --configuration Release 17 | 18 | echo "Done building!" 19 | 20 | # ------ 21 | -------------------------------------------------------------------------------- /src/WebApi/Models/MyDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace WebApi.Models 4 | { 5 | public class MyDbContext : DbContext 6 | { 7 | public MyDbContext(DbContextOptions options) : base(options) 8 | { 9 | } 10 | 11 | public DbSet People { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=localhost;Database=AspnetCoreCircleCiAzure;User Id=sa;Password=Xpto@1502;MultipleActiveResultSets=true" 4 | }, 5 | "Logging": { 6 | "IncludeScopes": false, 7 | "LogLevel": { 8 | "Default": "Debug", 9 | "System": "Information", 10 | "Microsoft": "Information" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/WebApi.IntegrationTests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=AspnetCoreCircleCiAzure_Test;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "Logging": { 6 | "IncludeScopes": false, 7 | "LogLevel": { 8 | "Default": "Debug", 9 | "System": "Information", 10 | "Microsoft": "Information" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /script/package: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # script/builds - builds the release 4 | 5 | # exit on sub-module failure 6 | set -e 7 | 8 | cd "$(dirname "$0")/.." 9 | 10 | # ------ 11 | 12 | echo "Packaging..." 13 | 14 | # Zip the content 15 | cp web.azure.config src/WebApi/bin/Release/netcoreapp1.1/publish/web.config 16 | cd src/WebApi/bin/Release/netcoreapp1.1/publish 17 | zip -r ../publish.zip ./* 18 | 19 | echo "Done packaging!" 20 | 21 | # ------ 22 | -------------------------------------------------------------------------------- /script/publish-azure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # script/builds - builds the release 4 | 5 | # exit on sub-module failure 6 | set -e 7 | 8 | cd "$(dirname "$0")/.." 9 | 10 | # ------ 11 | 12 | echo "Publishing Azure..." 13 | 14 | curl -H "If-Match: *" --user $AZURE_PUBLISH_USERNAME:$AZURE_PUBLISH_PASSWORD $AZURE_PUBLISH_URL/zip/site/wwwroot --upload-file $CIRCLE_ARTIFACTS/netcoreapp1.1/publish.zip 15 | 16 | echo "Done publishing!" 17 | 18 | # ------ 19 | -------------------------------------------------------------------------------- /src/WebApi/Models/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace WebApi.Models 5 | { 6 | public class Person 7 | { 8 | public int Id { get; set; } 9 | [Required] 10 | public string Name { get; set; } 11 | public string Phone { get; set; } 12 | [Required] 13 | public DateTime BirthDay { get; set; } 14 | [Required] 15 | public decimal Salary { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /script/publish-dockerhub: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # script/builds - builds the release 4 | 5 | # exit on sub-module failure 6 | set -e 7 | 8 | cd "$(dirname "$0")/.." 9 | 10 | # ------ 11 | 12 | echo "Publishing Docker Hub..." 13 | 14 | docker build -t aspnetcore-circleci-azure . 15 | docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS 16 | docker tag aspnetcore-circleci-azure $DOCKER_USER/aspnetcore-circleci-azure:latest 17 | docker push $DOCKER_USER/aspnetcore-circleci-azure 18 | 19 | echo "Done publishing!" 20 | 21 | # ------ 22 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # script/builds - builds the release 4 | 5 | # exit on sub-module failure 6 | set -e 7 | 8 | cd "$(dirname "$0")/.." 9 | 10 | # ------ 11 | 12 | sh script/restore 13 | 14 | # ------ 15 | 16 | echo "Testing..." 17 | export ASPNETCORE_ENVIRONMENT=test 18 | export ConnectionStrings__DefaultConnection="Server=localhost;Database=AspnetCoreCircleCiAzure_Test;User Id=sa;Password=Xpto@1502;MultipleActiveResultSets=true" 19 | cd test/WebApi.IntegrationTests/ 20 | dotnet test 21 | 22 | echo "Done testing!" 23 | 24 | # ------ 25 | -------------------------------------------------------------------------------- /src/WebApi/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /web.azure.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Builder; 8 | using WebApi; 9 | 10 | namespace WebApplication5 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | var host = new WebHostBuilder() 17 | .UseKestrel() 18 | .UseContentRoot(Directory.GetCurrentDirectory()) 19 | .UseIISIntegration() 20 | .UseStartup() 21 | .Build(); 22 | 23 | host.Run(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/WebApi.IntegrationTests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "testRunner": "xunit", 4 | "buildOptions": { 5 | "copyToOutput": { 6 | "include": [ "appsettings.json", "appsettings.test.json" ] 7 | } 8 | }, 9 | 10 | "dependencies": { 11 | "WebApi": { "target": "project" }, 12 | "Microsoft.AspNetCore.TestHost": "1.1.0", 13 | "xunit": "2.2.0-beta2-build3300", 14 | "dotnet-test-xunit": "2.2.0-preview2-build1029", 15 | "Microsoft.DotNet.InternalAbstractions": "1.0.0" 16 | }, 17 | 18 | "frameworks": { 19 | "netcoreapp1.1": { 20 | "dependencies": { 21 | "Microsoft.NETCore.App": { 22 | "type": "platform", 23 | "version": "1.1.0" 24 | } 25 | }, 26 | "imports": "dnxcore50" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:54421/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "api/values", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "WebApplication5": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "http://localhost:5000/api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/WebApi/Properties/PublishProfiles/aspcore-circleci-azure - Web Deploy-publish.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding(SupportsShouldProcess=$true)] 2 | param($publishProperties=@{}, $packOutput, $pubProfilePath) 3 | 4 | # to learn more about this file visit https://go.microsoft.com/fwlink/?LinkId=524327 5 | 6 | try{ 7 | if ($publishProperties['ProjectGuid'] -eq $null){ 8 | $publishProperties['ProjectGuid'] = '357815b0-24fe-4fc3-97b6-11e3c85ddd32' 9 | } 10 | 11 | $publishModulePath = Join-Path (Split-Path $MyInvocation.MyCommand.Path) 'publish-module.psm1' 12 | Import-Module $publishModulePath -DisableNameChecking -Force 13 | 14 | # call Publish-AspNet to perform the publish operation 15 | Publish-AspNet -publishProperties $publishProperties -packOutput $packOutput -pubProfilePath $pubProfilePath 16 | } 17 | catch{ 18 | "An error occurred during publish.`n{0}" -f $_.Exception.Message | Write-Error 19 | } -------------------------------------------------------------------------------- /test/WebApi.IntegrationTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("AspCoreCircleCiAzure.IntegrationTests")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("f8a57cd7-9987-407d-ac83-5ade7f38a8c9")] 20 | -------------------------------------------------------------------------------- /src/WebApi/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace WebApi.Controllers 5 | { 6 | [Route("api/[controller]")] 7 | public class ValuesController : Controller 8 | { 9 | // GET api/values 10 | [HttpGet] 11 | public IEnumerable Get() 12 | { 13 | return new string[] { "value1", "value2" }; 14 | } 15 | 16 | // GET api/values/5 17 | [HttpGet("{id}")] 18 | public string Get(int id) 19 | { 20 | return "value"; 21 | } 22 | 23 | // POST api/values 24 | [HttpPost] 25 | public void Post([FromBody]string value) 26 | { 27 | } 28 | 29 | // PUT api/values/5 30 | [HttpPut("{id}")] 31 | public void Put(int id, [FromBody]string value) 32 | { 33 | } 34 | 35 | // DELETE api/values/5 36 | [HttpDelete("{id}")] 37 | public void Delete(int id) 38 | { 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/WebApi/WebApi.xproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 357815b0-24fe-4fc3-97b6-11e3c85ddd32 10 | WebApi 11 | .\obj 12 | .\bin\ 13 | v4.6.1 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/WebApi/Migrations/MyDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using WebApi.Models; 6 | 7 | namespace WebApi.Migrations 8 | { 9 | [DbContext(typeof(MyDbContext))] 10 | partial class MyDbContextModelSnapshot : ModelSnapshot 11 | { 12 | protected override void BuildModel(ModelBuilder modelBuilder) 13 | { 14 | modelBuilder 15 | .HasAnnotation("ProductVersion", "1.1.0-rtm-22752") 16 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 17 | 18 | modelBuilder.Entity("AspCoreCircleCiAzure.Models.Person", b => 19 | { 20 | b.Property("Id") 21 | .ValueGeneratedOnAdd(); 22 | 23 | b.Property("BirthDay"); 24 | 25 | b.Property("Name"); 26 | 27 | b.Property("Phone"); 28 | 29 | b.Property("Salary"); 30 | 31 | b.HasKey("Id"); 32 | 33 | b.ToTable("People"); 34 | }); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/WebApi.IntegrationTests/WebApi.IntegrationTests.xproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | f8a57cd7-9987-407d-ac83-5ade7f38a8c9 10 | WebApi.IntegrationTests 11 | .\obj 12 | .\bin\ 13 | v4.6.1 14 | 15 | 16 | 2.0 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/WebApi/Migrations/20161116235219_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace WebApi.Migrations 6 | { 7 | public partial class InitialCreate : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "People", 13 | columns: table => new 14 | { 15 | Id = table.Column(nullable: false) 16 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 17 | BirthDay = table.Column(nullable: false), 18 | Name = table.Column(nullable: true), 19 | Phone = table.Column(nullable: true), 20 | Salary = table.Column(nullable: false) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("PK_People", x => x.Id); 25 | }); 26 | } 27 | 28 | protected override void Down(MigrationBuilder migrationBuilder) 29 | { 30 | migrationBuilder.DropTable( 31 | name: "People"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/WebApi/Migrations/20161116235219_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using WebApi.Models; 7 | 8 | namespace WebApi.Migrations 9 | { 10 | [DbContext(typeof(MyDbContext))] 11 | [Migration("20161116235219_InitialCreate")] 12 | partial class InitialCreate 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "1.1.0-rtm-22752") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("AspCoreCircleCiAzure.Models.Person", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd(); 24 | 25 | b.Property("BirthDay"); 26 | 27 | b.Property("Name"); 28 | 29 | b.Property("Phone"); 30 | 31 | b.Property("Salary"); 32 | 33 | b.HasKey("Id"); 34 | 35 | b.ToTable("People"); 36 | }); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | services: 3 | - docker 4 | 5 | dependencies: 6 | cache_directories: 7 | - "~/docker" 8 | pre: 9 | - mkdir -p ~/docker; 10 | - sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list' 11 | - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893 12 | - sudo apt-get update 13 | - sudo apt-get install dotnet-dev-1.0.0-preview2.1-003177 14 | override: 15 | # sqlserver 16 | - if [ -e ~/docker/mssql-server-linux.tar ]; then docker load -i ~/docker/mssql-server-linux.tar; fi 17 | - docker pull microsoft/mssql-server-linux 18 | - docker save microsoft/mssql-server-linux > ~/docker/mssql-server-linux.tar 19 | 20 | database: 21 | override: 22 | - docker run -d -p 1433:1433 -e SA_PASSWORD=Xpto@1502 -e ACCEPT_EULA=Y --name mssql microsoft/mssql-server-linux 23 | - docker ps -a 24 | - docker inspect mssql 25 | 26 | test: 27 | override: 28 | - sh script/test 29 | - if [[ -e test-results.xml ]]; then cp test-results.xml $CIRCLE_TEST_REPORTS/test-results.xml; fi 30 | post: 31 | - sh script/build 32 | - sh script/package 33 | - cp -R src/WebApi/bin/Release/netcoreapp1.1/ $CIRCLE_ARTIFACTS/ 34 | 35 | deployment: 36 | master: 37 | branch: master 38 | commands: 39 | - sh script/publish-azure 40 | - sh script/publish-dockerhub 41 | -------------------------------------------------------------------------------- /src/WebApi/Controllers/PeopleController.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.EntityFrameworkCore; 5 | using WebApi.Models; 6 | 7 | namespace WebApi.Controllers 8 | { 9 | [Route("api/people")] 10 | public class PeopleController : Controller 11 | { 12 | private readonly MyDbContext _context; 13 | 14 | public PeopleController(MyDbContext context) 15 | { 16 | _context = context; 17 | } 18 | 19 | [HttpGet] 20 | public async Task GetPeople() 21 | { 22 | var people = await _context.People.ToListAsync(); 23 | return Json(people); 24 | } 25 | 26 | [HttpGet, Route("{id}")] 27 | public async Task GetPerson(int id) 28 | { 29 | var person = await _context.People.FirstOrDefaultAsync(x => x.Id == id); 30 | 31 | if (person == null) 32 | { 33 | return NotFound(); 34 | } 35 | 36 | return Json(person); 37 | } 38 | 39 | [HttpPost] 40 | public async Task CreatePerson([FromBody]Person person) 41 | { 42 | if (!ModelState.IsValid) 43 | { 44 | return BadRequest(ModelState); 45 | } 46 | 47 | _context.People.Add(person); 48 | await _context.SaveChangesAsync(); 49 | 50 | return StatusCode((int)HttpStatusCode.Created, person); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/WebApi.IntegrationTests/Configuration/BaseIntegrationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using WebApi.Models; 3 | using Xunit; 4 | using Microsoft.AspNetCore.TestHost; 5 | using System.Threading.Tasks; 6 | using Microsoft.EntityFrameworkCore; 7 | 8 | namespace WebApi.IntegrationTests.Configuration 9 | { 10 | [Collection("Base collection")] 11 | public abstract class BaseIntegrationTest 12 | { 13 | protected readonly TestServer Server; 14 | protected readonly HttpClient Client; 15 | protected readonly MyDbContext TestDbContext; 16 | 17 | protected BaseTestFixture Fixture { get; } 18 | 19 | protected BaseIntegrationTest(BaseTestFixture fixture) 20 | { 21 | Fixture = fixture; 22 | 23 | TestDbContext = Fixture.TestDbContext; 24 | Server = Fixture.Server; 25 | Client = Fixture.Client; 26 | 27 | ClearDb().Wait(); 28 | } 29 | 30 | private async Task ClearDb() 31 | { 32 | var commands = new[] 33 | { 34 | "EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'", 35 | "EXEC sp_MSForEachTable 'DELETE FROM ?'", 36 | "EXEC sp_MSForEachTable 'ALTER TABLE ? CHECK CONSTRAINT ALL'" 37 | }; 38 | 39 | await TestDbContext.Database.OpenConnectionAsync(); 40 | 41 | foreach (var command in commands) 42 | { 43 | await TestDbContext.Database.ExecuteSqlCommandAsync(command); 44 | } 45 | 46 | TestDbContext.Database.CloseConnection(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/WebApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using WebApi.Models; 9 | 10 | namespace WebApi 11 | { 12 | public class Startup 13 | { 14 | public Startup(IHostingEnvironment env) 15 | { 16 | var builder = new ConfigurationBuilder() 17 | .SetBasePath(env.ContentRootPath) 18 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 19 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 20 | .AddEnvironmentVariables(); 21 | Configuration = builder.Build(); 22 | } 23 | 24 | public IConfigurationRoot Configuration { get; } 25 | 26 | // This method gets called by the runtime. Use this method to add services to the container. 27 | public void ConfigureServices(IServiceCollection services) 28 | { 29 | // Add framework services. 30 | services.AddMvc(); 31 | 32 | services.AddDbContext(options => 33 | options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 34 | } 35 | 36 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 37 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 38 | { 39 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 40 | loggerFactory.AddDebug(); 41 | 42 | app.UseDeveloperExceptionPage(); 43 | 44 | app.UseMvc(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/WebApi/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.AspNetCore.Diagnostics": "1.1.0", 4 | "Microsoft.AspNetCore.Mvc": "1.1.0", 5 | "Microsoft.AspNetCore.Routing": "1.1.0", 6 | "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", 7 | "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", 8 | "Microsoft.EntityFrameworkCore.Design": "1.1.0", 9 | "Microsoft.EntityFrameworkCore.SqlServer": "1.1.0", 10 | "Microsoft.EntityFrameworkCore.Tools": "1.1.0-preview4-final", 11 | "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", 12 | "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", 13 | "Microsoft.Extensions.Configuration.Json": "1.1.0", 14 | "Microsoft.Extensions.Logging": "1.1.0", 15 | "Microsoft.Extensions.Logging.Console": "1.1.0", 16 | "Microsoft.Extensions.Logging.Debug": "1.1.0", 17 | "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0" 18 | }, 19 | 20 | "tools": { 21 | "Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-preview3-final", 22 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 23 | }, 24 | 25 | "frameworks": { 26 | "netcoreapp1.1": { 27 | "dependencies": { 28 | "Microsoft.NETCore.App": { 29 | "type": "platform", 30 | "version": "1.1.0" 31 | } 32 | }, 33 | "imports": [ 34 | //"dotnet5.6", 35 | //"portable-net45+win8" 36 | "dnxcore50" 37 | ] 38 | } 39 | }, 40 | 41 | "buildOptions": { 42 | "emitEntryPoint": true, 43 | "preserveCompilationContext": true 44 | }, 45 | 46 | "runtimeOptions": { 47 | "configProperties": { 48 | "System.GC.Server": true 49 | } 50 | }, 51 | 52 | "publishOptions": { 53 | "include": [ 54 | "wwwroot", 55 | "**/*.cshtml", 56 | "appsettings.json", 57 | "web.config" 58 | ] 59 | }, 60 | 61 | "scripts": { 62 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/WebApi.IntegrationTests/Configuration/BaseTestFixture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.AspNetCore.TestHost; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.Configuration; 5 | using System; 6 | using System.Net.Http; 7 | using WebApi.Models; 8 | 9 | namespace WebApi.IntegrationTests.Configuration 10 | { 11 | public class BaseTestFixture : IDisposable 12 | { 13 | public readonly TestServer Server; 14 | public readonly HttpClient Client; 15 | public readonly MyDbContext TestDbContext; 16 | public readonly IConfigurationRoot Configuration; 17 | 18 | public BaseTestFixture() 19 | { 20 | var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); 21 | 22 | var builder = new ConfigurationBuilder() 23 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 24 | .AddJsonFile($"appsettings.{envName}.json", optional: true) 25 | .AddEnvironmentVariables(); 26 | 27 | Configuration = builder.Build(); 28 | 29 | var opts = new DbContextOptionsBuilder(); 30 | opts.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); 31 | TestDbContext = new MyDbContext(opts.Options); 32 | SetupDatabase(); 33 | 34 | Server = new TestServer(new WebHostBuilder().UseStartup()); 35 | Client = Server.CreateClient(); 36 | } 37 | 38 | private void SetupDatabase() 39 | { 40 | try 41 | { 42 | TestDbContext.Database.EnsureCreated(); 43 | TestDbContext.Database.Migrate(); 44 | } 45 | catch (Exception) 46 | { 47 | //TODO: Add a better logging 48 | // Does nothing 49 | } 50 | } 51 | 52 | public void Dispose() 53 | { 54 | TestDbContext.Dispose(); 55 | Client.Dispose(); 56 | Server.Dispose(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /AspnetCoreCircleCiAzure.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{37B59A29-4573-4B9E-A021-D7E25F51BDB7}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{23B0C0D4-9A77-4031-83C4-42147E69DAEA}" 9 | ProjectSection(SolutionItems) = preProject 10 | global.json = global.json 11 | EndProjectSection 12 | EndProject 13 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "WebApi", "src\WebApi\WebApi.xproj", "{357815B0-24FE-4FC3-97B6-11E3C85DDD32}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1E27F923-56EB-45EC-A200-2D4C7D8DC806}" 16 | EndProject 17 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "WebApi.IntegrationTests", "test\WebApi.IntegrationTests\WebApi.IntegrationTests.xproj", "{F8A57CD7-9987-407D-AC83-5ADE7F38A8C9}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {357815B0-24FE-4FC3-97B6-11E3C85DDD32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {357815B0-24FE-4FC3-97B6-11E3C85DDD32}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {357815B0-24FE-4FC3-97B6-11E3C85DDD32}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {357815B0-24FE-4FC3-97B6-11E3C85DDD32}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {F8A57CD7-9987-407D-AC83-5ADE7F38A8C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {F8A57CD7-9987-407D-AC83-5ADE7F38A8C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {F8A57CD7-9987-407D-AC83-5ADE7F38A8C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {F8A57CD7-9987-407D-AC83-5ADE7F38A8C9}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(NestedProjects) = preSolution 38 | {357815B0-24FE-4FC3-97B6-11E3C85DDD32} = {37B59A29-4573-4B9E-A021-D7E25F51BDB7} 39 | {F8A57CD7-9987-407D-AC83-5ADE7F38A8C9} = {1E27F923-56EB-45EC-A200-2D4C7D8DC806} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /test/WebApi.IntegrationTests/Controllers/ValuesControllerIntegrationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using WebApi.IntegrationTests.Configuration; 6 | using Xunit; 7 | using System.Text; 8 | using System.Net; 9 | 10 | namespace WebApi.IntegrationTests.Controllers 11 | { 12 | public class ValuesControllerIntegrationTest : BaseIntegrationTest 13 | { 14 | private const string BaseUrl = "/api/values"; 15 | 16 | public ValuesControllerIntegrationTest(BaseTestFixture fixture) : base(fixture) 17 | { 18 | } 19 | 20 | [Fact] 21 | public async Task GetShouldReturnValues() 22 | { 23 | var response = await Client.GetAsync(BaseUrl); 24 | response.EnsureSuccessStatusCode(); 25 | 26 | var dataString = await response.Content.ReadAsStringAsync(); 27 | var data = JsonConvert.DeserializeObject>(dataString); 28 | 29 | Assert.Equal(data.Count, 2); 30 | Assert.Contains(data, x => x == "value1"); 31 | Assert.Contains(data, x => x == "value2"); 32 | } 33 | 34 | [Fact] 35 | public async Task GetSingleShouldReturnValue() 36 | { 37 | var response = await Client.GetAsync($"{BaseUrl}/5"); 38 | response.EnsureSuccessStatusCode(); 39 | 40 | var data = await response.Content.ReadAsStringAsync(); 41 | 42 | Assert.Equal(data, "value"); 43 | } 44 | 45 | [Fact] 46 | public async Task PostShouldReturnOk() 47 | { 48 | var body = new { value = "my-value" }; 49 | var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); 50 | var response = await Client.PostAsync(BaseUrl, content); 51 | response.EnsureSuccessStatusCode(); 52 | 53 | Assert.Equal(response.StatusCode, HttpStatusCode.OK); 54 | } 55 | 56 | [Fact] 57 | public async Task PutShouldReturnOk() 58 | { 59 | var body = new { value = "my-value" }; 60 | var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); 61 | var response = await Client.PutAsync($"{BaseUrl}/5", content); 62 | response.EnsureSuccessStatusCode(); 63 | 64 | Assert.Equal(response.StatusCode, HttpStatusCode.OK); 65 | } 66 | 67 | [Fact] 68 | public async Task DeleteShouldReturnOk() 69 | { 70 | var response = await Client.DeleteAsync($"{BaseUrl}/5"); 71 | response.EnsureSuccessStatusCode(); 72 | 73 | Assert.Equal(response.StatusCode, HttpStatusCode.OK); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /test/WebApi.IntegrationTests/Controllers/PeopleControllerIntegrationTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using WebApi.IntegrationTests.Configuration; 9 | using WebApi.Models; 10 | using Xunit; 11 | 12 | namespace WebApi.IntegrationTests.Controllers 13 | { 14 | public class PeopleControllerIntegrationTest : BaseIntegrationTest 15 | { 16 | private const string BaseUrl = "/api/people"; 17 | 18 | public PeopleControllerIntegrationTest(BaseTestFixture fixture) : base(fixture) 19 | { 20 | } 21 | 22 | [Fact] 23 | public async Task GetShouldReturnValues() 24 | { 25 | var person = await CreatePerson(); 26 | 27 | var response = await Client.GetAsync(BaseUrl); 28 | response.EnsureSuccessStatusCode(); 29 | 30 | var dataString = await response.Content.ReadAsStringAsync(); 31 | var data = JsonConvert.DeserializeObject>(dataString); 32 | 33 | Assert.Equal(data.Count, 1); 34 | Assert.Contains(data, x => x.Name == person.Name); 35 | } 36 | 37 | [Fact] 38 | public async Task GetSinglePersonNoIdReturnsNotFound() 39 | { 40 | var response = await Client.GetAsync($"{BaseUrl}/99"); 41 | Assert.Equal(response.StatusCode, HttpStatusCode.NotFound); 42 | } 43 | 44 | [Fact] 45 | public async Task GetSinglePersonWithIdReturnsPerson() 46 | { 47 | var person = await CreatePerson(); 48 | 49 | var response = await Client.GetAsync($"{BaseUrl}/{person.Id}"); 50 | response.EnsureSuccessStatusCode(); 51 | 52 | var dataString = await response.Content.ReadAsStringAsync(); 53 | var data = JsonConvert.DeserializeObject(dataString); 54 | 55 | Assert.Equal(data.Name, person.Name); 56 | } 57 | 58 | [Fact] 59 | public async Task PostWithInvalidModelShouldReturnBadRequest() 60 | { 61 | var body = new { value = "my-value" }; 62 | var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); 63 | var response = await Client.PostAsync(BaseUrl, content); 64 | 65 | Assert.Equal(response.StatusCode, HttpStatusCode.BadRequest); 66 | } 67 | 68 | [Fact] 69 | public async Task PostWithValidModelShouldReturnCreated() 70 | { 71 | var person = new Person 72 | { 73 | Name = "Rafael dos Santos", 74 | Phone = "+5527900000000", 75 | BirthDay = new DateTime(1988, 09, 08), 76 | Salary = 1000 77 | }; 78 | 79 | var content = new StringContent(JsonConvert.SerializeObject(person), Encoding.UTF8, "application/json"); 80 | var response = await Client.PostAsync(BaseUrl, content); 81 | response.EnsureSuccessStatusCode(); 82 | 83 | var data = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); 84 | 85 | Assert.Equal(response.StatusCode, HttpStatusCode.Created); 86 | Assert.NotSame(data.Id, 0); 87 | Assert.Equal(data.Name, person.Name); 88 | } 89 | 90 | private async Task CreatePerson() 91 | { 92 | var person = new Person 93 | { 94 | Name = "Rafael dos Santos", 95 | Phone = "+5527900000000", 96 | BirthDay = new DateTime(1988, 09, 08), 97 | Salary = 1000 98 | }; 99 | 100 | await TestDbContext.AddAsync(person); 101 | await TestDbContext.SaveChangesAsync(); 102 | return person; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /src/WebApi/Properties/PublishProfiles/publish-module.psm1: -------------------------------------------------------------------------------- 1 | # WARNING: DO NOT MODIFY this file. Visual Studio will override it. 2 | param() 3 | 4 | $script:AspNetPublishHandlers = @{} 5 | 6 | <# 7 | These settings can be overridden with environment variables. 8 | The name of the environment variable should use "Publish" as a 9 | prefix and the names below. For example: 10 | 11 | $env:PublishMSDeployUseChecksum = $true 12 | #> 13 | $global:AspNetPublishSettings = New-Object -TypeName PSCustomObject @{ 14 | MsdeployDefaultProperties = @{ 15 | 'MSDeployUseChecksum'=$false 16 | 'SkipExtraFilesOnServer'=$true 17 | 'retryAttempts' = 20 18 | 'EnableMSDeployBackup' = $false 19 | 'DeleteExistingFiles' = $false 20 | 'AllowUntrustedCertificate'= $false 21 | 'MSDeployPackageContentFoldername'='website\' 22 | 'EnvironmentName' = 'Production' 23 | 'AuthType'='Basic' 24 | 'MSDeployPublishMethod'='WMSVC' 25 | } 26 | } 27 | 28 | function InternalOverrideSettingsFromEnv{ 29 | [cmdletbinding()] 30 | param( 31 | [Parameter(Position=0)] 32 | [object[]]$settings = ($global:AspNetPublishSettings,$global:AspNetPublishSettings.MsdeployDefaultProperties), 33 | 34 | [Parameter(Position=1)] 35 | [string]$prefix = 'Publish' 36 | ) 37 | process{ 38 | foreach($settingsObj in $settings){ 39 | if($settingsObj -eq $null){ 40 | continue 41 | } 42 | 43 | $settingNames = $null 44 | if($settingsObj -is [hashtable]){ 45 | $settingNames = $settingsObj.Keys 46 | } 47 | else{ 48 | $settingNames = ($settingsObj | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name) 49 | 50 | } 51 | 52 | foreach($name in @($settingNames)){ 53 | $fullname = ('{0}{1}' -f $prefix,$name) 54 | if(Test-Path "env:$fullname"){ 55 | $settingsObj.$name = ((get-childitem "env:$fullname").Value) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | InternalOverrideSettingsFromEnv -prefix 'Publish' -settings $global:AspNetPublishSettings,$global:AspNetPublishSettings.MsdeployDefaultProperties 63 | 64 | function Register-AspnetPublishHandler{ 65 | [cmdletbinding()] 66 | param( 67 | [Parameter(Mandatory=$true,Position=0)] 68 | $name, 69 | [Parameter(Mandatory=$true,Position=1)] 70 | [ScriptBlock]$handler, 71 | [switch]$force 72 | ) 73 | process{ 74 | if(!($script:AspNetPublishHandlers[$name]) -or $force ){ 75 | 'Adding handler for [{0}]' -f $name | Write-Verbose 76 | $script:AspNetPublishHandlers[$name] = $handler 77 | } 78 | elseif(!($force)){ 79 | 'Ignoring call to Register-AspnetPublishHandler for [name={0}], because a handler with that name exists and -force was not passed.' -f $name | Write-Verbose 80 | } 81 | } 82 | } 83 | 84 | function Get-AspnetPublishHandler{ 85 | [cmdletbinding()] 86 | param( 87 | [Parameter(Mandatory=$true,Position=0)] 88 | $name 89 | ) 90 | process{ 91 | $foundHandler = $script:AspNetPublishHandlers[$name] 92 | 93 | if(!$foundHandler){ 94 | throw ('AspnetPublishHandler with name "{0}" was not found' -f $name) 95 | } 96 | 97 | $foundHandler 98 | } 99 | } 100 | 101 | function GetInternal-ExcludeFilesArg{ 102 | [cmdletbinding()] 103 | param( 104 | $publishProperties 105 | ) 106 | process{ 107 | $excludeFiles = $publishProperties['ExcludeFiles'] 108 | foreach($exclude in $excludeFiles){ 109 | if($exclude){ 110 | [string]$objName = $exclude['objectname'] 111 | 112 | if([string]::IsNullOrEmpty($objName)){ 113 | $objName = 'filePath' 114 | } 115 | 116 | $excludePath = $exclude['absolutepath'] 117 | 118 | # output the result to the return list 119 | ('-skip:objectName={0},absolutePath=''{1}''' -f $objName, $excludePath) 120 | } 121 | } 122 | } 123 | } 124 | 125 | function GetInternal-ReplacementsMSDeployArgs{ 126 | [cmdletbinding()] 127 | param( 128 | $publishProperties 129 | ) 130 | process{ 131 | foreach($replace in ($publishProperties['Replacements'])){ 132 | if($replace){ 133 | $typeValue = $replace['type'] 134 | if(!$typeValue){ $typeValue = 'TextFile' } 135 | 136 | $file = $replace['file'] 137 | $match = $replace['match'] 138 | $newValue = $replace['newValue'] 139 | 140 | if($file -and $match -and $newValue){ 141 | $setParam = ('-setParam:type={0},scope={1},match={2},value={3}' -f $typeValue,$file, $match,$newValue) 142 | 'Adding setparam [{0}]' -f $setParam | Write-Verbose 143 | 144 | # return it 145 | $setParam 146 | } 147 | else{ 148 | 'Skipping replacement because its missing a required value.[file="{0}",match="{1}",newValue="{2}"]' -f $file,$match,$newValue | Write-Verbose 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | <# 156 | .SYNOPSIS 157 | Returns an array of msdeploy arguments that are used across different providers. 158 | For example this will handle useChecksum, AppOffline etc. 159 | This will also add default properties if they are missing. 160 | #> 161 | function GetInternal-SharedMSDeployParametersFrom{ 162 | [cmdletbinding()] 163 | param( 164 | [Parameter(Mandatory=$true,Position=0)] 165 | [HashTable]$publishProperties, 166 | [Parameter(Mandatory=$true,Position=1)] 167 | [System.IO.FileInfo]$packOutput 168 | ) 169 | process{ 170 | $sharedArgs = New-Object psobject -Property @{ 171 | ExtraArgs = @() 172 | DestFragment = '' 173 | EFMigrationData = @{} 174 | } 175 | 176 | # add default properties if they are missing 177 | foreach($propName in $global:AspNetPublishSettings.MsdeployDefaultProperties.Keys){ 178 | if($publishProperties["$propName"] -eq $null){ 179 | $defValue = $global:AspNetPublishSettings.MsdeployDefaultProperties["$propName"] 180 | 'Adding default property to publishProperties ["{0}"="{1}"]' -f $propName,$defValue | Write-Verbose 181 | $publishProperties["$propName"] = $defValue 182 | } 183 | } 184 | 185 | if($publishProperties['MSDeployUseChecksum'] -eq $true){ 186 | $sharedArgs.ExtraArgs += '-usechecksum' 187 | } 188 | 189 | if($publishProperties['EnableMSDeployAppOffline'] -eq $true){ 190 | $sharedArgs.ExtraArgs += '-enablerule:AppOffline' 191 | } 192 | 193 | if($publishProperties['WebPublishMethod'] -eq 'MSDeploy'){ 194 | if($publishProperties['SkipExtraFilesOnServer'] -eq $true){ 195 | $sharedArgs.ExtraArgs += '-enableRule:DoNotDeleteRule' 196 | } 197 | } 198 | 199 | if($publishProperties['WebPublishMethod'] -eq 'FileSystem'){ 200 | if($publishProperties['DeleteExistingFiles'] -eq $false){ 201 | $sharedArgs.ExtraArgs += '-enableRule:DoNotDeleteRule' 202 | } 203 | } 204 | 205 | if($publishProperties['retryAttempts']){ 206 | $sharedArgs.ExtraArgs += ('-retryAttempts:{0}' -f ([int]$publishProperties['retryAttempts'])) 207 | } 208 | 209 | if($publishProperties['EncryptWebConfig'] -eq $true){ 210 | $sharedArgs.ExtraArgs += '-EnableRule:EncryptWebConfig' 211 | } 212 | 213 | if($publishProperties['EnableMSDeployBackup'] -eq $false){ 214 | $sharedArgs.ExtraArgs += '-disablerule:BackupRule' 215 | } 216 | 217 | if($publishProperties['AllowUntrustedCertificate'] -eq $true){ 218 | $sharedArgs.ExtraArgs += '-allowUntrusted' 219 | } 220 | 221 | # add excludes 222 | $sharedArgs.ExtraArgs += (GetInternal-ExcludeFilesArg -publishProperties $publishProperties) 223 | # add replacements 224 | $sharedArgs.ExtraArgs += (GetInternal-ReplacementsMSDeployArgs -publishProperties $publishProperties) 225 | 226 | # add EF Migration 227 | if (($publishProperties['EfMigrations'] -ne $null) -and $publishProperties['EfMigrations'].Count -gt 0){ 228 | if (!(Test-Path -Path $publishProperties['ProjectPath'])) { 229 | throw 'ProjectPath property needs to be defined in the pubxml for EF migration.' 230 | } 231 | try { 232 | # generate T-SQL files 233 | $EFSqlFiles = GenerateInternal-EFMigrationScripts -projectPath $publishProperties['ProjectPath'] -packOutput $packOutput -EFMigrations $publishProperties['EfMigrations'] 234 | $sharedArgs.EFMigrationData.Add('EFSqlFiles',$EFSqlFiles) 235 | } 236 | catch { 237 | throw ('An error occurred while generating EF migrations. {0} {1}' -f $_.Exception,(Get-PSCallStack)) 238 | } 239 | } 240 | # add connection string update 241 | if (($publishProperties['DestinationConnectionStrings'] -ne $null) -and $publishProperties['DestinationConnectionStrings'].Count -gt 0) { 242 | try { 243 | # create/update appsettings.[environment].json 244 | GenerateInternal-AppSettingsFile -packOutput $packOutput -environmentName $publishProperties['EnvironmentName'] -connectionStrings $publishProperties['DestinationConnectionStrings'] 245 | } 246 | catch { 247 | throw ('An error occurred while generating the publish appsettings file. {0} {1}' -f $_.Exception,(Get-PSCallStack)) 248 | } 249 | } 250 | 251 | if(-not [string]::IsNullOrWhiteSpace($publishProperties['ProjectGuid'])) { 252 | AddInternal-ProjectGuidToWebConfig -publishProperties $publishProperties -packOutput $packOutput 253 | } 254 | 255 | # return the args 256 | $sharedArgs 257 | } 258 | } 259 | 260 | <# 261 | .SYNOPSIS 262 | This will publish the folder based on the properties in $publishProperties 263 | 264 | .PARAMETER publishProperties 265 | This is a hashtable containing the publish properties. See the examples here for more info on how to use this parameter. 266 | 267 | .PARAMETER packOutput 268 | The folder path to the output of the dnu publish command. This folder contains the files 269 | that will be published. 270 | 271 | .PARAMETER pubProfilePath 272 | Path to a publish profile (.pubxml file) to import publish properties from. If the same property exists in 273 | publishProperties and the publish profile then publishProperties will win. 274 | 275 | .EXAMPLE 276 | Publish-AspNet -packOutput $packOutput -publishProperties @{ 277 | 'WebPublishMethod'='MSDeploy' 278 | 'MSDeployServiceURL'='contoso.scm.azurewebsites.net:443';` 279 | 'DeployIisAppPath'='contoso';'Username'='$contoso';'Password'="$env:PublishPwd"} 280 | 281 | .EXAMPLE 282 | Publish-AspNet -packOutput $packOutput -publishProperties @{ 283 | 'WebPublishMethod'='FileSystem' 284 | 'publishUrl'="$publishDest" 285 | } 286 | 287 | .EXAMPLE 288 | Publish-AspNet -packOutput $packOutput -publishProperties @{ 289 | 'WebPublishMethod'='MSDeploy' 290 | 'MSDeployServiceURL'='contoso.scm.azurewebsites.net:443';` 291 | 'DeployIisAppPath'='contoso';'Username'='$contoso';'Password'="$env:PublishPwd" 292 | 'ExcludeFiles'=@( 293 | @{'absolutepath'='test.txt'}, 294 | @{'absolutepath'='references.js'} 295 | )} 296 | 297 | .EXAMPLE 298 | Publish-AspNet -packOutput $packOutput -publishProperties @{ 299 | 'WebPublishMethod'='FileSystem' 300 | 'publishUrl'="$publishDest" 301 | 'ExcludeFiles'=@( 302 | @{'absolutepath'='test.txt'}, 303 | @{'absolutepath'='_references.js'}) 304 | 'Replacements' = @( 305 | @{'file'='test.txt$';'match'='REPLACEME';'newValue'='updatedValue'}) 306 | } 307 | 308 | Publish-AspNet -packOutput $packOutput -publishProperties @{ 309 | 'WebPublishMethod'='FileSystem' 310 | 'publishUrl'="$publishDest" 311 | 'ExcludeFiles'=@( 312 | @{'absolutepath'='test.txt'}, 313 | @{'absolutepath'='c:\\full\\path\\ok\\as\\well\\_references.js'}) 314 | 'Replacements' = @( 315 | @{'file'='test.txt$';'match'='REPLACEME';'newValue'='updatedValue'}) 316 | } 317 | 318 | .EXAMPLE 319 | Publish-AspNet -packOutput $packOutput -publishProperties @{ 320 | 'WebPublishMethod'='FileSystem' 321 | 'publishUrl'="$publishDest" 322 | 'EnableMSDeployAppOffline'='true' 323 | 'AppOfflineTemplate'='offline-template.html' 324 | 'MSDeployUseChecksum'='true' 325 | } 326 | #> 327 | function Publish-AspNet{ 328 | param( 329 | [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 330 | [hashtable]$publishProperties = @{}, 331 | 332 | [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] 333 | [System.IO.FileInfo]$packOutput, 334 | 335 | [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)] 336 | [System.IO.FileInfo]$pubProfilePath 337 | ) 338 | process{ 339 | if($publishProperties['WebPublishMethodOverride']){ 340 | 'Overriding publish method from $publishProperties[''WebPublishMethodOverride''] to [{0}]' -f ($publishProperties['WebPublishMethodOverride']) | Write-Verbose 341 | $publishProperties['WebPublishMethod'] = $publishProperties['WebPublishMethodOverride'] 342 | } 343 | 344 | if(-not [string]::IsNullOrWhiteSpace($pubProfilePath)){ 345 | $profileProperties = Get-PropertiesFromPublishProfile -filepath $pubProfilePath 346 | foreach($key in $profileProperties.Keys){ 347 | if(-not ($publishProperties.ContainsKey($key))){ 348 | 'Adding properties from publish profile [''{0}''=''{1}'']' -f $key,$profileProperties[$key] | Write-Verbose 349 | $publishProperties.Add($key,$profileProperties[$key]) 350 | } 351 | } 352 | } 353 | 354 | if(!([System.IO.Path]::IsPathRooted($packOutput))){ 355 | $packOutput = [System.IO.Path]::GetFullPath((Join-Path $pwd $packOutput)) 356 | } 357 | 358 | $pubMethod = $publishProperties['WebPublishMethod'] 359 | 'Publishing with publish method [{0}]' -f $pubMethod | Write-Output 360 | 361 | # get the handler based on WebPublishMethod, and call it. 362 | &(Get-AspnetPublishHandler -name $pubMethod) $publishProperties $packOutput 363 | } 364 | } 365 | 366 | <# 367 | .SYNOPSIS 368 | 369 | Inputs: 370 | 371 | Example of $xmlDocument: '' 372 | Example of $providerDataArray: 373 | 374 | [System.Collections.ArrayList]$providerDataArray = @() 375 | 376 | $iisAppSourceKeyValue=@{"iisApp" = @{"path"='c:\temp\pathtofiles';"appOfflineTemplate" ='offline-template.html'}} 377 | $providerDataArray.Add($iisAppSourceKeyValue) 378 | 379 | $dbfullsqlKeyValue=@{"dbfullsql" = @{"path"="c:\Temp\PathToSqlFile"}} 380 | $providerDataArray.Add($dbfullsqlKeyValue) 381 | 382 | $dbfullsqlKeyValue=@{"dbfullsql" = @{"path"="c:\Temp\PathToSqlFile2"}} 383 | $providerDataArray.Add($dbfullsqlKeyValue) 384 | 385 | Manifest File content: 386 | 387 | 388 | 389 | 390 | 391 | 392 | #> 393 | function AddInternal-ProviderDataToManifest { 394 | [cmdletbinding()] 395 | param( 396 | [Parameter(Mandatory=$true, Position=0)] 397 | [XML]$xmlDocument, 398 | [Parameter(Position=1)] 399 | [System.Collections.ArrayList]$providerDataArray 400 | ) 401 | process { 402 | $siteNode = $xmlDocument.SelectSingleNode("/sitemanifest") 403 | if ($siteNode -eq $null) { 404 | throw 'sitemanifest element is missing in the xml object' 405 | } 406 | foreach ($providerData in $providerDataArray) { 407 | foreach ($providerName in $providerData.Keys) { 408 | $providerValue = $providerData[$providerName] 409 | $xmlNode = $xmlDocument.CreateElement($providerName) 410 | foreach ($providerValueKey in $providerValue.Keys) { 411 | $xmlNode.SetAttribute($providerValueKey, $providerValue[$providerValueKey]) | Out-Null 412 | } 413 | $siteNode.AppendChild($xmlNode) | Out-Null 414 | } 415 | } 416 | } 417 | } 418 | 419 | function AddInternal-ProjectGuidToWebConfig { 420 | [cmdletbinding()] 421 | param( 422 | [Parameter(Position=0)] 423 | [HashTable]$publishProperties, 424 | [Parameter(Position=1)] 425 | [System.IO.FileInfo]$packOutput 426 | ) 427 | process { 428 | try { 429 | [Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq") | Out-Null 430 | $webConfigPath = Join-Path $packOutput 'web.config' 431 | $projectGuidCommentValue = 'ProjectGuid: {0}' -f $publishProperties['ProjectGuid'] 432 | $xDoc = [System.Xml.Linq.XDocument]::Load($webConfigPath) 433 | $allNodes = $xDoc.DescendantNodes() 434 | $projectGuidComment = $allNodes | Where-Object { $_.NodeType -eq [System.Xml.XmlNodeType]::Comment -and $_.Value -eq $projectGuidCommentValue } | Select -First 1 435 | if($projectGuidComment -ne $null) { 436 | if($publishProperties['IgnoreProjectGuid'] -eq $true) { 437 | $projectGuidComment.Remove() | Out-Null 438 | $xDoc.Save($webConfigPath) | Out-Null 439 | } 440 | } 441 | else { 442 | if(-not ($publishProperties['IgnoreProjectGuid'] -eq $true)) { 443 | $projectGuidComment = New-Object -TypeName System.Xml.Linq.XComment -ArgumentList $projectGuidCommentValue 444 | $xDoc.LastNode.AddAfterSelf($projectGuidComment) | Out-Null 445 | $xDoc.Save($webConfigPath) | Out-Null 446 | } 447 | } 448 | } 449 | catch { 450 | } 451 | } 452 | } 453 | 454 | <# 455 | .SYNOPSIS 456 | 457 | Example of $EFMigrations: 458 | $EFMigrations = @{'CarContext'='Car Context ConnectionString';'MovieContext'='Movie Context Connection String'} 459 | 460 | #> 461 | 462 | function GenerateInternal-EFMigrationScripts { 463 | [cmdletbinding()] 464 | param( 465 | [Parameter(Mandatory=$true,Position=0)] 466 | [System.IO.FileInfo]$projectPath, 467 | [Parameter(Mandatory=$true,Position=1)] 468 | [System.IO.FileInfo]$packOutput, 469 | [Parameter(Position=2)] 470 | [HashTable]$EFMigrations 471 | ) 472 | process { 473 | $files = @{} 474 | $dotnetExePath = GetInternal-DotNetExePath 475 | foreach ($dbContextName in $EFMigrations.Keys) { 476 | try 477 | { 478 | $tempDir = GetInternal-PublishTempPath -packOutput $packOutput 479 | $efScriptFile = Join-Path $tempDir ('{0}.sql' -f $dbContextName) 480 | $arg = ('ef migrations script --idempotent --output {0} --context {1}' -f 481 | $efScriptFile, 482 | $dbContextName) 483 | 484 | Execute-Command $dotnetExePath $arg $projectPath | Out-Null 485 | if (Test-Path -Path $efScriptFile) { 486 | if (!($files.ContainsKey($dbContextName))) { 487 | $files.Add($dbContextName, $efScriptFile) | Out-Null 488 | } 489 | } 490 | } 491 | catch 492 | { 493 | throw 'error occured when executing dotnet.exe to generate EF T-SQL file' 494 | } 495 | } 496 | # return files object 497 | $files 498 | } 499 | } 500 | 501 | <# 502 | .SYNOPSIS 503 | 504 | Example of $connectionStrings: 505 | $connectionStrings = @{'DefaultConnection'='Default ConnectionString';'CarConnection'='Car Connection String'} 506 | 507 | #> 508 | function GenerateInternal-AppSettingsFile { 509 | [cmdletbinding()] 510 | param( 511 | [Parameter(Mandatory = $true,Position=0)] 512 | [System.IO.FileInfo]$packOutput, 513 | [Parameter(Mandatory = $true,Position=1)] 514 | [string]$environmentName, 515 | [Parameter(Position=2)] 516 | [HashTable]$connectionStrings 517 | ) 518 | process { 519 | $configProdJsonFile = 'appsettings.{0}.json' -f $environmentName 520 | $configProdJsonFilePath = Join-Path -Path $packOutput -ChildPath $configProdJsonFile 521 | 522 | if ([string]::IsNullOrEmpty($configProdJsonFilePath)) { 523 | throw ('The path of {0} is empty' -f $configProdJsonFilePath) 524 | } 525 | 526 | if(!(Test-Path -Path $configProdJsonFilePath)) { 527 | # create new file 528 | '{}' | out-file -encoding utf8 -filePath $configProdJsonFilePath -Force 529 | } 530 | 531 | $jsonObj = ConvertFrom-Json -InputObject (Get-Content -Path $configProdJsonFilePath -Raw) 532 | # update when there exists one or more connection strings 533 | if ($connectionStrings -ne $null) { 534 | foreach ($name in $connectionStrings.Keys) { 535 | #check for hierarchy style 536 | if ($jsonObj.ConnectionStrings.$name) { 537 | $jsonObj.ConnectionStrings.$name = $connectionStrings[$name] 538 | continue 539 | } 540 | #check for horizontal style 541 | $horizontalName = 'ConnectionStrings.{0}:' -f $name 542 | if ($jsonObj.$horizontalName) { 543 | $jsonObj.$horizontalName = $connectionStrings[$name] 544 | continue 545 | } 546 | # create new one 547 | if (!($jsonObj.ConnectionStrings)) { 548 | $contentForDefaultConnection = '{}' 549 | $jsonObj | Add-Member -name 'ConnectionStrings' -value (ConvertFrom-Json -InputObject $contentForDefaultConnection) -MemberType NoteProperty | Out-Null 550 | } 551 | if (!($jsonObj.ConnectionStrings.$name)) { 552 | $jsonObj.ConnectionStrings | Add-Member -name $name -value $connectionStrings[$name] -MemberType NoteProperty | Out-Null 553 | } 554 | } 555 | } 556 | 557 | $jsonObj | ConvertTo-Json | out-file -encoding utf8 -filePath $configProdJsonFilePath -Force 558 | 559 | #return the path of config.[environment].json 560 | $configProdJsonFilePath 561 | } 562 | } 563 | 564 | <# 565 | .SYNOPSIS 566 | 567 | Inputs: 568 | Example of $providerDataArray: 569 | 570 | [System.Collections.ArrayList]$providerDataArray = @() 571 | 572 | $iisAppSourceKeyValue=@{"iisApp" = @{"path"='c:\temp\pathtofiles';"appOfflineTemplate" ='offline-template.html'}} 573 | $providerDataArray.Add($iisAppSourceKeyValue) 574 | 575 | $dbfullsqlKeyValue=@{"dbfullsql" = @{"path"="c:\Temp\PathToSqlFile"}} 576 | $providerDataArray.Add($dbfullsqlKeyValue) 577 | 578 | $dbfullsqlKeyValue=@{"dbfullsql" = @{"path"="c:\Temp\PathToSqlFile2"}} 579 | $providerDataArray.Add($dbfullsqlKeyValue) 580 | 581 | Manifest File content: 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | #> 590 | 591 | function GenerateInternal-ManifestFile { 592 | [cmdletbinding()] 593 | param( 594 | [Parameter(Mandatory=$true,Position=0)] 595 | [System.IO.FileInfo]$packOutput, 596 | [Parameter(Mandatory=$true,Position=1)] 597 | $publishProperties, 598 | [Parameter(Mandatory=$true,Position=2)] 599 | [System.Collections.ArrayList]$providerDataArray, 600 | [Parameter(Mandatory=$true,Position=3)] 601 | [ValidateNotNull()] 602 | $manifestFileName 603 | ) 604 | process{ 605 | $xmlDocument = [xml]'' 606 | AddInternal-ProviderDataToManifest -xmlDocument $xmlDocument -providerDataArray $providerDataArray | Out-Null 607 | $publishTempDir = GetInternal-PublishTempPath -packOutput $packOutput 608 | $XMLFile = Join-Path $publishTempDir $manifestFileName 609 | $xmlDocument.OuterXml | out-file -encoding utf8 -filePath $XMLFile -Force 610 | 611 | # return 612 | [System.IO.FileInfo]$XMLFile 613 | } 614 | } 615 | 616 | function GetInternal-PublishTempPath { 617 | [cmdletbinding()] 618 | param( 619 | [Parameter(Mandatory=$true, Position=0)] 620 | [System.IO.FileInfo]$packOutput 621 | ) 622 | process { 623 | $tempDir = [io.path]::GetTempPath() 624 | $packOutputFolderName = Split-Path $packOutput -Leaf 625 | $publishTempDir = [io.path]::combine($tempDir,'PublishTemp','obj',$packOutputFolderName) 626 | if (!(Test-Path -Path $publishTempDir)) { 627 | New-Item -Path $publishTempDir -type directory | Out-Null 628 | } 629 | # return 630 | [System.IO.FileInfo]$publishTempDir 631 | } 632 | } 633 | 634 | function Publish-AspNetMSDeploy{ 635 | param( 636 | [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 637 | $publishProperties, 638 | [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] 639 | $packOutput 640 | ) 641 | process{ 642 | if($publishProperties){ 643 | $publishPwd = $publishProperties['Password'] 644 | 645 | $sharedArgs = GetInternal-SharedMSDeployParametersFrom -publishProperties $publishProperties -packOutput $packOutput 646 | $iisAppPath = $publishProperties['DeployIisAppPath'] 647 | 648 | # create source manifest 649 | 650 | # e.g 651 | # 652 | # 653 | # 654 | # 655 | # 656 | # 657 | 658 | [System.Collections.ArrayList]$providerDataArray = @() 659 | $iisAppValues = @{"path"=$packOutput}; 660 | $iisAppSourceKeyValue=@{"iisApp" = $iisAppValues} 661 | $providerDataArray.Add($iisAppSourceKeyValue) | Out-Null 662 | 663 | if ($sharedArgs.EFMigrationData -ne $null -and $sharedArgs.EFMigrationData.Contains('EFSqlFiles')) { 664 | foreach ($sqlFile in $sharedArgs.EFMigrationData['EFSqlFiles'].Values) { 665 | $dbFullSqlSourceKeyValue=@{"dbFullSql" = @{"path"=$sqlFile}} 666 | $providerDataArray.Add($dbFullSqlSourceKeyValue) | Out-Null 667 | } 668 | } 669 | 670 | [System.IO.FileInfo]$sourceXMLFile = GenerateInternal-ManifestFile -packOutput $packOutput -publishProperties $publishProperties -providerDataArray $providerDataArray -manifestFileName 'SourceManifest.xml' 671 | 672 | $providerDataArray.Clear() | Out-Null 673 | # create destination manifest 674 | 675 | # e.g 676 | # 677 | # 678 | # 679 | # 680 | # 681 | 682 | $iisAppValues = @{"path"=$iisAppPath}; 683 | if(-not [string]::IsNullOrWhiteSpace($publishProperties['AppOfflineTemplate'])){ 684 | $iisAppValues.Add("appOfflineTemplate", $publishProperties['AppOfflineTemplate']) | Out-Null 685 | } 686 | 687 | $iisAppDestinationKeyValue=@{"iisApp" = $iisAppValues} 688 | $providerDataArray.Add($iisAppDestinationKeyValue) | Out-Null 689 | 690 | if ($publishProperties['EfMigrations'] -ne $null -and $publishProperties['EfMigrations'].Count -gt 0) { 691 | foreach ($connectionString in $publishProperties['EfMigrations'].Values) { 692 | $dbFullSqlDestinationKeyValue=@{"dbFullSql" = @{"path"=$connectionString}} 693 | $providerDataArray.Add($dbFullSqlDestinationKeyValue) | Out-Null 694 | } 695 | } 696 | 697 | 698 | [System.IO.FileInfo]$destXMLFile = GenerateInternal-ManifestFile -packOutput $packOutput -publishProperties $publishProperties -providerDataArray $providerDataArray -manifestFileName 'DestinationManifest.xml' 699 | 700 | <# 701 | "C:\Program Files (x86)\IIS\Microsoft Web Deploy V3\msdeploy.exe" 702 | -source:manifest='C:\Users\testuser\AppData\Local\Temp\PublishTemp\obj\SourceManifest.xml' 703 | -dest:manifest='C:\Users\testuser\AppData\Local\Temp\PublishTemp\obj\DestManifest.xml',ComputerName='https://contoso.scm.azurewebsites.net/msdeploy.axd',UserName='$contoso',Password='',IncludeAcls='False',AuthType='Basic' 704 | -verb:sync 705 | -enableRule:DoNotDeleteRule 706 | -retryAttempts=2" 707 | #> 708 | 709 | if(-not [string]::IsNullOrWhiteSpace($publishProperties['MSDeployPublishMethod'])){ 710 | $serviceMethod = $publishProperties['MSDeployPublishMethod'] 711 | } 712 | 713 | $msdeployComputerName= InternalNormalize-MSDeployUrl -serviceUrl $publishProperties['MSDeployServiceURL'] -siteName $iisAppPath -serviceMethod $publishProperties['MSDeployPublishMethod'] 714 | if($publishProperties['UseMSDeployServiceURLAsIs'] -eq $true){ 715 | $msdeployComputerName = $publishProperties['MSDeployServiceURL'] 716 | } 717 | 718 | $publishArgs = @() 719 | #use manifest to publish 720 | $publishArgs += ('-source:manifest=''{0}''' -f $sourceXMLFile.FullName) 721 | $publishArgs += ('-dest:manifest=''{0}'',ComputerName=''{1}'',UserName=''{2}'',Password=''{3}'',IncludeAcls=''False'',AuthType=''{4}''{5}' -f 722 | $destXMLFile.FullName, 723 | $msdeployComputerName, 724 | $publishProperties['UserName'], 725 | $publishPwd, 726 | $publishProperties['AuthType'], 727 | $sharedArgs.DestFragment) 728 | $publishArgs += '-verb:sync' 729 | $publishArgs += $sharedArgs.ExtraArgs 730 | 731 | $command = '"{0}" {1}' -f (Get-MSDeploy),($publishArgs -join ' ') 732 | 733 | if (! [String]::IsNullOrEmpty($publishPwd)) { 734 | $command.Replace($publishPwd,'{PASSWORD-REMOVED-FROM-LOG}') | Print-CommandString 735 | } 736 | Execute-Command -exePath (Get-MSDeploy) -arguments ($publishArgs -join ' ') 737 | } 738 | else{ 739 | throw 'publishProperties is empty, cannot publish' 740 | } 741 | } 742 | } 743 | 744 | function Escape-TextForRegularExpressions{ 745 | [cmdletbinding()] 746 | param( 747 | [Parameter(Position=0,Mandatory=$true)] 748 | [string]$text 749 | ) 750 | process{ 751 | [regex]::Escape($text) 752 | } 753 | } 754 | 755 | function Publish-AspNetMSDeployPackage{ 756 | param( 757 | [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 758 | $publishProperties, 759 | [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] 760 | $packOutput 761 | ) 762 | process{ 763 | if($publishProperties){ 764 | $packageDestinationFilepath = $publishProperties['DesktopBuildPackageLocation'] 765 | 766 | if(!$packageDestinationFilepath){ 767 | throw ('The package destination property (DesktopBuildPackageLocation) was not found in the publish properties') 768 | } 769 | 770 | if(!([System.IO.Path]::IsPathRooted($packageDestinationFilepath))){ 771 | $packageDestinationFilepath = [System.IO.Path]::GetFullPath((Join-Path $pwd $packageDestinationFilepath)) 772 | } 773 | 774 | # if the dir doesn't exist create it 775 | $pkgDir = ((new-object -typename System.IO.FileInfo($packageDestinationFilepath)).Directory) 776 | if(!(Test-Path -Path $pkgDir)) { 777 | New-Item $pkgDir -type Directory | Out-Null 778 | } 779 | 780 | <# 781 | "C:\Program Files (x86)\IIS\Microsoft Web Deploy V3\msdeploy.exe" 782 | -source:manifest='C:\Users\testuser\AppData\Local\Temp\PublishTemp\obj\SourceManifest.xml' 783 | -dest:package=c:\temp\path\contosoweb.zip 784 | -verb:sync 785 | -enableRule:DoNotDeleteRule 786 | -retryAttempts=2 787 | #> 788 | 789 | $sharedArgs = GetInternal-SharedMSDeployParametersFrom -publishProperties $publishProperties -packOutput $packOutput 790 | 791 | # create source manifest 792 | 793 | # e.g 794 | # 795 | # 796 | # 797 | # 798 | 799 | [System.Collections.ArrayList]$providerDataArray = @() 800 | $iisAppSourceKeyValue=@{"iisApp" = @{"path"=$packOutput}} 801 | $providerDataArray.Add($iisAppSourceKeyValue) | Out-Null 802 | 803 | [System.IO.FileInfo]$sourceXMLFile = GenerateInternal-ManifestFile -packOutput $packOutput -publishProperties $publishProperties -providerDataArray $providerDataArray -manifestFileName 'SourceManifest.xml' 804 | 805 | $publishArgs = @() 806 | $publishArgs += ('-source:manifest=''{0}''' -f $sourceXMLFile.FullName) 807 | $publishArgs += ('-dest:package=''{0}''' -f $packageDestinationFilepath) 808 | $publishArgs += '-verb:sync' 809 | $packageContentFolder = $publishProperties['MSDeployPackageContentFoldername'] 810 | if(!$packageContentFolder){ $packageContentFolder = 'website' } 811 | $publishArgs += ('-replace:match=''{0}'',replace=''{1}''' -f (Escape-TextForRegularExpressions $packOutput), $packageContentFolder ) 812 | $publishArgs += $sharedArgs.ExtraArgs 813 | 814 | $command = '"{0}" {1}' -f (Get-MSDeploy),($publishArgs -join ' ') 815 | $command | Print-CommandString 816 | Execute-Command -exePath (Get-MSDeploy) -arguments ($publishArgs -join ' ') 817 | } 818 | else{ 819 | throw 'publishProperties is empty, cannot publish' 820 | } 821 | } 822 | } 823 | 824 | function Publish-AspNetFileSystem{ 825 | param( 826 | [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 827 | $publishProperties, 828 | [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] 829 | $packOutput 830 | ) 831 | process{ 832 | $pubOut = $publishProperties['publishUrl'] 833 | 834 | if([string]::IsNullOrWhiteSpace($pubOut)){ 835 | throw ('publishUrl is a required property for FileSystem publish but it was empty.') 836 | } 837 | 838 | # if it's a relative path then update it to a full path 839 | if(!([System.IO.Path]::IsPathRooted($pubOut))){ 840 | $pubOut = [System.IO.Path]::GetFullPath((Join-Path $pwd $pubOut)) 841 | $publishProperties['publishUrl'] = "$pubOut" 842 | } 843 | 844 | 'Publishing files to {0}' -f $pubOut | Write-Output 845 | 846 | # we use msdeploy.exe because it supports incremental publish/skips/replacements/etc 847 | # msdeploy.exe -verb:sync -source:manifest='C:\Users\testuser\AppData\Local\Temp\PublishTemp\obj\SourceManifest.xml' -dest:manifest='C:\Users\testuser\AppData\Local\Temp\PublishTemp\obj\DestManifest.xml' 848 | 849 | $sharedArgs = GetInternal-SharedMSDeployParametersFrom -publishProperties $publishProperties -packOutput $packOutput 850 | 851 | # create source manifest 852 | 853 | # e.g 854 | # 855 | # 856 | # 857 | # 858 | 859 | [System.Collections.ArrayList]$providerDataArray = @() 860 | $contentPathValues = @{"path"=$packOutput}; 861 | $contentPathSourceKeyValue=@{"contentPath" = $contentPathValues} 862 | $providerDataArray.Add($contentPathSourceKeyValue) | Out-Null 863 | 864 | [System.IO.FileInfo]$sourceXMLFile = GenerateInternal-ManifestFile -packOutput $packOutput -publishProperties $publishProperties -providerDataArray $providerDataArray -manifestFileName 'SourceManifest.xml' 865 | 866 | $providerDataArray.Clear() | Out-Null 867 | # create destination manifest 868 | 869 | # e.g 870 | # 871 | # 872 | # 873 | $contentPathValues = @{"path"=$publishProperties['publishUrl']}; 874 | if(-not [string]::IsNullOrWhiteSpace($publishProperties['AppOfflineTemplate'])){ 875 | $contentPathValues.Add("appOfflineTemplate", $publishProperties['AppOfflineTemplate']) | Out-Null 876 | } 877 | $contentPathDestinationKeyValue=@{"contentPath" = $contentPathValues} 878 | $providerDataArray.Add($contentPathDestinationKeyValue) | Out-Null 879 | 880 | [System.IO.FileInfo]$destXMLFile = GenerateInternal-ManifestFile -packOutput $packOutput -publishProperties $publishProperties -providerDataArray $providerDataArray -manifestFileName 'DestinationManifest.xml' 881 | 882 | $publishArgs = @() 883 | $publishArgs += ('-source:manifest=''{0}''' -f $sourceXMLFile.FullName) 884 | $publishArgs += ('-dest:manifest=''{0}''{1}' -f $destXMLFile.FullName, $sharedArgs.DestFragment) 885 | $publishArgs += '-verb:sync' 886 | $publishArgs += $sharedArgs.ExtraArgs 887 | 888 | $command = '"{0}" {1}' -f (Get-MSDeploy),($publishArgs -join ' ') 889 | $command | Print-CommandString 890 | Execute-Command -exePath (Get-MSDeploy) -arguments ($publishArgs -join ' ') 891 | 892 | # copy sql script to script folder 893 | if (($sharedArgs.EFMigrationData['EFSqlFiles'] -ne $null) -and ($sharedArgs.EFMigrationData['EFSqlFiles'].Count -gt 0)) { 894 | $scriptsDir = Join-Path $pubOut 'efscripts' 895 | 896 | if (!(Test-Path -Path $scriptsDir)) { 897 | New-Item -Path $scriptsDir -type directory | Out-Null 898 | } 899 | 900 | foreach ($sqlFile in $sharedArgs.EFMigrationData['EFSqlFiles'].Values) { 901 | Copy-Item $sqlFile -Destination $scriptsDir -Force -Recurse | Out-Null 902 | } 903 | } 904 | } 905 | } 906 | 907 | <# 908 | .SYNOPSIS 909 | This can be used to read a publish profile to extract the property values into a hashtable. 910 | 911 | .PARAMETER filepath 912 | Path to the publish profile to get the properties from. Currenlty this only supports reading 913 | .pubxml files. 914 | 915 | .EXAMPLE 916 | Get-PropertiesFromPublishProfile -filepath c:\projects\publish\devpublish.pubxml 917 | #> 918 | function Get-PropertiesFromPublishProfile{ 919 | [cmdletbinding()] 920 | param( 921 | [Parameter(Position=0,Mandatory=$true)] 922 | [ValidateNotNull()] 923 | [ValidateScript({Test-Path $_})] 924 | [System.IO.FileInfo]$filepath 925 | ) 926 | begin{ 927 | Add-Type -AssemblyName System.Core 928 | Add-Type -AssemblyName Microsoft.Build 929 | } 930 | process{ 931 | 'Reading publish properties from profile [{0}]' -f $filepath | Write-Verbose 932 | # use MSBuild to get the project and read properties 933 | $projectCollection = (New-Object Microsoft.Build.Evaluation.ProjectCollection) 934 | if(!([System.IO.Path]::IsPathRooted($filepath))){ 935 | $filepath = [System.IO.Path]::GetFullPath((Join-Path $pwd $filepath)) 936 | } 937 | $project = ([Microsoft.Build.Construction.ProjectRootElement]::Open([string]$filepath.Fullname, $projectCollection)) 938 | 939 | $properties = @{} 940 | foreach($property in $project.Properties){ 941 | $properties[$property.Name]=$property.Value 942 | } 943 | 944 | $properties 945 | } 946 | } 947 | 948 | function Print-CommandString{ 949 | [cmdletbinding()] 950 | param( 951 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)] 952 | $command 953 | ) 954 | process{ 955 | 'Executing command [{0}]' -f $command | Write-Output 956 | } 957 | } 958 | 959 | function Execute-CommandString{ 960 | [cmdletbinding()] 961 | param( 962 | [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)] 963 | [string[]]$command, 964 | 965 | [switch] 966 | $useInvokeExpression, 967 | 968 | [switch] 969 | $ignoreErrors 970 | ) 971 | process{ 972 | foreach($cmdToExec in $command){ 973 | 'Executing command [{0}]' -f $cmdToExec | Write-Verbose 974 | if($useInvokeExpression){ 975 | try { 976 | Invoke-Expression -Command $cmdToExec 977 | } 978 | catch { 979 | if(-not $ignoreErrors){ 980 | $msg = ('The command [{0}] exited with exception [{1}]' -f $cmdToExec, $_.ToString()) 981 | throw $msg 982 | } 983 | } 984 | } 985 | else { 986 | cmd.exe /D /C $cmdToExec 987 | 988 | if(-not $ignoreErrors -and ($LASTEXITCODE -ne 0)){ 989 | $msg = ('The command [{0}] exited with code [{1}]' -f $cmdToExec, $LASTEXITCODE) 990 | throw $msg 991 | } 992 | } 993 | } 994 | } 995 | } 996 | 997 | function Execute-Command { 998 | [cmdletbinding()] 999 | param( 1000 | [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 1001 | [String]$exePath, 1002 | [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] 1003 | [String]$arguments, 1004 | [Parameter(Position=2)] 1005 | [System.IO.FileInfo]$workingDirectory 1006 | ) 1007 | process{ 1008 | $psi = New-Object -TypeName System.Diagnostics.ProcessStartInfo 1009 | $psi.CreateNoWindow = $true 1010 | $psi.UseShellExecute = $false 1011 | $psi.RedirectStandardOutput = $true 1012 | $psi.RedirectStandardError=$true 1013 | $psi.FileName = $exePath 1014 | $psi.Arguments = $arguments 1015 | if($workingDirectory -and (Test-Path -Path $workingDirectory)) { 1016 | $psi.WorkingDirectory = $workingDirectory 1017 | } 1018 | 1019 | $process = New-Object -TypeName System.Diagnostics.Process 1020 | $process.StartInfo = $psi 1021 | $process.EnableRaisingEvents=$true 1022 | 1023 | # Register the event handler for error 1024 | $stdErrEvent = Register-ObjectEvent -InputObject $process -EventName 'ErrorDataReceived' -Action { 1025 | if (! [String]::IsNullOrEmpty($EventArgs.Data)) { 1026 | $EventArgs.Data | Write-Error 1027 | } 1028 | } 1029 | 1030 | # Starting process. 1031 | $process.Start() | Out-Null 1032 | $process.BeginErrorReadLine() | Out-Null 1033 | $output = $process.StandardOutput.ReadToEnd() 1034 | $process.WaitForExit() | Out-Null 1035 | $output | Write-Output 1036 | 1037 | # UnRegister the event handler for error 1038 | Unregister-Event -SourceIdentifier $stdErrEvent.Name | Out-Null 1039 | } 1040 | } 1041 | 1042 | 1043 | function GetInternal-DotNetExePath { 1044 | process { 1045 | $dotnetinstallpath = $env:dotnetinstallpath 1046 | if (!$dotnetinstallpath) { 1047 | $DotNetRegItem = Get-ItemProperty -Path 'hklm:\software\dotnet\setup\' 1048 | if ($env:DOTNET_HOME) { 1049 | $dotnetinstallpath = Join-Path $env:DOTNET_HOME -ChildPath 'dotnet.exe' 1050 | } 1051 | elseif ($DotNetRegItem -and $DotNetRegItem.InstallDir){ 1052 | $dotnetinstallpath = Join-Path $DotNetRegItem.InstallDir -ChildPath 'dotnet.exe' 1053 | } 1054 | } 1055 | if (!(Test-Path $dotnetinstallpath)) { 1056 | throw 'Unable to find dotnet.exe, please install it and try again' 1057 | } 1058 | # return 1059 | [System.IO.FileInfo]$dotnetinstallpath 1060 | } 1061 | } 1062 | 1063 | function Get-MSDeploy{ 1064 | [cmdletbinding()] 1065 | param() 1066 | process{ 1067 | $installPath = $env:msdeployinstallpath 1068 | 1069 | if(!$installPath){ 1070 | $keysToCheck = @('hklm:\SOFTWARE\Microsoft\IIS Extensions\MSDeploy\3','hklm:\SOFTWARE\Microsoft\IIS Extensions\MSDeploy\2','hklm:\SOFTWARE\Microsoft\IIS Extensions\MSDeploy\1') 1071 | 1072 | foreach($keyToCheck in $keysToCheck){ 1073 | if(Test-Path $keyToCheck){ 1074 | $installPath = (Get-itemproperty $keyToCheck -Name InstallPath -ErrorAction SilentlyContinue | select -ExpandProperty InstallPath -ErrorAction SilentlyContinue) 1075 | } 1076 | 1077 | if($installPath){ 1078 | break; 1079 | } 1080 | } 1081 | } 1082 | 1083 | if(!$installPath){ 1084 | throw "Unable to find msdeploy.exe, please install it and try again" 1085 | } 1086 | 1087 | [string]$msdInstallLoc = (join-path $installPath 'msdeploy.exe') 1088 | 1089 | "Found msdeploy.exe at [{0}]" -f $msdInstallLoc | Write-Verbose 1090 | 1091 | $msdInstallLoc 1092 | } 1093 | } 1094 | 1095 | function InternalNormalize-MSDeployUrl{ 1096 | [cmdletbinding()] 1097 | param( 1098 | [Parameter(Position=0,Mandatory=$true)] 1099 | [string]$serviceUrl, 1100 | 1101 | [string] $siteName, 1102 | 1103 | [ValidateSet('WMSVC','RemoteAgent','InProc')] 1104 | [string]$serviceMethod = 'WMSVC' 1105 | ) 1106 | process{ 1107 | $tempUrl = $serviceUrl 1108 | $resultUrl = $serviceUrl 1109 | 1110 | $httpsStr = 'https://' 1111 | $httpStr = 'http://' 1112 | $msdeployAxd = 'msdeploy.axd' 1113 | 1114 | if(-not [string]::IsNullOrWhiteSpace($serviceUrl)){ 1115 | if([string]::Compare($serviceMethod,'WMSVC',[StringComparison]::OrdinalIgnoreCase) -eq 0){ 1116 | # if no http or https then add one 1117 | if(-not ($serviceUrl.StartsWith($httpStr,[StringComparison]::OrdinalIgnoreCase) -or 1118 | $serviceUrl.StartsWith($httpsStr,[StringComparison]::OrdinalIgnoreCase)) ){ 1119 | 1120 | $serviceUrl = [string]::Concat($httpsStr,$serviceUrl.TrimStart()) 1121 | } 1122 | [System.Uri]$serviceUri = New-Object -TypeName 'System.Uri' $serviceUrl 1123 | [System.UriBuilder]$serviceUriBuilder = New-Object -TypeName 'System.UriBuilder' $serviceUrl 1124 | 1125 | # if it's https and the port was not passed in override it to 8172 1126 | if( ([string]::Compare('https',$serviceUriBuilder.Scheme,[StringComparison]::OrdinalIgnoreCase) -eq 0) -and 1127 | -not $serviceUrl.Contains((':{0}' -f $serviceUriBuilder.Port)) ) { 1128 | $serviceUriBuilder.Port = 8172 1129 | } 1130 | 1131 | # if no path then add one 1132 | if([string]::Compare('/',$serviceUriBuilder.Path,[StringComparison]::OrdinalIgnoreCase) -eq 0){ 1133 | $serviceUriBuilder.Path = $msdeployAxd 1134 | } 1135 | 1136 | if ([string]::IsNullOrEmpty($serviceUriBuilder.Query) -and -not([string]::IsNullOrEmpty($siteName))) 1137 | { 1138 | $serviceUriBuilder.Query = "site=" + $siteName; 1139 | } 1140 | 1141 | $resultUrl = $serviceUriBuilder.Uri.AbsoluteUri 1142 | } 1143 | elseif([string]::Compare($serviceMethod,'RemoteAgent',[StringComparison]::OrdinalIgnoreCase) -eq 0){ 1144 | [System.UriBuilder]$serviceUriBuilder = New-Object -TypeName 'System.UriBuilder' $serviceUrl 1145 | # http://{computername}/MSDEPLOYAGENTSERVICE 1146 | # remote agent must use http 1147 | $serviceUriBuilder.Scheme = 'http' 1148 | $serviceUriBuilder.Path = '/MSDEPLOYAGENTSERVICE' 1149 | 1150 | $resultUrl = $serviceUriBuilder.Uri.AbsoluteUri 1151 | } 1152 | else{ 1153 | # see if it's for localhost 1154 | [System.Uri]$serviceUri = New-Object -TypeName 'System.Uri' $serviceUrl 1155 | $resultUrl = $serviceUri.AbsoluteUri 1156 | } 1157 | } 1158 | 1159 | # return the result to the caller 1160 | $resultUrl 1161 | } 1162 | } 1163 | 1164 | function InternalRegister-AspNetKnownPublishHandlers{ 1165 | [cmdletbinding()] 1166 | param() 1167 | process{ 1168 | 'Registering MSDeploy handler' | Write-Verbose 1169 | Register-AspnetPublishHandler -name 'MSDeploy' -force -handler { 1170 | [cmdletbinding()] 1171 | param( 1172 | [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 1173 | $publishProperties, 1174 | [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] 1175 | $packOutput 1176 | ) 1177 | 1178 | Publish-AspNetMSDeploy -publishProperties $publishProperties -packOutput $packOutput 1179 | } 1180 | 1181 | 'Registering MSDeploy package handler' | Write-Verbose 1182 | Register-AspnetPublishHandler -name 'Package' -force -handler { 1183 | [cmdletbinding()] 1184 | param( 1185 | [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 1186 | $publishProperties, 1187 | [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] 1188 | $packOutput 1189 | ) 1190 | 1191 | Publish-AspNetMSDeployPackage -publishProperties $publishProperties -packOutput $packOutput 1192 | } 1193 | 1194 | 'Registering FileSystem handler' | Write-Verbose 1195 | Register-AspnetPublishHandler -name 'FileSystem' -force -handler { 1196 | [cmdletbinding()] 1197 | param( 1198 | [Parameter(Mandatory = $true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 1199 | $publishProperties, 1200 | [Parameter(Mandatory = $true,Position=1,ValueFromPipelineByPropertyName=$true)] 1201 | $packOutput 1202 | ) 1203 | 1204 | Publish-AspNetFileSystem -publishProperties $publishProperties -packOutput $packOutput 1205 | } 1206 | } 1207 | } 1208 | 1209 | <# 1210 | .SYNOPSIS 1211 | Used for testing purposes only. 1212 | #> 1213 | function InternalReset-AspNetPublishHandlers{ 1214 | [cmdletbinding()] 1215 | param() 1216 | process{ 1217 | $script:AspNetPublishHandlers = @{} 1218 | InternalRegister-AspNetKnownPublishHandlers 1219 | } 1220 | } 1221 | 1222 | Export-ModuleMember -function Get-*,Publish-*,Register-*,Enable-* 1223 | if($env:IsDeveloperMachine){ 1224 | # you can set the env var to expose all functions to importer. easy for development. 1225 | # this is required for executing pester test cases, it's set by build.ps1 1226 | Export-ModuleMember -function * 1227 | } 1228 | 1229 | # register the handlers so that Publish-AspNet can be called 1230 | InternalRegister-AspNetKnownPublishHandlers 1231 | 1232 | --------------------------------------------------------------------------------