Merge from Master

This commit is contained in:
Ramón Tomás 2017-06-15 08:54:29 +02:00
commit d6c6582b51
16 changed files with 126 additions and 35 deletions

View File

@ -43,7 +43,7 @@ You can download them and start reviewing these Guides/eBooks here:
| Architecting & Developing | Containers Lifecycle & CI/CD | App patterns with Xamarin.Forms |
| ------------ | ------------| ------------|
| <a href='https://aka.ms/microservicesebook'><img src="img/ebook_arch_dev_microservices_containers_cover.png"> </a> | <a href='https://aka.ms/dockerlifecycleebook'> <img src="img/ebook_containers_lifecycle.png"> </a> | <a href='https://aka.ms/xamarinpatternsebook'> <img src="img/xamarin-enterprise-patterns-ebook-cover-small.png"> </a> |
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (First Edition)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (Preview Edition) </a> </sup> |
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (First Edition)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (First Edition) </a> </sup> |
Send feedback to [dotnet-architecture-ebooks-feedback@service.microsoft.com](dotnet-architecture-ebooks-feedback@service.microsoft.com)
<p>
@ -76,7 +76,7 @@ Finally, those microservices are consumed by multiple client web and mobile apps
## Setting up your development environment for eShopOnContainers
### Visual Studio 2017 and Windows based
This is the more straightforward way to get started:
https://github.com/dotnet/eShopOnContainers/wiki/02.-Setting-eShopOnContainer-solution-up-in-a-Visual-Studio-2017-environment
https://github.com/dotnet-architecture/eShopOnContainers/wiki/02.-Setting-eShopOnContainers-in-a-Visual-Studio-2017-environment
### CLI and Windows based
For those who prefer the CLI on Windows, using dotnet CLI, docker CLI and VS Code for Windows:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1,12 +1,12 @@
#eShopOnContainers
# eShopOnContainers
eShopOnContainers is a reference app whose imagined purpose is to serve the mobile workforce of a fictitious company that sells products. The app allow to manage the catalog, view products, manage the basket and the orders.
<img src="Images/eShopOnContainers_Architecture_Diagram.png" alt="eShopOnContainers" Width="800" />
###Supported platforms: iOS, Android and Windows
### Supported platforms: iOS, Android and Windows
###The app architecture consists of two parts:
### The app architecture consists of two parts:
1. A Xamarin.Forms mobile app for iOS, Android and Windows.
2. Several .NET Web API microservices deployed as Docker containers.
@ -34,7 +34,7 @@ This project exercises the following platforms, frameworks or features:
* Entity Framework
* Identity Server 4
##Three platforms
## Three platforms
The app targets **three** platforms:
* iOS
@ -45,7 +45,7 @@ The app targets **three** platforms:
As of 07/03/2017, eShopOnContainers features **89.2% code share** (7.2% iOS / 16.7% Android / 8.7% Windows).
##Licenses
## Licenses
This project uses some third-party assets with a license that requires attribution:

View File

@ -6,7 +6,6 @@
public const string MockTag = "Mock";
public const string DefaultEndpoint = "http://13.88.8.119";
private string _baseEndpoint;
private static readonly GlobalSetting _instance = new GlobalSetting();
@ -31,6 +30,10 @@
}
}
public string ClientId { get { return "xamarin"; }}
public string ClientSecret { get { return "secret"; }}
public string AuthToken { get; set; }
public string RegisterWebsite { get; set; }
@ -47,6 +50,8 @@
public string UserInfoEndpoint { get; set; }
public string TokenEndpoint { get; set; }
public string LogoutEndpoint { get; set; }
public string IdentityCallback { get; set; }
@ -61,6 +66,7 @@
BasketEndpoint = string.Format("{0}:5103", baseEndpoint);
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint);
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint);
TokenEndpoint = string.Format("{0}:5105/connect/token", baseEndpoint);
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint);
IdentityCallback = string.Format("{0}:5105/xamarincallback", baseEndpoint);
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint);

View File

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace eShopOnContainers.Core.Models.Token
{
public class UserToken
{
[JsonProperty("id_token")]
public string IdToken { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
}

View File

@ -1,8 +1,12 @@
namespace eShopOnContainers.Core.Services.Identity
using eShopOnContainers.Core.Models.Token;
using System.Threading.Tasks;
namespace eShopOnContainers.Core.Services.Identity
{
public interface IIdentityService
{
string CreateAuthorizationRequest();
string CreateLogoutRequest(string token);
Task<UserToken> GetTokenAsync(string code);
}
}

View File

@ -1,11 +1,22 @@
using IdentityModel.Client;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using eShopOnContainers.Core.Services.RequestProvider;
using eShopOnContainers.Core.Models.Token;
namespace eShopOnContainers.Core.Services.Identity
{
public class IdentityService : IIdentityService
{
private readonly IRequestProvider _requestProvider;
public IdentityService(IRequestProvider requestProvider)
{
_requestProvider = requestProvider;
}
public string CreateAuthorizationRequest()
{
// Create URI to authorization endpoint
@ -13,11 +24,10 @@ namespace eShopOnContainers.Core.Services.Identity
// Dictionary with values for the authorize request
var dic = new Dictionary<string, string>();
dic.Add("client_id", "xamarin");
dic.Add("client_secret", "secret");
dic.Add("response_type", "code id_token token");
dic.Add("client_id", GlobalSetting.Instance.ClientId);
dic.Add("client_secret", GlobalSetting.Instance.ClientSecret);
dic.Add("response_type", "code id_token");
dic.Add("scope", "openid profile basket orders locations offline_access");
dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback);
dic.Add("nonce", Guid.NewGuid().ToString("N"));
@ -31,7 +41,7 @@ namespace eShopOnContainers.Core.Services.Identity
public string CreateLogoutRequest(string token)
{
if(string.IsNullOrEmpty(token))
if (string.IsNullOrEmpty(token))
{
return string.Empty;
}
@ -41,5 +51,12 @@ namespace eShopOnContainers.Core.Services.Identity
token,
GlobalSetting.Instance.LogoutCallback);
}
public async Task<UserToken> GetTokenAsync(string code)
{
string data = string.Format("grant_type=authorization_code&code={0}&redirect_uri={1}", code, WebUtility.UrlEncode(GlobalSetting.Instance.IdentityCallback));
var token = await _requestProvider.PostAsync<UserToken>(GlobalSetting.Instance.TokenEndpoint, data, GlobalSetting.Instance.ClientId, GlobalSetting.Instance.ClientSecret);
return token;
}
}
}

View File

@ -8,6 +8,8 @@ namespace eShopOnContainers.Core.Services.RequestProvider
Task<TResult> PostAsync<TResult>(string uri, TResult data, string token = "", string header = "");
Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret);
Task DeleteAsync(string uri, string token = "");
}
}

View File

@ -61,6 +61,28 @@ namespace eShopOnContainers.Core.Services.RequestProvider
return result;
}
public async Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret)
{
HttpClient httpClient = CreateHttpClient(string.Empty);
if (!string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(clientSecret))
{
AddBasicAuthenticationHeader(httpClient, clientId, clientSecret);
}
var content = new StringContent(data);
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
await HandleResponse(response);
string serialized = await response.Content.ReadAsStringAsync();
TResult result = await Task.Run(() =>
JsonConvert.DeserializeObject<TResult>(serialized, _serializerSettings));
return result;
}
public async Task DeleteAsync(string uri, string token = "")
{
HttpClient httpClient = CreateHttpClient(token);
@ -90,6 +112,17 @@ namespace eShopOnContainers.Core.Services.RequestProvider
httpClient.DefaultRequestHeaders.Add(parameter, Guid.NewGuid().ToString());
}
private void AddBasicAuthenticationHeader(HttpClient httpClient, string clientId, string clientSecret)
{
if (httpClient == null)
return;
if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
return;
httpClient.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(clientId, clientSecret);
}
private async Task HandleResponse(HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)

View File

@ -203,16 +203,15 @@ namespace eShopOnContainers.Core.ViewModels
private void Logout()
{
var authIdToken = Settings.AuthIdToken;
var logoutRequest = _identityService.CreateLogoutRequest(authIdToken);
if(!string.IsNullOrEmpty(logoutRequest))
if (!string.IsNullOrEmpty(logoutRequest))
{
// Logout
LoginUrl = logoutRequest;
}
if(Settings.UseMocks)
if (Settings.UseMocks)
{
Settings.AuthAccessToken = string.Empty;
Settings.AuthIdToken = string.Empty;
@ -235,12 +234,14 @@ namespace eShopOnContainers.Core.ViewModels
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
{
var authResponse = new AuthorizeResponse(url);
if (!string.IsNullOrWhiteSpace(authResponse.AccessToken))
if (!string.IsNullOrWhiteSpace(authResponse.Code))
{
if (authResponse.AccessToken != null)
var userToken = await _identityService.GetTokenAsync(authResponse.Code);
string accessToken = userToken.AccessToken;
if (!string.IsNullOrWhiteSpace(accessToken))
{
Settings.AuthAccessToken = authResponse.AccessToken;
Settings.AuthAccessToken = accessToken;
Settings.AuthIdToken = authResponse.IdentityToken;
await NavigationService.NavigateToAsync<MainViewModel>();

View File

@ -170,6 +170,7 @@
<Compile Include="Converters\FirstValidationErrorConverter.cs" />
<Compile Include="Effects\EntryLineColorEffect.cs" />
<Compile Include="Behaviors\LineColorBehavior.cs" />
<Compile Include="Models\Token\UserToken.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@ -263,6 +264,9 @@
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Models\Token\" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.ComponentModel.Annotations">
<HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.6\Profile\Profile44\System.ComponentModel.Annotations.dll</HintPath>

View File

@ -1,7 +1,8 @@
{
"dependencies": {
"Xamarin.Forms": "2.3.4.231",
"xunit": "2.2.0"
"xunit": "2.2.0",
"xunit.runner.console": "2.2.0"
},
"frameworks": {
".NETPortable,Version=v4.5,Profile=Profile111": {}

View File

@ -13,6 +13,7 @@
<AssemblyName>eShopOnContainersiOS</AssemblyName>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<SkipValidatePackageReferences>true</SkipValidatePackageReferences>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
<DebugSymbols>true</DebugSymbols>

View File

@ -74,7 +74,7 @@ namespace Identity.API.Services
if (!string.IsNullOrWhiteSpace(user.Name))
claims.Add(new Claim("name", user.Name));
if (!string.IsNullOrWhiteSpace(user.Name))
if (!string.IsNullOrWhiteSpace(user.LastName))
claims.Add(new Claim("last_name", user.LastName));
if (!string.IsNullOrWhiteSpace(user.CardNumber))