Click here to Skip to main content
14,578,986 members

WPF Asynchronous Functional Programming Styles in C#

Rate this:
2.43 (33 votes)
Please Sign up or sign in to vote.
2.43 (33 votes)
30 Mar 2020CPOL
Introducing an Async First Model for WPF
With the advent of Async/Await, Functional Programming style and Extension methods, we can easily create WPF applications with little more than making event handlers Async.

Introduction

We, as C# programmers, now have the ability using Async/Await with Functional programming styles to implement co-routine like behavior. We, as OOPers, get to functional programming styles anyway; when we continually refactor as far as we can. Why not go one small step further using an Async first methodology as we see in Node.js.

Background

Functional programming is lightweight. It's simply a method (function) that does only one thing and returns an object with no side effects. It is highly focused on what it does and may call other functions (called 'nesting'). Functions contain very few lines of code (maybe 10 lines max). Functions are ideal for creating small part reusable code. And intellisense has no problems with them. Start restocking your personal "toolkits" today!

Let's take a look at creating a function/(small method) ...

C#'s Functional Construct

Consider this simple Async/Await method/function where UpdateStatus is a UWP or WPF method call on the View Thread. We see just one call to a function that can satisfy all requirements of the view. That line of code in this example gets data. The other three lines of code are state logic for the current thread. If we move the CollectionViewSource.Source assignment to the return of Await GetData, we now have three lines of code that can effect everything needed for the view. Just three line pattern code for event handlers is established.

public async void CoRoutineEquivalent()
 {
     //On the original thread
     UpdateStatus("Getting Data");

     //This is a co-routine because it suspends execution of this thread
     //until data is returned
     //This pattern is also called a closure (more in upcoming articles)
     var data = await GetData();

     //We are now back on original thread
     UpdateStatus("Ready");

     //Update our 'global' collection view source
     cvs.Source = data;
 }

How can just three lines of code do everything every particular button click would need? Simple, let's rearrange our stateless function calls into an ordered set of function calls. Each function itself still remains stateless and "pure" while the overall application achieves stateful workflow.

public async void CoRoutineEquivalent()
 {
     //On the original thread
     UpdateStatus("Getting Data");
     //Task is run and data is returned
     cvs.Source = await StartWorkFlow();
     UpdateStatus("Ready");
 }

 private async Task<String> StartWorkFlow()
 {
     var data = await GetJSONData();
     var filtered = await data.Filter();
     var annotated = await filtered.Annotated();
     var finalize = await annotated.Finalize();
     return finalize;
 }

Each function call in StartWorkFlow suspends and resumes automatically. We now have the ability to integrate Asynchronous first in our designs and to start using extension methods to adopt more functional styles. We are now following the lead of Node.js where everything is asynchronous.

No Interfaces needed for Testing

Another advantage of this is that all of the function calls are independently tested without the need for mocking frameworks or interface definitions. The test simply calls the function with the permutations and parameters needed. There is, however; an implicit state in that each of the returned data is being used as input for subsequent function calls. The tester would then need to duplicate this logic to validate the end result.

Functions Have No State

The state however is not implemented in each function call, rather it's the state of the data upon return. The "purity" of these function calls ensures "any caller will always get the same result". The StartWorkFlow pattern is also very good for debugging because each step result is easily seen when single stepping.

Separation of Concerns

Does StartWorkFlow belong in the same class as the method CoRoutineEquivalent? The answer is most likely no as it can live safely in its own folder along with other functions which are easily re-used by other code. StartWorkFlow is easily moved to a static class named XWorkflow that contains not only this (soon to be static method) but all other WorkFlows needed for the project. Each class is typically an extension method named for the class type it extends. For example, all string extension methods are found in XString, integer extensions in XInt, or Tax rules in XTax, etc. We still use models but now we have tons of functional support for that model type.

Event Handlers are Mini Controllers

Putting your code in code behind is ok when using Functional Style techniques with Async/Await. Look no further than the MVC pattern where each "routed" URL request arrives. It's the controller in MVC that kicks off the request's workflow. In WPF/UWP, it's the RoutedEvent (rather than a URL route table) that calls the eventhandler (rather than the controller in MVC). Therefore; the same exact style of routing is happening within desktop apps that is happening in MVC apps. We are able to see any event handler in code behind as a mini MVC Controller equivalent where the routed event is already determined by WPF or UWP.

No More Dipatcher.Invoke(()=>{});

Using the magic of "closures", we can forget about this error. The calling thread must be STA, because many UI components require this.' All of the events that are sent below are on the same thread the method was called on, there's no change of cross-threaded updates.

private async Task<String> StartWorkFlow()
{
    var data = await GetJSONData();
    JSONData(this, data);
    var filtered = await data.Filter();
    FilteredData(this, filtered);
    var annotated = await filtered.Annotated();
    AnnotatedData(this, annotated);
    var finalize = await annotated.Finalize();
    FinalizedData(this, finalize);
    return finalize;
}

The code above shows events being fired (assuming they are all hooked up, didn't want to clutter code), whereby each event notifies some listener that a particular state of the run is complete. This is our ability to "track" states from stateless function calls.

Close Coupling Concern

95% of all views are closely coupled to the data they display anyway. For WPF/UWP, if any single column is bound to a specific path within the model or viewmodel, that view is Closely Coupled. Let's examine other more popular web frameworks to see how they do View Bindings.

What Do Popular Web View Frameworks Do?

Consider this, from Microsoft's own Web API 2 (just put out recently) documentation found here:

$(document).ready(function () {
  // Send an AJAX request
  $.getJSON(uri)
      .done(function (data) {
        // On success, 'data' contains a list of products.
        $.each(data, function (key, item) {
          // Add a list item for the product.
          $('<li>', { text: formatItem(item) }).appendTo($('#products'));
        });
      });
});

The div is named Products because it displays a specific concern. The DOM is manipulated to show each one as a line item. It could be argued that this is loosely coupled, except for two things. The view is known to be the rendering engine for Products only, and it points to a specific URL which returns Product data! That is not loosely coupled, it is a dedicated view for products.

What Does Angular Do?

Consider this in the most popular framework in the world right now, Angular.

import { Component } from '@angular/core';
export class Hero {
  id: number;
  name: string;
}
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <h2>{{hero.name}} details!</h2>
    <div><label>id: </label>{{hero.id}}</div>
    <div>
      <label>name: </label>
      <input [(ngModel)]="hero.name" placeholder="name">
    </div>
    `
})
export class AppComponent {
  title = 'Tour of Heroes';
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };
}

We see the ViewModel/AppComponent with the name of hero.id and hero.name closely coupled to the view!

What Does React Do?

The syntax below is the infamous declarative JSX syntax.

<div className="red">Children Text</div>;
<MyCounter count={3 + 5} />;

// Here, we set the "scores" attribute below to a JavaScript object.
var gameScores = {
  player1: 2,
  player2: 5
};
<DashboardUnit data-index="2">
  <h1>Scores</h1>
  <Scoreboard className="results" scores={gameScores} />
</DashboardUnit>;

In the code above, we see a dedicated view for GameScores, in fact we see the gameScores object in the same file as the view markup. Replacing the gameScores with a function is the obvious next step.

Compositional Views

We can see that in today's Web technology, very small single concern view parts are being closely coupled with their data. There are still other layers that are used, but they are all function calls. If we study Functional Programming techniques, we see it's all about "Purity" which means there are no side effects. Every function call returns exactly the same thing every time within the context it is called.

Points of Interest

Systems.Windows.Forms applications are still popular because they are just easy to write. Visual Studio still treats WPF and UWP applications as if they were System.Windows.Forms applications (just double click on a button in the designer and you'll have instant event handler). With adoption of this style presented in this article, you now have no reason not to complicate your design. Just make all the event handlers async.

Functional Composition using Extension methods coupled with small view parts designed for the type of data they display is ok. This was proved by the fact that most views are closely coupled anyway. Just reduce their size to small parts of the view and you will now have moved one step closer to View Composition. If you truly need loose coupling of the view, then use CollectionViewSource.Source property and set its autogenerate columns property to true.

History

  • 29th June, 2016: Initial post
  • 27th March, 2020: Updated article for just focusing on the async event handler concept and use of async Extensions methods

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mr. Angular
Software Developer (Senior)
United States United States
Mr Javaman

Comments and Discussions

 
QuestionUpdated Article to only focus on Asynchrounous fist styles within WPF Pin
Mr. Angular30-Mar-20 3:11
MemberMr. Angular30-Mar-20 3:11 
AnswerRe: Updated Article to only focus on Asynchrounous fist styles within WPF Pin
Nelek30-Mar-20 5:03
protectorNelek30-Mar-20 5:03 
GeneralRe: Updated Article to only focus on Asynchrounous fist styles within WPF Pin
Mr. Angular1-Apr-20 4:21
MemberMr. Angular1-Apr-20 4:21 
QuestionYou still are missing the big picture... Pin
PureNsanity18-Jul-16 4:00
professionalPureNsanity18-Jul-16 4:00 
AnswerRe: You still are missing the big picture... Pin
johannesnestler18-Jul-16 4:08
Memberjohannesnestler18-Jul-16 4:08 
AnswerLook at all the profound hurdles to the .NET ecosystem world Pin
Mr. Angular18-Jul-16 17:03
MemberMr. Angular18-Jul-16 17:03 
GeneralRe: You still are missing the big picture... Pin
PureNsanity18-Jul-16 18:39
professionalPureNsanity18-Jul-16 18:39 
GeneralYou're name speaks volumes Pin
Mr. Angular19-Jul-16 0:18
MemberMr. Angular19-Jul-16 0:18 
AnswerRe: You still are missing the big picture... Pin
Nelek30-Mar-20 4:43
protectorNelek30-Mar-20 4:43 
GeneralMessage Closed Pin
30-Mar-20 11:35
MemberMr. Angular30-Mar-20 11:35 
GeneralRe: You still are missing the big picture... Pin
PureNsanity30-Mar-20 13:03
professionalPureNsanity30-Mar-20 13:03 
GeneralPureNSanity is a fitting name for your replies Pin
Mr. Angular31-Mar-20 0:26
MemberMr. Angular31-Mar-20 0:26 
GeneralRe: You still are missing the big picture... Pin
PureNsanity31-Mar-20 22:33
professionalPureNsanity31-Mar-20 22:33 
GeneralSelf appointed arrogant Mentors Pin
Mr. Angular1-Apr-20 4:53
MemberMr. Angular1-Apr-20 4:53 
GeneralRe: Self appointed arrogant Mentors Pin
PureNsanity1-Apr-20 6:10
professionalPureNsanity1-Apr-20 6:10 
GeneralRe: You still are missing the big picture... Pin
PureNsanity30-Mar-20 12:45
professionalPureNsanity30-Mar-20 12:45 
GeneralRe: You still are missing the big picture... Pin
Nelek30-Mar-20 13:00
protectorNelek30-Mar-20 13:00 
GeneralRe: You still are missing the big picture... Pin
PureNsanity30-Mar-20 13:06
professionalPureNsanity30-Mar-20 13:06 
GeneralWhy NODE is better than .NET Pin
Mr. Angular31-Mar-20 1:00
MemberMr. Angular31-Mar-20 1:00 
GeneralRe: You still are missing the big picture... Pin
PureNsanity31-Mar-20 22:23
professionalPureNsanity31-Mar-20 22:23 
GeneralWhy WPF is DEAD (even 10 years ago) Pin
Mr. Angular1-Apr-20 4:15
MemberMr. Angular1-Apr-20 4:15 
GeneralRe: Why WPF is DEAD (even 10 years ago) Pin
PureNsanity1-Apr-20 6:05
professionalPureNsanity1-Apr-20 6:05 
QuestionStill not convinced... Pin
johannesnestler18-Jul-16 3:18
Memberjohannesnestler18-Jul-16 3:18 
AnswerAt first 15 years ago I loved MVVM Pattern, but not today Pin
Mr. Angular18-Jul-16 11:55
MemberMr. Angular18-Jul-16 11:55 
AnswerRe: Still not convinced... Pin
Nelek30-Mar-20 4:30
protectorNelek30-Mar-20 4:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 28 Mar 2020

Tagged as

Stats

22.3K views
6 bookmarked