You need to create customized reports based on Sharepoint data and you don’t have the access to the server to create a new web part to access it directly. You need to resort to other options to generate the report. Fortunately, Sharepoint allows some methods to access its data, so you can get the information you need. If you are using C#, you can use one of these three methods:

  • Client Side Object Model (CSOM) – this is a set of APIs that allow access to the Sharepoint data and allow you to maintain lists and documents in Sharepoint
  • REST API – with this API you can access Sharepoint data, not only using C#, but with any other platform that can perform and process REST requests, like web or mobile apps
  • SOAP Web Services – although this kind of access is being deprecated, there are a lot of programs that depend on it, so it’s being actively used until now. You can use this API with any platform that can process SOAP web services.

This article will show how to use these three APIs to access lists and documents from a Sharepoint server and put them in a WPF program, with an extra touch: the user will be able to export the data to Excel, for further manipulation.

To start, let’s create a WPF project in Visual Studio and name it SharepointAccess. We will use the MVVM pattern to develop it, so right click the References node in the Solution Explorer and select Manage NuGet Packages and add the MVVM Light package. That will add a ViewModel folder, with two files in it to your project. The next step is to make a correction in the ViewModelLocator.cs file. If you open it, you will see an error in the line

using Microsoft.Practices.ServiceLocation;

Just replace this using clause with

using CommonServiceLocator;

The next step is to link the DataContext of MainWindow to the MainViewModel, like it’s described in the header of ViewModelLocator:

<Window x:Class="SharepointAccess.MainWindow"
        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:local="clr-namespace:SharepointAccess"
        mc:Ignorable="d"
        Title="Sharepoint Access" Height="700" Width="900"
        DataContext="{Binding Source={StaticResource Locator}, Path=Main}">

Then, let’s add the UI to MainWindow.xaml:

<Grid>
    <Grid.Resources>
        <DataTemplate x:Key="DocumentsListTemplate">
            <StackPanel>
                <TextBlock Text="{Binding Title}" />
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Key="DocumentTemplate">
            <StackPanel Margin="0,5">
                <TextBlock Text="{Binding Title}" />
                <TextBlock Text="{Binding Url}" />

            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Key="FieldsListTemplate">
            <Grid >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Key}" TextTrimming="CharacterEllipsis"/>
                <TextBlock Text="{Binding Value}" Grid.Column="1" TextTrimming="CharacterEllipsis"/>
            </Grid>
        </DataTemplate>
    </Grid.Resources>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="40" />
        <RowDefinition Height="*" />
        <RowDefinition Height="40" />
    </Grid.RowDefinitions>
    <StackPanel Grid.Row="0" Grid.ColumnSpan="3" HorizontalAlignment="Stretch" Orientation="Horizontal">
        <TextBox Text="{Binding Address}" Width="400" Margin="5" HorizontalAlignment="Left"
                 VerticalContentAlignment="Center"/>
        <Button Content="Go" Command="{Binding GoCommand}" Width="65" Margin="5" HorizontalAlignment="Left"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal"  Grid.Row="0" Grid.Column="3" 
                      HorizontalAlignment="Right" Margin="5,5,10,5">
        <RadioButton Content=".NET Api" IsChecked="True" GroupName="ApiGroup" Margin="5,0"
                     Command="{Binding ApiSelectCommand}" CommandParameter="NetApi" />
        <RadioButton Content="REST" GroupName="ApiGroup"
                     Command="{Binding ApiSelectCommand}" CommandParameter="Rest" Margin="5,0"/>
        <RadioButton Content="SOAP"  GroupName="ApiGroup"
                     Command="{Binding ApiSelectCommand}" CommandParameter="Soap" Margin="5,0"/>
    </StackPanel>
    <ListBox Grid.Column="0" Grid.Row="1" ItemsSource="{Binding DocumentsLists}" 
             ItemTemplate="{StaticResource DocumentsListTemplate}"
             SelectedItem="{Binding SelectedList}"/>
    <ListBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding Documents}"
             ItemTemplate="{StaticResource DocumentTemplate}"
             SelectedItem="{Binding SelectedDocument}"/>
    <ListBox Grid.Column="2" Grid.Row="1" ItemsSource="{Binding Fields}"
             ItemTemplate="{StaticResource FieldsListTemplate}"
             />
    <TextBlock Text="{Binding ListTiming}" VerticalAlignment="Center" Margin="5" Grid.Row="2" Grid.Column="0" />
    <TextBlock Text="{Binding ItemTiming}" VerticalAlignment="Center" Margin="5" Grid.Row="2" Grid.Column="1" />
  </Grid>

The first line in the grid will have a text box to enter the address of the Sharepoint site, a button to go to the address and three radiobuttons to select the kind of access you want.

The main part of the window will have three listboxes to show the lists on the selected site, the documents in each list and the properties of the document.

As you can see, we’ve used data binding to fill the properties of the UI controls. We’re using the MVVM pattern and all these propperties should be bound to properties in the ViewModel. The buttons and radiobuttons have their Command properties bound to a property in the ViewModel, so we don`t have to add code to the code behind file. We’ve also used templates for the items in the listboxes, so the data is properly presented.

The last line will show the timings for getting the data.

If you run the program, it will run without errors and you will get an UI like this, that doesn’t do anything:

 

It’s time to add the properties in the MainViewModel to achieve the functionality we want. Before that, we’ll add two classes to manipulate the data that comes from the Sharepoint server, no matter the access we are using. Create a new folder named Model, and add these two files, Document.cs and DocumentsList.cs:

public class Document
{
    public Document(string id, string title, 
        Dictionary<string, object> fields,
        string url)
    {
        Id = id;
        Title = title;
        Fields = fields;
        Url = url;
    }
    public string Id { get; }
    public string Title { get; }
    public string Url { get; }
    public Dictionary<string, object> Fields { get; }
}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>
public class DocumentsList
{
    public DocumentsList(string title, string description)
    {
        Title = title;
        Description = description;
    }

    public string Title { get; }
    public string Description { get; }
}

These are very simple classes, that will store the data that comes from the Sharepoint server. The next step is to get the data from the server. For that we will use a repository that gets the data and returns it using these two classes. Create a new folder, named Repository and add a new interface, named IListRepository:

public interface IListRepository
{
    Task<List<Document>> GetDocumentsFromListAsync(string title);
    Task<List<DocumentsList>> GetListsAsync();
}

This interface declares two methods, GetListsAsync and GetDocumentsFromListAsync. These are asynchronous methods because we don want them to block the UI while they are being called. Now, its time to create the first access to Sharepoint, using the CSOM API. For that, you must add the NuGet package Microsoft.SharePointOnline.CSOM . This package will provide all the APIs to access Sharepoint data. We can now create our first repository. In the Repository folder, add a new class and name it CsomListRepository.cs:

public class CsomListRepository : IListRepository
{
    private string _sharepointSite;

    public CsomListRepository(string sharepointSite)
    {
        _sharepointSite = sharepointSite;
    }

    public Task<List<DocumentsList>> GetListsAsync()
    {
        return Task.Run(() =>
        {
            using (var context = new ClientContext(_sharepointSite))
            {
                var web = context.Web;
                
                var query = web.Lists.Include(l => l.Title, l => l.Description)
                     .Where(l => !l.Hidden && l.ItemCount > 0);

                var lists = context.LoadQuery(query);
                context.ExecuteQuery();

                return lists.Select(l => new DocumentsList(l.Title, l.Description)).ToList();
            }
        });
    }

    public Task<List<Document>> GetDocumentsFromListAsync(string listTitle)
    {
        return Task.Run(() =>
        {
            using (var context = new ClientContext(_sharepointSite))
            {
                var web = context.Web;
                var list = web.Lists.GetByTitle(listTitle);
                var query = new CamlQuery();

                query.ViewXml = "<View />";
                var items = list.GetItems(query);
                context.Load(list,
                    l => l.Title);
                context.Load(items, l => l.IncludeWithDefaultProperties(
                    i => i.Folder, i => i.File, i => i.DisplayName));
                context.ExecuteQuery();

                return items
                    .Where(i => i["Title"] != null)
                    .Select(i => new Document(i["ID"].ToString(), 
                    i["Title"].ToString(), i.FieldValues, i["FileRef"].ToString()))
                    .ToList();
            }
        });
    }
}

For accessing the Sharepoint data, we have to create a ClientContext, passing the Sharepoint site to access. Then, we get a reference to the Web property of the context and then we do a query for the lists that aren’t hidden and that have any items in them. The query should return with the titles and description of the lists in the website. To get the documents from a list we use a similar way: create a context, then a query and load the items of a list.

We can call this code in the creation of the ViewModel:

private IListRepository _listRepository;

public MainViewModel()
{
    Address = ConfigurationManager.AppSettings["WebSite"];
    GoToAddress();
}

This code will get the initial web site url from the configuration file for the app and call GoToAddress:

private async void GoToAddress()
{
    var sw = new Stopwatch();
    sw.Start();
    _listRepository = new CsomListRepository(Address);

    DocumentsLists = await _listRepository.GetListsAsync();
    ListTiming = $"Time to get lists: {sw.ElapsedMilliseconds}";
    ItemTiming = "";
}

The method calls the GetListsAsync method of the repository to get the lists and sets the DocumentsLists property with the result.

We must also declare some properties that will be used to bind to the control properties in the UI:

public List<DocumentsList> DocumentsLists
{
    get => _documentsLists;
    set
    {
        _documentsLists = value;
        RaisePropertyChanged();
    }
}
public string ListTiming
{
    get => _listTiming;
    set
    {
        _listTiming = value;
        RaisePropertyChanged();
    }
}
public string ItemTiming
{
    get => itemTiming;
    set
    {
        itemTiming = value;
        RaisePropertyChanged();
    }
}
public string Address
{
    get => address;
    set
    {
        address = value;
        RaisePropertyChanged();
    }
}

These properties will trigger the PropertyChanged event handler when they are modified, so the UI can be notified of their change.

If you run this code, you will have something like this:

Then, we must get the documents when we select a list. This is done when the SelectedList property changes:

public DocumentsList SelectedList
{
    get => _selectedList;
    set
    {
        if (_selectedList == value)
            return;
        _selectedList = value;
        GetDocumentsForList(_selectedList);
        RaisePropertyChanged();
    }
}

GetDocumentsForList is:

private async void GetDocumentsForList(DocumentsList list)
{
    var sw = new Stopwatch();
    sw.Start();
    if (list != null)
    {
        Documents = await _listRepository.GetDocumentsFromListAsync(list.Title);
        ItemTiming = $"Time to get items: {sw.ElapsedMilliseconds}";
    }
    else
    {
        Documents = null;
        ItemTiming = "";
    }
}

You have to declare the Documents and the Fields properties:

public List<Document> Documents
{
    get => documents;
    set
    {
        documents = value;
        RaisePropertyChanged();
    }
}

public Dictionary<string, object> Fields => _selectedDocument?.Fields;

One other change that must be made is to create a property named SelectedDocument that will be bound to the second list. When the user selects a document, it will fill the third list with the document’s properties:

public Document SelectedDocument
{
    get => _selectedDocument;
    set
    {
        if (_selectedDocument == value)
            return;
        _selectedDocument = value;
        RaisePropertyChanged();
        RaisePropertyChanged("Fields");
    }
}

Now, when you click on a list, you get all documents in it. Clicking on a document, opens its properties:

Everything works with the CSOM access, so it’s time to add the other two ways to access the data: REST and SOAP. We will implement these by creating two new repositories that will be selected at runtime.

To get items using the REST API, we must do HTTP calls to http://<website>/_api/Lists and  http://<website>/_api/Lists/GetByTitle(‘title’)/Items. In the Repository folder, create a new class and name it RestListRepository. This class should implement the IListRepository interface:

public class RestListRepository : IListRepository
{
    public Task<List<Document>> GetDocumentsFromListAsync(string title)
    {
        throw new NotImplementedException();
    }

    public Task<List<DocumentsList>> GetListsAsync()
    {
        throw new NotImplementedException();
    }
}

GetListsAsync will be:

private XNamespace ns = "http://www.w3.org/2005/Atom";
public Task<List<DocumentsList>> GetListsAsync()
{
    var doc = await GetResponseDocumentAsync(_sharepointSite + "Lists");
    if (doc == null)
        return null;

    var entries = doc.Element(ns + "feed").Descendants(ns + "entry");
    return entries.Select(GetDocumentsListFromElement)
        .Where(d => !string.IsNullOrEmpty(d.Title)).ToList();
}

GetResponseDocumentAsync will issue a HTTP Get request, will process the response and will return an XDocument:

public async Task<XDocument> GetResponseDocumentAsync(string url)
{
    var handler = new HttpClientHandler
    {
        UseDefaultCredentials = true
    };
    HttpClient httpClient = new HttpClient(handler);
    var headers = httpClient.DefaultRequestHeaders;
    var header = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
        "(KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36";
    if (!headers.UserAgent.TryParseAdd(header))
    {
        throw new Exception("Invalid header value: " + header);
    }
    Uri requestUri = new Uri(url);

    try
    {
        var httpResponse = await httpClient.GetAsync(requestUri);
        httpResponse.EnsureSuccessStatusCode();
        var httpResponseBody = await httpResponse.Content.ReadAsStringAsync();
        return XDocument.Parse(httpResponseBody);
    }
    catch
    {
        return null;
    }
}

The response will be a XML String. We could get the response as a Json object if we pass the accept header as application/json.  After the document is parsed, we process all entry elements, retrieving the lists, in GetDocumentsListFromElement:

private XNamespace mns = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
private XNamespace dns = "http://schemas.microsoft.com/ado/2007/08/dataservices";
private DocumentsList GetDocumentsListFromElement(XElement e)
{
    var element = e.Element(ns + "content")?.Element(mns + "properties");
    if (element == null)
        return new DocumentsList("", "");
    bool.TryParse(element.Element(dns + "Hidden")?.Value ?? "true", out bool isHidden);
    int.TryParse(element.Element(dns + "ItemCount")?.Value ?? "0", out int ItemCount);
    return !isHidden && ItemCount > 0 ?
      new DocumentsList(element.Element(dns + "Title")?.Value ?? "",
        element.Element(dns + "Description")?.Value ?? "") :
      new DocumentsList("", "");
}

Here we filter the list by parsing the Hidden and ItemCount properties and returning an empty document if the document is hidden or has no items. GetDocumentsFromListAsync is:

private async Task<Document> GetDocumentFromElementAsync(XElement e)
{
    var element = e.Element(ns + "content")?.Element(mns + "properties");
    if (element == null)
        return new Document("", "", null, "");
    var id = element.Element(dns + "Id")?.Value ?? "";
    var title = element.Element(dns + "Title")?.Value ?? "";
    var description = element.Element(dns + "Description")?.Value ?? "";
    var fields = element.Descendants().ToDictionary(el => el.Name.LocalName, el => (object)el.Value);
    int.TryParse(element.Element(dns + "FileSystemObjectType")?.Value ?? "-1", out int fileType);
    string docUrl = "";

    var url = GetUrlFromTitle(e, fileType == 0 ? "File" : "Folder");
    if (url != null)
    {
        var fileDoc = await GetResponseDocumentAsync(_sharepointSite + url);
        docUrl = fileDoc.Element(ns + "entry")?.
            Element(ns + "content")?.
            Element(mns + "properties")?.
            Element(dns + "ServerRelativeUrl")?.
            Value;
    }

    return new Document(id, title, fields, docUrl);
}

It parses the XML and extracts a document and its properties. GetUrlFromTitle gets the Url from the Title property and is:

private string GetUrlFromTitle(XElement element, string title)
{
    return element.Descendants(ns + "link")
            ?.FirstOrDefault(e1 => e1.Attribute("title")?.Value == title)
            ?.Attribute("href")?.Value;
}

The third access method is using the Soap service that Sharepoint makes available. This access method is listed as deprecated, but it’s still alive. You have to create a reference to the http://<website>/_vti_bin/Lists.asmx and create a WCF client for it. I preferred to create a .NET 2.0 Client instead of a WCF service, as I found easier to authenticate with this service.

In Visual Studio, right-click the References node and select the Add Service Reference. Then, click on the Advanced button and then, Add Web Reference. Put the url in the box and click the arrow button:

When you click the Add Reference button, the reference will be added to the project and it can be used. Create a new class in the Repository folder and name it SoapListRepository. Make the class implement the IListRepository interface. The GetListsAsync method will be:

XNamespace ns = "http://schemas.microsoft.com/sharepoint/soap/";
private Lists _proxy;

public async Task<List<DocumentsList>> GetListsAsync()
{
    var tcs = new TaskCompletionSource<XmlNode>();
    _proxy = new Lists
    {
        Url = _address,
        UseDefaultCredentials = true
    };
    _proxy.GetListCollectionCompleted += ProxyGetListCollectionCompleted;
    _proxy.GetListCollectionAsync(tcs);
    XmlNode response;
    try
    {
        response = await tcs.Task;
    }
    finally
    {
        _proxy.GetListCollectionCompleted -= ProxyGetListCollectionCompleted;
    }

    var list = XElement.Parse(response.OuterXml);
    var result = list?.Descendants(ns + "List")
        ?.Where(e => e.Attribute("Hidden").Value == "False")
        ?.Select(e => new DocumentsList(e.Attribute("Title").Value,
        e.Attribute("Description").Value)).ToList();
    return result;
}

private void ProxyGetListCollectionCompleted(object sender, GetListCollectionCompletedEventArgs e)
{
    var tcs = (TaskCompletionSource<XmlNode>)e.UserState;
    if (e.Cancelled)
    {
        tcs.TrySetCanceled();
    }
    else if (e.Error != null)
    {
        tcs.TrySetException(e.Error);
    }
    else
    {
        tcs.TrySetResult(e.Result);
    }
}

As we are using the .NET 2.0 web service, in order to convert the call to an asynchronous method, we must use a TaskCompletionSource to detect when the call to the service returns. Then we fire the call to the service. When it returns, the completed event is called and sets the TaskCompletionSource to the desired state: cancelled, if the call was cancelled, exception, if there was an error or set the result if the call succeeds. Then, we remove the event handler for the completed event and process the result (a XmlNode), to transform into a list of DocumentsList.

The call to GetDocumentsFromListAsync is very similar to GetListsAsync:

XNamespace rs = "urn:schemas-microsoft-com:rowset";
XNamespace z = "#RowsetSchema";

public async Task<List<Document>> GetDocumentsFromListAsync(string title)
{
    var tcs = new TaskCompletionSource<XmlNode>();
    _proxy = new Lists
    {
        Url = _address,
        UseDefaultCredentials = true
    };
    _proxy.GetListItemsCompleted += ProxyGetListItemsCompleted;
    _proxy.GetListItemsAsync(title, "", null, null, "", null, "", tcs);
    XmlNode response;
    try
    {
        response = await tcs.Task;
    }
    finally
    {
        _proxy.GetListItemsCompleted -= ProxyGetListItemsCompleted;
    }

    var list = XElement.Parse(response.OuterXml);

    var result = list?.Element(rs + "data").Descendants(z + "row")
        ?.Select(e => new Document(e.Attribute("ows_ID")?.Value,
        e.Attribute("ows_LinkFilename")?.Value, AttributesToDictionary(e),
        e.Attribute("ows_FileRef")?.Value)).ToList();
    return result;
}

private Dictionary<string, object> AttributesToDictionary(XElement e)
{
    return e.Attributes().ToDictionary(a => a.Name.ToString().Replace("ows_", ""), a => (object)a.Value);
}

private void ProxyGetListItemsCompleted(object sender, GetListItemsCompletedEventArgs e)
{
    var tcs = (TaskCompletionSource<XmlNode>)e.UserState;
    if (e.Cancelled)
    {
        tcs.TrySetCanceled();
    }
    else if (e.Error != null)
    {
        tcs.TrySetException(e.Error);
    }
    else
    {
        tcs.TrySetResult(e.Result);
    }
}

The main difference is the processing of the response, to get the documents list. Once you have the two methods in place, the only thing to do is select the correct repository in MainViewModel. For that, we create an enum for the API Selection:

public enum ApiSelection
{
    NetApi,
    Rest,
    Soap
};

Then, we need to declare a command bound to the radiobuttons, that will receive a string with the enum value:

public ICommand ApiSelectCommand =>
    _apiSelectCommand ?? (_apiSelectCommand = new RelayCommand<string>(s => SelectApi(s)));

private void SelectApi(string s)
{
    _selectedApi = (ApiSelection)Enum.Parse(typeof(ApiSelection), s, true);
    GoToAddress();
}

The last step is to select the repository in the GoToAddress method:

private async void GoToAddress()
{
    var sw = new Stopwatch();
    sw.Start();
    _listRepository = _selectedApi == ApiSelection.Rest ?
        (IListRepository)new RestListRepository(Address) :
        _selectedApi == ApiSelection.NetApi ?
        (IListRepository)new CsomListRepository(Address) :
        new SoapListRepository(Address);

    DocumentsLists = await _listRepository.GetListsAsync();
    ListTiming = $"Time to get lists: {sw.ElapsedMilliseconds}";
    ItemTiming = "";
}

With the code in place, you can run the app and see the data shown for each API.

One last change to the program is to add a command bound to the Go button, so you can change the address of the web site and get the lists and documents for the new site:

public ICommand GoCommand =>
            _goCommand ?? (_goCommand = new RelayCommand(GoToAddress, () => !string.IsNullOrEmpty(Address)));

This command has an extra touch: it will only enable the button if there is an address in the address box. If it’s empty, the button will be disabled. Now you can run the program, change the address of the website, and get the lists for the new website.

Conclusions

As you can see, we’ve created a WPF program that uses the MVVM pattern and accesses Sharepoint data using three different methods – it even has a time measuring feature, so you can check the performance difference and choose the right one for your case.

The full source code for this project is at https://github.com/bsonnino/SharepointAccess

Sometimes, you need to parse some html data to do some processing and present it to the user. That may be a daunting task, as some pages can become very complex and it may be difficult to do it.

For that, you can use an excellent tool, named HTML Agility Pack. With it, you can parse HTML from a string, a file, a web site or even from a WebBrowser: you can add a WebBrowser to your app, navigate to an URL and parse the data from there.

In this article, I’ll show how to make a query in Bing, retrieve and parse the response. For that, we need to create the query url and pass it to Bing. You may ask why I’m querying Bing and not Google – I’m doing that because Google makes it difficult to get its data, and I want to show you how to use HTML Agility Pack, and not how to retrieve data from Google :-). The query should be something like this:

https://www.bing.com/search?q=html+agility+pack&count=100

We will use the Query (q) and the number of results (count) parameters. With them, we can create our program. We will create a WPF program that gets the query text, parses it and presents the results in a Listbox.

Create a new WPF program and name it BingSearch.

The next step is to add the HTML Agility Pack to the project. Right-click the References node in the Solution Explorer and select Manage NuGet Packages. Then add the Html Agility Pack to the project.

Then, in the main window, add this XAML code:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel Grid.Row="0" Orientation ="Horizontal" 
                Margin="5,0" VerticalAlignment="Center">
        <TextBlock Text="Search" VerticalAlignment="Center"/>
        <TextBox x:Name="TxtSearch" Width="300" Height="30" 
                 Margin="5,0" VerticalContentAlignment="Center"/>
    </StackPanel>
    <Button Grid.Row="0" HorizontalAlignment="Right" 
            Content="Search" Margin="5,0" VerticalAlignment="Center"
            Width="65" Height="30" Click="SearchClick"/>
    <ListBox Grid.Row="1" x:Name="LbxResults" />
</Grid>

Right click in the button’s click event handler in the XAML and press F12 to add the handler in code and go to it. Then, add this code to the handler:

private async void SearchClick(object sender, RoutedEventArgs e)
{
    if (string.IsNullOrWhiteSpace(TxtSearch.Text))
        return;
    var queryString = WebUtility.UrlEncode(TxtSearch.Text);
    var htmlWeb = new HtmlWeb();
    var query = $"https://bing.com/search?q={queryString}&count=100";
    var doc = await htmlWeb.LoadFromWebAsync(query);
    var response = doc.DocumentNode.SelectSingleNode("//ol[@id='b_results']");
    var results = response.SelectNodes("//li[@class='b_algo']");
    if (results == null)
    {
        LbxResults.ItemsSource = null;
        return;
    }
    var searchResults = new List<SearchResult>();
    foreach (var result in results)
    {
        var refNode = result.Element("h2").Element("a");
        var url = refNode.Attributes["href"].Value;
        var text = refNode.InnerText;
        var description = result.Element("div").Element("p").InnerText;
        searchResults.Add(new SearchResult(text, url, description));
    }
    LbxResults.ItemsSource = searchResults;
}

Initially we encode the text to search to add it to the query and create the query string. Then we call the LoadFromWebAsync method to load the HTML data from the query response. When the response comes, we get the response node, from the ordered list with id b_results and extract from it the individual results. Finally, we parse each result and add it to a list of SearchResult, and assign the list to the items in the ListBox. You can note that we can finde the nodes using XPath, like in

var results = response.SelectNodes("//li[@class='b_algo']");

Or we can traverse the elements and get the text of the resulting node with something like:

var refNode = result.Element("h2").Element("a");
var url = refNode.Attributes["href"].Value;
var text = refNode.InnerText;
var description = WebUtility.HtmlDecode(
    result.Element("div").Element("p").InnerText);

SearchResult is declared as:

internal class SearchResult
{
    public string Text { get; }
    public string Url { get; }
    public string Description { get; }

    public SearchResult(string text, string url, string description)
    {
        Text = text;
        Url = url;
        Description = description;
    }
}

if you run the program, you will see something like this:

The data isn’t displayed because we haven’t defined any data template for the list items. You can define an item template like that in the XAML:

<ListBox.ItemTemplate>
    <DataTemplate>
        <StackPanel Margin="0,3">
            <TextBlock Text="{Binding Text}" FontWeight="Bold"/>
            <TextBlock >
              <Hyperlink NavigateUri="{Binding Url}" RequestNavigate="LinkNavigate">
                 <TextBlock Text="{Binding Url}"/>
              </Hyperlink>
            </TextBlock>
            <TextBlock Text="{Binding Description}" TextWrapping="Wrap"/>
        </StackPanel>
    </DataTemplate>
</ListBox.ItemTemplate>

The LinkNavigate event handler is:

private void LinkNavigate(object sender, RequestNavigateEventArgs e)
{
    System.Diagnostics.Process.Start(e.Uri.AbsoluteUri);
}

Now, when you run the program, you will get something like this:

You can click on the hyperlink and it will open a browser window with the selected page. We can even go further and add a WebBrowser to our app that will show the selected page when you click on an item. For that, you have to modify the XAML code with something like this:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <StackPanel Grid.Row="0" Orientation ="Horizontal" 
                Margin="5,0" VerticalAlignment="Center">
        <TextBlock Text="Search" VerticalAlignment="Center"/>
        <TextBox x:Name="TxtSearch" Width="300" Height="30" 
                 Margin="5,0" VerticalContentAlignment="Center"/>
    </StackPanel>
    <Button Grid.Row="0" HorizontalAlignment="Right" 
            Content="Search" Margin="5,0" VerticalAlignment="Center"
            Width="65" Height="30" Click="SearchClick"/>
    <ListBox Grid.Row="1" x:Name="LbxResults" 
             ScrollViewer.HorizontalScrollBarVisibility="Disabled"
             SelectionChanged="LinkChanged">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Margin="0,3">
                    <TextBlock Text="{Binding Text}" FontWeight="Bold"/>
                    <TextBlock >
                      <Hyperlink NavigateUri="{Binding Url}" RequestNavigate="LinkNavigate">
                         <TextBlock Text="{Binding Url}"/>
                      </Hyperlink>
                    </TextBlock>
                    <TextBlock Text="{Binding Description}" TextWrapping="Wrap"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <WebBrowser Grid.Column="1" Grid.RowSpan="2" x:Name="WebPage"  />
</Grid>

We’ve added a second column to the window and added a WebBrwser to it, then added a SelectionChanged event to the listbox, so we can navigate to the selected page.

The SelectionChanged event handler is:

private void LinkChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.AddedItems?.Count > 0)
    {
        WebPage.Navigate(((SearchResult)e.AddedItems[0]).Url);
    }
}

Now, when you run the app and click on a result, it will show the page in the WebBrowser. One thing that happened is that, sometimes a Javascript error pops up. To remove these errors, I used the solution obtained from here:

public MainWindow()
{
    InitializeComponent();
    WebPage.Navigated += (s, e) => SetSilent(WebPage, true);
}

public static void SetSilent(WebBrowser browser, bool silent)
{
    if (browser == null)
        throw new ArgumentNullException("browser");

    // get an IWebBrowser2 from the document
    IOleServiceProvider sp = browser.Document as IOleServiceProvider;
    if (sp != null)
    {
        Guid IID_IWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046");
        Guid IID_IWebBrowser2 = new Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E");

        object webBrowser;
        sp.QueryService(ref IID_IWebBrowserApp, ref IID_IWebBrowser2, out webBrowser
        if (webBrowser != null)
        {
            webBrowser.GetType().InvokeMember("Silent", 
                BindingFlags.Instance | BindingFlags.Public | 
                BindingFlags.PutDispProperty, null, webBrowser, 
                new object[] { silent });
        }
    }
}


[ComImport, Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), 
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IOleServiceProvider
{
    [PreserveSig]
    int QueryService([In] ref Guid guidService, [In] ref Guid riid, 
        [MarshalAs(UnmanagedType.IDispatch)] out object ppvObject);
}

With this code, the Javascript errors disappear and when you run the app, you will see something like this:

As you can see, the HTML Agility Pack makes it easy to process and parse HTML Pages, allowing you to manipulate them the way you want.

The full source code for this article is in https://github.com/bsonnino/BingSearch

Some time ago I wrote a post about converting a WPF application into .NET Core. One thing that called my attention in this Build 2019 talk was that the performance for file enumerations was enhanced in the .NET core apps. So I decided to check this with my own app and see what happens in my machine.

I added some measuring data in the app, so I could see what happens there:

private async void StartClick(object sender, RoutedEventArgs e)
{
    var fbd = new WPFFolderBrowserDialog();
    if (fbd.ShowDialog() != true)
        return;
    FilesList.ItemsSource = null;
    ExtList.ItemsSource = null;
    ExtSeries.ItemsSource = null;
    AbcList.ItemsSource = null;
    AbcSeries.ItemsSource = null;
    var selectedPath = fbd.FileName;
    Int64 minSize;
    if (!Int64.TryParse(MinSizeBox.Text, out minSize))
        return;
    List<FileInfo> files = null;
    var sw = new Stopwatch();
    var timeStr = "";
    await Task.Factory.StartNew(() =>
    {
       sw.Start();
       files = GetFilesInDirectory(selectedPath).ToList();
       timeStr = $" {sw.ElapsedMilliseconds} for enumeration";
       sw.Restart();
       files = files.Where(f => f.Length >= minSize)
         .OrderByDescending(f => f.Length)
         .ToList();
       timeStr += $" {sw.ElapsedMilliseconds} for ordering and filtering";
    });
    var totalSize = files.Sum(f => f.Length);
    TotalFilesText.Text = $"# Files: {files.Count}";
    LengthFilesText.Text = $"({totalSize:N0} bytes)";
    sw.Restart();
    FilesList.ItemsSource = files;
    var extensions = files.GroupBy(f => f.Extension)
        .Select(g => new { Extension = g.Key, Quantity = g.Count(), Size = g.Sum(f => f.Length) })
        .OrderByDescending(t => t.Size).ToList();
    ExtList.ItemsSource = extensions;
    ExtSeries.ItemsSource = extensions;
    var tmp = 0.0;
    var abcData = files.Select(f =>
    {
        tmp += f.Length;
        return new { f.Name, Percent = tmp / totalSize * 100 };
    }).ToList();
    AbcList.ItemsSource = abcData;
    AbcSeries.ItemsSource = abcData.OrderBy(d => d.Percent).Select((d, i) => new { Item = i, d.Percent });
    timeStr += $"  {sw.ElapsedMilliseconds} to fill data";
    TimesText.Text = timeStr;
}

That way, I could measure two things: the time to enumerate the files and the times to sort, filter and assign the files to the lists. Then, I run the two programs, to see what happened.

The machine I’ve run is a Virtual machine with a Core I5 and 4 virtual processors and a virtualized hard disk, with 12,230 files (93.13 GB of data). The measures may vary on your machine, but the differences should be comparable. To avoid bias, I ran 3 times each program (in Admin mode), then rebooted and run the other one.

Here are the results I’ve got:

Run Enumerate Sort/Filter Assign
.NET
1 137031 96 43
2 58828 56 9
3 59474 55 8
Avg 85111 69 20
.NET Core
1 91105 120 32
2 33422 90 14
3 32907 87 20
Avg 52478 99 22

 

As you can see by the numbers, the .NET Core application improved a lot the times for file enumeration, but still lacks some effort for sorting/filtering and assigning data to the UI lists. But that’s not bad for a platform still in preview!

If you do some testingfor the performance, I’m curious to see what you’ve got, you can put your results and comments in the Comments section.

 

One thing that has been recently announced by Microsoft is the availability of .NET Core 3. With it, you will be able to create WPF and Winforms apps with .NET Core. And one extra bonus is that both WPF and Winforms are being open sourced. You can check these in https://github.com/dotnet/wpf and https://github.com/dotnet/winforms.

The first step to create a .NET Core WPF program is to download the .NET Core 3.0 preview from https://dotnet.microsoft.com/download/dotnet-core/3.0. Once you have it installed, you can check that it was installed correctly by open a Command Line window and typing dotnet –info and seeing the installed version:

:

With that in place, you can change the current folder to a new folder and type

dotnet new wpf
dotnet run

This will create a new .NET Core 3.0 WPF project and will compile and run it. You should get something like this:

If you click on the Exit button, the application exits. If you take a look at the folder, you will see that it generated the WPF project file, App.xaml and App.xaml.cs, MainWindow.xaml and MainWindow.xaml.cs. The easiest way to edit these files is to use Visual Studio Code. Just open Visual Studio Code and go to menu File/Open Folder and open the folder for the project. There you will see the project files and will be able to run and debug your code:

A big difference can be noted in the csproj file. If you open it, you will see something like this:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

That’s very simple and there’s nothing else in the project file. There are some differences between this project and other types of .NET Core, like the console one:

  • The output type is WinExe, and not Exe, in the console app
  • The UseWPF clause is there and it’s set to true

Now, you can modify and run the project inside VS Code. Modify MainWindow.xaml and put this code in it:

<Window x:Class="DotNetCoreWPF.MainWindow" 
    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:local="clr-namespace:DotNetCoreWPF" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>

            <TextBlock Text="Id"      Grid.Column="0" Grid.Row="0" Margin="5" VerticalAlignment="Center"/>
            <TextBlock Text="Name"    Grid.Column="0" Grid.Row="1" Margin="5" VerticalAlignment="Center"/>
            <TextBlock Text="Address" Grid.Column="0" Grid.Row="2" Margin="5" VerticalAlignment="Center"/>
            <TextBlock Text="City"    Grid.Column="0" Grid.Row="3" Margin="5" VerticalAlignment="Center"/>
            <TextBlock Text="Email"   Grid.Column="0" Grid.Row="4" Margin="5" VerticalAlignment="Center"/>
            <TextBlock Text="Phone"   Grid.Column="0" Grid.Row="5" Margin="5" VerticalAlignment="Center"/>
            <TextBox Grid.Column="1" Grid.Row="0" Margin="5"/>
            <TextBox Grid.Column="1" Grid.Row="1" Margin="5"/>
            <TextBox Grid.Column="1" Grid.Row="2" Margin="5"/>
            <TextBox Grid.Column="1" Grid.Row="3" Margin="5"/>
            <TextBox Grid.Column="1" Grid.Row="4" Margin="5"/>
            <TextBox Grid.Column="1" Grid.Row="5" Margin="5"/>
        </Grid>
        <Button Content="Submit" Width="65" Height="35" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0"/>
    </Grid>
</Window>

Now, you can compile and run the app in VS Code with F5, and you will get something like this:

If you don’t want to use Visual Studio Code, you can edit your project in Visual Studio 2019. The first preview still doesn’t have a visual editor for the XAML file, but you can edit the XAML file in the editor, it will work fine.

Porting a WPF project to .NET Core

To port a WPF project to .NET Core, you should run the Portability Analyzer tool first, to see what problems you will find before porting it to .NET Core. This tool can be found here. You can download it and run on your current application, and check what APIs that are not portable.

I will be porting my DiskAnalisys project. This is a simple project, that uses the File.IO functions to enumerate the files in a folder and uses two NuGet packages to add a Folder Browser and Charts to WPF. The first step is to run the portability analysis on it. Run the PortabilityAnalizer app and point it to the folder where the executable is located:

When you click on the Analyze button, it will analyze the executable and generate an Excel spreadsheet with the results:

As you can see, all the code is compatible with .NET Core 3.0. So, let’s port it to .NET Core 3.0. I will show you three ways to do it: creating a new project, updating the .csproj file and using a tool.

Upgrading by Creating a new project

This way needs the most work, but it’s the simpler to fix. Just create a new folder and name it DiskAnalysisCorePrj. Then open a command line window and change the directory to the folder you’ve created. Then, type these commands:

dotnet new wpf
dotnet add package wpffolderbrowser
dotnet add package dotnetprojects.wpf.toolkit
dotnet run

These commands will create the WPF project, add the two required NuGet packages and run the default app. You may see a warning like this:

D:\Documentos\Artigos\Artigos\CSharp\WPFCore\DiskAnalysisCorePrj\DiskAnalysisCorePrj.csproj : warning NU1701: Package 'DotNetProjects.Wpf.Toolkit 5.0.43' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.
D:\Documentos\Artigos\Artigos\CSharp\WPFCore\DiskAnalysisCorePrj\DiskAnalysisCorePrj.csproj : warning NU1701: Package 'WPFFolderBrowser 1.0.2' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.
D:\Documentos\Artigos\Artigos\CSharp\WPFCore\DiskAnalysisCorePrj\DiskAnalysisCorePrj.csproj : warning NU1701: Package 'DotNetProjects.Wpf.Toolkit 5.0.43' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.
D:\Documentos\Artigos\Artigos\CSharp\WPFCore\DiskAnalysisCorePrj\DiskAnalysisCorePrj.csproj : warning NU1701: Package 'WPFFolderBrowser 1.0.2' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.

This means that the NuGet packages weren’t converted to .NET Core 3.0, but they are still usable (remember, the compatibility report showed 100% compatibility). Then, copy MainWindow.xaml and MainWindow.xaml.cs from the original folder to the new one. We don’t need to copy any other files, as no other files were changed. Then, type

dotnet run

and the program is executed:

Converting by Changing the .csproj file

This way is very simple, just changing the project file, but can be challenging, especially for very large projects. Just create a new folder and name it DiskAnalysisCoreCsp. Copy all files from the main folder of the original project (there’s no need of copying the Properties folder) and edit the .csproj file, changing it to:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="dotnetprojects.wpf.toolkit" Version="5.0.43" />
    <PackageReference Include="wpffolderbrowser" Version="1.0.2" />
  </ItemGroup>
</Project>

Then, type

dotnet run

and the program is executed.

Converting using a tool

The third way is to use a tool to convert the project. You must install the conversion extension created by Brian Lagunas, from here. Then, open your WPF project in Visual Studio, right-click in the project and select “Convert Project to .NET Core 3”.

That’s all. You now have a NET Core 3 app. If you did that in Visual Studio 2017, you won’t be able to open the project, you will need to compile it with dotnet run, or open it in Visual Studio code.

Conclusions

As you can see, although this is the first preview of WPF .NET Core, it has a lot of work done, and you will be able to port most of your WPF projects to .NET Core.

In a previous post, I’ve shown how to package a Delphi application for the Windows Store, and in this post I’ve shown how to package a WPF application. In both cases, the apps were packaged alone or with their references, so there was no trouble to package them to the store.

In the last post, I’ve refactored the Financial Calculator into a main app and a dll, so the code for the calculators could be reusable and testable. Now we have to package the apps for the store again, but this time there will be an extra task: to package the dll together with the app. In this article, I’ll show how to do that for both applications, the Delphi and the WPF one.

Packaging a Delphi app

To package the Delphi app, we just need to go to the project, select the Application Store configuration, then go to Project/Options/Provisioning and select the Ad Hoc deployment and add the certificate we created earlier. Then, we run the packaging and install the appx file. When you install and run the app, you will see a message like this:

That’s because the dll wasn’t packaged with the executable. If you rename the appx file to zip and open it with a zip manager (yes, an appx file is a zip file with another extension), you will see something like this:

As you can see, the appx file is a zip file with all the content needed to run the app. If you take a look at it, you will see that the dll isn’t there, that’s why you get the message. There is nothing that says that the dll should be added to the package, there are no references and the load of the dll is only done at runtime, so the packager doesn’t knows the dll is needed. We must do some things, so the dll is packaged with the app.

Go to the Projects window and right click on the FinCalc.exe node and select the Add option. Then add the FinCalcDll.dll to the project:

Now, when you rebuild the app, you will see that FinCalcDll.dll was added to the appx file and it will be used when you install it from the store:

There is something to note, here: this setting won’t work for the normal app. If you want to run the app as a normal app, you must continue to xcopy the dll to the output directory, this setting won’t copy it to the output directory. So, to be safe, do both things:

  • Add the dll to the project
  • Xcopy the dll to the output dir

Packaging a WPF app

Now that we’ve packaged the Delphi app with the dll, we must package the WPF app. To create a package to a WPF app, we must create a packaging project in Visual Studio:

Once the new project is created, in the Solution Explorer, you must right-click in the Applications node and select Add Reference. Then you must add the WPF project to the package. Then, right click on the project node and select Store/Create App Packages. There you can select if you want to create packages for the Store or for Sideloading:

If you want to create an app for the store, you must have a developer account and associate a name for the app in the store. We will choose to sideload the app:

Here we must note some things: as our dll is a 32 bit one, we must choose the x86 package only. We don’t need and app bundle, where all the platforms are packaged in a single file – we will use the “Generate app bundle” to Never, then click Create. That will create the appx package and show the folder where it was created. Opening the folder, you will find some files to install the app, including the appx file.

If you double click on it and click on Install, you will see something like this (just for the first time):

That’s because the certificate you’ve used is not installed in the machine. To install it, you can open the Package.appxmanifest file in Visual Studio and go to the Packaging tab:

Then, click on the Choose Certificate button and then on View Full Certificate. That will open the install certificate window:

You must click on the Install Certificate button and select Local Machine as the store location. Then click on Next and then in Place all certificates in the following store, clicking on Browse and selecting Trusted People. The certificate will be installed and you can click again in the Appx file to install it.

This app will run without problems, because the dll will be added to the package. Adding the dll in the original project as content will make it to be packaged for the store, there’s nothing else to do.

Conclusions

As you can see, packaging an app with a dll for the Windows Store is not too difficult, but you must be aware of these things, so the app doesn’t fail when running, for the lack of the dll.

All the source code for these projects are at https://github.com/bsonnino/FinCalcDll

 

While writing my last article, something occurred to me: what if the app uses an external dll for some functions, how can I package them to send them to the store. Once you are sending an app to the Windows Store, everything it needs to start and run must be packaged, or it won’t be certified.

When using an installer to package an app, all you must do is to include the main executable and all the needed files in the install script and the installer will take care of packaging everything. But when your are packaging an app for the Windows Store, there is no such thing as an install script. If you are packaging a Delphi app, just compile it to the Store and voilà!, an appx file is created. With Visual Studio, you can create a packaging project, add all the projects in the solution that you want and it will create an appx file for you.

But sometimes, you need to use an external dll, which you may not have the source code, and it must be packaged with the main executable. In this article, I will show how to package an external dll with the main executable with Delphi and with Visual Studio.

For the article, we will take on the Financial Calculator that we created for the last article and refactor it: the financial functions will be in an external Win32 dll and the UI will be in the main executable. That way, we will do two things:

  • Separate the UI and the business rules
  • Create an external component that may be used in many situations: we will use it for our two apps – the Delphi and the WPF one. That is a great way to refactor your code when you have stable business rules that you don’t want to touch and you need to evolve the UI of your app

Refactoring the Delphi UI

As you can see from this code, the business rules are mixed with the UI, thus making it difficult to understand it and change the code, if it’s needed.

procedure TForm1.CalculatePV;
begin
  try
    var FutureValue := StrToFloat(FvPresentValue.Text);
    var InterestRate := StrToFloat(IrPresentValue.Text) / 100.0;
    var NumPeriods := StrToInt(NpPresentValue.Text);
    var PresentValue := FutureValue / Power((1 + InterestRate), NumPeriods);
    PvPresentValue.Text := FormatFloat('0.00', PresentValue);
  except
    On EConvertError do
      PvPresentValue.Text := '';
  end;
end;

For example, all the values are dependent on the text box values and the result is also posted in the result text box. This is bad design and not testable. A better thing would be something like this:

function TForm1.CalculatePV(FutureValue, InterestRate : Double; NumPeriods : Integer); double;
begin
  try
    Result := FutureValue / Power((1 + InterestRate), NumPeriods);
  except
    Result := NAN;
  end;
end;

This is cleaner, does not depend on the UI and easier to understand. But it is not testable, yet, because the method is in the code behind for the UI, so to test it you should need to instantiate a new Form1, which is not feasible under automated tests (unless you are doing UI tests, which is not the case). You could move this code to another unit, to allow it to be testable, but it won’t be reusable. If you want to use the same code in another program, you should have to copy the unit, with all the problems you may have with that:

  • Difficulty to change the code: if you find an error or want to refactor the code, you should fix the same thing in many places
  • Impossible to use in programs written in other languages, unless you rewrite the code

The best way in this case is to move the code to an external dll. That way, the code will be both testable and reusable: you can even use the same dll in programs written in other languages with no change.

The first step is to create a new project in the project group, a dynamic library. Save it and call it FinCalcDll. Then add a new unit to it and save it as PVCalculator. You should be asking why am I saving the unit with this name and not as FinancialCalculators. I am doing this because I want to treat each unit as a single class and respect the Single Responsibility Principle. Following that principle, the class should have only one reason to change. If I put all calculators in a single unit (class), there will be more than one reason to change it: any change in any of the calculators will be a reason to change. Then, we can add the first function:

unit PVCalculator;

interface

Uses Math;

function CalculatePV(FutureValue, InterestRate: Double; NumPeriods : Integer): double; stdcall;

implementation

function CalculatePV(FutureValue, InterestRate: Double; NumPeriods : Integer):
  double;
begin
  try
    Result := FutureValue / Power((1 + InterestRate), NumPeriods);
  except
    Result := NAN;
  end;
end;

end.

We must use the Math unit, to have the Power function available and declare the function in the Interface section, so it can be visible externally. It must be declared as stdcall to be called by other languages. Create new units and save them as FVCalculator, IRRCalculator and PmtCalculator and add these functions:

unit PVCalculator;

interface

Uses Math;

function CalculatePV(FutureValue, InterestRate: Double; NumPeriods : Integer):
  double; stdcall;

implementation

function CalculatePV(FutureValue, InterestRate: Double; NumPeriods : Integer):
  double;
begin
  try
    Result := FutureValue / Power((1 + InterestRate), NumPeriods);
  except
    Result := NAN;
  end;
end;

end.
unit FVCalculator;

interface

Uses Math;

function CalculateFV(PresentValue, InterestRate: Double;NumPeriods : Integer):
  Double; stdcall;

implementation

function CalculateFV(PresentValue, InterestRate: Double;NumPeriods : Integer):
  Double;
begin
  try
    Result := PresentValue * Power((1 + InterestRate), NumPeriods);
  except
    Result := NAN;
  end;
end;

end.
unit IRRCalculator;

interface

Uses Math;

function CalculateIRR(PresentValue, Payment: Double;NumPeriods : Integer):
  Double; stdcall;

implementation

function CalculateIRR(PresentValue, Payment: Double;NumPeriods : Integer):
  Double;
begin
  Result := Nan;
  try
    var FoundRate := False;
    var MinRate := 0.0;
    var MaxRate := 1.0;
    if Payment * NumPeriods < PresentValue then begin
      Result := -1;
      exit;
    end;
    if Payment * NumPeriods = PresentValue then begin
      Result := 0;
      exit;
    end;
    while not FoundRate do begin
      var Rate := (MaxRate + MinRate) / 2.0;
      var SumPayments := 0.0;
      for var I := 1 to NumPeriods do
        SumPayments := SumPayments + Payment / Power((1 + Rate), I);
      if Abs(SumPayments - PresentValue) > 0.01 then begin
        if PresentValue < SumPayments then begin
          MinRate := Rate;
        end
        else begin
          MaxRate := Rate;
        end;
      end
      else begin
        FoundRate := True;
        Result := Rate;
      end;
    end;
  except
  end;
end;

end.<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>
unit PmtCalculator;

interface

Uses Math;

function CalculatePmt(PresentValue, InterestRate: Double;NumPeriods : Integer):
  Double; stdcall;

implementation

function CalculatePmt(PresentValue, InterestRate: Double;NumPeriods : Integer):
  Double;
begin
  try
    Result := (PresentValue * InterestRate) * Power((1 + InterestRate),
      NumPeriods) / (Power((1 + InterestRate), NumPeriods) - 1);
  except
    Result := Nan;
  end;
end;

end.

In the dpr file, you must export the functions. In the Projects window, select the dll and right-click on it, selecting the View Source option. In the source for the dpr file, add the Exports clause:

{$R *.res}
Exports
  CalculatePV, CalculateFV, CalculateIRR, CalculatePmt;

Then, in the Unit1 for the executable, make the changes needed to use the new dll functions:

implementation

{$R *.dfm}
function CalculatePV(FutureValue, InterestRate : Double;NumPeriods : Integer) :
  Double; stdcall; external 'FinCalcDll.dll';

function CalculateFV(PresentValue, InterestRate : Double;NumPeriods : Integer) :
  Double; stdcall; external 'FinCalcDll.dll';

function CalculateIRR(PresentValue, Payment: Double;NumPeriods : Integer) :
  Double; stdcall; external 'FinCalcDll.dll';

function CalculatePmt(PresentValue, InterestRate : Double;NumPeriods : Integer) :
  Double; stdcall; external 'FinCalcDll.dll';

procedure TForm1.PaymentChange(Sender: TObject);
begin
  try
    var PresentValue := StrToFloat(PvPayment.Text);
    var InterestRate := StrToFloat(IRPayment.Text) / 100.0;
    var NumPayments := StrToInt(NpPayment.Text);
    var Payment := CalculatePmt(PresentValue,InterestRate, NumPayments);
    PmtPayment.Text := FormatFloat('0.00', Payment);
  except
    On EConvertError do
      PmtPayment.Text := '';
  end;
end;

procedure TForm1.PresentValueChange(Sender: TObject);
begin
  try
    var FutureValue := StrToFloat(FvPresentValue.Text);
    var InterestRate := StrToFloat(IrPresentValue.Text) / 100.0;
    var NumPeriods := StrToInt(NpPresentValue.Text);
    var PresentValue := CalculatePV(FutureValue, InterestRate, NumPeriods);
    if IsNan(PresentValue) then
      PvPresentValue.Text := ''
    else
      PvPresentValue.Text := FormatFloat('0.00', PresentValue);
  except
    On EConvertError do
      PvPresentValue.Text := '';
  end;
end;

procedure TForm1.IRRChange(Sender: TObject);
begin
  try
    var NumPayments := StrToInt(NpIRR.Text);
    var PresentValue := StrToFloat(PvIRR.Text);
    var Payment := StrToFloat(PmtIRR.Text);
    var Rate := CalculateIRR(PresentValue, Payment, NumPayments);
    if Rate < 0 then begin
      IRIRR.Text := 'Rate Less than 0';
      exit;
    end;
    if IsNan(Rate) then begin
      IRIRR.Text := 'Error calculating rate';
      exit;
    end;
    IRIRR.Text := FormatFloat('0.00', Rate * 100.0);
  except
    On EConvertError do
      IRIRR.Text := '';
  end;
end;

procedure TForm1.FutureValueChange(Sender: TObject);
begin
  try
    var PresentValue := StrToFloat(PvFutureValue.Text);
    var InterestRate := StrToFloat(IrFutureValue.Text) / 100.0;
    var NumPeriods := StrToInt(NpFutureValue.Text);
    var FutureValue := CalculateFV(PresentValue,InterestRate, NumPeriods);
    if IsNan(FutureValue) then
      FvFutureValue.Text := ''
    else
      FvFutureValue.Text := FormatFloat('0.00', FutureValue);
  except
    On EConvertError do
      FvFutureValue.Text := '';
  end;
end;

We declare the functions and use them in the OnChange handlers of the textboxes. When you build and run the program, you will see something like this:

That’s because the dll is not where it should be, in the same folder of the executable. For that, you must take some steps:

  • Build the dll before the executable. If you don’t do that, the executable will be built and will use an outdated dll
  • Copy the dll after building the executable

For the first step, you need to go to the Projects window, select the dll, right click and select the “Build Sooner”  option. That will move the dll up in the project list and will make it to be built before the executable.

For the second step, you need to add a post-build step for the executable and copy the dll to the output dir. For that, you need to select the Project Options and go to Build Events:

There, in the Post-build events, you should add a command to copy the dll to the executable output dir:

One thing must be noted here: you must build the dll and the executable for the same platform. If you build the dll for x64, it won’t run on a x86 executable. Once you’ve done the two steps, you can build all projects and run the executable, it will run the same way as before. Now, we’ve refactored all the business rules into a dll and we can reuse it in other languages. To show that, we will create a WPF project in C# that will use this dll.

Creating a WPF project that uses the DLL

Go to Visual Studio and create a new WPF project, and name it FinCalcWPF. Then go to solution explorer and add the dll file to the project. When adding the dll, select Add as Link, to avoid to make a physical copy of the dll in the source directory. This way, you are just adding a link to the dll and when it’s rebuilt, the new version will be used. In the properties window, select Build Action to None and Copy to Output Directory to Copy if newer.

One thing must be noted here: the dll is for Win32, so the executable should also be for Win32. When you build the WPF app with the default setting (Any CPU), you can’t be sure it if will run as a Win32 process:

  • For a Win32 operating system, it will run as a Win32 process, so that’s ok
  • For a Win64 operating system, it may run as a Win32 or Win64 process, depending on your settings. If you go to Project/Options/Build and check the “Prefer 32-bit”, it will run as a Win32 process, else it will run as a Win64 process

So, if you don’t want any surprises, just change the build from Any CPU to x86 and you will be sure that the program will run with the dll.

Then, in MainWindow.xaml, add this code:

<Window x:Class="FinCalcWpf.MainWindow"
        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"
        mc:Ignorable="d"
        Title="Financial Calulator WPF" Height="293.774" Width="419.623">
    <Grid>
        <TabControl>
            <TabItem Header="Present Value">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="2*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" 
                             Margin="5" Text="Future Value"/>
                    <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                            Text="Interest Rate"/>
                    <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                            Text="Num.Periods"/>
                    <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                            Text="Present Value"/>
                    <TextBox Grid.Row="0" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                            x:Name="PvFvBox" TextChanged="PvOnTextChanged"/>
                    <TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PvIrBox" TextChanged="PvOnTextChanged"/>
                    <TextBox Grid.Row="2" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PvNpBox" TextChanged="PvOnTextChanged"/>
                    <TextBox Grid.Row="3" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PvPvBox" IsReadOnly="True"/>
                </Grid>
            </TabItem>
            <TabItem Header="Future Value">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="2*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" 
                               Margin="5" Text="Present Value"/>
                    <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Interest Rate"/>
                    <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Num.Periods"/>
                    <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Future Value"/>
                    <TextBox Grid.Row="0" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="FvPvBox" TextChanged="FvOnTextChanged"/>
                    <TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="FvIrBox" TextChanged="FvOnTextChanged"/>
                    <TextBox Grid.Row="2" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="FvNpBox" TextChanged="FvOnTextChanged"/>
                    <TextBox Grid.Row="3" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="FvFvBox" IsReadOnly="True"/>
                </Grid>
            </TabItem>
            <TabItem Header="Payment">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="2*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" 
                               Margin="5" Text="Present Value"/>
                    <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Interest Rate"/>
                    <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Num.Periods"/>
                    <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Payment"/>
                    <TextBox Grid.Row="0" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PmtPvBox" TextChanged="PmtOnTextChanged"/>
                    <TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PmtIrBox" TextChanged="PmtOnTextChanged"/>
                    <TextBox Grid.Row="2" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PmtNpBox" TextChanged="PmtOnTextChanged"/>
                    <TextBox Grid.Row="3" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PmtPmtBox" IsReadOnly="True"/>
                </Grid>
            </TabItem>
            <TabItem Header="Return Rate">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="2*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" 
                               Margin="5" Text="Present Value"/>
                    <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Payment"/>
                    <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Num.Periods"/>
                    <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Return Rate"/>
                    <TextBox Grid.Row="0" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="RrPvBox" TextChanged="RrOnTextChanged"/>
                    <TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="RrPmtBox" TextChanged="RrOnTextChanged"/>
                    <TextBox Grid.Row="2" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="RrNpBox" TextChanged="RrOnTextChanged"/>
                    <TextBox Grid.Row="3" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="RrRrBox" IsReadOnly="True"/>
                </Grid>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

We are adding the four tabs with the boxes, the same way we’ve added in the Delphi app. The code behind for the window is:

using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;

namespace FinCalcWpf
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        [DllImport("FinCalcDll.dll")]
        private static extern double CalculatePV(double futureValue, double interestRate, int numPeriods);

        [DllImport("FinCalcDll.dll")]
        private static extern double CalculateFV(double presentValue, double interestRate, int numPeriods);

        [DllImport("FinCalcDll.dll")]
        private static extern double CalculatePmt(double presentValue, double interestRate, int numPeriods);

        [DllImport("FinCalcDll.dll")]
        private static extern double CalculateIRR(double presentValue, double payment, int numPeriods);

        public MainWindow()
        {
            InitializeComponent();
        }

        private void PvOnTextChanged(object sender, TextChangedEventArgs e)
        {
            if (double.TryParse(PvFvBox.Text, out double futureValue) &&
                double.TryParse(PvIrBox.Text, out double interestRate) &&
                int.TryParse(PvNpBox.Text, out int numPeriods))
              PvPvBox.Text = CalculatePV(futureValue, interestRate / 100.0, numPeriods).ToString("N2");
        }

        private void FvOnTextChanged(object sender, TextChangedEventArgs e)
        {
            if (double.TryParse(FvPvBox.Text, out double presentValue) &&
                double.TryParse(FvIrBox.Text, out double interestRate) &&
                int.TryParse(FvNpBox.Text, out int numPeriods))
                FvFvBox.Text = CalculateFV(presentValue, interestRate / 100.0, numPeriods).ToString("N2");
        }

        private void PmtOnTextChanged(object sender, TextChangedEventArgs e)
        {
            if (double.TryParse(PmtPvBox.Text, out double presentValue) &&
                double.TryParse(PmtIrBox.Text, out double interestRate) &&
                int.TryParse(PmtNpBox.Text, out int numPeriods))
                PmtPmtBox.Text = CalculatePmt(presentValue, interestRate / 100.0, numPeriods).ToString("N2");
        }

        private void RrOnTextChanged(object sender, TextChangedEventArgs e)
        {
            if (double.TryParse(RrPvBox.Text, out double presentValue) &&
                double.TryParse(RrPmtBox.Text, out double payment) &&
                int.TryParse(RrNpBox.Text, out int numPeriods))
                RrRrBox.Text = (CalculateIRR(presentValue, payment, numPeriods)*100.0).ToString("N2");
        }
    }
}

We’ve declared the functions in the dll and the we used them in the TextChanged event handlers. That will fill the result boxes in the tabs when you fill the input boxes. When you run the program, you will have the same result in both apps:

As you can see, refactoring the code into a dll brings many advantages: the code is not dependent on the UI, it is reusable and, best of all, it is testable. Creating unit tests for the code is a great way to be sure that everything works fine and, if you are making a change, you haven’t introduced a bug. Now, we’ll add the tests for the dll functions.

Adding tests to the dll

To add tests to the dll we must create a new test project to the group. Right click on the Project group and select “Add new project”. Then, select the DUnitX project, and set its settings:

When you click the OK button, Delphi will create a new test project with an unit with sample tests. You need to add the four calculator units to your new project and then, we can create the first test:

unit PVCalculatorTests;

interface
uses
  DUnitX.TestFramework, PVCalculator, Math;

type

  [TestFixture]
  TPvCalculatorTests = class(TObject)
  public
    [Test]
    [TestCase('FutureValue','-1,0,0')]
    [TestCase('Rate','0,-1,0')]
    [TestCase('Periods','0,0,-1')]
    procedure NegativeInputParametersReturnNan(const FutureValue : Double;
      const Rate : Double; const Periods : Integer);
  end;

implementation

procedure TPvCalculatorTests.NegativeInputParametersReturnNan(const FutureValue,
  Rate: Double; const Periods: Integer);
begin
  var result := CalculatePv(FutureValue,Rate,Periods);
  Assert.IsTrue(IsNan(result));
end;

initialization
  TDUnitX.RegisterTestFixture(TPvCalculatorTests);
end.

We named the unit PvCalculatorTests. Then we add the PVCalculator and Math units to the Uses clause. Then, we set the [TextFixture] attribute to the test class, to tell the test framework that this is a class that will have tests. Then we create a method and decorate it with the [Test] attribute. As this will be a parametrized test, we add the cases with the TestCase attribute.

The test is simple. We will run the test with the parameters (there will always be a negative parameter) and the result must always be NaN, thus pointing an invalid entry. If you run the project you will see something like this:

As you can see, the generated test project is a console app that runs the tests and shows the results. If you want a GUI app for the tests, you should install the TestInsight IDE plugin. As you can see from the image, all tests failed, because we have not checked the input parameters. We can change that in PVCalculator:

function CalculatePV(FutureValue, InterestRate: Double; NumPeriods : Integer):
  double;
begin
  if (FutureValue < 0) or (InterestRate < 0) or (NumPeriods < 0) then begin
    Result := NaN;
    exit;
  end;
  try
    Result := FutureValue / Power((1 + InterestRate), NumPeriods);
  except
    Result := NaN;
  end;
end;

Now, when you run the tests, all pass:

Now, we can create more tests for this calculator:

unit PVCalculatorTests;

interface
uses
  DUnitX.TestFramework, PVCalculator, Math;

type

  [TestFixture]
  TPvCalculatorTests = class(TObject)
  public
    [Test]
    [TestCase('FutureValue','-1,0,0')]
    [TestCase('Rate','0,-1,0')]
    [TestCase('Periods','0,0,-1')]
    procedure NegativeInputParametersReturnNan(const FutureValue : Double;
      const Rate : Double; const Periods : Integer);

    [Test]
    [TestCase('OnePeriod','100,1')]
    [TestCase('TenPeriods','100,10')]
    [TestCase('OneHundredPeriods','100,100')]
    procedure ZeroRatePresentValueEqualsFutureValue(const FutureValue : Double;
      const Periods : Integer);

    [Test]
    [TestCase('OnePeriodOnePercent','0.01,1')]
    [TestCase('OnePeriodTenPercent','0.10,1')]
    [TestCase('OnePeriodHundredPercent','1.00,1')]
    [TestCase('TenPeriodOnePercent','0.01,10')]
    [TestCase('TenPeriodTenPercent','0.10,10')]
    [TestCase('TenPeriodHundredPercent','1.00,10')]
    [TestCase('HundredPeriodOnePercent','0.01,100')]
    [TestCase('HundredPeriodTenPercent','0.10,100')]
    [TestCase('HundredPeriodHundredPercent','1.00,100')]
    procedure ZeroFutureValueEqualsZeroPresentValue(const Rate : Double;
      const Periods : Integer);
      
    [Test]
    [TestCase('OnePeriodOnePercent','100,0.01,1,99.01')]
    [TestCase('OnePeriodTenPercent','100,0.10,1,90.91')]
    [TestCase('OnePeriodHundredPercent','100,1.00,1,50')]
    [TestCase('TenPeriodOnePercent','100,0.01,10,90.53')]
    [TestCase('TenPeriodTenPercent','100,0.10,10,38.55')]
    [TestCase('TenPeriodHundredPercent','100,1.00,10,0.10')]
    [TestCase('HundredPeriodOnePercent','100,0.01,100,36.97')]
    [TestCase('HundredPeriodTenPercent','100,0.10,100,0.01')]
    [TestCase('HundredPeriodHundredPercent','100,1.00,100,0.00')]
    procedure VariablePeriodTests(const FutureValue : Double;
      const Rate : Double; const Periods : Integer; const Expected : Double);

  end;

implementation

procedure TPvCalculatorTests.NegativeInputParametersReturnNan(const FutureValue,
  Rate: Double; const Periods: Integer);
begin
  var result := CalculatePv(FutureValue,Rate,Periods);
  Assert.IsTrue(IsNan(result));
end;

procedure TPvCalculatorTests.VariablePeriodTests(const FutureValue, Rate: Double;
  const Periods: Integer; const Expected: Double);
begin
  var result := CalculatePv(FutureValue,Rate,Periods);
  Assert.AreEqual(Expected,Double(result));
end;

procedure TPvCalculatorTests.ZeroFutureValueEqualsZeroPresentValue(
  const Rate: Double; const Periods: Integer);
begin
  var result := CalculatePv(0,Rate,Periods);
  Assert.AreEqual(Double(0.0),Double(result));
end;

procedure TPvCalculatorTests.ZeroRatePresentValueEqualsFutureValue(
  const FutureValue Double; const Periods: Integer);
begin
  var result := CalculatePv(FutureValue,0,Periods);
  Assert.AreEqual(FutureValue,Double(result));
end;

initialization
  TDUnitX.RegisterTestFixture(TPvCalculatorTests);
end.

You should note one thing. When you run the tests, you will see that some of them fail:

This is not a failure in our code, but a failure in the test. As we are comparing double values, there are many decimals to compare and that’s not what you want. You can change your test to compare the difference to a maximum value. If the difference is greater than the maximum, the test fails:

procedure TPvCalculatorTests.VariablePeriodTests(const FutureValue, Rate: Double;
  const Periods: Integer; const Expected: Double);
begin
  var result := CalculatePv(FutureValue,Rate,Periods);
  Assert.AreEqual(Expected,Double(result), 0.01);
end;

Now, all tests pass. You can create tests for the other calculators the same way we did for this one. If you are using Delphi Rio and run the tests with debugging, you will see that some tests give a floating point error:

procedure TPmtCalculatorTests.ZeroPeriodsValueEqualsPresentValue(
  const PresentValue, Rate: Double);
begin
  var result := CalculatePmt(PresentValue,Rate, 0);
  Assert.AreEqual(Double(PresentValue),Double(result),0.01);
end;

procedure TPmtCalculatorTests.ZeroRatePmtEqualsPresentValueDivPeriods(
  const PresentValue: Double; const Periods: Integer);
begin
  var result := CalculatePmt(PresentValue,0,Periods);
  Assert.AreEqual(Double(PresentValue/Periods),Double(result),0.01);
end;

But the tests still pass. That’s because there is a bug in Delphi Rio (QC#RSP-19882), where comparisons with NaN return true, while they should return false. This can be solved by changing the tests to:

procedure TPmtCalculatorTests.ZeroPeriodsValueEqualsPresentValue(
  const PresentValue, Rate: Double);
begin
  var result := CalculatePmt(PresentValue,Rate, 0);
  Assert.IsFalse(IsNan(Result));
  Assert.AreEqual(Double(PresentValue),Double(result),0.01);
end;

procedure TPmtCalculatorTests.ZeroRatePmtEqualsPresentValueDivPeriods(
  const PresentValue: Double; const Periods: Integer);
begin
  var result := CalculatePmt(PresentValue,0,Periods);
  Assert.IsFalse(IsNan(Result));
  Assert.AreEqual(Double(PresentValue/Periods),Double(result),0.01);
end;

When you run the tests again, you will see that they will fail. We must change the calculator to solve this:

function CalculatePmt(PresentValue, InterestRate: Double;NumPeriods : Integer):
  Double;
begin
  if (PresentValue < 0) or (InterestRate < 0) or (NumPeriods < 0) then begin
    Result := NaN;
    exit;
  end;
  try
    if InterestRate = 0 then
      Result := PresentValue/NumPeriods
    else if NumPeriods = 0 then
      Result := PresentValue
    else
      Result := (PresentValue * InterestRate) * Power((1 + InterestRate),
        NumPeriods) / (Power((1 + InterestRate), NumPeriods) - 1);
  except
    Result := Nan;
  end;
end;

After this, our dll and its tests are ready to use and can be used in any language that supports Win32 dlls.

Conclusions

We’ve come a long way from the calculator code mixed with the UI to a new dll with unit tests. This architecture is more robust, reusable and easier to maintain. If we need to make changes to the dll, we are covered by unit tests, that can assure we are not introducing new bugs. And if some bug is found in the functions, it’s just a matter of writing a new test that fails, thus making sure of the bug, fix the code and rerun the test, making sure it’s passed. Using the Red-Refactor-Green procedure, we have a safety net for changing our code.

The full code for this article is at https://github.com/bsonnino/FinCalcDll

One of the features I really like in Visual Studio while I am developing is the Edit and Continue feature. Just add a breakpoint in the code, edit the code and maybe even reposition the run pointer and you can try new features, set some properties or add new code and run, without having to restart the program.

This is really a timesaver, I’ve used it a lot of time and it has saved me hours of development. But, when I was doing some WPF or UWP development, the UI development was slowed down, because every time I needed to make some change to the design, I had to stop the program, make a change and re-run again.

Things were worse because the designer was less than optimal and, for complex designs, with lots of resources, animations and so on, I couldn’t see what was happening at design time. When the live visual tree was introduced, this was made better: I could see the visual tree and interact with it, setting some properties at runtime. The pitfall in this is that I needed to make the notes of what was changed, because the changes would be lost when the program stopped.

Then came XAML Edit and Continue. What a difference! The name of the feature is misleading, because it’s not Edit and Continue (I would suggest XAML Runtime Edititng). You don’t need to stop the program and set a breakpoint anywhere – just edit the code while it’s running and see the changes happen!

This is a great feature, you can start from a blank page and start adding data to it. If you have a ViewModel attached to the View, you can also use data binding to show the ViewModel data. One thing should be noted, here: if you are using x:Bind to make the binding to the data, it won’t work at runtime, because this binding is resolved at compile time and it won’t be available at runtime.

I use this feature when I want to try something in my UI – that can be something new or something that can be improved in my code. For example, if I have this CommandBar in my code (code courtesy of https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.commandbar), I can try to tweak its options directly in the code and see what happens.

<Page.TopAppBar>
    <CommandBar>
        <AppBarToggleButton Icon="Shuffle" Label="Shuffle" Click="AppBarButton_Click"/>
        <AppBarToggleButton Icon="RepeatAll" Label="Repeat" Click="AppBarButton_Click"/>
        <AppBarSeparator/>
        <AppBarButton Icon="Back" Label="Back" Click="AppBarButton_Click"/>
        <AppBarButton Icon="Stop" Label="Stop" Click="AppBarButton_Click"/>
        <AppBarButton Icon="Play" Label="Play" Click="AppBarButton_Click"/>
        <AppBarButton Icon="Forward" Label="Forward" Click="AppBarButton_Click"/>

        <CommandBar.SecondaryCommands>
            <AppBarButton Icon="Like" Label="Like" Click="AppBarButton_Click"/>
            <AppBarButton Icon="Dislike" Label="Dislike" Click="AppBarButton_Click"/>
        </CommandBar.SecondaryCommands>

        <CommandBar.Content>
            <TextBlock Text="Now playing..." Margin="12,14"/>
        </CommandBar.Content>
    </CommandBar>
</Page.TopAppBar>

For example, I want to know what happens when the IsCompact  property of an AppBarButton is set to False. I can just add it to the code and see what happens:

Then I removed the tag from the code. Instead of seeing the UI be restored to the default, the IsCompact property remained to False. You should be aware that once you set a property and remove it from the code, the property will still be set to the value until you reset it explicitely or rerun the program. Be aware of that or you will have some hard time to see why things aren’t the way you expect :-).

If you are using a resource dictionary, you can also change the styles there and the UI will change accordingly. For example, I have a Resource Dictionary like this:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="CommandBar">
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="Background" Value="Gray"/>
    </Style>
</ResourceDictionary>

I add it to the app resources with something like this:

<Application
    x:Class="App1.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
        <ResourceDictionary Source="ResourceDict.xaml"/>
    </Application.Resources>
</Application>

When I take a look at the UI, I see something like this:

As you can see, the content is white, while the buttons are black, and surely that’s not what I want. I can add a new style like this:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="CommandBar">
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="Background" Value="Gray"/>
    </Style>
   <Style TargetType="SymbolIcon">
       <Setter Property="Foreground" Value="White"/>
   </Style>
</ResourceDictionary>

And the UI reflects the new style for the icons:

One other area that you can tweak is when you are designing a responsive view. You will have something like this:

 <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState>
                    <!-- VisualState to be triggered when window width is >=720 effective pixels -->
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="720" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!-- Widest possible layout moves some elements around to optimize for more available width 
                        and keeps SplitView pane always showing inline -->
                        <Setter Target="MySplitView.DisplayMode" Value="Inline" />
                        <Setter Target="MySplitView.IsPaneOpen" Value="True" />
                        <Setter Target="BackgroundCombo.(RelativePanel.RightOf)" Value="BackgroundImage" />
                        <Setter Target="BackgroundCombo.(RelativePanel.AlignTopWith)" Value="BackgroundImage" />
                        <Setter Target="BackgroundCombo.(RelativePanel.AlignLeftWith)" Value="FitCombo" />
                        <Setter Target="PictureLabel.(RelativePanel.Below)" Value="BackgroundImage" />
                        <Setter Target="FitCombo.(RelativePanel.RightOf)" Value="PicturesPanel" />
                        <Setter Target="FitCombo.(RelativePanel.AlignTopWith)" Value="PictureLabel" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState>
                    <!-- VisualState to be triggered when window width is >=548 and <720 effective pixels -->
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="548" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!-- For intermediate window widths as well as most phones on landscape orientation, 
                        this state keeps primary layout narrow while showing the splitview pane to take advantage of more available width than narrow layout -->
                        <Setter Target="MySplitView.DisplayMode" Value="Inline" />
                        <Setter Target="MySplitView.IsPaneOpen" Value="True" />
                        <Setter Target="BackgroundCombo.(RelativePanel.Below)" Value="BackgroundImage" />
                        <Setter Target="PictureLabel.(RelativePanel.Below)" Value="BackgroundCombo" />
                        <Setter Target="FitCombo.(RelativePanel.Below)" Value="BrowseButton" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState>
                    <!-- VisualState to be triggered when window width is >=0 and <548 effective pixels -->
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="0" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!-- For the most narrow windows and phones in portrait orientation, this state collapses the SplitView pane into overlay mode
                        and adds dynamic RelativePanel constraints that puts all elements stacked below each other -->
                        <Setter Target="MySplitView.DisplayMode" Value="Overlay" />
                        <Setter Target="MySplitView.IsPaneOpen" Value="False" />
                        <Setter Target="BackgroundCombo.(RelativePanel.Below)" Value="BackgroundImage" />
                        <Setter Target="PictureLabel.(RelativePanel.Below)" Value="BackgroundCombo" />
                        <Setter Target="FitCombo.(RelativePanel.Below)" Value="BrowseButton" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

You can change the triggers for the states, changing the windows sizes or the display for the many states. That is something that will save you a lot of time when developing responsive designs!

One thing in which this feature really shines is in testing animations – if you are using Visual Studio to create XAML animations, you are out of luck to test them, there is no way to test them in design time – you must use Blend for that. With this feature, you can create, tweak and test your animations without having to restart your program. For example, if you have this UI:

<Page.Resources>
    <Storyboard x:Name="myStoryboard">
        <DoubleAnimation
            Storyboard.TargetName="MyAnimatedRectangle"
            Storyboard.TargetProperty="Opacity"
            From="1.0" To="0.0" Duration="0:0:2"
            AutoReverse="True" />
    </Storyboard>
</Page.Resources>
<Grid>
    <Rectangle x:Name="MyAnimatedRectangle" 
               Width="300" Height="200" Fill="Blue"/>
</Grid>

And your animation is started by a button click like this one:

private void AppBarButton_Click(object sender, RoutedEventArgs e)
{
    myStoryboard.Begin();
}

You can run your program and change the animation parameters at runtime. Every time you click the button, the new animation will be run and you will be able to see its effects. Nice, no?

Conclusions

This feature is one of the nicest ones when it comes to XAML development. If you are a beginner learning XAML, you can use it to learn and see what happens when you change things, thus speeding up your learning curve, and if you are an experienced XAML designer, you can design your views interactively, using all the features you want. You just need to create a blank app and start designing!

The source code for this article is at https://github.com/bsonnino/XamlEditAndContinue

Some time ago, I’ve written this post about loading XAML dynamically. You can use this in many different ways:

  • store the views in a database
  • load the views from a server
  • change views depending on the user

Using that method, you can change the view, but there is a pitfall: all code must be loaded when the code is initialized. To change anything, you must recompile all the code. That way, the Views you are loading must have properties that match the ones with the ViewModels already in the code.

So, I started to think a way to load the XAML files and its corresponding ViewModel dynamically. Fortunately, C# provides us a mechanism to compile C# code at runtime and execute it. That’s great for our needs.

Compiling C# code at runtime

Compiling code at runtime is not new, you can compile code using the CSharpCodeProvider class with some code like this one:

        static void Main(string[] args)
        {
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters {GenerateInMemory = true};
            parameters.ReferencedAssemblies.Add("System.dll");
            var code =
@"  
using System;
namespace DynamicCompile
{
    public class DynamicCode
    {
        public void Execute()
        {
            Console.WriteLine(""Hello World"");
        }
    }
}";
            CompilerResults results = provider.CompileAssemblyFromSource(
                parameters, code);
            if (!results.Errors.HasErrors)
            {
                var type = results.CompiledAssembly.GetType("DynamicCompile.DynamicCode");
                var method = type.GetMethod("Execute", BindingFlags.Public | BindingFlags.Instance);
                var cls = Activator.CreateInstance(type);
                method?.Invoke(cls, null);
            }
            else
            {
                foreach (CompilerError error in results.Errors)
                {
                    Console.WriteLine(error.ErrorText);
                }
            }
        }

If you execute this code, you will see “Hello World” written in the console. To use it, you must add the namespaces System.CodeDom.Compiler and System. CSharp. This code will use the legacy compiler and will compile code before C#6. If you want to compile C#6 code or newer, you will have to use the Roslyn compiler. To do that, you must add the Microsoft.CodeDom.Providers.DotNetCompilerPlatform NuGet package and add the Microsoft.CodeDom.Providers.DotNetCompilerPlatform namespace. That way, the same code will be able to compile C#6 or later:

        static void Main(string[] args)
        {
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters {GenerateInMemory = true};
            parameters.ReferencedAssemblies.Add("System.dll");
            var code =
@"  
using System;
namespace DynamicCompile
{
    public class DynamicCode
    {
        const string str = ""World"";
        public void Execute()
        {
            Console.WriteLine($""Hello {str}"");
        }
    }
}";
            CompilerResults results = provider.CompileAssemblyFromSource(
                parameters, code);
            if (!results.Errors.HasErrors)
            {
                var type = results.CompiledAssembly.GetType("DynamicCompile.DynamicCode");
                var method = type.GetMethod("Execute", BindingFlags.Public | BindingFlags.Instance);
                var cls = Activator.CreateInstance(type);
                method?.Invoke(cls, null);
            }
            else
            {
                foreach (CompilerError error in results.Errors)
                {
                    Console.WriteLine(error.ErrorText);
                }
            }
        }

As you can see, the code above gives the same result, but uses the string interpolation, available in C#6. With this knowledge, we can work on loading the ViewModel code at runtime.

Loading and Running the ViewModel code at runtime

We’ve seen how to compile and run some code at runtime. With this knowledge, we can create a loose file with the ViewModel corresponding to the loaded XAML, load it and compile it at runtime. One extra bonus is that the View doesn’t know where do the data comes from, so we can load the ViewModel, compile it, instantiate an instance of the compiled class and assign this instance as the DataContext for the View.

As a sample, we will create a simple WPF project that will load a XAML file and its ViewModel into the main screen. Create a new blank WPF project and call it DynamicXAMLAndVM. In the XAML for MainPage.xaml, add this code:

<Grid>
   <ContentControl x:Name="WndContent"/> 
</Grid>

In the code behind for MainPage.xaml.cs, add this code:

public MainWindow()
{
    InitializeComponent();
    using (FileStream fs = new FileStream("CustView.xaml", FileMode.Open))
    {
        WndContent.Content= XamlReader.Load(fs) as FrameworkElement;
        var vmType = LoadViewModel("CustViewModel");
        if (vmType == null)
            MessageBox.Show(_errors, "Errors in compile");
        else
            DataContext = Activator.CreateInstance(vmType);
    }
    
}

private string _errors;
public Type LoadViewModel(string viewModelName)
{
    CSharpCodeProvider provider = new CSharpCodeProvider();
    CompilerParameters parameters = new CompilerParameters {GenerateInMemory = true};
    parameters.ReferencedAssemblies.Add("System.dll");
    parameters.ReferencedAssemblies.Add(Assembly.GetEntryAssembly().Location);
    var code = File.ReadAllText(viewModelName + ".cs");
    CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
    if (results.Errors.HasErrors)
    {
        _errors = "";
        foreach (CompilerError error in results.Errors)
        {
            _errors += $"Error #{error.ErrorNumber}: {error.ErrorText}\n";
        }

        return null;
    }
    else
    {
        Assembly assembly = results.CompiledAssembly;
        return assembly.GetType($"DynamicXamlAndVM.{viewModelName}");
    }
}

In the constructor, we load the XAML file and assign it to the Content property of the ContentControl. Then we call LoadViewModel, that will load the ViewModel and return its type. If the type is not null, we create an instance of the class with Activator.CreateInstance and assign it to the DataContext of the View. That’s all that’s needed to load the XAML and the ViewModel.

LoadViewModel will create a CSharpCodeProvider and will compile the code. If there are no errors in the code, it will return the parsed type. There is something that should be noticed here: we are referencing two assemblies for the compilation: System.dll and the current assembly. I’m doing that because the compiled class will be located in a separate assembly and won’t have access to the code in the current one.

I am not using any MVVM framework. Instead, I have created a new class, RelayCommand, based on Josh Smith’s code found here.

public class RelayCommand : ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _canExecute = canExecute;
        _execute = execute;
    }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }
}

To access this code in the main assembly, you must add it to the Referenced Assemblies. If you are using a third party MVVM framework, you must add all its assemblies to the referenced assemblies.

The next step is to create the View and the ViewModel. Create a new file and name it CustView.xaml. In the properties page, set its BuildAction to None and the Copy to output directory to Copy if newer. Then, add this code:

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="40"/>
        <RowDefinition Height="40"/>
        <RowDefinition Height="40"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="2*"/>
    </Grid.ColumnDefinitions>
    <TextBlock Grid.Column="0" Grid.Row="0" Text="ID" Margin="5"/>
    <TextBlock Grid.Column="0" Grid.Row="1" Text="Name" Margin="5"/>
    <TextBlock Grid.Column="0" Grid.Row="2" Text="Address" Margin="5"/>
    <TextBlock Grid.Column="0" Grid.Row="3" Text="City" Margin="5"/>
    <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding ID}" Margin="5"/>
    <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Name}" Margin="5"/>
    <TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Address}" Margin="5"/>
    <TextBox Grid.Column="1" Grid.Row="3" Text="{Binding City}" Margin="5"/>
    <Button Grid.Column="1" Grid.Row="4" Command="{Binding ClearCommand}" Margin="5" 
            HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Clear Data"
            Width="65" Height="35" />
</Grid>

As you can see, it’s a simple grid with some TextBoxes and a Button. The Text property of the TextBoxes are bound to properties in the DataContext. Thanks to the data binding, the View does not know from where the data comes from. We just need to assign a class that has the properties ID, Name, Address, City and a command named ClearCommand. The ViewModel is also a loose cs file. Create a new file and name it CustViewModel.cs, setting its BuildAction to None and the Copy to output directory to Copy if newer. Then, add this code:

class CustViewModel : INotifyPropertyChanged
{
    private string _id;
    private string _name;
    private string _address;
    private string _city;
    public event PropertyChangedEventHandler PropertyChanged;

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

    public CustViewModel()
    {
        ID = "007";
        Name = "James Bond";
        Address = "MI6";
        City = "London";

    }
    public string ID
    {
        get => _id;
        set
        {
            _id = value;
            OnPropertyChanged();
        }
    }

    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }

    public string Address
    {
        get => _address;
        set
        {
            _address = value;
            OnPropertyChanged();
        }
    }

    public string City
    {
        get => _city;
        set
        {
            _city = value;
            OnPropertyChanged();
        }
    }

    public ICommand ClearCommand => new RelayCommand(
        o =>
        {
            ID = "";
            Name = "";
            Address = "";
            City = "";
        },
        o => true);
}

This is a simple ViewModel, which has the properties needed for the View and a command that will clear all bound properties when it’s executed. That’s all that is needed to load a View with its ViewModel and add it to the main screen. When you run the app, you will see something like this:

As you can see, all the data is loaded into the TextBoxes and, when you click on the button, the data is cleared. Nice, no? This code is tied to the files in the project. Now, we can streamline this process, refactoring the code and allowing it to call any file added in the project. For that, we will use some rules:

  • All the views will be in he Views folder
  • All the viewmodels will be in the ViewModels folder and will have the same name of the View
  • The namespace for all viewmodels will be DynamicVM

Refactoring the code

In the project, create a new folder and name it Views and move the CustView.xaml file to it. Rename the file to Cust.xaml. Then, create a new folder and name it ViewModels and move the CustViewModel.cs file to it. Rename it to Cust.cs. In the file, rename the class to Cust and rename the namespace to DynamicVM.

Create a new class named DynamicContentLoader and add this code:

using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;
using Microsoft.CodeDom.Providers.DotNetCompilerPlatform;

namespace DynamicXamlAndVMRefactored
{
    public class DynamicContentLoader
    {
        public static string Errors => _errors;
        public static FrameworkElement Load(string viewName)
        {
            var viewPath = $"Views\\{viewName}.xaml";
            if (!File.Exists(viewPath))
                return null;
            try
            {
                using (FileStream fs = new FileStream(viewPath, FileMode.Open))
                {
                    var result = XamlReader.Load(fs) as FrameworkElement;
                    if (result == null)
                        return null;
                    var viewModelPath = $"ViewModels\\{viewName}.cs";
                    if (File.Exists(viewModelPath))
                    {
                        var vmType = LoadViewModel(viewModelPath, viewName);
                        if (vmType != null)
                        {
                            result.DataContext = Activator.CreateInstance(vmType);
                        }
                    }

                    return result;
                }
            }
            catch (Exception e)
            {
                _errors = e.Message;
                return null;
            }
            
        }

        private static string _errors;
        private static Type LoadViewModel(string viewModelName, string viewName)
        {
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters { GenerateInMemory = true };
            parameters.ReferencedAssemblies.Add("System.dll");
            parameters.ReferencedAssemblies.Add(Assembly.GetEntryAssembly().Location);
            var code = File.ReadAllText(viewModelName);
            CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
            if (results.Errors.HasErrors)
            {
                _errors = "";
                foreach (CompilerError error in results.Errors)
                {
                    _errors += $"Error #{error.ErrorNumber}: {error.ErrorText}\n";
                }

                return null;
            }
            else
            {
                Assembly assembly = results.CompiledAssembly;
                return assembly.GetType($"DynamicVM.{viewName}");
            }
        }
    }
}

This code is very similar to the previous one, we’ve created a class with a static method Load, that loads a XAML file and, if there is a corresponding ViewModel, loads it and assigns it as the DataContext for the view. That way, the code in MainWindow.xaml.cs is simplified:

public MainWindow()
{
    InitializeComponent();
    var content = DynamicContentLoader.Load("Cust");
    if (DynamicContentLoader.Errors != null)
        MessageBox.Show(DynamicContentLoader.Errors, "Errors in compile");
    WndContent.Content = content;
}

Running the program, you will have the same result as the previous run.

Conclusions

As you’ve seen, if you want to have dynamic views, you are not limited to use codeless windows. You can also add code for it, compile it on the fly and use it tied to the view, thanks to the magic of data binding. That way, you can create applications fully dynamic, that can be easily updated whenever you want, with no need of recompilation and redeploy.

All the source code for this article is at https://github.com/bsonnino/DynamicXamlAndVM

After using the program developed in the last post, I was thinking about some ways to optimize it. Then I went to the FileFinder class and saw this:

class FileFinder
{
    public async Task<ConcurrentDictionary<string, List>> GetFiles(string[] paths, 
        Regex excludeFilesRegex, Regex excludePathsRegex, bool incremental)
    {
        var files = new ConcurrentDictionary<string, List>();
        var tasks = paths.Select(path =>
            Task.Factory.StartNew(() =>
            {
                var rootDir = "";
                var drive = Path.GetPathRoot(path);
                if (!string.IsNullOrWhiteSpace(drive))
                {
                    rootDir = drive[0] + "_drive";
                    rootDir = rootDir + path.Substring(2);
                }
                else
                    rootDir = path;
                var selectedFiles = GetFilesInDirectory(path, excludeFilesRegex, excludePathsRegex, incremental);
                files.AddOrUpdate(rootDir, selectedFiles.ToList(), (a, b) => b);
            }));
        await Task.WhenAll(tasks);
        return files;
    }

    private List GetFilesInDirectory(string directory, Regex excludeFilesRegex, 
        Regex excludePathsRegex,bool incremental)
    {
        var files = new List();
        try
        {
            var directories = Directory.GetDirectories(directory);
            try
            {
                var selectedFiles = Directory.EnumerateFiles(directory).Where(f => !excludeFilesRegex.IsMatch(f.ToLower()));
                if (incremental)
                    selectedFiles = selectedFiles.Where(f => (File.GetAttributes(f) & FileAttributes.Archive) != 0);
                files.AddRange(selectedFiles);
            }
            catch
            {
            }
            foreach (var dir in directories.Where(d => !excludePathsRegex.IsMatch(d.ToLower())))
            {
                files.AddRange(GetFilesInDirectory(Path.Combine(directory, dir), excludeFilesRegex, excludePathsRegex, incremental));
            }
        }
        catch
        {
        }

        return files;
    }
}

I pass the filters to the GetFilesInDirectory method and do my filter there. That way, the folders I don’t want aren’t enumerated. For that, I had to make a change in the Config class, adding a new property for the path Regex and initializing it:

public class Config
{
    public Config(string fileName)
    {
        if (!File.Exists(fileName))
            return;
        var doc = XDocument.Load(fileName);
        if (doc.Root == null)
            return;
        IncludePaths = doc.Root.Element("IncludePaths")?.Value.Split(';');
        ExcludeFiles = doc.Root.Element("ExcludeFiles")?.Value.Split(';') ?? new string[0] ;
        ExcludePaths = doc.Root.Element("ExcludePaths")?.Value.Split(';') ?? new string[0];
        BackupFile = $"{doc.Root.Element("BackupFile")?.Value}{DateTime.Now:yyyyMMddhhmmss}.zip";
        ExcludeFilesRegex = new Regex(string.Join("|", ExcludeFiles));
        ExcludePathRegex = new Regex(string.Join("|", ExcludePaths));
    }

    public Regex ExcludeFilesRegex { get; }
    public Regex ExcludePathRegex { get; }
    public IEnumerable IncludePaths { get; }
    public IEnumerable ExcludeFiles { get; }
    public IEnumerable ExcludePaths { get; }
    public string BackupFile { get; }
}

With this changes, I could run the program again and measure the differences. That made a great difference. Before the change, the program was taking 160s to enumerate the files and give me 470000 files. After the change, enumerating the files took only 14.5s to give me the same files (I ran the programs three times each to avoid distortions). That’s a huge difference, no?

Then I started to think a little bit more and thought that the Regex could be compiled. So, I made two simple changes in the Config class:

ExcludeFilesRegex = new Regex(string.Join("|", ExcludeFiles),RegexOptions.Compiled);
ExcludePathRegex = new Regex(string.Join("|", ExcludePaths), RegexOptions.Compiled);

When I ran again the program, it took me 11.5s to enumerate the files. It doesn’ t seem much, but it’s a 25% improvement with just a simple change. That was really good. That way, I have a backup program that enumerates files way faster than before.

All the source code for the project is at https://github.com/bsonnino/BackupData

I’m sure that you have had this scenario at least once: you download a file from the internet and try to run it and it doesn’t work fine. If it is a CHM file, the help file doesn’t open the content:

image

 

If it’s an executable, it will show a message telling you that you got it from an unreliable source or, even worse, it stays there and does nothing. A client of mine set me a zip with some files to install. I downloaded the zip, unzipped it and tried to run and nothing happened. The only thing I could do is to open Task Manager and kill the Explorer process.

This is caused by a mechanism Windows has to lock files that come from the internet and can harm your computer. If it is a zipped file and you unzip it with Explorer, all extracted files will be protected with this mechanism. If you right click the file in Explorer, select Properties and check on the “Unlock” box, everything runs fine. The file is unlocked.

image

image

But what is this protection mechanism and how can we check if the file is protected and unprotect it?

The mechanism used is called “Alternate Data Streams” (ADS), and it’s a way that NTFS has to add extra data in a file. This data is not shown when you open a file and is size is not added to the total file size, but the data is there, hidden and taking disk space. If you open a command line prompt window and do a Dir /R command, you can see these alternate streams:

image

In the image above, you can see two lines for each file. The first one is the “real” file, and the second one is the “hidden” file. You can see that the “hidden” file has the same name of the “real” file and a complement after the colon. The Zone.Identifier:$DATA is the alternate stream for the file. It may have any name and can have any size (it can even be larger than the “real” file) and it’s a very effective way to hide information – if you open a file, you won’t see any trace of the alternate data stream. But, with great power comes great responsibility, and that can also be used for evil purposes – it can hide things the user is not aware and would not want in his computer.

For our blocked files, we know that they have a ADS, named Zone.Identifier. But what’s in the files? To show the contents of an ADS, you cannot use the normal tools. You can use Powershell to open them. For example, if you open a Powershell command line (se Window+X, then select “Windows Powershell”, you can type

Get-Content -Path .\Web_Servers_Succinctly.pdf -Stream Zone.Identifier

And it will show the contents of the file:

image

We can see that this file was transferred from Zone=3. From here we can see that 3 is URLZONE_INTERNET, the file came from the internet. So that’s what is blocking the file. It we unblock the file, here’s what we get when we query the streams in the file:

image

We have changed the ZoneId to 4, URLZONE_UNTRUSTED. This is enough to unblock the file.

Manipulating ADSs with C#

Now we know the mechanism behind Windows protection and we can create a C# program that unblocks all files in a directory. There is no API to access file streams in .NET, so you must resort to Win32 P/Invoke. The functions NtfsFindFirstStream and NtfsFindNextStream enumerate all streams in a file. DeleteFile will delete a stream, and CreateFile will create and add the stream.

Fortunately, we don’t have to work with this functions, as a brave developer has created a library to handle ADS in .NET (https://github.com/RichardD2/NTFS-Streams) and we can add it from Nuget. We will create a program to list, show contents or delete alternate streams with C#.

In Visual Studio, create a new Console application. Right click in the References node and select Manage Nuget Packages. There, install Trinet.Core.IO.Ntfs.

image

Then, in Program.cs, we will start to create our program:

static void Main(string[] args)
{
    if (args.Length == 0)
    {
        Console.WriteLine("usage: adsviewer -(l|s|d)  [streamname]");
        return;
    }
    var selSwitch = ProcessSwitches(args);
    if (selSwitch == null)
        return;
    var parameters = args.Where(a => !a.StartsWith("-"));
    string fileName = ".";
    if (parameters.Count() > 0)
        fileName = parameters.First();
    var streamName = parameters.Skip(1).FirstOrDefault();
    if (File.Exists(fileName))
        ProcessFile(fileName, streamName, selSwitch);
    else if (Directory.Exists(fileName))
        foreach (var file in Directory.GetFiles(fileName))
            ProcessFile(file, streamName, selSwitch);
    else
    {
        Console.WriteLine("error: file/folder does not exist");
        return;
    }
}

Initially, we’ve checked if the user passed any parameters. If not, we show the usage help. Then, we process the switch (the option with a hiphen), that can be a “l” (to list the stream names), a “s” (to show the content of the streams or a “d”  (to delete the streams.

Then, we get the file or folder name and the stream name. If the user didn’t pass any file name, we admit he wants to process all the streams in all files in the current directory. If he passes a name, we check if it’s a file or a folder. If it’s a folder, we will process all files in that folder.

If the user passes a stream name, we will only process that stream. If he doesn’t pass a name, we will process all streams in the file.

ProcessSwitches checks the passed switch and sees if its valid:

private static string ProcessSwitches(string[] args)
{
    var switches = args.Where(a => a.StartsWith("-"));
    if (switches.Count() > 1)
    {
        Console.WriteLine("error: you can use only one switch at a time (-l, -s, -d)");
        return null;
    }
    var selSwitch = switches.Count() == 0 ? "l" : switches.First().Substring(1);
    if (!"lsd".Contains(selSwitch))
    {
        Console.WriteLine("error: valid switches: -l(ist), -s(how contents), -d(elete)");
        return null;
    }
    return selSwitch;
}

ProcessFile will process one file. If the user passed a directory name, we enumerate all files in the directory and call ProcessFile for each file.

private static void ProcessFile(string fileName, string streamName, string selSwitch)
{
    switch (selSwitch)
    {
        case "l":
            ListStreams(fileName);
            break;
        case "s":
            ShowContents(fileName, streamName);
            break;
        case "d":
            DeleteStream(fileName, streamName);
            break;
    }
}

Then, we have three kind of processing: if the user wants to list the names, we call ListStreams:

private static void ListStreams(string fileName)
{
    FileInfo file = new FileInfo(fileName);
    Console.WriteLine($"{file.Name} - {file.Length:n0}");
    foreach (AlternateDataStreamInfo s in file.ListAlternateDataStreams())
        Console.WriteLine($"    {s.Name} - {s.Size:n0}");
}

It will enumerate all streams and list their names and sizes. The second processing is to show the contents, in ShowContents:

private static void ShowContents(string fileName, string streamName)
{
    FileInfo file = new FileInfo(fileName);
    Console.WriteLine($"{file.Name} - {file.Length:n0}");
    if (!string.IsNullOrWhiteSpace(streamName) &amp;&amp; file.AlternateDataStreamExists(streamName))
    {
        AlternateDataStreamInfo s = file.GetAlternateDataStream(streamName, FileMode.Open);
        ShowContentsOfStream(file, s);
    }
    else if (string.IsNullOrWhiteSpace(streamName))
        foreach (AlternateDataStreamInfo s in file.ListAlternateDataStreams())
            ShowContentsOfStream(file, s);
}

If the user passed a name of a valid stream, it will show its contents. If it has not passed a name, the program will enumerate all streams and show the contents for them all. The ShowContentsOfStream method is:

private static void ShowContentsOfStream(FileInfo file, AlternateDataStreamInfo s)
{
    Console.WriteLine($"    {s.Name}");
    using (TextReader reader = s.OpenText())
    {
        Console.WriteLine(reader.ReadToEnd());
    }
}

Finally, the third kind of processing is to delete the streams. For that, we use the DeleteStreams method:

private static void DeleteStream(string fileName, string streamName)
{
    FileInfo file = new FileInfo(fileName);
    Console.WriteLine($"{file.Name} - {file.Length:n0}");
    if (!string.IsNullOrWhiteSpace(streamName) && file.AlternateDataStreamExists(streamName))
    {
        file.DeleteAlternateDataStream(streamName);
        Console.WriteLine($"    {streamName} deleted");
    }
    else if (string.IsNullOrWhiteSpace(streamName))
        foreach (AlternateDataStreamInfo s in file.ListAlternateDataStreams())
        {
            s.Delete();
            Console.WriteLine($"    {s.Name} deleted");
        }
}

If the user passed a valid stream name, the program will delete that stream. If he hasn’t passed a name, the program will delete all streams in the file.

That way, we arrived at the end of our program.

Conclusions

ADSs are used in many ways, to add extra data to a file. Although there are legitimate ways to use them, sometimes, that can be used to store harmful data. We have developed a tool that can list all ADSs in a file, show their contents or delete them. With it, you can analyze the data that is attached to your file and decide what to do with it. Next time you get something that came from the internet and is blocked, you can easily unblock it with this tool.

The full source code is in http://github.com/bsonnino/ADSViewer