Add BasicApi and BasicViews apps

- #7805
- make initial copy of apps from aspnet/Performance repo
  - add apps to solution
  - add Readme for the benchmark apps
- update BasicApi app to actually do authentication and authorization
  - bug in the ported app
- refactor `Main` methods and add `CreateWebHostBuilder(...)` methods
- change projects to understand `$(BenchmarksTargetFramework)`
  - use NuGet.org EF packages to avoid changing the Universe build graph
- use SQLite instead of LocalDb by default
  - remove unnecessary appsettings.json files and JSON configuration support
- add EF migrations
  - (greatly) reduce startup times compared to creating / deleting databases
- add MySql, PostgreSQL, and SqlServer support
  - load BasicApi data in a `DbContext.OnModelCreating(...)` override
    - no longer need seed.sql
  - generalize migrations to support multiple providers
  - use negative seeding indices to work around npgsql/Npgsql.EntityFrameworkCore.PostgreSQL#36
  - work around Pomelo lack of strong name (PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#603)
  - use BenchmarksOnly* properties for EF dependencies
    - see also aspnet/Universe#1224
- drop databases (if SQLite) or migrations (otherwise) in `IApplicationLifetime.ApplicationStopping` handlers
- add functional tests
  - drop SQLite database at end of test run
- add benchmarks automation
  - add anonymous BasicApi action i.e. require no authorization
  - add non-antiforgery BasicViews actions

Address PR comments
- remove `AntiforgeryTestHelper` workarounds
- use `[ApiController]`
- use `ActionResult<Pet>`
- remove unused classes

nits:
- take VS suggestions in added files
- optionally display create and delete SQL scripts for per-database migrations
- merge `InsertData(...)` calls for consistency with most supported EF providers
  - SQLite is the only one that requires separate `INSERT`s and EF does the splitting
This commit is contained in:
Doug Bunting 2018-05-29 12:18:39 -07:00
parent 6911e192e4
commit 53857d052f
No known key found for this signature in database
GPG Key ID: 888B4EB7822B32E9
47 changed files with 2661 additions and 0 deletions

32
Mvc.sln
View File

@ -170,6 +170,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPagesClassLibrary", "t
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{51E3E785-A9D1-4196-BAFE-A17FF4304B89}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{2859F266-673A-45A2-9E3C-7B39C6DDD38E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicApi", "benchmarkapps\BasicApi\BasicApi.csproj", "{910F023A-88E3-4CB4-8793-AC4005C7B421}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicViews", "benchmarkapps\BasicViews\BasicViews.csproj", "{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -894,6 +900,30 @@ Global
{51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|x86.ActiveCfg = Release|Any CPU
{51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|x86.Build.0 = Release|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Debug|Any CPU.Build.0 = Debug|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Debug|x86.ActiveCfg = Debug|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Debug|x86.Build.0 = Debug|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Release|Any CPU.ActiveCfg = Release|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Release|Any CPU.Build.0 = Release|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Release|x86.ActiveCfg = Release|Any CPU
{910F023A-88E3-4CB4-8793-AC4005C7B421}.Release|x86.Build.0 = Release|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Debug|x86.ActiveCfg = Debug|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Debug|x86.Build.0 = Debug|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Release|Any CPU.Build.0 = Release|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Release|x86.ActiveCfg = Release|Any CPU
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -963,6 +993,8 @@ Global
{E83D3745-9BCF-40E8-8D34-AFBA604C2439} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{17122147-ADFD-41C8-87D9-CCC582CCA8F9} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{51E3E785-A9D1-4196-BAFE-A17FF4304B89} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{910F023A-88E3-4CB4-8793-AC4005C7B421} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E}
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A}

View File

@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
<TargetFrameworks Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net461</TargetFrameworks>
<TargetFrameworks Condition="'$(BenchmarksTargetFramework)' != ''">$(BenchmarksTargetFramework)</TargetFrameworks>
<DefineConstants Condition=" '$(GenerateSqlScripts)'=='true' ">$(DefineConstants);GENERATE_SQL_SCRIPTS</DefineConstants>
<DefineConstants>$(DefineConstants);__RemoveThisBitTo__GENERATE_SQL_SCRIPTS</DefineConstants>
<WarningsNotAsErrors>CS8002;$(WarningsNotAsErrors)</WarningsNotAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="$(BenchmarksOnlyNpgsqlEntityFrameworkCorePostgreSQLPackageVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net461'">
<PackageReference Include="MySqlConnector" Version="$(BenchmarksOnlyMySqlConnectorPackageVersion)" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="$(BenchmarksOnlyPomeloEntityFrameworkCoreMySqlPackageVersion)" />
</ItemGroup>
<!-- These references are used when running locally -->
<ItemGroup Condition="'$(BenchmarksTargetFramework)' == ''">
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="$(BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion)" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(BenchmarksOnlyMicrosoftEntityFrameworkCoreSqlitePackageVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(BenchmarksOnlyMicrosoftEntityFrameworkCoreSqlServerPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="$(MicrosoftExtensionsConfigurationCommandLinePackageVersion)" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Core\Microsoft.AspNetCore.Mvc.Core.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.DataAnnotations\Microsoft.AspNetCore.Mvc.DataAnnotations.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Formatters.Json\Microsoft.AspNetCore.Mvc.Formatters.Json.csproj" />
</ItemGroup>
<!--
These references are used when running on the Benchmarks Server.
Use All meta-package and not App to include Microsoft.EntityFrameworkCore.Sqlite.
-->
<ItemGroup Condition="'$(BenchmarksTargetFramework)' != ''">
<PackageReference Include="Microsoft.AspNetCore.All" Version="$(MicrosoftAspNetCoreAllPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,139 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Threading.Tasks;
using BasicApi.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BasicApi.Controllers
{
[ApiController]
[Authorize("pet-store-reader")]
[Route("/pet")]
public class PetController : ControllerBase
{
public PetController(BasicApiContext dbContext)
{
DbContext = dbContext;
}
public BasicApiContext DbContext { get; }
[HttpGet("{id}", Name = "FindPetById")]
public async Task<ActionResult<Pet>> FindById(int id)
{
var pet = await DbContext.Pets
.Include(p => p.Category)
.Include(p => p.Images)
.Include(p => p.Tags)
.FirstOrDefaultAsync(p => p.Id == id);
if (pet == null)
{
return new NotFoundResult();
}
return pet;
}
[AllowAnonymous]
[HttpGet("anonymous/{id}")]
public async Task<ActionResult<Pet>> FindByIdWithoutToken(int id)
{
var pet = await DbContext.Pets
.Include(p => p.Category)
.Include(p => p.Images)
.Include(p => p.Tags)
.FirstOrDefaultAsync(p => p.Id == id);
if (pet == null)
{
return new NotFoundResult();
}
return pet;
}
[HttpGet("findByCategory/{categoryId}")]
public async Task<ActionResult<Pet>> FindByCategory(int categoryId)
{
var pet = await DbContext.Pets
.Include(p => p.Category)
.Include(p => p.Images)
.Include(p => p.Tags)
.FirstOrDefaultAsync(p => p.Category != null && p.Category.Id == categoryId);
if (pet == null)
{
return new NotFoundResult();
}
return pet;
}
[HttpGet("findByStatus")]
public async Task<ActionResult<Pet>> FindByStatus(string status)
{
var pet = await DbContext.Pets
.Include(p => p.Category)
.Include(p => p.Images)
.Include(p => p.Tags)
.FirstOrDefaultAsync(p => p.Status == status);
if (pet == null)
{
return new NotFoundResult();
}
return pet;
}
[HttpGet("findByTags")]
public async Task<ActionResult<Pet>> FindByTags(string[] tags)
{
var pet = await DbContext.Pets
.Include(p => p.Category)
.Include(p => p.Images)
.Include(p => p.Tags)
.FirstOrDefaultAsync(p => p.Tags.Any(t => tags.Contains(t.Name)));
if (pet == null)
{
return new NotFoundResult();
}
return pet;
}
[Authorize("pet-store-writer")]
[HttpPost]
public async Task<IActionResult> AddPet([FromBody] Pet pet)
{
DbContext.Pets.Add(pet);
await DbContext.SaveChangesAsync();
return new CreatedAtRouteResult("FindPetById", new { id = pet.Id }, pet);
}
[Authorize("pet-store-writer")]
[HttpPut]
public IActionResult EditPet(Pet pet)
{
throw new NotImplementedException();
}
[Authorize("pet-store-writer")]
[HttpPost("{id}/uploadImage")]
public IActionResult UploadImage(int id, IFormFile file)
{
throw new NotImplementedException();
}
[Authorize("pet-store-writer")]
[HttpDelete("{id}")]
public IActionResult DeletePet(int id)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace BasicApi.Controllers
{
public class TokenController : ControllerBase
{
private static readonly Dictionary<string, ClaimsIdentity> _identities;
static TokenController()
{
_identities = new Dictionary<string, ClaimsIdentity>(StringComparer.Ordinal);
var reader = new ClaimsIdentity();
reader.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, "reader@example.com"));
reader.AddClaim(new Claim("scope", "pet-store-reader"));
_identities.Add("reader@example.com", reader);
var writer = new ClaimsIdentity();
writer.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, "writer@example.com"));
writer.AddClaim(new Claim("scope", "pet-store-reader"));
writer.AddClaim(new Claim("scope", "pet-store-writer"));
_identities.Add("writer@example.com", writer);
}
private readonly SigningCredentials _credentials;
private readonly JwtBearerOptions _options;
public TokenController(
IOptionsSnapshot<JwtBearerOptions> options,
SigningCredentials credentials)
{
_options = options.Get(JwtBearerDefaults.AuthenticationScheme);
_credentials = credentials;
}
[HttpGet("/token")]
public IActionResult GetToken(string username)
{
if (username == null || !_identities.TryGetValue(username, out var identity))
{
return new StatusCodeResult(403);
}
var handler = _options.SecurityTokenValidators.OfType<JwtSecurityTokenHandler>().First();
var tokenDescriptor = new SecurityTokenDescriptor()
{
Issuer = _options.TokenValidationParameters.ValidIssuer,
Audience = _options.TokenValidationParameters.ValidAudience,
SigningCredentials = _credentials,
Subject = identity
};
var securityToken = handler.CreateJwtSecurityToken(
issuer: _options.TokenValidationParameters.ValidIssuer,
audience: _options.TokenValidationParameters.ValidAudience,
signingCredentials: _credentials,
subject: identity);
var token = handler.WriteToken(securityToken);
return Content(token);
}
}
}

View File

@ -0,0 +1,172 @@
// <auto-generated />
using BasicApi.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace BasicApi.Migrations
{
[DbContext(typeof(BasicApiContext))]
[Migration("20180609000420_InitialCreate")]
partial class InitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.HasAnnotation("ProductVersion", "2.1.0-rtm-30799")
.HasAnnotation("Relational:MaxIdentifierLength", 63)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("BasicApi.Models.Category", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Name");
b.HasKey("Id");
b.ToTable("Categories");
b.HasData(
new { Id = -1, Name = "Dogs" },
new { Id = -2, Name = "Cats" },
new { Id = -3, Name = "Rabbits" },
new { Id = -4, Name = "Lions" }
);
});
modelBuilder.Entity("BasicApi.Models.Image", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int?>("PetId");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("PetId");
b.ToTable("Images");
b.HasData(
new { Id = -1, PetId = -1, Url = "http://example.com/pets/-1_1.png" },
new { Id = -2, PetId = -2, Url = "http://example.com/pets/-2_1.png" },
new { Id = -3, PetId = -3, Url = "http://example.com/pets/-3_1.png" },
new { Id = -4, PetId = -4, Url = "http://example.com/pets/-4_1.png" },
new { Id = -5, PetId = -5, Url = "http://example.com/pets/-5_1.png" },
new { Id = -6, PetId = -6, Url = "http://example.com/pets/-6_1.png" },
new { Id = -7, PetId = -7, Url = "http://example.com/pets/-7_1.png" },
new { Id = -8, PetId = -8, Url = "http://example.com/pets/-8_1.png" },
new { Id = -9, PetId = -9, Url = "http://example.com/pets/-9_1.png" },
new { Id = -10, PetId = -10, Url = "http://example.com/pets/-10_1.png" },
new { Id = -11, PetId = -11, Url = "http://example.com/pets/-11_1.png" },
new { Id = -12, PetId = -12, Url = "http://example.com/pets/-12_1.png" }
);
});
modelBuilder.Entity("BasicApi.Models.Pet", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("Age");
b.Property<int?>("CategoryId");
b.Property<bool>("HasVaccinations");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50);
b.Property<string>("Status")
.IsRequired();
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("Pets");
b.HasData(
new { Id = -1, Age = 1, CategoryId = -1, HasVaccinations = true, Name = "Dogs1", Status = "available" },
new { Id = -2, Age = 1, CategoryId = -1, HasVaccinations = true, Name = "Dogs2", Status = "available" },
new { Id = -3, Age = 1, CategoryId = -1, HasVaccinations = true, Name = "Dogs3", Status = "available" },
new { Id = -4, Age = 1, CategoryId = -2, HasVaccinations = true, Name = "Cats1", Status = "available" },
new { Id = -5, Age = 1, CategoryId = -2, HasVaccinations = true, Name = "Cats2", Status = "available" },
new { Id = -6, Age = 1, CategoryId = -2, HasVaccinations = true, Name = "Cats3", Status = "available" },
new { Id = -7, Age = 1, CategoryId = -3, HasVaccinations = true, Name = "Rabbits1", Status = "available" },
new { Id = -8, Age = 1, CategoryId = -3, HasVaccinations = true, Name = "Rabbits2", Status = "available" },
new { Id = -9, Age = 1, CategoryId = -3, HasVaccinations = true, Name = "Rabbits3", Status = "available" },
new { Id = -10, Age = 1, CategoryId = -4, HasVaccinations = true, Name = "Lions1", Status = "available" },
new { Id = -11, Age = 1, CategoryId = -4, HasVaccinations = true, Name = "Lions2", Status = "available" },
new { Id = -12, Age = 1, CategoryId = -4, HasVaccinations = true, Name = "Lions3", Status = "available" }
);
});
modelBuilder.Entity("BasicApi.Models.Tag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Name");
b.Property<int?>("PetId");
b.HasKey("Id");
b.HasIndex("PetId");
b.ToTable("Tags");
b.HasData(
new { Id = -1, Name = "Tag1", PetId = -1 },
new { Id = -2, Name = "Tag1", PetId = -2 },
new { Id = -3, Name = "Tag1", PetId = -3 },
new { Id = -4, Name = "Tag1", PetId = -4 },
new { Id = -5, Name = "Tag1", PetId = -5 },
new { Id = -6, Name = "Tag1", PetId = -6 },
new { Id = -7, Name = "Tag1", PetId = -7 },
new { Id = -8, Name = "Tag1", PetId = -8 },
new { Id = -9, Name = "Tag1", PetId = -9 },
new { Id = -10, Name = "Tag1", PetId = -10 },
new { Id = -11, Name = "Tag1", PetId = -11 },
new { Id = -12, Name = "Tag1", PetId = -12 }
);
});
modelBuilder.Entity("BasicApi.Models.Image", b =>
{
b.HasOne("BasicApi.Models.Pet")
.WithMany("Images")
.HasForeignKey("PetId");
});
modelBuilder.Entity("BasicApi.Models.Pet", b =>
{
b.HasOne("BasicApi.Models.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId");
});
modelBuilder.Entity("BasicApi.Models.Tag", b =>
{
b.HasOne("BasicApi.Models.Pet")
.WithMany("Tags")
.HasForeignKey("PetId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,218 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace BasicApi.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Categories",
columns: table => new
{
Id = table.Column<int>(nullable: false)
#if !NET461
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn)
#endif
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.Annotation("Sqlite:Autoincrement", true)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Categories", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Pets",
columns: table => new
{
Id = table.Column<int>(nullable: false)
#if !NET461
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn)
#endif
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.Annotation("Sqlite:Autoincrement", true)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Age = table.Column<int>(nullable: false),
CategoryId = table.Column<int>(nullable: true),
HasVaccinations = table.Column<bool>(nullable: false),
Name = table.Column<string>(maxLength: 50, nullable: false),
Status = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Pets", x => x.Id);
table.ForeignKey(
name: "FK_Pets_Categories_CategoryId",
column: x => x.CategoryId,
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Images",
columns: table => new
{
Id = table.Column<int>(nullable: false)
#if !NET461
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn)
#endif
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.Annotation("Sqlite:Autoincrement", true)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Url = table.Column<string>(nullable: true),
PetId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Images", x => x.Id);
table.ForeignKey(
name: "FK_Images_Pets_PetId",
column: x => x.PetId,
principalTable: "Pets",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Tags",
columns: table => new
{
Id = table.Column<int>(nullable: false)
#if !NET461
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn)
#endif
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.Annotation("Sqlite:Autoincrement", true)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(nullable: true),
PetId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Tags", x => x.Id);
table.ForeignKey(
name: "FK_Tags_Pets_PetId",
column: x => x.PetId,
principalTable: "Pets",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.InsertData(
table: "Categories",
columns: new[] { "Id", "Name" },
values: new object[,]
{
{ -1, "Dogs" },
{ -2, "Cats" },
{ -3, "Rabbits" },
{ -4, "Lions" }
});
migrationBuilder.InsertData(
table: "Pets",
columns: new[] { "Id", "Age", "CategoryId", "HasVaccinations", "Name", "Status" },
values: new object[,]
{
{ -1, 1, -1, true, "Dogs1", "available" },
{ -2, 1, -1, true, "Dogs2", "available" },
{ -3, 1, -1, true, "Dogs3", "available" },
{ -4, 1, -2, true, "Cats1", "available" },
{ -5, 1, -2, true, "Cats2", "available" },
{ -6, 1, -2, true, "Cats3", "available" },
{ -7, 1, -3, true, "Rabbits1", "available" },
{ -8, 1, -3, true, "Rabbits2", "available" },
{ -9, 1, -3, true, "Rabbits3", "available" },
{ -10, 1, -4, true, "Lions1", "available" },
{ -11, 1, -4, true, "Lions2", "available" },
{ -12, 1, -4, true, "Lions3", "available" }
});
migrationBuilder.InsertData(
table: "Images",
columns: new[] { "Id", "PetId", "Url" },
values: new object[,]
{
{ -1, -1, "http://example.com/pets/-1_1.png" },
{ -2, -2, "http://example.com/pets/-2_1.png" },
{ -11, -11, "http://example.com/pets/-11_1.png" },
{ -3, -3, "http://example.com/pets/-3_1.png" },
{ -4, -4, "http://example.com/pets/-4_1.png" },
{ -10, -10, "http://example.com/pets/-10_1.png" },
{ -5, -5, "http://example.com/pets/-5_1.png" },
{ -6, -6, "http://example.com/pets/-6_1.png" },
{ -12, -12, "http://example.com/pets/-12_1.png" },
{ -7, -7, "http://example.com/pets/-7_1.png" },
{ -9, -9, "http://example.com/pets/-9_1.png" },
{ -8, -8, "http://example.com/pets/-8_1.png" }
});
migrationBuilder.InsertData(
table: "Tags",
columns: new[] { "Id", "Name", "PetId" },
values: new object[,]
{
{ -11, "Tag1", -11 },
{ -10, "Tag1", -10 },
{ -9, "Tag1", -9 },
{ -6, "Tag1", -6 },
{ -7, "Tag1", -7 },
{ -5, "Tag1", -5 },
{ -4, "Tag1", -4 },
{ -3, "Tag1", -3 },
{ -2, "Tag1", -2 },
{ -1, "Tag1", -1 },
{ -8, "Tag1", -8 },
{ -12, "Tag1", -12 }
});
migrationBuilder.CreateIndex(
name: "IX_Images_PetId",
table: "Images",
column: "PetId");
migrationBuilder.CreateIndex(
name: "IX_Pets_CategoryId",
table: "Pets",
column: "CategoryId");
migrationBuilder.CreateIndex(
name: "IX_Tags_PetId",
table: "Tags",
column: "PetId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Images");
migrationBuilder.DropTable(
name: "Tags");
migrationBuilder.DropTable(
name: "Pets");
migrationBuilder.DropTable(
name: "Categories");
}
}
}

View File

@ -0,0 +1,170 @@
// <auto-generated />
using BasicApi.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace BasicApi.Migrations
{
[DbContext(typeof(BasicApiContext))]
partial class BasicApiContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.HasAnnotation("ProductVersion", "2.1.0-rtm-30799")
.HasAnnotation("Relational:MaxIdentifierLength", 63)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("BasicApi.Models.Category", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Name");
b.HasKey("Id");
b.ToTable("Categories");
b.HasData(
new { Id = -1, Name = "Dogs" },
new { Id = -2, Name = "Cats" },
new { Id = -3, Name = "Rabbits" },
new { Id = -4, Name = "Lions" }
);
});
modelBuilder.Entity("BasicApi.Models.Image", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int?>("PetId");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("PetId");
b.ToTable("Images");
b.HasData(
new { Id = -1, PetId = -1, Url = "http://example.com/pets/-1_1.png" },
new { Id = -2, PetId = -2, Url = "http://example.com/pets/-2_1.png" },
new { Id = -3, PetId = -3, Url = "http://example.com/pets/-3_1.png" },
new { Id = -4, PetId = -4, Url = "http://example.com/pets/-4_1.png" },
new { Id = -5, PetId = -5, Url = "http://example.com/pets/-5_1.png" },
new { Id = -6, PetId = -6, Url = "http://example.com/pets/-6_1.png" },
new { Id = -7, PetId = -7, Url = "http://example.com/pets/-7_1.png" },
new { Id = -8, PetId = -8, Url = "http://example.com/pets/-8_1.png" },
new { Id = -9, PetId = -9, Url = "http://example.com/pets/-9_1.png" },
new { Id = -10, PetId = -10, Url = "http://example.com/pets/-10_1.png" },
new { Id = -11, PetId = -11, Url = "http://example.com/pets/-11_1.png" },
new { Id = -12, PetId = -12, Url = "http://example.com/pets/-12_1.png" }
);
});
modelBuilder.Entity("BasicApi.Models.Pet", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("Age");
b.Property<int?>("CategoryId");
b.Property<bool>("HasVaccinations");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50);
b.Property<string>("Status")
.IsRequired();
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("Pets");
b.HasData(
new { Id = -1, Age = 1, CategoryId = -1, HasVaccinations = true, Name = "Dogs1", Status = "available" },
new { Id = -2, Age = 1, CategoryId = -1, HasVaccinations = true, Name = "Dogs2", Status = "available" },
new { Id = -3, Age = 1, CategoryId = -1, HasVaccinations = true, Name = "Dogs3", Status = "available" },
new { Id = -4, Age = 1, CategoryId = -2, HasVaccinations = true, Name = "Cats1", Status = "available" },
new { Id = -5, Age = 1, CategoryId = -2, HasVaccinations = true, Name = "Cats2", Status = "available" },
new { Id = -6, Age = 1, CategoryId = -2, HasVaccinations = true, Name = "Cats3", Status = "available" },
new { Id = -7, Age = 1, CategoryId = -3, HasVaccinations = true, Name = "Rabbits1", Status = "available" },
new { Id = -8, Age = 1, CategoryId = -3, HasVaccinations = true, Name = "Rabbits2", Status = "available" },
new { Id = -9, Age = 1, CategoryId = -3, HasVaccinations = true, Name = "Rabbits3", Status = "available" },
new { Id = -10, Age = 1, CategoryId = -4, HasVaccinations = true, Name = "Lions1", Status = "available" },
new { Id = -11, Age = 1, CategoryId = -4, HasVaccinations = true, Name = "Lions2", Status = "available" },
new { Id = -12, Age = 1, CategoryId = -4, HasVaccinations = true, Name = "Lions3", Status = "available" }
);
});
modelBuilder.Entity("BasicApi.Models.Tag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Name");
b.Property<int?>("PetId");
b.HasKey("Id");
b.HasIndex("PetId");
b.ToTable("Tags");
b.HasData(
new { Id = -1, Name = "Tag1", PetId = -1 },
new { Id = -2, Name = "Tag1", PetId = -2 },
new { Id = -3, Name = "Tag1", PetId = -3 },
new { Id = -4, Name = "Tag1", PetId = -4 },
new { Id = -5, Name = "Tag1", PetId = -5 },
new { Id = -6, Name = "Tag1", PetId = -6 },
new { Id = -7, Name = "Tag1", PetId = -7 },
new { Id = -8, Name = "Tag1", PetId = -8 },
new { Id = -9, Name = "Tag1", PetId = -9 },
new { Id = -10, Name = "Tag1", PetId = -10 },
new { Id = -11, Name = "Tag1", PetId = -11 },
new { Id = -12, Name = "Tag1", PetId = -12 }
);
});
modelBuilder.Entity("BasicApi.Models.Image", b =>
{
b.HasOne("BasicApi.Models.Pet")
.WithMany("Images")
.HasForeignKey("PetId");
});
modelBuilder.Entity("BasicApi.Models.Pet", b =>
{
b.HasOne("BasicApi.Models.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId");
});
modelBuilder.Entity("BasicApi.Models.Tag", b =>
{
b.HasOne("BasicApi.Models.Pet")
.WithMany("Tags")
.HasForeignKey("PetId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,190 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.EntityFrameworkCore;
namespace BasicApi.Models
{
public class BasicApiContext : DbContext
{
public BasicApiContext(DbContextOptions options)
: base(options)
{
}
public DbSet<Category> Categories { get; set; }
public DbSet<Image> Images { get; set; }
public DbSet<Pet> Pets { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var id = -1;
var categories = new[]
{
new Category { Id = id--, Name = "Dogs" },
new Category { Id = id--, Name = "Cats" },
new Category { Id = id--, Name = "Rabbits" },
new Category { Id = id, Name = "Lions" },
};
id = -1;
var categoryId = -1;
var pets = new[]
{
new
{
Age = 1,
CategoryId = categoryId,
HasVaccinations = true,
Id = id--,
Name = "Dogs1",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId,
HasVaccinations = true,
Id = id--,
Name = "Dogs2",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId--,
HasVaccinations = true,
Id = id--,
Name = "Dogs3",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId,
HasVaccinations = true,
Id = id--,
Name = "Cats1",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId,
HasVaccinations = true,
Id = id--,
Name = "Cats2",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId--,
HasVaccinations = true,
Id = id--,
Name = "Cats3",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId,
HasVaccinations = true,
Id = id--,
Name = "Rabbits1",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId,
HasVaccinations = true,
Id = id--,
Name = "Rabbits2",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId--,
HasVaccinations = true,
Id = id--,
Name = "Rabbits3",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId,
HasVaccinations = true,
Id = id--,
Name = "Lions1",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId,
HasVaccinations = true,
Id = id--,
Name = "Lions2",
Status = "available",
},
new
{
Age = 1,
CategoryId = categoryId,
HasVaccinations = true,
Id = id,
Name = "Lions3",
Status = "available",
},
};
id = -1;
var images = new[]
{
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id--}_1.png" },
new { Id = id, PetId = id, Url = $"http://example.com/pets/{id}_1.png" },
};
id = -1;
var tags = new[]
{
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id--, Name = "Tag1" },
new { Id = id, PetId = id, Name = "Tag1" },
};
modelBuilder.Entity<Category>().HasData(categories);
modelBuilder.Entity<Pet>().HasData(pets);
modelBuilder.Entity<Image>().HasData(images);
modelBuilder.Entity<Tag>().HasData(tags);
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace BasicApi.Models
{
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace BasicApi.Models
{
public class Image
{
public int Id { get; set; }
public string Url { get; set; }
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace BasicApi.Models
{
public class Pet
{
public int Id { get; set; }
[Range(0, 150)]
public int Age { get; set; }
public Category Category { get; set; }
public bool HasVaccinations { get; set; }
[Required]
[StringLength(50, MinimumLength = 2)]
public string Name { get; set; }
public List<Image> Images { get; set; }
public List<Tag> Tags { get; set; }
[Required]
public string Status { get; set; }
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace BasicApi.Models
{
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
}
}

View File

@ -0,0 +1,246 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
#if GENERATE_SQL_SCRIPTS
using System.Linq;
#endif
using System.Security.Cryptography;
using BasicApi.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Serialization;
using Npgsql;
namespace BasicApi
{
public class Startup
{
private bool _isSQLite;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var rsa = new RSACryptoServiceProvider(2048);
var key = new RsaSecurityKey(rsa.ExportParameters(true));
services.AddSingleton(new SigningCredentials(
key,
SecurityAlgorithms.RsaSha256Signature));
services.AddAuthentication().AddJwtBearer(options =>
{
options.TokenValidationParameters.IssuerSigningKey = key;
options.TokenValidationParameters.ValidAudience = "Myself";
options.TokenValidationParameters.ValidIssuer = "BasicApi";
});
var connectionString = Configuration["ConnectionString"];
var databaseType = Configuration["Database"];
if (string.IsNullOrEmpty(databaseType))
{
// Use SQLite when running outside a benchmark test or if benchmarks user specified "None".
// ("None" is not passed to the web application.)
databaseType = "SQLite";
}
else if (string.IsNullOrEmpty(connectionString))
{
throw new ArgumentException("Connection string must be specified for {databaseType}.");
}
switch (databaseType.ToUpper())
{
#if !NET461
case "MYSQL":
services
.AddEntityFrameworkMySql()
.AddDbContext<BasicApiContext>(options => options.UseMySql(connectionString));
break;
#endif
case "POSTGRESQL":
var settings = new NpgsqlConnectionStringBuilder(connectionString);
if (!settings.NoResetOnClose)
{
throw new ArgumentException("No Reset On Close=true must be specified for Npgsql.");
}
if (settings.Enlist)
{
throw new ArgumentException("Enlist=false must be specified for Npgsql.");
}
services
.AddEntityFrameworkNpgsql()
.AddDbContextPool<BasicApiContext>(options => options.UseNpgsql(connectionString));
break;
case "SQLITE":
_isSQLite = true;
services
.AddEntityFrameworkSqlite()
.AddDbContextPool<BasicApiContext>(options => options.UseSqlite("Data Source=BasicApi.db"));
break;
case "SQLSERVER":
services
.AddEntityFrameworkSqlServer()
.AddDbContextPool<BasicApiContext>(options => options.UseSqlServer(connectionString));
break;
default:
throw new ArgumentException($"Application does not support database type {databaseType}.");
}
services.AddAuthorization(options =>
{
options.AddPolicy(
"pet-store-reader",
builder => builder
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireClaim("scope", "pet-store-reader"));
options.AddPolicy(
"pet-store-writer",
builder => builder
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireClaim("scope", "pet-store-writer"));
});
services
.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters(json => json.ContractResolver = new CamelCasePropertyNamesContractResolver())
.AddDataAnnotations();
}
public void Configure(IApplicationBuilder app, IApplicationLifetime lifetime)
{
var services = app.ApplicationServices;
CreateDatabaseTables(services);
if (_isSQLite)
{
lifetime.ApplicationStopping.Register(() => DropDatabase(services));
}
else
{
lifetime.ApplicationStopping.Register(() => DropDatabaseTables(services));
}
app.Use(next => async context =>
{
try
{
await next(context);
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
});
app.UseAuthentication();
app.UseMvc();
}
private void CreateDatabaseTables(IServiceProvider services)
{
using (var serviceScope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var dbContext = serviceScope.ServiceProvider.GetRequiredService<BasicApiContext>())
{
#if GENERATE_SQL_SCRIPTS
var migrator = dbContext.GetService<IMigrator>();
var script = migrator.GenerateScript(
fromMigration: Migration.InitialDatabase,
toMigration: dbContext.Database.GetMigrations().LastOrDefault());
Console.WriteLine("Create script:");
Console.WriteLine(script);
#endif
dbContext.Database.Migrate();
}
}
}
// Don't leave SQLite's .db file behind.
public static void DropDatabase(IServiceProvider services)
{
using (var serviceScope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var dbContext = serviceScope.ServiceProvider.GetRequiredService<BasicApiContext>())
{
#if GENERATE_SQL_SCRIPTS
var migrator = dbContext.GetService<IMigrator>();
var script = migrator.GenerateScript(
fromMigration: dbContext.Database.GetAppliedMigrations().LastOrDefault(),
toMigration: Migration.InitialDatabase);
Console.WriteLine("Delete script:");
Console.WriteLine(script);
#endif
dbContext.Database.EnsureDeleted();
}
}
}
private void DropDatabaseTables(IServiceProvider services)
{
using (var serviceScope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var dbContext = serviceScope.ServiceProvider.GetRequiredService<BasicApiContext>())
{
var migrator = dbContext.GetService<IMigrator>();
#if GENERATE_SQL_SCRIPTS
var script = migrator.GenerateScript(
fromMigration: dbContext.Database.GetAppliedMigrations().LastOrDefault(),
toMigration: Migration.InitialDatabase);
Console.WriteLine("Delete script:");
Console.WriteLine(script);
#endif
migrator.Migrate(Migration.InitialDatabase);
}
}
}
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args)
.Build();
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
return new WebHostBuilder()
.UseKestrel()
.UseUrls("http://+:5000")
.UseConfiguration(configuration)
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>();
}
}
}

View File

@ -0,0 +1,48 @@
{
"Default": {
"Client": "Wrk",
"Headers": {
"Cache-Control": "no-cache"
},
"PresetHeaders": "Json",
"ReadyStateText": "Application started.",
"Source": {
"BranchOrCommit": "dev",
"Project": "benchmarkapps/BasicApi/BasicApi.csproj",
"Repository": "https://github.com/aspnet/mvc.git"
}
},
"BasicApi.GetToken": {
"Path": "/token",
"PresetHeaders": "Plaintext",
"Query": "?username=reader@example.com"
},
"BasicApi.GetUsingQueryString": {
"ClientProperties": {
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/getWithToken.lua"
},
"Path": "/pet/findByStatus",
"Query": "?status=available"
},
"BasicApi.GetUsingRouteValue": {
"ClientProperties": {
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/getWithToken.lua"
},
"Path": "/pet/-1"
},
"BasicApi.GetUsingRouteValueWithoutAuthorization": {
"ClientProperties": {
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/getWithToken.lua"
},
"Path": "/pet/anonymous/-1"
},
"BasicApi.GetUsingRouteValueWithoutToken": {
"Path": "/pet/anonymous/-1"
},
"BasicApi.Post": {
"ClientProperties": {
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/postJsonWithToken.lua"
},
"Path": "/pet"
}
}

View File

@ -0,0 +1,51 @@
-- script that retrieves an authentication token to send in all future requests
-- keep this file and postJsonWithToken.lua in sync with respect to token handling
-- use token for at most maxRequests, default throughout test
local counter = 0
local maxRequests = -1
-- request access necessary for both reading and writing by default
local username = "writer@example.com"
-- marker that we have completed the first request
local token = nil
function init(args)
if args[1] ~= nil then
maxRequests = args[1]
print("Max requests: " .. maxRequests)
end
if args[2] ~= nil then
username = args[2]
end
local path = "/token?username=" .. username
-- initialize first (empty) request
req = wrk.format("GET", path, nil, "")
end
function request()
return req
end
function response(status, headers, body)
if not token and status == 200 then
token = body
wrk.headers["Authorization"] = "Bearer " .. token
req = wrk.format()
return
end
if not token then
print("Failed initial request! status: " .. status)
wrk.thread:stop()
end
if counter == maxRequests then
wrk.thread:stop()
end
counter = counter + 1
end

View File

@ -0,0 +1,83 @@
-- script that retrieves an authentication token to send in all future requests and adds a body for those requests
-- keep this file and getWithToken.lua in sync with respect to token handling
-- do not use wrk's default request
local req = nil
-- use token for at most maxRequests, default throughout test
local counter = 0
local maxRequests = -1
-- request access necessary for both reading and writing by default
local username = "writer@example.com"
-- marker that we have completed the first request
local token = nil
function init(args)
if args[1] ~= nil then
maxRequests = args[1]
print("Max requests: " .. maxRequests)
end
if args[2] ~= nil then
username = args[2]
end
local path = "/token?username=" .. username
-- initialize first (empty) request
req = wrk.format("GET", path, nil, "")
end
function request()
return req
end
function response(status, headers, body)
if not token and status == 200 then
token = body
wrk.headers["Authorization"] = "Bearer " .. token
wrk.headers["Content-Type"] = "application/json"
wrk.method = "POST"
wrk.body = [[
{
"category": {
"name": "Cats"
},
"images": [
{
"url": "http://example.com/images/fluffy1.png"
},
{
"url": "http://example.com/images/fluffy2.png"
},
],
"tags": [
{
"name": "orange"
},
{
"name": "kitty"
}
],
"age": 2,
"hasVaccinations": "true",
"name": "fluffy",
"status": "available"
}]]
req = wrk.format()
return
end
if not token then
print("Failed initial request! status: " .. status)
wrk.thread:stop()
end
if counter == maxRequests then
wrk.thread:stop()
end
counter = counter + 1
end

View File

@ -0,0 +1,5 @@
{
"configProperties": {
"System.GC.Server": true
}
}

View File

@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
<TargetFrameworks Condition=" '$(DeveloperBuild)' != 'true' AND '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net461</TargetFrameworks>
<TargetFrameworks Condition="'$(BenchmarksTargetFramework)' != ''">$(BenchmarksTargetFramework)</TargetFrameworks>
<DefineConstants Condition=" '$(GenerateSqlScripts)'=='true' ">$(DefineConstants);GENERATE_SQL_SCRIPTS</DefineConstants>
<DefineConstants>$(DefineConstants);__RemoveThisBitTo__GENERATE_SQL_SCRIPTS</DefineConstants>
<WarningsNotAsErrors>CS8002;$(WarningsNotAsErrors)</WarningsNotAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="$(BenchmarksOnlyNpgsqlEntityFrameworkCorePostgreSQLPackageVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net461'">
<PackageReference Include="MySqlConnector" Version="$(BenchmarksOnlyMySqlConnectorPackageVersion)" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="$(BenchmarksOnlyPomeloEntityFrameworkCoreMySqlPackageVersion)" />
</ItemGroup>
<!-- These references are used when running locally -->
<ItemGroup Condition="'$(BenchmarksTargetFramework)' == ''">
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(MicrosoftAspNetCoreServerIISIntegrationPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="$(BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion)" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(BenchmarksOnlyMicrosoftEntityFrameworkCoreSqlitePackageVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(BenchmarksOnlyMicrosoftEntityFrameworkCoreSqlServerPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="$(MicrosoftExtensionsConfigurationCommandLinePackageVersion)" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
</ItemGroup>
<!--
These references are used when running on the Benchmarks Server.
Use All meta-package and not App to include Microsoft.EntityFrameworkCore.Sqlite.
-->
<ItemGroup Condition="'$(BenchmarksTargetFramework)' != ''">
<PackageReference Include="Microsoft.AspNetCore.All" Version="$(MicrosoftAspNetCoreAllPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.EntityFrameworkCore;
namespace BasicViews
{
public class BasicViewsContext : DbContext
{
public BasicViewsContext(DbContextOptions options)
: base(options)
{
}
public virtual DbSet<Person> People { get; set; }
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc;
namespace BasicViews.Components
{
public class CurrentUser : ViewComponent
{
private static readonly string[] Names = { "Curly", "Curly Joe", "Joe", "Larry", "Moe", "Shemp" };
private static int index = 0;
public string Invoke()
{
index = index++ / Names.Length;
return Names[index];
}
}
}

View File

@ -0,0 +1,75 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace BasicViews.Controllers
{
public class HomeController : Controller
{
private readonly BasicViewsContext _context;
public HomeController(BasicViewsContext context)
{
_context = context;
}
[HttpGet]
public IActionResult Index()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(Person person)
{
if (ModelState.IsValid)
{
_context.Add(person);
await _context.SaveChangesAsync();
}
return View(person);
}
[HttpGet]
public IActionResult IndexWithoutToken()
{
return View(viewName: nameof(Index));
}
[HttpPost]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> IndexWithoutToken(Person person)
{
if (ModelState.IsValid)
{
_context.Add(person);
await _context.SaveChangesAsync();
}
return View(viewName: nameof(Index), model: person);
}
[HttpGet]
public IActionResult HtmlHelpers()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> HtmlHelpers(Person person)
{
if (ModelState.IsValid)
{
_context.Add(person);
await _context.SaveChangesAsync();
}
return View(person);
}
}
}

View File

@ -0,0 +1,44 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace BasicViews.Migrations
{
[DbContext(typeof(BasicViewsContext))]
[Migration("20180609000611_InitialCreate")]
partial class InitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.HasAnnotation("ProductVersion", "2.1.0-rtm-30799")
.HasAnnotation("Relational:MaxIdentifierLength", 63)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("BasicViews.Person", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("Age");
b.Property<DateTimeOffset>("BirthDate");
b.Property<string>("Name")
.HasMaxLength(27);
b.HasKey("Id");
b.ToTable("People");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace BasicViews.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "People",
columns: table => new
{
Id = table.Column<int>(nullable: false)
#if !NET461
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn)
#endif
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.Annotation("Sqlite:Autoincrement", true)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(maxLength: 27, nullable: true),
Age = table.Column<int>(nullable: false),
BirthDate = table.Column<DateTimeOffset>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_People", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "People");
}
}
}

View File

@ -0,0 +1,42 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace BasicViews.Migrations
{
[DbContext(typeof(BasicViewsContext))]
partial class BasicViewsContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.HasAnnotation("ProductVersion", "2.1.0-rtm-30799")
.HasAnnotation("Relational:MaxIdentifierLength", 63)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("BasicViews.Person", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("Age");
b.Property<DateTimeOffset>("BirthDate");
b.Property<string>("Name")
.HasMaxLength(27);
b.HasKey("Id");
b.ToTable("People");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel.DataAnnotations;
namespace BasicViews
{
public class Person
{
public int Id { get; set; }
[StringLength(27, MinimumLength = 2)]
public string Name { get; set; }
[Range(10, 54)]
public int Age { get; set; }
public DateTimeOffset BirthDate { get; set; }
}
}

View File

@ -0,0 +1,207 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
#if GENERATE_SQL_SCRIPTS
using System.Linq;
#endif
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;
namespace BasicViews
{
public class Startup
{
private bool _isSQLite;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration["ConnectionString"];
var databaseType = Configuration["Database"];
if (string.IsNullOrEmpty(databaseType))
{
// Use SQLite when running outside a benchmark test or if benchmarks user specified "None".
// ("None" is not passed to the web application.)
databaseType = "SQLite";
}
else if (string.IsNullOrEmpty(connectionString))
{
throw new ArgumentException("Connection string must be specified for {databaseType}.");
}
switch (databaseType.ToUpper())
{
#if !NET461
case "MYSQL":
services
.AddEntityFrameworkMySql()
.AddDbContext<BasicViewsContext>(options => options.UseMySql(connectionString));
break;
#endif
case "POSTGRESQL":
var settings = new NpgsqlConnectionStringBuilder(connectionString);
if (!settings.NoResetOnClose)
{
throw new ArgumentException("No Reset On Close=true must be specified for Npgsql.");
}
if (settings.Enlist)
{
throw new ArgumentException("Enlist=false must be specified for Npgsql.");
}
services
.AddEntityFrameworkNpgsql()
.AddDbContextPool<BasicViewsContext>(options => options.UseNpgsql(connectionString));
break;
case "SQLITE":
_isSQLite = true;
services
.AddEntityFrameworkSqlite()
.AddDbContextPool<BasicViewsContext>(options => options.UseSqlite("Data Source=BasicViews.db"));
break;
case "SQLSERVER":
services
.AddEntityFrameworkSqlServer()
.AddDbContextPool<BasicViewsContext>(options => options.UseSqlServer(connectionString));
break;
default:
throw new ArgumentException($"Application does not support database type {databaseType}.");
}
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IApplicationLifetime lifetime)
{
var services = app.ApplicationServices;
CreateDatabaseTables(services);
if (_isSQLite)
{
lifetime.ApplicationStopping.Register(() => DropDatabase(services));
}
else
{
lifetime.ApplicationStopping.Register(() => DropDatabaseTables(services));
}
app.Use(next => async context =>
{
try
{
await next(context);
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
});
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
private void CreateDatabaseTables(IServiceProvider services)
{
using (var serviceScope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var dbContext = serviceScope.ServiceProvider.GetRequiredService<BasicViewsContext>())
{
#if GENERATE_SQL_SCRIPTS
var migrator = dbContext.GetService<IMigrator>();
var script = migrator.GenerateScript(
fromMigration: Migration.InitialDatabase,
toMigration: dbContext.Database.GetMigrations().LastOrDefault());
Console.WriteLine("Create script:");
Console.WriteLine(script);
#endif
dbContext.Database.Migrate();
}
}
}
// Don't leave SQLite's .db file behind.
public static void DropDatabase(IServiceProvider services)
{
using (var serviceScope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var dbContext = serviceScope.ServiceProvider.GetRequiredService<BasicViewsContext>())
{
#if GENERATE_SQL_SCRIPTS
var migrator = dbContext.GetService<IMigrator>();
var script = migrator.GenerateScript(
fromMigration: dbContext.Database.GetAppliedMigrations().LastOrDefault(),
toMigration: Migration.InitialDatabase);
Console.WriteLine("Delete script:");
Console.WriteLine(script);
#endif
dbContext.Database.EnsureDeleted();
}
}
}
private void DropDatabaseTables(IServiceProvider services)
{
using (var serviceScope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var dbContext = serviceScope.ServiceProvider.GetRequiredService<BasicViewsContext>())
{
var migrator = dbContext.GetService<IMigrator>();
#if GENERATE_SQL_SCRIPTS
var script = migrator.GenerateScript(
fromMigration: dbContext.Database.GetAppliedMigrations().LastOrDefault(),
toMigration: Migration.InitialDatabase);
Console.WriteLine("Delete script:");
Console.WriteLine(script);
#endif
migrator.Migrate(Migration.InitialDatabase);
}
}
}
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args)
.Build();
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
return new WebHostBuilder()
.UseKestrel()
.UseUrls("http://+:5000")
.UseConfiguration(configuration)
.UseIISIntegration()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>();
}
}
}

View File

@ -0,0 +1,23 @@
@using BasicViews
@model Person
@Html.ValidationSummary()
@using (Html.BeginForm())
{
<div>
@Html.LabelFor(p => p.Name)
@Html.EditorFor(p => p.Name)
</div>
<div>
@Html.LabelFor(p => p.Age)
@Html.EditorFor(p => p.Age)
</div>
<div>
@Html.LabelFor(p => p.BirthDate)
@Html.EditorFor(p => p.BirthDate)
</div>
<input type="submit" />
@Html.AntiForgeryToken()
}

View File

@ -0,0 +1,21 @@
@using BasicViews
@model Person
<div asp-validation-summary="All">
</div>
<form asp-action="Index" asp-controller="Home">
<div>
<label asp-for="Name"></label>
<input asp-for="Name" />
</div>
<div>
<label asp-for="Age"></label>
<input asp-for="Age" />
</div>
<div>
<label asp-for="BirthDate"></label>
<input asp-for="BirthDate" />
</div>
<input type="submit" />
</form>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>MVC with views</title>
<environment names="Development">
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
<link rel="stylesheet" href="~/css/site.min.css" asp-fallback-href="~/css/site.css"
asp-fallback-test-class="test-it" asp-fallback-test-property="float"
asp-fallback-test-value="right" asp-append-version="true" />
</environment>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-controller="Home" asp-action="Index" class="navbar-brand">BasicViews</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-controller="Home" asp-action="Index">TagHelpers</a></li>
<li><a asp-controller="Home" asp-action="HtmlHelpers">HtmlHelpers</a></li>
</ul>
</div>
</div>
</div>
<div class="container body-content">
<p>Hello @await Component.InvokeAsync("CurrentUser")!</p>
<div>
@RenderBody()
</div>
</div>
<environment names="Development">
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<script src="~/js/site.min.js" asp-fallback-src="~/js/site.js"
asp-fallback-test="test" asp-append-version="true"></script>
</environment>
@RenderSection("scripts", required: false)
</body>
</html>

View File

@ -0,0 +1 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -0,0 +1,39 @@
{
"Default": {
"Client": "Wrk",
"Headers": {
"Cache-Control": "no-cache"
},
"PresetHeaders": "Html",
"ReadyStateText": "Application started.",
"Source": {
"BranchOrCommit": "dev",
"Project": "benchmarkapps/BasicViews/BasicViews.csproj",
"Repository": "https://github.com/aspnet/mvc.git"
}
},
"BasicViews.GetHtmlHelpers": {
"Path": "/Home/HtmlHelpers"
},
"BasicViews.GetTagHelpers": {
"Path": "/Home/Index"
},
"BasicViews.Post": {
"ClientProperties": {
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/postWithToken.lua"
},
"Path": "/Home/Index"
},
"BasicViews.PostIgnoringToken": {
"ClientProperties": {
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/postWithToken.lua"
},
"Path": "/Home/IndexWithoutToken"
},
"BasicViews.PostWithoutToken": {
"ClientProperties": {
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/post.lua"
},
"Path": "/Home/IndexWithoutToken"
}
}

View File

@ -0,0 +1,7 @@
-- script that POSTs body for requests
function init(args)
wrk.body = "Age=12&BirthDate=2006-03-01T09%3A51%3A43.041-07%3A00&Name=George"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
wrk.method = "POST"
end

View File

@ -0,0 +1,55 @@
-- script that retrieves an antiforgery token to send in all future requests and adds a body for those requests
-- do not use wrk's default request
local req = nil
-- use token for at most maxRequests, default throughout test
local counter = 0
local maxRequests = -1
-- marker that we have completed the first request
local token = nil
function init(args)
-- initialize first (empty) request
req = wrk.format("GET")
end
function request()
return req
end
function response(status, headers, body)
if not token and status == 200 then
local cookie = string.gsub(headers["Set-Cookie"], "^([^;]*)(;.*)?$", "%1")
if not cookie or cookie == "" then
print("Unable to find antiforgery cookie in initial response!")
wrk.thread:stop()
end
token = string.gsub(body, '^.* name="__RequestVerificationToken".* value="([^"]*)"[ >].*$', "%1")
if not token or token == "" then
print("Unable to find antiforgery token in initial response!")
wrk.thread:stop()
end
wrk.body = "Age=12&BirthDate=2006-03-01T09%3A51%3A43.041-07%3A00&Name=George&__RequestVerificationToken=" .. token
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
wrk.headers["Cookie"] = cookie
wrk.method = "POST"
req = wrk.format()
return
end
if not token then
print("Failed initial request! status: " .. status)
wrk.thread:stop()
end
if counter == maxRequests then
wrk.thread:stop()
end
counter = counter + 1
end

View File

@ -0,0 +1,5 @@
{
"configProperties": {
"System.GC.Server": true
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" forwardWindowsAuthToken="false" stdoutLogEnabled="false" />
</system.webServer>
</configuration>

View File

@ -0,0 +1,6 @@
label {
font-size: 1.2em;
}
.test-it {
float: right;
}

View File

@ -0,0 +1,6 @@
label {
font-size: 1.3em;
}
.test-it {
float: right;
}

View File

@ -0,0 +1,3 @@
console.log("Hello World");
function test() {
}

View File

@ -0,0 +1,3 @@
console.log("Hello Minified World");
function test() {
}

15
benchmarkapps/README.md Normal file
View File

@ -0,0 +1,15 @@
## Purpose
These projects assist in Benchmarking MVC.
They makes it easier to test local changes than having the App in the Benchmarks repo by letting us make changes in MVC branches and use the example commandline below to run the benchmarks against our branches.
## Usage
1. Push changes you would like to test to a branch on GitHub
2. Clone aspnet/benchmarks repo to your machine or install the global BenchmarksDriver tool https://www.nuget.org/packages/BenchmarksDriver/
3. If cloned go to the BenchmarksDriver project
4. Use the following command as a guideline for running a test using your changes
`benchmarks --server <server-endpoint> --client <client-endpoint> -j https://raw.githubusercontent.com/aspnet/MVC/dev/benchmarkaps/BasicApi/BasicApi.json`
5. For more info/commands see https://github.com/aspnet/benchmarks/blob/dev/src/BenchmarksDriver/README.md

View File

@ -5,12 +5,27 @@
<PropertyGroup Label="Package Versions">
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
<!--
BenchmarksOnly* package versions come from NuGet.org and are intended only for use in benchmarks apps where EF
is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product
dependencies. Do not use these properties elsewhere.
-->
<BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion>2.1.0</BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion>
<BenchmarksOnlyMicrosoftEntityFrameworkCoreSqlitePackageVersion>2.1.0</BenchmarksOnlyMicrosoftEntityFrameworkCoreSqlitePackageVersion>
<BenchmarksOnlyMicrosoftEntityFrameworkCoreSqlServerPackageVersion>2.1.0</BenchmarksOnlyMicrosoftEntityFrameworkCoreSqlServerPackageVersion>
<BenchmarksOnlyMySqlConnectorPackageVersion>0.42.1</BenchmarksOnlyMySqlConnectorPackageVersion>
<BenchmarksOnlyNpgsqlEntityFrameworkCorePostgreSQLPackageVersion>2.1.0</BenchmarksOnlyNpgsqlEntityFrameworkCorePostgreSQLPackageVersion>
<BenchmarksOnlyPomeloEntityFrameworkCoreMySqlPackageVersion>2.1.0-rc1-final</BenchmarksOnlyPomeloEntityFrameworkCoreMySqlPackageVersion>
<InternalAspNetCoreAnalyzersPackageVersion>2.2.0-preview1-34492</InternalAspNetCoreAnalyzersPackageVersion>
<InternalAspNetCoreSdkPackageVersion>2.2.0-preview1-17090</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreAllPackageVersion>2.2.0-preview1-34492</MicrosoftAspNetCoreAllPackageVersion>
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>2.2.0-preview1-34492</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
<MicrosoftAspNetCoreAntiforgeryPackageVersion>2.2.0-preview1-34492</MicrosoftAspNetCoreAntiforgeryPackageVersion>
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>2.2.0-preview1-34492</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.2.0-preview1-34492</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
<MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>2.2.0-preview1-34492</MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>
<MicrosoftAspNetCoreAuthenticationPackageVersion>2.2.0-preview1-34492</MicrosoftAspNetCoreAuthenticationPackageVersion>
<MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>2.2.0-preview1-34492</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-preview1-34492</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
@ -51,6 +66,7 @@
<MicrosoftDiaSymReaderNativePackageVersion>1.7.0</MicrosoftDiaSymReaderNativePackageVersion>
<MicrosoftExtensionsCachingMemoryPackageVersion>2.2.0-preview1-34492</MicrosoftExtensionsCachingMemoryPackageVersion>
<MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>2.2.0-preview1-34492</MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>2.2.0-preview1-34492</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.2.0-preview1-34492</MicrosoftExtensionsConfigurationJsonPackageVersion>
<MicrosoftExtensionsConfigurationPackageVersion>2.2.0-preview1-34492</MicrosoftExtensionsConfigurationPackageVersion>
<MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>2.2.0-preview1-34492</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>

View File

@ -0,0 +1,223 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class BasicApiTest : IClassFixture<BasicApiFixture>
{
private static readonly byte[] PetBytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)
.GetBytes(@"{
""category"" : {
""name"" : ""Cats""
},
""images"": [
{
""url"": ""http://example.com/images/fluffy1.png""
},
{
""url"": ""http://example.com/images/fluffy2.png""
},
],
""tags"": [
{
""name"": ""orange""
},
{
""name"": ""kitty""
}
],
""age"": 2,
""hasVaccinations"": ""true"",
""name"" : ""fluffy"",
""status"" : ""available""
}");
public BasicApiTest(BasicApiFixture fixture)
{
Client = fixture.CreateClient();
}
public HttpClient Client { get; }
[Fact]
public async Task Token_WithUnknownUser_ReturnsForbidden()
{
// Arrange & Act
var response = await Client.GetAsync("/token?username=fallguy@example.com");
// Assert
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
[Fact]
public async Task Token_WithKnownUser_ReturnsOkAndToken()
{
// Arrange & Act
var response = await Client.GetAsync("/token?username=reader@example.com");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType);
var token = await response.Content.ReadAsStringAsync();
Assert.NotNull(token);
Assert.NotEmpty(token);
}
[Fact]
public async Task FindByStatus_WithNoToken_ReturnsUnauthorized()
{
// Arrange & Act
var response = await Client.GetAsync("/pet/findByStatus?status=available");
// Assert
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Theory]
[InlineData("reader@example.com")]
[InlineData("writer@example.com")]
public async Task FindByStatus_WithToken_ReturnsOkAndPet(string username)
{
// Arrange & Act 1
var token = await Client.GetStringAsync($"/token?username={username}");
// Assert 1 (guard)
Assert.NotEmpty(token);
// Arrange 2
var request = new HttpRequestMessage(HttpMethod.Get, "/pet/findByStatus?status=available");
request.Headers.Add(HeaderNames.Authorization, $"Bearer {token}");
// Act 2
var response = await Client.SendAsync(request);
// Assert 2
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
var json = await response.Content.ReadAsStringAsync();
Assert.NotNull(json);
Assert.NotEmpty(json);
}
[Fact]
public async Task FindById_WithInvalidPetId_ReturnsNotFound()
{
// Arrange & Act 1
var token = await Client.GetStringAsync("/token?username=reader@example.com");
// Assert 1 (guard)
Assert.NotEmpty(token);
// Arrange 2
var request = new HttpRequestMessage(HttpMethod.Get, "/pet/100");
request.Headers.Add(HeaderNames.Authorization, $"Bearer {token}");
// Act 2
var response = await Client.SendAsync(request);
// Assert 2
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task FindById_WithValidPetId_ReturnsOkAndPet()
{
// Arrange & Act 1
var token = await Client.GetStringAsync("/token?username=reader@example.com");
// Assert 1 (guard)
Assert.NotEmpty(token);
// Arrange 2
var request = new HttpRequestMessage(HttpMethod.Get, "/pet/-1");
request.Headers.Add(HeaderNames.Authorization, $"Bearer {token}");
// Act 2
var response = await Client.SendAsync(request);
// Assert 2
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
var json = await response.Content.ReadAsStringAsync();
Assert.NotNull(json);
Assert.NotEmpty(json);
}
[Fact]
public async Task AddPet_WithInsufficientClaims_ReturnsForbidden()
{
// Arrange & Act 1
var token = await Client.GetStringAsync("/token?username=reader@example.com");
// Assert 1 (guard)
Assert.NotEmpty(token);
// Arrange 2
var request = new HttpRequestMessage(HttpMethod.Post, "/pet")
{
Content = new ByteArrayContent(PetBytes)
{
Headers =
{
{ "Content-Type", "application/json" },
},
},
Headers =
{
{ HeaderNames.Authorization, $"Bearer {token}" },
},
};
// Act 2
var response = await Client.SendAsync(request);
// Assert 2
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
[Fact]
public async Task AddPet_WithValidClaims_ReturnsCreated()
{
// Arrange & Act 1
var token = await Client.GetStringAsync("/token?username=writer@example.com");
// Assert 1 (guard)
Assert.NotEmpty(token);
// Arrange 2
var request = new HttpRequestMessage(HttpMethod.Post, "/pet")
{
Content = new ByteArrayContent(PetBytes)
{
Headers =
{
{ HeaderNames.ContentType, "application/json" },
},
},
Headers =
{
{ HeaderNames.Authorization, $"Bearer {token}" },
},
};
// Act 2
var response = await Client.SendAsync(request);
// Assert 2
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var location = response.Headers.Location.ToString();
Assert.NotNull(location);
Assert.EndsWith("/1", location);
}
}
}

View File

@ -0,0 +1,83 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class BasicViewsTest : IClassFixture<BasicViewsFixture>
{
public BasicViewsTest(BasicViewsFixture fixture)
{
Client = fixture.CreateClient();
}
public HttpClient Client { get; }
[Theory]
[InlineData("/")]
[InlineData("/Home/HtmlHelpers")]
public async Task Get_ReturnsOkAndAntiforgeryToken(string path)
{
// Arrange & Act
var response = await Client.GetAsync(path);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/html", response.Content.Headers.ContentType.MediaType);
var html = await response.Content.ReadAsStringAsync();
Assert.NotNull(html);
Assert.NotEmpty(html);
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(html, "/");
Assert.NotNull(token);
Assert.NotEmpty(token);
}
[Theory]
[InlineData("/")]
[InlineData("/Home/HtmlHelpers")]
public async Task Post_ReturnsOkAndNewPerson(string path)
{
// Arrange & Act 1
var html = await Client.GetStringAsync(path);
// Assert 1 (guard)
Assert.NotEmpty(html);
// Arrange 2
var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(html, "/");
var name = Guid.NewGuid().ToString();
name = name.Substring(startIndex: 0, length: name.LastIndexOf('-'));
var form = new Dictionary<string, string>
{
{ "__RequestVerificationToken", token },
{ "Age", "12" },
{ "BirthDate", "2006-03-01T09:51:43.041-07:00" },
{ "Name", name },
};
var content = new FormUrlEncodedContent(form);
var request = new HttpRequestMessage(HttpMethod.Post, path)
{
Content = content,
};
// Act 2
var response = await Client.SendAsync(request);
// Assert 2
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.NotNull(body);
Assert.Contains($@"value=""{name}""", body);
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BasicApi;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class BasicApiFixture : MvcTestFixture<Startup>
{
// Do not leave .db file behind. Also, ensure added pet gets expected id (1) in subsequent runs.
protected override void Dispose(bool disposing)
{
if (disposing)
{
Startup.DropDatabase(Server.Host.Services);
}
base.Dispose(disposing);
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BasicViews;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class BasicViewsFixture : MvcTestFixture<Startup>
{
// Do not leave .db file behind.
protected override void Dispose(bool disposing)
{
if (disposing)
{
Startup.DropDatabase(Server.Host.Services);
}
base.Dispose(disposing);
}
}
}

View File

@ -24,6 +24,8 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Testing\Microsoft.AspNetCore.Mvc.Testing.csproj" />
<ProjectReference Include="..\..\benchmarkapps\BasicApi\BasicApi.csproj" />
<ProjectReference Include="..\..\benchmarkapps\BasicViews\BasicViews.csproj" />
<ProjectReference Include="..\..\samples\MvcSandbox\MvcSandbox.csproj" />
<ProjectReference Include="..\WebSites\ApiExplorerWebSite\ApiExplorerWebSite.csproj" />