This commit is contained in:
Ramón Tomás 2017-06-05 21:54:13 +02:00
commit b7c5613238
25 changed files with 164 additions and 65 deletions

View File

@ -2,7 +2,7 @@ version: '2'
services: services:
ci-build: ci-build:
image: microsoft/aspnetcore-build:1.0-1.1 image: microsoft/aspnetcore-build:1.1.2
volumes: volumes:
- .:/src - .:/src
working_dir: /src working_dir: /src

View File

@ -0,0 +1,56 @@
version: '2'
services:
basket.api:
image: eshop/basket.api
depends_on:
- basket.data
- identity.api
- rabbitmq
catalog.api:
image: eshop/catalog.api
depends_on:
- sql.data
- rabbitmq
identity.api:
image: eshop/identity.api
depends_on:
- sql.data
ordering.api:
image: eshop/ordering.api
depends_on:
- sql.data
webspa:
image: eshop/webspa
depends_on:
- identity.api
- basket.api
webmvc:
image: eshop/webmvc
depends_on:
- catalog.api
- ordering.api
- identity.api
- basket.api
sql.data:
image: microsoft/mssql-server-linux
basket.data:
image: redis
ports:
- "6379:6379"
rabbitmq:
image: rabbitmq
ports:
- "5672:5672"
webstatus:
image: eshop/webstatus

View File

@ -5,7 +5,7 @@ services:
image: eshop/basket.api image: eshop/basket.api
build: build:
context: ./src/Services/Basket/Basket.API context: ./src/Services/Basket/Basket.API
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- basket.data - basket.data
- identity.api - identity.api
@ -15,7 +15,7 @@ services:
image: eshop/catalog.api image: eshop/catalog.api
build: build:
context: ./src/Services/Catalog/Catalog.API context: ./src/Services/Catalog/Catalog.API
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- sql.data - sql.data
- rabbitmq - rabbitmq
@ -24,7 +24,7 @@ services:
image: eshop/identity.api image: eshop/identity.api
build: build:
context: ./src/Services/Identity/Identity.API context: ./src/Services/Identity/Identity.API
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- sql.data - sql.data
@ -32,7 +32,7 @@ services:
image: eshop/ordering.api image: eshop/ordering.api
build: build:
context: ./src/Services/Ordering/Ordering.API context: ./src/Services/Ordering/Ordering.API
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- sql.data - sql.data
@ -40,7 +40,7 @@ services:
image: eshop/webspa image: eshop/webspa
build: build:
context: ./src/Web/WebSPA context: ./src/Web/WebSPA
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- identity.api - identity.api
- basket.api - basket.api
@ -49,7 +49,7 @@ services:
image: eshop/webmvc image: eshop/webmvc
build: build:
context: ./src/Web/WebMVC context: ./src/Web/WebMVC
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- catalog.api - catalog.api
- ordering.api - ordering.api
@ -73,4 +73,5 @@ services:
image: eshop/webstatus image: eshop/webstatus
build: build:
context: ./src/Web/WebStatus context: ./src/Web/WebStatus
dockerfile: Dockerfile dockerfile: Dockerfile

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26403.3 VisualStudioVersion = 15.0.26430.12
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
EndProject EndProject

View File

@ -96,7 +96,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
// as it is disposed after each call // as it is disposed after each call
var origin = GetOriginFromUri(uri); var origin = GetOriginFromUri(uri);
return HttpInvoker(origin, () => return HttpInvoker(origin, async () =>
{ {
var requestMessage = new HttpRequestMessage(method, uri); var requestMessage = new HttpRequestMessage(method, uri);
@ -112,7 +112,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
requestMessage.Headers.Add("x-requestid", requestId); requestMessage.Headers.Add("x-requestid", requestId);
} }
var response = _client.SendAsync(requestMessage).Result; var response = await _client.SendAsync(requestMessage);
// raise exception if HttpResponseCode 500 // raise exception if HttpResponseCode 500
// needed for circuit breaker to track fails // needed for circuit breaker to track fails
@ -122,7 +122,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
throw new HttpRequestException(); throw new HttpRequestException();
} }
return Task.FromResult(response); return response;
}); });
} }
@ -132,13 +132,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
if (!_policyWrappers.TryGetValue(normalizedOrigin, out PolicyWrap policyWrap)) if (!_policyWrappers.TryGetValue(normalizedOrigin, out PolicyWrap policyWrap))
{ {
policyWrap = Policy.Wrap(_policyCreator(normalizedOrigin).ToArray()); policyWrap = Policy.WrapAsync(_policyCreator(normalizedOrigin).ToArray());
_policyWrappers.TryAdd(normalizedOrigin, policyWrap); _policyWrappers.TryAdd(normalizedOrigin, policyWrap);
} }
// Executes the action applying all // Executes the action applying all
// the policies defined in the wrapper // the policies defined in the wrapper
return await policyWrap.Execute(action, new Context(normalizedOrigin)); return await policyWrap.ExecuteAsync(action, new Context(normalizedOrigin));
} }

View File

@ -14,8 +14,9 @@ namespace eShopOnContainers.Core.Services.Identity
// Dictionary with values for the authorize request // Dictionary with values for the authorize request
var dic = new Dictionary<string, string>(); var dic = new Dictionary<string, string>();
dic.Add("client_id", "xamarin"); dic.Add("client_id", "xamarin");
dic.Add("response_type", "id_token token"); dic.Add("client_secret", "secret");
dic.Add("scope", "openid profile basket orders"); dic.Add("response_type", "code id_token token");
dic.Add("scope", "openid profile basket orders offline_access");
dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback); dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback);
dic.Add("nonce", Guid.NewGuid().ToString("N")); dic.Add("nonce", Guid.NewGuid().ToString("N"));
@ -24,7 +25,7 @@ namespace eShopOnContainers.Core.Services.Identity
var currentCSRFToken = Guid.NewGuid().ToString("N"); var currentCSRFToken = Guid.NewGuid().ToString("N");
dic.Add("state", currentCSRFToken); dic.Add("state", currentCSRFToken);
var authorizeUri = authorizeRequest.Create(dic); var authorizeUri = authorizeRequest.Create(dic);
return authorizeUri; return authorizeUri;
} }

View File

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<PackageTargetFallback>$(PackageTargetFallback);netstandard1.6.1;dnxcore50;portable-net451+win8</PackageTargetFallback> <PackageTargetFallback>$(PackageTargetFallback);netstandard1.6.1;dnxcore50;portable-net451+win8</PackageTargetFallback>
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath> <DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath>

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.1 FROM microsoft/aspnetcore:1.1.2
ARG source ARG source
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80

View File

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<UserSecretsId>aspnet-Catalog.API-20161122013618</UserSecretsId> <UserSecretsId>aspnet-Catalog.API-20161122013618</UserSecretsId>
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback> <PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback>
@ -44,6 +45,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
<PackageReference Include="Polly" Version="5.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.1 FROM microsoft/aspnetcore:1.1.2
ARG source ARG source
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80

View File

@ -17,11 +17,13 @@
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Polly;
using RabbitMQ.Client; using RabbitMQ.Client;
using System; using System;
using System.Data.Common; using System.Data.Common;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
public class Startup public class Startup
{ {
@ -85,7 +87,7 @@
services.AddSwaggerGen(options => services.AddSwaggerGen(options =>
{ {
options.DescribeAllEnumsAsStrings(); options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1",new Swashbuckle.AspNetCore.Swagger.Info options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{ {
Title = "eShopOnContainers - Catalog HTTP API", Title = "eShopOnContainers - Catalog HTTP API",
Version = "v1", Version = "v1",
@ -144,11 +146,7 @@
var context = (CatalogContext)app var context = (CatalogContext)app
.ApplicationServices.GetService(typeof(CatalogContext)); .ApplicationServices.GetService(typeof(CatalogContext));
WaitForSqlAvailability(context, loggerFactory); WaitForSqlAvailabilityAsync(context, loggerFactory, app).Wait();
//Seed Data
CatalogContextSeed.SeedAsync(app, loggerFactory)
.Wait();
var integrationEventLogContext = new IntegrationEventLogContext( var integrationEventLogContext = new IntegrationEventLogContext(
new DbContextOptionsBuilder<IntegrationEventLogContext>() new DbContextOptionsBuilder<IntegrationEventLogContext>()
@ -158,28 +156,28 @@
integrationEventLogContext.Database.Migrate(); integrationEventLogContext.Database.Migrate();
} }
private void WaitForSqlAvailability(CatalogContext ctx, ILoggerFactory loggerFactory, int? retry = 0) private async Task WaitForSqlAvailabilityAsync(CatalogContext ctx, ILoggerFactory loggerFactory, IApplicationBuilder app, int retries = 0)
{ {
int retryForAvailability = retry.Value; var logger = loggerFactory.CreateLogger(nameof(Startup));
var policy = CreatePolicy(retries, logger, nameof (WaitForSqlAvailabilityAsync));
await policy.ExecuteAsync(async () =>
{
await CatalogContextSeed.SeedAsync(app, loggerFactory);
});
try }
{
ctx.Database.OpenConnection(); private Policy CreatePolicy(int retries, ILogger logger, string prefix)
} {
catch (SqlException ex) return Policy.Handle<SqlException>().
{ WaitAndRetryAsync(
if (retryForAvailability < 10) retryCount: retries,
{ sleepDurationProvider: retry => TimeSpan.FromSeconds(5),
retryForAvailability++; onRetry: (exception, timeSpan, retry, ctx) =>
var log = loggerFactory.CreateLogger(nameof(Startup)); {
log.LogError(ex.Message); logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}");
WaitForSqlAvailability(ctx, loggerFactory, retryForAvailability); }
} );
}
finally
{
ctx.Database.CloseConnection();
}
} }
} }
} }

View File

@ -1,7 +1,6 @@
using IdentityServer4.Models; using IdentityServer4;
using Microsoft.Extensions.Options; using IdentityServer4.Models;
using System.Collections.Generic; using System.Collections.Generic;
using IdentityServer4;
namespace Identity.API.Configuration namespace Identity.API.Configuration
{ {
@ -56,19 +55,26 @@ namespace Identity.API.Configuration
{ {
ClientId = "xamarin", ClientId = "xamarin",
ClientName = "eShop Xamarin OpenId Client", ClientName = "eShop Xamarin OpenId Client",
AllowedGrantTypes = GrantTypes.Implicit, AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = true, //Used to retrieve the access token on the back channel.
RedirectUris = { clientsUrl["Xamarin"] }, ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { clientsUrl["Xamarin"] },
RequireConsent = false, RequireConsent = false,
PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Account/Redirecting" }, PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Account/Redirecting" },
AllowedCorsOrigins = { "http://eshopxamarin" }, AllowedCorsOrigins = { "http://eshopxamarin" },
AllowedScopes = AllowedScopes = new List<string>
{ {
IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"orders", "orders",
"basket" "basket"
} },
//Allow requesting refresh tokens for long lived API access
AllowOfflineAccess = true
}, },
new Client new Client
{ {
@ -97,7 +103,7 @@ namespace Identity.API.Configuration
IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess, IdentityServerConstants.StandardScopes.OfflineAccess,
"orders", "orders",
"basket", "basket"
}, },
} }
}; };

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.1 FROM microsoft/aspnetcore:1.1.2
ARG source ARG source
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80

View File

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<UserSecretsId>aspnet-eShopOnContainers.Identity-90487118-103c-4ff0-b9da-e5e26f7ab0c5</UserSecretsId> <UserSecretsId>aspnet-eShopOnContainers.Identity-90487118-103c-4ff0-b9da-e5e26f7ab0c5</UserSecretsId>
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback> <PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback>

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.1 FROM microsoft/aspnetcore:1.1.2
ARG source ARG source
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80

View File

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<UserSecretsId>aspnet-Ordering.API-20161122013547</UserSecretsId> <UserSecretsId>aspnet-Ordering.API-20161122013547</UserSecretsId>
<PackageTargetFallback>$(PackageTargetFallback);netstandard1.6.1;dnxcore50;portable-net451+win8</PackageTargetFallback> <PackageTargetFallback>$(PackageTargetFallback);netstandard1.6.1;dnxcore50;portable-net451+win8</PackageTargetFallback>
@ -57,6 +58,7 @@
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" /> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" />
<PackageReference Include="Dapper" Version="1.50.2" /> <PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="System.ValueTuple" Version="4.3.1" /> <PackageReference Include="System.ValueTuple" Version="4.3.1" />
<PackageReference Include="Polly" Version="5.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -23,10 +23,13 @@
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ordering.Infrastructure; using Ordering.Infrastructure;
using Polly;
using RabbitMQ.Client; using RabbitMQ.Client;
using System; using System;
using System.Data.Common; using System.Data.Common;
using System.Data.SqlClient;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
public class Startup public class Startup
{ {
@ -156,7 +159,7 @@
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
}); });
OrderingContextSeed.SeedAsync(app).Wait(); WaitForSqlAvailabilityAsync(loggerFactory, app).Wait();
var integrationEventLogContext = new IntegrationEventLogContext( var integrationEventLogContext = new IntegrationEventLogContext(
new DbContextOptionsBuilder<IntegrationEventLogContext>() new DbContextOptionsBuilder<IntegrationEventLogContext>()
@ -175,5 +178,30 @@
RequireHttpsMetadata = false RequireHttpsMetadata = false
}); });
} }
private async Task WaitForSqlAvailabilityAsync(ILoggerFactory loggerFactory, IApplicationBuilder app, int retries = 0)
{
var logger = loggerFactory.CreateLogger(nameof(Startup));
var policy = CreatePolicy(retries, logger, nameof(WaitForSqlAvailabilityAsync));
await policy.ExecuteAsync(async () =>
{
await OrderingContextSeed.SeedAsync(app);
});
}
private Policy CreatePolicy(int retries, ILogger logger, string prefix)
{
return Policy.Handle<SqlException>().
WaitAndRetryAsync(
retryCount: retries,
sleepDurationProvider: retry => TimeSpan.FromSeconds(5),
onRetry: (exception, timeSpan, retry, ctx) =>
{
logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}");
}
);
}
} }
} }

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.1 FROM microsoft/aspnetcore:1.1.2
ARG source ARG source
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80

View File

@ -20,7 +20,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
=> new Policy[] => new Policy[]
{ {
Policy.Handle<HttpRequestException>() Policy.Handle<HttpRequestException>()
.WaitAndRetry( .WaitAndRetryAsync(
// number of retries // number of retries
6, 6,
// exponential backofff // exponential backofff
@ -36,7 +36,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure
_logger.LogDebug(msg); _logger.LogDebug(msg);
}), }),
Policy.Handle<HttpRequestException>() Policy.Handle<HttpRequestException>()
.CircuitBreaker( .CircuitBreakerAsync(
// number of exceptions before breaking circuit // number of exceptions before breaking circuit
5, 5,
// time circuit opened before retry // time circuit opened before retry

View File

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<UserSecretsId>aspnet-Microsoft.eShopOnContainers-946ae052-8305-4a99-965b-ec8636ddbae3</UserSecretsId> <UserSecretsId>aspnet-Microsoft.eShopOnContainers-946ae052-8305-4a99-965b-ec8636ddbae3</UserSecretsId>
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback> <PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback>

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.1 FROM microsoft/aspnetcore:1.1.2
ARG source ARG source
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80

View File

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<PackageId>eShopOnContainers.WebSPA</PackageId> <PackageId>eShopOnContainers.WebSPA</PackageId>
<UserSecretsId>aspnetcorespa-c23d27a4-eb88-4b18-9b77-2a93f3b15119</UserSecretsId> <UserSecretsId>aspnetcorespa-c23d27a4-eb88-4b18-9b77-2a93f3b15119</UserSecretsId>

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.1 FROM microsoft/aspnetcore:1.1.2
ARG source ARG source
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80

View File

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework> <TargetFramework>netcoreapp1.1.2</TargetFramework>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>