Added sample for use new IHttpClientFactory feature on WebMVC

This commit is contained in:
Unai Zorrilla Castro 2018-05-16 12:44:32 +02:00
parent 36fb20acea
commit cf4b7ff47f
6 changed files with 289 additions and 140 deletions

View File

@ -71,7 +71,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{ {
var user = _appUserParser.Parse(HttpContext.User); var user = _appUserParser.Parse(HttpContext.User);
await _basketSvc.AddItemToBasket(user, productDetails.Id); await _basketSvc.AddItemToBasket(user, productDetails.Id);
//await _basketSvc.AddItemToBasket(user, product);
} }
return RedirectToAction("Index", "Catalog"); return RedirectToAction("Index", "Catalog");
} }

View File

@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace WebMVC.Infrastructure
{
public class HttpClientAuthorizationDelegatingHandler
: DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccesor;
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccesor)
{
_httpContextAccesor = httpContextAccesor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var authorizationHeader = _httpContextAccesor.HttpContext
.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authorizationHeader))
{
request.Headers.Add("Authorization", new List<string>() { authorizationHeader });
}
var token = await GetToken();
if (token != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
async Task<string> GetToken()
{
const string ACCESS_TOKEN = "access_token";
return await _httpContextAccesor.HttpContext
.GetTokenAsync(ACCESS_TOKEN);
}
}
}

View File

@ -0,0 +1,60 @@
using Microsoft.Extensions.Logging;
using Polly;
using System;
using System.Net.Http;
namespace WebMVC.Infrastructure
{
public class HttpClientDefaultPolicies
{
const int RETRY_COUNT = 6;
const int EXCEPTIONS_ALLOWED_BEFORE_CIRCUIT_BREAKER = 5;
private readonly ILogger<HttpClientDefaultPolicies> _logger;
public HttpClientDefaultPolicies(ILogger<HttpClientDefaultPolicies> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public Policy GetWaitAndRetryPolicy()
{
return Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync(
// number of retries
RETRY_COUNT,
// exponential backofff
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
// on retry
(exception, timeSpan, retryCount, context) =>
{
var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
$"of {context.PolicyKey} " +
$"at {context.OperationKey}, " +
$"due to: {exception}.";
_logger.LogWarning(msg);
_logger.LogDebug(msg);
});
}
public Policy GetCircuitBreakerPolicy()
{
return Policy.Handle<HttpRequestException>()
.CircuitBreakerAsync(
// number of exceptions before breaking circuit
EXCEPTIONS_ALLOWED_BEFORE_CIRCUIT_BREAKER,
// time circuit opened before retry
TimeSpan.FromMinutes(1),
(exception, duration) =>
{
// on circuit opened
_logger.LogTrace("Circuit breaker opened");
},
() =>
{
// on circuit closed
_logger.LogTrace("Circuit breaker reset");
});
}
}
}

View File

@ -1,11 +1,9 @@
using Microsoft.AspNetCore.Authentication; using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using WebMVC.Infrastructure; using WebMVC.Infrastructure;
using WebMVC.Models; using WebMVC.Models;
@ -15,29 +13,26 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public class BasketService : IBasketService public class BasketService : IBasketService
{ {
private readonly IOptionsSnapshot<AppSettings> _settings; private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IHttpClient _apiClient; private readonly HttpClient _apiClient;
private readonly string _basketByPassUrl; private readonly string _basketByPassUrl;
private readonly string _purchaseUrl; private readonly string _purchaseUrl;
private readonly IHttpContextAccessor _httpContextAccesor;
private readonly string _bffUrl; private readonly string _bffUrl;
public BasketService(IOptionsSnapshot<AppSettings> settings, public BasketService(HttpClient httpClient,IOptionsSnapshot<AppSettings> settings)
IHttpContextAccessor httpContextAccesor, IHttpClient httpClient)
{ {
_apiClient = httpClient;
_settings = settings; _settings = settings;
_basketByPassUrl = $"{_settings.Value.PurchaseUrl}/api/v1/b/basket"; _basketByPassUrl = $"{_settings.Value.PurchaseUrl}/api/v1/b/basket";
_purchaseUrl = $"{_settings.Value.PurchaseUrl}/api/v1"; _purchaseUrl = $"{_settings.Value.PurchaseUrl}/api/v1";
_httpContextAccesor = httpContextAccesor;
_apiClient = httpClient;
} }
public async Task<Basket> GetBasket(ApplicationUser user) public async Task<Basket> GetBasket(ApplicationUser user)
{ {
var token = await GetUserTokenAsync();
var getBasketUri = API.Basket.GetBasket(_basketByPassUrl, user.Id); var getBasketUri = API.Basket.GetBasket(_basketByPassUrl, user.Id);
var dataString = await _apiClient.GetStringAsync(getBasketUri, token); var dataString = await _apiClient.GetStringAsync(getBasketUri);
return string.IsNullOrEmpty(dataString) ? return string.IsNullOrEmpty(dataString) ?
new Basket() { BuyerId = user.Id} : new Basket() { BuyerId = user.Id} :
@ -46,10 +41,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<Basket> UpdateBasket(Basket basket) public async Task<Basket> UpdateBasket(Basket basket)
{ {
var token = await GetUserTokenAsync();
var updateBasketUri = API.Basket.UpdateBasket(_basketByPassUrl); var updateBasketUri = API.Basket.UpdateBasket(_basketByPassUrl);
var content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(updateBasketUri, basket, token); var response = await _apiClient.PostAsync(updateBasketUri, content);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
@ -58,10 +53,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task Checkout(BasketDTO basket) public async Task Checkout(BasketDTO basket)
{ {
var token = await GetUserTokenAsync();
var updateBasketUri = API.Basket.CheckoutBasket(_basketByPassUrl); var updateBasketUri = API.Basket.CheckoutBasket(_basketByPassUrl);
var content = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(updateBasketUri, basket, token); var response = await _apiClient.PostAsync(updateBasketUri, content);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
@ -69,54 +64,52 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
public async Task<Basket> SetQuantities(ApplicationUser user, Dictionary<string, int> quantities) public async Task<Basket> SetQuantities(ApplicationUser user, Dictionary<string, int> quantities)
{ {
var token = await GetUserTokenAsync();
var updateBasketUri = API.Purchase.UpdateBasketItem(_purchaseUrl); var updateBasketUri = API.Purchase.UpdateBasketItem(_purchaseUrl);
var userId = user.Id; var basketUpdate = new
var response = await _apiClient.PutAsync(updateBasketUri, new
{ {
BasketId = userId, BasketId = user.Id,
Updates = quantities.Select(kvp => new Updates = quantities.Select(kvp => new
{ {
BasketItemId = kvp.Key, BasketItemId = kvp.Key,
NewQty = kvp.Value NewQty = kvp.Value
}).ToArray() }).ToArray()
}, token); };
var content = new StringContent(JsonConvert.SerializeObject(basketUpdate), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PutAsync(updateBasketUri,content);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync(); var jsonResponse = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Basket>(jsonResponse); return JsonConvert.DeserializeObject<Basket>(jsonResponse);
} }
public async Task<Order> GetOrderDraft(string basketId) public async Task<Order> GetOrderDraft(string basketId)
{ {
var token = await GetUserTokenAsync();
var draftOrderUri = API.Purchase.GetOrderDraft(_purchaseUrl, basketId); var draftOrderUri = API.Purchase.GetOrderDraft(_purchaseUrl, basketId);
var json = await _apiClient.GetStringAsync(draftOrderUri, token); var response = await _apiClient.GetStringAsync(draftOrderUri);
return JsonConvert.DeserializeObject<Order>(json);
return JsonConvert.DeserializeObject<Order>(response);
} }
public async Task AddItemToBasket(ApplicationUser user, int productId) public async Task AddItemToBasket(ApplicationUser user, int productId)
{ {
var token = await GetUserTokenAsync();
var updateBasketUri = API.Purchase.AddItemToBasket(_purchaseUrl); var updateBasketUri = API.Purchase.AddItemToBasket(_purchaseUrl);
var userId = user.Id;
var response = await _apiClient.PostAsync(updateBasketUri, new var newItem = new
{ {
CatalogItemId = productId, CatalogItemId = productId,
BasketId = userId, BasketId = user.Id,
Quantity = 1 Quantity = 1
}, token); };
} var content = new StringContent(JsonConvert.SerializeObject(newItem), System.Text.Encoding.UTF8, "application/json");
async Task<string> GetUserTokenAsync() var response = await _apiClient.PostAsync(updateBasketUri, content);
{
var context = _httpContextAccesor.HttpContext;
return await context.GetTokenAsync("access_token");
} }
} }
} }

View File

@ -14,9 +14,12 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.HealthChecks; using Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Polly;
using Polly.Registry;
using StackExchange.Redis; using StackExchange.Redis;
using System; using System;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using WebMVC.Infrastructure; using WebMVC.Infrastructure;
using WebMVC.Infrastructure.Middlewares; using WebMVC.Infrastructure.Middlewares;
using WebMVC.Services; using WebMVC.Services;
@ -35,104 +38,11 @@ namespace Microsoft.eShopOnContainers.WebMVC
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
RegisterAppInsights(services); services.AddAppInsight(Configuration)
.AddHealthChecks(Configuration)
services.AddMvc(); .AddCustomMvc(Configuration)
.AddCustomApplicationServices(Configuration)
services.AddSession(); .AddCustomAuthentication(Configuration);
if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString)
{
services.AddDataProtection(opts =>
{
opts.ApplicationDiscriminator = "eshop.webmvc";
})
.PersistKeysToRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys");
}
services.Configure<AppSettings>(Configuration);
services.AddHealthChecks(checks =>
{
var minutes = 1;
if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed))
{
minutes = minutesParsed;
}
checks.AddUrlCheck(Configuration["CatalogUrlHC"], TimeSpan.FromMinutes(minutes));
checks.AddUrlCheck(Configuration["OrderingUrlHC"], TimeSpan.FromMinutes(minutes));
checks.AddUrlCheck(Configuration["BasketUrlHC"], TimeSpan.Zero); //No cache for this HealthCheck, better just for demos
checks.AddUrlCheck(Configuration["IdentityUrlHC"], TimeSpan.FromMinutes(minutes));
checks.AddUrlCheck(Configuration["MarketingUrlHC"], TimeSpan.FromMinutes(minutes));
});
// Add application services.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<ICatalogService, CatalogService>();
services.AddTransient<IOrderingService, OrderingService>();
services.AddTransient<IBasketService, BasketService>();
services.AddTransient<ICampaignService, CampaignService>();
services.AddTransient<ILocationService, LocationService>();
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
{
services.AddSingleton<IResilientHttpClientFactory, ResilientHttpClientFactory>(sp =>
{
var logger = sp.GetRequiredService<ILogger<ResilientHttpClient>>();
var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
var retryCount = 6;
if (!string.IsNullOrEmpty(Configuration["HttpClientRetryCount"]))
{
retryCount = int.Parse(Configuration["HttpClientRetryCount"]);
}
var exceptionsAllowedBeforeBreaking = 5;
if (!string.IsNullOrEmpty(Configuration["HttpClientExceptionsAllowedBeforeBreaking"]))
{
exceptionsAllowedBeforeBreaking = int.Parse(Configuration["HttpClientExceptionsAllowedBeforeBreaking"]);
}
return new ResilientHttpClientFactory(logger, httpContextAccessor, exceptionsAllowedBeforeBreaking, retryCount);
});
services.AddSingleton<IHttpClient, ResilientHttpClient>(sp => sp.GetService<IResilientHttpClientFactory>().CreateResilientHttpClient());
}
else
{
services.AddSingleton<IHttpClient, StandardHttpClient>();
}
var useLoadTest = Configuration.GetValue<bool>("UseLoadTest");
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
var callBackUrl = Configuration.GetValue<string>("CallBackUrl");
// Add Authentication services
services.AddAuthentication(options => {
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options => {
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = identityUrl.ToString();
options.SignedOutRedirectUri = callBackUrl.ToString();
options.ClientId = useLoadTest ? "mvctest" : "mvc";
options.ClientSecret = "secret";
options.ResponseType = useLoadTest ? "code id_token token" : "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.RequireHttpsMetadata = false;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("orders");
options.Scope.Add("basket");
options.Scope.Add("marketing");
options.Scope.Add("locations");
options.Scope.Add("webshoppingagg");
options.Scope.Add("orders.signalrhub");
});
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -140,8 +50,6 @@ namespace Microsoft.eShopOnContainers.WebMVC
{ {
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
loggerFactory.AddAzureWebAppDiagnostics(); loggerFactory.AddAzureWebAppDiagnostics();
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
@ -173,7 +81,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
{ {
app.UseMiddleware<ByPassAuthMiddleware>(); app.UseMiddleware<ByPassAuthMiddleware>();
} }
app.UseAuthentication(); app.UseAuthentication();
var log = loggerFactory.CreateLogger("identity"); var log = loggerFactory.CreateLogger("identity");
@ -191,23 +99,162 @@ namespace Microsoft.eShopOnContainers.WebMVC
template: "{controller=Error}/{action=Error}"); template: "{controller=Error}/{action=Error}");
}); });
} }
}
private void RegisterAppInsights(IServiceCollection services) static class ServiceCollectionExtensions
{
public static IServiceCollection AddAppInsight(this IServiceCollection services, IConfiguration configuration)
{ {
services.AddApplicationInsightsTelemetry(Configuration); services.AddApplicationInsightsTelemetry(configuration);
var orchestratorType = Configuration.GetValue<string>("OrchestratorType"); var orchestratorType = configuration.GetValue<string>("OrchestratorType");
if (orchestratorType?.ToUpper() == "K8S") if (orchestratorType?.ToUpper() == "K8S")
{ {
// Enable K8s telemetry initializer // Enable K8s telemetry initializer
services.EnableKubernetes(); services.EnableKubernetes();
} }
if (orchestratorType?.ToUpper() == "SF") if (orchestratorType?.ToUpper() == "SF")
{ {
// Enable SF telemetry initializer // Enable SF telemetry initializer
services.AddSingleton<ITelemetryInitializer>((serviceProvider) => services.AddSingleton<ITelemetryInitializer>((serviceProvider) =>
new FabricTelemetryInitializer()); new FabricTelemetryInitializer());
} }
return services;
} }
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration)
{
services.AddHealthChecks(checks =>
{
var minutes = 1;
if (int.TryParse(configuration["HealthCheck:Timeout"], out var minutesParsed))
{
minutes = minutesParsed;
}
checks.AddUrlCheck(configuration["CatalogUrlHC"], TimeSpan.FromMinutes(minutes));
checks.AddUrlCheck(configuration["OrderingUrlHC"], TimeSpan.FromMinutes(minutes));
checks.AddUrlCheck(configuration["BasketUrlHC"], TimeSpan.Zero); //No cache for this HealthCheck, better just for demos
checks.AddUrlCheck(configuration["IdentityUrlHC"], TimeSpan.FromMinutes(minutes));
checks.AddUrlCheck(configuration["MarketingUrlHC"], TimeSpan.FromMinutes(minutes));
});
return services;
}
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration)
{
services.AddMvc();
services.AddSession();
if (configuration.GetValue<string>("IsClusterEnv") == bool.TrueString)
{
services.AddDataProtection(opts =>
{
opts.ApplicationDiscriminator = "eshop.webmvc";
})
.PersistKeysToRedis(ConnectionMultiplexer.Connect(configuration["DPConnectionString"]), "DataProtection-Keys");
}
services.Configure<AppSettings>(configuration);
return services;
}
public static IServiceCollection AddCustomApplicationServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<HttpClientDefaultPolicies>();
var defaultPolicies = services.BuildServiceProvider().GetService<HttpClientDefaultPolicies>();
var registry = services.AddPolicyRegistry();
registry.Add("WaitAndRetry", defaultPolicies.GetWaitAndRetryPolicy());
registry.Add("CircuitBreaker", defaultPolicies.GetCircuitBreakerPolicy());
services.AddHttpClient<IBasketService, BasketService>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandlerFromRegistry("WaitAndRetry")
.AddPolicyHandlerFromRegistry("CircuitBreaker");
services.AddTransient<ICatalogService, CatalogService>();
services.AddTransient<IOrderingService, OrderingService>();
services.AddTransient<ICampaignService, CampaignService>();
services.AddTransient<ILocationService, LocationService>();
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
if (configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
{
services.AddSingleton<IResilientHttpClientFactory, ResilientHttpClientFactory>(sp =>
{
var logger = sp.GetRequiredService<ILogger<ResilientHttpClient>>();
var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
var retryCount = 6;
if (!string.IsNullOrEmpty(configuration["HttpClientRetryCount"]))
{
retryCount = int.Parse(configuration["HttpClientRetryCount"]);
}
var exceptionsAllowedBeforeBreaking = 5;
if (!string.IsNullOrEmpty(configuration["HttpClientExceptionsAllowedBeforeBreaking"]))
{
exceptionsAllowedBeforeBreaking = int.Parse(configuration["HttpClientExceptionsAllowedBeforeBreaking"]);
}
return new ResilientHttpClientFactory(logger, httpContextAccessor, exceptionsAllowedBeforeBreaking, retryCount);
});
services.AddSingleton<IHttpClient, ResilientHttpClient>(sp => sp.GetService<IResilientHttpClientFactory>().CreateResilientHttpClient());
}
else
{
services.AddSingleton<IHttpClient, StandardHttpClient>();
}
return services;
}
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
{
var useLoadTest = configuration.GetValue<bool>("UseLoadTest");
var identityUrl = configuration.GetValue<string>("IdentityUrl");
var callBackUrl = configuration.GetValue<string>("CallBackUrl");
// Add Authentication services
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = identityUrl.ToString();
options.SignedOutRedirectUri = callBackUrl.ToString();
options.ClientId = useLoadTest ? "mvctest" : "mvc";
options.ClientSecret = "secret";
options.ResponseType = useLoadTest ? "code id_token token" : "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.RequireHttpsMetadata = false;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("orders");
options.Scope.Add("basket");
options.Scope.Add("marketing");
options.Scope.Add("locations");
options.Scope.Add("webshoppingagg");
options.Scope.Add("orders.signalrhub");
});
return services;
}
} }
} }

View File

@ -23,6 +23,7 @@
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" /> <PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.0-beta8" /> <PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.0-beta8" />
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.1.1-beta1" /> <PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.1.1-beta1" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="2.1.0-rc1-final" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.1.0-rc1-final" /> <PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.1.0-rc1-final" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-rc1-final" /> <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-rc1-final" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Redis" Version="0.3.3" /> <PackageReference Include="Microsoft.AspNetCore.DataProtection.Redis" Version="0.3.3" />