Adding Endpoints with Authorization in separate PublicApi project (#413)

* Adding tests for GetById endpoint

* Updating tests and messages

* Adding paged endpoint and also AutoMapper

* Authenticate endpoint works as bool with tests

* Got JWT token security working with Create and Delete endpoints and Swashbuckle.

* Working on getting cookie and jwt token auth working in the same app
All tests are passing

* Creating new project and moving APIs
Build succeeds; tests need updated.

* all tests passing after moving services to PublicApi project

* Fix authorize attributes

* Uncomment and update ApiCatalogControllerLists tests

Co-authored-by: Eric Fleming <eric-fleming18@hotmail.com>
This commit is contained in:
Steve Smith
2020-06-30 11:36:28 -04:00
committed by GitHub
parent e5e9868003
commit dd5a435cb4
49 changed files with 1024 additions and 75 deletions

View File

@@ -0,0 +1,8 @@
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints
{
public class AuthenticateRequest : BaseRequest
{
public string Username { get; set; }
public string Password { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints
{
public class AuthenticateResponse : BaseResponse
{
public AuthenticateResponse(Guid correlationId) : base(correlationId)
{
}
public AuthenticateResponse()
{
}
public bool Result { get; set; }
public string Token { get; set; }
}
}

View File

@@ -0,0 +1,50 @@
using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.ApplicationCore.Constants;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.IdentityModel.Tokens;
using Swashbuckle.AspNetCore.Annotations;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints
{
public class Authenticate : BaseAsyncEndpoint<AuthenticateRequest, AuthenticateResponse>
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ITokenClaimsService _tokenClaimsService;
public Authenticate(SignInManager<ApplicationUser> signInManager,
ITokenClaimsService tokenClaimsService)
{
_signInManager = signInManager;
_tokenClaimsService = tokenClaimsService;
}
[HttpPost("api/authenticate")]
[SwaggerOperation(
Summary = "Authenticates a user",
Description = "Authenticates a user",
OperationId = "auth.authenticate",
Tags = new[] { "AuthEndpoints" })
]
public override async Task<ActionResult<AuthenticateResponse>> HandleAsync(AuthenticateRequest request)
{
var response = new AuthenticateResponse(request.CorrelationId());
var result = await _signInManager.PasswordSignInAsync(request.Username, request.Password, false, true);
response.Result = result.Succeeded;
response.Token = await _tokenClaimsService.GetTokenAsync(request.Username);
return response;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace Microsoft.eShopWeb.PublicApi
{
/// <summary>
/// Base class used by API requests
/// </summary>
public abstract class BaseMessage
{
/// <summary>
/// Unique Identifier used by logging
/// </summary>
protected Guid _correlationId = Guid.NewGuid();
public Guid CorrelationId() => _correlationId;
}
}

View File

@@ -0,0 +1,9 @@
namespace Microsoft.eShopWeb.PublicApi
{
/// <summary>
/// Base class used by API requests
/// </summary>
public abstract class BaseRequest : BaseMessage
{
}
}

View File

@@ -0,0 +1,19 @@
using System;
namespace Microsoft.eShopWeb.PublicApi
{
/// <summary>
/// Base class used by API responses
/// </summary>
public abstract class BaseResponse : BaseMessage
{
public BaseResponse(Guid correlationId) : base()
{
base._correlationId = correlationId;
}
public BaseResponse()
{
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class CatalogItemDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string PictureUri { get; set; }
public int CatalogTypeId { get; set; }
public int CatalogBrandId { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class CreateCatalogItemRequest : BaseRequest
{
public int CatalogBrandId { get; set; }
public int CatalogTypeId { get; set; }
public string Description { get; set; }
public string Name { get; set; }
public string PictureUri { get; set; }
public decimal Price { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class CreateCatalogItemResponse : BaseResponse
{
public CreateCatalogItemResponse(Guid correlationId) : base(correlationId)
{
}
public CreateCatalogItemResponse()
{
}
public CatalogItemDto CatalogItem { get; set; }
}
}

View File

@@ -0,0 +1,52 @@
using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.ApplicationCore.Constants;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Swashbuckle.AspNetCore.Annotations;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
[Authorize(Roles = AuthorizationConstants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class Create : BaseAsyncEndpoint<CreateCatalogItemRequest, CreateCatalogItemResponse>
{
private readonly IAsyncRepository<CatalogItem> _itemRepository;
public Create(IAsyncRepository<CatalogItem> itemRepository)
{
_itemRepository = itemRepository;
}
[HttpPost("api/catalog-items")]
[SwaggerOperation(
Summary = "Creates a new Catalog Item",
Description = "Creates a new Catalog Item",
OperationId = "catalog-items.create",
Tags = new[] { "CatalogItemEndpoints" })
]
public override async Task<ActionResult<CreateCatalogItemResponse>> HandleAsync(CreateCatalogItemRequest request)
{
var response = new CreateCatalogItemResponse(request.CorrelationId());
CatalogItem newItem = new CatalogItem(request.CatalogTypeId, request.CatalogBrandId, request.Description, request.Name, request.Price, request.PictureUri);
newItem = await _itemRepository.AddAsync(newItem);
var dto = new CatalogItemDto
{
Id = newItem.Id,
CatalogBrandId = newItem.CatalogBrandId,
CatalogTypeId = newItem.CatalogTypeId,
Description = newItem.Description,
Name = newItem.Name,
PictureUri = newItem.PictureUri,
Price = newItem.Price
};
response.CatalogItem = dto;
return response;
}
}
}

View File

@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Mvc;
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class DeleteCatalogItemRequest : BaseRequest
{
//[FromRoute]
public int CatalogItemId { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class DeleteCatalogItemResponse : BaseResponse
{
public DeleteCatalogItemResponse(Guid correlationId) : base(correlationId)
{
}
public DeleteCatalogItemResponse()
{
}
public string Status { get; set; } = "Deleted";
}
}

View File

@@ -0,0 +1,42 @@
using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.ApplicationCore.Constants;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Swashbuckle.AspNetCore.Annotations;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
[Authorize(Roles = AuthorizationConstants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class Delete : BaseAsyncEndpoint<DeleteCatalogItemRequest, DeleteCatalogItemResponse>
{
private readonly IAsyncRepository<CatalogItem> _itemRepository;
public Delete(IAsyncRepository<CatalogItem> itemRepository)
{
_itemRepository = itemRepository;
}
[HttpDelete("api/catalog-items/{CatalogItemId}")]
[SwaggerOperation(
Summary = "Deletes a Catalog Item",
Description = "Deletes a Catalog Item",
OperationId = "catalog-items.Delete",
Tags = new[] { "CatalogItemEndpoints" })
]
public override async Task<ActionResult<DeleteCatalogItemResponse>> HandleAsync([FromRoute]DeleteCatalogItemRequest request)
{
var response = new DeleteCatalogItemResponse(request.CorrelationId());
var itemToDelete = await _itemRepository.GetByIdAsync(request.CatalogItemId);
if (itemToDelete is null) return NotFound();
await _itemRepository.DeleteAsync(itemToDelete);
return Ok(response);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class GetByIdCatalogItemRequest : BaseRequest
{
public int CatalogItemId { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class GetByIdCatalogItemResponse : BaseResponse
{
public GetByIdCatalogItemResponse(Guid correlationId) : base(correlationId)
{
}
public GetByIdCatalogItemResponse()
{
}
public CatalogItemDto CatalogItem { get; set; }
}
}

View File

@@ -0,0 +1,46 @@
using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Swashbuckle.AspNetCore.Annotations;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class GetById : BaseAsyncEndpoint<GetByIdCatalogItemRequest, GetByIdCatalogItemResponse>
{
private readonly IAsyncRepository<CatalogItem> _itemRepository;
public GetById(IAsyncRepository<CatalogItem> itemRepository)
{
_itemRepository = itemRepository;
}
[HttpGet("api/catalog-items/{CatalogItemId}")]
[SwaggerOperation(
Summary = "Get a Catalog Item by Id",
Description = "Gets a Catalog Item by Id",
OperationId = "catalog-items.GetById",
Tags = new[] { "CatalogItemEndpoints" })
]
public override async Task<ActionResult<GetByIdCatalogItemResponse>> HandleAsync([FromRoute]GetByIdCatalogItemRequest request)
{
var response = new GetByIdCatalogItemResponse(request.CorrelationId());
var item = await _itemRepository.GetByIdAsync(request.CatalogItemId);
if (item is null) return NotFound();
response.CatalogItem = new CatalogItemDto
{
Id = item.Id,
CatalogBrandId = item.CatalogBrandId,
CatalogTypeId = item.CatalogTypeId,
Description = item.Description,
Name = item.Name,
PictureUri = item.PictureUri,
Price = item.Price
};
return Ok(response);
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class ListPagedCatalogItemRequest : BaseRequest
{
public int PageSize { get; set; }
public int PageIndex { get; set; }
public int? CatalogBrandId { get; set; }
public int? CatalogTypeId { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class ListPagedCatalogItemResponse : BaseResponse
{
public ListPagedCatalogItemResponse(Guid correlationId) : base(correlationId)
{
}
public ListPagedCatalogItemResponse()
{
}
public List<CatalogItemDto> CatalogItems { get; set; } = new List<CatalogItemDto>();
public int PageCount { get; set; }
}
}

View File

@@ -0,0 +1,61 @@
using Ardalis.ApiEndpoints;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Specifications;
using Swashbuckle.AspNetCore.Annotations;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
{
public class ListPaged : BaseAsyncEndpoint<ListPagedCatalogItemRequest, ListPagedCatalogItemResponse>
{
private readonly IAsyncRepository<CatalogItem> _itemRepository;
private readonly IUriComposer _uriComposer;
private readonly IMapper _mapper;
public ListPaged(IAsyncRepository<CatalogItem> itemRepository,
IUriComposer uriComposer,
IMapper mapper)
{
_itemRepository = itemRepository;
_uriComposer = uriComposer;
_mapper = mapper;
}
[HttpGet("api/catalog-items")]
[SwaggerOperation(
Summary = "List Catalog Items (paged)",
Description = "List Catalog Items (paged)",
OperationId = "catalog-items.ListPaged",
Tags = new[] { "CatalogItemEndpoints" })
]
public override async Task<ActionResult<ListPagedCatalogItemResponse>> HandleAsync([FromQuery]ListPagedCatalogItemRequest request)
{
var response = new ListPagedCatalogItemResponse(request.CorrelationId());
var filterSpec = new CatalogFilterSpecification(request.CatalogBrandId, request.CatalogTypeId);
int totalItems = await _itemRepository.CountAsync(filterSpec);
var pagedSpec = new CatalogFilterPaginatedSpecification(
skip: request.PageIndex * request.PageSize,
take: request.PageSize,
brandId: request.CatalogBrandId,
typeId: request.CatalogTypeId);
var items = await _itemRepository.ListAsync(pagedSpec);
response.CatalogItems.AddRange(items.Select(_mapper.Map<CatalogItemDto>));
foreach (CatalogItemDto item in response.CatalogItems)
{
item.PictureUri = _uriComposer.ComposePicUri(item.PictureUri);
}
response.PageCount = int.Parse(Math.Ceiling((decimal)totalItems / request.PageSize).ToString());
return Ok(response);
}
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Microsoft.eShopWeb.PublicApi
{
public class CustomSchemaFilters : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var excludeProperties = new[] { "CorrelationId" };
foreach (var prop in excludeProperties)
if (schema.Properties.ContainsKey(prop))
schema.Properties.Remove(prop);
}
}
}

22
src/PublicApi/Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["src/PublicApi/PublicApi.csproj", "src/PublicApi/"]
RUN dotnet restore "src/PublicApi/PublicApi.csproj"
COPY . .
WORKDIR "/src/src/PublicApi"
RUN dotnet build "PublicApi.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "PublicApi.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "PublicApi.dll"]

View File

@@ -0,0 +1,14 @@
using AutoMapper;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints;
namespace Microsoft.eShopWeb.PublicApi
{
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<CatalogItem, CatalogItemDto>();
}
}
}

51
src/PublicApi/Program.cs Normal file
View File

@@ -0,0 +1,51 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.PublicApi
{
public class Program
{
public async static Task Main(string[] args)
{
var host = CreateHostBuilder(args)
.Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
try
{
var catalogContext = services.GetRequiredService<CatalogContext>();
await CatalogContextSeed.SeedAsync(catalogContext, loggerFactory);
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
await AppIdentityDbContextSeed.SeedAsync(userManager, roleManager);
}
catch (Exception ex)
{
var logger = loggerFactory.CreateLogger<Program>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@@ -0,0 +1,37 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:52023",
"sslPort": 44339
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"PublicApi": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"publishAllPorts": true,
"useSSL": true
}
}
}

View File

@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Microsoft.eShopWeb.PublicApi</RootNamespace>
<UserSecretsId>5b662463-1efd-4bae-bde4-befe0be3e8ff</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ardalis.ApiEndpoints" Version="1.0.0" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="5.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.3" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ApplicationCore\ApplicationCore.csproj" />
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>
</Project>

4
src/PublicApi/README.md Normal file
View File

@@ -0,0 +1,4 @@
# API Endpoints
This folder demonstrates how to configure API endpoints as individual classes. You can compare it to the traditional controller-based approach found in /Controllers/Api.

193
src/PublicApi/Startup.cs Normal file
View File

@@ -0,0 +1,193 @@
using System.Collections.Generic;
using System.Text;
using AutoMapper;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopWeb.ApplicationCore.Constants;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Services;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.eShopWeb.Infrastructure.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
namespace Microsoft.eShopWeb.PublicApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureDevelopmentServices(IServiceCollection services)
{
// use in-memory database
ConfigureInMemoryDatabases(services);
// use real database
//ConfigureProductionServices(services);
}
private void ConfigureInMemoryDatabases(IServiceCollection services)
{
// use in-memory database
services.AddDbContext<CatalogContext>(c =>
c.UseInMemoryDatabase("Catalog"));
// Add Identity DbContext
services.AddDbContext<AppIdentityDbContext>(options =>
options.UseInMemoryDatabase("Identity"));
ConfigureServices(services);
}
public void ConfigureProductionServices(IServiceCollection services)
{
// use real database
// Requires LocalDB which can be installed with SQL Server Express 2016
// https://www.microsoft.com/en-us/download/details.aspx?id=54284
services.AddDbContext<CatalogContext>(c =>
c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection")));
// Add Identity DbContext
services.AddDbContext<AppIdentityDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection")));
ConfigureServices(services);
}
public void ConfigureTestingServices(IServiceCollection services)
{
ConfigureInMemoryDatabases(services);
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
services.Configure<CatalogSettings>(Configuration);
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>();
// Add memory cache services
services.AddMemoryCache();
// https://stackoverflow.com/questions/46938248/asp-net-core-2-0-combining-cookies-and-bearer-authorization-for-the-same-endpoin
var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY);
services.AddAuthentication(config =>
{
//config.DefaultScheme = "smart";
//config.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
//config.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
config.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
config.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
config.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddControllers();
services.AddAutoMapper(typeof(Startup).Assembly);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
c.EnableAnnotations();
c.SchemaFilter<CustomSchemaFilters>();
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n
Enter 'Bearer' [space] and then your token in the text input below.
\r\n\r\nExample: 'Bearer 12345abcdef'",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"ConnectionStrings": {
"CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;",
"IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;"
},
"CatalogBaseUrl": "",
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning",
"System": "Warning"
},
"AllowedHosts": "*"
}
}