Merge branch 'master' into dev
# Conflicts: # src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj # src/Services/Catalog/Catalog.API/Startup.cs # src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs # src/Web/WebMVC/Startup.cs
This commit is contained in:
commit
756cb3dafa
21
README.md
21
README.md
@ -14,11 +14,15 @@ Sample .NET Core reference application, powered by Microsoft, based on a simplif
|
||||
**Architecture overview**: This reference application is cross-platform either at the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps.
|
||||
The architecture proposes a simplified microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using Http as the current communication protocol.
|
||||
<p>
|
||||
The plan is to add asynchronous communication for data updates propagation across multiple services based on integration events and an event bus plus other features defined at the <a href='https://github.com/dotnet/eShopOnContainers/wiki/01.-Roadmap-and-Milestones-for-future-releases'>roadmap</a>.
|
||||
It also supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus plus other features defined at the <a href='https://github.com/dotnet/eShopOnContainers/wiki/01.-Roadmap-and-Milestones-for-future-releases'>roadmap</a>.
|
||||
<p>
|
||||
<img src="img/eshop_logo.png">
|
||||
<img src="img/eShopOnContainers_Architecture_Diagram.png">
|
||||
<p>
|
||||
The microservices are different in type, meaning different internal architecture patterns approaches depending on it purpose, as shown in the image below.
|
||||
<p>
|
||||
<img src="img/eShopOnContainers_Types_Of_Microservices.png">
|
||||
<p>
|
||||
<p>
|
||||
Additional miroservice styles with other frameworks and No-SQL databases will be added, eventually. This is a great opportunity for pull requests from the community, like a new microservice using Nancy, or even other languages like Node, Go, Python or data containers with MongoDB with Azure DocDB compatibility, PostgreSQL, RavenDB, Event Store, MySql, etc. You name it! :)
|
||||
|
||||
@ -30,15 +34,14 @@ Additional miroservice styles with other frameworks and No-SQL databases will be
|
||||
## Related documentation and guidance
|
||||
While developing this reference application, we are creating a reference Guide/eBook named <b>"Architecting and Developing Containerized and Microservice based .NET Applications"</b> which explains in detail how to develop this kind of architectural style (microservices, Docker containers, Domain-Driven Design for certain microservices) plus other simpler architectural styles, like monolithic apps that can also live as Docker containers.
|
||||
<p>
|
||||
There's also an additional eBook focusing on Containers/Docker lifecycle (DevOps, CI/CD, etc.) with Microsoft Tools, already published.
|
||||
You can start reviewing these Guides/eBooks here:
|
||||
There are also additional eBooks focusing on Containers/Docker lifecycle (DevOps, CI/CD, etc.) with Microsoft Tools, already published plus an additional eBook focusing on Enterprise Apps Patterns with Xamarin.Forms.
|
||||
You can download them and start reviewing these Guides/eBooks here:
|
||||
<p>
|
||||
You can download both eBooks from here:
|
||||
|
||||
| Architecting & Developing | Containers Lifecycle & CI/CD |
|
||||
| ------------ | ------------|
|
||||
| <a href='docs/architecting-and-developing-containerized-and-microservice-based-net-applications-ebook-early-draft.pdf'><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='docs/architecting-and-developing-containerized-and-microservice-based-net-applications-ebook-early-draft.pdf'>**Download** (Early DRAFT, still work in progress)</a> | <a href='https://aka.ms/dockerlifecycleebook'>**Download** - First Edition from late 2016</a> |
|
||||
| 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** (Early DRAFT, still work in progress)</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** (Early DRAFT, still work in progress) </a> </sup> |
|
||||
|
||||
Send feedback to [cesardl@microsoft.com](cesardl@microsoft.com)
|
||||
<p>
|
||||
@ -89,7 +92,7 @@ The app was also partially tested on "Docker for Mac" using a development MacOS
|
||||
|
||||
## Sending feedback and pull requests
|
||||
As mentioned, we'd appreciate to your feedback, improvements and ideas.
|
||||
You can create new issues at the issues section, do pull requests and/or send emails to eshop_feedback@service.microsoft.com
|
||||
You can create new issues at the issues section, do pull requests and/or send emails to **eshop_feedback@service.microsoft.com**
|
||||
|
||||
## Questions
|
||||
[QUESTION] Answer +1 if the solution is working for you (Through VS2017 or CLI environment):
|
||||
|
@ -45,6 +45,7 @@ services:
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:5102
|
||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
|
||||
- identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||
- BasketUrl=http://basket.api:5103
|
||||
- EventBusConnection=rabbitmq
|
||||
ports:
|
||||
- "5102:5102"
|
||||
|
@ -19,6 +19,7 @@ services:
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:5103
|
||||
- ConnectionString=basket.data
|
||||
- identityUrl=http://identity.api:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105.
|
||||
- EventBusConnection=rabbitmq
|
||||
ports:
|
||||
- "5103:5103"
|
||||
|
||||
@ -28,6 +29,7 @@ services:
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:5101
|
||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word
|
||||
- ExternalCatalogBaseUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5101 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105.
|
||||
- EventBusConnection=rabbitmq
|
||||
ports:
|
||||
- "5101:5101"
|
||||
|
||||
@ -48,6 +50,8 @@ services:
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:5102
|
||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
|
||||
- identityUrl=http://identity.api:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105.
|
||||
- BasketUrl=http://basket.api:5103
|
||||
- EventBusConnection=rabbitmq
|
||||
ports:
|
||||
- "5102:5102"
|
||||
|
||||
@ -82,10 +86,13 @@ services:
|
||||
|
||||
webstatus:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ASPNETCORE_ENVIRONMENT=Production
|
||||
- ASPNETCORE_URLS=http://0.0.0.0:5107
|
||||
- CatalogUrl=http://catalog.api:5101/hc
|
||||
- OrderingUrl=http://ordering.api:5102/hc
|
||||
- BasketUrl=http://basket.api:5103/hc
|
||||
- IdentityUrl=http://10.0.75.1:5105/hc
|
||||
- mvc=http://webmvc:5100/hc
|
||||
- spa=http://webspa:5104/hc
|
||||
- IdentityUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
|
||||
ports:
|
||||
- "5107:5107"
|
||||
- "5107:5107"
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 427 KiB After Width: | Height: | Size: 405 KiB |
BIN
img/eShopOnContainers_Architecture_Diagram_old.png
Normal file
BIN
img/eShopOnContainers_Architecture_Diagram_old.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 427 KiB |
BIN
img/eShopOnContainers_Types_Of_Microservices.png
Normal file
BIN
img/eShopOnContainers_Types_Of_Microservices.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
Binary file not shown.
Before Width: | Height: | Size: 30 KiB |
BIN
img/xamarin-enterprise-patterns-ebook-cover-small.png
Normal file
BIN
img/xamarin-enterprise-patterns-ebook-cover-small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
@ -3,7 +3,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:light="clr-namespace:Xamarin.Forms.Themes;assembly=Xamarin.Forms.Theme.Light"
|
||||
xmlns:converters="clr-namespace:eShopOnContainers.Core.Converters;assembly=eShopOnContainers.Core"
|
||||
xmlns:effects="clr-namespace:eShopOnContainers.Core.Effects;assembly=eShopOnContainers.Core"
|
||||
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
|
||||
x:Class="eShopOnContainers.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary MergedWith="light:LightThemeResources">
|
||||
@ -17,6 +17,7 @@
|
||||
<Color x:Key="GreenColor">#00A69C</Color>
|
||||
<Color x:Key="DarkGreenColor">#00857D</Color>
|
||||
<Color x:Key="GrayColor">#e2e2e2</Color>
|
||||
<Color x:Key="ErrorColor">#ff5252</Color>
|
||||
|
||||
<!-- FONTS -->
|
||||
<OnPlatform
|
||||
@ -100,6 +101,7 @@
|
||||
<!-- CONVERTERS -->
|
||||
<converters:CountToBoolConverter x:Key="CountToBoolConverter" />
|
||||
<converters:DatetimeConverter x:Key="DatetimeConverter" />
|
||||
<converters:FirstValidationErrorConverter x:Key="FirstValidationErrorConverter" />
|
||||
<converters:ImageConverter x:Key="ImageConverter" />
|
||||
<converters:ItemTappedEventArgsConverter x:Key="ItemTappedEventArgsConverter" />
|
||||
<converters:InverseCountToBoolConverter x:Key="InverseCountToBoolConverter" />
|
||||
@ -110,6 +112,14 @@
|
||||
<converters:WebNavigatedEventArgsConverter x:Key="WebNavigatedEventArgsConverter" />
|
||||
|
||||
<!-- STYLES -->
|
||||
<Style x:Key="ValidationErrorLabelStyle"
|
||||
TargetType="{x:Type Label}">
|
||||
<Setter Property="TextColor"
|
||||
Value="{StaticResource ErrorColor}" />
|
||||
<Setter Property="FontSize"
|
||||
Value="{StaticResource LittleSize}" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="EntryStyle"
|
||||
TargetType="{x:Type Entry}">
|
||||
<Setter Property="FontFamily"
|
||||
@ -126,9 +136,9 @@
|
||||
Value="Bold" />
|
||||
<Setter Property="Opacity"
|
||||
Value="0.6" />
|
||||
<Setter Property="effects:LineColorEffect.ApplyLineColor"
|
||||
<Setter Property="behaviors:LineColorBehavior.ApplyLineColor"
|
||||
Value="True" />
|
||||
<Setter Property="effects:LineColorEffect.LineColor"
|
||||
<Setter Property="behaviors:LineColorBehavior.LineColor"
|
||||
Value="{StaticResource BlackColor}" />
|
||||
<Style.Triggers>
|
||||
<Trigger TargetType="Entry"
|
||||
@ -157,16 +167,16 @@
|
||||
Value="Transparent" />
|
||||
<Setter Property="Opacity"
|
||||
Value="0.6" />
|
||||
<Setter Property="effects:LineColorEffect.ApplyLineColor"
|
||||
<Setter Property="behaviors:LineColorBehavior.ApplyLineColor"
|
||||
Value="True" />
|
||||
<Setter Property="effects:LineColorEffect.LineColor"
|
||||
<Setter Property="behaviors:LineColorBehavior.LineColor"
|
||||
Value="Gray" />
|
||||
<Style.Triggers>
|
||||
<Trigger TargetType="Entry"
|
||||
Property="IsFocused"
|
||||
Value="True">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="effects:LineColorEffect.LineColor"
|
||||
<Setter Property="behaviors:LineColorBehavior.LineColor"
|
||||
Value="{StaticResource GreenColor}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
|
@ -1,6 +1,6 @@
|
||||
using eShopOnContainers.Core.Helpers;
|
||||
using eShopOnContainers.Services;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
@ -27,13 +27,13 @@ namespace eShopOnContainers
|
||||
private void InitApp()
|
||||
{
|
||||
UseMockServices = Settings.UseMocks;
|
||||
|
||||
ViewModelLocator.Instance.UpdateDependencies(UseMockServices);
|
||||
ViewModelLocator.Initialize();
|
||||
ViewModelLocator.UpdateDependencies(UseMockServices);
|
||||
}
|
||||
|
||||
private Task InitNavigation()
|
||||
{
|
||||
var navigationService = ViewModelLocator.Instance.Resolve<INavigationService>();
|
||||
var navigationService = ViewModelLocator.Resolve<INavigationService>();
|
||||
return navigationService.InitializeAsync();
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,18 @@
|
||||
using System.Linq;
|
||||
using Xamarin.Forms;
|
||||
using eShopOnContainers.Core.Effects;
|
||||
|
||||
namespace eShopOnContainers.Core.Effects
|
||||
namespace eShopOnContainers.Core.Behaviors
|
||||
{
|
||||
public static class LineColorEffect
|
||||
public static class LineColorBehavior
|
||||
{
|
||||
public static readonly BindableProperty ApplyLineColorProperty =
|
||||
BindableProperty.CreateAttached("ApplyLineColor", typeof(bool), typeof(LineColorEffect), false,
|
||||
BindableProperty.CreateAttached("ApplyLineColor", typeof(bool), typeof(LineColorBehavior), false,
|
||||
propertyChanged: OnApplyLineColorChanged);
|
||||
|
||||
public static readonly BindableProperty LineColorProperty =
|
||||
BindableProperty.CreateAttached("LineColor", typeof(Color), typeof(LineColorBehavior), Color.Default);
|
||||
|
||||
public static bool GetApplyLineColor(BindableObject view)
|
||||
{
|
||||
return (bool)view.GetValue(ApplyLineColorProperty);
|
||||
@ -19,6 +23,16 @@ namespace eShopOnContainers.Core.Effects
|
||||
view.SetValue(ApplyLineColorProperty, value);
|
||||
}
|
||||
|
||||
public static Color GetLineColor(BindableObject view)
|
||||
{
|
||||
return (Color)view.GetValue(LineColorProperty);
|
||||
}
|
||||
|
||||
public static void SetLineColor(BindableObject view, Color value)
|
||||
{
|
||||
view.SetValue(LineColorProperty, value);
|
||||
}
|
||||
|
||||
private static void OnApplyLineColorChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var view = bindable as View;
|
||||
@ -28,9 +42,9 @@ namespace eShopOnContainers.Core.Effects
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasShadow = (bool)newValue;
|
||||
bool hasLine = (bool)newValue;
|
||||
|
||||
if (hasShadow)
|
||||
if (hasLine)
|
||||
{
|
||||
view.Effects.Add(new EntryLineColorEffect());
|
||||
}
|
||||
@ -43,25 +57,5 @@ namespace eShopOnContainers.Core.Effects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly BindableProperty LineColorProperty =
|
||||
BindableProperty.CreateAttached("LineColor", typeof(Color), typeof(LineColorEffect), Color.Default);
|
||||
|
||||
public static Color GetLineColor(BindableObject view)
|
||||
{
|
||||
return (Color)view.GetValue(LineColorProperty);
|
||||
}
|
||||
|
||||
public static void SetLineColor(BindableObject view, Color value)
|
||||
{
|
||||
view.SetValue(LineColorProperty, value);
|
||||
}
|
||||
|
||||
class EntryLineColorEffect : RoutingEffect
|
||||
{
|
||||
public EntryLineColorEffect() : base("eShopOnContainers.EntryLineColorEffect")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace eShopOnContainers.Core.Converters
|
||||
{
|
||||
public class FirstValidationErrorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
ICollection<string> errors = value as ICollection<string>;
|
||||
return errors != null && errors.Count > 0 ? errors.ElementAt(0) : null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace eShopOnContainers.Core.Effects
|
||||
{
|
||||
public class EntryLineColorEffect : RoutingEffect
|
||||
{
|
||||
public EntryLineColorEffect() : base("eShopOnContainers.EntryLineColorEffect")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using eShopOnContainers.Core.Animations.Base;
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace eShopOnContainers.Core.Extensions
|
||||
{
|
||||
public static class AnimationExtension
|
||||
{
|
||||
public static async void Animate(this VisualElement visualElement, AnimationBase animation, Action onFinishedCallback = null)
|
||||
{
|
||||
animation.Target = visualElement;
|
||||
|
||||
await animation.Begin();
|
||||
|
||||
onFinishedCallback?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using eShopOnContainers.Core.Models.Basket;
|
||||
using eShopOnContainers.Core.Models.Catalog;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@ -21,7 +21,7 @@ namespace eShopOnContainers.Core.Helpers
|
||||
|
||||
try
|
||||
{
|
||||
if (!ViewModelLocator.Instance.UseMockService
|
||||
if (!ViewModelLocator.UseMockService
|
||||
&& Settings.UrlBase != GlobalSetting.DefaultEndpoint)
|
||||
{
|
||||
foreach (var catalogItem in catalogItems)
|
||||
@ -54,7 +54,7 @@ namespace eShopOnContainers.Core.Helpers
|
||||
|
||||
try
|
||||
{
|
||||
if (!ViewModelLocator.Instance.UseMockService
|
||||
if (!ViewModelLocator.UseMockService
|
||||
&& Settings.UrlBase != GlobalSetting.DefaultEndpoint)
|
||||
{
|
||||
foreach (var basketItem in basketItems)
|
||||
|
@ -1,4 +1,4 @@
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using Plugin.Settings;
|
||||
using Plugin.Settings.Abstractions;
|
||||
|
||||
@ -27,7 +27,7 @@ namespace eShopOnContainers.Core.Helpers
|
||||
private const string IdUrlBase = "url_base";
|
||||
private static readonly string AccessTokenDefault = string.Empty;
|
||||
private static readonly string IdTokenDefault = string.Empty;
|
||||
private static readonly bool UseMocksDefault = ViewModelLocator.Instance.UseMockService;
|
||||
private static readonly bool UseMocksDefault = true;
|
||||
private static readonly string UrlBaseDefault = GlobalSetting.Instance.BaseEndpoint;
|
||||
|
||||
#endregion
|
||||
|
@ -1,5 +1,4 @@
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using System;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace eShopOnContainers.Services
|
||||
@ -12,12 +11,6 @@ namespace eShopOnContainers.Services
|
||||
|
||||
Task NavigateToAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase;
|
||||
|
||||
Task NavigateToAsync(Type viewModelType);
|
||||
|
||||
Task NavigateToAsync(Type viewModelType, object parameter);
|
||||
|
||||
Task NavigateBackAsync();
|
||||
|
||||
Task RemoveLastFromBackStackAsync();
|
||||
|
||||
Task RemoveBackStackAsync();
|
||||
|
@ -1,9 +1,10 @@
|
||||
using eShopOnContainers.Core.Helpers;
|
||||
using eShopOnContainers.Core.ViewModels;
|
||||
using eShopOnContainers.Core.Views;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms;
|
||||
|
||||
@ -11,23 +12,6 @@ namespace eShopOnContainers.Services
|
||||
{
|
||||
public class NavigationService : INavigationService
|
||||
{
|
||||
protected readonly Dictionary<Type, Type> _mappings;
|
||||
|
||||
protected Application CurrentApplication
|
||||
{
|
||||
get
|
||||
{
|
||||
return Application.Current;
|
||||
}
|
||||
}
|
||||
|
||||
public NavigationService()
|
||||
{
|
||||
_mappings = new Dictionary<Type, Type>();
|
||||
|
||||
CreatePageViewModelMappings();
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
if(string.IsNullOrEmpty(Settings.AuthAccessToken))
|
||||
@ -46,32 +30,9 @@ namespace eShopOnContainers.Services
|
||||
return InternalNavigateToAsync(typeof(TViewModel), parameter);
|
||||
}
|
||||
|
||||
public Task NavigateToAsync(Type viewModelType)
|
||||
public Task RemoveLastFromBackStackAsync()
|
||||
{
|
||||
return InternalNavigateToAsync(viewModelType, null);
|
||||
}
|
||||
|
||||
public Task NavigateToAsync(Type viewModelType, object parameter)
|
||||
{
|
||||
return InternalNavigateToAsync(viewModelType, parameter);
|
||||
}
|
||||
|
||||
public async Task NavigateBackAsync()
|
||||
{
|
||||
if (CurrentApplication.MainPage is CatalogView)
|
||||
{
|
||||
var mainPage = CurrentApplication.MainPage as CatalogView;
|
||||
await mainPage.Navigation.PopAsync();
|
||||
}
|
||||
else if (CurrentApplication.MainPage != null)
|
||||
{
|
||||
await CurrentApplication.MainPage.Navigation.PopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Task RemoveLastFromBackStackAsync()
|
||||
{
|
||||
var mainPage = CurrentApplication.MainPage as CustomNavigationView;
|
||||
var mainPage = Application.Current.MainPage as CustomNavigationView;
|
||||
|
||||
if (mainPage != null)
|
||||
{
|
||||
@ -82,9 +43,9 @@ namespace eShopOnContainers.Services
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public virtual Task RemoveBackStackAsync()
|
||||
public Task RemoveBackStackAsync()
|
||||
{
|
||||
var mainPage = CurrentApplication.MainPage as CustomNavigationView;
|
||||
var mainPage = Application.Current.MainPage as CustomNavigationView;
|
||||
|
||||
if (mainPage != null)
|
||||
{
|
||||
@ -98,67 +59,49 @@ namespace eShopOnContainers.Services
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
protected virtual async Task InternalNavigateToAsync(Type viewModelType, object parameter)
|
||||
private async Task InternalNavigateToAsync(Type viewModelType, object parameter)
|
||||
{
|
||||
Page page = CreateAndBindPage(viewModelType, parameter);
|
||||
Page page = CreatePage(viewModelType, parameter);
|
||||
|
||||
if (page is LoginView)
|
||||
{
|
||||
CurrentApplication.MainPage = new CustomNavigationView(page);
|
||||
Application.Current.MainPage = new CustomNavigationView(page);
|
||||
}
|
||||
else
|
||||
{
|
||||
var navigationPage = CurrentApplication.MainPage as CustomNavigationView;
|
||||
|
||||
{
|
||||
var navigationPage = Application.Current.MainPage as CustomNavigationView;
|
||||
if (navigationPage != null)
|
||||
{
|
||||
await navigationPage.PushAsync(page);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentApplication.MainPage = new CustomNavigationView(page);
|
||||
Application.Current.MainPage = new CustomNavigationView(page);
|
||||
}
|
||||
}
|
||||
|
||||
await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);
|
||||
}
|
||||
|
||||
protected Type GetPageTypeForViewModel(Type viewModelType)
|
||||
{
|
||||
if (!_mappings.ContainsKey(viewModelType))
|
||||
{
|
||||
throw new KeyNotFoundException($"No map for ${viewModelType} was found on navigation mappings");
|
||||
}
|
||||
private Type GetPageTypeForViewModel(Type viewModelType)
|
||||
{
|
||||
var viewName = viewModelType.FullName.Replace("Model", string.Empty);
|
||||
var viewModelAssemblyName = viewModelType.GetTypeInfo().Assembly.FullName;
|
||||
var viewAssemblyName = string.Format(CultureInfo.InvariantCulture, "{0}, {1}", viewName, viewModelAssemblyName);
|
||||
var viewType = Type.GetType(viewAssemblyName);
|
||||
return viewType;
|
||||
}
|
||||
|
||||
return _mappings[viewModelType];
|
||||
}
|
||||
private Page CreatePage(Type viewModelType, object parameter)
|
||||
{
|
||||
Type pageType = GetPageTypeForViewModel(viewModelType);
|
||||
if (pageType == null)
|
||||
{
|
||||
throw new Exception($"Cannot locate page type for {viewModelType}");
|
||||
}
|
||||
|
||||
protected Page CreateAndBindPage(Type viewModelType, object parameter)
|
||||
{
|
||||
Type pageType = GetPageTypeForViewModel(viewModelType);
|
||||
|
||||
if (pageType == null)
|
||||
{
|
||||
throw new Exception($"Mapping type for {viewModelType} is not a page");
|
||||
}
|
||||
|
||||
Page page = Activator.CreateInstance(pageType) as Page;
|
||||
ViewModelBase viewModel = ViewModelLocator.Instance.Resolve(viewModelType) as ViewModelBase;
|
||||
page.BindingContext = viewModel;
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
private void CreatePageViewModelMappings()
|
||||
{
|
||||
_mappings.Add(typeof(BasketViewModel), typeof(BasketView));
|
||||
_mappings.Add(typeof(CatalogViewModel), typeof(CatalogView));
|
||||
_mappings.Add(typeof(CheckoutViewModel), typeof(CheckoutView));
|
||||
_mappings.Add(typeof(LoginViewModel), typeof(LoginView));
|
||||
_mappings.Add(typeof(MainViewModel), typeof(MainView));
|
||||
_mappings.Add(typeof(OrderDetailViewModel), typeof(OrderDetailView));
|
||||
_mappings.Add(typeof(ProfileViewModel), typeof(ProfileView));
|
||||
_mappings.Add(typeof(SettingsViewModel), typeof(SettingsView));
|
||||
}
|
||||
Page page = Activator.CreateInstance(pageType) as Page;
|
||||
return page;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace eShopOnContainers.Core.Validations
|
||||
@ -8,13 +7,24 @@ namespace eShopOnContainers.Core.Validations
|
||||
public class ValidatableObject<T> : ExtendedBindableObject, IValidity
|
||||
{
|
||||
private readonly List<IValidationRule<T>> _validations;
|
||||
private readonly ObservableCollection<string> _errors;
|
||||
private List<string> _errors;
|
||||
private T _value;
|
||||
private bool _isValid;
|
||||
|
||||
public List<IValidationRule<T>> Validations => _validations;
|
||||
|
||||
public ObservableCollection<string> Errors => _errors;
|
||||
public List<string> Errors
|
||||
{
|
||||
get
|
||||
{
|
||||
return _errors;
|
||||
}
|
||||
set
|
||||
{
|
||||
_errors = value;
|
||||
RaisePropertyChanged(() => Errors);
|
||||
}
|
||||
}
|
||||
|
||||
public T Value
|
||||
{
|
||||
@ -22,7 +32,6 @@ namespace eShopOnContainers.Core.Validations
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_value = value;
|
||||
@ -36,11 +45,9 @@ namespace eShopOnContainers.Core.Validations
|
||||
{
|
||||
return _isValid;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_isValid = value;
|
||||
_errors.Clear();
|
||||
RaisePropertyChanged(() => IsValid);
|
||||
}
|
||||
}
|
||||
@ -48,7 +55,7 @@ namespace eShopOnContainers.Core.Validations
|
||||
public ValidatableObject()
|
||||
{
|
||||
_isValid = true;
|
||||
_errors = new ObservableCollection<string>();
|
||||
_errors = new List<string>();
|
||||
_validations = new List<IValidationRule<T>>();
|
||||
}
|
||||
|
||||
@ -59,11 +66,7 @@ namespace eShopOnContainers.Core.Validations
|
||||
IEnumerable<string> errors = _validations.Where(v => !v.Check(Value))
|
||||
.Select(v => v.ValidationMessage);
|
||||
|
||||
foreach (var error in errors)
|
||||
{
|
||||
Errors.Add(error);
|
||||
}
|
||||
|
||||
Errors = errors.ToList();
|
||||
IsValid = !Errors.Any();
|
||||
|
||||
return this.IsValid;
|
||||
|
@ -3,7 +3,7 @@ using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace eShopOnContainers.ViewModels.Base
|
||||
namespace eShopOnContainers.Core.ViewModels.Base
|
||||
{
|
||||
public abstract class ExtendedBindableObject : BindableObject
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace eShopOnContainers.Core.ViewModels.Base
|
||||
{
|
||||
public class MessengerKeys
|
||||
public class MessageKeys
|
||||
{
|
||||
// Add product to basket
|
||||
public const string AddProduct = "AddProduct";
|
@ -1,9 +1,8 @@
|
||||
using eShopOnContainers.Core;
|
||||
using eShopOnContainers.Core.Helpers;
|
||||
using eShopOnContainers.Core.Helpers;
|
||||
using eShopOnContainers.Services;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace eShopOnContainers.ViewModels.Base
|
||||
namespace eShopOnContainers.Core.ViewModels.Base
|
||||
{
|
||||
public abstract class ViewModelBase : ExtendedBindableObject
|
||||
{
|
||||
@ -28,8 +27,8 @@ namespace eShopOnContainers.ViewModels.Base
|
||||
|
||||
public ViewModelBase()
|
||||
{
|
||||
DialogService = ViewModelLocator.Instance.Resolve<IDialogService>();
|
||||
NavigationService = ViewModelLocator.Instance.Resolve<INavigationService>();
|
||||
DialogService = ViewModelLocator.Resolve<IDialogService>();
|
||||
NavigationService = ViewModelLocator.Resolve<INavigationService>();
|
||||
GlobalSetting.Instance.BaseEndpoint = Settings.UrlBase;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
using Microsoft.Practices.Unity;
|
||||
using eShopOnContainers.Core.ViewModels;
|
||||
using eShopOnContainers.Services;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using eShopOnContainers.Core.Services.Catalog;
|
||||
using eShopOnContainers.Core.Services.OpenUrl;
|
||||
using eShopOnContainers.Core.Services.RequestProvider;
|
||||
@ -9,100 +10,101 @@ using eShopOnContainers.Core.Services.Basket;
|
||||
using eShopOnContainers.Core.Services.Identity;
|
||||
using eShopOnContainers.Core.Services.Order;
|
||||
using eShopOnContainers.Core.Services.User;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace eShopOnContainers.ViewModels.Base
|
||||
namespace eShopOnContainers.Core.ViewModels.Base
|
||||
{
|
||||
public class ViewModelLocator
|
||||
public static class ViewModelLocator
|
||||
{
|
||||
private bool _useMockService;
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
private static readonly IUnityContainer _unityContainer = new UnityContainer();
|
||||
|
||||
private static readonly ViewModelLocator _instance = new ViewModelLocator();
|
||||
public static readonly BindableProperty AutoWireViewModelProperty =
|
||||
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
|
||||
|
||||
public static ViewModelLocator Instance
|
||||
{
|
||||
get { return _instance; }
|
||||
}
|
||||
public static bool GetAutoWireViewModel(BindableObject bindable)
|
||||
{
|
||||
return (bool)bindable.GetValue(ViewModelLocator.AutoWireViewModelProperty);
|
||||
}
|
||||
|
||||
public bool UseMockService
|
||||
{
|
||||
get { return _useMockService; }
|
||||
set { _useMockService = value; ; }
|
||||
}
|
||||
public static void SetAutoWireViewModel(BindableObject bindable, bool value)
|
||||
{
|
||||
bindable.SetValue(ViewModelLocator.AutoWireViewModelProperty, value);
|
||||
}
|
||||
|
||||
protected ViewModelLocator()
|
||||
{
|
||||
_unityContainer = new UnityContainer();
|
||||
public static bool UseMockService { get; set; }
|
||||
|
||||
// Services
|
||||
_unityContainer.RegisterType<IDialogService, DialogService>();
|
||||
RegisterSingleton<INavigationService, NavigationService>();
|
||||
_unityContainer.RegisterType<IOpenUrlService, OpenUrlService>();
|
||||
_unityContainer.RegisterType<IRequestProvider, RequestProvider>();
|
||||
_unityContainer.RegisterType<IIdentityService, IdentityService>();
|
||||
public static void Initialize()
|
||||
{
|
||||
// Services
|
||||
_unityContainer.RegisterType<IDialogService, DialogService>();
|
||||
_unityContainer.RegisterType<INavigationService, NavigationService>(new ContainerControlledLifetimeManager());
|
||||
_unityContainer.RegisterType<IOpenUrlService, OpenUrlService>();
|
||||
_unityContainer.RegisterType<IRequestProvider, RequestProvider>();
|
||||
_unityContainer.RegisterType<IIdentityService, IdentityService>();
|
||||
_unityContainer.RegisterType<ICatalogService, CatalogMockService>();
|
||||
_unityContainer.RegisterType<IBasketService, BasketMockService>();
|
||||
_unityContainer.RegisterType<IUserService, UserMockService>();
|
||||
|
||||
_unityContainer.RegisterType<ICatalogService, CatalogMockService>();
|
||||
_unityContainer.RegisterType<IBasketService, BasketMockService>();
|
||||
_unityContainer.RegisterType<IUserService, UserMockService>();
|
||||
// View models
|
||||
_unityContainer.RegisterType<BasketViewModel>();
|
||||
_unityContainer.RegisterType<CatalogViewModel>();
|
||||
_unityContainer.RegisterType<CheckoutViewModel>();
|
||||
_unityContainer.RegisterType<LoginViewModel>();
|
||||
_unityContainer.RegisterType<MainViewModel>();
|
||||
_unityContainer.RegisterType<OrderDetailViewModel>();
|
||||
_unityContainer.RegisterType<ProfileViewModel>();
|
||||
_unityContainer.RegisterType<SettingsViewModel>();
|
||||
}
|
||||
|
||||
// View models
|
||||
_unityContainer.RegisterType<BasketViewModel>();
|
||||
_unityContainer.RegisterType<CatalogViewModel>();
|
||||
_unityContainer.RegisterType<CheckoutViewModel>();
|
||||
_unityContainer.RegisterType<LoginViewModel>();
|
||||
_unityContainer.RegisterType<MainViewModel>();
|
||||
_unityContainer.RegisterType<OrderDetailViewModel>();
|
||||
_unityContainer.RegisterType<ProfileViewModel>();
|
||||
_unityContainer.RegisterType<SettingsViewModel>();
|
||||
}
|
||||
public static void UpdateDependencies(bool useMockServices)
|
||||
{
|
||||
// Change injected dependencies
|
||||
if (useMockServices)
|
||||
{
|
||||
_unityContainer.RegisterInstance<ICatalogService>(new CatalogMockService());
|
||||
_unityContainer.RegisterInstance<IBasketService>(new BasketMockService());
|
||||
_unityContainer.RegisterInstance<IOrderService>(new OrderMockService());
|
||||
_unityContainer.RegisterInstance<IUserService>(new UserMockService());
|
||||
|
||||
public void UpdateDependencies(bool useMockServices)
|
||||
{
|
||||
// Change injected dependencies
|
||||
if (useMockServices)
|
||||
{
|
||||
_unityContainer.RegisterInstance<ICatalogService>(new CatalogMockService());
|
||||
_unityContainer.RegisterInstance<IBasketService>(new BasketMockService());
|
||||
_unityContainer.RegisterInstance<IOrderService>(new OrderMockService());
|
||||
_unityContainer.RegisterInstance<IUserService>(new UserMockService());
|
||||
UseMockService = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var requestProvider = Resolve<IRequestProvider>();
|
||||
_unityContainer.RegisterInstance<ICatalogService>(new CatalogService(requestProvider));
|
||||
_unityContainer.RegisterInstance<IBasketService>(new BasketService(requestProvider));
|
||||
_unityContainer.RegisterInstance<IOrderService>(new OrderService(requestProvider));
|
||||
_unityContainer.RegisterInstance<IUserService>(new UserService(requestProvider));
|
||||
|
||||
UseMockService = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var requestProvider = Resolve<IRequestProvider>();
|
||||
_unityContainer.RegisterInstance<ICatalogService>(new CatalogService(requestProvider));
|
||||
_unityContainer.RegisterInstance<IBasketService>(new BasketService(requestProvider));
|
||||
_unityContainer.RegisterInstance<IOrderService>(new OrderService(requestProvider));
|
||||
_unityContainer.RegisterInstance<IUserService>(new UserService(requestProvider));
|
||||
UseMockService = false;
|
||||
}
|
||||
}
|
||||
|
||||
UseMockService = false;
|
||||
}
|
||||
}
|
||||
|
||||
public T Resolve<T>()
|
||||
{
|
||||
return _unityContainer.Resolve<T>();
|
||||
}
|
||||
public static T Resolve<T>()
|
||||
{
|
||||
return _unityContainer.Resolve<T>();
|
||||
}
|
||||
|
||||
public object Resolve(Type type)
|
||||
{
|
||||
return _unityContainer.Resolve(type);
|
||||
}
|
||||
private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var view = bindable as Element;
|
||||
if (view == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public void Register<T>(T instance)
|
||||
{
|
||||
_unityContainer.RegisterInstance<T>(instance);
|
||||
}
|
||||
var viewType = view.GetType();
|
||||
var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");
|
||||
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
|
||||
var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}Model, {1}", viewName, viewAssemblyName);
|
||||
|
||||
public void Register<TInterface, T>() where T : TInterface
|
||||
{
|
||||
_unityContainer.RegisterType<TInterface, T>();
|
||||
}
|
||||
|
||||
public void RegisterSingleton<TInterface, T>() where T : TInterface
|
||||
{
|
||||
_unityContainer.RegisterType<TInterface, T>(new ContainerControlledLifetimeManager());
|
||||
}
|
||||
}
|
||||
var viewModelType = Type.GetType(viewModelName);
|
||||
if (viewModelType == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var viewModel = _unityContainer.Resolve(viewModelType);
|
||||
view.BindingContext = viewModel;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@ using eShopOnContainers.Core.Models.Catalog;
|
||||
using eShopOnContainers.Core.Services.Basket;
|
||||
using eShopOnContainers.Core.Services.User;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -61,7 +59,7 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand AddCommand => new Command<BasketItem>(AddItem);
|
||||
public ICommand AddCommand => new Command<BasketItem>(async (item) => await AddItemAsync(item));
|
||||
|
||||
public ICommand CheckoutCommand => new Command(async () => await CheckoutAsync());
|
||||
|
||||
@ -84,22 +82,22 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
foreach (var basketItem in basket.Items)
|
||||
{
|
||||
BadgeCount += basketItem.Quantity;
|
||||
AddBasketItem(basketItem);
|
||||
await AddBasketItemAsync(basketItem);
|
||||
}
|
||||
}
|
||||
|
||||
MessagingCenter.Unsubscribe<CatalogViewModel, CatalogItem>(this, MessengerKeys.AddProduct);
|
||||
MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(this, MessengerKeys.AddProduct, (sender, arg) =>
|
||||
MessagingCenter.Unsubscribe<CatalogViewModel, CatalogItem>(this, MessageKeys.AddProduct);
|
||||
MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(this, MessageKeys.AddProduct, async (sender, arg) =>
|
||||
{
|
||||
BadgeCount++;
|
||||
|
||||
AddCatalogItem(arg);
|
||||
await AddCatalogItemAsync(arg);
|
||||
});
|
||||
|
||||
await base.InitializeAsync(navigationData);
|
||||
}
|
||||
|
||||
private void AddCatalogItem(CatalogItem item)
|
||||
private async Task AddCatalogItemAsync(CatalogItem item)
|
||||
{
|
||||
BasketItems.Add(new BasketItem
|
||||
{
|
||||
@ -110,26 +108,26 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
Quantity = 1
|
||||
});
|
||||
|
||||
ReCalculateTotal();
|
||||
await ReCalculateTotalAsync();
|
||||
}
|
||||
|
||||
private void AddItem(BasketItem item)
|
||||
private async Task AddItemAsync(BasketItem item)
|
||||
{
|
||||
BadgeCount++;
|
||||
|
||||
AddBasketItem(item);
|
||||
await AddBasketItemAsync(item);
|
||||
|
||||
RaisePropertyChanged(() => BasketItems);
|
||||
}
|
||||
|
||||
private void AddBasketItem(BasketItem item)
|
||||
private async Task AddBasketItemAsync(BasketItem item)
|
||||
{
|
||||
BasketItems.Add(item);
|
||||
|
||||
ReCalculateTotal();
|
||||
await ReCalculateTotalAsync();
|
||||
}
|
||||
|
||||
private async void ReCalculateTotal()
|
||||
private async Task ReCalculateTotalAsync()
|
||||
{
|
||||
Total = 0;
|
||||
|
||||
|
@ -1,14 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System.Collections.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using eShopOnContainers.Core.Models.Catalog;
|
||||
using eShopOnContainers.Core.Services.Catalog;
|
||||
using System.Windows.Input;
|
||||
using System.Linq;
|
||||
using eShopOnContainers.Core.Services.Basket;
|
||||
using eShopOnContainers.Core.Helpers;
|
||||
using eShopOnContainers.Core.Services.User;
|
||||
|
||||
namespace eShopOnContainers.Core.ViewModels
|
||||
@ -20,19 +17,11 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
private CatalogBrand _brand;
|
||||
private ObservableCollection<CatalogType> _types;
|
||||
private CatalogType _type;
|
||||
|
||||
private IBasketService _basketService;
|
||||
private ICatalogService _productsService;
|
||||
private IUserService _userService;
|
||||
|
||||
public CatalogViewModel(
|
||||
IBasketService basketService,
|
||||
ICatalogService productsService,
|
||||
IUserService userService)
|
||||
public CatalogViewModel(ICatalogService productsService)
|
||||
{
|
||||
_basketService = basketService;
|
||||
_productsService = productsService;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public ObservableCollection<CatalogItem> Products
|
||||
@ -91,9 +80,9 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
|
||||
public ICommand AddCatalogItemCommand => new Command<CatalogItem>(AddCatalogItem);
|
||||
|
||||
public ICommand FilterCommand => new Command(Filter);
|
||||
public ICommand FilterCommand => new Command(async () => await FilterAsync());
|
||||
|
||||
public ICommand ClearFilterCommand => new Command(ClearFilter);
|
||||
public ICommand ClearFilterCommand => new Command(async () => await ClearFilterAsync());
|
||||
|
||||
public override async Task InitializeAsync(object navigationData)
|
||||
{
|
||||
@ -110,10 +99,10 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
private void AddCatalogItem(CatalogItem catalogItem)
|
||||
{
|
||||
// Add new item to Basket
|
||||
MessagingCenter.Send(this, MessengerKeys.AddProduct, catalogItem);
|
||||
MessagingCenter.Send(this, MessageKeys.AddProduct, catalogItem);
|
||||
}
|
||||
|
||||
private async void Filter()
|
||||
private async Task FilterAsync()
|
||||
{
|
||||
if (Brand == null && Type == null)
|
||||
{
|
||||
@ -123,13 +112,13 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
IsBusy = true;
|
||||
|
||||
// Filter catalog products
|
||||
MessagingCenter.Send(this, MessengerKeys.Filter);
|
||||
MessagingCenter.Send(this, MessageKeys.Filter);
|
||||
Products = await _productsService.FilterAsync(Brand.Id, Type.Id);
|
||||
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
private async void ClearFilter()
|
||||
private async Task ClearFilterAsync()
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
using eShopOnContainers.Core.Models.Navigation;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Forms;
|
||||
using System.Threading.Tasks;
|
||||
@ -139,7 +139,7 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
await _basketService.ClearBasketAsync(_shippingAddress.Id.ToString(), authToken);
|
||||
|
||||
// Reset Basket badge
|
||||
var basketViewModel = ViewModelLocator.Instance.Resolve<BasketViewModel>();
|
||||
var basketViewModel = ViewModelLocator.Resolve<BasketViewModel>();
|
||||
basketViewModel.BadgeCount = 0;
|
||||
|
||||
// Navigate to Orders
|
||||
|
@ -2,9 +2,8 @@
|
||||
using eShopOnContainers.Core.Models.User;
|
||||
using eShopOnContainers.Core.Services.Identity;
|
||||
using eShopOnContainers.Core.Services.OpenUrl;
|
||||
using eShopOnContainers.Core.Services.User;
|
||||
using eShopOnContainers.Core.Validations;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using IdentityModel.Client;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
@ -25,16 +24,13 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
|
||||
private IOpenUrlService _openUrlService;
|
||||
private IIdentityService _identityService;
|
||||
private IUserService _userService;
|
||||
|
||||
public LoginViewModel(
|
||||
IOpenUrlService openUrlService,
|
||||
IIdentityService identityService,
|
||||
IUserService userService)
|
||||
IIdentityService identityService)
|
||||
{
|
||||
_openUrlService = openUrlService;
|
||||
_identityService = identityService;
|
||||
_userService = userService;
|
||||
|
||||
_userName = new ValidatableObject<string>();
|
||||
_password = new ValidatableObject<string>();
|
||||
@ -131,6 +127,10 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
|
||||
public ICommand SettingsCommand => new Command(async () => await SettingsAsync());
|
||||
|
||||
public ICommand ValidateUserNameCommand => new Command(() => ValidateUserName());
|
||||
|
||||
public ICommand ValidatePasswordCommand => new Command(() => ValidatePassword());
|
||||
|
||||
public override Task InitializeAsync(object navigationData)
|
||||
{
|
||||
if(navigationData is LogoutParameter)
|
||||
@ -255,16 +255,26 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
|
||||
private bool Validate()
|
||||
{
|
||||
bool isValidUser = _userName.Validate();
|
||||
bool isValidPassword = _password.Validate();
|
||||
bool isValidUser = ValidateUserName();
|
||||
bool isValidPassword = ValidatePassword();
|
||||
|
||||
return isValidUser && isValidPassword;
|
||||
}
|
||||
|
||||
private bool ValidateUserName()
|
||||
{
|
||||
return _userName.Validate();
|
||||
}
|
||||
|
||||
private bool ValidatePassword()
|
||||
{
|
||||
return _password.Validate();
|
||||
}
|
||||
|
||||
private void AddValidations()
|
||||
{
|
||||
_userName.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "Username should not be empty" });
|
||||
_password.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "Password should not be empty" });
|
||||
_userName.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "A username is required." });
|
||||
_password.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "A password is required." });
|
||||
}
|
||||
|
||||
public void InvalidateMock()
|
||||
|
@ -1,8 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using eShopOnContainers.Core.Models.Navigation;
|
||||
using Xamarin.Forms;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace eShopOnContainers.Core.ViewModels
|
||||
@ -19,7 +18,7 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
{
|
||||
// Change selected application tab
|
||||
var tabIndex = ((TabParameter)navigationData).TabIndex;
|
||||
MessagingCenter.Send(this, MessengerKeys.ChangeTab, tabIndex);
|
||||
MessagingCenter.Send(this, MessageKeys.ChangeTab, tabIndex);
|
||||
}
|
||||
|
||||
return base.InitializeAsync(navigationData);
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using eShopOnContainers.Core.Models.Orders;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using eShopOnContainers.Core.Services.Catalog;
|
||||
using eShopOnContainers.Core.Services.Basket;
|
||||
using eShopOnContainers.Core.Services.Order;
|
||||
@ -13,17 +13,10 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
{
|
||||
private Order _order;
|
||||
|
||||
private IBasketService _orderService;
|
||||
private ICatalogService _catalogService;
|
||||
private IOrderService _ordersService;
|
||||
|
||||
public OrderDetailViewModel(
|
||||
IBasketService orderService,
|
||||
ICatalogService catalogService,
|
||||
IOrderService ordersService)
|
||||
public OrderDetailViewModel(IOrderService ordersService)
|
||||
{
|
||||
_orderService = orderService;
|
||||
_catalogService = catalogService;
|
||||
_ordersService = ordersService;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ using eShopOnContainers.Core.Helpers;
|
||||
using eShopOnContainers.Core.Models.Orders;
|
||||
using eShopOnContainers.Core.Models.User;
|
||||
using eShopOnContainers.Core.Services.Order;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
@ -1,4 +1,4 @@
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Forms;
|
||||
using System.Threading.Tasks;
|
||||
@ -71,7 +71,7 @@ namespace eShopOnContainers.Core.ViewModels
|
||||
|
||||
private void MockServices()
|
||||
{
|
||||
ViewModelLocator.Instance.UpdateDependencies(!UseAzureServices);
|
||||
ViewModelLocator.UpdateDependencies(!UseAzureServices);
|
||||
UpdateInfo();
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="eShopOnContainers.Core.Views.BasketView"
|
||||
x:Class="eShopOnContainers.Core.Views.BasketView"
|
||||
xmlns:viewModelBase="clr-namespace:eShopOnContainers.Core.ViewModels.Base;assembly=eShopOnContainers.Core"
|
||||
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
|
||||
xmlns:templates="clr-namespace:eShopOnContainers.Core.Views.Templates;assembly=eShopOnContainers.Core"
|
||||
Title="Cart">
|
||||
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
|
||||
Title="Cart">
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="eShopOnContainers.Core.Views.CatalogView"
|
||||
xmlns:templates="clr-namespace:eShopOnContainers.Core.Views.Templates;assembly=eShopOnContainers.Core"
|
||||
xmlns:views="clr-namespace:eShopOnContainers.Core.Views;assembly=eShopOnContainers.Core"
|
||||
xmlns:views="clr-namespace:eShopOnContainers.Core.Views;assembly=eShopOnContainers.Core"
|
||||
xmlns:viewModelBase="clr-namespace:eShopOnContainers.Core.ViewModels.Base;assembly=eShopOnContainers.Core"
|
||||
xmlns:animations="clr-namespace:eShopOnContainers.Core.Animations;assembly=eShopOnContainers.Core"
|
||||
xmlns:triggers="clr-namespace:eShopOnContainers.Core.Triggers;assembly=eShopOnContainers.Core"
|
||||
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
|
||||
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
|
||||
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
|
||||
Title="Catalog">
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
|
@ -8,17 +8,15 @@ namespace eShopOnContainers.Core.Views
|
||||
{
|
||||
public partial class CatalogView : ContentPage, IMenuContainerPage
|
||||
{
|
||||
private FiltersView _filterView;
|
||||
private FiltersView _filterView = new FiltersView();
|
||||
|
||||
public CatalogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_filterView = new FiltersView();
|
||||
|
||||
SlideMenu = _filterView;
|
||||
|
||||
MessagingCenter.Subscribe<CatalogViewModel>(this, MessengerKeys.Filter, (sender) =>
|
||||
MessagingCenter.Subscribe<CatalogViewModel>(this, MessageKeys.Filter, (sender) =>
|
||||
{
|
||||
Filter();
|
||||
});
|
||||
@ -42,7 +40,6 @@ namespace eShopOnContainers.Core.Views
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
|
@ -1,8 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="eShopOnContainers.Core.Views.CheckoutView"
|
||||
xmlns:templates="clr-namespace:eShopOnContainers.Core.Views.Templates;assembly=eShopOnContainers.Core"
|
||||
x:Class="eShopOnContainers.Core.Views.CheckoutView"
|
||||
xmlns:viewModelBase="clr-namespace:eShopOnContainers.Core.ViewModels.Base;assembly=eShopOnContainers.Core"
|
||||
xmlns:templates="clr-namespace:eShopOnContainers.Core.Views.Templates;assembly=eShopOnContainers.Core"
|
||||
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
|
||||
Title="Checkout">
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
@ -82,8 +84,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- ORDER INFO -->
|
||||
<Grid
|
||||
x:Name="OrderInfo">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
@ -139,8 +140,7 @@
|
||||
</Grid>
|
||||
<!-- SHIPPING ADDRESS -->
|
||||
<Grid
|
||||
x:Name="ShippingAddress"
|
||||
Grid.Row="1"
|
||||
Grid.Row="1"
|
||||
Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@ -167,9 +167,7 @@
|
||||
</StackLayout>
|
||||
</Grid>
|
||||
<!-- ORDER ITEMS -->
|
||||
<Grid
|
||||
x:Name="OrderItems"
|
||||
Grid.Row="2">
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
|
@ -2,9 +2,11 @@
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="eShopOnContainers.Core.Views.LoginView"
|
||||
xmlns:viewModelBase="clr-namespace:eShopOnContainers.Core.ViewModels.Base;assembly=eShopOnContainers.Core"
|
||||
xmlns:animations="clr-namespace:eShopOnContainers.Core.Animations;assembly=eShopOnContainers.Core"
|
||||
xmlns:triggers="clr-namespace:eShopOnContainers.Core.Triggers;assembly=eShopOnContainers.Core"
|
||||
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core">
|
||||
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
|
||||
viewModelBase:ViewModelLocator.AutoWireViewModel="true">
|
||||
<ContentPage.Title>
|
||||
<OnPlatform
|
||||
x:TypeArguments="x:String"
|
||||
@ -175,30 +177,60 @@
|
||||
Margin="24">
|
||||
<Label
|
||||
Text="User name or email"
|
||||
Style="{StaticResource HeaderLabelStyle}"/>
|
||||
<Entry
|
||||
Text="{Binding UserName.Value, Mode=TwoWay}">
|
||||
<Entry.Style>
|
||||
Style="{StaticResource HeaderLabelStyle}" />
|
||||
<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
|
||||
<Entry.Style>
|
||||
<OnPlatform x:TypeArguments="Style"
|
||||
iOS="{StaticResource EntryStyle}"
|
||||
Android="{StaticResource EntryStyle}"
|
||||
WinPhone="{StaticResource UwpEntryStyle}"/>
|
||||
</Entry.Style>
|
||||
<Entry.Behaviors>
|
||||
<behaviors:EventToCommandBehavior
|
||||
EventName="TextChanged"
|
||||
Command="{Binding ValidateUserNameCommand}" />
|
||||
</Entry.Behaviors>
|
||||
<Entry.Triggers>
|
||||
<DataTrigger
|
||||
TargetType="Entry"
|
||||
Binding="{Binding UserName.IsValid}"
|
||||
Value="False">
|
||||
<Setter Property="behaviors:LineColorBehavior.LineColor" Value="{StaticResource ErrorColor}" />
|
||||
</DataTrigger>
|
||||
</Entry.Triggers>
|
||||
</Entry>
|
||||
<Label
|
||||
Text="{Binding UserName.Errors, Converter={StaticResource FirstValidationErrorConverter}"
|
||||
Style="{StaticResource ValidationErrorLabelStyle}" />
|
||||
<Label
|
||||
Text="Password"
|
||||
Style="{StaticResource HeaderLabelStyle}"/>
|
||||
<Entry
|
||||
IsPassword="True"
|
||||
Text="{Binding Password.Value, Mode=TwoWay}"
|
||||
Style="{StaticResource EntryStyle}">
|
||||
Text="{Binding Password.Value, Mode=TwoWay}">
|
||||
<Entry.Style>
|
||||
<OnPlatform x:TypeArguments="Style"
|
||||
iOS="{StaticResource EntryStyle}"
|
||||
Android="{StaticResource EntryStyle}"
|
||||
WinPhone="{StaticResource UwpEntryStyle}"/>
|
||||
</Entry.Style>
|
||||
<Entry.Behaviors>
|
||||
<behaviors:EventToCommandBehavior
|
||||
EventName="TextChanged"
|
||||
Command="{Binding ValidatePasswordCommand}" />
|
||||
</Entry.Behaviors>
|
||||
<Entry.Triggers>
|
||||
<DataTrigger
|
||||
TargetType="Entry"
|
||||
Binding="{Binding Password.IsValid}"
|
||||
Value="False">
|
||||
<Setter Property="behaviors:LineColorBehavior.LineColor" Value="{StaticResource ErrorColor}" />
|
||||
</DataTrigger>
|
||||
</Entry.Triggers>
|
||||
</Entry>
|
||||
<Label
|
||||
Text="{Binding Password.Errors, Converter={StaticResource FirstValidationErrorConverter}"
|
||||
Style="{StaticResource ValidationErrorLabelStyle}" />
|
||||
</StackLayout>
|
||||
<!-- LOGIN BUTTON -->
|
||||
<Grid
|
||||
|
@ -1,5 +1,4 @@
|
||||
using eShopOnContainers.Core.Helpers;
|
||||
using eShopOnContainers.Core.ViewModels;
|
||||
using eShopOnContainers.Core.ViewModels;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
@ -22,14 +21,16 @@ namespace eShopOnContainers.Core.Views
|
||||
this.Content = null;
|
||||
this.Content = content;
|
||||
|
||||
_animate = true;
|
||||
await AnimateIn();
|
||||
|
||||
var vm = BindingContext as LoginViewModel;
|
||||
|
||||
if(vm != null)
|
||||
var vm = BindingContext as LoginViewModel;
|
||||
if (vm != null)
|
||||
{
|
||||
vm.InvalidateMock();
|
||||
|
||||
if (!vm.IsMock)
|
||||
{
|
||||
_animate = true;
|
||||
await AnimateIn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,14 +55,13 @@ namespace eShopOnContainers.Core.Views
|
||||
{
|
||||
while (_animate)
|
||||
{
|
||||
await uiElement.ScaleTo(1.05, duration, Easing.SinInOut);
|
||||
|
||||
await Task.WhenAll(
|
||||
uiElement.FadeTo(1, duration, Easing.SinInOut),
|
||||
uiElement.LayoutTo(new Rectangle(new Point(0, 0), new Size(uiElement.Width, uiElement.Height))),
|
||||
uiElement.FadeTo(.9, duration, Easing.SinInOut),
|
||||
uiElement.ScaleTo(1.15, duration, Easing.SinInOut)
|
||||
);
|
||||
await uiElement.ScaleTo(1.05, duration, Easing.SinInOut);
|
||||
await Task.WhenAll(
|
||||
uiElement.FadeTo(1, duration, Easing.SinInOut),
|
||||
uiElement.LayoutTo(new Rectangle(new Point(0, 0), new Size(uiElement.Width, uiElement.Height))),
|
||||
uiElement.FadeTo(.9, duration, Easing.SinInOut),
|
||||
uiElement.ScaleTo(1.15, duration, Easing.SinInOut)
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -1,12 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="eShopOnContainers.Core.Views.MainView"
|
||||
xmlns:views="clr-namespace:eShopOnContainers.Core.Views;assembly=eShopOnContainers.Core"
|
||||
xmlns:viewModelBase="clr-namespace:eShopOnContainers.Core.ViewModels.Base;assembly=eShopOnContainers.Core"
|
||||
xmlns:controls="clr-namespace:eShopOnContainers.Core.Controls;assembly=eShopOnContainers.Core"
|
||||
BarBackgroundColor="{StaticResource DarkGreenColor}"
|
||||
BackgroundColor="{StaticResource BackgroundColor}"
|
||||
BarTextColor="{StaticResource WhiteColor}">
|
||||
BarTextColor="{StaticResource WhiteColor}"
|
||||
viewModelBase:ViewModelLocator.AutoWireViewModel="true">
|
||||
<TabbedPage.Title>
|
||||
<OnPlatform
|
||||
x:TypeArguments="x:String"
|
||||
|
@ -1,6 +1,5 @@
|
||||
using eShopOnContainers.Core.ViewModels;
|
||||
using eShopOnContainers.Core.ViewModels.Base;
|
||||
using eShopOnContainers.ViewModels.Base;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace eShopOnContainers.Core.Views
|
||||
@ -16,7 +15,7 @@ namespace eShopOnContainers.Core.Views
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
MessagingCenter.Subscribe<MainViewModel, int>(this, MessengerKeys.ChangeTab, (sender, arg) =>
|
||||
MessagingCenter.Subscribe<MainViewModel, int>(this, MessageKeys.ChangeTab, (sender, arg) =>
|
||||
{
|
||||
switch(arg)
|
||||
{
|
||||
@ -32,17 +31,9 @@ namespace eShopOnContainers.Core.Views
|
||||
}
|
||||
});
|
||||
|
||||
var homeViewModel = ViewModelLocator.Instance.Resolve<CatalogViewModel>();
|
||||
await homeViewModel.InitializeAsync(null);
|
||||
HomeView.BindingContext = homeViewModel;
|
||||
|
||||
var basketViewModel = ViewModelLocator.Instance.Resolve<BasketViewModel>();
|
||||
await basketViewModel.InitializeAsync(null);
|
||||
BasketView.BindingContext = basketViewModel;
|
||||
|
||||
var profileViewModel = ViewModelLocator.Instance.Resolve<ProfileViewModel>();
|
||||
await profileViewModel.InitializeAsync(null);
|
||||
ProfileView.BindingContext = profileViewModel;
|
||||
await ((CatalogViewModel)HomeView.BindingContext).InitializeAsync(null);
|
||||
await ((BasketViewModel)BasketView.BindingContext).InitializeAsync(null);
|
||||
await ((ProfileViewModel)ProfileView.BindingContext).InitializeAsync(null);
|
||||
}
|
||||
|
||||
protected override async void OnCurrentPageChanged()
|
||||
|
@ -1,10 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="eShopOnContainers.Core.Views.OrderDetailView"
|
||||
x:Class="eShopOnContainers.Core.Views.OrderDetailView"
|
||||
xmlns:viewModelBase="clr-namespace:eShopOnContainers.Core.ViewModels.Base;assembly=eShopOnContainers.Core"
|
||||
xmlns:templates="clr-namespace:eShopOnContainers.Core.Views.Templates;assembly=eShopOnContainers.Core"
|
||||
xmlns:animations="clr-namespace:eShopOnContainers.Core.Animations;assembly=eShopOnContainers.Core"
|
||||
xmlns:triggers="clr-namespace:eShopOnContainers.Core.Triggers;assembly=eShopOnContainers.Core"
|
||||
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
|
||||
Title="{Binding Order.OrderNumber}">
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
|
@ -1,10 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="eShopOnContainers.Core.Views.ProfileView"
|
||||
xmlns:views="clr-namespace:eShopOnContainers.Core.Views;assembly=eShopOnContainers.Core"
|
||||
x:Class="eShopOnContainers.Core.Views.ProfileView"
|
||||
xmlns:views="clr-namespace:eShopOnContainers.Core.Views;assembly=eShopOnContainers.Core"
|
||||
xmlns:viewModelBase="clr-namespace:eShopOnContainers.Core.ViewModels.Base;assembly=eShopOnContainers.Core"
|
||||
xmlns:templates="clr-namespace:eShopOnContainers.Core.Views.Templates;assembly=eShopOnContainers.Core"
|
||||
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
|
||||
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
|
||||
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
|
||||
Title="My profile">
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
|
@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="eShopOnContainers.Core.Views.SettingsView"
|
||||
x:Class="eShopOnContainers.Core.Views.SettingsView"
|
||||
xmlns:viewModelBase="clr-namespace:eShopOnContainers.Core.ViewModels.Base;assembly=eShopOnContainers.Core"
|
||||
xmlns:controls="clr-namespace:eShopOnContainers.Core.Controls;assembly=eShopOnContainers.Core"
|
||||
xmlns:animations="clr-namespace:eShopOnContainers.Core.Animations;assembly=eShopOnContainers.Core"
|
||||
xmlns:triggers="clr-namespace:eShopOnContainers.Core.Triggers;assembly=eShopOnContainers.Core"
|
||||
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
|
||||
xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core"
|
||||
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
|
||||
Title="Settings">
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
|
@ -112,7 +112,7 @@
|
||||
<Compile Include="Validations\IValidity.cs" />
|
||||
<Compile Include="Validations\ValidatableObject.cs" />
|
||||
<Compile Include="ViewModels\Base\ExtendedBindableObject.cs" />
|
||||
<Compile Include="ViewModels\Base\MessengerKeys.cs" />
|
||||
<Compile Include="ViewModels\Base\MessageKeys.cs" />
|
||||
<Compile Include="ViewModels\Base\ViewModelBase.cs" />
|
||||
<Compile Include="ViewModels\Base\ViewModelLocator.cs" />
|
||||
<Compile Include="ViewModels\BasketViewModel.cs" />
|
||||
@ -166,6 +166,9 @@
|
||||
<DependentUpon>ProductTemplate.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Converters\WebNavigatingEventArgsConverter.cs" />
|
||||
<Compile Include="Converters\FirstValidationErrorConverter.cs" />
|
||||
<Compile Include="Effects\EntryLineColorEffect.cs" />
|
||||
<Compile Include="Behaviors\LineColorBehavior.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
@ -352,4 +355,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
@ -3,7 +3,7 @@ using eShopOnContainers.Droid.Effects;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using System;
|
||||
using Android.Widget;
|
||||
using eShopOnContainers.Core.Effects;
|
||||
using eShopOnContainers.Core.Behaviors;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
@ -34,7 +34,7 @@ namespace eShopOnContainers.Droid.Effects
|
||||
|
||||
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
|
||||
{
|
||||
if (args.PropertyName == LineColorEffect.LineColorProperty.PropertyName)
|
||||
if (args.PropertyName == LineColorBehavior.LineColorProperty.PropertyName)
|
||||
{
|
||||
UpdateLineColor();
|
||||
}
|
||||
@ -46,7 +46,7 @@ namespace eShopOnContainers.Droid.Effects
|
||||
{
|
||||
if (control != null)
|
||||
{
|
||||
control.Background.SetColorFilter(LineColorEffect.GetLineColor(Element).ToAndroid(), Android.Graphics.PorterDuff.Mode.SrcAtop);
|
||||
control.Background.SetColorFilter(LineColorBehavior.GetLineColor(Element).ToAndroid(), Android.Graphics.PorterDuff.Mode.SrcAtop);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -8,7 +8,7 @@ using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.UWP;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using eShopOnContainers.Windows.Effects;
|
||||
using eShopOnContainers.Core.Effects;
|
||||
using eShopOnContainers.Core.Behaviors;
|
||||
|
||||
[assembly: ResolutionGroupName("eShopOnContainers")]
|
||||
[assembly: ExportEffect(typeof(EntryLineColorEffect), "EntryLineColorEffect")]
|
||||
@ -38,7 +38,7 @@ namespace eShopOnContainers.Windows.Effects
|
||||
|
||||
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
|
||||
{
|
||||
if (args.PropertyName == LineColorEffect.LineColorProperty.PropertyName)
|
||||
if (args.PropertyName == LineColorBehavior.LineColorProperty.PropertyName)
|
||||
{
|
||||
UpdateLineColor();
|
||||
}
|
||||
@ -49,7 +49,7 @@ namespace eShopOnContainers.Windows.Effects
|
||||
if (control != null)
|
||||
{
|
||||
control.BorderThickness = new Xaml.Thickness(0, 0, 0, 1);
|
||||
var lineColor = XamarinFormColorToWindowsColor(LineColorEffect.GetLineColor(Element));
|
||||
var lineColor = XamarinFormColorToWindowsColor(LineColorBehavior.GetLineColor(Element));
|
||||
control.BorderBrush = new Media.SolidColorBrush(lineColor);
|
||||
|
||||
var style = Xaml.Application.Current.Resources["FormTextBoxStyle"] as Xaml.Style;
|
||||
|
@ -1,6 +1,6 @@
|
||||
using CoreAnimation;
|
||||
using CoreGraphics;
|
||||
using eShopOnContainers.Core.Effects;
|
||||
using eShopOnContainers.Core.Behaviors;
|
||||
using eShopOnContainers.iOS.Effects;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
@ -39,7 +39,7 @@ namespace eShopOnContainers.iOS.Effects
|
||||
{
|
||||
base.OnElementPropertyChanged(args);
|
||||
|
||||
if (args.PropertyName == LineColorEffect.LineColorProperty.PropertyName ||
|
||||
if (args.PropertyName == LineColorBehavior.LineColorProperty.PropertyName ||
|
||||
args.PropertyName == "Height")
|
||||
{
|
||||
Initialize();
|
||||
@ -71,7 +71,7 @@ namespace eShopOnContainers.iOS.Effects
|
||||
}
|
||||
|
||||
lineLayer.Frame = new CGRect(0f, Control.Frame.Height - 1f, Control.Bounds.Width, 1f);
|
||||
lineLayer.BorderColor = LineColorEffect.GetLineColor(Element).ToCGColor();
|
||||
lineLayer.BorderColor = LineColorBehavior.GetLineColor(Element).ToCGColor();
|
||||
control.TintColor = control.TextColor;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
using System.Data.Common;
|
||||
using System.Reflection;
|
||||
using global::Catalog.API.IntegrationEvents;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class Startup
|
||||
{
|
||||
|
@ -1,31 +1,30 @@
|
||||
using IdentityServer4.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Collections.Generic;
|
||||
using IdentityServer4;
|
||||
|
||||
namespace Identity.API.Configuration
|
||||
{
|
||||
public class Config
|
||||
{
|
||||
// scopes define the resources in your system
|
||||
public static IEnumerable<Scope> GetScopes()
|
||||
// ApiResources define the apis in your system
|
||||
public static IEnumerable<ApiResource> GetApis()
|
||||
{
|
||||
return new List<Scope>
|
||||
return new List<ApiResource>
|
||||
{
|
||||
//Authentication OpenId uses this scopes;
|
||||
StandardScopes.OpenId,
|
||||
StandardScopes.Profile,
|
||||
new ApiResource("orders", "Orders Service"),
|
||||
new ApiResource("basket", "Basket Service")
|
||||
};
|
||||
}
|
||||
|
||||
//Each api we want to securice;
|
||||
new Scope
|
||||
{
|
||||
Name = "orders",
|
||||
Description = "Orders Service"
|
||||
},
|
||||
new Scope
|
||||
{
|
||||
Name = "basket",
|
||||
Description = "Basket Service"
|
||||
}
|
||||
// Identity resources are data like user ID, name, or email address of a user
|
||||
// see: http://docs.identityserver.io/en/release/configuration/resources.html
|
||||
public static IEnumerable<IdentityResource> GetResources()
|
||||
{
|
||||
return new List<IdentityResource>
|
||||
{
|
||||
new IdentityResources.OpenId(),
|
||||
new IdentityResources.Profile()
|
||||
};
|
||||
}
|
||||
|
||||
@ -47,8 +46,8 @@ namespace Identity.API.Configuration
|
||||
AllowedCorsOrigins = { $"{clientsUrl["Spa"]}" },
|
||||
AllowedScopes =
|
||||
{
|
||||
StandardScopes.OpenId.Name,
|
||||
StandardScopes.Profile.Name,
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile,
|
||||
"orders",
|
||||
"basket"
|
||||
}
|
||||
@ -65,8 +64,8 @@ namespace Identity.API.Configuration
|
||||
AllowedCorsOrigins = { "http://eshopxamarin" },
|
||||
AllowedScopes =
|
||||
{
|
||||
StandardScopes.OpenId.Name,
|
||||
StandardScopes.Profile.Name,
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile,
|
||||
"orders",
|
||||
"basket"
|
||||
}
|
||||
@ -82,6 +81,7 @@ namespace Identity.API.Configuration
|
||||
ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client
|
||||
AllowedGrantTypes = GrantTypes.Hybrid,
|
||||
RequireConsent = false,
|
||||
AllowOfflineAccess = true,
|
||||
RedirectUris = new List<string>
|
||||
{
|
||||
$"{clientsUrl["Mvc"]}/signin-oidc",
|
||||
@ -96,9 +96,9 @@ namespace Identity.API.Configuration
|
||||
},
|
||||
AllowedScopes = new List<string>
|
||||
{
|
||||
StandardScopes.OpenId.Name,
|
||||
StandardScopes.Profile.Name,
|
||||
StandardScopes.OfflineAccess.Name,
|
||||
IdentityServerConstants.StandardScopes.OpenId,
|
||||
IdentityServerConstants.StandardScopes.Profile,
|
||||
IdentityServerConstants.StandardScopes.OfflineAccess,
|
||||
"orders",
|
||||
"basket",
|
||||
},
|
||||
|
@ -5,7 +5,6 @@
|
||||
using IdentityModel;
|
||||
using IdentityServer4.Quickstart.UI.Models;
|
||||
using IdentityServer4.Services;
|
||||
using IdentityServer4.Services.InMemory;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
|
@ -22,7 +22,7 @@ namespace IdentityServer4.Quickstart.UI.Controllers
|
||||
{
|
||||
private readonly ILogger<ConsentController> _logger;
|
||||
private readonly IClientStore _clientStore;
|
||||
private readonly IScopeStore _scopeStore;
|
||||
private readonly IResourceStore _resourceStore;
|
||||
private readonly IIdentityServerInteractionService _interaction;
|
||||
|
||||
|
||||
@ -30,12 +30,12 @@ namespace IdentityServer4.Quickstart.UI.Controllers
|
||||
ILogger<ConsentController> logger,
|
||||
IIdentityServerInteractionService interaction,
|
||||
IClientStore clientStore,
|
||||
IScopeStore scopeStore)
|
||||
IResourceStore resourceStore)
|
||||
{
|
||||
_logger = logger;
|
||||
_interaction = interaction;
|
||||
_clientStore = clientStore;
|
||||
_scopeStore = scopeStore;
|
||||
_resourceStore = resourceStore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -120,10 +120,10 @@ namespace IdentityServer4.Quickstart.UI.Controllers
|
||||
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
|
||||
if (client != null)
|
||||
{
|
||||
var scopes = await _scopeStore.FindEnabledScopesAsync(request.ScopesRequested);
|
||||
if (scopes != null && scopes.Any())
|
||||
var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
|
||||
if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any()))
|
||||
{
|
||||
return new ConsentViewModel(model, returnUrl, request, client, scopes);
|
||||
return new ConsentViewModel(model, returnUrl, request, client, resources);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -41,8 +41,8 @@
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.0-msbuild3-final">
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.0-rc3" />
|
||||
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.0-rc3" />
|
||||
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.0" />
|
||||
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
|
||||
|
@ -10,7 +10,7 @@ namespace Identity.API.Models.AccountViewModels
|
||||
{
|
||||
public class ConsentViewModel : ConsentInputModel
|
||||
{
|
||||
public ConsentViewModel(ConsentInputModel model, string returnUrl, AuthorizationRequest request, Client client, IEnumerable<Scope> scopes)
|
||||
public ConsentViewModel(ConsentInputModel model, string returnUrl, AuthorizationRequest request, Client client, Resources resources)
|
||||
{
|
||||
RememberConsent = model?.RememberConsent ?? true;
|
||||
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>();
|
||||
@ -22,8 +22,8 @@ namespace Identity.API.Models.AccountViewModels
|
||||
ClientLogoUrl = client.LogoUri;
|
||||
AllowRememberConsent = client.AllowRememberConsent;
|
||||
|
||||
IdentityScopes = scopes.Where(x => x.Type == ScopeType.Identity).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
||||
ResourceScopes = scopes.Where(x => x.Type == ScopeType.Resource).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
||||
IdentityScopes = resources.IdentityResources.Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
||||
ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
||||
}
|
||||
|
||||
public string ClientName { get; set; }
|
||||
@ -47,6 +47,16 @@ namespace Identity.API.Models.AccountViewModels
|
||||
Checked = check || scope.Required;
|
||||
}
|
||||
|
||||
public ScopeViewModel(IdentityResource identity, bool check)
|
||||
{
|
||||
Name = identity.Name;
|
||||
DisplayName = identity.DisplayName;
|
||||
Description = identity.Description;
|
||||
Emphasize = identity.Emphasize;
|
||||
Required = identity.Required;
|
||||
Checked = check || identity.Required;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
@ -78,7 +78,8 @@ namespace eShopOnContainers.Identity
|
||||
// Adds IdentityServer
|
||||
services.AddIdentityServer(x => x.IssuerUri = "null")
|
||||
.AddSigningCredential(Certificate.Get())
|
||||
.AddInMemoryScopes(Config.GetScopes())
|
||||
.AddInMemoryApiResources(Config.GetApis())
|
||||
.AddInMemoryIdentityResources(Config.GetResources())
|
||||
.AddInMemoryClients(Config.GetClients(clientUrls))
|
||||
.AddAspNetIdentity<ApplicationUser>()
|
||||
.Services.AddTransient<IProfileService, ProfileService>();
|
||||
|
@ -33,6 +33,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
|
||||
.Where(i => i.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>)))
|
||||
.Select(i => new KeyedService("IAsyncNotificationHandler", i)))
|
||||
.AsImplementedInterfaces();
|
||||
|
||||
|
||||
builder
|
||||
.RegisterAssemblyTypes(typeof(CreateOrderCommandValidator).GetTypeInfo().Assembly)
|
||||
|
@ -14,6 +14,7 @@ using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Options;
|
||||
using WebMVC.Services.Utilities;
|
||||
using Microsoft.Extensions.HealthChecks;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.HttpResilience;
|
||||
|
||||
@ -48,7 +49,10 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
||||
|
||||
services.AddHealthChecks(checks =>
|
||||
{
|
||||
checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask<IHealthCheckResult>(HealthCheckResult.Healthy("Ok")));
|
||||
checks.AddUrlCheck(Configuration["CatalogUrl"]);
|
||||
checks.AddUrlCheck(Configuration["OrderingUrl"]);
|
||||
checks.AddUrlCheck(Configuration["BasketUrl"]);
|
||||
checks.AddUrlCheck(Configuration["IdentityUrl"]);
|
||||
});
|
||||
|
||||
// Add application services.
|
||||
@ -115,15 +119,10 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
||||
ResponseType = "code id_token",
|
||||
SaveTokens = true,
|
||||
GetClaimsFromUserInfoEndpoint = true,
|
||||
RequireHttpsMetadata = false,
|
||||
RequireHttpsMetadata = false,
|
||||
Scope = { "openid", "profile", "orders", "basket" }
|
||||
};
|
||||
|
||||
oidcOptions.Scope.Clear();
|
||||
oidcOptions.Scope.Add("openid");
|
||||
oidcOptions.Scope.Add("profile");
|
||||
oidcOptions.Scope.Add("orders");
|
||||
oidcOptions.Scope.Add("basket");
|
||||
|
||||
//Wait untill identity service is ready on compose.
|
||||
app.UseOpenIdConnectAuthentication(oidcOptions);
|
||||
|
||||
|
@ -1,9 +0,0 @@
|
||||
|
||||
Let's create here the simplest monolithic MVC Web app with just the Home Page and ASP.NET Core MVC.
|
||||
Please, don't say anything about "Containers" here.
|
||||
|
||||
From a "branding point of view" it should be named as "eShopWeb"
|
||||
|
||||
Cesar.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user