<![CDATA[Techne]]>asvegah.github.io//asvegah.github.io//favicon.pngTechneasvegah.github.io//Ghost 3.37Mon, 09 Nov 2020 12:24:02 GMT60<![CDATA[Digital Twins: Values, Challenges and Enablers From a Modelling and Ontology Perspective]]>asvegah.github.io//digital-twins/5fa1f4faa0141837a0bc4815Fri, 06 Nov 2020 21:02:00 GMT

Abstract: Nowadays, Industry 4.0 is widely known in the industry. Many large companies have pushed and adapted to Industry 4.0 to increase their business competitiveness, market shares, and capture new business opportunities. So small and medium-sized companies must adjust to improve their production capabilities to keep up with the technological development of Industry 4.0. With the recent wave of digitalization, the latest trend in every industry is to build systems and approaches that will help it not only during the conceptualization, prototyping, testing and design optimization phase but also during the operation phase with the ultimate aim to use them throughout the whole product life cycle and perhaps much beyond.

A digital twin can be defined as a virtual representation of a physical asset i.e. product or a process enabled through data and simulators. Recent advances in computational pipelines, artificial intelligence, big data processing and management tools bring the promise of digital twins and their impact on society closer to reality. One such manifestation of digital twining is mass customization, a marketing and manufacturing technique that combines flexibility and personalization offering products tailored to each individual customer’s needs, at a low cost. The mass customization paradigm allows customers to select, order, and receive a specially configured product, often choosing from a multitude of product options. These product options often require real-time prediction, optimization, monitoring and controlling for improved decision making.  In the design phase, one of the main challenges in fulfilling custom orders is to generate bills of materials and design specifications quickly and accurately from the thousands of possible combinations.  In the operational and maintenance phase, one of the main challenges is supervisory, diagnostic and control, and predictive maintenance of the product. In this work, we review the recent status of methodologies and techniques related to the construction of digital twins mostly from a modelling perspective. Our aim is to provide a detailed coverage of the current challenges and enabling technologies along with recommendations and reflections for various stakeholders by designing and developing digital twin systems combined with existing production techniques such mass customization.

Keywords: Industry 4.0; Digital Twins; Internet of Things; Mass Customization; Design Optimization; Predictive Maintenance.

Introduction

In today's hypercompetitive environment, there's pressure to develop and bring to market products faster, to identify efficiencies and how they are built, and to incorporate innovations in design and features. This is particularly true of highly sophisticated machinery and other complex systems, including buildings and cities. In the new product introduction process, the luxury of spending months and even years designing an improved or new system seldom exists anymore. Being able to create relatively quickly an accurate, functioning virtual replica of a future system and test it under all manner of scenarios is highly desired and beneficial. This is the role of a digital twin in the design phase. A digital twin is a virtual replica of a physical thing. This virtual twin exists only as software rendered by computing power. Having a digital replica of a physical thing can significantly improve on one or more of the following processes, design, simulation, planning, building, operating, maintaining, optimizing, and disposal.

For example, isn't it useful to first build a virtual skyscraper, then put it through a variety of earthquakes to accurately understand what might happen? Based on the data provided, the design can then be modified. In the build phase, a digital twin can be used to provide the construction specifications or what we call parametric estimates to different providers. In this way, a digital twin can be an asset in streamlining the procurement process. In addition, and importantly, during build, sensors are applied to the physical object to collect and transmit data back to its virtual replica. This is what enables diagnostic and control during the operational and maintenance phases. At this point, with enough sensors, the virtual twin is providing all relevant data about the state of the physical twin. For example, an operational machine can render accurately in its digital twin its temperature, vibration, speed, and so much more. All of this becomes possible because of increasingly better digital technologies that include faster computers, better telemetry, that is, the communication of measurements from a collection point to receiving equipment, smaller, more accurate sensors, data management, and artificial intelligence.

An ontology approach seems to be an effective methodology for product lifecycle management (PLM). Ontology is a formal and explicit specifications of shared conceptualizations, representing concepts and their relations that are relevant for a given domain of discourse and serve as a means for establishing a conceptually concise basis for communicating knowledge for many purposes. In this paper a framework for representing a digital twin based on an ontology approach is proposed. It consists in the management of a number of ontologies, the product functionalities ontology, product configuration ontology, data pipeline ontology and operations and control ontologies. These ontologies represent the requirement and configuration knowledge, operational, control and predictive maintenance for a real customization of the product.


Literature Review

During the design phase, it is possible to virtually create the optimum solution and accurately render it operational before a single physical action is taken. Then we can simulate that solution under different types of real scenarios. It is the result of exhaustive simulations and rich data that specify areas such as best architecture, configuration, materials, and cost.  During operations, an abundance of data is being collected and fed back to its digital twin over a digital thread. Think of this as a data pipeline that enables analytics of various states and stages. Backed by artificial intelligence, the digital twin can identify and even predict maintenance issues before they happen. It has become a data-informed model of a physical system as Figure 1 demonstrates. This compelling feature reduces cost since it is typically cheaper to proactively conduct maintenance than to repair it after it is broken. Finally, this continuous real-time feed of data can help with optimization. That is, improve its performance by enabling the system to either automatically modify its own behaviour or by prompting the manual intervention of a human.

Digital Twins: Values, Challenges and Enablers From a Modelling and Ontology Perspective
Representation of a data informed Digital Twin

Digital twins have become particularly ubiquitous in the Internet of Things or IoT world. IoT devices are everywhere now, in our homes, across our cities, and in our factories, where we call them industrial IoT devices. These internet-connected electronics collect and produce data and services and interact and communicate with each other and central systems. The data collected from these devices creates detailed knowledge, enabling capabilities. The use of digital twins in the context of IoT will likely be one of the defining qualities of the future of this topic. With billions of new IoT devices being deployed and managed each year, it must be clear by now that digital twins have a remarkable future ahead. More advanced digital twins can produce specifications for the manufacturing process, including the production line, the metrics that must be captured during build, and testing procedures for parts and the product. As you can imagine, it is helpful to understand how a production line might perform for a given product before that production line is built. A digitally enabled manufacturing plant, called a smart factory, may have digital twins from design through to fully manufactured products, and even to post-deployment performance and maintenance in the field. This is the end-state of comprehensive product lifecycle management or PLM.

Digital Twins: Values, Challenges and Enablers From a Modelling and Ontology Perspective
Product Lifecyle Management of a Digital Twin and some needed functions/tools

Values and Benefits

Description

Real-time remote monitoring and control

Generally, it is almost impossible to gain an in-depth view of a very large system physically in real-time. A digital twin owing to its very nature can be accessible anywhere. The performance of the system can not only be monitored but also controlled remotely using feedback mechanisms.

Greater efficiency and safety

 

It is envisioned that digital twinning will enable greater autonomy with humans in the loop as and when required. This will ensure that the dangerous, dull and dirty jobs are allocated to robots with humans controlling them remotely. This way humans will be able to focus on more creative and innovative jobs

 

Predictive maintenance and scheduling

A comprehensive digital twinning will ensure that multiple sensors monitoring the physical assets will be generating big data in real-time. Through a smart analysis of data, faults in the system can be detected much in advance. This will enable better scheduling of maintenance

 

Scenario and risk assessment

A digital twin or to be more precise a digital sibling of the system will enable what-if analyses resulting in better risk assessment. It will be possible to perturb the system to synthesize unexpected scenarios and study the response of the system as well as the corresponding mitigation strategies. This kind of analysis without jeopardizing the real asset is only possible via a digital twin.

Better intra- and inter-team synergy and collaborations

 

With greater autonomy and all the information at a fingertip, teams can better utilize their time in improving synergies and collaborations leading to greater productivity.

 

 

More efficient and informed decision support system

Availability of quantitative data and advanced analytics in real-time will assist in more informed and faster decision makings.

Personalization of products and services

With detailed historical requirements, preferences of various stakeholders and evolving market trends and competitions, the demand of customized products and services are bound to increase. A digital twin in the context of factories of the future will enable faster and smoother gear shifts to account for changing needs.

Better documentation and communication

Readily available information in real-time combined with automated reporting will help keep stakeholders well informed thereby improving transparency.

]]>
<![CDATA[A Generic Framework for Distributed Deep Neural Networks over the Cloud, the Edge, and End Devices]]>asvegah.github.io//ddnn/5fa202bfa0141837a0bc4835Fri, 06 Nov 2020 21:00:00 GMT

Abstract: This paper proposes deep neural networks (DNNs) over distributed computing hierarchies, consisting of the cloud, the edge and end devices. Although a deep neural network (DNN) can accommodate inference in the cloud, a distributed deep neural network (DDNN) supported by scalable distributed computing hierarchy is advantageous. For a DNNN can scale up in neural network size as well as scale out in geographical span and it allows fast and localized inference using shallow portions of the neural network at the edge and end devices. In implementing a DDNN, we map sections of a DNN onto a distributed computing hierarchy.

Keywords: Edge Computing; Internet of Things; Neural Networks.

Introduction

The framework of a large-scale distributed computing hierarchy has assumed new significance in the emerging era of IoT. It is widely expected that most of data generated by the massive number of IoT devices must be processed locally at the devices or at the edge, for otherwise the total amount of sensor data for a centralized cloud would overwhelm the communication network bandwidth. To this end, the goal of this project is to explore the development of heterogeneous mobile edge computing platform (Figure 1) that performs real-time Big Data analytics and deep learning with low-latency computing at the network edge.

A Generic Framework for Distributed Deep Neural Networks over the Cloud, the Edge, and End Devices
Overview of Mobile Edge Computing Platform.

With the rise in Big Data, Cloud Computing, and the emergence of IoT Services, it is becoming essential to collate, process, manipulate data at the network edge to assist with intelligent re-orchestration. Moreover, key technologies for the creation of autonomous vehicles, smart homes and healthcare will be powered by thousands of IoT sensors and end devices, but questions such as: – What does it take to create and manage a new end point devices? (compute and storage resources) How do you identify an end service canonically across infrastructure and platform services? (identity e.g. scalability) How do you allocate resources for an end device? (resource provisioning e.g. bandwidth) What does it take to operate an end device? (monitoring e.g. dependability) How do you measure resource utilization and cost of operating an end device? (metering and chargeback e.g. energy efficiency) These questions persist regardless of an organization’s IoT strategy. Managing autonomous and intelligent re-orchestration end device (i.e., creating them, provisioning resources, deploying, metering, charging, and deprecating) at scale proves to be challenging in addressing these questions.

The rise in Internet of Things (IoT) devices as well as a dramatic increase in the number of end devices provides appealing opportunities for machine learning applications at the network edge as they are often directly connected to sensors (e.g., cameras, microphones, gyroscopes) that capture a large quantity of input data. However, the current approaches to machine learning systems on end devices are either to offload input sensory data to DNNs in the cloud, with the associated privacy concerns, latency and communication issues or perform Figure 1. Overview of Mobile Edge Computing Platform A Generic Framework for Distributed Deep Neural Networks over the Cloud, the Edge and End Devices directly on the end device using simple Machine Learning (ML) models leading to reduced system accuracy. The use of hierarchically distributed computing structures consisted of the cloud, the edge and end devices addresses these shortcomings by providing system scalability for large-scale intelligent tasks in distributed IoT and end devices which supports coordinated central and local decisions.

To this end, in implementing a DDNN, we map sections of a single DNN onto a distributed computing hierarchy and by jointly training these sections, the DDNNs can effectively address some of the challenges mentioned before. Moreover, via distributed computing, DDNNs enhance sensor fusion, data privacy and system fault tolerance for DNN applications. As a proof of concept, we show a DDNN can exploit geographical diversity of end point devices. The contributions envisaged are:

1. Literature review of existing methods for data-driven adaptation of end devices and requirements analysis of the needs at scale.

2. A generic DDNN framework and its implementation that maps sections of a DNN onto a distributed computing hierarchy.

3. A joint training method that minimizes communication and resource usage for devices and maximizes usefulness of extracted features which are utilized in the cloud, while allowing low-latency classification via early exit for a high percentage of input samples.

4. A mobile application to extensively test DDNN framework for orchestration and reorchestration of end point devices at scale.

5. A microservices architecture that implements governance at scale.


Materials and Methods

One approach is the combination of small neural network (NN) model on end devices and a larger NN model in the cloud. The small NN model on the end device performs initial feature extraction and classification if confident or otherwise the end device can draw on the large NN model in the cloud. However, this approach comes with certain challenges such limited memory and battery life one end devices such as sensors as well as multiple models at the cloud, edge and end device need to be learnt jointly which may incur huge communication costs in coordinated decision making. To address these concerns under the same optimization framework, it is desirable that a system could train a single end-to-end model, such as a DNN, and partition it between end devices and the cloud, to provide a simpler and more principled approach. DDNN maps a trained DNN onto heterogeneous physical devices distributed locally, at the edge, and in the cloud. Since DDNN relies on a jointly trained DNN framework at all parts in the neural network, for both training and inference, many of the difficult engineering decisions are greatly simplified. Figure 2 provides an overview of the DDNN architecture. The configurations presented show how DDNN can scale the inference computation across different physical devices. The cloud based DDNN in (a) can be viewed as the standard DNN running in the cloud as described in the introduction. In this case, sensor input captured on end devices is sent to the cloud in original format (raw input format), where all layers of DNN inference is performed.

A Generic Framework for Distributed Deep Neural Networks over the Cloud, the Edge, and End Devices
Overview of the DDNN architecture. The vertical lines represent the DNN pipeline, which connects the horizontal bars (NN layers). (a) is the standard DNN (processed entirely in the cloud), (b) introduces end devices and a local exit point that may classify samples before the cloud, (c) extends (b) by adding multiple end devices which are aggregated together for classification, (d) and (e) extend (b) and (c) by adding edge layers between the cloud and end devices, and (f) shows how the edge can also be distributed like the end devices.
A Generic Framework for Distributed Deep Neural Networks over the Cloud, the Edge, and End Devices

Results

Through DDNNs, we undertook design and development of visual gesture recognizer using Raspberry Pi (x19), Neural Compute Stick (x5), Nvidia Jetson TX1 (x2), Microsoft Kinect V2, associated machine vision, and the CUDA and Tensor Flow deep-learning framework.  In this work, computer vision libraries are used together to design and develop a real-time visually and contextually intelligent gesture recognizer.

In machine learning approaches, the developer first creates a training set consisting of videos of people performing the gesture. The developer then labels the videos with which frames and which portions of the depth or RGB data in the frame correspond to the gesture’s movements. Finally, the developer runs an existing machine learning algorithm, such as AdaBoost, to synthesize gesture recognition code that can be included in a program. The developer takes recordings of many different people performing the same gesture, then tags the recordings to provide labeled data. From the labeled data, the developer synthesizes a classifier for the gesture. The classifier runs as a library in the application. Machine learning approaches have important benefits compared to manually written poses. If the training set contains a diverse group of users, such as users of different sizes and ages, the machine learning algorithm can “automatically” discover how to detect the gesture for different users without manual tweaking.

With the rise of sensors such as the Microsoft Kinect, Leap Motion, and hand motion sensors in phones such as the Samsung Galaxy S5, natural user interface (NUI) has become practical. NUI raises two key challenges for the developer: first, developers must create new code to recognize new gestures, which is a time-consuming process. Second, to recognize these gestures, applications must have access to depth and video of the user, raising privacy problems. We address both problems with Prepose, a novel domain-specific language (DSL) for easily building gesture recognizers, combined with a system architecture that protects user privacy against untrusted applications by running Prepose code in a trusted core, and only interacting with applications via gesture events. Prepose lowers the cost of developing new gesture recognizers by exposing a range of primitives to developers that can capture many different gestures. Further, Prepose is designed to enable static analysis using SMT solvers, allowing the system to check security and privacy properties before running a gesture recognizer. We demonstrate that Prepose is expressive by creating novel gesture recognizers for 28 gestures in three representative domains: physical therapy, tai-chi, and ballet. We further show that matching user motions against Prepose gestures is efficient, by measuring on traces obtained from Microsoft Kinect runs. Because of the privacy-sensitive nature of always on Kinect sensors, we have designed the Prepose language to be analyzable: we enable security and privacy assurance through precise static analysis. In Prepose, we employ a sound static analysis that uses an SMT solver (Z3), something that works well on Prepose but would be hardly possible for a general-purpose language. We demonstrate that static analysis of Prepose code is efficient and investigate how analysis time scales with the complexity of gestures. Our Z3-based approach scales well in practice: safety checking is under 0.5 seconds per gesture; average validity checking time is only 188 ms; lastly, for 97% of the cases, the conflict detection time is below 5 seconds, with only one query taking longer than 15 seconds.

A Generic Framework for Distributed Deep Neural Networks over the Cloud, the Edge, and End Devices
A Generic Framework for Distributed Deep Neural Networks over the Cloud, the Edge, and End Devices
]]>
<![CDATA[9. Line of Business Scenarios: Dependency Injection]]>asvegah.github.io//lobs-di/5fa59b0736892200a25208bfFri, 06 Nov 2020 20:32:20 GMT

Dependency Injection (DI) is a design pattern to instantiate loosely coupled classes. It's also the fifth statement of the SOLID principles:

One should depend upon abstractions, [not] concretions

The best way to understand it is by a simple example of how to use it. The following code uses DI to implement loose coupling:

public interface IClass2 
{
}

public class Class2 : IClass2
{
}

public class Class1
{
    public readonly IClass2 _class2;
 
    public Class1():this(DependencyFactory.Resolve<IClass2>())
    {
    }
 
    public Class1(IClass2 class2)
    {
        _class2 = class2;
    }
} 

This is what we know when we have a look a little closer to this class:

  • Class1 needs an interface IClass2 to work.
  • Class1 doesn't know which class is implementing the interface IClass2 and how it was initialized.
  • If we would want to test Class1, we don't need the Class2 that implements the interface IClass2, we just need a mock of this interface.

Advantages of using DI

There are 2 important reasons to use DI:

  • Unit Testing: DI enables you to replace complex dependencies, such as databases, with mocked implementations of those dependencies. This allows you to completely isolate the code that is being testing.
  • Validation/Exception Management: DI allows you to inject additional code between the dependencies. For example, it is possible to inject exception management logic or validation logic, which means that the developer no longer needs to write this logic for every class.

Dependency Injection and ServiceLocator

We are using DI to decoupled our services from the ViewModels and encourage unit testing best practices at the same time. The DI framework we are using in our app is the new Microsoft.Extensions.DependencyInjection created by Microsoft for dotnet core.

Because the application allows opening new windows to permit the user work separately from the rest of the app, we have to be careful when we define the registration of our Services or ViewModels. We have created the ServiceLocator class to solve this problem:

  • Multiple Service Locators: We need multiple service locators when we open a new window, to avoid re-utilization of the same services in different contexts.
static private readonly ConcurrentDictionary<int, ServiceLocator> _serviceLocators = new ConcurrentDictionary<int, ServiceLocator>();

static public ServiceLocator Current
{
    get
    {
        int currentViewId = ApplicationView.GetForCurrentView().Id;
        return _serviceLocators.GetOrAdd(currentViewId, key => new ServiceLocator());
    }
}
  • Register Services and ViewModels: This is done in the Configure method. We can divide the registration depending on the way the service will be resolve.
static public void Configure(IServiceCollection serviceCollection)
{
    serviceCollection.AddSingleton<IDataServiceFactory, DataServiceFactory>();
    serviceCollection.AddSingleton<ILookupTables, LookupTables>();
    serviceCollection.AddSingleton<ICustomerService, CustomerService>();
    serviceCollection.AddSingleton<IOrderService, OrderService>();
    ...
    
    serviceCollection.AddScoped<IContextService, ContextService>();
    serviceCollection.AddScoped<INavigationService, NavigationService>();
    serviceCollection.AddScoped<IDialogService, DialogService>();
    serviceCollection.AddScoped<ICommonServices, CommonServices>();

    serviceCollection.AddTransient<ShellViewModel>();
    serviceCollection.AddTransient<MainShellViewModel>();
    ...
    ...

    _rootServiceProvider = serviceCollection.BuildServiceProvider();
}
Resolved as Services
Singleton: Same service every time we resolve it Example of these type of services are: IDataServiceFactory, ILookupTables, IMessageService, ILogService, etc
Scoped: A new instance of the service will ve returned if we are in a different scope, i.e. when a new window is opened Example of these type of services are: IContextService, INavigationService, IDialogService and ICommonServices
Transient: A new version of the service is returned every time we request for it All the ViewModels of the app are registered as Transient
]]>
<![CDATA[8. Line of Business Scenarios: Services]]>asvegah.github.io//lobs-services/5fa599a036892200a25208aeFri, 06 Nov 2020 20:31:45 GMT

View-models make use of Services to execute the operations requested by the user, such as create, update or retrieve a list of customers or products. View-models also make use of Services to log the user activity, show dialogs or display a text in the status-bar by sending a message to the shell view.

Services contains the core functionality of the application. We distinguish two kinds of services:

  • Application Services – implement core functionality needed by the infrastructure of the application. This functionality in independent of the business of the application and can be reused in other solutions. Examples of application services are Navigation Service or Message Service that can be reused for any other application.
  • Domain Services (or Business Services) – implements the functionality specific for the business of the application. Examples of domain services are Customer Services or Product Services that are specific for a product management application.

The following diagram shows the two group of services used in this application:

ovw-services-groups-2

Application Services

Here is a brief description of the Application Services used in this application:

Service Description
Navigation Service Expose the functionality to navigate back and forward to a different view. It also offers the possibility to open a view in a new window.
Message Service Enables communication between different components of the application without having to know anything about each other. The communication between components are based on a publishers-subscribers pattern.
Log Service Offers the methods to write logs to a local repository to keep track of the user activity for debugging or auditing purposes.
Login Service Implements the authentication and authorization mechanism to access the application.
Dialog Service Offers methods to display a dialog message to show information or ask for confirmation.
IFilePickerService Allows the application access to the file system with a dialog to select a file
ISettingsService Stores and provides configuration and setting values needed by the application
Context Service Exposes properties and methods related to the current execution context. This service is used internally to manage the execution in a multi-window environment where each window is executed in a different thread.

Domain Services

The domain services in this application offer CRUD operations (Create, Read, Update, Delete) over the business entities. We have a specific service for Customers, Orders, OrderItems and Products.

To see the common methods used in these services, let’s examine the Customer service:

  • GetCustomer(id) – get a single customer by its id.
  • GetCustomers(request) – get a collection of customers using the request parameter.
  • GetCustomers(skip, take, request) – same as GetCustomers(request) but returns only ‘take’ number of items starting from the ‘skip’ parameter.
  • GetCustomersCount(request) – return the number of Customers using the request parameter.
  • UpdateCustomer(customer) – update or create a new Customer with the values contained in the customer parameter.
  • DeleteCustomer(customer) – delete the Customer specified by the customer parameter.

There is also a LookupTables Service used to retrieve information for common Tables such as Categories or CountryCodes. This service is used, for example, to get the name of a country by its code, or the tax rate for a specific tax type.

Since we are using MVVM, and the ViewModel-First approach, we will use a Navigation Service abstraction to facilitate the ViewModel-based navigation.

This kind of navigation, oposed to View-based navigation, is the navigation that uses a ViewModel as the subject that determines the navigation. The View isn't specified explicitly. Instead, there is a mechanism to associate each ViewModel with its corresponding View. This is where our Navigation Service comes in. It will perform the navigation itself, but also the will glue the ViewModel with its View.

INavigationService

This contract is in charge of the Navigation of the app. The service is agnostic of the platform that is going to use it and that's why is located in the App.ViewModels project.

Let's take a look to the interface:

public interface INavigationService
{
    bool IsMainView { get; }

    bool CanGoBack { get; }

    void Initialize(object frame);

    bool Navigate<TViewModel>(object parameter = null);
    bool Navigate(Type viewModelType, object parameter = null);

    Task<int> CreateNewViewAsync<TViewModel>(object parameter = null);
    Task<int> CreateNewViewAsync(Type viewModelType, object parameter = null);

    void GoBack();

    Task CloseViewAsync();
}

Let's review each one of these methods:

Method Usage
Initialize This method is needed in order to initialize the Navigation Service
Navigate There are 2 overloads of this Method. Both the Type of the destination ViewModel is needed plus some arguments
CreateNewViewAsync The app example, allow us to create a View in a new Window. There are also 2 overloads of this method, and both need the target ViewModel to load
GoBack Navigate back in the stack
CloseViewAsync Close the actual Window

The Navigation Service sits between the View and the ViewModel. As the Navigate method takes the type of the ViewModel, our implementation will have to find the View that is associated with it. Let's review now how this interface is implemented.

INavigationService implementation

The interface is implemented in the App.Application project by the NavigationService class.

View Lookup

The Navigation Service will need a mechanism to associate Views to ViewModels. In our implementation, this is done using an internal dictionary.

Whenever the Navigate method is called, this dictionary will be queried for the Type of the View that corresponds to the ViewModel.

Let's take a look to the implementation of the Navigate method:

public bool Navigate(Type viewModelType, object parameter = null)
{
    if (Frame == null)
    {
        throw new InvalidOperationException("Navigation frame not initialized.");
    }

    return Frame.Navigate(GetView(viewModelType), parameter);
}

In the first place, we're checking whether the Frame is null. The Frame property is the object that will perform the navigation at the UI side. It's usually set at the very beginning of the execution. In our application, inside the ShellView.InitializeNavigation() method, where the service is resolved.

In the second place, where are telling the Frame to navigate to the View associated to viewModelType. The lookup is done in GetView:

static public Type GetView(Type viewModel)
{
    if (_viewModelMap.TryGetValue(viewModel, out Type view))
    {
        return view;
    }
    throw new InvalidOperationException($"View not registered for ViewModel '{viewModel.FullName}'");
}

View Registration

In order to know the association between a ViewModel and its View, some kind of registration is needed. For this effect, we will expose a method in our implementation:

static public void Register<TViewModel, TView>() where TView : Page
{
    if (!_viewModelMap.TryAdd(typeof(TViewModel), typeof(TView)))
    {
        throw new InvalidOperationException($"ViewModel already registered '{typeof(TViewModel).FullName}'");
    }
}

It just adds the type of the ViewModel and the type of the View in a common dictionary.

The registration is usually done at the beginning of the execution, so all entries are available as soon as possible. In our application, you can locate the registration in the Startup class, inside the ConfigureNavigation method.

private static void ConfigureNavigation()
{
    NavigationService.Register<ShellViewModel, ShellView>();
    NavigationService.Register<MainShellViewModel, MainShellView>();

    NavigationService.Register<DashboardViewModel, DashboardView>();
	...
}

Additional functionalities

In our implementation, there are a few additional properties and methods to enable more advanced navigation scenarios, like to pop a new Window or close it programmatically.

Opening new Windows

In our application, there are scenarios where we will feature multitasking by opening new Windows. It's provided by the CreateNewViewAsync methods.

Also, we made the functionality symmetrical by exposing the CloseViewAsync method, that is very easy to implement thanks to the ApplicationViewSwitcher static class.

public async Task CloseViewAsync()
{
    int currentId = ApplicationView.GetForCurrentView().Id;
    await ApplicationViewSwitcher.SwitchAsync(MainViewId, currentId, ApplicationViewSwitchingOptions.ConsolidateViews);
}

Message Service

Another service that it's important to know is the one represented by the interface IMessageService. The mission of this service is to send messages between decoupled components (Views, ViewModels, etc), and once the subscriber receive the message, react to it executing some action. This pattern is known as the Event Aggregator Pattern:

eventaggregator-1

The IMessageService exposes the following methods:

public interface IMessageService
{
    void Subscribe<TSender>(object target, Action<TSender, string, object> action) where TSender : class;
    void Subscribe<TSender, TArgs>(object target, Action<TSender, string, TArgs> action) where TSender : class;

    void Unsubscribe(object target);
    void Unsubscribe<TSender>(object target) where TSender : class;
    void Unsubscribe<TSender, TArgs>(object target) where TSender : class;

    void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class;
}
Methods Description
Subscribe This is used to subscribe your class to a specific event. When we subscribe to an event, we need to indicate the sender (TSender), the target object, and the action to execute when the message is received
Unsubscribe Unsubscribe an event already registered. It's important to unsubscribe events in order to avoid memory leaks
Send Communicate to subscriber that a specific event has occurred. When we send a message we are passing the sender (TSender), the message that identifies the event occurred (message) and additional arguments when necessary

How to use it

The best thing to understand how it works is to check how it's being used in the App. For example, lets see how the CustomersViewModel loads the detail of a Customer, when it is selected from the List of Customers. In this process the following actors are involved:

  • CustomerListViewModel: When an element of the list is selected, this ViewModel will use the IMessageService to send to the Customer selected to the possible subscribers.
private TModel _selectedItem = default(TModel);
public TModel SelectedItem
{
    get => _selectedItem;
    set
    {
        if (Set(ref _selectedItem, value))
        {
            if (!IsMultipleSelection)
            {
                MessageService.Send(this, "ItemSelected", _selectedItem);
            }
        }
    }
}
  • CustomersViewModel: This ViewModel will Subcribe to the Customers list ItemSelected event. It will also unsubscribe from this event when necessary.
public void Subscribe()
{
    MessageService.Subscribe<CustomerListViewModel>(this, OnMessage);
    CustomerList.Subscribe();
    CustomerDetails.Subscribe();
    CustomerOrders.Subscribe();
}

private async void OnMessage(CustomerListViewModel viewModel, string message, object args)
{
    if (viewModel == CustomerList && message == "ItemSelected")
    {
        await ContextService.RunAsync(() =>
        {
            OnItemSelected();
        });
    }
}

private async void OnItemSelected()
{
    if (CustomerDetails.IsEditMode)
    {
        StatusReady();
        CustomerDetails.CancelEdit();
    }
    CustomerOrders.IsMultipleSelection = false;
    var selected = CustomerList.SelectedItem;
    if (!CustomerList.IsMultipleSelection)
    {
        if (selected != null && !selected.IsEmpty)
        {
            await PopulateDetails(selected);
            await PopulateOrders(selected);
        }
    }
    CustomerDetails.Item = selected;
}

We are executing the OnItemSelected method when we received from the CustomerListViewModel a message ItemSelected.

Finally, as we previously mentioned, it's important to control the subscriptions and unsubcriptions of the events to prevent memory leaks. We will do this overriding the events OnNavigatedTo and OnNavigatingFrommethods of our Views:

protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    ViewModel.Subscribe();
    await ViewModel.LoadAsync(e.Parameter as CustomerListArgs);
}

protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
    ViewModel.Unload();
    ViewModel.Unsubscribe();
}

Message Service Implementation

The purpouse of this section is to show how an Event Aggregator Pattern can be implemented. The key to unsderstand the whole process are the Subscriber and Subscriptions internal classes.

Subscriptions class

This class is just a Diccionary of actions associated with a Type. We will use it to store the action to execute when a Subscriber receives a message.

class Subscriptions
{
    private Dictionary<Type, Delegate> _subscriptions = null;

    public Subscriptions()
    {
        _subscriptions = new Dictionary<Type, Delegate>();
    }

    public bool IsEmpty => _subscriptions.Count == 0;

    public void AddSubscription<TSender, TArgs>(Action<TSender, string, TArgs> action)
    {
        _subscriptions.Add(typeof(TArgs), action);
    }

    public void RemoveSubscription<TArgs>()
    {
        _subscriptions.Remove(typeof(TArgs));
    }

    public void TryInvoke<TArgs>(object sender, string message, TArgs args)
    {
        var argsType = typeof(TArgs);
        foreach (var keyValue in _subscriptions.Where(r => r.Key.IsAssignableFrom(argsType)))
        {
            var action = keyValue.Value;
            action?.DynamicInvoke(sender, message, args);
        }
    }
}

Subscriber class

The Subscriber class is the responsable of:

  • Container of the subscriptions defined for a specific Type.
  • Create the WeakReference between the Publisher and the Subscriber.
  • Unregister subscriptions.
  • Invoke the action to execute when the subscriber receives a message.
class Subscriber
{
    private WeakReference _reference = null;

    private Dictionary<Type, Subscriptions> _subscriptions;

    public Subscriber(object target)
    {
        _reference = new WeakReference(target);
        _subscriptions = new Dictionary<Type, Subscriptions>();
    }

    public object Target => _reference.Target;

    public bool IsEmpty => _subscriptions.Count == 0;

    public void AddSubscription<TSender, TArgs>(Action<TSender, string, TArgs> action)
    {
        if (!_subscriptions.TryGetValue(typeof(TSender), out Subscriptions subscriptions))
        {
            subscriptions = new Subscriptions();
            _subscriptions.Add(typeof(TSender), subscriptions);
        }
        subscriptions.AddSubscription(action);
    }

    public void RemoveSubscription<TSender>()
    {
        _subscriptions.Remove(typeof(TSender));
    }
    public void RemoveSubscription<TSender, TArgs>()
    {
        if (_subscriptions.TryGetValue(typeof(TSender), out Subscriptions subscriptions))
        {
            subscriptions.RemoveSubscription<TArgs>();
            if (subscriptions.IsEmpty)
            {
                _subscriptions.Remove(typeof(TSender));
            }
        }
    }

    public void TryInvoke<TArgs>(object sender, string message, TArgs args)
    {
        var target = _reference.Target;
        if (_reference.IsAlive)
        {
            var senderType = sender.GetType();
            foreach (var keyValue in _subscriptions.Where(r => r.Key.IsAssignableFrom(senderType)))
            {
                var subscriptions = keyValue.Value;
                subscriptions.TryInvoke(sender, message, args);
            }
        }
    }
}

IMessageService implementation

Once we understand how the internal classes Subscriptions and Subscriber work, the implementation of the IMessageService becomes much simplier. These are the implementation of main three methods of IMessageService:

  • Subscribe: We are just simply creating a new Subscriber in case it wasn't already defined, and associate an Action to be executed.
public void Subscribe<TSender, TArgs>(object target, Action<TSender, string, TArgs> action) where TSender : class
{
    if (target == null)
        throw new ArgumentNullException(nameof(target));
    if (action == null)
        throw new ArgumentNullException(nameof(action));

    lock (_sync)
    {
        var subscriber = _subscribers.Where(r => r.Target == target).FirstOrDefault();
        if (subscriber == null)
        {
            subscriber = new Subscriber(target);
            _subscribers.Add(subscriber);
        }
        subscriber.AddSubscription<TSender, TArgs>(action);
    }
}
  • Unsubscribe: Just remove a subscription for the collection.
public void Unsubscribe(object target)
{
    if (target == null)
        throw new ArgumentNullException(nameof(target));

    lock (_sync)
    {
        var subscriber = _subscribers.Where(r => r.Target == target).FirstOrDefault();
        if (subscriber != null)
        {
            _subscribers.Remove(subscriber);
        }
    }
}
  • Send: Execute the actions associated to Subscribers when a specific event occurs.
public void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class
{
    if (sender == null)
        throw new ArgumentNullException(nameof(sender));

    foreach (var subscriber in GetSubscribersSnapshot())
    {
        // Avoid sending message to self
        if (subscriber.Target != sender)
        {
            subscriber.TryInvoke(sender, message, args);
        }
    }
}

Log Service

Allows for top exception and optionally all the InnerExceptions Logging

  • WriteAsync: Logs the top exception when MustExploreDeepExceptions property in the class VirtualCollection is false (by default) or the top Exception and all InnerExceptions when true. MustExploreDeepExceptions is optionally set in the constructor:
VirtualCollection(ILogService logService, int rangeSize = 16, bool mustExploreDeepExceptions=false)


// log top Exception only
WriteAsync(LogType type, string source, string action, string message, string description)


// log top Exception and all Inner ones 
WriteAsync(LogType type, string source, string action, Exception ex)
]]>
<![CDATA[7. Line of Business Scenarios: Model-View-ViewModel]]>asvegah.github.io//lobs-mvvm/5fa5981636892200a2520893Fri, 06 Nov 2020 20:29:59 GMT

The UWP developer experience typically involves creating a user interface in XAML, and then adding code-behind that operates on the user interface. As apps are modified, and grow in size and scope, complex maintenance issues can arise. These issues include the tight coupling between the UI controls and the business logic, which increases the cost of making UI modifications, and the difficulty
of unit testing such code.

The Model-View-ViewModel (MVVM) pattern helps to cleanly separate the business and presentation logic of an application from its user interface (UI).

Maintaining a clean separation between application logic and the UI helps to address numerous development issues and can make an application easier to test, maintain, and evolve. It can also greatly improve code re-use opportunities and allows developers and UI designers to more easily collaborate when developing their respective parts of an app.

The MVVM Pattern

There are three core components in the MVVM pattern: the model, the view, and the view model. Each serves a distinct purpose. Figure 1-1 shows the relationships between the three components.

mvvm

  • Model: The data layer and the business logic of our app. It is also referred to as the domain object.

  • View: User interface.

  • View Model: Presentation Logic. Its role is to intermediate between the Model and the View. The View-Model contains the state of the view and communicates with it through Data Binding, Commands and Notifications.

In addition to understanding the responsibilities of each components, it's also important to understand how they interact with each other. At a high level, the view "knows about" the view model, and the view model "knows about" the model, but the model is unaware of the view model, and the view model is unaware of the view. Therefore, the view model isolates the view from the model, and allows the model to evolve independently of the view.

The benefits of using the MVVM pattern are as follows:

  • The separation the user interface from the rest of the code means that designers can focus on the view, while developers can work
    on the view model and model components.

  • Developers can create unit tests for the view model and the model, without using the view. The unit tests for the view model can exercise exactly the same functionality as used by the view.

  • The app UI can be redesigned without touching the code, provided that the view is implemented entirely in XAML. Therefore, a new version of the view should work with the existing view model.

  • The View-Models and the Model component could be re-use in order to develop other versions of the app or the same app for other platforms.

The following sections discuss in detail the responsibilities of each of the component of the MVVM pattern.

Model

Model classes are non-visual classes that encapsulate the app's data. Therefore, the model can be thought of as representing the app's domain model, which usually includes a data model along with business and validation logic. Examples of model objects include data transfer objects (DTOs), Plain Old CLR Objects (POCOs), and generated entity and proxy objects.

Model classes are typically used in conjunction with services or repositories that encapsulate data access and caching.

View

The view is responsible for defining the structure, layout, and appearance of what the user sees on screen. Ideally, each view is defined in XAML, with a limited code-behind that does not contain business logic. However, in some cases, the code-behind might contain UI logic that implements visual behavior that is difficult to express in XAML, such as animations.

Tip: Avoid enabling and disabling UI elements in the code-behind

Ensure that view models are responsible for defining logical state changes that affect some aspects of the view's display, such as whether a command is available, or an indication that an operation is pending. Therefore, enable and disable UI elements by binding to view model properties, rather than enabling and disabling them in code-behind.

View Model

The view model implements properties and commands to which the view can data bind to, and notifies the view of any state changes through change notification events. The properties and commands that the view model provides define the functionality to be offered by the UI, but the view determines how that functionality is to be displayed.

Tip: Keep the UI responsive with asynchronous operations

UWP apps should keep the UI thread unblocked to improve the user's perception of
performance. Therefore, in the view model, use asynchronous methods for I/O operations and raise events to asynchronously notify views of property changes.

The view model is also responsible for coordinating the view's interactions with any model classes that are required. There's typically a one-to-many relationship between the view model and the model classes.

Each view model provides data from a model in a form that the view can easily consume. To accomplish this, the view model sometimes performs data conversion. Placing this data conversion in the view model is a good idea because it provides properties that the view can bind to. For example, the view model might combine the values of two properties to make it easier for display by the view, or maybe the view doesn't really need to display the whole model's data and only part of the model will be offered to the view by the View Model.

Tip: Centralize data conversions in a conversion layer

It's also possible to use converters as a separate data conversion layer that sits between the view model and the view. This can be necessary, for example, when data requires special formatting that the view model doesn't provide.

For collections, the view-friendly ObservableCollection<T> is provided. This collection implements collection changed notification, relieving the developer from having to implement the INotifyCollectionChanged interface on collections.

View First vs ViewModel First

View First / ViewModel First refers to whether you build the app top down or bottom up.

  • View first: The Views is the first thing to implement during development and then a ViewModel that matches. The development is focused on creating the Views before the ViewModels and therefore the navigation between them is a responsability of the Views.

  • View Model first: The ViewModel is the first element created during development and then the View that matches. The ViewModel is responsable of creating the View associated with it. We will also need to create an abstraction of the Navigation between Views, and manage this Navigation in our ViewModels (usually through an IoC container or a Service Locator pattern).

Binding object

Binding object is the piece that transfers data values from the source to the target, and optionally from the target back to the source.

In the traditional {Binding} approach, the ViewModel has to be associated to the DataContext dependency property that is exposed by the View. For more information check section: Windows 10 Binding.

Data Binding

The Data Binding is the mechanisism in place to communicate the View with the View Model. It relates 2 properties keeping them syncronized.

In order for the view model to participate in data binding with the view, its properties must raise the PropertyChanged event. View models satisfy this requirement by implementing the INotifyPropertyChanged interface, and raising the PropertyChanged event when a property is changed.

This is an example of how to implement the INotifyPropertyChanged interface:

public class ModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool Set<T>(ref T field, T newValue = default(T), [CallerMemberName] string propertyName = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, newValue))
        {
            field = newValue;
            NotifyPropertyChanged(propertyName);
            return true;
        }
        return false;
    }

    protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Types of Bindings.

We can differentiate 3 types of bindings:

One-Time Binding:

The value of the View Model property will be informed to the View just one time, normally when the View is being loaded. There are two ways to implement this time of binding:

  • Creating a simple property in the View Model and bind the property to the View:
// View Model property
public string Name { get; set; }
<!--XAML binding-->
<TextBlock Text="{x:Bind Name}" />
  • Creating a property in the View Model that will indicate changes raising the PropretyChanged event, and indicating the binding mode OneTime in XAML:
// View Model property
private string _name;
public string Name
{
    get => _name;
    set => Set(ref _name, value);
}
<!--XAML binding-->
<TextBlock Text="{x:Bind Name, Mode=OneTime}" />

One-Way Binding:

The value of the View Model property can change more than one time, and it will update the View. If we change the target View property, changes won't be reflected in the View Model.

// View Model property
private string _name;
public string Name
{
    get => _name;
    set => Set(ref _name, value);
}
<!--XAML binding-->
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />

Two-Way Binding:

The value of the View Model property can change more than one time, it will update the View and can be updated back by changing the target View property.

// View Model property
private string _name;
public string Name
{
    get => _name;
    set => Set(ref _name, value);
}
<!--XAML binding-->
<TextBlock Text="{x:Bind Name, Mode=TwoWay}" />

Commands

There's another method of communication between the View and View Model: the Commands.

Commands are implementation of the interface ICommand of the .Net framework, and are used to bind view "event handlers" to the View Model. For example the event click of a Button control.

One of the big problems with event handlers is that they can create a tight coupling between the instance that exposes the event and the instance that subscribes to it. This is a frequent cause for memory leaks in .NET.

When we use a Command to expose the “event handler” and bind the UI element to that Command using data-binding, we are creating a loosely coupled relation and therefore preventing possible memory leaks.

Let see how to use them:

// Command defined in our ViewModel
public ICommand EditCommand => new RelayCommand(Edit);
private void Edit()
{
    BeginEdit();
}
<!--Binding the Commands in XAML-->
<controls:Details x:Name="details"
            DetailsContent="{x:Bind ViewModel}"
            DetailsTemplate="{StaticResource DetailsTemplate}"
            IsEditMode="{x:Bind ViewModel.IsEditMode, Mode=OneWay}"
            EditCommand="{x:Bind ViewModel.EditCommand}"
            DeleteCommand="{x:Bind ViewModel.DeleteCommand}"
            SaveCommand="{x:Bind ViewModel.SaveCommand}"
            CancelCommand="{x:Bind ViewModel.CancelCommand}" />

Summary

MVVM arquitecture is the most extended practice developing Windows 10 enterprise apps. It helps to cleanly separate the business and presentation logic of an application from its user interface (UI). Maintaining a clean separation between application logic and the UI helps to address numerous development issues and can make an application easier to test, maintain, and evolve. It can also greatly improve code re-use opportunities and allows developers and UI designers to more easily collaborate when developing their respective parts of an app.

]]>
<![CDATA[6. Line of Business Scenarios: Data Access]]>asvegah.github.io//lobs-da/5fa571d436892200a25207e4Fri, 06 Nov 2020 20:26:45 GMT

The data we use in the App is located in a SQLServer local database and we are using Entity Framework Core to manage the data access.

We will explain the arquitecture of the .NET Standard project Data included in the app. This project holds the data access logic of the app and it's a cross-platform library that can be shared for example with a Xamarin mobile app, a WPF desktop app, etc.

Let's explain Entity Framework Core first and then we will review how the data access has been implemented in detail.

Entity Framework Core

Entity Framework Core is a lightweight, extensible, and cross-platform version of the popular Entity Framework data access technology.

Entity Framework is an object-relational mapper (ORM) that allow us to work with a database using .Net objects. Also it supports many database engines.

To use Entity Framwork Core we need to install the corresponding Nuget package throuh the Package Manager Console:

  • Install-Package Microsoft.EntityFrameworkCore
  • Install-Package Microsoft.EntityFrameworkCore.Sqlite
  • Install-Package Microsoft.EntityFrameworkCore.SqlServer

There are two ways to face a development with Entity Framework:

  • Code first: Code first is when you are facing a development creating the Models before the database. With Entity Framework you can generate migration files that will reflect every change you do in your models and it will be applied in the database. This is the approach we are using in the App.

code-first

  • Database first: In this case, you already have a database, and the changes you do in the data structure needs to be reflected in your Models code.

databasefirst

Models definitions

The database entities are represented in code through models. As we are implementing database first development, our model need to have the same fields that have been defined in the database. This is an example of one of the models we use in the app:

[Table("CountryCodes")]
public partial class CountryCode
{
    [MaxLength(2)]
    [Key]
    public string CountryCodeID { get; set; }

    [Required]
    [MaxLength(50)]
    public string Name { get; set; }
}

Let's explain now the Data Annotations attributes that we used to define our models.

Data Annotations

Data Annotations are used to represent certain restrictions that will be reflected in the database entities mapped with our models.

Data Annotations attributes are .NET attributes which can be applied on an entity class or properties to override default conventions in EF Core. These attributes are not only used in Entity Framework but they can also be used with ASP.NET MVC or data controls.

In the model defined above we can distinguish the folowing attirbutes:

  • [Table("CountryCodes")]: Entity class attribute to configure the corresponding table name and schema in the database.
  • [Key]: Property attribute to specify a key property in an entity and make the corresponding column a PrimaryKey column in the database.
  • [MaxLenght(2)]: Property attribute to specify the maximum string length allowed in the corresponding column in the database.
  • [Required]: Property attribute to specify that the corresponding column is a NotNull column in the database.

There's also available to use:

  • Timestamp: Can be applied to a property to specify the data type of the corresponding column in the database as rowversion.
  • ConcurrencyCheck: Can be applied to a property to specify that the corresponding column should be included in the optimistic concurrency check.
  • MinLength: Can be applied to a property to specify the minimum string length allowed in the corresponding column in the database.
  • StringLength: Can be applied to a property to specify the maximum string length allowed in the corresponding column in the database.
  • Column: Can be applied to a property to configure the corresponding column name, order and data type in the database.
  • ForeignKey: Can be applied to a property to mark it as a foreign key property.
  • NotMapped: Can be applied to a property or entity class which should be excluded from the model and should not generate a corresponding column or table in the database.
  • DatabaseGenerated: Can be applied to a property to configure how the underlying database should generate the value for the corresponding column e.g. identity, computed or none.
  • InverseProperty: Can be applied to a property to specify the inverse of a navigation property that represents the other end of the same relationship.

DbContext

DbContext is an important class in Entity Framework Core. It represent the bridge between our entity models previously explained and the database.

databasefirst

The Airhandlers CASS App uses SQLServer as the default data source, but is prepared to work with the DbContext of your prefference. There's a different derived class of DbContext for each source of data. This is the implementation for SQLite:

public class SQLServerDb : DbContext, IDataSource
{
    private string _connectionString = null;

    public SQLServerDb(string connectionString)
    {
        // TODO: Remove default connection string
        connectionString = connectionString ?? "Data Source=Airhandlers.db";
        _connectionString = connectionString;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite(_connectionString);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<SubCategory>().HasKey(e => new { e.CategoryID, e.SubCategoryID });
        modelBuilder.Entity<OrderItem>().HasKey(e => new { e.OrderID, e.OrderLine });
    }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<Product> Products { get; set; }
    public DbSet<Project> Projects { get; set; }
    public DbSet<Units> Units { get; set; }

    public DbSet<Category> Categories { get; set; }
    public DbSet<SubCategory> SubCategories { get; set; }

    public DbSet<CountryCode> CountryCodes { get; set; }
    public DbSet<ProspectType> ProspectType { get; set; }
    public DbSet<Price> Prices { get; set; }
    public DbSet<ProjectStatus> ProjectStatus { get; set; }
    public DbSet<Term> Term { get; set; }
}

The principal methods of the DbContext class are:

Method Usage
Entry Gets an DbEntityEntry for the given entity. The entry provides access to change tracking information and operations for the entity.
SaveChanges Executes INSERT, UPDATE and DELETE commands to the database for the entities with Added, Modified and Deleted state.
SaveChangesAsync Asynchronous method of SaveChanges()
Set Creates a DbSet that can be used to query and save instances of TEntity.
OnModelCreating Override this method to further configure the model that was discovered by convention from the entity types exposed in DbSet properties on your derived context.

Lifetime

The lifetime of the context begins when the context is declared and finishes when the context is disposed or when is garbage-collected. Therefore, the correct way to use it is with a using clause to make sure that the context is disposed after operate with the database:

using (var context = new SQLiteDb())
{
    ...
}

DbSet

A DbSet property is exposed in the DbContext class as follows:

public DbSet<Customer> Customers { get; set; }

What is does to expose the table Customers in a way that we are allowed to query its data using LinQ, and execute Inserts, Updates and Deletes over the corresponding table.

Most important methods of DbSet are:

Method Name Description
Add Adds the given entity to the context with the Added state. When the changes are saved, the entities in the Added states are inserted into the database. After the changes are saved, the object state changes to Unchanged. Example: dbcontext.Customers.Add(customerEntity)
Include Returns the included non-generic LINQ to Entities query against a DbContext. (Inherited from DbQuery) Example: var customerList = dbcontext.Customers.Include(s => s.Projects).ToList<Customer>();
Remove Marks the given entity as Deleted. When the changes are saved, the entity is deleted from the database. The entity must exist in the context in some other state before this method is called. Example: dbcontext.Customers.Remove(customerEntity);

Just accesing a DbSet property from our context, it's not executing a query over the database. The query is executed when:

  • It is enumerated by a foreach.
  • It is enumerated by a collection operation such as ToArray, ToDictionary, or ToList.
  • LINQ operators such as First or Any are specified in the outermost part of the query.
  • The following methods are called: the Load extension method on a DbSet, DbEntityEntry.Reload, and Database.ExecuteSqlCommand.

Commit changes to database

The way Entity Framework works is through a ChangeTracker. When you work with an entity, you can modify it, or insert a new one or delete it, and these changes will be recorded in the ChangeTracker but the changes won't be pushed to the database until the method SaveChanges is called:

This is how to use it:

public async Task<int> UpdateCustomerAsync(Customer customer)
{
    if (customer.CustomerID > 0)
    {
        _dataSource.Entry(customer).State = EntityState.Modified;
    }
    else
    {
        customer.CustomerID = UIDGenerator.Next();
        customer.CreatedOn = DateTime.UtcNow;
        _dataSource.Entry(customer).State = EntityState.Added;
    }
    customer.LastModifiedOn = DateTime.UtcNow;
    customer.SearchTerms = customer.BuildSearchTerms();
    int res = await _dataSource.SaveChangesAsync();
    return res;
}

Other considerations

In order to use Entity Framework properly, there are a few things that a developer needs to take into account:

  • Connections: By default, the context manages connections to the database. The context opens and closes connections as needed. For example, the context opens a connection to execute a query, and then closes the connection when all the result sets have been processed. There are cases when you want to have more control over when the connection opens and closes. For example, when working with SQL Server Compact, opening and closing the same connection is expensive. You can manage this process manually by using the Connection property.

  • Multithreading: The context is not thread safe. You can still create a multithreaded application as long as an instance of the same entity class is not tracked by multiple contexts at the same time.

One of the most important parts of the application is how we access to the different Data Sources and how this logic is decoupled from the rest of the app.

We will review in detail the App.Data project and how we use it in the application.

App.Data project

Since our data source is a relational database, we are using Entity Framework Core to manipulate the data and expose it.

We can split the project in three principal parts:

Data

The Data folder contains all the data models, representing each table of the database:

datamodels-1

These models will be our Data Transfer Objects or DTOs of our app.

IDataSource

This interface represents the database context. We will have a class implementing IDataSource for each context representing a database.

public interface IDataSource : IDisposable
{
    DbSet<CountryCode> CountryCodes { get; }
    DbSet<PaymentType> PaymentTypes { get; }
    DbSet<TaxType> TaxTypes { get; }
    DbSet<ProjectStatus> ProjectStatus { get; }
    DbSet<Shipper> Shippers { get; }

    DbSet<Customer> Customers { get; }
    DbSet<Project> Projects { get; }
    DbSet<Unit> Units { get; }
    DbSet<Product> Products { get; }

    EntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;

    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
}

We can find two implementations of this interface: SQLiteDb and SQLServerDb, representing two different databases that will share the same logic to access, manipulate and expose the data.

IDataService

The interface IDataService is used for access and manipulate data from the database.

public interface IDataService : IDisposable
{
    Task<Customer> GetCustomerAsync(long id);
    Task<IList<Customer>> GetCustomersAsync(int skip, int take, DataRequest<Customer> request);
    Task<IList<Customer>> GetCustomerKeysAsync(int skip, int take, DataRequest<Customer> request);
    Task<int> GetCustomersCountAsync(DataRequest<Customer> request);
    Task<int> UpdateCustomerAsync(Customer customer);
    Task<int> DeleteCustomersAsync(params Customer[] customers);

    Task<Project> GetProjectAsync(long id);
    Task<IList<Project>> GetProjectsAsync(int skip, int take, DataRequest<Project> request);
    Task<IList<Project>> GetProjectKeysAsync(int skip, int take, DataRequest<Project> request);
    Task<int> GetProjectsCountAsync(DataRequest<Project> request);
    Task<int> UpdateProjectAsync(Project Project);
    Task<int> DeleteProjectsAsync(params Project[] Projects);

    Task<ProjectItem> GetProjectItemAsync(long ProjectID, int ProjectLine);
    Task<IList<ProjectItem>> GetUnitsAsync(int skip, int take, DataRequest<ProjectItem> request);
    Task<IList<ProjectItem>> GetProjectItemKeysAsync(int skip, int take, DataRequest<ProjectItem> request);
    Task<int> GetUnitsCountAsync(DataRequest<ProjectItem> request);
    Task<int> UpdateProjectItemAsync(ProjectItem ProjectItem);
    Task<int> DeleteUnitsAsync(params ProjectItem[] Units);

    Task<Product> GetProductAsync(string id);
    Task<IList<Product>> GetProductsAsync(int skip, int take, DataRequest<Product> request);
    Task<IList<Product>> GetProductKeysAsync(int skip, int take, DataRequest<Product> request);
    Task<int> GetProductsCountAsync(DataRequest<Product> request);
    Task<int> UpdateProductAsync(Product product);
    Task<int> DeleteProductsAsync(params Product[] products);


    Task<IList<Category>> GetCategoriesAsync();
    Task<IList<CountryCode>> GetCountryCodesAsync();
    Task<IList<ProjectStatus>> GetProjectStatusAsync();
    Task<IList<PaymentType>> GetPaymentTypesAsync();
    Task<IList<Shipper>> GetShippersAsync();
    Task<IList<TaxType>> GetTaxTypesAsync();
}

Accessing the data from the app

Data Service Factory

The first thing we have to define in the app, it's the source of the data we are going to consume. The interface IDataServiceFactory is the one responsable of provide the inplementation of IDataService that we use in the app.

public interface IDataServiceFactory
{
    IDataService CreateDataService();
}

The possible data services to be used in the app are: SQLite and SQLServer, and they are defined in the following enum class:

public enum DataProviderType
{
    SQLite,
    SQLServer
}

To establish the Data Service to use, we just need to set the property DataProvider of the AppSettings class. By default, we are loading the SQLite data provider:

public DataProviderType DataProvider
{
    get => (DataProviderType)GetSettingsValue("DataProvider", (int)DataProviderType.SQLite);
    set => LocalSettings.Values["DataProvider"] = (int)value;
}

With the data provider defined, we can review now how we are accessing to the data from our ViewModels.

Data Services

We have additional services, one per functionality of the app located in the Services folder inside the App.ViewModels project:

data-services

Let's have a look a one of them:

public interface ICustomerService
{
    Task<CustomerModel> GetCustomerAsync(long id);
    Task<IList<CustomerModel>> GetCustomersAsync(DataRequest<Customer> request);
    Task<IList<CustomerModel>> GetCustomersAsync(int skip, int take, DataRequest<Customer> request);
    Task<int> GetCustomersCountAsync(DataRequest<Customer> request);

    Task<int> UpdateCustomerAsync(CustomerModel model);

    Task<int> DeleteCustomerAsync(CustomerModel model);
    Task<int> DeleteCustomerRangeAsync(int index, int length, DataRequest<Customer> request);
}

This contract is offering us all the possible operations that we can do when we work with Customers.

Also, these services are not only accessing the data source, they are also acting as mappers between our DTOs and the Models that we used to represent the data visually. There are some advantajes in mapping our DTOs to Models:

  • Not all the info of the DTO needs to be displayed in our views.
  • DTOs should be normally simple classes with no behaviour defined in Project to facilitate serialization.
  • The Models are the ones implementing the interface INotifyPropertyChanged, reducing complexity in our DTOs.

Let's check the inplementation of the ICustomerService:

public class CustomerService : ICustomerService
{
    public CustomerService(IDataServiceFactory dataServiceFactory)
    {
        DataServiceFactory = dataServiceFactory;
    }

    public IDataServiceFactory DataServiceFactory { get; }

    public async Task<CustomerModel> GetCustomerAsync(long id)
    {
        using (var dataService = DataServiceFactory.CreateDataService())
        {
            var item = await dataService.GetCustomerAsync(id);
            if (item != null)
            {
                return await CreateCustomerModelAsync(item, includeAllFields: true);
            }
            return null;
        }
    }
}

As you can see, we are using the IDataServiceFactory to get the IDataService interface to consult a customer information.

Finally, we just need to check how we are injecting this ICustomerService in the CustomerDetailViewModel class to check how all the pieces are connected:

public class CustomerDetailsViewModel : GenericDetailsViewModel<CustomerModel>
{
    public CustomerDetailsViewModel(ICustomerService customerService, ICommonServices commonServices) : base(commonServices)
    {
        CustomerService = customerService;
    }

    public ICustomerService CustomerService { get; }
}
]]>
<![CDATA[5. Line of Business Scenarios: Models]]>asvegah.github.io//lobs-models/5fa5724b36892200a25207eaFri, 06 Nov 2020 20:22:15 GMT

Models are another essential component in the MVVM architecture pattern. Model classes are non-visual classes that encapsulate the app's data. Therefore, the model can be thought as the representation of a domain object, which usually includes data and business validation logic.

In this application, a model wraps the data of a business object to better expose its properties to the view. In other words, the business object is the raw data received from the data source while the Model is a “view friendly” version, adapting or extending its properties for a better representation of the data.

For example, the Customer business object contains the properties “Name” and “Last Name” and the CustomerModel expose also the property “FullName” as a concatenation of these two properties to be used in the Customer details view.

In a more complex scenario, the Customer business object contains only the “CountryCode” while the CustomerModel also expose the “CountryName” property updated from a lookup table if the “CountryCode” changes if, for instance, the user select a new country in a ComboBox control.

The Model also helps decouple the business objects used in the data layer from the classes used in the presentation layer, so if a change is required in a business object schema, the application will be less affected.

All models inherit from the ObservableObject class. This class contains a typical implementation of the * INotifyPropertyChanged* interface, providing property change notifications, so an observer (usually the view) can update the representation of the model.

]]>
<![CDATA[4. Line of Business Scenarios: ViewModels]]>asvegah.github.io//lobs-vm/5fa5725e36892200a25207eeFri, 06 Nov 2020 20:00:35 GMT

In a MVVM architecture, the view-models turns into the main core of the app.

Views depend on view-models to present the information to the users and execute user actions, but view-models don’t need to know anything about the views. Applying this principle permits reusing the same view-model in different view implementations for different platforms.

In general, many of the properties and methods are common for all the different view-models so we use inheritance to reuse functionality.

ViewModelBase

This view-model is the base class for all view-models in the application and offers the following funcionality:

  • Resolves and exposes the common services that every view-model in the app uses, which are: IContextService, INavigationService, IMessageService, IDialogService and the ILogService.
  • It handles the Log common operations for all view-models in the app.
  • It's responsable of communicate the status of the view-model using the Message Service.

The following code is a simplified implementation of the ViewModelBase.

public class ViewModelBase : ObservableObject
{
    public ViewModelBase(ICommonServices commonServices)
    {
        ContextService = commonServices.ContextService;
        NavigationService = commonServices.NavigationService;
        MessageService = commonServices.MessageService;
        DialogService = commonServices.DialogService;
        LogService = commonServices.LogService;
    }

    public IContextService ContextService { get; }
    public INavigationService NavigationService { get; }
    public IMessageService MessageService { get; }
    public IDialogService DialogService { get; }
    public ILogService LogService { get; }

    virtual public string Title => String.Empty;

    public async void LogInformation(string source, string action, string message, string description)
    {
        await LogService.WriteAsync(LogType.Information, source, action, message, description);
    }

    public async void LogWarning(string source, string action, string message, string description)
    {
        await LogService.WriteAsync(LogType.Warning, source, action, message, description);
    }

    public async void LogError(string source, string action, string message, string description)
    {
        await LogService.WriteAsync(LogType.Error, source, action, message, description);
    }

    public void StatusReady()
    {
        MessageService.Send(this, "StatusMessage", "Ready");
    }
    public void StatusMessage(string message)
    {
        MessageService.Send(this, "StatusMessage", message);
    }
    public void StatusError(string message)
    {
        MessageService.Send(this, "StatusError", message);
    }
}

Domain Base ViewModels

We have defined 2 base view-models representing a common data structure to be displayed in then Inventory Sample app: List and Detail generic view-models.

GenericListViewModel

class GenericListViewModel<TModel> : ViewModelBase where TModel : ObservableObject

This base view-model handles the common operations of a List, like:

  • Filter the list of items.
  • Single and multiple item selection handling.
  • Refresh, New and Delete operations over the list of items.

This class contains abstract methods to be implemented by the concrete clasess like OnRefresh, OnNew or OnDeleteSelection.

GenericDetailsViewModel

class GenericDetailsViewModel<TModel> : ViewModelBase where TModel : ObservableObject, new()

This generic view-model puts focus on:

  • Display the detail of the model as ReadOnly or Edit mode.
  • Validates user input before saving changes.
  • Delete current item.

This class contains abstract methods to be implemented by the concrete clasess like SaveItem, DeleteItem or GetValidationConstraints.

]]>
<![CDATA[3. Line of Business Scenarios: Views]]>asvegah.github.io//lobs-views/5fa5727936892200a25207f2Fri, 06 Nov 2020 19:58:48 GMT

Views are essentially what the user sees on the screen to interact with the application.

Most of the information presented in this application is shown as a Master-Detail structure with a list of items on the top and the details of the selected item on the bottom. The details of an item may be a group of properties describing the item (i.e. Customer details) or a list of items related to the selected item (i.e. Customer orders).

The following image shows a Master-Details view in this application.

List-Detail

As you can see, the principal View is divided in two sections: A list with the customers of the app at the top, and the detail of the customer selected at the bottom. This is the XAML markup for that view:

<!--Customers-->
<Grid Grid.RowSpan="{x:Bind GetRowSpan(ViewModel.CustomerList.IsMultipleSelection), Mode=OneWay}">
    <controls:Section Header="{x:Bind ViewModel.CustomerList.Title, Mode=OneWay}" 
                        HeaderTemplate="{StaticResource ListHeaderTemplate}"
                        HeaderButtonGlyph="&#xE2B4;" 
                        HeaderButtonClick="OpenInNewView"
                        IsButtonVisible="{x:Bind ViewModel.IsMainView}">
        <views:CustomersList ViewModel="{x:Bind ViewModel.CustomerList}" />
    </controls:Section>
</Grid>

<!--Details-->
<Grid Grid.Row="1" 
        BorderBrush="LightGray"
        BorderThickness="0,1,0,0"
        Visibility="{x:Bind ViewModel.CustomerList.IsMultipleSelection, Mode=OneWay, Converter={StaticResource InverseBoolToVisibilityConverter}}">
    <controls:Section IsEnabled="{x:Bind ViewModel.CustomerDetails.IsEnabled, Mode=OneWay}" 
                        HeaderButtonGlyph="&#xE2B4;" 
                        HeaderButtonClick="OpenDetailsInNewView" 
                        Background="{StaticResource DetailsViewBackgroundColor}"
                        Visibility="{x:Bind ViewModel.CustomerDetails.IsDataAvailable, Mode=OneWay}">

        <Pivot x:Name="pivot">
            <PivotItem Header="Customer">
                <views:CustomersDetails ViewModel="{x:Bind ViewModel.CustomerDetails}" />
            </PivotItem>
            <PivotItem Header="Orders">
                <views:CustomersOrders ViewModel="{x:Bind ViewModel.CustomerOrders}" />
            </PivotItem>
        </Pivot>
    </controls:Section>

    <!--Empty Details-->
    <controls:Section Header="No item selected" 
                        Visibility="{x:Bind ViewModel.CustomerDetails.IsDataUnavailable, Mode=OneWay}" />
</Grid>

We can highlight two principal sections:

  • The list of customers:
<views:CustomersList ViewModel="{x:Bind ViewModel.CustomerList}" />
  • The detail of the customer selected:
<views:CustomersDetails ViewModel="{x:Bind ViewModel.CustomerDetails}" />

Lists

The lists in the App are defined in XAML as UserControls to be integrated in a principal View, and the ViewModel associated to this control are of the type GenericListViewModel<TModel>, and in the particular case of the customers:

public class CustomerListViewModel : <CustomerModel>

To know more about the GenericListViewModel check this section.

DataList control

The Datalist control is a generic UserControl that we'll use in all of our lists in the App. It is defined like so:

<UserControl
    x:Class="App.Controls.DataList"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:controls="using:App.Controls"
    mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="400">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <!--<RowDefinition Height="28"/>-->
        </Grid.RowDefinitions>

        <!--Toolbar-->
        <controls:ListToolbar
            DefaultCommands="{x:Bind DefaultCommands, Mode=OneWay}"
            NewLabel="{x:Bind NewLabel, Mode=OneWay}"
            Query="{x:Bind Query, Mode=TwoWay}"
            QuerySubmitted="OnQuerySubmitted"
            ToolbarMode="{x:Bind ToolbarMode, Mode=OneWay}"
            ButtonClick="OnToolbarClick" />

        <!--Header-->
        <ContentControl Grid.Row="1" 
                        ContentTemplate="{x:Bind HeaderTemplate}" 
                        HorizontalContentAlignment="Stretch" />

        <!--List Content-->
        <Grid Grid.Row="2">
            <ListView x:Name="listview"
                  DoubleTapped="OnDoubleTapped"
                  ItemsSource="{x:Bind ItemsSource, Mode=OneWay}"
                  ItemTemplate="{x:Bind ItemTemplate}"
                  ItemContainerStyle="{StaticResource RowItemStyle}"
                  SelectedItem="{x:Bind SelectedItem, Mode=TwoWay}"
                  SelectionMode="{x:Bind SelectionMode, Mode=OneWay}"
                  SelectionChanged="OnSelectionChanged"
                  Visibility="{x:Bind IsDataAvailable, Mode=OneWay}" />
            <TextBlock Margin="6" Text="{x:Bind DataUnavailableMessage, Mode=OneWay}" Visibility="{x:Bind IsDataUnavailable, Mode=OneWay}"/>
        </Grid>
    </Grid>
</UserControl>

There are 3 parts to define in every list:

  • The toolbar actions over the list, represented by the UserControl ListToolbar.
  • The header of the list.
  • And finally a ListView to display the data.

We can see how it's being used in the UserControl CustomersList.xaml:

<UserControl
    x:Class="App.Views.CustomersList"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:models="using:App.Models"
    xmlns:controls="using:App.Controls"
    mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="400">

    <UserControl.Resources>
        <DataTemplate x:Key="HeaderTemplate">
            <Grid BorderBrush="LightGray" BorderThickness="0,0,0,1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="40"/>
                    <ColumnDefinition Width="8*"/>
                    <ColumnDefinition Width="10*"/>
                    <ColumnDefinition Width="12*"/>
                    <ColumnDefinition Width="10*"/>
                    <ColumnDefinition Width="10*"/>
                    <ColumnDefinition Width="8*"/>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="1" Text="Customer ID" Style="{StaticResource ColumnHeaderStyle}" />
                <TextBlock Grid.Column="2" Text="Name" Style="{StaticResource ColumnHeaderStyle}" />
                <TextBlock Grid.Column="3" Text="eMail" Style="{StaticResource ColumnHeaderStyle}" />
                <TextBlock Grid.Column="4" Text="Phone" Style="{StaticResource ColumnHeaderStyle}" />
                <TextBlock Grid.Column="5" Text="Address" Style="{StaticResource ColumnHeaderStyle}" />
                <TextBlock Grid.Column="6" Text="Country" Style="{StaticResource ColumnHeaderStyle}" />
            </Grid>
        </DataTemplate>

        <DataTemplate x:Key="ItemTemplate" x:DataType="models:CustomerModel">
            <Grid BorderThickness="0,0,0,0" BorderBrush="LightGray" Height="32">
                <Grid Visibility="{x:Bind IsEmpty, Converter={StaticResource InverseBoolToVisibilityConverter}}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="40"/>
                        <ColumnDefinition Width="8*"/>
                        <ColumnDefinition Width="10*"/>
                        <ColumnDefinition Width="12*"/>
                        <ColumnDefinition Width="10*"/>
                        <ColumnDefinition Width="10*"/>
                        <ColumnDefinition Width="8*"/>
                    </Grid.ColumnDefinitions>
                    <Border Grid.Column="0" Margin="1" Padding="1">
                        <PersonPicture ProfilePicture="{x:Bind ThumbnailSource, Mode=OneWay, Converter={StaticResource ObjectToImageConverter}}" Width="28" Height="28" x:Phase="1" />
                    </Border>
                    <TextBlock Grid.Column="1" Text="{x:Bind CustomerID}" Style="{StaticResource ColumnValueStyle}" />
                    <TextBlock Grid.Column="2" Text="{x:Bind FullName, Mode=OneWay}" Style="{StaticResource ColumnValueStyle}" />
                    <TextBlock Grid.Column="3" Text="{x:Bind EmailAddress, Mode=OneWay}" Style="{StaticResource ColumnValueStyle}" />
                    <TextBlock Grid.Column="4" Text="{x:Bind Phone, Mode=OneWay}" Style="{StaticResource ColumnValueStyle}" />
                    <TextBlock Grid.Column="5" Text="{x:Bind AddressLine1, Mode=OneWay}" Style="{StaticResource ColumnValueStyle}" />
                    <TextBlock Grid.Column="6" Text="{x:Bind CountryName, Mode=OneWay}" Style="{StaticResource ColumnValueStyle}" />
                </Grid>
            </Grid>
        </DataTemplate>
    </UserControl.Resources>

    <controls:DataList ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}"
                       ItemSecondaryActionInvokedCommand="{x:Bind ViewModel.OpenInNewViewCommand}"
                       NewLabel="New Customer"
                       SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=TwoWay}"
                       HeaderTemplate="{StaticResource HeaderTemplate}"
                       ItemTemplate="{StaticResource ItemTemplate}"
                       IsMultipleSelection="{x:Bind ViewModel.IsMultipleSelection, Mode=TwoWay}"
                       ItemsCount="{x:Bind ViewModel.ItemsCount, Mode=OneWay}"
                       NewCommand="{x:Bind ViewModel.NewCommand}"
                       RefreshCommand="{x:Bind ViewModel.RefreshCommand}"
                       Query="{x:Bind ViewModel.Query, Mode=TwoWay}"
                       QuerySubmittedCommand="{x:Bind ViewModel.RefreshCommand}"
                       StartSelectionCommand="{x:Bind ViewModel.StartSelectionCommand}"
                       CancelSelectionCommand="{x:Bind ViewModel.CancelSelectionCommand}"
                       SelectItemsCommand="{x:Bind ViewModel.SelectItemsCommand}"
                       DeselectItemsCommand="{x:Bind ViewModel.DeselectItemsCommand}"
                       SelectRangesCommand="{x:Bind ViewModel.SelectRangesCommand}"
                       DeleteCommand="{x:Bind ViewModel.DeleteSelectionCommand}" />
</UserControl>

This is just 2 ItemTemplates, one for the header of the list and the template for the items, and the DataList control. Please note how the all data and oprations are binded to the control.

Detail

Similar to the list control, we will have a common control to display the details of a specific Domain Model, and in this case, the ViewModel associated to these controls will be the GenericDetailsViewModel<TModel>, and again in the case of the customer details will be:

public class CustomerDetailsViewModel : GenericDetailsViewModel<CustomerModel>

More about the GenericDetailsViewModel check this section.

Details control

The Details control it will be used to display the detail of a Domain Model

<UserControl
    x:Class="App.Controls.Details"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:controls="using:App.Controls"
    mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="400">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <controls:DetailToolbar ToolbarMode="{x:Bind ToolbarMode, Mode=OneWay}" DefaultCommands="{x:Bind DefaultCommands, Mode=OneWay}" ButtonClick="OnToolbarClick" />
        <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
            <ContentControl x:Name="container" Content="{x:Bind DetailsContent}" ContentTemplate="{x:Bind DetailsTemplate}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" />
        </ScrollViewer>
    </Grid>
</UserControl>

This control is defined by:

  • A toolbar with the operations over the Domain Model details.
  • And the details, which require the Content, i.e. the ViewModel, and a DetailsTemplate, which is the form to display the information.

An example of how it's used in the CustomerDetails.xaml control:

<UserControl
    x:Class="App.Views.CustomersDetails"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:controls="using:App.Controls"
    xmlns:views="using:App.Views"
    xmlns:viewmodels="using:App.ViewModels"
    mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="400">

    <UserControl.Resources>
        <DataTemplate x:Key="DetailsTemplate" x:DataType="viewmodels:CustomerDetailsViewModel">
            <controls:FluidGrid Margin="0,12,0,0" Columns="2" ColumnSpacing="12" RowSpacing="12">
                <controls:LabelTextBox Label="First Name" Text="{x:Bind EditableItem.FirstName, Mode=TwoWay}" />
                <controls:LabelTextBox Label="Last Name" Text="{x:Bind EditableItem.LastName, Mode=TwoWay}" />
                <controls:FluidGrid Columns="2" ColumnSpacing="6" RowSpacing="12" MinColumnWidth="60">
                    <controls:LabelTextBox Label="Middle Name" Text="{x:Bind EditableItem.MiddleName, Mode=TwoWay}" />
                    <controls:LabelTextBox Label="Sufix" Text="{x:Bind EditableItem.Suffix, Mode=TwoWay}" />
                </controls:FluidGrid>
                <controls:LabelTextBox Label="EMail Address" Text="{x:Bind EditableItem.EmailAddress, Mode=TwoWay}" />
                <controls:LabelTextBox Label="Phone" Text="{x:Bind EditableItem.Phone, Mode=TwoWay}" />
                <controls:LabelTextBox Label="Address" Text="{x:Bind EditableItem.AddressLine1, Mode=TwoWay}" />
                <controls:LabelTextBox Label="Postal Code" Text="{x:Bind EditableItem.PostalCode, Mode=TwoWay}" />
                <controls:LabelTextBox Label="City" Text="{x:Bind EditableItem.City, Mode=TwoWay}" />
                <controls:LabelTextBox Label="Region" Text="{x:Bind EditableItem.Region, Mode=TwoWay}" />
                <controls:LabelComboBox Label="Country" ItemsSource="{x:Bind LookupTables.CountryCodes}"
                                        SelectedValue="{x:Bind EditableItem.CountryCode, Mode=TwoWay}"
                                        SelectedValuePath="CountryCodeID" DisplayMemberPath="Name" />
            </controls:FluidGrid>
        </DataTemplate>
    </UserControl.Resources>

    <Grid ColumnSpacing="6" 
          Visibility="{x:Bind ViewModel.Item.IsEmpty, Mode=OneWay, Converter={StaticResource InverseBoolToVisibilityConverter}}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="260"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <Grid BorderThickness="0,0,1,0" BorderBrush="LightGray">
            <views:CustomersCard Margin="6,12" ViewModel="{x:Bind ViewModel}" Item="{x:Bind ViewModel.Item, Mode=OneWay}" />
        </Grid>

        <controls:Details x:Name="details" Grid.Column="1" Margin="6,0,0,0"
                          DetailsContent="{x:Bind ViewModel}"
                          DetailsTemplate="{StaticResource DetailsTemplate}"
                          IsEditMode="{x:Bind ViewModel.IsEditMode, Mode=OneWay}"
                          EditCommand="{x:Bind ViewModel.EditCommand}"
                          DeleteCommand="{x:Bind ViewModel.DeleteCommand}"
                          SaveCommand="{x:Bind ViewModel.SaveCommand}"
                          CancelCommand="{x:Bind ViewModel.CancelCommand}" />
    </Grid>
</UserControl>

Which is just a template representing the form with the Customer info, and the Details control with all the Data Binding and Commands.

]]>
<![CDATA[2. Line of Business Scenarios: Architecture Overview]]>asvegah.github.io//lobs-arch/5fa58bf836892200a2520843Fri, 06 Nov 2020 19:55:17 GMT

The software system is based on a MVVM architecture pattern to facilitate the separation of the user interface from the business logic of the application. You can read more details about the MVVM pattern in the MVVM section of this documentation.

The following diagram shows the different layers in the application.

ovw-layers

Views

Views are essentially what the user sees on the screen to interact with the application. Examples of Views in this application are: CustomersView, ProjectsView or ProductsView.

Views contains layers and controls to display the user interface. The layers organize the controls in the view, and the controls show the information to the user. All views depend on a view-model that manage the logic of the User Interface.

When you examine the code-behind of a view, you will notice that the first thing it does in the constructor is instantiate the view-model. Another thing to notice is the overridden of two methods: OnNavigatedTo and OnNavigatingFrom.

The OnNavigatedTo is executed when the user navigates to this view and is used to initialize the view-model with the parameters received in the navigation.

The OnNavigatingFrom is executed when the user navigates out of this view and is used to free the resources used by the view-model.

The following code shows a typical implementation of a view.

    public class CustomersView : Page
    {
        public CustomersView()
        {
            ViewModel = ServiceLocator.Current.GetService<CustomersViewModel>();
            InitializeComponent();
        }

        public CustomersViewModel ViewModel { get; }

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            ViewModel.Subscribe();
            await ViewModel.LoadAsync(e.Parameter as CustomerListArgs);
        }

        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            ViewModel.Unload();
            ViewModel.Unsubscribe();
        }
    }

In order to simplify the development and make the code more readable, views are subdivided in subviews.

For example, the CustomersView consists on the following subviews:

  • CustomersList – contains the list of customers and the controls to execute common actions over the collection of customers, such as: Add new customer, search customers or refresh customer list.
  • CustomersDetails – contains the details of a selected customer with input controls to enable the edition of the selected customer in the list. It also contains the common actions available for a customer, such as: Edit or delete.
  • CustomersCard – shows the main properties of the selected customer as a quick and read only view.
  • CustomersProjects – contains the list of orders associated to the selected customer.
  • CustomersView – is the top-level view containing all the subviews described before.

The following image shows the diferent subviews contained in the CustomersView.

ovw-views-subviews

Shell views

A Shell view is a special type of view. This view is the shell of a window and serves as a container for other views.

A Shell view contains a frame to enable the navigation between other views and a status bar on the bottom to notify messages to the user.

Anytime we open a new window in the application, a new Shell view is created, and the content is initialized using the frame to navigate to the specific view.

MainShell view

The main window uses a specialized version of a Shell view: the MainShell view.

The MainShell view is like another Shell view but it contains a navigation pane on the left to offer the user different options to navigate to.

When you execute the application and log in, the MainShell view is created. There can be multiple Shell views in the application, but only one MainShell view.

The following image identifies the different elements in the MainShell view.

ovw-views-mainshell

ViewModels

View-models are another essential part in the MVVM architecture pattern. You can read more details about the concepts of the View-model in the MVVM – View Model section of this documentation.

The view-model contains the UI logic of the application, so it is reasonable to believe that there will be, at least, on view-model for each View. In some cases, where the view requires more complexity, more than one view-model are used for a single view.

To see how view-models are related to views, let’s see an example with the Customers view. The Customers view is associated with a Customers view-model and this view-model references two other view-models: CustomersList view-model and CustomersDetails view-model.

The following diagram shows this relationship.

ovw-viewmodels-relationships

ViewModel Hierarchy

Most of the properties and methods are common for all the different view-models so we use inheritance to reuse functionality.

The following diagram shows the view-models hierarchy.

ovw-viewmodels-hierarchy

ObservableObject

The ObservableObject is the base class for all the objects that need to notify that a property has changed. This class contains a typical implementation of the INotifyPropertyChanged interface.

All view-model and model classes inherit from this object.

ViewModelBase

This is the base class for all view-models in the application. It contains common members used by all view-models. It also contains a reference to the common services used by most of the view-models. These services are:

  • Navigation service
  • Message service
  • Dialog service
  • Log service
  • Context service

GenericListViewModel

This is a generic class used by all the view-models that need to manage a list of elements. It contains common functionality used to query, select, add or delete elements from a data source. This functionality includes the following members:

  • Items – a generic list of items
  • ItemsCount – the number of items in the list
  • SelectedItem – the item currently selected
  • SelectedItems – list of items selected when in multi-selection mode
  • SelectedIndexRanges – list of range of items selected when using a virtual collection
  • Query – the text used to query and retrieve items from the data source
  • OnRefresh – abstract method executed to retrieve the list of items
  • OnNew – abstract method executed when the “New Item” command is invoked
  • OnDeleteSelection – abstract method to delete selected items

Please, refer to the GenericListViewModel source code for a detailed list of members implemented in this class.

GenericDetailsViewModel

This is a generic class used by all the view-models that contains the details of a single item. It contains common functionality used to edit and delete an item. This functionality includes the following members:

  • Item – a generic item to be shown or modified
  • OnEdit – executed when the item edition starts
  • OnCancel – executed when the item edition is canceled
  • OnSave – executed when the item changes need to be saved
  • OnDelete – executed when the item is going to be deleted

Please, refer to the GenericDetailsViewModel source code for a detailed list of members implemented in this class.

Models

Model classes are non-visual classes that encapsulate the app's data. Therefore, the model can be thought of as representing the app's domain model, which usually includes a data model along with business and validation logic. Examples of model objects include data transfer objects (DTOs), Plain Old CLR Objects (POCOs), and generated entity and proxy objects.

Model classes are typically used in conjunction with services or repositories that encapsulate data access and caching.

Services

View-models make use of Services to execute the operations requested by the user, such as create, update or retrieve a list of customers or products. View-models also make use of Services to log the user activity, show dialogs or display a text in the status-bar by sending a message to the shell view.

Services contains the core functionality of the application. We distinguish two kinds of services:

  • Application Services – implement core functionality needed by the infrastructure of the application. This functionality in independent of the business of the application and can be reused in other solutions. Examples of application services are Navigation Service or Message Service that can be reused for any other application.
  • Domain Services (or Business Services) – implements the functionality specific for the business of the application. Examples of domain services are Customer Services or Product Services that are specific for a product management application.

The following diagram shows the two group of services used in this application:

ovw-services-groups

Application Services

Here is a brief description of the Application Services used in this application:

Service Description
Navigation Service Expose the functionality to navigate back and forward to a different view. It also offers the possibility to open a view in a new window.
Message Service Enables communication between different components of the application without having to know anything about each other. The communication between components are based on a publishers-subscribers pattern.
Log Service Offers the methods to write logs to a local repository to keep track of the user activity for debugging or auditing purposes.
Login Service Implements the authentication and authorization mechanism to access the application.
Dialog Service Offers methods to display a dialog message to show information or ask for confirmation.
Context Service Exposes properties and methods related to the current execution context. This service is used internally to manage the execution in a multi-window environment where each window is executed in a different thread.

Domain Services

The domain services in this application offer CRUD operations (Create, Read, Update, Delete) over the business entities. We have a specific service for Customers, Projects, Units and Products.

To see the common methods used in these services, let’s examine the Customer service:

  • GetCustomer(id) – get a single customer by its id.
  • GetCustomers(request) – get a collection of customers using the request parameter.
  • GetCustomers(skip, take, request) – same as GetCustomers(request) but returns only ‘take’ number of items starting from the ‘skip’ parameter.
  • GetCustomersCount(request) – return the number of Customers using the request parameter.
  • UpdateCustomer(customer) – update or create a new Customer with the values contained in the customer parameter.
  • DeleteCustomer(customer) – delete the Customer specified by the customer parameter.

There is also a LookupTables Service used to retrieve information for common Tables such as Categories or CountryCodes. This service is used, for example, to get the name of a country by its code, or the tax rate for a specific tax type.

Services and Dependency Injection

When we need to make use of a service, the first thing we need is a reference to that service. The easiest way to get a reference to a service could be just creating an instance of the required service.

For example, let’s say we need to write a log using the Log Service:

    var logService = new LogService();
    logService.Write(message);

Simple and straightforward, but this approach could lead to further problems:

  1. We are assuming that the Log Service is implemented in the scope of the current library, but this is not always the case. In this application, most of the services used by view-models are implemented in a different library not referenced by the view-models library.
  2. We are creating a new instance to write a log, but, shouldn’t it be better to reuse the same instance in all the application? The answer depends on several factors and may be subject to changes in the future. In any case, the decision on how to create an instance of a service should be taken outside the component that uses the service.
  3. On the other hand, creating a hard reference to a service is also a bad idea if we are planning to test our application. When testing a component, we will need a mechanism to replace one or more services by a fake implementation to trace and check if the component is working as expected.

To solve these and other issues that can arise by using hard references, we make use of the Dependency Injection pattern.

With Dependency Injection, we get an instance of a service by using a ServiceLocator.

Note:
The ServiceLocator implemented in this application is based on the .DependencyInjection.Abstractions package library.

To get an instance of a service using the ServiceLocator we can use the following code:

    var logService = ServiceLocator.Request<ILogService>()

The first thing to note is that we are not creating an instance of a service, we are requesting a service specifying an interface.

This have the following considerations:

  • The ServiceLocator may return a new instance or reuse an existing instance of the service (singleton).
  • The ServiceLocator may return a real implementation of ILogService or a fake implementation for testing purposes.
  • Since we are requesting an interface, the service can be implemented in another library, out of the scope of the current component.

If the ServiceLocator returns a new instance, an existing instance, a real implementation or a fake implementation depends on the configuration of the service in the ServiceLocator. We will talk about the ServiceLocator configuration later.

The following diagram shows the relationship between components using a ServiceLocator.

ovw-servicelocator-refs

As you can see in the diagram, the component consuming the ILogService can make use of the LogService, even when the service is implemented out of the scope of the component.

Dependency Injection is not only used to resolve Service instances, it is also used to request view-models and can be used for any object in the application. We just need to tell the ServiceLocator how to resolve the request.

ServiceLocator Configuration

We saw before how to request a service using the ServiceLocator. Now let’s see how to configure the ServiceLocator to return a specific instance when we request for a service.

The ServiceLocator uses a ServiceCollection class where services are registered and configured to be used in the application.

Note: The ServiceCollection class is provided by the DependencyInjection Library.

To register a service in the ServiceCollection we can use the following code:

    serviceCollection.AddSingleton<ILogService, LogService>();

In this case, we are registering the ILogService interface to use the LogService implementation as a singleton. This means that the next time we request a ILogService, the ServiceLocator will return an instance of the LogService class and will reuse the same instance for all the future requests.

If we want the ServiceLocator to return a new instance for every request, we can register the ILogService as transient:

    serviceCollection.AddTransient<ILogService, LogService>();

Registering a service as transient means that we always want a fresh new instance of the service implementation.

In this application, all services are configured as singleton, but there is an exception to this rule: The Navigation service.

The Navigation service is used to navigate to another view in the application. In a single window environment, the Navigation service can be reused by all components of the application, but in a multi-window environment, each window needs its own instance of the service.

To solve this scenario, DependencyInjection Library introduces the concept of scopes and scoped services. We can see a scope as a context of execution. The following code configures a service as scoped.

    serviceCollection.AddScoped<INavigationService, NavigationService>();

Scoped services are similar to singleton services but only when requested in the same scope. Requesting a scoped service in the same scope will return always the same instance but requesting a service in a different scope will return a different instance.

We, as developers, are responsible of creating a new scope when necessary. In this application, a new scope is created for each new window opened in the application. This way, when we request a INavigationService in one window we will receive a Navigation service to navigate in that window.

Declaring Dependencies in the Constructor

When using the Dependency Injection pattern is very common to declare the required services in the constructor of the class.

For example, the Customer service depends on the ILogService and its constructor is declared as follows:

    public CustomerService(ILogService logService)
    {
        LogService = logService;
    }

    private ILogService LogService { get; }

When we request a Customer service, the ServiceLocator examine the constructor and tries to resolve its dependencies. Since we did configure the ServiceLocator to resolve the ILogService, it will obtain the reference to the ILogService and pass it to the constructor of the Customer service.

Dependency Injection is not only used to resolve services, it is also used to resolve view-models and in general it could be used to resolve the instantiation of any class used in the application when required.

Note:
Only classes registered in the ServiceCollection can be resolved by using the ServiceLocator.

Summary

This Sample architecture is designed following the MVVM pattern, separating the user interface from the business logic of the application.

  • Views represents the user interface of the application.
  • ViewModels contains the user interface logic.
  • Models represents the business data of the application.

View-models make use of services to execute the operation requested by the user. We distinguish two kinds of services:

  • Domain services – implement the business functionality of the application.
  • Application services – implement core functionality needed by the infrastructure of the application.

View-models and services are instantiated using a ServiceLocator following the Dependency Injection pattern.

  • We request a service to the ServiceLocator by specifying an interface.
  • ServiceLocator can be configured to instantiate services as Singleton, Transient or Scoped.
]]>
<![CDATA[1. Line of Business Scenarios: Introduction]]>asvegah.github.io//lobs-intro/5fa549dd36892200a25206cbFri, 06 Nov 2020 14:18:03 GMTThis publication showcases a Windows 10 application (using the Universal Windows Platform) focused in Line of Business scenarios, showing how to use the latest Windows capabilities in Desktop applications. The is based around creating and managing customer, orders, and products. To streamline the engineering operations in order to support the accelerated growth generated a company's long term market and product development plans integrating customers, orders, manufacturing processes and employee in a seamless information system. This will drive the development and integration of smart and automated information system for enabling the company to make efficient business decisions.

During the development of an enterprise app, we as developers, must face several challenges as:

  • App requirements that can change over time.
  • New business opportunities and challenges.
  • Ongoing feedback during development that can significantly affect the scope and requirements of the app.

With these in mind, it's important to build apps that can be easily modified or extended over time. Designing for such adaptability can be difficult as it requires an architecture that allows individual parts of the app to be independently developed and tested in isolation without affecting the rest of the app.

An effective remedy for these challenges is to partition an app into discrete, loosely coupled components that can be easily integrated together into an app. Such an approach offers several benefits:

  • It allows individual functionality to be developed, tested, extended, and maintained by different individuals or teams.
  • It promotes reuse and a clean separation of concerns between the app's horizontal capabilities, such as authentication and data access, and the vertical capabilities, such as app specific business functionality. This allows the dependencies and interactions between app components to be more easily managed.
  • It helps maintain a separation of roles by allowing different individuals, or teams, to focus on a specific task or piece of functionality according to their expertise. In particular, it provides a cleaner separation between the user interface and the app's business logic.

Before proceeding to decouple the app in different components, it's important to choose the design patterns that will help us to do it properly. These are the patterns we have decided to choose for the App:

  • Model-View-ViewModel (MVVM): Windows 10 enterprise apps are specially design to apply this pattern to decouple the business logic, the presentation logic and the UI views.
  • Dependency Injection: Dependency injection containers reduce the dependency coupling between objects by providing a facility to construct instances of classes with their dependencies injected, and manage their lifetime based on the configuration of the container.
  • Messaging: Message-based communication between loosely coupled components.
  • Navigation: Define how the Navigation will work, and where the Navigation logic will reside.
  • Data Access: Define how to connect with data sources and the technology to use for that purpose.

This guide not only explain in detail each of the patterns used, it also shows you how are being applied in the App.

Application

This is an enterprise desktop application for Windows 10 focused on Line of Business scenarios and implementing best patterns and practices to solve mass customization requirements.

This application simulates a real-world scenario where you can manage customers, enquires and products as well generate quotation documents.

The application solution has been divided in three decoupled projects, and each of them represents an important role in the app:

Project Description
App.Data .NET Standard project with the data access logic of the application
App.ViewModels .NET Standard project representing the business and the presentation logic of the application
App.Application The Windows 10 executable application containing the User Interface and services implementation
App.Extension .NET Framework project representing data acess and business logic for outputs of the application

The App.Data project

This project is a .NET Standard Library with the main purpose of interact with a database through the Entity Framework Core. It also contains the definitions of the DTOs (Data Transfer Objects) of the application.

The App.ViewModels project

This project is a .NET Standard Library containing the presentation logic and infrastructure components of the application. This project is agnostic of the platform used by the application. A special attention is required for its folder structure:

Folder Description
Infrastructure This folder contains the infrastructure services as well as the base class for Models and ViewModels
Models Application Domain Models
Services Application Domain Services
ViewModels ViewModels classes with the presentation logic of the application

The App.App project

This project is the Windows 10 executable application. It contains platform specific implementations of the app like services and the User Interface.

Features

This sample highlights:

  • Using .NET Standard.
  • The master/details UI pattern.
  • Forms layouts.
  • Using the repository pattern to connect to SQLite / SQLServer.
  • Example of Multiples views of the app
  • Fluent Design

Fluent Design

connected-anim

Fluent Design is an important aspect of any modern Windows 10 application. It's a new design language that sets the guidelines on how the User Interface of an application should look, interact and express in the Windows 10 ecosystem.
Some key aspects of Fluent Design are:

  • The usage of transparencies with blur effects that blend naturally with the rest of the system.
  • Usage of lightning and shadow effects that take part in user interactions
  • Depth. Each of the elements of the user interface may be distributed as part of a 3D space, bringing the relevant content at a given time while its context remains behind, making it easier to the user to focus
  • Fluid and connected animations. The elements of the user interface should act and animate and adapt to changes and act upon user interactions.

reveal-button


]]>