Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Generic WPF CRUD Control (Solution Design)

0.00/5 (No votes)
22 Dec 2016 1  
A generic CRUD control implemented based on the MVVM pattern

Introduction

This article describes the usage and implementation details of a WPF CrudControl which is a generic CRUD control implemented based on the MVVM pattern. The control abstracts both the UI and business logic to achieve a foundation for a complete CRUD operation implementation. In the getting started article, we will explain how to use the WPF CrudControl.

Problem Definition

To develop a reusable control that serves all CRUD operations and related aspects (like validation and resetting) and facilitate developing a lookup data management module with minimum code.

UI Wire-frame

Consists of two main views as figure1 demonstrates:

View #1

  1. Search Criteria: Consists of a placeholder for a business related search criteria controls, search action, and reset action.
  2. Sorting: Consists of a sort-by combobox for business properties and a sort-direction combobox.
  3. Listing, Add, Edit & Delete Actions
    • DataGrid: For listing data with the following columns:
      • A checkbox column: To select row for deletion
      • Business-related columns
      • Edit action: Populates the pop-up window bound to the selected entity
    • Add & Delete Actions
      • Add action: Populates the pop-up window bound to a new entity
      • Delete action: Deletes the selected entities
  4. Pager: Consists of next / previous actions, current page number, total records, and page-size combobox

View #2

  1. Add / Edit pop-up window: Consists of a placeholder for the business related input controls that will hold the entity values, save action, and an action to reset the changes to the original values.

Northwind Demo

The solution is applied on Northwind database for two modules Suppliers and Products. In this article, we will demonstrate the solution with the Products module as it has more advanced scenario. At the demo, we used Unity as a IoC/DI container, MVVMLight toolkit and WPF Modern UI library for styling the main window and navigation.

To run the demo:

  • Restore packages using NuGet packages manager
  • Install SQL Server LocalDB

Solution Design

MVVM based, and mainly relies on polymorphism and generics. Click to see the full design diagram. The solution currently depends on Microsoft.Practices.ServiceLocation and EntityFramework.

1. Core

The Entity class is the core for all base classes at view model layer, and it should be inherited by the business models as the figure demonstrates. It peforms the following:

  • Implements the INotifyDataErrorInfo interface and has a public method Validate() that is called by AddEditEntityBase class. It applies the ValidationAttribute data annotations that are provided with Entity's properties using ValidationContext, so an error is raised when an incorrect value is entered in the bound property, and the associated UI control colored with its error template.
  • Implements IEditableObject that backs-up the entity's original values to restore them when the reset action is fired.
  • Has IsSelected property that identifies that the Entity is selected from the UI for any usage (i.e. deletion). Also, it has IsSelectable property to be bound to identify whether the Entity is selectable or not based on business logic.

The generic repository interface IRepository<Entity> is constrained with Entity type. In this solution, the implementation of IRepository<Entity> and IUnitOfWork requires DbContext object during their instantiations.

2. Views Layer

The abstracted views consist of five parts as the following figure demonstrates:

The main abstracted view is GenericCrudControl that contains the XAML parts of the Listing, Sorting, Pager and SearchCriteriaContainer. The AddEditPopupWindow and SearchCriteriaContainer have a ContentControl to hold the business controls.

The user control GenericCrudControl uses DataGrid control to list data and load business columns that are provided using an exposed collection-based dependency property of type CustomDataGridColumn that inherits the base class DataGridColumn. And it generates the element depending on the ColumnType. It provides two types of columns, a TextColumn as default type and TemplateColumn type as DataTemplate.

The SortingProperties is a collection-based dependency property of type SortingProperty to provide the business sorting-by properties and it is bound to Sorting-By combobox. It has a property called PropertyPath that identifies the path that is used to generate a dynamic IOrderedQueryable<Entity> based on the currently selected value.

All the UI CRUD controls styles could be customized using an exposed dependency properties of type Style.

Usage

The following snippet XAML is from ProductList.xaml user control demonstrates the usage of GenericCrudControl:

<crud:GenericCRUDControl>
    <crud:GenericCRUDControl.SortingProperties>
        <crud:SortingProperty DisplayName="Product Name" PropertyPath="ProductName">
        </crud:SortingProperty>
        <crud:SortingProperty DisplayName="Category" PropertyPath="Category.CategoryName">
        </crud:SortingProperty>
        <crud:SortingProperty DisplayName="Supplier" PropertyPath="Supplier.ContactName">
        </crud:SortingProperty>
    </crud:GenericCRUDControl.SortingProperties>
    <crud:GenericCRUDControl.Columns>
        <crud:CustomDataGridColumn Header="Category Name" BindingExpression="Category.CategoryName">
        </crud:CustomDataGridColumn>
        <crud:CustomDataGridColumn Header="Product Name" BindingExpression="ProductName">
        </crud:CustomDataGridColumn>
        <crud:CustomDataGridColumn ColumnType="TemplateColumn" Header="Stock">
            <crud:CustomDataGridColumn.DataGridColumnTemplate>
                <DataTemplate>
                    <ProgressBar Maximum="150" Value="{Binding UnitsInStock}"></ProgressBar>
                </DataTemplate>
            </crud:CustomDataGridColumn.DataGridColumnTemplate>
        </crud:CustomDataGridColumn>
        <crud:CustomDataGridColumn Header="Supplier Name" 
         BindingExpression="Supplier.ContactName" Width="*"></crud:CustomDataGridColumn>
    </crud:GenericCRUDControl.Columns>
</crud:GenericCRUDControl>

3. View Model Layer

Contains the backbone logic of the solution as the following figure demonstrates:

The GenericCRUDBase<Entity> is the controller class that is inherited by your business view model, so the usage is like that:

public class ProductsListViewModel : GenericCRUDBase<Product>
{
    public ProductsListViewModel(ProductsSearchViewModel productsSearchViewModel, 
        ProductAddEditViewModel productAddEditViewModel)
        : base(productsSearchViewModel, productAddEditViewModel)
    {
        ListingIncludes = new Expression<Func<Product, object>>[]
        {
            p => p.Category,
            p => p.Supplier
        };
    }
}

It requires the SearchCriteriaBase<Entity> and AddEditEntityBase<Entity> concrete objects at the constructor. It is responsible for:

  • Subscribing to the change criteria events for the searching and pager actions
  • Loading data with searching, paging and sorting
  • Populating Add popup window with new entity using DialogService
  • Populating Edit popup window with the selected entity using DialogService
  • Deleting selected entities

The ListingIncludes is an array of lambda expressions that refers to the navigation properties needed to be included within data retrieval.

The AddEditEntityBase<Entity> is a base view model class that is responsible for saving Entity, reset Entity changes and it closes its associated window after successful saving as it inherits the base class PopupViewModelBase that has a delegate CloseAssociatedWindow defined in DialogService.
It has a ContentControl that will hold the concrete view resolved by WPF engine based on specified DataTemplate.

The SearchCriteriaBase<Entity> has two virtual methods that are overridden in the concrete class:

  • GetSearchCriteria() method that returns Expression<Func<Entity, bool>> based on the entered search criteria.
  • ResetSearchCriteria() method that resets the input controls.

Delegates

There are a few delegates to inject a business logic before/after abstracted logic like:

At GenericCrudBase<Entity>:

  • PostDataRetrievalDelegate: A delegate that is called after retrieving data from database to manipulate the data based on business logic (i.e. updating some properties).
  • PreAddEditDelegate: A delegate that is called before populating Add/Edit popup window in order to prepare its data based on business logic (i.e., base on Entity values rebind a combobox's ItemSource).
  • PostAddEditDelegate: A delegate that is called after successfully saving the Entity to apply a specific business logic.

The Result

The following screenshot demonstrates the result of applying WPF CrudControl on Northwind's Product module.

Third-Party Implementations

  • INotifyDataErrorInfo
  • IEditableObject
  • ObservableObject
  • RelayCommand<T>
  • RelayCommand
  • Dynamic Sorting
  • EnumToItemsSource

Conclusion

Using WPF CrudControl gives a huge productivity boost for straightforward Crud operations. It requires relatively minimal coding effort, while keeping it possible to customize its behavior.

You can enhance the WPF CrudControl in this repository and use it directly through this NuGet package.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here