Jekyll2018-05-18T20:44:35+00:00http://www.softwarearbeiter.de/softwarearbeiter.deAus dem Leben eines SoftwarearbeitersSignalR for ASP.NET Core 2.0 with Aurelia and Webpack2017-09-15T00:00:00+00:002017-09-15T00:00:00+00:00http://www.softwarearbeiter.de/ASPNET-Core-SignalR-with-Aurelia-and-Webpack<p>Just today (September 15, 2017) Microsoft announced the <a href="https://blogs.msdn.microsoft.com/webdev/2017/09/14/announcing-signalr-for-asp-net-core-2-0/">first alpha version of <strong>SignalR for ASP.NET Core 2.0</strong></a>. Now these are great news! I immediately had to try this out with my favorite way of building web apps: The <a href="https://aurelia.io">Aurelia framework</a>. I can already say: It works like a charm. But let me start from the beginning.</p>
<blockquote>
<p><strong>TL;DR</strong> If you do not want to follow this tutorial and just want the code, go ahead, have a look at my <a href="https://github.com/Spontifixus/aurelia-playground/tree/feature-signalr-core">Aurelia Playground over on GitHub</a>!</p>
</blockquote>
<h2 id="setting-up-a-simple-application">Setting up a simple application</h2>
<p>I used a skeleton I once created to have an easy starting point with Aurelia. Initially the skeleton was set up using the Aurelia template coming with the the <a href="https://blogs.msdn.microsoft.com/webdev/2017/02/14/building-single-page-applications-on-asp-net-core-with-javascriptservices/">ASP.NET Core SPA templates</a> (<code class="highlighter-rouge">dotnet new aurelia</code>). I upgraded Bootstrap to the shiny new version 4 but that’s basically all I changed. Feel free to use it as a starting point for your projects!</p>
<p>For this project I am going to use this template as a baseline. Go ahead and clone <a href="https://github.com/Spontifixus/aurelia-playground.git">https://github.com/Spontifixus/aurelia-playground.git</a> to get started and read on to build your first ASP.NET Core 2.0 SignalR application!</p>
<!--more-->
<h2 id="signalr-on-the-server">SignalR on the server</h2>
<p>This section covers the server-side components needed to power the chat application.</p>
<h3 id="installing-the-nuget-package">Installing the nuget package</h3>
<p>To install the new SignalR, everything you need to do, is to open a console and run</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet add package Microsoft.AspNetCore.SignalR <span class="nt">--version</span> 1.0.0-alpha1-final
</code></pre></div></div>
<p>That installs all required packages and dependencies needed by SignalR.</p>
<h3 id="creating-a-chathub">Creating a ChatHub</h3>
<p>The center of all SignalR activities are so called Hubs. Hubs handle connections and provide methods the clients can invoke remotely. Of course hubs can send events to the clients, too! So we need to create a hub class, that can receive a chat message and distribute it among the clients.</p>
<p>Let’s create a folder named “SignalR” and add a file named “ChatHub.cs” to it:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Threading.Tasks</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.AspNetCore.SignalR</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">AureliaPlayground.SignalR</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ChatHub</span> <span class="p">:</span> <span class="n">Hub</span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">Task</span> <span class="nf">SendMessage</span><span class="p">(</span><span class="n">Message</span> <span class="n">message</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="n">Clients</span><span class="p">.</span><span class="n">All</span><span class="p">.</span><span class="nf">InvokeAsync</span><span class="p">(</span><span class="s">"IncomingMessageEvent"</span><span class="p">,</span> <span class="n">message</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As you can see, the method <code class="highlighter-rouge">SendMessage</code> takes a message and distributes it to all clients. Of course the definition of the <code class="highlighter-rouge">Message</code>-class is still missing, so let’s add that quickly. Add a file named “Message.cs” to the “SignalR”-folder:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="nn">AureliaPlayground.SignalR</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Message</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">SenderName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Text</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Obviously this is a very simple implementation. It should be expanded to have at least a proper error handling, but for now this will do.</p>
<h3 id="configuring-our-application">Configuring our Application</h3>
<p>Now that our <code class="highlighter-rouge">ChatHub</code>-class was implemented, we need to make the hub known to the system. We do this by first registering the SignalR components to the dependency injection container and then configuring a route to the chat hub. Adding SignalR to the container can be achieved by adding the following line to the <code class="highlighter-rouge">ConfigureServices</code>-method of the <code class="highlighter-rouge">Startup</code>-class:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">services</span><span class="p">.</span><span class="nf">AddSignalR</span><span class="p">();</span>
</code></pre></div></div>
<p>Don’t forget to add the using directive (<code class="highlighter-rouge">using Microsoft.AspNetCore.SignalR;</code>)!</p>
<p>With the new SignalR for ASP.NET Core 2.0 configuring the routes is as easy as we know it from ASP.NET Core MVC - it works very much the same way. Just add the following snippet to the <code class="highlighter-rouge">Configure</code>-method of the <code class="highlighter-rouge">Startup</code>-class:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">UseSignalR</span><span class="p">(</span><span class="n">routes</span> <span class="p">=></span>
<span class="p">{</span>
<span class="n">routes</span><span class="p">.</span><span class="n">MapHub</span><span class="p"><</span><span class="n">ChatHub</span><span class="p">>(</span><span class="s">"chat"</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Again, don’t forget to add the using directive for the <code class="highlighter-rouge">ChatHub</code>-class. This code configures SignalR to publish the hub under the <code class="highlighter-rouge">/chat</code> URL.</p>
<p>This is all you need to do to get things running on the server side!</p>
<h2 id="signalr-on-the-client">SignalR on the client</h2>
<p>This sections explains how to setup the client-side components of our application.</p>
<h3 id="installing-the-client">Installing the client</h3>
<p>Microsoft built an entirely new SignalR client for JavaScript or TypeScript. The new client has a much simpler interface than the older versions and thus can be configured and used much more intuitively.</p>
<p>First things first: To be able to use SignalR on the client side install the npm package by opening a command line and running</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">npm</span> <span class="n">install</span> <span class="n">@aspnet</span><span class="p">/</span><span class="n">signalr</span><span class="p">-</span><span class="n">client</span> <span class="p">--</span><span class="n">save</span>
</code></pre></div></div>
<p>Our little application uses webpack, and to bundle the client into our vendor bundle, we need to modify the relevant config file. So add <code class="highlighter-rouge">@aspnet/signalr-client</code> to the list of packages in the “webpack.config.vendor.js” file:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">vendor</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'aurelia-event-aggregator'</span><span class="p">,</span>
<span class="s1">'aurelia-fetch-client'</span><span class="p">,</span>
<span class="s1">'aurelia-framework'</span><span class="p">,</span>
<span class="s1">'aurelia-history-browser'</span><span class="p">,</span>
<span class="s1">'aurelia-logging-console'</span><span class="p">,</span>
<span class="s1">'aurelia-pal-browser'</span><span class="p">,</span>
<span class="s1">'aurelia-polyfills'</span><span class="p">,</span>
<span class="s1">'aurelia-route-recognizer'</span><span class="p">,</span>
<span class="s1">'aurelia-router'</span><span class="p">,</span>
<span class="s1">'aurelia-templating-binding'</span><span class="p">,</span>
<span class="s1">'aurelia-templating-resources'</span><span class="p">,</span>
<span class="s1">'aurelia-templating-router'</span><span class="p">,</span>
<span class="s1">'bootstrap'</span><span class="p">,</span>
<span class="s1">'bootstrap/dist/css/bootstrap.css'</span><span class="p">,</span>
<span class="s1">'jquery'</span><span class="p">,</span>
<span class="s1">'popper.js'</span><span class="p">,</span>
<span class="s1">'@aspnet/signalr-client'</span>
<span class="p">]</span>
</code></pre></div></div>
<p>Then open a console and run webpack to rebundle the “vendor.js” file:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>webpack <span class="nt">--config</span> webpack.config.vendor.js
</code></pre></div></div>
<p>Note that although jQuery is still included in our build it is only used by bootstrap, but no longer by SignalR!</p>
<h3 id="create-an-aurelia-component">Create an Aurelia component</h3>
<p>All we need now is a page in our application that we can use to chat. For this create a new folder named “chat” in the “ClientApp/components”-folder of our application. Then add a component named “chat.ts” to this folder:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Chat</span> <span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>
<p>To establish a connection to the chat hub we need to set up a hub connection. The SignalR client provides a class taking care of this. Let’s create a private field and a constructor to create an instance of the <code class="highlighter-rouge">HubConnection</code>-class:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">private</span> <span class="nx">chatHubConnection</span><span class="p">:</span> <span class="nx">HubConnection</span><span class="p">;</span>
<span class="kd">constructor</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">chatHubConnection</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HubConnection</span><span class="p">(</span><span class="s1">'/chat'</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Note that in a real-life application you usually would encapsulate the connectivity to the chat hub in a chat service that you then can resolve using dependency injection.</p>
<p>Before we listen for incoming messages we need to add a field to store them, so we can access them from the template:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">chatLog</span><span class="p">:</span> <span class="nx">Message</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span>
</code></pre></div></div>
<p>Now we can register to the <code class="highlighter-rouge">IncomingMessageEvent</code> in the constructor:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">this</span><span class="p">.</span><span class="nx">chatHubConnection</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'IncomingMessageEvent'</span><span class="p">,</span> <span class="p">(</span><span class="nx">incomingMessage</span><span class="p">:</span> <span class="nx">Message</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">chatLog</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">incomingMessage</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>You might note that we use a class named “Message” here, so add a new class below the <code class="highlighter-rouge">Chat</code>-class:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ChatMessage</span> <span class="p">{</span>
<span class="nl">SenderName</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span>
<span class="nl">Text</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>To actually listen to new events we need to start the hub connection. Starting a hub connection returns a promise that gets resolved once the connection is established. We will store the promise in a private field so we can access it later on to ensure a working connection. To start the connection when our component gets activated we implement the <code class="highlighter-rouge">activate</code>-method:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">private</span> <span class="nx">connectionPromise</span><span class="p">?:</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span><span class="p">;</span>
<span class="nx">activate</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">connectionPromise</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">chatHubConnection</span><span class="p">.</span><span class="nx">start</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Also we implement the <code class="highlighter-rouge">deactivate</code>-method to stop the connection once we navigate away from the chat:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">deactivate</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">connectionPromise</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">chatHubConnection</span><span class="p">.</span><span class="nx">stop</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now we need a method to send a new message to the chat. For this we add a field containing an empty message that we can bind to from the template and a method actually sending the message. We use the connection promise to ensure that the hub is connected before sending the message:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="nx">sendMessage</span><span class="p">():</span> <span class="nb">Promise</span><span class="o"><</span><span class="k">void</span><span class="o">></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">connectionPromise</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">warning</span><span class="p">(</span><span class="s1">'Chat: No connection to the server.'</span><span class="p">)</span>
<span class="p">}</span>
<span class="kr">await</span> <span class="nx">connectionPromise</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">chatHubConnection</span><span class="p">.</span><span class="nx">invoke</span><span class="p">(</span><span class="s1">'sendMessage'</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">currentMessage</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">currentMessage</span><span class="p">.</span><span class="nx">Text</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Finally we need a template for our component. So add a file named “chat.html” to the chat folder:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><template></span>
<span class="nt"><h1></span>Chat<span class="nt"></h1></span>
<span class="nt"><p></span>This is a simple example of a SignalR Chat application. Every message you type here will be displayed at all other clients.<span class="nt"></p></span>
<span class="nt"><form></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"form-row"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"col"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">class=</span><span class="s">"form-control"</span> <span class="na">placeholder=</span><span class="s">"Your name..."</span> <span class="na">value</span><span class="err">.</span><span class="na">two-way=</span><span class="s">"currentMessage.SenderName"</span><span class="nt">></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"col-7"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">class=</span><span class="s">"form-control"</span> <span class="na">placeholder=</span><span class="s">"Your message..."</span> <span class="na">value</span><span class="err">.</span><span class="na">two-way=</span><span class="s">"currentMessage.Text"</span><span class="nt">></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"col"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span> <span class="na">click</span><span class="err">.</span><span class="na">delegate=</span><span class="s">"sendMessage()"</span><span class="nt">></span>Send<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></form></span>
<span class="nt"><p></span>
<span class="nt"><ul></span>
<span class="nt"><li</span> <span class="na">repeat</span><span class="err">.</span><span class="na">for =</span><span class="err"> </span><span class="s">"message of chatLog"</span><span class="nt">><strong></span>${message.SenderName}:<span class="nt"></strong></span> ${message.Text}<span class="nt"></li></span>
<span class="nt"></ul></span>
<span class="nt"></p></span>
<span class="nt"></template></span>
</code></pre></div></div>
<p>This template provides a simple form where we can enter a name and the message and click a button to send it. Incoming messages are just listed below the form.</p>
<h3 id="add-the-navigation-item">Add the navigation item</h3>
<p>Open the file “ClientApp/components/app/app.ts” and add our component to the navigation menu:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nl">route</span><span class="p">:</span> <span class="s1">'chat'</span><span class="p">,</span>
<span class="nx">name</span><span class="p">:</span> <span class="s1">'chat'</span><span class="p">,</span>
<span class="nx">settings</span><span class="p">:</span> <span class="p">{</span> <span class="nl">icon</span><span class="p">:</span> <span class="s1">'th-list'</span> <span class="p">},</span>
<span class="nx">moduleId</span><span class="p">:</span> <span class="nx">PLATFORM</span><span class="p">.</span><span class="nx">moduleName</span><span class="p">(</span><span class="s1">'../chat/chat'</span><span class="p">),</span>
<span class="nx">nav</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nx">title</span><span class="p">:</span> <span class="s1">'Chat'</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="build-and-run-the-application">Build and run the application</h2>
<p>To run the application open a console and run</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>webpack <span class="nt">--config</span> webpack.config.vendor.js
webpack
dotnet restore
dotnet build
dotnet run
</code></pre></div></div>
<h2 id="summary">Summary</h2>
<p>The first impression is that the first alpha of SignalR for ASP.NET Core 2.0 already works really well and is fun to use. I literally put this together in about half an hour (writing this up took a bit longer of course…).</p>
<p>I am really pleased where this is going and how well this works together with Aurelia and Webpack. What are your experiences with this?</p>Just today (September 15, 2017) Microsoft announced the first alpha version of SignalR for ASP.NET Core 2.0. Now these are great news! I immediately had to try this out with my favorite way of building web apps: The Aurelia framework. I can already say: It works like a charm. But let me start from the beginning. TL;DR If you do not want to follow this tutorial and just want the code, go ahead, have a look at my Aurelia Playground over on GitHub! Setting up a simple application I used a skeleton I once created to have an easy starting point with Aurelia. Initially the skeleton was set up using the Aurelia template coming with the the ASP.NET Core SPA templates (dotnet new aurelia). I upgraded Bootstrap to the shiny new version 4 but that’s basically all I changed. Feel free to use it as a starting point for your projects! For this project I am going to use this template as a baseline. Go ahead and clone https://github.com/Spontifixus/aurelia-playground.git to get started and read on to build your first ASP.NET Core 2.0 SignalR application!Reflection ohne Reflection: Properties lesen und setzen per Databinding2017-02-06T00:00:00+00:002017-02-06T00:00:00+00:00http://www.softwarearbeiter.de/Reflection-ohne-Reflection<p>Nehmen wir an, wir entwickeln ein UserControl, dass die DependencyProperty <code class="highlighter-rouge">TextPath</code> vom Typ <code class="highlighter-rouge">string</code> bereitstellt. Diese Property kann der Anwender im XAML-Code setzen um das Steuerelement zu veranlassen um den Wert einer bestimmten Property seines DataContexts auszulesen oder zu setzen. Das riecht danach, das Problem mit handelsüblicher Reflection zu lösen:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">propertyInfo</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">DataContext</span>
<span class="p">.</span><span class="nf">GetType</span><span class="p">()</span>
<span class="p">.</span><span class="nf">GetProperty</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">TextPath</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="n">propertyInfo</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">DataContext</span><span class="p">,</span> <span class="k">null</span><span class="p">);</span>
</code></pre></div></div>
<p>Das klappte auch hervorragend. Bis einer der Anwender der Komponente auf die (absolut nachvollziehbare) Idee kam, den Namen der auszulesenden Property under Verwendung der <a href="http://msdn.microsoft.com/en-us/library/cc645024%28v=vs.95%29.aspx">Property Path Syntax</a> anzugeben:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><MyControl</span> <span class="na">TextPath=</span><span class="s">"Address.City"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>Und dann stellt man plötzlich fest, dass man mit Reflection in diesem Fall nicht sehr weit kommt. Theoretisch wäre eine Lösung zwar möglich, aber nur, wenn man bereit ist einen Parser für die Property Path Syntax zu schreiben und noch mehr Reflection in den Ring zu schicken.</p>
<p>Es gibt allerdings eine Komponente in Silverlight und im .NET-Framework die diese Funktionalität schon fix und fertig liefert: Die Klasse <code class="highlighter-rouge">System.Windows.Data.Binding</code>. Das Prinzip ist einfach: Man bindet die Quelle (in diesem Fall den DataContext des Steuerelements) unter Verwendung des angegebenen Pfades an eine Property einer Helferklasse und lasse das Binding seine Arbeit tun. Wie das konkret vonstatten geht?<!--more--></p>
<p>Erste Zutat ist die Helferklasse. Dies muss von <code class="highlighter-rouge">DependencyObject</code> ableiten und eine <code class="highlighter-rouge">DependencyProperty</code> bereitstellen an die man später binden kann:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">BindingEvaluator</span> <span class="p">:</span> <span class="n">DependencyObject</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">DependencyProperty</span> <span class="n">TargetProperty</span> <span class="p">=</span>
<span class="n">DependencyProperty</span><span class="p">.</span><span class="nf">Register</span><span class="p">(</span><span class="s">"Target"</span><span class="p">,</span>
<span class="k">typeof</span><span class="p">(</span><span class="kt">object</span><span class="p">),</span>
<span class="k">typeof</span><span class="p">(</span><span class="n">BindingEvaluator</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">PropertyMetadata</span><span class="p">(</span><span class="k">default</span><span class="p">(</span><span class="kt">object</span><span class="p">)));</span>
<span class="k">public</span> <span class="kt">object</span> <span class="n">Target</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">TargetProperty</span><span class="p">);</span> <span class="p">}</span>
<span class="k">set</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nf">SetValue</span><span class="p">(</span><span class="n">TargetProperty</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Für die eigentliche Auswertung erstellt man <a href="http://msdn.microsoft.com/en-us//library/bb383977.aspx">Extension-Methoden</a> die es ermöglichen, den Wert einer Property eines Objektes sowohl auszulesen als auch zu setzen. Dazu wird ein Binding erstellt, das als Quelle das Zielobjekt erhält und als Pfad den angegeben Property Path. Anschließend kann über die <code class="highlighter-rouge">Target</code>-Property der <code class="highlighter-rouge">BindingEvaluator</code>-Klasse der Wert gelesen oder geschrieben werden:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">ObjectExtensions</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="kt">object</span> <span class="nf">GetPropertyValue</span><span class="p">(</span><span class="k">this</span> <span class="kt">object</span> <span class="n">source</span><span class="p">,</span>
<span class="kt">string</span> <span class="n">propertyPath</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">binding</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Binding</span> <span class="p">{</span> <span class="n">Source</span> <span class="p">=</span> <span class="n">source</span><span class="p">,</span>
<span class="n">Path</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">PropertyPath</span><span class="p">(</span><span class="n">propertyPath</span><span class="p">)};</span>
<span class="kt">var</span> <span class="n">evaluator</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BindingEvaluator</span><span class="p">();</span>
<span class="n">BindingOperations</span><span class="p">.</span><span class="nf">SetBinding</span><span class="p">(</span><span class="n">evaluator</span><span class="p">,</span>
<span class="n">BindingEvaluator</span><span class="p">.</span><span class="n">TargetProperty</span><span class="p">,</span>
<span class="n">binding</span><span class="p">);</span>
<span class="k">return</span> <span class="n">evaluator</span><span class="p">.</span><span class="n">Target</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">SetPropertyValue</span><span class="p">(</span><span class="k">this</span> <span class="kt">object</span> <span class="n">source</span><span class="p">,</span>
<span class="kt">string</span> <span class="n">propertyPath</span><span class="p">,</span>
<span class="kt">object</span> <span class="k">value</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">binding</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Binding</span> <span class="p">{</span> <span class="n">Source</span> <span class="p">=</span> <span class="n">source</span><span class="p">,</span>
<span class="n">Path</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">PropertyPath</span><span class="p">(</span><span class="n">propertyPath</span><span class="p">),</span>
<span class="n">Mode</span> <span class="p">=</span> <span class="n">BindingMode</span><span class="p">.</span><span class="n">TwoWay</span> <span class="p">};</span>
<span class="kt">var</span> <span class="n">evaluator</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BindingEvaluator</span><span class="p">();</span>
<span class="n">BindingOperations</span><span class="p">.</span><span class="nf">SetBinding</span><span class="p">(</span><span class="n">evaluator</span><span class="p">,</span>
<span class="n">BindingEvaluator</span><span class="p">.</span><span class="n">TargetProperty</span><span class="p">,</span>
<span class="n">binding</span><span class="p">);</span>
<span class="n">evaluator</span><span class="p">.</span><span class="n">Target</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Dabei ist zu beachten, dass in der Methode <code class="highlighter-rouge">SetPropertyValue</code> das Binding als TwoWay-Binding konfiguriert wird, da der Wert ansonsten nicht auf das Zielobjekt zurückgeschrieben wird.</p>
<p>Aufgerufen werden die Methoden dann direkt auf dem Zielobjekt:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">city</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">DataContext</span><span class="p">.</span><span class="nf">GetPropertyValue</span><span class="p">(</span><span class="s">"Address.City"</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="n">DataContext</span><span class="p">.</span><span class="nf">SetPropertyValue</span><span class="p">(</span><span class="s">"Address.City"</span><span class="p">,</span> <span class="s">"Hannover"</span><span class="p">);</span>
</code></pre></div></div>
<p>Keine Lösung kommt ohne Nachteile aus. Es ist unumstritten, dass Reflection teuer ist. Aber: Die hier vorgestellte Variante ist noch teurer. Liest man einen einfach verschachtelten Property Path (also z.B. “Address.City”) eine Million mal mit Reflection aus dauert das insgesamt knapp 600 Millisekunden. Verwendet man zum Lesen der Daten stattdessen den oben vorgestellten Ansatz dauert der Vorgang gute 26 Sekunden!</p>
<p><strong>Fazit:</strong> Muss man nur ab und an den Wert einer Property unter Verwendung eines Property Path auslesen ist der hier vorgestellte Weg eine gute Möglichkeit. Zur Massenverarbeitung ist er aber definitiv ungeeignet.</p>Nehmen wir an, wir entwickeln ein UserControl, dass die DependencyProperty TextPath vom Typ string bereitstellt. Diese Property kann der Anwender im XAML-Code setzen um das Steuerelement zu veranlassen um den Wert einer bestimmten Property seines DataContexts auszulesen oder zu setzen. Das riecht danach, das Problem mit handelsüblicher Reflection zu lösen: var propertyInfo = this.DataContext .GetType() .GetProperty(this.TextPath); var result = propertyInfo.GetValue(this.DataContext, null); Das klappte auch hervorragend. Bis einer der Anwender der Komponente auf die (absolut nachvollziehbare) Idee kam, den Namen der auszulesenden Property under Verwendung der Property Path Syntax anzugeben: <MyControl TextPath="Address.City" /> Und dann stellt man plötzlich fest, dass man mit Reflection in diesem Fall nicht sehr weit kommt. Theoretisch wäre eine Lösung zwar möglich, aber nur, wenn man bereit ist einen Parser für die Property Path Syntax zu schreiben und noch mehr Reflection in den Ring zu schicken. Es gibt allerdings eine Komponente in Silverlight und im .NET-Framework die diese Funktionalität schon fix und fertig liefert: Die Klasse System.Windows.Data.Binding. Das Prinzip ist einfach: Man bindet die Quelle (in diesem Fall den DataContext des Steuerelements) unter Verwendung des angegebenen Pfades an eine Property einer Helferklasse und lasse das Binding seine Arbeit tun. Wie das konkret vonstatten geht?Wenn es watschelt und quakt: Ducktyping in C#2012-06-08T00:00:00+00:002012-06-08T00:00:00+00:00http://www.softwarearbeiter.de/Ducktyping-in-CSharp<blockquote>
<p><em>“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”</em><br />
— James Whitcomb Riley</p>
</blockquote>
<p>Oder auf gut Deutsch: “Wenn ich einen Vogel sehe, der wie eine Ente watschelt und wie eine Ente quakt, dann ist dieser Vogel für mich eine Ente.” Das selbe Prinzip das James Whitcomb Riley auf die Enten angewandt hat, kann man auch in der Software-Entwicklung anwenden. Zwei Fragen stellen sich dabei zwangsläufig. Ist das wirklich eine gute Idee? Und wenn ja, wie geht das?<!--more--></p>
<p>Kritiker des Ducktypings werden sagen, wenn ein Objekt einer Klasse in einen anderen Typ umgewandelt werden soll, muss der neue Typ eben vom alten Typ abgeleitet sein. Diese Aussage ist absolut korrekt und wo das möglich ist, sollte das auch so gehandhabt werden. Allerdings gibt es Szenarien in denen genau das nicht der Fall ist, und in diesen Fällen kann Ducktyping Abhilfe schaffen. Nehmen wir an, wir hätten ein Interface vom Typ <code class="highlighter-rouge">IEnte</code> und eine Klasse vom Typ <code class="highlighter-rouge">Vogel</code>:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IEnte</span>
<span class="p">{</span>
<span class="k">void</span> <span class="nf">Watscheln</span><span class="p">();</span>
<span class="k">void</span> <span class="nf">Quaken</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Vogel</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Watscheln</span><span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Quaken</span><span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Die Klasse <code class="highlighter-rouge">Vogel</code> hat zwar die Methoden <code class="highlighter-rouge">Watscheln()</code> und <code class="highlighter-rouge">Quaken()</code>, leitet aber (aus welchen Gründen auch immer) nicht vom Interface <code class="highlighter-rouge">IEnte</code> ab. Hier braucht man also einen Wrapper, der ein Objekt vom Typ <code class="highlighter-rouge">Vogel</code> in einer Klasse verpackt, die <code class="highlighter-rouge">IEnte</code> implementiert. Das hört sich leicht an, hat aber seine Tücken - und war vor .NET 4 unmöglich. Denn ein Objekt vom <code class="highlighter-rouge">Vogel</code> kann nicht auf <code class="highlighter-rouge">IEnte</code> gecastet werden, das würde einen Compilerfehler verursachen. An dieser Stelle kommt das keyword <code class="highlighter-rouge">dynamic</code> ins Spiel. Die <a href="http://msdn.microsoft.com/de-de/library/dd264741.aspx">MSDN-Dokumentation</a> sagt dazu:</p>
<blockquote>
<p>“Der <code class="highlighter-rouge">dynamic</code>-Typ ermöglicht die Vorgänge, in denen die Überprüfung des Kompilierzeittyps umgangen wird. Stattdessen werden diese Vorgänge zur Laufzeit aufgelöst.”</p>
</blockquote>
<p>Die Implementierung einer Wrapper-Klasse könnte also so aussehen:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">SiehtAusWieEineEnte</span> <span class="p">:</span> <span class="n">IEnte</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="kt">dynamic</span> <span class="n">_ente</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">SiehtAusWieEineEnte</span><span class="p">(</span><span class="kt">dynamic</span> <span class="n">ente</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="n">_ente</span> <span class="p">=</span> <span class="n">ente</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Quaken</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="n">_ente</span><span class="p">.</span><span class="nf">Quaken</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Watscheln</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="n">_ente</span><span class="p">.</span><span class="nf">Watscheln</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Die Überprüfung ob <code class="highlighter-rouge">this._ente</code> überhaupt über die Methoden <code class="highlighter-rouge">Watscheln()</code> und <code class="highlighter-rouge">Quaken()</code> verfügt erfolgt nicht zur Compilezeit, sondern zur Laufzeit. Und so kann man mit folgenden Zeilen ein Objekt des Typs <code class="highlighter-rouge">Vogel</code> in einem Objekt, das das Interface <code class="highlighter-rouge">IEnte</code> implementiert, verpacken:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">vogel</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vogel</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">ente</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SiehtAusWieEineEnte</span><span class="p">(</span><span class="n">vogel</span><span class="p">);</span>
<span class="n">ente</span><span class="p">.</span><span class="nf">Watscheln</span><span class="p">();</span>
<span class="n">ente</span><span class="p">.</span><span class="nf">Quaken</span><span class="p">();</span>
</code></pre></div></div>“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.” — James Whitcomb Riley Oder auf gut Deutsch: “Wenn ich einen Vogel sehe, der wie eine Ente watschelt und wie eine Ente quakt, dann ist dieser Vogel für mich eine Ente.” Das selbe Prinzip das James Whitcomb Riley auf die Enten angewandt hat, kann man auch in der Software-Entwicklung anwenden. Zwei Fragen stellen sich dabei zwangsläufig. Ist das wirklich eine gute Idee? Und wenn ja, wie geht das?