Share via



January 2018

Volume 33 Number 1

[Universal Windows Platform]

Creating a Line-of-Business App with the UWP

By Bruno Sonnino

One day, your boss comes to you with a new assignment: You must create a new line-of-business (LOB) app for Windows. Web isn’t an option and you must choose the best platform for it, to ensure it will be available for at least 10 more years.

That seems to be a difficult choice: You can select Windows Forms or Windows Presentation Foundation (WPF), which are mature technologies with a lot of components, and you, as a .NET developer, have a lot of knowledge and experience working with them. Will they be there in 10 years? My guess is yes; there’s no sign that Microsoft will discontinue any of them in the near future.

But in addition to being a bit old (Windows Forms is from 2001 and WPF was created in 2006), these technologies have some other problems: Windows Forms doesn’t use the latest graphics cards and you’re stuck with the same old boring style, unless you use extra components or do some magic tricks. WPF, though it’s still being updated, hasn’t caught up with the latest improvements to the OS and its SDK. By using the Desktop Bridge to convert WPF applications to UWP, they can be deployed to the Microsoft Store and benefit from easy deployment and install/uninstall, worldwide discovery and distribution, and so on. On the other hand, the converted apps will only work for PCs and won’t be able to run on other form factors.

Digging a little more, you discover the Universal Windows Platform (UWP) for developing for Windows 10 and see that it doesn’t have the downsides of Windows Forms and WPF. It’s being actively improved and can be used with no change in a wide range of devices, from the tiny Raspberry Pi to the huge Surface Hub. But everybody says that the UWP is for developing small apps for Windows tablets and phones; there’s no such thing as an LOB app using the UWP. And, besides that, it’s very difficult to learn and develop: all those new patterns, the XAML language, the sandbox that limits what can be done, and on and on.

All that is far from the reality. Though in the past the UWP had some limitations, and could be difficult to learn and use, this is no longer true. There’s been aggressive development of new features—with the new Fall Creators Update (FCU) and its SDK you can even use .NET Standard 2.0 APIs, as well as access SQL Server directly. At the same time, new tools and components can give you the best experience to develop a new app. Windows Template Studio (WTS) is an extension to Visual Studio that provides a fast start to a full-featured UWP app and you can use the Telerik components for UWP for free, for the best UX. Not bad, no? Now you have a new platform for developing your UWP LOB apps!

In this article, I’m going to develop a UWP LOB app using WTS, the Telerik components and direct access to SQL Server using .NET Standard 2.0, so you can see how everything works firsthand.

Introduction to the Windows Template Studio

As I said before, you need the FCU and its corresponding SDK to use .NET Standard 2.0. Once you have FCU installed on your machine (if you aren’t sure, press Win+R and type Winver to show your Windows version. Your version should be 1709 or newer). The support for .NET Standard 2.0 in the UWP is available only in Visual Studio 2017 update 4 or newer, so if you haven’t updated, you must do it now. You also need the .NET 4.7 runtime installed to use it.

With the infrastructure in place, you can install WTS. In Visual Studio, go to Tools | Extensions and Updates and search for Windows Template Studio. After you download it, restart Visual Studio to finish the installation. You can also install it by going to aka.ms/wtsinstall, downloading the installer and then installing WTS.

Windows Template Studio is a new, open source extension (you can get the source code from aka.ms/wts) that will give you a great quick start to UWP development: You can choose the type of project (a Navigation Pane with the hamburger menu, Blank, or Pivot and Tabs), the Model-View-ViewModel (MVVM) framework for your MVVM pattern, the pages you want in your project and the Windows 10 features you want to include. There’s a lot of development going on in this project, and PRISM, Visual Basic templates and Xamarin support are already available in the nightly builds (coming soon to the stable version).

With WTS installed, you can go to Visual Studio and create a new project using File | New Project | Windows Universal and select the Windows Template Studio. A new window will open to choose the project (see Figure 1).

Windows Template Studio Main Screen
Figure 1 Windows Template Studio Main Screen

Select the Navigation Pane project and the MVVM Light framework, then select Next. Now, choose the pages in the app, as shown in Figure 2.

Page Selection
Figure 2 Page Selection

As you can see in the right Summary column, there’s already a blank page selected, named Main. Select a Settings page and name it Settings, and a Master-Detail page and name it Sales, then click on the Next button. In the next window you can select some features for the app, such as Live Tile or Toast, or a First Use dialog that will be presented the first time the app is run. I won’t choose any features now, except the Settings storage that’s selected when you choose the Settings page.

Now, click on the Create button to create the new app with the selected features. You can run the app—it’s a complete app with a hamburger menu, two pages (blank and master-detail) and a settings page (accessed by clicking the cog icon at the bottom) that allows you to change themes for the app.

If you go to Solution Explorer, you’ll see that many folders were created for the app, such as Models, ViewModels and Services. There’s even a Strings folder with an en-US subfolder for the localization of strings. If you want to add more languages to the app, you just need to add a new subfolder to the Strings folder, name it with the locale of the language (like fr-FR), copy the Resources.resw file and translate it to the new language. Impressive what you’ve been able to do with just a few clicks, no? But I’m pretty sure that’s not what your boss had in mind when he gave you the assignment, so let’s go customize that app!

Accessing SQL Server from the App

One nice feature that was introduced in the FCU and in Visual Studio update 4 is the support for .NET Standard 2.0 in the UWP. This is a great improvement, because it allows UWP apps to use a huge number of APIs that were unavailable before, including SQL Server client access and Entity Framework Core.

To make SQL Server client access available in your app, go to the app properties and select Build 16299 as the Minimum Version in the application tab. Then right-click the References node and select “Manage NuGet packages” and install System.Data.SqlClient. With that, you’re ready to access a local SQL Server database. One note: the access isn’t made by using named pipes—the default access method—but by using TCP/IP. So, you must run the SQL Server Configuration app and enable TCP/IP connections for your server instance.

I’ll also use the Telerik UI for UWP grid, now a free open source product that you’ll find at bit.ly/2AFWktT. So, while you still have the NuGet package manager window open, select and install the Telerik.UI.for.UniversalWindowsPlatform package. If you’re adding a grid page in WTS, this package is installed automatically.

For this app, I’ll use the WorldWideImporters sample database, which you can download from bit.ly/2fLYuBk and restore it to your SQL Server instance.

Now, you must change the default data access. If you go to the Models folder, you’ll see the SampleOrder class, with this comment at the beginning:

// TODO WTS: Remove this class once your pages/features are using your data.
// This is used by the SampleDataService.
// It is the model class we use to display data on pages like Grid, Chart, and Master Detail.

There are a lot of comments throughout the project that give guidance on what you need to do. In this case, you need an Order class that’s very similar to this one. Rename the class to Order and change it to something similar to the one in Figure 3.

Figure 3 The Order Class

public class Order : INotifyPropertyChanged
{
  public long OrderId { get; set; }
  public DateTime OrderDate { get; set; }
  public string Company { get; set; }
  public decimal OrderTotal { get; set; }
  public DateTime? DatePicked { get; set; }
  public bool Delivered => DatePicked != null;
  private IEnumerable<OrderItem> _orderItems;
  public event PropertyChangedEventHandler PropertyChanged;
  public IEnumerable<OrderItem> OrderItems
  {
    get => _orderItems;
    set
    {
      _orderItems = value;
      PropertyChanged?.Invoke(
        this, new PropertyChangedEventArgs("OrderItems"));
    }
  }
  public override string ToString() => $"{Company} {OrderDate:g}  {OrderTotal}";
}

This class implements the INotifyPropertyChanged interface, because I want to notify the UI when the order items are changed so I can load them on demand when displaying the order. I’ve defined another class, OrderItem, to store the order items:

public class OrderItem
{
  public string Description { get; set; }
  public decimal UnitPrice { get; set; }
  public int Quantity { get; set; }
  public decimal TotalPrice => UnitPrice * Quantity;
}

The SalesViewModel, shown in Figure 4, must also be modified to reflect these changes.

Figure 4 SalesViewModel

public class SalesViewModel : ViewModelBase
{
  private Order _selected;
  public Order Selected
  {
    get => _selected;
    set
    {
      Set(ref _selected, value);
    }
  }
  public ObservableCollection<Order> Orders { get; private set; }
  public async Task LoadDataAsync(MasterDetailsViewState viewState)
  {
    var orders = await DataService.GetOrdersAsync();
    if (orders != null)
    {
      Orders = new ObservableCollection<Order>(orders);
      RaisePropertyChanged("Orders");
    }
    if (viewState == MasterDetailsViewState.Both)
    {
      Selected = Orders.FirstOrDefault();
    }
  }
}

When the Selected property is changed, it will check whether the order items are already loaded and, if not, it calls the GetOrder­ItemsAsync method in the data service to load them.

The last change in the code to make is in the SampleDataService class, to remove the sample data and create the SQL Server access, as shown in Figure 5. I’ve renamed the class to DataService, to reflect that it’s not a sample anymore.

Figure 5 Code to Retrieve the Orders from the Database

public static async Task<IEnumerable<Order>> GetOrdersAsync()
{
  using (SqlConnection conn = new SqlConnection(
    "Database=WideWorldImporters;Server=.;User ID=sa;Password=pass"))
  {
    try
    {
      await conn.OpenAsync();
      SqlCommand cmd = new SqlCommand("select o.OrderId, " +
        "c.CustomerName, o.OrderDate, o.PickingCompletedWhen, " +
        "sum(l.Quantity * l.UnitPrice) as OrderTotal " +
        "from Sales.Orders o " +
        "inner join Sales.Customers c on c.CustomerID = o.CustomerID " +
        "inner join Sales.OrderLines l on o.OrderID = l.OrderID " +
        "group by o.OrderId, c.CustomerName, o.OrderDate,
          o.PickingCompletedWhen " +
        "order by o.OrderDate desc", conn);
      var results = new List<Order>();
      using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
      {
        while(reader.Read())
        {
          var order = new Order
          {
            Company = reader.GetString(1),
            OrderId = reader.GetInt32(0),
            OrderDate = reader.GetDateTime(2),
            OrderTotal = reader.GetDecimal(4),
            DatePicked = !reader.IsDBNull(3) ? reader.GetDateTime(3) :
              (DateTime?)null
          };
          results.Add(order);
        }
        return results;
      }
    }
    catch
    {
      return null;
    }
  }
}
public static async Task<IEnumerable<OrderItem>> GetOrderItemsAsync(
  int orderId)
{
  using (SqlConnection conn = new SqlConnection(
        "Database=WideWorldImporters;Server=.;User ID=sa;Password=pass"))
  {
    try
    {
      await conn.OpenAsync();
      SqlCommand cmd = new SqlCommand(
        "select Description,Quantity,UnitPrice " +
        $"from Sales.OrderLines where OrderID = {orderId}", conn);
      var results = new List<OrderItem>();
      using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
      {
        while (reader.Read())
        {
          var orderItem = new OrderItem
          {
            Description = reader.GetString(0),
            Quantity = reader.GetInt32(1),
            UnitPrice = reader.GetDecimal(2),
          };
          results.Add(orderItem);
        }
        return results;
      }
    }
    catch
    {
      return null;
    }
  }
}

The code is the same you’d use in any .NET app; there’s no change at all. Now I need to modify the list items data template in Sales­Page.xaml to reflect the changes, as shown in Figure 6.

Figure 6 The List Items Data Template

<DataTemplate x:Key="ItemTemplate" x:DataType="model:Order">
  <Grid Height="64" Padding="0,8">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <StackPanel Grid.Column="1" Margin="12,0,0,0" VerticalAlignment="Center">
      <TextBlock Text="{x:Bind Company}" Style="{ThemeResource ListTitleStyle}"/>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{x:Bind OrderDate.ToShortDateString()}"
                   Margin="0,0,12,0" />
        <TextBlock Text="{x:Bind OrderTotal}" Margin="0,0,12,0" />
      </StackPanel>
    </StackPanel>
  </Grid>
</DataTemplate><DataTemplate x:Key="DetailsTemplate">
  <views:SalesDetailControl MasterMenuItem="{Binding}"/>
</DataTemplate>

I need to change the DataType so it refers to the new Order class and change the fields presented in the TextBlocks. I must also change the codebehind in SalesDetailControl.xaml.cs to load the Items for the selected order when it’s changed. This is done in the OnMasterMenuItemChanged method, which is transformed to an async method:

private static async void OnMasterMenuItemChangedAsync(DependencyObject d,
  DependencyPropertyChangedEventArgs e)
{
  var newOrder = e.NewValue as Order;
  if (newOrder != null && newOrder.OrderItems == null)
    newOrder.OrderItems = await
      DataService.GetOrderItemsAsync((int)newOrder.OrderId);
}

Next, I must change SalesDetailControl to point to the new fields and show them, as shown in Figure 7.

Figure 7 Changes in SalesDetailControl

<Grid Name="block" Padding="0,15,0,0">
  <Grid.Resources>
    <Style x:Key="RightAlignField" TargetType="TextBlock">
      <Setter Property="HorizontalAlignment" Value="Right" />
      <Setter Property="VerticalAlignment" Value="Center" />
      <Setter Property="Margin" Value="0,0,12,0" />
    </Style>
  </Grid.Resources>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <TextBlock Margin="12,0,0,0"
    Text="{x:Bind MasterMenuItem.Company, Mode=OneWay}"
    Style="{StaticResource SubheaderTextBlockStyle}" />
  <StackPanel Orientation="Horizontal" Grid.Row="1" Margin="12,12,0,12">
    <TextBlock Text="Order date:"
      Style="{StaticResource BodyTextBlockStyle}" Margin="0,0,12,0"/>
    <TextBlock Text="{x:Bind MasterMenuItem.OrderDate.ToShortDateString(),
      Mode=OneWay}"
      Style="{StaticResource BodyTextBlockStyle}" Margin="0,0,12,0"/>
    <TextBlock Text="Order total:" Style="{StaticResource BodyTextBlockStyle}"
      Margin="0,0,12,0"/>
    <TextBlock Text="{x:Bind MasterMenuItem.OrderTotal, Mode=OneWay}"
      Style="{StaticResource BodyTextBlockStyle}" />
  </StackPanel>
  <grid:RadDataGrid ItemsSource="{x:Bind MasterMenuItem.OrderItems,
    Mode=OneWay}" Grid.Row="2"
    UserGroupMode="Disabled" Margin="12,0"
    UserFilterMode="Disabled" BorderBrush="Transparent"
      AutoGenerateColumns="False">
    <grid:RadDataGrid.Columns>
      <grid:DataGridTextColumn PropertyName="Description" />
      <grid:DataGridNumericalColumn
        PropertyName="UnitPrice"  Header="Unit Price"
        CellContentStyle="{StaticResource RightAlignField}"/>
      <grid:DataGridNumericalColumn
        PropertyName="Quantity"  Header="Quantity"
        CellContentStyle="{StaticResource RightAlignField}"/>
      <grid:DataGridNumericalColumn
        PropertyName="TotalPrice"  Header="Total Price"
        CellContentStyle="{StaticResource RightAlignField}"/>
    </grid:RadDataGrid.Columns>
  </grid:RadDataGrid>
</Grid>

I display the sales data in the top of the control and use a Telerik RadDataGrid to show the order items. Now, when I run the application, I get the orders on the second page, like what’s shown in Figure 8.

Application Showing Database Orders
Figure 8 Application Showing Database Orders

I still have the empty main page. I’ll use it to show a grid of all customers. In MainPage.xaml, add the DataGrid to the content grid:

<grid:RadDataGrid ItemsSource="{Binding Customers}"/>

You must add the namespace to the Page tag, but you don’t need to remember the syntax and correct namespace. Just put the mouse cursor over RadDataGrid, type Ctrl+. and a box will open, indicating the right namespace to add. As you can see from the added line, I’m binding the ItemsSource property to Customers. The DataContext for the page is an instance of MainViewModel, so I must create this property in MainViewModel.cs, as shown in Figure 9.

Figure 9 MainViewModel Class

public class MainViewModel : ViewModelBase
{
  public ObservableCollection<Customer> Customers { get; private set; }
  public async Task LoadCustomersAsync()
  {
    var customers = await DataService.GetCustomersAsync();
    if (customers != null)
    {
      Customers = new ObservableCollection<Customer>(customers);
      RaisePropertyChanged("Customers");
    }
  }
  public MainViewModel()
  {
    LoadCustomersAsync();
  }
}

I’m loading the customers when the ViewModel is created. One thing to notice here is that I don’t wait for the loading to complete. When the data is fully loaded, the ViewModel indicates that the Customers property has changed and that loads the data in the view. The GetCustomersAsync method in DataService is very similar to GetOrdersAsync, as you can see in Figure 10.

Figure 10 GetCustomersAsync Method

public static async Task<IEnumerable<Customer>> GetCustomersAsync()
{
  using (SqlConnection conn = new SqlConnection(
    "Database=WideWorldImporters;Server=.;User ID=sa;Password=pass"))
  {
    try
    {
      await conn.OpenAsync();
      SqlCommand cmd = new SqlCommand("select c.CustomerID,
        c.CustomerName, " +
        "cat.CustomerCategoryName, c.DeliveryAddressLine2, 
          c.DeliveryPostalCode, " +
        "city.CityName, c.PhoneNumber " +
        "from Sales.Customers c " +
        "inner join Sales.CustomerCategories cat on c.CustomerCategoryID =
          cat.CustomerCategoryID " +
        "inner join Application.Cities city on c.DeliveryCityID =
          city.CityID", conn);
      var results = new List<Customer>();
      using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
      {
        while (reader.Read())
        {
          var customer = new Customer
          {
            CustomerId = reader.GetInt32(0),
            Name = reader.GetString(1),
            Category = reader.GetString(2),
            Address = reader.GetString(3),
            PostalCode = reader.GetString(4),
            City = reader.GetString(5),
            Phone = reader.GetString(6)
          };
          results.Add(customer);
        }
        return results;
      }
    }
    catch
    {
      return null;
    }
  }
}

With that, you can run the app and see the customers on the main page, as shown in Figure 11. Using the Telerik grid, you get a lot of goodies for free: grouping, sorting and filtering are built into the grid and there’s no extra work to do.

Customers Grid with Grouping and Filtering
Figure 11 Customers Grid with Grouping and Filtering

Finishing Touches

I have now a basic LOB application, with a customers grid and a master-detail view showing the orders, but it can use some finishing touches. The icons in the lateral bar for both pages are the same and they can be customized. These icons are set in the ShellViewModel. If you go there, you’ll see these comments, which point to where to go to change the icons and the text for the items:

// TODO WTS: Change the symbols for each item as appropriate for your app
// More on Segoe UI Symbol icons:
// https://docs.microsoft.com/windows/uwp/style/segoe-ui-symbol-font
// Or to use an IconElement instead of a Symbol see
// https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/
projectTypes/navigationpane.md
// Edit String/en-US/Resources.resw: Add a menu item title for each page

You can use font symbol icons (as in the actual code) or images from other sources if you use IconElements. (You can use .png files, XAML paths, or characters from any other fonts; take a look at bit.ly/2zICuB2 for more information.) I’m going to use two symbols from Segoe UI Symbol, People and ShoppingCart. To do that, I must change the NavigationItems in the code:

_primaryItems.Add(new ShellNavigationItem("Shell_Main".GetLocalized(),
  Symbol.People, typeof(MainViewModel).FullName));
_primaryItems.Add(new ShellNavigationItem("Shell_Sales".GetLocalized(),
  (Symbol)0xE7BF, typeof(SalesViewModel).FullName));

For the first item, there’s already a symbol in the Symbol enumeration, Symbol.People, but for the second there’s no such enumeration, so I use the hex value and cast it to the Symbol enumeration. To change the title of the page and the caption of the menu item, I edit Resources.resw and change Shell_Main and Main_Title.Text to Customers. I can also add some customization to the grid by changing some properties:

<grid:RadDataGrid ItemsSource="{Binding Customers}" UserColumnReorderMode="Interactive"
                  ColumnResizeHandleDisplayMode="Always"
                  AlternationStep="2" AlternateRowBackground="LightBlue"/>

Adding a Live Tile to the App

I can also enhance the application by adding a live tile. I’ll do so by going to Solution Explorer, right-clicking the project node and selecting Windows Template Studio | New feature, then choosing Live Tile. When you click on the Next button, it will show the affected tiles (both new ones and those that have been changed), so you can see if you really like what you did. Clicking on the Finish button will add the changes to the app.

Everything to add a Live Tile will be there, you just need to create the tile content. This is already done in LiveTileService.Samples. It’s a partial class that adds the method SampleUpdate to LiveTileService. As shown in Figure 12, I’ll rename the file to LiveTileService.LobData and add two methods to it, Update­CustomerCount and UpdateOrdersCount, which will show in the live tiles how many customers or orders are in the database.

Figure 12 Class to Update the Live Tile

internal partial class LiveTileService
{
  private const string TileTitle = "LoB App with UWP";
  public void UpdateCustomerCount(int custCount)
  {
    string tileContent =
      $@"There are {(custCount > 0 ? custCount.ToString() : "no")}
        customers in the database";
    UpdateTileData(tileContent, "Customer");
  }
  public void UpdateOrderCount(int orderCount)
  {
    string tileContent =
      $@"There are {(orderCount > 0 ? orderCount.ToString() : "no")}
        orders in the database";
    UpdateTileData(tileContent,"Order");
  }
  private void UpdateTileData(string tileBody, string tileTag)
  {
    TileContent tileContent = new TileContent()
    {
      Visual = new TileVisual()
      {
        TileMedium = new TileBinding()
        {
          Content = new TileBindingContentAdaptive()
          {
            Children =
            {
              new AdaptiveText()
              {
                Text = TileTitle,
                HintWrap = true
              },
              new AdaptiveText()
              {
                Text = tileBody,
                HintStyle = AdaptiveTextStyle.CaptionSubtle,
                HintWrap = true
              }
            }
          }
        },
        TileWide = new TileBinding()
        {
          Content = new TileBindingContentAdaptive()
          {
            Children =
            {
              new AdaptiveText()
              {
                Text = $"{TileTitle}",
                HintStyle = AdaptiveTextStyle.Caption
              },
              new AdaptiveText()
              {
                Text = tileBody,
                HintStyle = AdaptiveTextStyle.CaptionSubtle,
                HintWrap = true
              }
            }
          }
        }
      }
    };
    var notification = new TileNotification(tileContent.GetXml())
    {
      Tag = tileTag
    };
    UpdateTile(notification);
  }
}

UpdateSample was originally called in the initialization, in the StartupAsync method of ActivationService. I’m going to replace it with the new UpdateCustomerCount:

private async Task StartupAsync()
{
  Singleton<LiveTileService>.Instance.UpdateCustomerCount(0);
  ThemeSelectorService.SetRequestedTheme();
  await Task.CompletedTask;
}

At this point, I still don’t have the customer count to update. That will happen when I get the customers in MainViewModel:

public async Task LoadCustomersAsync()
{
  var customers = await DataService.GetCustomersAsync();
  if (customers != null)
  {
    Customers = new ObservableCollection<Customer>(customers);
    RaisePropertyChanged("Customers");
    Singleton<LiveTileService>.Instance.UpdateCustomerCount(Customers.Count);
  }
}

The orders count will be updated when I get the orders in SalesViewModel:

public async Task LoadDataAsync(MasterDetailsViewState viewState)
{
  var orders = await DataService.GetOrdersAsync();
  if (orders != null)
  {
    Orders = new ObservableCollection<Order>(orders);
    RaisePropertyChanged("Orders");
    Singleton<LiveTileService>.Instance.UpdateOrderCount(Orders.Count);
  }
  if (viewState == MasterDetailsViewState.Both)
  {
    Selected = Orders.FirstOrDefault();
  }
}

With that, I have an app as shown in Figure 13.

The Finished App
Figure 13 The Finished App

This app can show the customers and orders retrieved from a local database, updating the live tile with the customers and order counts. You can group, sort or filter the customers listed and show the orders using a master-detail view. Not bad!

Wrapping Up

As you can see, the UWP isn’t just for small apps. You can use it for your LOB apps, getting the data from many sources, including a local SQL Server (and you can even use Entity Framework as an ORM). With .NET Standard 2.0, you have access to a lot of APIs that are already available in the .NET Framework, with no change. WTS gives you a quick start, helping you to quickly and easily create an application using best practices with the tools you prefer, and to add Windows features to it. There are great UI controls to enhance the appearance of the app and the app will run in a wide range of devices: on a phone, a desktop, the Surface Hub and even on HoloLens without change.

When it comes to deploying, you can send your app to the store and have worldwide discovery, easy install and uninstall and auto updates. If you don’t want to deploy it through the store, you can do so using the Web (bit.ly/2zH0nZY). As you can see, when you have to create a new LOB app for Windows, you should certainly consider the UWP, because it can offer you everything you want for this kind of app and more, with the great advantage that it’s being aggressively developed and will bring you a lot of improvements for years to come.


Bruno Sonnino has been a Microsoft Most Valuable Professional (MVP) since 2007. He’s a developer, consultant, and author, having written many books and articles about Windows development. You can follow him on Twitter: @bsonnino or read his blog posts at blogs.msmvps.com/bsonnino.

Thanks to the following Microsoft technical expert who reviewed this article: Clint Rutkas


Discuss this article in the MSDN Magazine forum