Real-World MVVM with Entity Framework and ASP.NET Web API

I just completed a sample application using Simple MVVM Toolkit together with Trackable Entities to build a real-world N-Tier solution with a WPF client and portable POCO entities that are automatically change-tracked and sent to an ASP.NET Web API service that uses Entity Framework to perform asynchronous CRUD operations (Create, Retrieve, Update, Delete). The sample includes a Windows Presentation Foundation client, but the toolkit has a Visual Studio template for building a multi-platform client with portable view models that are shared across WPF, Silverlight, Windows Phone, Windows Store, iOS and Android.

Download the Simple MVVM Trackable Entities sample application here.

The nice thing about this sample is that it demonstrates how to build a complete end-to-end solution.  Client-side entities don’t care if they are sent to a WCF or Web API service and are marked up for serialization using both [DataContract] and [JsonObject] attributes.  Both WCF and Json.NET serializers accept attribute-free classes, but the attributes are included in order to handle cyclic references. The WPF client binds views to view models which expose entities as properties, and because ChangeTrackingCollection<T> extends ObservableCollection<T>, it is data-binding friendly.

View models have methods which call GetChanges on the change tracker, so that only changed entities are sent to the service.  GetChanges traverses the object graph in all directions, including 1-1, M-1, 1-M and M-M relations, and returns only entities which have been added, modified or deleted, saving precious bandwidth and improving performance.  Service operations return inserted and updated entities back to the client, which include database-generated values, such as identity and concurrency tokens.  View models then invoke MergeChanges on the change tracker to update existing entities with current values.

  1. public async void ConfirmSave()
  2. {
  3.     if (Model == null) return;
  4.     try
  5.     {
  6.         if (IsNew)
  7.         {
  8.             // Save new entity
  9.             var createdOrder = await _orderServiceAgent.CreateOrder(Model);
  10.             Model = createdOrder;
  11.         }
  12.         else
  13.         {
  14.             // Get changes, exit if none
  15.             var changedOrder = ChangeTracker.GetChanges().SingleOrDefault();
  16.             if (changedOrder == null) return;
  17.  
  18.             // Save changes
  19.             var updatedOrder = await _orderServiceAgent.UpdateOrder(changedOrder);
  20.             ChangeTracker.MergeChanges(updatedOrder);
  21.  
  22.             // Unsubscribe to collection changed on order details
  23.             Model.OrderDetails.CollectionChanged -= OnOrderDetailsChanged;
  24.  
  25.             // End editing
  26.             EndEdit();
  27.         }
  28.  
  29.         // Notify view of confirmation
  30.         Notify(ResultNotice, new NotificationEventArgs<bool>(null, true));
  31.     }
  32.     catch (Exception ex)
  33.     {
  34.         NotifyError(null, ex);
  35.     }
  36. }

The ConfirmSave method is from the OrderViewModelDetail class, which exposes a ResultNotice event to facilitate communication with OrderDetailView.xaml.  The code-behind for OrderDetailView handles ResultNotice by setting the view’s DialogResult, which closes the dialog and sets the result to true for confirmation or false for cancellation.

  1. public partial class OrderDetailView : Window
  2. {
  3.     public OrderDetailView(Order order)
  4.     {
  5.         ViewModel = (OrderViewModelDetail)DataContext;
  6.         ViewModel.Initialize(order);
  7.         ViewModel.ErrorNotice += OnErrorNotice;
  8.         ViewModel.ResultNotice += OnResultNotice;
  9.     }
  10.  
  11.     public OrderViewModelDetail ViewModel { get; private set; }
  12.  
  13.     private void OnResultNotice(object sender, NotificationEventArgs<bool> eventArgs)
  14.     {
  15.         DialogResult = eventArgs.Data;
  16.     }
  17.  
  18.     private void OnErrorNotice(object sender, NotificationEventArgs<Exception> eventArgs)
  19.     {
  20.         MessageBox.Show(eventArgs.Data.Message, “Error”);
  21.     }
  22.  
  23.     private void OnUnloaded(object sender, RoutedEventArgs e)
  24.     {
  25.         ViewModel.ErrorNotice -= OnErrorNotice;
  26.         ViewModel.ResultNotice -= OnResultNotice;
  27.     }
  28. }

I enjoyed putting the sample together because it gave me the opportunity to revisit my MVVM toolkit and soak up some of the goodness I put into it.  For example, the ViewModelDetail base class implements IEditableObject by cloning and caching the entity when BeginEdit is called, and pointing the Model property of the view model to the cached entity.  Because the user is working off a separate entity, the UI showing the original entity does not reflect changes the user is making until EndEdit is called, when values are copied from the working copy back to the original.  CancelEdit simply points Model to the original and discards the edited version.  The view model base class also includes IsEditing and IsDirty properties, which are updated appropriately.

I also took advantage of support for async and await in .NET 4.5. For example, CustomerServiceAgent provides an async GetCustomers method, which is called by the view model to bind a list of customers to a combo box.  This transparently marshals code following await onto the UI thread to update the contents of the combo box.

  1. public class CustomerServiceAgent : ICustomerServiceAgent
  2. {
  3.     public async Task<IEnumerable<Customer>> GetCustomers()
  4.     {
  5.         const string request = “api/Customer”;
  6.         var response = await ServiceProxy.Instance.GetAsync(request);
  7.         response.EnsureSuccessStatusCode();
  8.         var result = await response.Content.ReadAsAsync<IEnumerable<Customer>>();
  9.         return result;
  10.     }
  11. }

Tinkering with XAML for the views allowed me the opportunity to solve some common challenges.  For example, the customer orders view has a pair of data grids that need to function in concert as master-detail, with the first grid showing orders for a selected customer, and the second grid showing details for the selected order.  I had to bind SelectedIndex on the orders grid to the SelectedOrderIndex property on the view model, and bind SelectedItem to the SelectedOrder property.  I got the details grid to synchronize by binding ItemsSource to SelectedOrder.OrderDetails.

Another interesting problem was how to populate a Products data grid combo box column in the details grid on OrderDetailView.xaml.  That required placing a Products property on the view model and using a RelativeSource binding on the ElementStyle and EditingElementStyle properties of the combo box column.

  1. <DataGrid Grid.Row=2 Grid.Column=0 Height=140 VerticalAlignment=Top
  2.           ItemsSource=“{Binding Model.OrderDetails} AutoGenerateColumns=False Margin=0,10,0,0 IsTabStop=True TabIndex=3 >
  3.     <DataGrid.Columns>
  4.         <DataGridTextColumn Binding=“{Binding OrderDetailId} ClipboardContentBinding=“{x:Null} Header=OrderDetail Id/>
  5.         <DataGridComboBoxColumn SelectedValueBinding=“{Binding ProductId}
  6.                             SelectedValuePath=ProductId
  7.                             DisplayMemberPath=ProductName
  8.                             Header=Product Width=150>
  9.             <DataGridComboBoxColumn.ElementStyle>
  10.                 <Style TargetType=ComboBox>
  11.                     <Setter Property=ItemsSource Value=“{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Products}/>
  12.                     <Setter Property=IsReadOnly Value=True/>
  13.                 </Style>
  14.             </DataGridComboBoxColumn.ElementStyle>
  15.             <DataGridComboBoxColumn.EditingElementStyle>
  16.                 <Style TargetType=ComboBox>
  17.                     <Setter Property=ItemsSource Value=“{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Products}/>
  18.                 </Style>
  19.             </DataGridComboBoxColumn.EditingElementStyle>
  20.         </DataGridComboBoxColumn>
  21.         <DataGridTextColumn Binding=“{Binding UnitPrice, StringFormat=\{0:C\}} ClipboardContentBinding=“{x:Null} Header=Unit Price/>
  22.         <DataGridTextColumn Binding=“{Binding Quantity} ClipboardContentBinding=“{x:Null} Header=Quantity/>
  23.         <DataGridTextColumn Binding=“{Binding Discount, StringFormat=\{0:F\}} ClipboardContentBinding=“{x:Null} Header=Discount/>
  24.     </DataGrid.Columns>
  25. </DataGrid>

Here is a screen shot of the main view, which has a “Load” button for retrieving customers.  Selecting a customer from the combo box will retrieve the customer’s orders with details.

mvvm-trackable-main

Clicking “Create Order” will bring up the order detail view with a new Order. Clicking “Modify Order” will open the order detail view with the selected Order.  Clicking “Delete Order” will prompt the user to confirm the delete, then pass the id for the selected order to the delete operation on the Orders controller of the Web API service.

Here is a screen shot of the Add Order dialog.  The user interacts with the order details grid to add, modify or remove details from the order.  Clicking OK will pass a new or existing order to the Orders controller, together with new or changed details.  Because orders and details are change-tracked, they can be sent to a service for persistence in one round trip, so that Entity Framework can perform multiple inserts, updates and deletes within a single transaction.

mvvm-trackable-add

On the client-side, Trackable Entities marks entities as Created, Modified or Deleted as individual properties are modified and as they are added or removed from a change tracking collection.  Change state is carried with the entity across service boundaries as a simple, lightweight TrackingState enumeration.  Then on the service-side, ApplyChanges is called on the order to traverses the object graph, informing the DbContext of the entity’s change state.  That’s how the magic works.

Enjoy!

About Tony Sneed

Sr. Software Solutions Architect, Hilti Global Application Software
This entry was posted in Technical and tagged , , , . Bookmark the permalink.

13 Responses to Real-World MVVM with Entity Framework and ASP.NET Web API

  1. nhwilly says:

    This looks pretty cool and does many of the things I think I want. I have a couple of questions, though.
    1. I have seen some courses on the Repository pattern that show the Repository interfaces in the Model namespace (what you call entities, I believe). Is there a reason that your interfaces are in the Services?
    2. I was hoping to put all my validation into my Models and use the same model code on both the client and the server to increase security. I can see that the mapping files contains the server side validation logic, but then aren’t I going to have to maintain the validation logic in two places?

    Thanks for this, it’s helping me with my project a lot.

    • nhwilly says:

      OK, answering my own question here by building out the NTier project on my own little database. So forget question #1.

      And I found a reference to why you build the structure the way your did (WCF serializer). So that answers #2.

      Great tool, BTW.

  2. strattonn says:

    I ran the app, modified an order, changed one product for another product, went back to the main screen and the item is not changed until I Load or move to another order and come back.

    • Tony Sneed says:

      The dialog which updates an order uses a view model detail class that clones the existing entity. The edit is not confirmed until the user clicks OK on the dialog, at which time the order is updated on the main form. If changes are not reflected, there might be data binding errors. Run the app in Debug and check the output window.

  3. Huy says:

    Can you implement input Product column by lookup from table Item ?

  4. I want to use Trackable Entities with ASP.NET MVC but do not know how to get started. I only found templates with a console client. Could you advise how to integrate trackable entities with an ASP.NET MVC application ?

    • Tony Sneed says:

      You can still use the same templates but discard the console client and replace it with an ASP.NET MVC app. However, the client-side change tracker does not currently have a Javascript implementation. The change tracker is a portable class library, so it will work in an MVC app server-side. But that’s not really the best use. What I know is that you can use Javascript to set the TrackingState property manually. Long Le has an example of how to do this: http://blog.longle.net/2014/01/06/unit-of-work-unity-3-quick-start-video.

  5. Graeme says:

    I use your ServiceProxy class from this app but need to pass the user’s Windows credentials to the service. So I added the line for setting the credentials here. But for some reason, randomly, the creds are not being passed correctly and so my database access is failing. In general if I step through the method in the debugger it works, if I let it run it often fails and the WebApi tries to login to the database with the ASP account instead of my windows account.

    Is this the correct way to pass in my credentials?
    Any clues on how to debug why / where the issue could be happening?
    Thanks for any pointers.

    public static HttpClient Instance
    {
    get
    {
    if (_instance == null)
    {
    lock (SyncRoot)
    {
    if (_instance == null)
    {
    string baseAddress = ConfigurationManager.AppSettings[“WebApiAddress”];
    _instance = new HttpClient (new HttpClientHandler() { UseDefaultCredentials = true });
    _instance.BaseAddress = new Uri(baseAddress);
    }
    }
    }
    return _instance;
    }
    }

    • Tony Sneed says:

      Web API security is an advanced topic, but I can point you to some resources that might help. First, check out my DevWeek presentation slides. Then have a look at the demos that go with the presentation. You’ll need to select either basic or token-based authentication, depending on your scenario.

      The credentials your service uses to connect to your database is a different matter entirely. For that, use a DB connection string with integrated security, then create a login for SQL Server using the account used for your IIS application pool identity.

  6. Søren Bech Christensen says:

    Great sample app Tony, thanks for sharing
    .
    I have a question though: Are we supposed to see the changes reflected in the WPFapplication, when stepping through the console app?

    Best
    Søren

  7. Oliver Shaw says:

    Great tool, is this something that is still being maintained?

    Is / will it be possible to use with client side Blazor?

    • Tony Sneed says:

      The toolkit has been ported to .NET Standard, but it’s not being actively maintained at this point. Good question about Blazor. It should be compatible, but I haven’t tried it.

  8. Thank you for updating SimpleMVVM.Express for .NET Standard to a release version. It’s great (for me) that you are keeping an eye on it.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.