Jekyll2022-02-07T10:12:49+00:00https://darenm.github.io/feed.xmlCustomMayd - Thoughts on coding and developer trainingI am a 8 times Windows Developer MVP and the owner of CustomMayd - a developer training and custom development consultancy.Daren MayWindows App SDK Desktop App with native interop - Part 12022-02-05T05:00:00+00:002022-02-05T05:00:00+00:00https://darenm.github.io/custommayd/winui/desktop/xaml/native/interop/2022/02/05/native-api-and-winui<p>As the Windows App SDK moves beyond release 1.0 and begins to expand it's capability, I thought it was time to start exploring it more fully. In this post I will explore the integration of a native API into a Windows App SDK. You can learn more about Windows App SDK here: <a href="https://docs.microsoft.com/windows/apps/windows-app-sdk/">Windows App SDK</a></p>
<p>The source code for the WinApp can be found here: <a href="https://github.com/darenm/HemanWinUI/tree/part1">HemanWinUI.</a></p>
<p><a href="https://www.darenmay.com/custommayd/winui/desktop/xaml/native/interop/2022/02/05/native-api-and-winui-part2.html">Part 2</a> of this tutorial can be found <a href="https://www.darenmay.com/custommayd/winui/desktop/xaml/native/interop/2022/02/05/native-api-and-winui-part2.html">here</a>.</p>
<h2 id="choosing-a-native-api">Choosing a Native API</h2>
<p>As I want to explore the entire process of building a native API from source through to including it in a Windows App SDK desktop application (I'll call it a WinApp from now on), I wanted to choose a simple standalone API. I settled on the <a href="https://github.com/prideout/heman">heman</a> library:</p>
<blockquote>
<p>This toy project is a tiny MIT-licensed C library of image utilities for dealing with height maps, normal maps, distance fields, and the like. It has a very low-level API, where an "image" is simply a flat array of floats. There are no dependencies and only one header file.</p>
</blockquote>
<h2 id="preparing-my-environment">Preparing my environment</h2>
<p>I have various builds of Visual Studio installed - 2019, 2022 (release and preview). I also have Visual Studio Code installed and I plan to use VSCode to compile the C API.</p>
<p>I noticed that the <a href="https://github.com/prideout/heman">heman</a> library uses <a href="https://cmake.org">CMake</a>, so I installed the following:</p>
<ul>
<li><a href="https://cmake.org/download/">CMake</a> for Windows</li>
<li><a href="https://www.nirsoft.net/utils/dll_export_viewer.html">DLL Export Viewer tool</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools">C/C++ VSCode Extension</a></li>
<li><a href="https://github.com/microsoft/vscode-cmake-tools">CMake Tools VSCode Extension</a></li>
</ul>
<p>A guide for getting started with CMake in Visual Studio Code is available <a href="https://code.visualstudio.com/docs/cpp/CMake-linux">here</a>.</p>
<h2 id="building-the-api-on-windows">Building the API on Windows</h2>
<ol>
<li>
<p>Open Windows terminal and clone the repo</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">git</span><span class="w"> </span><span class="nx">clone</span><span class="w"> </span><span class="nx">https://github.com/prideout/heman.git</span><span class="w">
</span></code></pre></div></div>
</li>
<li>
<p>Open the directory in Visual Studio Code:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cd</span><span class="w"> </span><span class="nx">heman</span><span class="w">
</span><span class="n">code</span><span class="w"> </span><span class="o">.</span><span class="w">
</span></code></pre></div></div>
</li>
<li>
<p>When VSCode launched, I was invited to select a kit for to use for the API. I chose Visual Studio 2022 Enterprise Release amd64. This configured the CMake tool.</p>
<blockquote>
<p><strong>Important</strong>: This will compile the output to target x64 - remember this and configure the WinApp to x64 later on to match.</p>
</blockquote>
</li>
<li>
<p>To configure the project, open the Command Palette [<strong>Ctrl+Shift+P</strong>] and run <strong>CMake: Configure</strong>.</p>
</li>
<li>
<p>To build <strong>heman</strong>, open the Command Palette [<strong>Ctrl+Shift+P</strong>] and run the <strong>CMake: Build</strong> command.</p>
<p>This will build a static library <strong>heman.lib</strong> by default. To use this library in a WinApp, we'll need to update the build process to create a DLL instead.</p>
</li>
<li>
<p>To configure the cmake to create a DLL, open the CMakeLists.txt file and locate the following line:</p>
<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">add_library</span><span class="p">(</span>heman STATIC <span class="si">${</span><span class="nv">HEMAN_SOURCE</span><span class="si">}</span> <span class="si">${</span><span class="nv">MATH_SOURCE</span><span class="si">}</span><span class="p">)</span>
</code></pre></div></div>
</li>
<li>
<p>Update the line to the following:</p>
<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">include</span> <span class="p">(</span>GenerateExportHeader<span class="p">)</span>
<span class="nb">add_library</span><span class="p">(</span>heman SHARED <span class="si">${</span><span class="nv">HEMAN_SOURCE</span><span class="si">}</span> <span class="si">${</span><span class="nv">MATH_SOURCE</span><span class="si">}</span><span class="p">)</span>
</code></pre></div></div>
<p><strong>Note</strong>: As well as the <code>include</code>, notice that the <code>add_library</code> statement uses <code>SHARED</code> instead of <code>STATIC</code>.</p>
</li>
<li>
<p>Rebuild <strong>heman</strong>, open the Command Palette [<strong>Ctrl+Shift+P</strong>] and run the <strong>CMake: Build</strong> command. Notice in the output that <strong>heman.dll</strong> is now produced.</p>
</li>
<li>
<p>To review the functions that have been exported within the DLL, I use the <a href="https://www.nirsoft.net/utils/dll_export_viewer.html">DLL Export Viewer tool</a>. launch the tool and open the <strong>heman.dll</strong>. Unfortunately there are no exported functions:</p>
<p><img src="https://darenm.github.io/assets/heman-no-exports.png" alt="heman.dll with no exports" /></p>
</li>
<li>
<p>There are a number of ways that functions can be exported via a DLL - Microsoft documentation discussing approaches can be found here: <a href="https://docs.microsoft.com/cpp/build/exporting-from-a-dll?view=msvc-170">Exporting from a DLL</a>.</p>
<p>If writing code from scratch, or if you wish to update existing source, function definitions in a header file are prefixed with <code>__declspec(dllexport)</code> - see <a href="https://docs.microsoft.com/cpp/build/exporting-from-a-dll-using-declspec-dllexport?view=msvc-170">Exporting from a DLL Using __declspec(dllexport)</a> for more details. For example:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">Sum</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">);</span>
</code></pre></div></div>
<p>would become:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">__declspec</span><span class="p">(</span><span class="n">dllexport</span><span class="p">)</span> <span class="kt">int</span> <span class="nf">Sum</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">);</span>
</code></pre></div></div>
<p>Another approach, that I shall use here, is to create a module definition file that lists the desired exports - see <a href="https://docs.microsoft.com/cpp/build/exporting-from-a-dll-using-def-files?view=msvc-170">Exporting from a DLL Using DEF Files</a> for more information.</p>
</li>
<li>
<p>To export functions, we need to know the names of each function. Open <strong>include/heman.h</strong> and you will see the API function definitions. For example:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Peek at the stored texel values in a SWIG-amenable way.</span>
<span class="kt">void</span> <span class="nf">heman_image_array</span><span class="p">(</span><span class="n">heman_image</span><span class="o">*</span> <span class="n">img</span><span class="p">,</span> <span class="n">HEMAN_FLOAT</span><span class="o">**</span> <span class="n">outview</span><span class="p">,</span> <span class="kt">int</span><span class="o">*</span> <span class="n">n</span><span class="p">);</span>
<span class="c1">// Peek at the given texel value.</span>
<span class="n">HEMAN_FLOAT</span><span class="o">*</span><span class="nf">heman_image_texel</span><span class="p">(</span><span class="n">heman_image</span><span class="o">*</span><span class="p">,</span> <span class="kt">int</span> <span class="n">x</span><span class="p">,</span> <span class="kt">int</span> <span class="n">y</span><span class="p">);</span>
</code></pre></div></div>
<p>Each function we wish to export will need to be added to a module definition file.</p>
</li>
<li>
<p>To add a module definition, add a new file at the same directory level as <strong>CMakeLists.txt</strong> and name it <strong>heman.def</strong>.</p>
</li>
<li>
<p>Open <strong>heman.def</strong> and add the following:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LIBRARY
EXPORTS
heman_image_create
heman_image_info
heman_image_data
heman_image_array
heman_image_texel
heman_image_sample
heman_image_clear
heman_image_destroy
heman_image_extract_alpha
heman_image_extract_rgb
heman_color_create_gradient
heman_color_set_gamma
heman_color_apply_gradient
heman_color_from_grayscale
heman_color_to_grayscale
heman_color_from_cpcf
heman_generate_island_heightmap
heman_generate_rock_heightmap
heman_generate_planet_heightmap
heman_generate_archipelago_heightmap
heman_generate_archipelago_political
heman_generate_simplex_fbm
heman_lighting_apply
heman_lighting_compute_normals
heman_lighting_compute_occlusion
heman_lighting_set_occlusion_scale
heman_distance_create_sdf
heman_distance_create_df
heman_distance_create_cpcf
heman_distance_from_cpcf
heman_distance_identity_cpcf
heman_import_u8
heman_export_ply
heman_export_with_colors_ply
heman_export_u8
heman_ops_stitch_horizontal
heman_ops_stitch_vertical
heman_ops_normalize_f32
heman_ops_max
heman_ops_step
heman_ops_stairstep
heman_ops_percentiles
heman_ops_sweep
heman_ops_laplacian
heman_ops_sobel
heman_ops_accumulate
heman_ops_warp
heman_ops_warp_points
heman_ops_extract_mask
heman_ops_replace_color
heman_ops_merge_political
heman_ops_emboss
heman_points_create
heman_points_destroy
heman_points_from_grid
heman_points_from_poisson
heman_points_from_density
heman_draw_points
heman_draw_colored_points
heman_draw_colored_circles
heman_draw_splats
heman_draw_contour_from_points
heman_get_num_threads
</code></pre></div></div>
</li>
<li>
<p>Reopen the CMakeLists.txt file and locate the following line:</p>
<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">add_library</span><span class="p">(</span>heman SHARED <span class="si">${</span><span class="nv">HEMAN_SOURCE</span><span class="si">}</span> <span class="si">${</span><span class="nv">MATH_SOURCE</span><span class="si">}</span><span class="p">)</span>
</code></pre></div></div>
</li>
<li>
<p>Update the line to the following:</p>
<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">include</span> <span class="p">(</span>GenerateExportHeader<span class="p">)</span>
<span class="nb">add_library</span><span class="p">(</span>heman SHARED <span class="si">${</span><span class="nv">HEMAN_SOURCE</span><span class="si">}</span> <span class="si">${</span><span class="nv">MATH_SOURCE</span><span class="si">}</span> heman.def<span class="p">)</span>
</code></pre></div></div>
</li>
<li>
<p>Rebuild <strong>heman</strong>, open the Command Palette [<strong>Ctrl+Shift+P</strong>] and run the <strong>CMake: Build</strong> command.</p>
</li>
<li>
<p>Use the <a href="https://www.nirsoft.net/utils/dll_export_viewer.html">DLL Export Viewer tool</a> and open the <strong>heman.dll</strong>. This time you will see all of the exported functions:</p>
<p><img src="https://darenm.github.io/assets/heman-with-exports.png" alt="heman.dll with with exports" /></p>
</li>
<li>
<p>In order to use the output of the heman APIs, we need to convert them to PNGs. The necessary utilities are included in the test project, however we need to update them so they can be exposed in the DLL.</p>
</li>
<li>
<p>Navigate to the <strong>test</strong> folder and copy the existing <strong>hut.h</strong> file (which contains the actual implementation of the utilities) to <strong>hut.c</strong>. Open the new <strong>hut.c</strong> file and replace the following lines:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Heman utilities. This is part of the test suite, not the core library.</span>
<span class="cp"># define STB_IMAGE_IMPLEMENTATION
# define STB_IMAGE_WRITE_IMPLEMENTATION
# define STB_IMAGE_RESIZE_IMPLEMENTATION
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-variable"
# pragma GCC diagnostic ignored "-Wunused-value"
# pragma GCC diagnostic ignored "-Wpointer-sign"
# pragma GCC diagnostic ignored "-Wunknown-pragmas"
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
# include "heman.h"
# include "stb_image.h"
# include "stb_image_resize.h"
# include "stb_image_write.h"
# pragma GCC diagnostic pop
</span></code></pre></div></div>
<p>with</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Heman utilities. This is part of the test suite, not the core library.</span>
<span class="cp"># include "hut.h"
</span></code></pre></div></div>
</li>
<li>
<p>Open the <strong>hut.h</strong> file and replace the contents with the following:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Heman utilities. This is part of the test suite, not the core library.</span>
<span class="cp">#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-value"
#pragma GCC diagnostic ignored "-Wpointer-sign"
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#include "heman.h"
#include "stb_image.h"
#include "stb_image_resize.h"
#include "stb_image_write.h"
#pragma GCC diagnostic pop
</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="nf">hut_read_image</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">filename</span><span class="p">,</span> <span class="kt">int</span> <span class="n">nbands</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">hut_write_image</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">filename</span><span class="p">,</span> <span class="n">heman_image</span><span class="o">*</span> <span class="n">img</span><span class="p">,</span> <span class="kt">float</span> <span class="n">minv</span><span class="p">,</span> <span class="kt">float</span> <span class="n">maxv</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">hut_write_image_scaled</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">filename</span><span class="p">,</span> <span class="n">heman_image</span><span class="o">*</span> <span class="n">img</span><span class="p">,</span> <span class="kt">int</span> <span class="n">dwidth</span><span class="p">,</span> <span class="kt">int</span> <span class="n">dheight</span><span class="p">);</span>
</code></pre></div></div>
<p>We removed the function implementations (now in <strong>hut.c</strong>) and added the <code>include "heman.h"</code> line.</p>
</li>
<li>
<p>Return once again to the <strong>CMakeLists.txt</strong> file and locate the following line:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>add_library(heman SHARED ${HEMAN_SOURCE} ${MATH_SOURCE} heman.def)
</code></pre></div></div>
<p>Update it to include the new <strong>hut.c</strong> file as follows:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>add_library(heman SHARED ${HEMAN_SOURCE} ${MATH_SOURCE} test/hut.c heman.def)
</code></pre></div></div>
</li>
<li>
<p>Open the <strong>heman.def</strong> file and add the following to the bottom of the list</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hut_read_image
hut_write_image
hut_write_image_scaled
</code></pre></div></div>
</li>
<li>
<p>Rebuild <strong>heman</strong>, open the Command Palette [<strong>Ctrl+Shift+P</strong>] and run the <strong>CMake: Build</strong> command.</p>
</li>
<li>
<p>Use the <a href="https://www.nirsoft.net/utils/dll_export_viewer.html">DLL Export Viewer tool</a> and open the <strong>heman.dll</strong>. You should be able to find the hut functions added to the list.</p>
</li>
<li>
<p>We are now ready to consume this DLL in a WinApp.</p>
</li>
</ol>
<h2 id="create-a-winapp-project">Create a WinApp project</h2>
<ol>
<li>
<p>Ensure you have installed and configures Visual Studio 2022 as documented here: <a href="https://docs.microsoft.com/windows/apps/windows-app-sdk/set-up-your-development-environment?tabs=vs-2022">Install tools for developing apps for Windows 10 and Windows 11</a>.</p>
</li>
<li>
<p>In Visual Studio, create a <strong>Blank App, Packaged (WinUI 3 in Desktop)</strong> C# app - name is <strong>HemanWinUI</strong>.</p>
</li>
<li>
<p>Once the solution opens, change the solution plaforms selection to match the compilation target you used for the <strong>heman.dll</strong> - for example, I am using <strong>x64</strong>;</p>
</li>
<li>
<p>To ensure you have everything setup correctly, compile and run the application. The "awesome" default app will launch - click the button for an instant win...</p>
</li>
<li>
<p>Close down the app and briefly review the project. If you are familiar with WPF or UWP apps, the project layout will be somewhat familiar:</p>
<ul>
<li><strong>App.xaml</strong> defines global resources</li>
<li><strong>App.xaml.cs</strong> contains the app startup code. It is much simpler by default than the UWP equivalent, although it can support similar scenarios. WPF developers will feel right at home. Instead of creating a Frame control and assuming navigation is required, etc. a WinApp creates a <strong>MainWindow</strong> and activates it.</li>
<li><strong>MainWindow.xaml</strong> defines the glorious default UI.</li>
<li><strong>MainWindow.xaml.cs</strong> implements the code-behind that supports the click functionality.</li>
</ul>
<p>In this tutorial, I will be updating the UI in MainWindow and adding a class that integrates with the <strong>heman</strong>.</p>
</li>
<li>
<p>To incorporate the heman API into the app, add the <strong>heman.dll</strong> to the <strong>HemanWinUI</strong> project. Once added, select the <strong>heman.dll</strong> file and update the following properties:</p>
<ul>
<li><strong>Build Action</strong> to <strong>Content</strong></li>
<li><strong>Copy to Output Directory</strong> to <strong>Copy if newer</strong>.</li>
</ul>
</li>
<li>
<p>Add a new class to the project and name it <strong>HemanApi.cs</strong>.</p>
</li>
<li>
<p>Update the usings as followings:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Runtime.InteropServices</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">CC</span> <span class="p">=</span> <span class="n">System</span><span class="p">.</span><span class="n">Runtime</span><span class="p">.</span><span class="n">InteropServices</span><span class="p">.</span><span class="n">CallingConvention</span><span class="p">;</span>
</code></pre></div></div>
</li>
<li>
<p>To create a simple example that illustrates the integration to that heman API, update the class definition to the following:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">class</span> <span class="nc">HemanApi</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">int</span> <span class="nf">GetNumberOfThreads</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_get_num_threads</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">NativeMethods</span>
<span class="p">{</span>
<span class="k">const</span> <span class="kt">string</span> <span class="n">DLL</span> <span class="p">=</span> <span class="s">"heman.dll"</span><span class="p">;</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">,</span> <span class="n">CallingConvention</span> <span class="p">=</span> <span class="n">CC</span><span class="p">.</span><span class="n">Cdecl</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="kt">int</span> <span class="nf">heman_get_num_threads</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This code follows the recommended practice of using an nested private static class named <strong>NativeMethods</strong> to encapsulate the <code>DLLImport</code> attributed external function definitions. The <strong>HemanApi</strong> class then exposes a more C# friendly method for consuming the native method. As the implementation grows, these methods will increase in complexity.</p>
<blockquote>
<p><strong>Note</strong>: To learn more about native interoperability, P/invoke, and best practices, refer to the documentation here: <a href="https://docs.microsoft.com/dotnet/standard/native-interop/">Native interoperability</a></p>
</blockquote>
</li>
<li>
<p>To implement a simple UI to interact with this native API, open <strong>MainWindow.xaml</strong> and replace the exiting <code><StackPanel></code> with the following:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><StackPanel</span> <span class="na">Orientation=</span><span class="s">"Vertical"</span>
<span class="na">HorizontalAlignment=</span><span class="s">"Center"</span>
<span class="na">VerticalAlignment=</span><span class="s">"Center"</span><span class="nt">></span>
<span class="nt"><TextBlock</span> <span class="na">x:Name=</span><span class="s">"ThreadInfo"</span>
<span class="na">Text=</span><span class="s">"No data retrieved yet."</span> <span class="nt">/></span>
<span class="nt"><Button</span> <span class="na">Click=</span><span class="s">"ThreadButtonClick"</span><span class="nt">></span>Get Thread Count<span class="nt"></Button></span>
<span class="nt"></StackPanel></span>
</code></pre></div></div>
<p>A very simple UI.</p>
</li>
<li>
<p>Switch to the code-behind - <strong>MainWindow.xaml.cs</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Microsoft.UI.Xaml</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">HemanWinUI</span>
<span class="p">{</span>
<span class="c1">/// <summary></span>
<span class="c1">/// An empty window that can be used on its own or navigated to within a Frame.</span>
<span class="c1">/// </summary></span>
<span class="k">public</span> <span class="k">sealed</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">MainWindow</span> <span class="p">:</span> <span class="n">Window</span>
<span class="p">{</span>
<span class="k">public</span> <span class="nf">MainWindow</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">InitializeComponent</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">ThreadButtonClick</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">RoutedEventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">hemanApi</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HemanApi</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">threadCount</span> <span class="p">=</span> <span class="n">hemanApi</span><span class="p">.</span><span class="nf">GetNumberOfThreads</span><span class="p">();</span>
<span class="n">ThreadInfo</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="s">$"Heman API returned </span><span class="p">{</span><span class="n">threadCount</span><span class="p">}</span><span class="s"> thread</span><span class="p">{(</span><span class="n">threadCount</span> <span class="p">==</span> <span class="m">1</span> <span class="p">?</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">:</span> <span class="s">"s"</span><span class="p">)}</span><span class="s">."</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This straightforward code creates an instance of the <strong>HemanApi</strong> class, retrieves the thread count from the heman native method and then displays the value.</p>
<p><img src="https://darenm.github.io/assets/WinUIDesktop.png" alt="Displaying the thread count from the heman api" /></p>
<blockquote>
<p><strong>Important</strong>: If a <strong>BadImageFormatException</strong> is raised, ensure your WinApp target and the target you build the <strong>heman.dll</strong> match - i.e.:</p>
<ul>
<li>WinApp target - x64</li>
<li>heman.dll target - amd64</li>
</ul>
</blockquote>
</li>
</ol>
<h2 id="wrap-up">Wrap up</h2>
<p>In this tutorial I showed how a C API using the CMake utility can be built on Windows and added to WinApp desktop project. I also showed how a simple native API call can be made from the WinApp.</p>
<p>In the next part of this tutorial I will increase the complexity and deal with generating some height maps and displaying them within the WinApp.</p>
<p>The source code for the WinApp can be found here: <a href="https://github.com/darenm/HemanWinUI/tree/part1">HemanWinUI.</a></p>
<p><a href="https://www.darenmay.com/custommayd/winui/desktop/xaml/native/interop/2022/02/05/native-api-and-winui-part2.html">Part 2</a> of this tutorial can be found <a href="https://www.darenmay.com/custommayd/winui/desktop/xaml/native/interop/2022/02/05/native-api-and-winui-part2.html">here</a>.</p>Daren MayAs the Windows App SDK moves beyond release 1.0 and begins to expand it's capability, I thought it was time to start exploring it more fully. In this post I will explore the integration of a native API into a Windows App SDK. You can learn more about Windows App SDK here: Windows App SDKWindows App SDK Desktop App with native interop - Part 22022-02-05T05:00:00+00:002022-02-05T05:00:00+00:00https://darenm.github.io/custommayd/winui/desktop/xaml/native/interop/2022/02/05/native-api-and-winui-part2<p>As the Windows App SDK moves beyond release 1.0 and begins to expand it's capability, I thought it was time to start exploring it more fully. In this post I will explore the integration of a native API into a Windows App SDK. You can learn more about Windows App SDK here: <a href="https://docs.microsoft.com/en-us/windows/apps/windows-app-sdk/">Windows App SDK</a></p>
<p>In this second part, I will extend the implementation of the native API and add a UI.</p>
<p><a href="https://www.darenmay.com/custommayd/winui/desktop/xaml/native/interop/2022/02/05/native-api-and-winui.html">Part 1</a> can be found <a href="https://www.darenmay.com/custommayd/winui/desktop/xaml/native/interop/2022/02/05/native-api-and-winui.html">here</a>.</p>
<p>The source code for the WinApp can be found here: <a href="https://github.com/darenm/HemanWinUI/tree/part2">HemanWinUI Part 2.</a></p>
<h2 id="objective">Objective</h2>
<p>The <a href="https://github.com/prideout/heman">heman api</a> readme outlines an example that produces 5 images and joins them into a single horizontal "film strip" image similar to:</p>
<p><img src="https://darenm.github.io/assets/filmstrip.png" alt="height map film strip" /></p>
<p>The readme provides and high-level view of the C code to generate the film strip, but the actual code is a little more extensive:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">test_lighting</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">grad</span> <span class="o">=</span> <span class="n">heman_color_create_gradient</span><span class="p">(</span>
<span class="mi">256</span><span class="p">,</span> <span class="n">COUNT</span><span class="p">(</span><span class="n">cp_colors</span><span class="p">),</span> <span class="n">cp_locations</span><span class="p">,</span> <span class="n">cp_colors</span><span class="p">);</span>
<span class="c1">// Generate the heightmap.</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">hmap</span> <span class="o">=</span> <span class="n">heman_generate_island_heightmap</span><span class="p">(</span><span class="n">SIZE</span><span class="p">,</span> <span class="n">SIZE</span><span class="p">,</span> <span class="n">time</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">hmapviz</span> <span class="o">=</span> <span class="n">heman_ops_normalize_f32</span><span class="p">(</span><span class="n">hmap</span><span class="p">,</span> <span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">);</span>
<span class="c1">// Compute ambient occlusion.</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">occ</span> <span class="o">=</span> <span class="n">heman_lighting_compute_occlusion</span><span class="p">(</span><span class="n">hmap</span><span class="p">);</span>
<span class="c1">// Create a normal map.</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">norm</span> <span class="o">=</span> <span class="n">heman_lighting_compute_normals</span><span class="p">(</span><span class="n">hmap</span><span class="p">);</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">normviz</span> <span class="o">=</span> <span class="n">heman_ops_normalize_f32</span><span class="p">(</span><span class="n">norm</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="c1">// Create an albedo image.</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">albedo</span> <span class="o">=</span> <span class="n">heman_color_apply_gradient</span><span class="p">(</span><span class="n">hmap</span><span class="p">,</span> <span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="n">grad</span><span class="p">);</span>
<span class="n">heman_image_destroy</span><span class="p">(</span><span class="n">grad</span><span class="p">);</span>
<span class="c1">// Perform lighting.</span>
<span class="kt">float</span> <span class="n">lightpos</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="n">f</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="n">f</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">};</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">final</span> <span class="o">=</span>
<span class="n">heman_lighting_apply</span><span class="p">(</span><span class="n">hmap</span><span class="p">,</span> <span class="n">albedo</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="n">lightpos</span><span class="p">);</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">frames</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">normviz</span><span class="p">,</span> <span class="n">albedo</span><span class="p">,</span> <span class="n">final</span><span class="p">};</span>
<span class="n">frames</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">heman_color_from_grayscale</span><span class="p">(</span><span class="n">hmapviz</span><span class="p">);</span>
<span class="n">frames</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">heman_color_from_grayscale</span><span class="p">(</span><span class="n">occ</span><span class="p">);</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">filmstrip</span> <span class="o">=</span> <span class="n">heman_ops_stitch_horizontal</span><span class="p">(</span><span class="n">frames</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
<span class="n">hut_write_image</span><span class="p">(</span><span class="n">OUTFOLDER</span> <span class="s">"filmstrip.png"</span><span class="p">,</span> <span class="n">filmstrip</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">heman_export_ply</span><span class="p">(</span><span class="n">hmap</span><span class="p">,</span> <span class="n">OUTFOLDER</span> <span class="s">"heightmap.ply"</span><span class="p">);</span>
<span class="n">heman_export_with_colors_ply</span><span class="p">(</span><span class="n">hmap</span><span class="p">,</span> <span class="n">final</span><span class="p">,</span> <span class="n">OUTFOLDER</span> <span class="s">"colors.ply"</span><span class="p">);</span>
<span class="n">heman_image_destroy</span><span class="p">(</span><span class="n">frames</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="n">heman_image_destroy</span><span class="p">(</span><span class="n">frames</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="n">heman_image_destroy</span><span class="p">(</span><span class="n">hmap</span><span class="p">);</span>
<span class="n">heman_image_destroy</span><span class="p">(</span><span class="n">hmapviz</span><span class="p">);</span>
<span class="n">heman_image_destroy</span><span class="p">(</span><span class="n">occ</span><span class="p">);</span>
<span class="n">heman_image_destroy</span><span class="p">(</span><span class="n">norm</span><span class="p">);</span>
<span class="n">heman_image_destroy</span><span class="p">(</span><span class="n">normviz</span><span class="p">);</span>
<span class="n">heman_image_destroy</span><span class="p">(</span><span class="n">albedo</span><span class="p">);</span>
<span class="n">heman_image_destroy</span><span class="p">(</span><span class="n">final</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Migrating this code over to C# will look remarkably similar.</p>
<h2 id="analyzing-the-create-gradient-function-call">Analyzing the create gradient function call</h2>
<p>The first function call we will look at migrating is the following C code:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">heman_image</span><span class="o">*</span> <span class="n">grad</span> <span class="o">=</span> <span class="n">heman_color_create_gradient</span><span class="p">(</span><span class="mi">256</span><span class="p">,</span> <span class="n">COUNT</span><span class="p">(</span><span class="n">cp_colors</span><span class="p">),</span> <span class="n">cp_locations</span><span class="p">,</span> <span class="n">cp_colors</span><span class="p">);</span>
</code></pre></div></div>
<p>So there is a whole bunch of things going on here. We can see that the function returns a pointer to the <code>heman_image</code> structure, has a dependency on a <code>COUNT</code> macro (uppercase function names are usually macros), <code>cp_locations</code> and <code>cp_colors</code>. The function definition looks like:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Create a 1-pixel tall, 3-band image representing a color gradient that lerps</span>
<span class="c1">// the given control points, in a gamma correct way. Each control point is</span>
<span class="c1">// defined by an X location (one integer each) and an RGB value (one 32-bit</span>
<span class="c1">// word for each color).</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="nf">heman_color_create_gradient</span><span class="p">(</span><span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">num_colors</span><span class="p">,</span>
<span class="k">const</span> <span class="kt">int</span><span class="o">*</span> <span class="n">cp_locations</span><span class="p">,</span> <span class="k">const</span> <span class="n">heman_color</span><span class="o">*</span> <span class="n">cp_colors</span><span class="p">);</span>
</code></pre></div></div>
<p>Let's take a look at the <code>heman_image</code> structure:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// An "image" encapsulates three integers (width, height, number of bands)</span>
<span class="c1">// and an array of (w * h * nbands) floats, in scanline order. For simplicity</span>
<span class="c1">// the API disallows struct definitions, so this is just an opaque handle.</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="n">heman_image_s</span> <span class="n">heman_image</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">heman_image_s</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">width</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">height</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">nbands</span><span class="p">;</span>
<span class="n">HEMAN_FLOAT</span><span class="o">*</span> <span class="n">data</span><span class="p">;</span>
<span class="p">};</span>
<span class="c1">// Image values in heman are always floating point, but clients may</span>
<span class="c1">// choose either 32-bit floats or 64-bit floats at compile time.</span>
<span class="cp">#ifdef USE_DOUBLE_PRECISION
#define HEMAN_FLOAT double
#else
#define HEMAN_FLOAT float
#endif
</span></code></pre></div></div>
<p>Navigating the <code>typedef</code> and macro definition of <code>HEMAN_FLOAT</code> (we are using the single precision <code>float</code> type) we can see the structure is pretty straightforward. The only challenging aspect is that <code>data</code> is just a pointer to an unknown number of floats... however, if I review the remaining code I can see that the members of the <code>heman_image</code> structure are never accessed at this level, so we could just pass around a pointer to the struct without ever marshalling the underlying data to a C# type. If we were to implement the struct in C#, it would look similar to the code below. Notice the use of <a href="https://docs.microsoft.com/en-us/dotnet/api/system.intptr?view=net-6.0">IntPtr</a> that can be used to represent a pointer to any data type (it isn't a "pointer to an int value", it is "an int value that may hold a pointer or handle").</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">StructLayout</span><span class="p">(</span><span class="n">LayoutKind</span><span class="p">.</span><span class="n">Sequential</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">struct</span> <span class="nc">HemanImage</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Width</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Height</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">NBands</span><span class="p">;</span>
<span class="k">public</span> <span class="n">IntPtr</span> <span class="n">data</span><span class="p">;</span> <span class="c1">// Array of floats which we don't need to access</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Moving on to the <code>width</code> and <code>num_colors</code> - these are easy to deal with, they are just <code>int</code> values.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">heman_image</span><span class="o">*</span> <span class="nf">heman_color_create_gradient</span><span class="p">(</span><span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">num_colors</span><span class="p">,</span>
<span class="k">const</span> <span class="kt">int</span><span class="o">*</span> <span class="n">cp_locations</span><span class="p">,</span> <span class="k">const</span> <span class="n">heman_color</span><span class="o">*</span> <span class="n">cp_colors</span><span class="p">);</span>
</code></pre></div></div>
<p>The next parameter, <code>cp_locations</code> (control point locations), expects a pointer to an array of <code>int</code> values, and the final parameter, <code>cp_colors</code> (control point colors), expects a pointer to an array of <code>heman_color</code> values.</p>
<p>The <code>cp_locations</code> definition looks like this in C:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Create a reasonable ocean-to-land color gradient.</span>
<span class="kt">int</span> <span class="n">cp_locations</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
<span class="mo">000</span><span class="p">,</span> <span class="c1">// Dark Blue</span>
<span class="mi">126</span><span class="p">,</span> <span class="c1">// Light Blue</span>
<span class="mi">127</span><span class="p">,</span> <span class="c1">// Yellow</span>
<span class="mi">128</span><span class="p">,</span> <span class="c1">// Dark Green</span>
<span class="mi">160</span><span class="p">,</span> <span class="c1">// Brown</span>
<span class="mi">200</span><span class="p">,</span> <span class="c1">// White</span>
<span class="mi">255</span><span class="p">,</span> <span class="c1">// White</span>
<span class="p">};</span>
</code></pre></div></div>
<p>This maps easily to C#:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span><span class="p">[]</span> <span class="n">cpLocations</span> <span class="p">=</span>
<span class="p">{</span>
<span class="m">000</span><span class="p">,</span> <span class="c1">// Dark Blue</span>
<span class="m">126</span><span class="p">,</span> <span class="c1">// Light Blue</span>
<span class="m">127</span><span class="p">,</span> <span class="c1">// Yellow</span>
<span class="m">128</span><span class="p">,</span> <span class="c1">// Dark Green</span>
<span class="m">160</span><span class="p">,</span> <span class="c1">// Brown</span>
<span class="m">200</span><span class="p">,</span> <span class="c1">// White</span>
<span class="m">255</span><span class="p">,</span> <span class="c1">// White</span>
<span class="p">};</span>
</code></pre></div></div>
<p>The <code>cp_colors</code> definition looks like this in C:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">heman_color</span> <span class="n">cp_colors</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
<span class="mh">0x001070</span><span class="p">,</span> <span class="c1">// Dark Blue</span>
<span class="mh">0x2C5A7C</span><span class="p">,</span> <span class="c1">// Light Blue</span>
<span class="mh">0xE0F0A0</span><span class="p">,</span> <span class="c1">// Yellow</span>
<span class="mh">0x5D943C</span><span class="p">,</span> <span class="c1">// Dark Green</span>
<span class="mh">0x606011</span><span class="p">,</span> <span class="c1">// Brown</span>
<span class="mh">0xFFFFFF</span><span class="p">,</span> <span class="c1">// White</span>
<span class="mh">0xFFFFFF</span><span class="p">,</span> <span class="c1">// White</span>
<span class="p">};</span>
<span class="k">typedef</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">heman_color</span><span class="p">;</span>
</code></pre></div></div>
<p>We can see that <code>heman_color</code> is an unsigned int - so we can map that easily to C#. The C# code will look like:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint</span><span class="p">[]</span> <span class="n">cpColors</span> <span class="p">=</span>
<span class="p">{</span>
<span class="m">0x001070</span><span class="p">,</span> <span class="c1">// Dark Blue</span>
<span class="m">0x2C5A7C</span><span class="p">,</span> <span class="c1">// Light Blue</span>
<span class="m">0xE0F0A0</span><span class="p">,</span> <span class="c1">// Yellow</span>
<span class="m">0x5D943C</span><span class="p">,</span> <span class="c1">// Dark Green</span>
<span class="m">0x606011</span><span class="p">,</span> <span class="c1">// Brown</span>
<span class="m">0xFFFFFF</span><span class="p">,</span> <span class="c1">// White</span>
<span class="m">0xFFFFFF</span><span class="p">,</span> <span class="c1">// White</span>
<span class="p">};</span>
</code></pre></div></div>
<p>However, the parameters are pointers to these two arrays - how do we create those pointers? Well, there is a challenge regarding data in a .NET app - it can move around, or be grabage collected, which can be cataclysmic if it occurs after the original address is passed to native libraries. So, in order to prevent this, we need to leverage <a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.gchandle?view=net-6.0">GCHandle</a> to pin the array in memory and obtain a pointer to it. The standard approach to that in C# is similar to the following:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span><span class="p">[]</span> <span class="n">array</span> <span class="p">=</span> <span class="p">{</span> <span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="m">5</span> <span class="p">};</span>
<span class="n">GCHandle</span> <span class="n">handle</span> <span class="p">=</span> <span class="n">GCHandle</span><span class="p">.</span><span class="nf">Alloc</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">GCHandleType</span><span class="p">.</span><span class="n">Pinned</span><span class="p">);</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="n">IntPtr</span> <span class="n">pointer</span> <span class="p">=</span> <span class="n">handle</span><span class="p">.</span><span class="nf">AddrOfPinnedObject</span><span class="p">();</span>
<span class="c1">// do something with the pointer</span>
<span class="p">}</span>
<span class="k">finally</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">handle</span><span class="p">.</span><span class="n">IsAllocated</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">handle</span><span class="p">.</span><span class="nf">Free</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Notice that we allocate a handle and pin the array, so it isn't moved or collected, then we create an <code>IntPtr</code> that holds the address of the array. <code>finally</code> we ensure the handle is freed so that the memory can be collected.</p>
<p>So, after all of this analysis, we can say the following about the function the C function definition below:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">heman_image</span><span class="o">*</span> <span class="nf">heman_color_create_gradient</span><span class="p">(</span><span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">num_colors</span><span class="p">,</span>
<span class="k">const</span> <span class="kt">int</span><span class="o">*</span> <span class="n">cp_locations</span><span class="p">,</span> <span class="k">const</span> <span class="n">heman_color</span><span class="o">*</span> <span class="n">cp_colors</span><span class="p">);</span>
</code></pre></div></div>
<p>In C# this function will:</p>
<ul>
<li>Return an <code>IntPtr</code></li>
<li>Take an <code>int</code> argument for <code>width</code></li>
<li>Take an <code>int</code> argument for <code>num_colors</code></li>
<li>Take an <code>IntPtr</code> for the address of <code>cp_locations</code></li>
<li>Take an <code>IntPtr</code> for the address of <code>cp_colors</code></li>
</ul>
<p>This is what the function definition will look like in C#:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_color_create_gradient</span><span class="p">(</span><span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">num_colors</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">cp_locations</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">cp_colors</span><span class="p">);</span>
</code></pre></div></div>
<p>To use this with the <code>heman.dll</code> we added to the project, we need to add the <code>DllImport</code> attribute, so overall it will look like this:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="s">"heman.dll"</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_color_create_gradient</span><span class="p">(</span><span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">num_colors</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">cp_locations</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">cp_colors</span><span class="p">);</span>
</code></pre></div></div>
<p>To migrate all of the remaining functions, you would repeat this process. I save specific remarks on particular functions for later in the tutorial.</p>
<p>Refer to <a href="https://docs.microsoft.com/en-us/dotnet/standard/native-interop/type-marshaling">Type marshaling</a> for information on how map native types to .NET types, and vice versa. More information about marshalling, etc. can be found below:</p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/dotnet/standard/native-interop/customize-struct-marshaling">Customize structure marshaling</a></li>
<li><a href="https://docs.microsoft.com/en-us/dotnet/standard/native-interop/customize-parameter-marshaling">Customizing parameter marshaling</a></li>
<li><a href="https://docs.microsoft.com/en-us/dotnet/standard/native-interop/best-practices">Native interoperability best practices</a></li>
<li><a href="https://docs.microsoft.com/en-us/dotnet/standard/native-interop/charset">Charsets and marshaling</a></li>
</ul>
<h2 id="updating-the-nativemethods-class">Updating the NativeMethods class</h2>
<ol>
<li>
<p>In Visual Studio, open the <strong>HemanWinUI</strong> solution.</p>
</li>
<li>
<p>Open the <strong>HemanApi</strong> class and locate the <strong>NativeMethods</strong> class. Replace the definition with the following:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">NativeMethods</span>
<span class="p">{</span>
<span class="k">const</span> <span class="kt">string</span> <span class="n">DLL</span> <span class="p">=</span> <span class="s">"heman.dll"</span><span class="p">;</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="kt">int</span> <span class="nf">heman_get_num_threads</span><span class="p">();</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_color_create_gradient</span><span class="p">(</span><span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">num_colors</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">cp_locations</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">cp_colors</span><span class="p">);</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_generate_island_heightmap</span><span class="p">(</span><span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">height</span><span class="p">,</span> <span class="kt">int</span> <span class="n">seed</span><span class="p">);</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_ops_normalize_f32</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">source</span><span class="p">,</span> <span class="kt">float</span> <span class="n">minval</span><span class="p">,</span> <span class="kt">float</span> <span class="n">maxval</span><span class="p">);</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_lighting_compute_occlusion</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">heightmap</span><span class="p">);</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_lighting_compute_normals</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">heightmap</span><span class="p">);</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_color_apply_gradient</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">heightmap</span><span class="p">,</span> <span class="kt">float</span> <span class="n">minheight</span><span class="p">,</span> <span class="kt">float</span> <span class="n">maxheight</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">gradient</span><span class="p">);</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="k">void</span> <span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">img</span><span class="p">);</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_lighting_apply</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">heightmap</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">colorbuffer</span><span class="p">,</span> <span class="kt">float</span> <span class="n">occlusion</span><span class="p">,</span> <span class="kt">float</span> <span class="n">diffuse</span><span class="p">,</span> <span class="kt">float</span> <span class="n">diffuse_softening</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">light_position</span><span class="p">);</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_color_from_grayscale</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">gray</span><span class="p">);</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="n">IntPtr</span> <span class="nf">heman_ops_stitch_horizontal</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">images</span><span class="p">,</span> <span class="kt">int</span> <span class="n">count</span><span class="p">);</span>
<span class="p">[</span><span class="nf">DllImport</span><span class="p">(</span><span class="n">DLL</span><span class="p">,</span> <span class="n">CharSet</span> <span class="p">=</span> <span class="n">CharSet</span><span class="p">.</span><span class="n">Ansi</span><span class="p">)]</span>
<span class="k">internal</span> <span class="k">static</span> <span class="k">extern</span> <span class="k">void</span> <span class="nf">hut_write_image</span><span class="p">(</span><span class="kt">string</span> <span class="n">filename</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">img</span><span class="p">,</span> <span class="kt">float</span> <span class="n">minv</span><span class="p">,</span> <span class="kt">float</span> <span class="n">maxv</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">[</span><span class="nf">StructLayout</span><span class="p">(</span><span class="n">LayoutKind</span><span class="p">.</span><span class="n">Sequential</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">struct</span> <span class="nc">HemanImage</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Width</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Height</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">NBands</span><span class="p">;</span>
<span class="k">public</span> <span class="n">IntPtr</span> <span class="n">data</span><span class="p">;</span> <span class="c1">// Array of floats which we don't need to access</span>
<span class="p">}</span>
</code></pre></div></div>
</li>
<li>
<p>Take a quick look at the function definitions - they are all very similar to the earlier discussion. You may note that where there are strings, the is a <code>CharSet = CharSet.Ansi</code> property added to <code>DllImport</code> to ensure that the string values are marshalled correctly.</p>
</li>
</ol>
<h2 id="adding-the-example-implementation">Adding the example implementation</h2>
<ol>
<li>
<p>Navigate to the definition of the <strong>HemanApi</strong> class:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">class</span> <span class="nc">HemanApi</span>
<span class="p">{</span>
</code></pre></div></div>
</li>
<li>
<p>Beneath this, add the following:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kt">int</span> <span class="n">Size</span> <span class="p">=</span> <span class="m">512</span><span class="p">;</span>
<span class="k">static</span> <span class="k">readonly</span> <span class="kt">int</span><span class="p">[]</span> <span class="n">cpLocations</span> <span class="p">=</span>
<span class="p">{</span>
<span class="m">000</span><span class="p">,</span> <span class="c1">// Dark Blue</span>
<span class="m">126</span><span class="p">,</span> <span class="c1">// Light Blue</span>
<span class="m">127</span><span class="p">,</span> <span class="c1">// Yellow</span>
<span class="m">128</span><span class="p">,</span> <span class="c1">// Dark Green</span>
<span class="m">160</span><span class="p">,</span> <span class="c1">// Brown</span>
<span class="m">200</span><span class="p">,</span> <span class="c1">// White</span>
<span class="m">255</span><span class="p">,</span> <span class="c1">// White</span>
<span class="p">};</span>
<span class="k">static</span> <span class="k">readonly</span> <span class="kt">uint</span><span class="p">[]</span> <span class="n">cpColors</span> <span class="p">=</span>
<span class="p">{</span>
<span class="m">0x001070</span><span class="p">,</span> <span class="c1">// Dark Blue</span>
<span class="m">0x2C5A7C</span><span class="p">,</span> <span class="c1">// Light Blue</span>
<span class="m">0xE0F0A0</span><span class="p">,</span> <span class="c1">// Yellow</span>
<span class="m">0x5D943C</span><span class="p">,</span> <span class="c1">// Dark Green</span>
<span class="m">0x606011</span><span class="p">,</span> <span class="c1">// Brown</span>
<span class="m">0xFFFFFF</span><span class="p">,</span> <span class="c1">// White</span>
<span class="m">0xFFFFFF</span><span class="p">,</span> <span class="c1">// White</span>
<span class="p">};</span>
<span class="k">static</span> <span class="k">readonly</span> <span class="kt">float</span><span class="p">[]</span> <span class="n">lightPos</span> <span class="p">=</span> <span class="p">{</span> <span class="p">-</span><span class="m">0.5f</span><span class="p">,</span> <span class="m">0.5f</span><span class="p">,</span> <span class="m">1.0f</span> <span class="p">};</span>
</code></pre></div></div>
<p>These are basically the same arrays as discussed earlier (with the addition of <code>lightPos</code>, but essentially the same), just marked as <code>static</code> and <code>readonly</code>.</p>
</li>
<li>
<p>Add the following method that will be used to clean up handles:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">FreeHandle</span><span class="p">(</span><span class="n">GCHandle</span> <span class="n">handle</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">handle</span><span class="p">.</span><span class="n">IsAllocated</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">handle</span><span class="p">.</span><span class="nf">Free</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
</li>
<li>
<p>Now that we have the prerequisites, let's add the method that will implement the example:</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="kt">string</span> <span class="nf">RenderExample</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">IntPtr</span><span class="p">[]</span> <span class="n">frames</span> <span class="p">=</span> <span class="k">new</span> <span class="n">IntPtr</span><span class="p">[</span><span class="m">5</span><span class="p">];</span>
<span class="n">GCHandle</span> <span class="n">cpLocationsHandle</span> <span class="p">=</span> <span class="n">GCHandle</span><span class="p">.</span><span class="nf">Alloc</span><span class="p">(</span><span class="n">cpLocations</span><span class="p">,</span> <span class="n">GCHandleType</span><span class="p">.</span><span class="n">Pinned</span><span class="p">);</span>
<span class="n">GCHandle</span> <span class="n">cpColorsHandle</span> <span class="p">=</span> <span class="n">GCHandle</span><span class="p">.</span><span class="nf">Alloc</span><span class="p">(</span><span class="n">cpColors</span><span class="p">,</span> <span class="n">GCHandleType</span><span class="p">.</span><span class="n">Pinned</span><span class="p">);</span>
<span class="n">GCHandle</span> <span class="n">lightPosHandle</span> <span class="p">=</span> <span class="n">GCHandle</span><span class="p">.</span><span class="nf">Alloc</span><span class="p">(</span><span class="n">lightPos</span><span class="p">,</span> <span class="n">GCHandleType</span><span class="p">.</span><span class="n">Pinned</span><span class="p">);</span>
<span class="n">GCHandle</span> <span class="n">framesHandle</span> <span class="p">=</span> <span class="n">GCHandle</span><span class="p">.</span><span class="nf">Alloc</span><span class="p">(</span><span class="n">frames</span><span class="p">,</span> <span class="n">GCHandleType</span><span class="p">.</span><span class="n">Pinned</span><span class="p">);</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="n">IntPtr</span> <span class="n">cpLocationsPointer</span> <span class="p">=</span> <span class="n">cpLocationsHandle</span><span class="p">.</span><span class="nf">AddrOfPinnedObject</span><span class="p">();</span>
<span class="n">IntPtr</span> <span class="n">cpColorsPointer</span> <span class="p">=</span> <span class="n">cpColorsHandle</span><span class="p">.</span><span class="nf">AddrOfPinnedObject</span><span class="p">();</span>
<span class="n">IntPtr</span> <span class="n">lightPosPointer</span> <span class="p">=</span> <span class="n">lightPosHandle</span><span class="p">.</span><span class="nf">AddrOfPinnedObject</span><span class="p">();</span>
<span class="n">IntPtr</span> <span class="n">framesPointer</span> <span class="p">=</span> <span class="n">framesHandle</span><span class="p">.</span><span class="nf">AddrOfPinnedObject</span><span class="p">();</span>
<span class="c1">// Create a gradient</span>
<span class="c1">// Generate the heightmap.</span>
<span class="c1">// Compute ambient occlusion.</span>
<span class="c1">// Create a normal map.</span>
<span class="c1">// Create an albedo image.</span>
<span class="c1">// Perform lighting.</span>
<span class="c1">// Create the film strip image</span>
<span class="c1">// Copy the final IntPtr data to a HemanImage struct</span>
<span class="c1">// Cleanup</span>
<span class="p">}</span>
<span class="k">finally</span>
<span class="p">{</span>
<span class="nf">FreeHandle</span><span class="p">(</span><span class="n">cpLocationsHandle</span><span class="p">);</span>
<span class="nf">FreeHandle</span><span class="p">(</span><span class="n">cpColorsHandle</span><span class="p">);</span>
<span class="nf">FreeHandle</span><span class="p">(</span><span class="n">lightPosHandle</span><span class="p">);</span>
<span class="nf">FreeHandle</span><span class="p">(</span><span class="n">framesHandle</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The main structure of this method creates the <code>IntPtr</code> instances for the arrays and ensures the associated handles are freed when the method is complete. Now let's look at each comment in turn - the C code and the C# version.</p>
</li>
</ol>
<h3 id="create-a-gradient">Create a gradient</h3>
<ol>
<li>
<p>The C code for this function is:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">heman_image</span><span class="o">*</span> <span class="n">grad</span> <span class="o">=</span> <span class="n">heman_color_create_gradient</span><span class="p">(</span>
<span class="mi">256</span><span class="p">,</span> <span class="n">COUNT</span><span class="p">(</span><span class="n">cp_colors</span><span class="p">),</span> <span class="n">cp_locations</span><span class="p">,</span> <span class="n">cp_colors</span><span class="p">);</span>
</code></pre></div></div>
</li>
<li>
<p>Locate the <code>// Create a gradient comment</code> and update it to:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Create a gradient</span>
<span class="kt">var</span> <span class="n">grad</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_color_create_gradient</span><span class="p">(</span><span class="m">256</span><span class="p">,</span> <span class="n">cpColors</span><span class="p">.</span><span class="n">Length</span><span class="p">,</span> <span class="n">cpLocationsPointer</span><span class="p">,</span> <span class="n">cpColorsPointer</span><span class="p">);</span>
</code></pre></div></div>
<p>Notice the use of the <code>Length</code> property of an array amd the <code>IntPtr</code> instances created earlier. <code>grad</code> now holds an <code>IntPtr</code> instance that points to the location of the <strong>grad</strong> heman image.</p>
</li>
</ol>
<h3 id="generate-the-heightmap">Generate the heightmap</h3>
<ol>
<li>
<p>The C code for these functions is:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Generate the heightmap.</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">hmap</span> <span class="o">=</span> <span class="n">heman_generate_island_heightmap</span><span class="p">(</span><span class="n">SIZE</span><span class="p">,</span> <span class="n">SIZE</span><span class="p">,</span> <span class="n">time</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
<span class="n">heman_image</span><span class="o">*</span> <span class="n">hmapviz</span> <span class="o">=</span> <span class="n">heman_ops_normalize_f32</span><span class="p">(</span><span class="n">hmap</span><span class="p">,</span> <span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">);</span>
</code></pre></div></div>
</li>
<li>
<p>Locate the <code>// Generate the heightmap.</code> and update it to:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Generate the heightmap.</span>
<span class="kt">var</span> <span class="n">time</span> <span class="p">=</span> <span class="p">(</span><span class="n">DateTime</span><span class="p">.</span><span class="n">Now</span> <span class="p">-</span> <span class="k">new</span> <span class="nf">DateTime</span><span class="p">(</span><span class="m">1970</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">)).</span><span class="n">Seconds</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">hmap</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_generate_island_heightmap</span><span class="p">(</span><span class="n">Size</span><span class="p">,</span> <span class="n">Size</span><span class="p">,</span> <span class="n">time</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">hmapViz</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_ops_normalize_f32</span><span class="p">(</span><span class="n">hmap</span><span class="p">,</span> <span class="p">-</span><span class="m">0.5f</span><span class="p">,</span> <span class="m">0.5f</span><span class="p">);</span>
</code></pre></div></div>
<p>Notice the introduction of the <code>time</code> variable - the C <a href="https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/time-time32-time64?view=msvc-170"><code>time(0)</code></a> function returns the number of seconds since midnight 1/1/1970, so we need to do something similar.</p>
<p>The <code>Size</code> constant is used to specify the width and height of the island height map.</p>
<p>For the normalize function, we use the <code>f</code> qualifier to ensure we are passing <code>float</code> values and pass the <code>hmap</code> value.</p>
<p>As you can see, the code is very straightforward and follows the earlier pattern.</p>
</li>
</ol>
<h3 id="implementing-the-next-few-parts">Implementing the next few parts</h3>
<ol>
<li>
<p>Locate the <code>// Compute ambient occlusion.</code> comment and update it as follows:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Compute ambient occlusion.</span>
<span class="kt">var</span> <span class="n">occ</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_lighting_compute_occlusion</span><span class="p">(</span><span class="n">hmap</span><span class="p">);</span>
<span class="c1">// Create a normal map.</span>
<span class="kt">var</span> <span class="n">norm</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_lighting_compute_normals</span><span class="p">(</span><span class="n">hmap</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">normviz</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_ops_normalize_f32</span><span class="p">(</span><span class="n">norm</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">,</span> <span class="m">1</span><span class="p">);</span>
<span class="c1">// Create an albedo image.</span>
<span class="kt">var</span> <span class="n">albedo</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_color_apply_gradient</span><span class="p">(</span><span class="n">hmap</span><span class="p">,</span> <span class="p">-</span><span class="m">0.5f</span><span class="p">,</span> <span class="m">0.5f</span><span class="p">,</span> <span class="n">grad</span><span class="p">);</span>
<span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">grad</span><span class="p">);</span>
<span class="c1">// Perform lighting.</span>
<span class="kt">var</span> <span class="n">final</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_lighting_apply</span><span class="p">(</span><span class="n">hmap</span><span class="p">,</span> <span class="n">albedo</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="m">0.5f</span><span class="p">,</span> <span class="n">lightPosPointer</span><span class="p">);</span>
</code></pre></div></div>
<p>This follows the same pattern, with the introduction of the <code>NativeMethods.heman_image_destroy)grad);</code> which frees up memory used by the <code>grad</code> image.</p>
</li>
<li>
<p>Locate the <code>// Create film strip image</code> comment and update it as follows:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Create film strip image</span>
<span class="n">frames</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_color_from_grayscale</span><span class="p">(</span><span class="n">hmapViz</span><span class="p">);</span>
<span class="n">frames</span><span class="p">[</span><span class="m">1</span><span class="p">]</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_color_from_grayscale</span><span class="p">(</span><span class="n">occ</span><span class="p">);</span>
<span class="n">frames</span><span class="p">[</span><span class="m">2</span><span class="p">]</span> <span class="p">=</span> <span class="n">normviz</span><span class="p">;</span>
<span class="n">frames</span><span class="p">[</span><span class="m">3</span><span class="p">]</span> <span class="p">=</span> <span class="n">albedo</span><span class="p">;</span>
<span class="n">frames</span><span class="p">[</span><span class="m">4</span><span class="p">]</span> <span class="p">=</span> <span class="n">final</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">filmstrip</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_ops_stitch_horizontal</span><span class="p">(</span><span class="n">framesPointer</span><span class="p">,</span> <span class="n">frames</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">path</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="nf">GetTempPath</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">randomFilename</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="nf">GetRandomFileName</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">fileName</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">randomFilename</span><span class="p">.</span><span class="nf">Split</span><span class="p">(</span><span class="sc">'.'</span><span class="p">)[</span><span class="m">0</span><span class="p">]</span> <span class="p">+</span> <span class="s">".png"</span><span class="p">);</span>
<span class="n">NativeMethods</span><span class="p">.</span><span class="nf">hut_write_image</span><span class="p">(</span><span class="n">fileName</span><span class="p">,</span> <span class="n">filmstrip</span><span class="p">,</span> <span class="m">0f</span><span class="p">,</span> <span class="m">1f</span><span class="p">);</span>
</code></pre></div></div>
<p>The film strip image is constructed from 5 images. The <code>frames</code> array contains <code>IntPtr</code> instances that locate each individual frame in memory. The <code>NativeMethods.heman_color_from_grayscale</code> function makes a single channel greyscale image a 3 scale color image so that it can be added to the filmstrip.</p>
<p>As the example saves the resultant film strip image out to a file, we generate a temporary filename and pass that to <code>NativeMethods.hut_write_image</code>.</p>
</li>
</ol>
<h3 id="marshalling-an-intptr-to-struct">Marshalling an IntPtr to struct</h3>
<p>Earlier I stated that as we aren't actually utilizing any of the <code>heman_image</code> structures returned by the functions, we could just use <code>IntPtr</code> instances to hold the return addresses and pass them into other functions. However, if you do want to get at the underlying structure, mechanisms exist to do so. Let's add one version:</p>
<ol>
<li>
<p>Locate the <code>// Copy the final IntPtr data to a HemanImage struct</code> comment and update it as follows:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Copy the final IntPtr data to a HemanImage struct</span>
<span class="kt">var</span> <span class="n">finalImage</span> <span class="p">=</span> <span class="n">Marshal</span><span class="p">.</span><span class="n">PtrToStructure</span><span class="p"><</span><span class="n">HemanImage</span><span class="p">>(</span><span class="n">final</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">width</span> <span class="p">=</span> <span class="n">finalImage</span><span class="p">.</span><span class="n">Width</span><span class="p">;</span>
</code></pre></div></div>
<p>The <code>Marshal.PtrToStructure<HemanImage>(final)</code> creates a new instance of the <code>HemanImage</code> struct and then copies the data addressed by the <code>final</code> pointer to the struct. The size of the <code>HemanImage</code> structure is used to determine how much data is copied from that address.</p>
<p>You can debug and confirm the values are copied over.</p>
</li>
</ol>
<h3 id="final-cleanup">Final Cleanup</h3>
<ol>
<li>
<p>Locate the <code>// Cleanup</code> comment and update it as follows:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">frames</span><span class="p">[</span><span class="m">0</span><span class="p">]);</span>
<span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">frames</span><span class="p">[</span><span class="m">1</span><span class="p">]);</span>
<span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">hmap</span><span class="p">);</span>
<span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">hmapViz</span><span class="p">);</span>
<span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">occ</span><span class="p">);</span>
<span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">norm</span><span class="p">);</span>
<span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">normviz</span><span class="p">);</span>
<span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">albedo</span><span class="p">);</span>
<span class="n">NativeMethods</span><span class="p">.</span><span class="nf">heman_image_destroy</span><span class="p">(</span><span class="n">final</span><span class="p">);</span>
<span class="k">return</span> <span class="n">fileName</span><span class="p">;</span>
</code></pre></div></div>
<p>Self-evidently, this code cleans up the remaining native memory addressed by the the <code>IntPtr</code> instances returned by the native methods.</p>
<p>Finally, the filename is returned so the image can be used elsewhere.</p>
</li>
</ol>
<p>At this point, you have an example that would save a PNG to your <code>%TEMP%</code> folder. We now need to add a UI to call this method and display the image.</p>
<h2 id="adding-a-ui">Adding a UI</h2>
<ol>
<li>
<p>Open the <code>MainWindow.xaml</code> file and delete the existing <code><StackPanel></code> and its children.</p>
</li>
<li>
<p>Add the following <code><Grid></code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Grid</span> <span class="na">x:Name=</span><span class="s">"RootGrid"</span><span class="nt">></span>
<span class="nt"><Grid.Resources></span>
<span class="nt"><ThemeShadow</span> <span class="na">x:Name=</span><span class="s">"SharedShadow"</span> <span class="nt">/></span>
<span class="nt"><Storyboard</span> <span class="na">x:Name=</span><span class="s">"HideStoryboard"</span><span class="nt">></span>
<span class="nt"><FadeOutThemeAnimation</span> <span class="na">Storyboard.TargetName=</span><span class="s">"OutputImage"</span> <span class="nt">/></span>
<span class="nt"></Storyboard></span>
<span class="nt"><Storyboard</span> <span class="na">x:Name=</span><span class="s">"ShowStoryboard"</span><span class="nt">></span>
<span class="nt"><FadeInThemeAnimation</span> <span class="na">Storyboard.TargetName=</span><span class="s">"OutputImage"</span> <span class="nt">/></span>
<span class="nt"></Storyboard></span>
<span class="nt"></Grid.Resources></span>
<span class="nt"><Grid.RowDefinitions></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"Auto"</span> <span class="nt">/></span>
<span class="nt"><RowDefinition</span> <span class="nt">/></span>
<span class="nt"></Grid.RowDefinitions></span>
<span class="c"><!--Grid for shadow projection --></span>
<span class="c"><!-- Button to launch image render --></span>
<span class="c"><!-- Image to display rendered image --></span>
<span class="nt"></Image></span>
<span class="nt"></Grid></span>
</code></pre></div></div>
<p>This grid has a number of resource defined:</p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.xaml.media.themeshadow?view=winui-3.0">ThemeShadow</a> - A ThemeShadow is a preconfigured shadow effect that can be applied to any XAML element to draw shadows appropriately based on x, y, z coordinates. It is a good practice to define a single shadow resource and re-used on many UI elements.</li>
<li>Two storyboards, <strong>HideStoryboard</strong> and <strong>ShowStoryboard</strong> that utilize the <a href="https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.xaml.media.animation.fadeinthemeanimation?view=winui-3.0">FadeInThemeAnimation</a> and <a href="https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.xaml.media.animation.fadeoutthemeanimation?view=winui-3.0">FadeOutThemeAnimation</a> animations so that the rendered images appear in a more pleasing manner.</li>
</ul>
<p>The grid is defined with two rows - the first will hold a button to execute the image render, the second will hold the image control.</p>
</li>
<li>
<p>Locate the <code><!--Grid for shadow projection --></code> comment and update it as follows:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Grid</span> <span class="na">x:Name=</span><span class="s">"BackgroundGrid"</span>
<span class="na">Background=</span><span class="s">"{StaticResource ApplicationPageBackgroundThemeBrush}"</span>
<span class="na">Grid.RowSpan=</span><span class="s">"2"</span><span class="nt">/></span>
</code></pre></div></div>
<p>In order to utilize the <code>ThemeShadow</code> there must be an element on which the shadows can be rendered. This element <strong>cannot be</strong> the parent of any of the controls that are projecting shadows. Note that the <code>rowspan</code> is set to both rows.</p>
</li>
<li>
<p>Locate the <code><!-- Button to launch image render --></code> comment and update it as follows:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- Button to launch image render --></span>
<span class="nt"><Button</span> <span class="na">Margin=</span><span class="s">"4"</span>
<span class="na">Click=</span><span class="s">"ThreadButtonClick"</span><span class="nt">></span>Render the Example<span class="nt"></Button></span>
</code></pre></div></div>
<p>Nothing fancy here - no use of <code>x:Bind</code>, etc. just a simple click event that will be handled in the code-behind. Of course, you could utilize MVVM and click-binding, or a command, if you so chose.</p>
</li>
<li>
<p>Locate the <code><!-- Image to display rendered image --></code> comment and update the code as follows:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- Image to display rendered image --></span>
<span class="nt"><Image</span> <span class="na">Grid.Row=</span><span class="s">"1"</span>
<span class="na">Margin=</span><span class="s">"24"</span>
<span class="na">x:Name=</span><span class="s">"OutputImage"</span>
<span class="na">Visibility=</span><span class="s">"Collapsed"</span>
<span class="na">Shadow=</span><span class="s">"{StaticResource SharedShadow}"</span>
<span class="na">Translation=</span><span class="s">"0,0,200"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>Notice the use of the <strong>SharedShadow</strong> resource to set a shadow for the image, as well as the translation - the Z value of 200 determines how "high" the image control is above the underlying element (<strong>BackgroundGrid</strong>) and so influences the size of the cast shadow. Also note the control has <code>Visibility="Collapsed"</code> - this is due to the fact that an empty control of zero size still results in a shadow being cast, which looks wrong:</p>
<p><img src="https://darenm.github.io/assets/WinUIEmptyShadow.png" alt="Empty image casting a shadow" /></p>
</li>
</ol>
<h2 id="implementing-the-code-behind">Implementing the code-behind</h2>
<ol>
<li>
<p>Open the <strong>MainWindow.xaml.cs</strong> file. Delete the click handler from part 1 so that the code looks similar to:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Microsoft.UI.Xaml</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.UI.Xaml.Media.Imaging</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.IO</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">Windows.Storage</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">HemanWinUI</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">sealed</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">MainWindow</span> <span class="p">:</span> <span class="n">Window</span>
<span class="p">{</span>
<span class="k">public</span> <span class="nf">MainWindow</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">InitializeComponent</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
</li>
<li>
<p>Add a method that handles the button click event:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">ThreadButtonClick</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">RoutedEventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">HideStoryboard</span><span class="p">.</span><span class="nf">Begin</span><span class="p">();</span>
<span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Delay</span><span class="p">(</span><span class="m">1</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">fileName</span> <span class="p">=</span> <span class="n">HemanApi</span><span class="p">.</span><span class="nf">RenderExample</span><span class="p">();</span>
<span class="k">await</span> <span class="nf">LoadImage</span><span class="p">(</span><span class="n">fileName</span><span class="p">);</span>
<span class="n">ShowStoryboard</span><span class="p">.</span><span class="nf">Begin</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Notice the method is marked async - the image load code (added next) runs asynchronously and we want to launch the animations immediately, which is what the <code>await Task.Delay(1);</code> code achieves (try running it without).</p>
<p>First thing the method does is launch the hide animation - redundant on the first run, but fades out the last image is displayed.</p>
<p>Next, the example we created earlier is run and the filename returned. We await the load of the image, and then launch the storyboard that fades in the new image.</p>
</li>
<li>
<p>Add a method that loads the image from a filename:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">LoadImage</span><span class="p">(</span><span class="kt">string</span> <span class="n">fileName</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">file</span> <span class="p">=</span> <span class="k">await</span> <span class="n">StorageFile</span><span class="p">.</span><span class="nf">GetFileFromPathAsync</span><span class="p">(</span><span class="n">fileName</span><span class="p">);</span>
<span class="k">using</span> <span class="nn">var</span> <span class="n">fileStream</span> <span class="p">=</span> <span class="k">await</span> <span class="n">WindowsRuntimeStorageExtensions</span><span class="p">.</span><span class="nf">OpenStreamForReadAsync</span><span class="p">(</span><span class="n">file</span><span class="p">);</span>
<span class="n">BitmapImage</span> <span class="n">bitmapImage</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>
<span class="k">await</span> <span class="n">bitmapImage</span><span class="p">.</span><span class="nf">SetSourceAsync</span><span class="p">(</span><span class="n">fileStream</span><span class="p">.</span><span class="nf">AsRandomAccessStream</span><span class="p">());</span>
<span class="n">OutputImage</span><span class="p">.</span><span class="n">Source</span> <span class="p">=</span> <span class="n">bitmapImage</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">OutputImage</span><span class="p">.</span><span class="n">Visibility</span> <span class="p">!=</span> <span class="n">Visibility</span><span class="p">.</span><span class="n">Visible</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">OutputImage</span><span class="p">.</span><span class="n">Visibility</span> <span class="p">=</span> <span class="n">Visibility</span><span class="p">.</span><span class="n">Visible</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This code uses the <code>StorageFile</code> class to open a stream to read the file, create a new <code>BitmapImage</code> to use as the source for the <code>Image</code> element. It also makes the <code>Image</code> control visible if it is still collapsed.</p>
</li>
</ol>
<p>And that's it - the app should now launch and render the height maps from the heman api. Click the button again to create a new fantasy island:</p>
<p><img src="https://darenm.github.io/assets/WinUIDesktopWithImage.png" alt="WinApp with image" /></p>
<h2 id="wrap-up">Wrap up</h2>
<p>In this tutorial I complete the WinApp started early and showed my approach to implementing a native dll in a WinApp. I also showed how shadows and animations can be easily added to the app.</p>
<p>My next post will explore interacting with Win32 APIs.</p>
<p>The source code for the WinApp can be found here: <a href="https://github.com/darenm/HemanWinUI/tree/part2">HemanWinUI Part 2.</a></p>Daren MayAs the Windows App SDK moves beyond release 1.0 and begins to expand it's capability, I thought it was time to start exploring it more fully. In this post I will explore the integration of a native API into a Windows App SDK. You can learn more about Windows App SDK here: Windows App SDKIoT Central model export/import issue2020-12-01T05:00:00+00:002020-12-01T05:00:00+00:00https://darenm.github.io/custommayd/azure/iot/iotcentral/2020/12/01/iotcentral-model-export-issue<p>I ran into an issue on IoT Central today where I exported a model, deleted it, tried to re-import and it failed. I discovered the issue after some manual manipulation of the exported JSON and it was related to the generated @id property for the request object of a command capability.</p>
<p>This is the displayed error:</p>
<p><img src="https://darenm.github.io/assets/iotcentral-error.png" alt="Encountered error while parsing model document." /></p>
<p>This is a fragment of what was exported and where I found the issue:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"@id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dtmi:refrigeratedTrucksDm12012020:RefrigeratedTruck7fe:GoToCustomer;1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Command"</span><span class="p">,</span><span class="w">
</span><span class="nl">"commandType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"synchronous"</span><span class="p">,</span><span class="w">
</span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"en"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Go to customer"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GoToCustomer"</span><span class="p">,</span><span class="w">
</span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"@id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dtmi:refrigeratedTrucksDm12012020:RefrigeratedTruck7fe:GoToCustomer:__request:CustomerID;1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CommandPayload"</span><span class="p">,</span><span class="w">
</span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"en"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Customer ID"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CustomerID"</span><span class="p">,</span><span class="w">
</span><span class="nl">"schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"integer"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>This is the edited JSON that worked (I removed the <code>__</code> characters from the <code>__request</code> part of the @id):</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"@id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dtmi:refrigeratedTrucksDm12012020:RefrigeratedTruck7fe:GoToCustomer;1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Command"</span><span class="p">,</span><span class="w">
</span><span class="nl">"commandType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"synchronous"</span><span class="p">,</span><span class="w">
</span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"en"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Go to customer"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GoToCustomer"</span><span class="p">,</span><span class="w">
</span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"@id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dtmi:refrigeratedTrucksDm12012020:RefrigeratedTruck7fe:GoToCustomer:request:CustomerID;1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CommandPayload"</span><span class="p">,</span><span class="w">
</span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"en"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Customer ID"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CustomerID"</span><span class="p">,</span><span class="w">
</span><span class="nl">"schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"integer"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>I hope this helps someone.</p>Daren MayI ran into an issue on IoT Central today where I exported a model, deleted it, tried to re-import and it failed. I discovered the issue after some manual manipulation of the exported JSON and it was related to the generated @id property for the request object of a command capability.Howto Consume Webservices With Uno2020-10-29T00:00:00+00:002020-10-29T00:00:00+00:00https://darenm.github.io/2020/10/29/howto-consume-webservices-with-Uno<p>This how-to will walk you through the process of creating a multi-platform application that leverages web services. Specifically you will be:</p>
<ul>
<li>Creating a simple Uno application.</li>
<li>Registering for The Cat API web service.</li>
<li>Building a number of data models that utilize JSON serialization.</li>
<li>Building a web service base class that supports REST service operations and then build a number of services that derived from it.</li>
<li>Using the services in a view-model.</li>
<li>Build a XAML UI that utilizes the view-model.</li>
<li>Test the application in UWP and WASM.</li>
</ul>
<p>Throughout the how-to there will be notes on recommended practices and tips that highlight resources for additional learning.</p>
<blockquote>
<p><strong>TIP</strong>:
The complete source code that goes along with this tutorial is available in the <a href="https://github.com/unoplatform/Uno.Samples">unoplatform/Uno.Samples</a> GitHub repository - <a href="https://github.com/unoplatform/Uno.Samples/tree/master/UI/TheCatApiClient">TheCatApiClient</a></p>
</blockquote>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="http://www.visualstudio.com/downloads/">Visual Studio 2019 16.3 or later</a>
<ul>
<li><strong>Universal Windows Platform</strong> workload installed</li>
<li><strong>Mobile Development with .NET (Xamarin)</strong> workload installed</li>
<li><strong>ASP</strong>.<strong>NET and web</strong> workload installed</li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=nventivecorp.uno-platform-addin">Uno Platform Extension</a> installed</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>TIP</strong>:
For a step-by-step guide to installing the prerequisites, see <a href="https://platform.uno/docs/articles/get-started-vs.html">Getting started on Visual Studio</a></p>
</blockquote>
<h2 id="task-1---create-a-simple-uno-application">Task 1 - Create a simple Uno application</h2>
<p>In this task you will create a simple Single Page App with the Uno Platform. This app - <strong>TheCatApiClient</strong> - will be used to demonstrate an approach to consuming REST web services using the <strong>HttpClient</strong> class.</p>
<ol>
<li>
<p>Open Visual Studio and click on <strong>Create a new project</strong>.</p>
<p><img src="https://darenm.github.io/assets/newproject1.PNG" alt="Visual Studio new project dialog" /></p>
</li>
<li>
<p>In the <strong>Search for Templates</strong> search box, enter <strong>Uno</strong></p>
<p><img src="https://darenm.github.io/assets/newproject2.PNG" alt="Visual Studio new project dialog searching for Uno" /></p>
</li>
<li>
<p>In the filtered list of templates, select <strong>Cross-Platform App (Uno Platform)</strong> and then click <strong>Next</strong>.</p>
</li>
<li>
<p>In the <strong>Configure your new project</strong> window, set the <strong>Project name</strong> to <strong>TheCatApiClient</strong>, choose where you would like to save your project and click the <strong>Create</strong> button.</p>
<p><img src="https://darenm.github.io/assets/newproject3.PNG" alt="Configure your new project dialog" /></p>
<blockquote>
<p><strong>IMPORTANT</strong>:
The C# and XAML snippets in this tutorial requires that the solution is named <strong>TheCatApiClient</strong>. Using a different name will result in build errors when you copy code from this tutorial into the solution.</p>
</blockquote>
</li>
<li>
<p>In the <strong>Solution Explorer</strong>, to update the Uno NuGet packages to the latest version, right-click on the Solution file <strong>Solution 'TheCatApiClient'</strong> and select <strong>Manage NuGet Packages for Solution...</strong> from the context menu.</p>
<p>The <strong>Manage Packages for Solution</strong> page will open.</p>
</li>
<li>
<p>At the top-right of the <strong>Manage Packages for Solution</strong> page, in the <strong>Package source</strong> dropdown, ensure that <strong>nuget.org</strong> or <strong>NuGet official package source</strong> is selected.</p>
</li>
<li>
<p>At the top-left of page, to view solution packages available to update, click <strong>Updates</strong>.</p>
</li>
<li>
<p>On the <strong>Updates</strong> tab, to use the stable version of the packages, ensure <strong>Include prerelease</strong> is unchecked.</p>
</li>
<li>
<p>From the list of available updates, select the following packages if they appear:</p>
<ul>
<li><strong>Uno.Core</strong></li>
<li><strong>Uno.UI</strong></li>
<li><strong>Uno.UI.RemoteControl</strong></li>
<li><strong>Uno.UI.WebAssembly</strong></li>
<li><strong>Uno.Wasm.Bootstrap</strong></li>
<li><strong>Uno.Wasm.Bootstrap.DevServer</strong></li>
</ul>
<blockquote>
<p><strong>IMPORTANT</strong>:
<strong>Do not</strong> update the <strong>Microsoft.Extensions.Logging.Console</strong>. Recent versions of the package use APIs that aren't supported by WebAssembly, and aren't compatible with Uno Platform.</p>
</blockquote>
</li>
<li>
<p>To update the packages, click <strong>Update</strong>.</p>
</li>
<li>
<p>At the top-left of page, to browse the available packages, click <strong>Browse</strong>.</p>
</li>
<li>
<p>In the <strong>Search</strong> field, enter <strong>System.Text.Json</strong> and select <strong>System.Text.Json</strong>.</p>
<blockquote>
<p><strong>TIP</strong>:
The <strong>System.Text.Json</strong> package provides functionality for serializing to and deserializing from JavaScript Object Notation (JSON). You can learn more about how to serialize and deserialize here: <a href="https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to">How to serialize and deserialize (marshal and unmarshal) JSON in .NET</a></p>
</blockquote>
</li>
<li>
<p>In the right pane of the <strong>Manage Packages for Solution</strong> page, select every project and click <strong>Install</strong>.</p>
<p><img src="https://darenm.github.io/assets/json-install.png" alt="Install System.Text.Json nuget package" /></p>
</li>
<li>
<p>To workaround a Visual studio issue regarding the XAML editor, you'll need to close any opened file in the editor, then close the solution or Visual Studio, then re-open it.</p>
</li>
<li>
<p>To test the UWP version of the application, in the <strong>Solution Explorer</strong>, right-click the <strong>TheCatApiClient.UWP</strong> project and click <strong>Set as Startup Project</strong></p>
</li>
<li>
<p>To launch the application, under the <strong>Debug</strong> menu, click <strong>Start Debugging</strong> or use the shortcut key, commonly <strong>F5</strong>.</p>
<p><img src="https://darenm.github.io/assets/TheCatApiClient-UWP.png" alt="The Cat API Client app running cross-platform" /></p>
<p>The following image shows the sample app running on the Android Simulator, IPhone Simulator and as a WASM app in the browser.</p>
<p><img src="https://darenm.github.io/assets/crosplat-start.png" alt="The Cat API Client app running cross-platform" /></p>
</li>
</ol>
<p>You have now created a sample application that will be extended in later tasks.</p>
<h2 id="task-2---sign-up-for-api-key">Task 2 - Sign up for API key</h2>
<p>In order to demonstrate how to consume a webservice, there needs to be a webservice available. In this tutorial you will build a simple client that leverages <a href="https://thecatapi.com/">The Cat API</a>. The website for this service describes it as:</p>
<blockquote>
<p>A public service API all about Cats, free to use when making your fancy new App, Website or Service.</p>
</blockquote>
<p>In order to use this service, you need to sign up for a free API key.</p>
<ol>
<li>
<p>In a browser, navigate to The Cat API signup page: <a href="https://thecatapi.com/signup">https://thecatapi.com/signup</a></p>
</li>
<li>
<p>Complete the signup form and click <strong>SIGNUP</strong>.</p>
<p>You will receive an email with your API key - you will need the key later.</p>
</li>
<li>
<p>Review the API authentication information: <a href="https://docs.thecatapi.com/authentication">https://docs.thecatapi.com/authentication</a>.</p>
<p>Notice that there are two options for passing the API key - the preferred method using a Request Header, the approach we will use in the code, and passing it as a Query Parameter. The Query Parameter approach makes it very easy to query the API via a browser.</p>
</li>
<li>
<p>To test the API and your API key, open the following URL in a browser (replace the <code>{YOUR-API-KEY}</code> value with your key):</p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">https://api.thecatapi.com/v1/breeds/search?q=sib&api_key={YOUR-API-KEY}
</span></code></pre></div></div>
<p>e.g.</p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">https://api.thecatapi.com/v1/breeds/search?q=sib&api_key=B1D218CC-0D2D-4B3E-89EA-EEC373EB3CA5
</span></code></pre></div></div>
<p>This simple breed search query will return some JSON data that describes the Siberian cat breed:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"weight"</span><span class="p">:</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"imperial"</span><span class="p">:</span><span class="w"> </span><span class="s2">"8 - 16"</span><span class="p">,</span><span class="w">
</span><span class="nl">"metric"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4 - 7"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sibe"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Siberian"</span><span class="p">,</span><span class="w">
</span><span class="nl">"cfa_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://cfa.org/Breeds/BreedsSthruT/Siberian.aspx"</span><span class="p">,</span><span class="w">
</span><span class="nl">"vetstreet_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://www.vetstreet.com/cats/siberian"</span><span class="p">,</span><span class="w">
</span><span class="nl">"vcahospitals_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://vcahospitals.com/know-your-pet/cat-breeds/siberian"</span><span class="p">,</span><span class="w">
</span><span class="nl">"temperament"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Curious, Intelligent, Loyal, Sweet, Agile, Playful, Affectionate"</span><span class="p">,</span><span class="w">
</span><span class="nl">"origin"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Russia"</span><span class="p">,</span><span class="w">
</span><span class="nl">"country_codes"</span><span class="p">:</span><span class="w"> </span><span class="s2">"RU"</span><span class="p">,</span><span class="w">
</span><span class="nl">"country_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"RU"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The Siberians dog like temperament and affection makes the ideal lap cat and will live quite happily indoors. Very agile and powerful, the Siberian cat can easily leap and reach high places, including the tops of refrigerators and even doors. "</span><span class="p">,</span><span class="w">
</span><span class="nl">"life_span"</span><span class="p">:</span><span class="w"> </span><span class="s2">"12 - 15"</span><span class="p">,</span><span class="w">
</span><span class="nl">"indoor"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"lap"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"alt_names"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Moscow Semi-longhair, HairSiberian Forest Cat"</span><span class="p">,</span><span class="w">
</span><span class="nl">"adaptability"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"affection_level"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"child_friendly"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
</span><span class="nl">"dog_friendly"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"energy_level"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"grooming"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
</span><span class="nl">"health_issues"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
</span><span class="nl">"intelligence"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"shedding_level"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
</span><span class="nl">"social_needs"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
</span><span class="nl">"stranger_friendly"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
</span><span class="nl">"vocalisation"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"experimental"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"hairless"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"natural"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"rare"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"rex"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"suppressed_tail"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"short_legs"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"wikipedia_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://en.wikipedia.org/wiki/Siberian_(cat)"</span><span class="p">,</span><span class="w">
</span><span class="nl">"hypoallergenic"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<blockquote>
<p>[!TIP]
You can learn more about the breed search API here - <a href="https://docs.thecatapi.com/api-reference/breeds/breeds-search">GET
/breeds/search</a>.</p>
</blockquote>
</li>
</ol>
<p>Now you have signed up for the API, you are ready to start implementing the API client by create a data model.</p>
<h2 id="task-3---create-api-model">Task 3 - Create API model</h2>
<p>The primary objective of this tutorial is to demonstrate how to implement a REST web service client that runs in each of the project heads. To achieve that, you won't be creating a client that makes full use of <strong>TheCatApi</strong> - it will focus on breed search and favorites. The app will also use use simplified models (less fields) where possible.</p>
<ol>
<li>
<p>To create a folder for the models, in the <strong>Solution Explorer</strong>, right-click the <strong>TheCatApiClient.Shared</strong> project, select <strong>Add</strong>, click <strong>New Folder</strong>, and then name the new folder <strong>Models</strong>.</p>
</li>
<li>
<p>To add a folder for the data types, right-click the <strong>Models</strong> folder you just created, select <strong>Add</strong>, click <strong>New Folder</strong>, and then name the new folder <strong>DataModels</strong>.</p>
</li>
<li>
<p>To add a folder for the view models types, right-click the <strong>Models</strong> folder, select <strong>Add</strong>, click <strong>New Folder</strong>, and then name the new folder <strong>ViewModels</strong>.</p>
<p>After creating the folders, the project should look similar to:</p>
<p><img src="https://darenm.github.io/assets/model-folders.png" alt="Shared project model folders" /></p>
</li>
<li>
<p>Examine the JSON returned from the breed search you executed earlier:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"weight"</span><span class="p">:</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"imperial"</span><span class="p">:</span><span class="w"> </span><span class="s2">"8 - 16"</span><span class="p">,</span><span class="w">
</span><span class="nl">"metric"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4 - 7"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sibe"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Siberian"</span><span class="p">,</span><span class="w">
</span><span class="nl">"cfa_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://cfa.org/Breeds/BreedsSthruT/Siberian.aspx"</span><span class="p">,</span><span class="w">
</span><span class="nl">"vetstreet_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://www.vetstreet.com/cats/siberian"</span><span class="p">,</span><span class="w">
</span><span class="nl">"vcahospitals_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://vcahospitals.com/know-your-pet/cat-breeds/siberian"</span><span class="p">,</span><span class="w">
</span><span class="nl">"temperament"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Curious, Intelligent, Loyal, Sweet, Agile, Playful, Affectionate"</span><span class="p">,</span><span class="w">
</span><span class="nl">"origin"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Russia"</span><span class="p">,</span><span class="w">
</span><span class="nl">"country_codes"</span><span class="p">:</span><span class="w"> </span><span class="s2">"RU"</span><span class="p">,</span><span class="w">
</span><span class="nl">"country_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"RU"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The Siberians dog like temperament and affection makes the ideal lap cat and will live quite happily indoors. Very agile and powerful, the Siberian cat can easily leap and reach high places, including the tops of refrigerators and even doors. "</span><span class="p">,</span><span class="w">
</span><span class="nl">"life_span"</span><span class="p">:</span><span class="w"> </span><span class="s2">"12 - 15"</span><span class="p">,</span><span class="w">
</span><span class="nl">"indoor"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"lap"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"alt_names"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Moscow Semi-longhair, HairSiberian Forest Cat"</span><span class="p">,</span><span class="w">
</span><span class="nl">"adaptability"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"affection_level"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"child_friendly"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
</span><span class="nl">"dog_friendly"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"energy_level"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"grooming"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
</span><span class="nl">"health_issues"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
</span><span class="nl">"intelligence"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"shedding_level"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
</span><span class="nl">"social_needs"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
</span><span class="nl">"stranger_friendly"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
</span><span class="nl">"vocalisation"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"experimental"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"hairless"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"natural"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"rare"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"rex"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"suppressed_tail"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"short_legs"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"wikipedia_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://en.wikipedia.org/wiki/Siberian_(cat)"</span><span class="p">,</span><span class="w">
</span><span class="nl">"hypoallergenic"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<p>That is a lot of fields - you will use a simpler data model in this tutorial.</p>
</li>
<li>
<p>To create a simple model that represents the breed search results, right-click the <strong>DataModels</strong> folder, select <strong>Add</strong> and click <strong>Class...</strong></p>
</li>
<li>
<p>On the <strong>Add New Item</strong> dialog, in the <strong>Name</strong> field, enter <strong>Breed.cs</strong></p>
</li>
<li>
<p>In the editor, replace the content of the <strong>Breed.cs</strong> class with the following:</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.Text.Json.Serialization</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.Models.DataModels</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">Breed</span>
<span class="p">{</span>
<span class="p">[</span><span class="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"id"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Id</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"name"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Name</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"temperament"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Temperament</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"origin"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Origin</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"description"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Description</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"wikipedia_url"</span><span class="p">)]</span>
<span class="k">public</span> <span class="n">Uri</span> <span class="n">WikipediaUrl</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>
<blockquote>
<p><strong>TIP</strong>:
This class is using the <strong>JsonPropertyNameAttribute</strong> from the <strong>System.Text.Json</strong> package to map the lowercase JSON property names to the <a href="https://techterms.com/definition/pascalcase">PascalCase</a> convention used in C#. To learn more about <strong>System.Text.Json</strong>, you can review the documentation here - <a href="https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to">How to serialize and deserialize (marshal and unmarshal) JSON in .NET</a>.</p>
</blockquote>
</li>
</ol>
<p>You have now created a simple data model that can be used to deserialize the JSON returned by the breed search API. Next, you will create a service that interacts with the web service API.</p>
<h2 id="task-4---create-simple-get-service-for-breed-search">Task 4 - Create simple GET service for breed search</h2>
<p>In this task, you will create a number of classes that demonstrate how to use the <strong>HttpClient</strong> class to interact with a web service. Along the way, you will see some patterns that may assist in the reuse of the code in additional projects.</p>
<ol>
<li>
<p>To create a folder for the web services, in the <strong>Solution Explorer</strong>, right-click the <strong>TheCatApiClient.Shared</strong> project, select <strong>Add</strong>, click <strong>New Folder</strong>, and then name the new folder <strong>WebServices</strong>.</p>
</li>
<li>
<p>To add a base class for all the web services, right-click the <strong>WebServices</strong> folder, select <strong>Add</strong> and click <strong>Class...</strong></p>
</li>
<li>
<p>On the <strong>Add New Item</strong> dialog, in the <strong>Name</strong> field, enter <strong>WebApiBase.cs</strong></p>
</li>
<li>
<p>In the editor, replace the content of the <strong>WebApiBase.cs</strong> class with the following:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Net.Http</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">Uno.Extensions.Specialized</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.WebServices</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">WebApiBase</span>
<span class="p">{</span>
<span class="c1">// Insert variables below here</span>
<span class="c1">// Insert static constructor below here</span>
<span class="c1">// Insert CreateRequestMessage method below here</span>
<span class="c1">// Insert GetAsync method below here</span>
<span class="c1">// Insert DeleteAsync method below here</span>
<span class="c1">// Insert PostAsync method below here</span>
<span class="c1">// Insert PutAsync method below here</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This defines the structure of the class <strong>WebApiBase</strong>. It is defined as an abstract class with the intention that every web service API class inherits from it.</p>
</li>
<li>
<p>To add the member variable for an <strong>HttpClient</strong>, locate the comment <strong>// Insert variables below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert variables below here</span>
<span class="k">protected</span> <span class="k">static</span> <span class="n">HttpClient</span> <span class="n">_client</span><span class="p">;</span>
</code></pre></div></div>
<p>Notice that the <strong>_client</strong> variable is declared as <strong>static</strong>. This means there will only be one instance of the <strong>HttpClient</strong> shared by every instance of <strong>WebApiBase</strong> as <strong>HttpClient</strong> is intended to be instantiated once and re-used throughout the life of an application.</p>
<blockquote>
<p><strong>TIP</strong>:
You can review the Microsoft remarks on <strong>HttpClient</strong> here - <a href="https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.1#remarks">HttpClient Remarks</a>.</p>
</blockquote>
</li>
<li>
<p>To initialize the static <strong>_client</strong> variable, locate the comment <strong>// Insert static constructor below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert static constructor below here</span>
<span class="k">static</span> <span class="nf">WebApiBase</span><span class="p">()</span>
<span class="p">{</span>
<span class="cp">#if __WASM__
</span> <span class="kt">var</span> <span class="n">innerHandler</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Uno</span><span class="p">.</span><span class="n">UI</span><span class="p">.</span><span class="n">Wasm</span><span class="p">.</span><span class="nf">WasmHttpHandler</span><span class="p">();</span>
<span class="cp">#else
</span> <span class="kt">var</span> <span class="n">innerHandler</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClientHandler</span><span class="p">();</span>
<span class="cp">#endif
</span> <span class="n">_client</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClient</span><span class="p">(</span><span class="n">innerHandler</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p><strong>IMPORTANT</strong>:
The WebAssembly version of the application requires an alternative implementation of <strong>HttpMessageHandler</strong>, therefore the <code>#if __WASM__</code> preprocessor directive ensures that <strong>Uno.UI.Wasm.WasmHttpHandler</strong> is used, rather than <strong>HttpClientHandler</strong>.</p>
</blockquote>
<p>To facilitate the re-use of the single <strong>HttpClient</strong> instance, you will use instances of the <strong>HttpRequestMessage</strong> class to represent each request, rather than directly setting the <strong>BaseAddress</strong> and <strong>DefaultRequestHeader</strong> properties on the single <strong>HttpClient</strong> instance. This provides a thread-safe, reusable way of making multiple requests with the single <strong>HttpClient</strong> instance.</p>
</li>
<li>
<p>To simplify the creation of an <strong>HttpRequestMessage</strong> instance, locate the comment <strong>// Insert CreateRequestMessage method below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert CreateRequestMessage method below here</span>
<span class="k">private</span> <span class="n">HttpRequestMessage</span> <span class="nf">CreateRequestMessage</span><span class="p">(</span><span class="n">HttpMethod</span> <span class="n">method</span><span class="p">,</span> <span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="n">headers</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">httpRequestMessage</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpRequestMessage</span><span class="p">(</span><span class="n">method</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">headers</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">&&</span> <span class="n">headers</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">header</span> <span class="k">in</span> <span class="n">headers</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">httpRequestMessage</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">Key</span><span class="p">,</span> <span class="n">header</span><span class="p">.</span><span class="n">Value</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">httpRequestMessage</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Notice that this method expects the <strong>HttpMethod</strong> representing a GET, DELETE, UPDATE, or POST operation, the string URL and an optional dictionary of headers. In this initial implementation of the <strong>WebApiBase</strong> class, you will consume this from the <strong>GetAsync</strong> method you are about to implement.</p>
<blockquote>
<p><strong>NOTE</strong>:
You will use the header dictionary to supply the authentication key you created earlier.</p>
</blockquote>
</li>
<li>
<p>To implement a method that will retrieve a string from a web API, locate the comment <strong>// Insert GetAsync method below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert GetAsync method below here</span>
<span class="k">protected</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="nf">GetAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="n">headers</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="nf">CreateRequestMessage</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Get</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="p">))</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_client</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Notice that this asynchronous method uses the <strong>CreateRequestMessage</strong> you just created. As the returned <strong>HttpRequestMessage</strong> object implements the <strong>IDisposable</strong> interface, the <strong>request</strong> variable is defined within a <code>using</code> statement that obtains the resource, executes the statements that you specify, and then automatically disposes of the <strong>HttpRequestMessage</strong> instance.</p>
<p>The remainder of the method uses the single <strong>HttpClient</strong> instance to send the <strong>request</strong> and <code>await</code> a response. If the response is successful, the reponse content is read and returned as a string. If, for some reason, the response does not indicate success, a <code>null</code> is returned.</p>
<blockquote>
<p><strong>IMPORTANT</strong>:
As implementing adequate error handling would add many lines of code, more robust error handling is omitted for clarity. If you are interested in learning more about the considerations for handling errors with <strong>HttpClient</strong>, review the following blog post - <a href="https://josef.codes/httpclient-error-handling-a-test-driven-approach/">HttpClient - Error handling, a test driven approach</a>.</p>
</blockquote>
</li>
<li>
<p>You will implement the remaining methods of the <strong>WebApiBase</strong> class in a later task. The current implementation should look similar to:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Net.Http</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">Uno.Extensions.Specialized</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.WebServices</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">WebApiBase</span>
<span class="p">{</span>
<span class="c1">// Insert variables below here</span>
<span class="k">protected</span> <span class="k">static</span> <span class="n">HttpClient</span> <span class="n">_client</span><span class="p">;</span>
<span class="c1">// Insert static constructor below here</span>
<span class="k">static</span> <span class="nf">WebApiBase</span><span class="p">()</span>
<span class="p">{</span>
<span class="cp">#if __WASM__
</span> <span class="kt">var</span> <span class="n">innerHandler</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Uno</span><span class="p">.</span><span class="n">UI</span><span class="p">.</span><span class="n">Wasm</span><span class="p">.</span><span class="nf">WasmHttpHandler</span><span class="p">();</span>
<span class="cp">#else
</span> <span class="kt">var</span> <span class="n">innerHandler</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClientHandler</span><span class="p">();</span>
<span class="cp">#endif
</span> <span class="n">_client</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpClient</span><span class="p">(</span><span class="n">innerHandler</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Insert CreateRequestMessage method below here</span>
<span class="k">private</span> <span class="n">HttpRequestMessage</span> <span class="nf">CreateRequestMessage</span><span class="p">(</span><span class="n">HttpMethod</span> <span class="n">method</span><span class="p">,</span> <span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="n">headers</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">httpRequestMessage</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">HttpRequestMessage</span><span class="p">(</span><span class="n">method</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">headers</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">&&</span> <span class="n">headers</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">header</span> <span class="k">in</span> <span class="n">headers</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">httpRequestMessage</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">Key</span><span class="p">,</span> <span class="n">header</span><span class="p">.</span><span class="n">Value</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">httpRequestMessage</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Insert GetAsync method below here</span>
<span class="k">protected</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="nf">GetAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="n">headers</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="nf">CreateRequestMessage</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Get</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="p">))</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_client</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Insert DeleteAsync method below here</span>
<span class="c1">// Insert PostAsync method below here</span>
<span class="c1">// Insert PutAsync method below here</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You will now implement the Breed Search API service. This service will inherit from <strong>WebApiBase</strong> and have a single method named <strong>Search</strong> that expects a search string.</p>
</li>
<li>
<p>To add a class for the Breed Search API service, in the <strong>TheCatApiClient.Shared</strong> project, right-click the <strong>WebServices</strong> folder, select <strong>Add</strong> and click <strong>Class...</strong></p>
</li>
<li>
<p>On the <strong>Add New Item</strong> dialog, in the <strong>Name</strong> field, enter <strong>BreedSearchApi.cs</strong></p>
</li>
<li>
<p>In the editor, replace the content of the <strong>BreedSearchApi.cs</strong> class with the following:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Net</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Text.Json</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">TheCatApiClient.Shared.Models.DataModels</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.WebServices</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">BreedSearchApi</span> <span class="p">:</span> <span class="n">WebApiBase</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">Breed</span><span class="p">>></span> <span class="nf">Search</span><span class="p">(</span><span class="kt">string</span> <span class="n">search</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/breeds/search?q=</span><span class="p">{</span><span class="n">WebUtility</span><span class="p">.</span><span class="nf">HtmlEncode</span><span class="p">(</span><span class="n">search</span><span class="p">)}</span><span class="s">"</span><span class="p">,</span>
<span class="k">new</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="p">{</span>
<span class="p">{</span><span class="s">"accept"</span><span class="p">,</span> <span class="s">"application/json"</span> <span class="p">},</span>
<span class="p">{</span><span class="s">"x-api-key"</span><span class="p">,</span> <span class="s">"{YOUR-API-KEY}"</span><span class="p">}</span>
<span class="p">});</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">Breed</span><span class="p">>>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">Breed</span><span class="p">>();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As most of the complexity for working with a web service is encapsulated in the <strong>WebApiBase</strong> class, this service is straightforward.</p>
<p>First, you will note that the method is asynchronous. You will then notice the use of string interpolation and the <strong>WebUtility.HtmlEncode</strong> method to construct the API url. Also note how the request headers are supplied to the call - the <strong>accept</strong> header advises the API that the client expects JSON, and the <strong>x-api-key</strong> specifies your API key.</p>
<blockquote>
<p><strong>IMPORTANT</strong>:
Replace the <code>{YOUR-API-KEY}</code> value with the API key you were sent when you registered with TheCatApi.</p>
</blockquote>
<p>Once the result is returned, it is null-checked and, if not-null, the JSON is deserialized into an enumerable collection of <strong>Breed</strong> instances using the data model you created earlier. If the result is null, then an empty list is returned.</p>
</li>
</ol>
<p>At this point, you have implemented a simple breed search service. Now, it is time to create the view and view-model that will allow a user to enter a search term and display the results.</p>
<h2 id="task-5---create-a-base-view-model">Task 5 - Create a base view model</h2>
<p>In this sample application you will be adopting the Model-View-ViewModel (MVVM) pattern, which helps to cleanly separate business and presentation logic. In this task you will be creating the base view model that will be used in the following task to create the view model for the main page.</p>
<blockquote>
<p><strong>TIP</strong>:
If you want to learn more about MVVM, review the document here - <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/mvvm">The Model-View-ViewModel Pattern</a>.</p>
</blockquote>
<ol>
<li>
<p>To add a base view-model class, in the <strong>TheCatApiClient.Shared</strong> project, right-click the <strong>Models\ViewModels</strong> folder, select <strong>Add</strong> and click <strong>Class...</strong></p>
</li>
<li>
<p>On the <strong>Add New Item</strong> dialog, in the <strong>Name</strong> field, enter <strong>DispatchedBindableBase.cs</strong></p>
<blockquote>
<p><strong>TIP</strong>:
Although this sample only has a single view-model, it is a good practice to collect common code such as an <strong>INotifyPropertyChanged</strong> implementation and UI thread dispatch into a base class. Doing so will ease the addition of new view-models as needed and reduce code duplication.</p>
</blockquote>
</li>
<li>
<p>In the editor, replace the content of the <strong>DispatchedBindableBase.cs</strong> class with the following:</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.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.ComponentModel</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Runtime.CompilerServices</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">Windows.ApplicationModel.Core</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Windows.UI.Core</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.Models.ViewModels</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">DispatchedBindableBase</span> <span class="p">:</span> <span class="n">INotifyPropertyChanged</span>
<span class="p">{</span>
<span class="c1">// Insert the event and property below here</span>
<span class="c1">// Insert SetProperty below here</span>
<span class="c1">// Insert RaisePropertyChanged below here</span>
<span class="c1">// Insert DispatchAsync below here</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Notice that the class will implement <strong>INotifyPropertyChanged</strong> - this will allow view-models to notify bound controls when the property values have changed.</p>
</li>
<li>
<p>To the add a property and event, locate the comment <strong>// Insert the event and property below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert the event and property below here</span>
<span class="k">public</span> <span class="k">event</span> <span class="n">PropertyChangedEventHandler</span> <span class="n">PropertyChanged</span><span class="p">;</span>
<span class="k">protected</span> <span class="n">CoreDispatcher</span> <span class="n">Dispatcher</span> <span class="p">=></span> <span class="n">CoreApplication</span><span class="p">.</span><span class="n">MainView</span><span class="p">.</span><span class="n">Dispatcher</span><span class="p">;</span>
</code></pre></div></div>
<p>The <strong>PropertyChanged</strong> event declaration satisfies the <strong>INotifyPropertyChanged</strong> interface contract, but isn't particularly helpful alone - you will add methods that make it easier to use shortly.</p>
<p>The <strong>Dispatcher</strong> property is used later to ensure property updates are performed on the UI thread - an essential detail for updating controls in multi-tasking applications.</p>
<blockquote>
<p><strong>TIP</strong>:
You can learn more about the UI Thread and multi-tasking here - <a href="https://docs.microsoft.com/en-us/windows/uwp/debug-test-perf/keep-the-ui-thread-responsive">Keep the UI thread responsive</a></p>
</blockquote>
</li>
<li>
<p>To add the method that will be used to set a property value and raise the property changed event, locate the comment <strong>// Insert SetProperty below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert SetProperty below here</span>
<span class="k">protected</span> <span class="k">virtual</span> <span class="kt">bool</span> <span class="n">SetProperty</span><span class="p"><</span><span class="n">T</span><span class="p">>(</span><span class="k">ref</span> <span class="n">T</span> <span class="n">backingVariable</span><span class="p">,</span> <span class="n">T</span> <span class="k">value</span><span class="p">,</span> <span class="p">[</span><span class="n">CallerMemberName</span><span class="p">]</span> <span class="kt">string</span> <span class="n">propertyName</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">EqualityComparer</span><span class="p"><</span><span class="n">T</span><span class="p">>.</span><span class="n">Default</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">backingVariable</span><span class="p">,</span> <span class="k">value</span><span class="p">))</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="n">backingVariable</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
<span class="nf">RaisePropertyChanged</span><span class="p">(</span><span class="n">propertyName</span><span class="p">);</span>
<span class="k">return</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>There can be a bit to unpack here. First, this is a generic method that allows any type to be used, as indicated by the use of <code><T></code>, so it can be used for all properties.</p>
<p>Then, looking at parameters, the <strong>backingVariable</strong> is passed by reference, allowing this method to change the value for the caller as well as locally. Next, the <strong>value</strong> is supplied. Finally, the <strong>propertyName</strong> parameter is defined. At first glance, this appears to be an optional parameter with a default value of null. However the compiler notes the presence of the <strong>CallerMemberName</strong> attribute and assigns the name of the calling method to the property (in the case of a property setter method, the name of the property).</p>
<p>The method body then compares the incoming value with the current property value, and returns false if they are equal. This prevents a situation where a property is updated, which updates a control via binding, which then sets the same property with the same value again. Without this guard, the code would continue to loop.</p>
<p>The <strong>value</strong> is then assigned to the <strong>backingVariable</strong> (updating the variable in the caller). The property changed event is raise (you will add that next) and a true value is returned.</p>
<blockquote>
<p><strong>TIP</strong>:
You can learn more about <strong>CallerMemberNameAttribute</strong> here - <a href="https://docs.microsoft.com/dotnet/csharp/language-reference/attributes/caller-information">Reserved attributes: Determine caller information</a>.</p>
</blockquote>
</li>
<li>
<p>To add the method that will actually raise the property changed event, locate the comment <strong>// Insert RaisePropertyChanged below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert RaisePropertyChanged below here</span>
<span class="k">protected</span> <span class="k">void</span> <span class="nf">RaisePropertyChanged</span><span class="p">([</span><span class="n">CallerMemberName</span><span class="p">]</span> <span class="kt">string</span> <span class="n">propertyName</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="cp">#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
</span> <span class="nf">DispatchAsync</span><span class="p">(()</span> <span class="p">=></span> <span class="n">PropertyChanged</span><span class="p">?.</span><span class="nf">Invoke</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">new</span> <span class="nf">PropertyChangedEventArgs</span><span class="p">(</span><span class="n">propertyName</span><span class="p">)));</span>
<span class="cp">#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
</span><span class="p">}</span>
</code></pre></div></div>
<p>This method uses a helper method (added next) to ensure the event is raised on the UI thread. As the code does not await this execution, the compiler would usually display a warning in the assumption that this is a mistake. As it is intentional, the code includes <code>#pragma</code> directives that temporarily disable that warning.</p>
<blockquote>
<p><strong>TIP</strong>:
If you want to learn more about lambda expressions, review the document here - <a href="https://docs.microsoft.com/dotnet/csharp/language-reference/operators/lambda-expressions">Lambda expressions</a></p>
</blockquote>
</li>
<li>
<p>To add a helper method that will ensure a callback is executed on the UI thread, locate the comment <strong>// Insert Dispatch below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert DispatchAsync below here</span>
<span class="k">protected</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">DispatchAsync</span><span class="p">(</span><span class="n">DispatchedHandler</span> <span class="n">callback</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// As WASM is currently single-threaded, and Dispatcher.HasThreadAccess always returns false for broader compatibility reasons</span>
<span class="c1">// the following code ensures the app always directly invokes the callback on WASM.</span>
<span class="kt">var</span> <span class="n">hasThreadAccess</span> <span class="p">=</span>
<span class="cp">#if __WASM__
</span> <span class="k">true</span><span class="p">;</span>
<span class="cp">#else
</span> <span class="n">Dispatcher</span><span class="p">.</span><span class="n">HasThreadAccess</span><span class="p">;</span>
<span class="cp">#endif
</span>
<span class="k">if</span> <span class="p">(</span><span class="n">hasThreadAccess</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">callback</span><span class="p">.</span><span class="nf">Invoke</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">await</span> <span class="n">Dispatcher</span><span class="p">.</span><span class="nf">RunAsync</span><span class="p">(</span><span class="n">CoreDispatcherPriority</span><span class="p">.</span><span class="n">Normal</span><span class="p">,</span> <span class="n">callback</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p><strong>NOTE</strong>:
As <strong>WASM</strong> is currently single-threaded, the <strong>Uno.WASM</strong> implementation of <code>Dispatcher.HasThreadAccess</code> always returns <code>false</code> by default. This default has been chosen to prevent live-locking for certain frameworks that expect a multi-threaded environment. This app overrides that default WASM behavior here via conditional compilation and always invokes the callback directly on WASM.</p>
</blockquote>
<p>In XAML applications, control bindings should only be updated on the UI thread, therefore this code uses the value of <strong>hasThreadAccess</strong> to determine if the callback can be directly invoked. If not, the <strong>Dispatcher.RunAsync</strong> method is used to execute the callback asynchronously on the UI Thread. This method is used when setting properties and whenever bound collections are updated, to ensure the collection views (such as GridView, ListView, etc.) are updated.</p>
</li>
<li>
<p>Once complete the base class will look similar to the following:</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.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.ComponentModel</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Runtime.CompilerServices</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">Windows.ApplicationModel.Core</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Windows.UI.Core</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.Models.ViewModels</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">DispatchedBindableBase</span> <span class="p">:</span> <span class="n">INotifyPropertyChanged</span>
<span class="p">{</span>
<span class="c1">// Insert variables below here</span>
<span class="k">protected</span> <span class="n">CoreDispatcher</span> <span class="n">Dispatcher</span> <span class="p">=></span> <span class="n">CoreApplication</span><span class="p">.</span><span class="n">MainView</span><span class="p">.</span><span class="n">Dispatcher</span><span class="p">;</span>
<span class="c1">// Insert variables below here</span>
<span class="k">public</span> <span class="k">event</span> <span class="n">PropertyChangedEventHandler</span> <span class="n">PropertyChanged</span><span class="p">;</span>
<span class="c1">// Insert SetProperty below here</span>
<span class="k">protected</span> <span class="k">virtual</span> <span class="kt">bool</span> <span class="n">SetProperty</span><span class="p"><</span><span class="n">T</span><span class="p">>(</span><span class="k">ref</span> <span class="n">T</span> <span class="n">backingVariable</span><span class="p">,</span> <span class="n">T</span> <span class="k">value</span><span class="p">,</span> <span class="p">[</span><span class="n">CallerMemberName</span><span class="p">]</span> <span class="kt">string</span> <span class="n">propertyName</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">EqualityComparer</span><span class="p"><</span><span class="n">T</span><span class="p">>.</span><span class="n">Default</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">backingVariable</span><span class="p">,</span> <span class="k">value</span><span class="p">))</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="n">backingVariable</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
<span class="nf">RaisePropertyChanged</span><span class="p">(</span><span class="n">propertyName</span><span class="p">);</span>
<span class="k">return</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Insert RaisePropertyChanged below here</span>
<span class="k">protected</span> <span class="k">void</span> <span class="nf">RaisePropertyChanged</span><span class="p">([</span><span class="n">CallerMemberName</span><span class="p">]</span> <span class="kt">string</span> <span class="n">propertyName</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="cp">#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
</span> <span class="nf">DispatchAsync</span><span class="p">(()</span> <span class="p">=></span> <span class="n">PropertyChanged</span><span class="p">?.</span><span class="nf">Invoke</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">new</span> <span class="nf">PropertyChangedEventArgs</span><span class="p">(</span><span class="n">propertyName</span><span class="p">)));</span>
<span class="cp">#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
</span> <span class="p">}</span>
<span class="c1">// Insert DispatchAsync below here</span>
<span class="k">protected</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">DispatchAsync</span><span class="p">(</span><span class="n">DispatchedHandler</span> <span class="n">callback</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">Dispatcher</span><span class="p">.</span><span class="n">HasThreadAccess</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">callback</span><span class="p">.</span><span class="nf">Invoke</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">await</span> <span class="n">Dispatcher</span><span class="p">.</span><span class="nf">RunAsync</span><span class="p">(</span><span class="n">CoreDispatcherPriority</span><span class="p">.</span><span class="n">Normal</span><span class="p">,</span> <span class="n">callback</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>
</li>
</ol>
<h2 id="task-6---build-the-search-ui-view-model">Task 6 - Build the search UI view model</h2>
<p>In this task you will build the view-model that implements a simple breed search feature using the base class and web service client you just implemented.</p>
<ol>
<li>
<p>On the <strong>Add New Item</strong> dialog, in the <strong>Name</strong> field, enter <strong>MainViewModel.cs</strong></p>
</li>
<li>
<p>In the editor, replace the content of the <strong>MainViewModel.cs</strong> class with the following:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.ObjectModel</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Linq</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">TheCatApiClient.Shared.Models.DataModels</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">TheCatApiClient.Shared.WebServices</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Uno.Extensions.Specialized</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.Models.ViewModels</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">MainViewModel</span> <span class="p">:</span> <span class="n">DispatchedBindableBase</span>
<span class="p">{</span>
<span class="c1">// Insert member variables below here</span>
<span class="c1">// Insert properties below here</span>
<span class="c1">// Insert constructor below here</span>
<span class="c1">// Insert SearchBreeds below here</span>
<span class="c1">// Insert Favorites below here</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This <strong>MainViewModel</strong> class inherits from the base class you just created.</p>
</li>
<li>
<p>To add the member variables, locate the comment <strong>// Insert variables below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert variables below here</span>
<span class="k">private</span> <span class="kt">bool</span> <span class="n">_isBusy</span><span class="p">;</span>
<span class="k">private</span> <span class="kt">string</span> <span class="n">_searchTerm</span><span class="p">;</span>
<span class="k">private</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Breed</span><span class="p">></span> <span class="n">_searchResults</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Breed</span><span class="p">>();</span>
<span class="k">private</span> <span class="n">BreedSearchApi</span> <span class="n">_breedSearchApi</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BreedSearchApi</span><span class="p">();</span>
</code></pre></div></div>
<p>The <strong>_searchTerm</strong> variable is the backing variable for the property that will hold the user-entered search term.</p>
<p>The <strong>_searchResults</strong> variable is the backing variable for the property that will hold the search results. As it is an <strong>ObservableCollection</strong>, any changes to the collection will update the UI.</p>
<p>The final variable, <strong>_breedSearchApi</strong>, holds a reference to an instance of the <strong>BreedSearchApi</strong> you created earlier.</p>
<blockquote>
<p><strong>TIP</strong>:
You can learn more about <strong>ObservableCollection</strong> and its events here - <a href="https://docs.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.observablecollection-1?view=netcore-3.1">ObservableCollection<T> Class</a></p>
</blockquote>
</li>
<li>
<p>To add the properties that will be used in XAML bindings, locate the comment <strong>// Insert properties below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert properties below here</span>
<span class="k">public</span> <span class="kt">bool</span> <span class="n">IsBusy</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">=></span> <span class="n">_isBusy</span><span class="p">;</span>
<span class="k">set</span> <span class="p">=></span> <span class="nf">SetProperty</span><span class="p">(</span><span class="k">ref</span> <span class="n">_isBusy</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">SearchTerm</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">=></span> <span class="n">_searchTerm</span><span class="p">;</span>
<span class="k">set</span> <span class="p">=></span> <span class="nf">SetProperty</span><span class="p">(</span><span class="k">ref</span> <span class="n">_searchTerm</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Breed</span><span class="p">></span> <span class="n">SearchResults</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">=></span> <span class="n">_searchResults</span><span class="p">;</span>
<span class="k">set</span> <span class="p">=></span> <span class="nf">SetProperty</span><span class="p">(</span><span class="k">ref</span> <span class="n">_searchResults</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>These properties use the backing variables you added earlier, and utilize the <strong>SetProperty</strong> inherited from <strong>BaseViewModel</strong> to raise property changed notifications to ensure the UI is updated when the values change.</p>
</li>
<li>
<p>To add the method that is invoked when the user wishes to search for breeds, locate the comment <strong>// Insert SearchBreeds below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert SearchBreeds below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">SearchBreeds</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">SearchTerm</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_breedSearchApi</span><span class="p">.</span><span class="nf">Search</span><span class="p">(</span><span class="n">SearchTerm</span><span class="p">).</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="n">SearchResults</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Breed</span><span class="p">>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">finally</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As much of the complexity of interacting with the breed search API is encapsulated, this method is pretty straightforward. Notice that it first ensures the <strong>SearchTerm</strong> is not empty, and then sets the <strong>IsBusy</strong> property to <code>true</code>. <strong>IsBusy</strong> is used to toggle a data loading indicator in the view. Notice the use of <code>try {} finally {}</code> to ensure the <strong>IsBusy</strong> property is always set back to false even if an exception is thrown. Of course, in production you would also be handling the exception more gracefully...</p>
<p>The <strong>result</strong> variable is then assigned the return value from the asynchronous call to the <strong>Search</strong> method you implemented earlier. Remember that it will contain either an empty <code>List<Breed></code> or a populated <code>IEnumerable<Breed></code> containing the search results. The <code>ConfigureAwait(false)</code> call configures the task so that continuation after the await does not have to be run in the caller context, therefore avoiding any possible deadlocks. This means that the code that follows may not execute on the UI thread. Fortunately, the base view model ensures that property updates are dispatched to the UI thread.</p>
<p>Next, if results are present, the <strong>SearchResults</strong> collection is replaced. Finally, the <strong>IsBusy</strong> property is set to <code>false</code>.</p>
</li>
<li>
<p>You will implement the remaining methods of the <strong>MainViewModel</strong> class in a later task. The current implementation should look similar to:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Linq</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">TheCatApiClient.Shared.Models.DataModels</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">TheCatApiClient.Shared.WebServices</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Uno.Extensions.Specialized</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.Models.ViewModels</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">MainViewModel</span> <span class="p">:</span> <span class="n">BaseViewModel</span>
<span class="p">{</span>
<span class="c1">// Insert member variables below here</span>
<span class="k">private</span> <span class="kt">bool</span> <span class="n">_isBusy</span><span class="p">;</span>
<span class="k">private</span> <span class="kt">string</span> <span class="n">_searchTerm</span><span class="p">;</span>
<span class="k">private</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Breed</span><span class="p">></span> <span class="n">_searchResults</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Breed</span><span class="p">>();</span>
<span class="k">private</span> <span class="n">BreedSearchApi</span> <span class="n">_breedSearchApi</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BreedSearchApi</span><span class="p">();</span>
<span class="c1">// Insert properties below here</span>
<span class="k">public</span> <span class="kt">bool</span> <span class="n">IsBusy</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">=></span> <span class="n">_isBusy</span><span class="p">;</span>
<span class="k">set</span> <span class="p">=></span> <span class="nf">SetProperty</span><span class="p">(</span><span class="k">ref</span> <span class="n">_isBusy</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">SearchTerm</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">=></span> <span class="n">_searchTerm</span><span class="p">;</span>
<span class="k">set</span> <span class="p">=></span> <span class="nf">SetProperty</span><span class="p">(</span><span class="k">ref</span> <span class="n">_searchTerm</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Breed</span><span class="p">></span> <span class="n">SearchResults</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">=></span> <span class="n">_searchResults</span><span class="p">;</span>
<span class="k">set</span> <span class="p">=></span> <span class="nf">SetProperty</span><span class="p">(</span><span class="k">ref</span> <span class="n">_searchResults</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Insert constructor below here</span>
<span class="k">public</span> <span class="nf">MainViewModel</span><span class="p">()</span>
<span class="p">{</span>
<span class="p">}</span>
<span class="c1">// Insert SearchBreeds below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">SearchBreeds</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">SearchTerm</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_breedSearchApi</span><span class="p">.</span><span class="nf">Search</span><span class="p">(</span><span class="n">SearchTerm</span><span class="p">).</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="n">SearchResults</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Breed</span><span class="p">>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">finally</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Insert Favorites below here</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
</li>
</ol>
<p>You have now built the view-model for the Main Page. Next, it is time to build the XAML for the view.</p>
<h2 id="task-7---add-breed-search-to-the-main-view">Task 7 - Add breed search to the main view</h2>
<p>In this task you will create the XAML for the UI and implement the bindings for the <strong>MainViewModel</strong>. When the UWP version of the app is running, the UI will look similar to this:</p>
<p><img src="https://darenm.github.io/assets/uwp-ui-preview.png" alt="UWP Search UI preview" /></p>
<ol>
<li>
<p>In the <strong>Solution Explorer</strong>, in the <strong>TheCatApiClient.Shared</strong> project, open the <strong>MainPage.xaml</strong> file.</p>
</li>
<li>
<p>If the <strong>MainPage.xaml</strong> file has opened in the <strong>Design</strong> view, switch to the <strong>XAML</strong> view.</p>
</li>
<li>
<p>Replace the contents of the <strong>MainPage.xaml</strong> file with the following:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Page</span>
<span class="na">x:Class=</span><span class="s">"TheCatApiClient.MainPage"</span>
<span class="na">xmlns=</span><span class="s">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span>
<span class="na">xmlns:x=</span><span class="s">"http://schemas.microsoft.com/winfx/2006/xaml"</span>
<span class="na">xmlns:local=</span><span class="s">"using:TheCatApiClient"</span>
<span class="na">xmlns:d=</span><span class="s">"http://schemas.microsoft.com/expression/blend/2008"</span>
<span class="na">xmlns:mc=</span><span class="s">"http://schemas.openxmlformats.org/markup-compatibility/2006"</span>
<span class="na">xmlns:viewmodels=</span><span class="s">"using:TheCatApiClient.Shared.Models.ViewModels"</span>
<span class="na">xmlns:datamodels=</span><span class="s">"using:TheCatApiClient.Shared.Models.DataModels"</span>
<span class="na">mc:Ignorable=</span><span class="s">"d"</span><span class="nt">></span>
<span class="nt"><Page.DataContext></span>
<span class="nt"><viewmodels:MainViewModel</span> <span class="na">x:Name=</span><span class="s">"ViewModel"</span> <span class="nt">/></span>
<span class="nt"></Page.DataContext></span>
<span class="nt"><Grid</span> <span class="na">Background=</span><span class="s">"{ThemeResource ApplicationPageBackgroundThemeBrush}"</span>
<span class="na">toolkit:VisibleBoundsPadding.PaddingMask=</span><span class="s">"All"</span><span class="nt">></span>
<span class="nt"><Grid</span> <span class="na">Padding=</span><span class="s">"12"</span><span class="nt">></span>
<span class="nt"><Grid.RowDefinitions></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"Auto"</span><span class="nt">/></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"Auto"</span><span class="nt">/></span>
<span class="nt"><RowDefinition/></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"Auto"</span><span class="nt">/></span>
<span class="nt"><RowDefinition/></span>
<span class="nt"></Grid.RowDefinitions></span>
<span class="c"><!-- ROW 0 - Title --></span>
<span class="nt"><TextBlock</span> <span class="na">Text=</span><span class="s">"The Cat API Client"</span> <span class="na">Style=</span><span class="s">"{StaticResource HeaderTextBlockStyle}"</span> <span class="nt">/></span>
<span class="c"><!-- ROW 1 - Search Box --></span>
<span class="c"><!-- ROW 2 - Search Results --></span>
<span class="c"><!-- ROW 3 - Favorites Title --></span>
<span class="nt"><TextBlock</span> <span class="na">Grid.Row=</span><span class="s">"3"</span> <span class="na">Text=</span><span class="s">"Favorites"</span> <span class="na">Style=</span><span class="s">"{StaticResource SubheaderTextBlockStyle}"</span> <span class="nt">/></span>
<span class="c"><!-- ROW 4 - Favorites --></span>
<span class="c"><!-- Busy Overlay --></span>
<span class="nt"></Grid></span>
<span class="nt"></Grid></span>
<span class="nt"></Page></span>
</code></pre></div></div>
<p>This page utilizes a simple grid layout with 6 rows to separate the various areas of the UI and will define an overlay that will appear when the app is busy retrieving data from the service.</p>
<p>Notice that the XAML includes namespace definitions for the <strong>ViewModels</strong> and <strong>DataModels</strong>, as well as the <strong>Uno.IO.Toolkit</strong>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Page</span>
<span class="err">...</span>
<span class="na">xmlns:viewmodels=</span><span class="s">"using:TheCatApiClient.Shared.Models.ViewModels"</span>
<span class="na">xmlns:datamodels=</span><span class="s">"using:TheCatApiClient.Shared.Models.DataModels"</span>
<span class="na">xmlns:toolkit=</span><span class="s">"using:Uno.UI.Toolkit"</span>
<span class="err">...</span>
<span class="nt">></span>
</code></pre></div></div>
<p>The Uno UI toolkit is added so that <strong>toolkit:VisibleBoundsPadding</strong> can be added - this ensures that the UI will adapt appropriately when the app is running on a device that has a "notch", etc.</p>
<p>Also note how the view-model <strong>MainViewModel</strong> is instantiated and assigned as the <strong>DataContext</strong>. The view-model can then be referred to as <strong>ViewModel</strong> in the <code>x:Bind</code> expressions you will add shortly.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Page.DataContext></span>
<span class="nt"><viewmodels:MainViewModel</span> <span class="na">x:Name=</span><span class="s">"ViewModel"</span> <span class="nt">/></span>
<span class="nt"></Page.DataContext></span>
</code></pre></div></div>
</li>
<li>
<p>To add the search box, locate the <code><!-- ROW 1 - Search Box --></code> comment and replace it with the following:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- ROW 1 - Search Box --></span>
<span class="nt"><AutoSuggestBox</span> <span class="na">Grid.Row=</span><span class="s">"1"</span> <span class="na">PlaceholderText=</span><span class="s">"Search for breed"</span> <span class="na">QueryIcon=</span><span class="s">"Find"</span>
<span class="na">Name=</span><span class="s">"BreedSearchBox"</span>
<span class="na">Width=</span><span class="s">"300"</span> <span class="na">Margin=</span><span class="s">"4,20"</span> <span class="na">HorizontalAlignment=</span><span class="s">"Left"</span>
<span class="na">Text=</span><span class="s">"{x:Bind ViewModel.SearchTerm, Mode=OneWay}"</span>
<span class="na">QuerySubmitted=</span><span class="s">"BreedSearchBox_QuerySubmitted"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>Notice that the <strong>Text</strong> property is bound to the <strong>ViewModel.SearchTerm</strong>. This value is updated when the search button is pressed, so it will be available when the <strong>QuerySubmitted</strong> event is raised.</p>
</li>
<li>
<p>To add the search results control, locate the <code><!-- ROW 2 - Search Results --></code> comment and replace it with the following:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- ROW 2 - Search Results --></span>
<span class="nt"><GridView</span> <span class="na">Grid.Row=</span><span class="s">"2"</span> <span class="na">ItemsSource=</span><span class="s">"{x:Bind ViewModel.SearchResults, Mode=OneWay}"</span> <span class="na">Margin=</span><span class="s">"4"</span>
<span class="na">SelectionMode=</span><span class="s">"None"</span><span class="nt">></span>
<span class="nt"><GridView.ItemTemplate></span>
<span class="nt"><DataTemplate</span> <span class="na">x:DataType=</span><span class="s">"datamodels:Breed"</span><span class="nt">></span>
<span class="nt"><StackPanel</span> <span class="na">Width=</span><span class="s">"300"</span> <span class="na">Padding=</span><span class="s">"4"</span><span class="nt">></span>
<span class="nt"><TextBlock</span> <span class="na">Text=</span><span class="s">"{x:Bind Name}"</span> <span class="na">Style=</span><span class="s">"{StaticResource TitleTextBlockStyle}"</span><span class="nt">/></span>
<span class="nt"><TextBlock</span> <span class="na">Text=</span><span class="s">"{x:Bind Description}"</span> <span class="na">Style=</span><span class="s">"{StaticResource BodyTextBlockStyle}"</span> <span class="na">TextWrapping=</span><span class="s">"WrapWholeWords"</span>
<span class="na">Height=</span><span class="s">"70"</span> <span class="na">TextTrimming=</span><span class="s">"CharacterEllipsis"</span><span class="nt">/></span>
<span class="nt"></StackPanel></span>
<span class="nt"></DataTemplate></span>
<span class="nt"></GridView.ItemTemplate></span>
<span class="nt"></GridView></span>
</code></pre></div></div>
<p>Notice that the <strong>ItemsSource</strong> property is bound to <strong>ViewModel.SearchResults</strong> and will be updated when the breed service search returns results.</p>
<p>The <strong>DataTemplate</strong> used for each of the search result items is strongly-typed using <code>x:DataType="datamodels:Breed"</code>. This allows the two <strong>TextBlock</strong> controls to use <code>x:Bind</code> to reference the <strong>Name</strong> and <strong>Description</strong> propertied from the <strong>Breed</strong> class.</p>
<p>You will add the favorites implementation in a later task.</p>
</li>
<li>
<p>To add an overlay for the UI that will be displayed when the service is retrieving data, locate the <code><!-- Busy Overlay --></code> comment and replace it with the following:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- Busy Overlay --></span>
<span class="nt"><Grid</span> <span class="na">Background=</span><span class="s">"Gray"</span> <span class="na">Opacity=</span><span class="s">"0.75"</span> <span class="na">Visibility=</span><span class="s">"{x:Bind ViewModel.IsBusy, Mode=OneWay}"</span>
<span class="na">Grid.Row=</span><span class="s">"1"</span> <span class="na">Grid.RowSpan=</span><span class="s">"4"</span><span class="nt">></span>
<span class="nt"><TextBlock</span> <span class="na">Text=</span><span class="s">"Downloading data..."</span> <span class="na">Style=</span><span class="s">"{StaticResource TitleTextBlockStyle}"</span> <span class="na">VerticalAlignment=</span><span class="s">"Center"</span> <span class="na">HorizontalAlignment=</span><span class="s">"Center"</span><span class="nt">/></span>
<span class="nt"></Grid></span>
</code></pre></div></div>
<p>Notice that this grid is displayed from row 1 and spans 4 rows. As this grid is defined last, it will appear on top of the other controls, block click/touch interactions with the underlying controls as well showing a semi-opaque message that the app is <strong>Downloading data</strong>.</p>
<p><img src="https://darenm.github.io/assets/uwp-ui-busy.png" alt="UWP Search UI preview" /></p>
<blockquote>
<p><strong>NOTE</strong>:
You would typically use a <strong>ProgressRing</strong> here, but in a effort to keep the UI as simple as possible, this approach was taken.</p>
</blockquote>
</li>
<li>
<p>When you have completed adding the XAML, it should look similar to this.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Page</span>
<span class="na">x:Class=</span><span class="s">"TheCatApiClient.MainPage"</span>
<span class="na">xmlns=</span><span class="s">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span>
<span class="na">xmlns:x=</span><span class="s">"http://schemas.microsoft.com/winfx/2006/xaml"</span>
<span class="na">xmlns:local=</span><span class="s">"using:TheCatApiClient"</span>
<span class="na">xmlns:d=</span><span class="s">"http://schemas.microsoft.com/expression/blend/2008"</span>
<span class="na">xmlns:mc=</span><span class="s">"http://schemas.openxmlformats.org/markup-compatibility/2006"</span>
<span class="na">xmlns:viewmodels=</span><span class="s">"using:TheCatApiClient.Shared.Models.ViewModels"</span>
<span class="na">xmlns:datamodels=</span><span class="s">"using:TheCatApiClient.Shared.Models.DataModels"</span>
<span class="na">mc:Ignorable=</span><span class="s">"d"</span><span class="nt">></span>
<span class="nt"><Page.DataContext></span>
<span class="nt"><viewmodels:MainViewModel</span> <span class="na">x:Name=</span><span class="s">"ViewModel"</span> <span class="nt">/></span>
<span class="nt"></Page.DataContext></span>
<span class="nt"><Grid</span> <span class="na">Background=</span><span class="s">"{ThemeResource ApplicationPageBackgroundThemeBrush}"</span>
<span class="na">Margin=</span><span class="s">"12"</span><span class="nt">></span>
<span class="nt"><Grid.RowDefinitions></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"Auto"</span><span class="nt">/></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"Auto"</span><span class="nt">/></span>
<span class="nt"><RowDefinition/></span>
<span class="nt"><RowDefinition</span> <span class="na">Height=</span><span class="s">"Auto"</span><span class="nt">/></span>
<span class="nt"><RowDefinition/></span>
<span class="nt"></Grid.RowDefinitions></span>
<span class="c"><!-- ROW 0 - Title --></span>
<span class="nt"><TextBlock</span> <span class="na">Text=</span><span class="s">"{x:Bind ViewModel.Title}"</span> <span class="na">Style=</span><span class="s">"{StaticResource HeaderTextBlockStyle}"</span> <span class="nt">/></span>
<span class="c"><!-- ROW 1 - Search Box --></span>
<span class="nt"><AutoSuggestBox</span> <span class="na">Grid.Row=</span><span class="s">"1"</span> <span class="na">PlaceholderText=</span><span class="s">"Search for breed"</span> <span class="na">QueryIcon=</span><span class="s">"Find"</span>
<span class="na">Name=</span><span class="s">"BreedSearchBox"</span>
<span class="na">Width=</span><span class="s">"300"</span> <span class="na">Margin=</span><span class="s">"4,20"</span> <span class="na">HorizontalAlignment=</span><span class="s">"Left"</span>
<span class="na">Text=</span><span class="s">"{x:Bind ViewModel.SearchTerm, Mode=TwoWay}"</span>
<span class="na">QuerySubmitted=</span><span class="s">"BreedSearchBox_QuerySubmitted"</span> <span class="nt">/></span>
<span class="c"><!-- ROW 2 - Search Results --></span>
<span class="nt"><GridView</span> <span class="na">Grid.Row=</span><span class="s">"2"</span> <span class="na">ItemsSource=</span><span class="s">"{x:Bind ViewModel.SearchResults, Mode=OneWay}"</span> <span class="na">Margin=</span><span class="s">"4"</span>
<span class="na">SelectionMode=</span><span class="s">"None"</span><span class="nt">></span>
<span class="nt"><GridView.ItemTemplate></span>
<span class="nt"><DataTemplate</span> <span class="na">x:DataType=</span><span class="s">"datamodels:Breed"</span><span class="nt">></span>
<span class="nt"><StackPanel</span> <span class="na">Width=</span><span class="s">"300"</span> <span class="na">Padding=</span><span class="s">"4"</span><span class="nt">></span>
<span class="nt"><TextBlock</span> <span class="na">Text=</span><span class="s">"{x:Bind Name}"</span> <span class="na">Style=</span><span class="s">"{StaticResource TitleTextBlockStyle}"</span><span class="nt">/></span>
<span class="nt"><TextBlock</span> <span class="na">Text=</span><span class="s">"{x:Bind Description}"</span> <span class="na">Style=</span><span class="s">"{StaticResource BodyTextBlockStyle}"</span> <span class="na">TextWrapping=</span><span class="s">"WrapWholeWords"</span>
<span class="na">Height=</span><span class="s">"70"</span> <span class="na">TextTrimming=</span><span class="s">"CharacterEllipsis"</span><span class="nt">/></span>
<span class="nt"></StackPanel></span>
<span class="nt"></DataTemplate></span>
<span class="nt"></GridView.ItemTemplate></span>
<span class="nt"></GridView></span>
<span class="c"><!-- ROW 3 - Favorites Title --></span>
<span class="nt"><TextBlock</span> <span class="na">Grid.Row=</span><span class="s">"3"</span> <span class="na">Text=</span><span class="s">"{x:Bind ViewModel.Subtitle}"</span> <span class="na">Style=</span><span class="s">"{StaticResource SubheaderTextBlockStyle}"</span> <span class="nt">/></span>
<span class="c"><!-- ROW 4 - Favorites --></span>
<span class="c"><!-- Busy Overlay --></span>
<span class="nt"><Grid</span> <span class="na">Background=</span><span class="s">"Gray"</span> <span class="na">Opacity=</span><span class="s">"0.75"</span> <span class="na">Visibility=</span><span class="s">"{x:Bind ViewModel.IsBusy, Mode=OneWay}"</span>
<span class="na">Grid.Row=</span><span class="s">"1"</span> <span class="na">Grid.RowSpan=</span><span class="s">"4"</span><span class="nt">></span>
<span class="nt"><TextBlock</span> <span class="na">Text=</span><span class="s">"Downloading data..."</span> <span class="na">Style=</span><span class="s">"{StaticResource TitleTextBlockStyle}"</span> <span class="na">VerticalAlignment=</span><span class="s">"Center"</span> <span class="na">HorizontalAlignment=</span><span class="s">"Center"</span><span class="nt">/></span>
<span class="nt"></Grid></span>
<span class="nt"></Grid></span>
<span class="nt"></Page></span>
</code></pre></div></div>
</li>
<li>
<p>To add the code-behind handler for the <strong>QuerySubmitted</strong> event, switch to the code view by either opening <strong>MainPage.xaml.cs</strong> or by pressing the default keyboard shortcut <strong>F7</strong>.</p>
</li>
<li>
<p>Locate the <strong>MainPage</strong> constructor and add the following code beneath it:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">BreedSearchBox_QuerySubmitted</span><span class="p">(</span><span class="n">AutoSuggestBox</span> <span class="n">sender</span><span class="p">,</span> <span class="n">AutoSuggestBoxQuerySubmittedEventArgs</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">await</span> <span class="n">ViewModel</span><span class="p">.</span><span class="nf">SearchBreeds</span><span class="p">();</span>
<span class="p">}</span>
<span class="c1">// Insert favorites below here</span>
</code></pre></div></div>
<p>This method simply calls the <strong>SearchBreeds</strong> method on the view-model.</p>
</li>
<li>
<p>You have now completed the implementation of a simple web service client. To run the application, press <strong>F5</strong>.</p>
</li>
<li>
<p>In the application, in the <strong>Search for breed</strong> search box, enter <strong>S</strong> and click the search icon.</p>
<p>The <strong>Downloading data...</strong> UI should be shown and then the results should be populated. You should be able to scroll the results and select one (nothing will happen yet).</p>
<p><img src="https://darenm.github.io/assets/uwp-ui-preview.png" alt="UWP Search UI preview" /></p>
</li>
<li>
<p>When you have finished testing the UWP app, close it.</p>
</li>
<li>
<p>To test the app in Wasm, in the <strong>Solution Explorer</strong>, right-click the <strong>TheCatApiClient.Wasm</strong> project and select <strong>Set as Startup project</strong>.</p>
</li>
<li>
<p>To run the application, press <strong>F5</strong>.</p>
<p>Visual Studio will open a browser and display the application.</p>
</li>
<li>
<p>In the application, in the <strong>Search for breed</strong> search box, enter <strong>S</strong> and click the search icon.</p>
<p>The <strong>Downloading data...</strong> UI should be shown and then the results should be populated. You should be able to scroll the results and select one (nothing will happen yet).</p>
<p><img src="https://darenm.github.io/assets/wasm-ui-preview.png" alt="WebAssembly Search UI preview" /></p>
<p>This validates that your code to use <strong>Uno.UI.Wasm.WasmHttpHandler</strong> for Wasm, rather than <strong>HttpClientHandler</strong> is working.</p>
<p>Here is how the app looks running in the iPhone and Android simulators:</p>
<p><img src="https://darenm.github.io/assets/app-running-simulators.png" alt="Application running on iPhone and Android simulators" /></p>
</li>
</ol>
<p>Congratulations - you now have a cross-platform application that interacts with a web service using an HTTP GET operation. If you are interested in seeing how to implement additional operations, continue with the next tasks.</p>
<h2 id="task-8---add-new-services-to-support-favorites">Task 8 - Add new services to support favorites</h2>
<p>In the previous tasks, you created a web service client and UI that performs a simple cat breed search. In this task you will add a service that leverages the favorites API. We will build a UI that will add a picture of a selected breed to your favorites when you click it in the search results, and will remove it from the favorites when you click it in the favorites list.</p>
<p>To achieve this, the <strong>WebApiBase</strong> class will updated to include additional operations that support HTTP Post, Delete and Put verbs. You will then add two more services:</p>
<ul>
<li><strong>ImageApi</strong> - returns a list of images for a given breed.</li>
<li><strong>FavoritesApi</strong> - supports adding and deleting an image from your favorites.</li>
</ul>
<p>To support these services, 4 more data models will be added:</p>
<ul>
<li><strong>CatImage</strong> - represents the results returned from the <strong>GetByBreed</strong> operation of the <strong>ImageApi</strong>.</li>
<li><strong>Favorite</strong> - represents the results returned from the <strong>Get</strong> and <strong>GetAll</strong> operations of the <strong>FavoritesApi</strong>.</li>
<li><strong>FavoriteImage</strong> - represents the image returned as part of a <strong>Favorite</strong> from the <strong>FavoritesApi</strong>.</li>
<li><strong>Response</strong> - represents a general result returned by the <strong>Add</strong> and <strong>Delete</strong> operations of the <strong>FavoritesApi</strong>.</li>
</ul>
<p>You will start by adding the data models.</p>
<ol>
<li>
<p>To create a simple model that represents the cat image search results, right-click the <strong>DataModels</strong> folder, select <strong>Add</strong> and click <strong>Class...</strong></p>
</li>
<li>
<p>On the <strong>Add New Item</strong> dialog, in the <strong>Name</strong> field, enter <strong>CatImage.cs</strong></p>
</li>
<li>
<p>In the editor, replace the content of the <strong>CatImage.cs</strong> class with the following:</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.Text.Json.Serialization</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.Models.DataModels</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">CatImage</span>
<span class="p">{</span>
<span class="p">[</span><span class="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"id"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Id</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"height"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Height</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"url"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Url</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"width"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Width</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>
</li>
<li>
<p>Repeat the above and add the <strong>Favorite.cs</strong> class, replacing the content with the following:</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.Text.Json.Serialization</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.Models.DataModels</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">Favorite</span>
<span class="p">{</span>
<span class="p">[</span><span class="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"created_at"</span><span class="p">)]</span>
<span class="k">public</span> <span class="n">DateTime</span> <span class="n">CreatedAt</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"id"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Id</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"image"</span><span class="p">)]</span>
<span class="k">public</span> <span class="n">FavoriteImage</span> <span class="n">Image</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"image_id"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">ImageId</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"sub_id"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">SubId</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"user_id"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">UserId</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>
</li>
<li>
<p>Repeat the above and add the <strong>FavoriteImage.cs</strong> class, replacing the content with the following:</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.Text.Json.Serialization</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.Models.DataModels</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">FavoriteImage</span>
<span class="p">{</span>
<span class="p">[</span><span class="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"id"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Id</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"url"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Url</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>
</li>
<li>
<p>Repeat the above and add the <strong>Response.cs</strong> class, replacing the content with the following:</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.Text.Json.Serialization</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.Models.DataModels</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">Response</span>
<span class="p">{</span>
<span class="p">[</span><span class="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"id"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Id</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="nf">JsonPropertyName</span><span class="p">(</span><span class="s">"message"</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Message</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>
</li>
<li>
<p>To extend the <strong>WebApiBase</strong> class, open the <strong>WebApiBase.cs</strong> file in the editor.</p>
</li>
<li>
<p>To add support for the delete operation, locate the comment <strong>// Insert DeleteAsync method below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert DeleteAsync method below here</span>
<span class="k">protected</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="nf">DeleteAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="n">headers</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="nf">CreateRequestMessage</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Delete</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="p">))</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_client</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The code is very similar to the <strong>GetAsync</strong> method added earlier, just using <code>HttpMethod.Delete</code>.</p>
</li>
<li>
<p>To add support for the post operation, locate the comment <strong>// Insert PostAsync method below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert PostAsync method below here</span>
<span class="k">protected</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="nf">PostAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="kt">string</span> <span class="n">payload</span><span class="p">,</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="n">headers</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="nf">CreateRequestMessage</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Post</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">request</span><span class="p">.</span><span class="n">Content</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StringContent</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">);</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_client</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The change for this method is the use of <code>HttpMethod.Post</code> and the addition of the <strong>payload</strong> parameter, which is added to the request as <strong>StringContent</strong>. Callers of this method will submit JSON as the payload - notice the encoding and MIME type specification.</p>
</li>
<li>
<p>To add support for the put operation, locate the comment <strong>// Insert PutAsync method below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert PostAsync method below here</span>
<span class="k">protected</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="nf">PostAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">url</span><span class="p">,</span> <span class="kt">string</span> <span class="n">payload</span><span class="p">,</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="n">headers</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">request</span> <span class="p">=</span> <span class="nf">CreateRequestMessage</span><span class="p">(</span><span class="n">HttpMethod</span><span class="p">.</span><span class="n">Post</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">request</span><span class="p">.</span><span class="n">Content</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StringContent</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">);</span>
<span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_client</span><span class="p">.</span><span class="nf">SendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Put is very similar to post. It is included here for completeness although we don't use it in this tutorial.</p>
<p>Now you will add the additional services. The first service you will add is the <strong>ImageApi</strong>. If you recall, if the user clicks on a breed, this service will be used to retrieve a list of images of that breed.</p>
</li>
<li>
<p>To add a class for the Favorites API service, in the <strong>TheCatApiClient.Shared</strong> project, right-click the <strong>WebServices</strong> folder, select <strong>Add</strong> and click <strong>Class...</strong></p>
</li>
<li>
<p>On the <strong>Add New Item</strong> dialog, in the <strong>Name</strong> field, enter <strong>FavoritesApi.cs</strong></p>
</li>
<li>
<p>In the editor, replace the content of the <strong>FavoritesApi.cs</strong> class with the following:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Net</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Text.Json</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">TheCatApiClient.Shared.Models.DataModels</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.WebServices</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ImageApi</span> <span class="p">:</span> <span class="n">WebApiBase</span>
<span class="p">{</span>
<span class="k">private</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="n">_defaultHeaders</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="p">{</span>
<span class="p">{</span><span class="s">"accept"</span><span class="p">,</span> <span class="s">"application/json"</span> <span class="p">},</span>
<span class="p">{</span><span class="s">"x-api-key"</span><span class="p">,</span> <span class="s">"{YOUR-API-KEY}"</span><span class="p">}</span>
<span class="p">};</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">CatImage</span><span class="p">>></span> <span class="nf">GetByBreed</span><span class="p">(</span><span class="kt">string</span> <span class="n">breedId</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/images/search?breed_id=</span><span class="p">{</span><span class="n">WebUtility</span><span class="p">.</span><span class="nf">HtmlEncode</span><span class="p">(</span><span class="n">breedId</span><span class="p">)}</span><span class="s">"</span><span class="p">,</span>
<span class="n">_defaultHeaders</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">CatImage</span><span class="p">>>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">CatImage</span><span class="p">>();</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">CatImage</span><span class="p">></span> <span class="nf">GetById</span><span class="p">(</span><span class="kt">string</span> <span class="n">imageId</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/images/</span><span class="p">{</span><span class="n">WebUtility</span><span class="p">.</span><span class="nf">HtmlEncode</span><span class="p">(</span><span class="n">imageId</span><span class="p">)}</span><span class="s">"</span><span class="p">,</span>
<span class="n">_defaultHeaders</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">CatImage</span><span class="p">>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Notice that the default headers including your API key have been moved to <strong>_defaultHeaders</strong> to ease reuse.</p>
<p>This service implements two methods - <strong>GetByBreed</strong> which returns back all images for a breed and <strong>GetById</strong>, which returns a specific image. They each utilize the <strong>WebApiBase.GetAsync</strong> method you used earlier.</p>
<blockquote>
<p><strong>TIP</strong>:
You can review the images service documentation here - <a href="https://docs.thecatapi.com/api-reference/images/">/images</a></p>
</blockquote>
</li>
<li>
<p>To add a class for the Favorites API service, in the <strong>TheCatApiClient.Shared</strong> project, right-click the <strong>WebServices</strong> folder, select <strong>Add</strong> and click <strong>Class...</strong></p>
</li>
<li>
<p>On the <strong>Add New Item</strong> dialog, in the <strong>Name</strong> field, enter <strong>FavoritesApi.cs</strong></p>
</li>
<li>
<p>In the editor, replace the content of the <strong>FavoritesApi.cs</strong> class with the following:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Text.Json</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">TheCatApiClient.Shared.Models.DataModels</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.WebServices</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">FavoritesApi</span> <span class="p">:</span> <span class="n">WebApiBase</span>
<span class="p">{</span>
<span class="k">private</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="n">_defaultHeaders</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="p">{</span>
<span class="p">{</span><span class="s">"accept"</span><span class="p">,</span> <span class="s">"application/json"</span> <span class="p">},</span>
<span class="p">{</span><span class="s">"x-api-key"</span><span class="p">,</span> <span class="s">"{YOUR-API-KEY}"</span><span class="p">}</span>
<span class="p">};</span>
<span class="c1">// Insert GetAll and Get below here</span>
<span class="c1">// Insert Add below here</span>
<span class="c1">// Insert Delete below here</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
</li>
<li>
<p>To add support for retrieving all of your favorites, or a specific favorite, locate the comment <strong>// Insert GetAll and Get below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert GetAll and Get below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">Favorite</span><span class="p">>></span> <span class="nf">GetAll</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/favourites"</span><span class="p">,</span>
<span class="n">_defaultHeaders</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">Favorite</span><span class="p">>>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">Favorite</span><span class="p">>();</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">Favorite</span><span class="p">></span> <span class="nf">Get</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/favourites/</span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
<span class="n">_defaultHeaders</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">Favorite</span><span class="p">>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>These methods are very similar to the methods you have implemented earlier - <strong>GetAll</strong> returns a collection of the users <strong>Favorite</strong> instances, whereas <strong>Get</strong> returns a single <strong>Favorite</strong> by its ID.</p>
<blockquote>
<p><strong>TIP</strong>:
You can review the favorites service documentation here - <a href="https://docs.thecatapi.com/api-reference/favourites/">/favorites</a></p>
</blockquote>
</li>
<li>
<p>To add support for adding an image to your favorites, locate the comment <strong>// Insert Add below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert Add below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">Response</span><span class="p">></span> <span class="nf">Add</span><span class="p">(</span><span class="n">CatImage</span> <span class="n">image</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">PostAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/favourites"</span><span class="p">,</span>
<span class="n">JsonSerializer</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">(</span>
<span class="k">new</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span>
<span class="p">{</span>
<span class="p">{</span> <span class="s">"image_id"</span><span class="p">,</span> <span class="n">image</span><span class="p">.</span><span class="n">Id</span> <span class="p">},</span>
<span class="p">{</span> <span class="s">"sub_id"</span><span class="p">,</span> <span class="s">"uno-client"</span> <span class="p">}</span>
<span class="p">}),</span>
<span class="n">_defaultHeaders</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">Response</span><span class="p">>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In order to add an image to your favorites, the API expects a JSON payload that contains the image ID and an optional <strong>sub_id</strong> value. The <strong>Add</strong> methods constructs this using a <strong>Dictionary</strong> that is then serialized to JSON and submitted as the payload. Rather than returning a <strong>Favorite</strong>, it returns a <strong>Response</strong> instance which contains the new Favorite ID.</p>
</li>
<li>
<p>To add support for adding an image to your favorites, locate the comment <strong>// Insert Add below here</strong> and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert Add below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">Response</span><span class="p">></span> <span class="nf">Add</span><span class="p">(</span><span class="n">CatImage</span> <span class="n">image</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">PostAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/favourites"</span><span class="p">,</span>
<span class="n">JsonSerializer</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">(</span>
<span class="k">new</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span>
<span class="p">{</span>
<span class="p">{</span> <span class="s">"image_id"</span><span class="p">,</span> <span class="n">image</span><span class="p">.</span><span class="n">Id</span> <span class="p">},</span>
<span class="p">{</span> <span class="s">"sub_id"</span><span class="p">,</span> <span class="s">"uno-client"</span> <span class="p">}</span>
<span class="p">}),</span>
<span class="n">_defaultHeaders</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">Response</span><span class="p">>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In order to add an image to your favorites, the api expects a JSON payload that contains the image ID and an optional <strong>sub_id</strong> value. The <strong>Add</strong> methods constructs this using a <strong>Dictionary</strong> that is then serialized to JSON and submitted as the payload. Rather than returning a <strong>Favorite</strong>, it returns a <strong>Response</strong> instance which contains the new Favorite ID.</p>
</li>
<li>
<p>The implementation of <strong>FavoritesApi</strong> should look similar to:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Text.Json</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">TheCatApiClient.Shared.Models.DataModels</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">TheCatApiClient.Shared.WebServices</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">FavoritesApi</span> <span class="p">:</span> <span class="n">WebApiBase</span>
<span class="p">{</span>
<span class="k">private</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="n">_defaultHeaders</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="p">{</span>
<span class="p">{</span><span class="s">"accept"</span><span class="p">,</span> <span class="s">"application/json"</span> <span class="p">},</span>
<span class="p">{</span><span class="s">"x-api-key"</span><span class="p">,</span> <span class="s">"{YOUR-API-KEY}"</span><span class="p">}</span>
<span class="p">};</span>
<span class="c1">// Insert GetAll and Get below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">Favorite</span><span class="p">>></span> <span class="nf">GetAll</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/favourites"</span><span class="p">,</span>
<span class="n">_defaultHeaders</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">IEnumerable</span><span class="p"><</span><span class="n">Favorite</span><span class="p">>>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">Favorite</span><span class="p">>();</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">Favorite</span><span class="p">></span> <span class="nf">Get</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/favourites/</span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
<span class="n">_defaultHeaders</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">Favorite</span><span class="p">>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Insert Add below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">Response</span><span class="p">></span> <span class="nf">Add</span><span class="p">(</span><span class="n">CatImage</span> <span class="n">image</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">PostAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/favourites"</span><span class="p">,</span>
<span class="n">JsonSerializer</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">(</span><span class="k">new</span> <span class="n">Dictionary</span><span class="p"><</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">></span> <span class="p">{</span> <span class="p">{</span> <span class="s">"image_id"</span><span class="p">,</span> <span class="n">image</span><span class="p">.</span><span class="n">Id</span> <span class="p">},</span> <span class="p">{</span> <span class="s">"sub_id"</span><span class="p">,</span> <span class="s">"uno-client"</span> <span class="p">}</span> <span class="p">}),</span>
<span class="n">_defaultHeaders</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">Response</span><span class="p">>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Insert Delete below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">Response</span><span class="p">></span> <span class="nf">Delete</span><span class="p">(</span><span class="n">Favorite</span> <span class="n">favorite</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">DeleteAsync</span><span class="p">(</span>
<span class="s">$"https://api.thecatapi.com/v1/favourites/</span><span class="p">{</span><span class="n">favorite</span><span class="p">.</span><span class="n">Id</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
<span class="n">_defaultHeaders</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Deserialize</span><span class="p"><</span><span class="n">Response</span><span class="p">>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
</li>
</ol>
<p>Now that the services are complete, it is time to update the UI.</p>
<h2 id="task-9---add-favorites-to-the-ui-view-model">Task 9 - Add favorites to the UI view model</h2>
<p>In this task you will add the favorites capability to the Main view-model.</p>
<ol>
<li>
<p>Open the <strong>MainViewModel.cs</strong> file in the editor.</p>
</li>
<li>
<p>To add the variables, property and method placeholders for the favorites implementation, locate the <strong>// Insert Favorites below here</strong> comment and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert Favorites below here</span>
<span class="k">private</span> <span class="n">ImageApi</span> <span class="n">_imageApi</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ImageApi</span><span class="p">();</span>
<span class="k">private</span> <span class="n">FavoritesApi</span> <span class="n">_favoritesApi</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">FavoritesApi</span><span class="p">();</span>
<span class="k">private</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Favorite</span><span class="p">></span> <span class="n">_favorites</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Favorite</span><span class="p">>();</span>
<span class="k">public</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Favorite</span><span class="p">></span> <span class="n">Favorites</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">=></span> <span class="n">_favorites</span><span class="p">;</span>
<span class="k">set</span> <span class="p">=></span> <span class="nf">SetProperty</span><span class="p">(</span><span class="k">ref</span> <span class="n">_favorites</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Insert LoadFavorites below here</span>
<span class="c1">// Insert AddFavorite below here</span>
<span class="c1">// Insert DeleteFavorite below here</span>
</code></pre></div></div>
<p>This code creates the API services and the collection that will hold the user's favorites.</p>
</li>
<li>
<p>To add the method that retrieves the current user favorites, locate the <strong>// Insert LoadFavorites below here</strong> comment and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert LoadFavorites below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">LoadFavorites</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_favoritesApi</span><span class="p">.</span><span class="nf">GetAll</span><span class="p">().</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="n">Favorites</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">Favorite</span><span class="p">>(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">finally</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This method is virtually identical to the <strong>SearchBreeds</strong> method that you created earlier.</p>
</li>
<li>
<p>To implement support for adding an image to favorites, locate the <strong>// Insert AddFavorite below here</strong> comment and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert AddFavorite below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">AddFavorite</span><span class="p">(</span><span class="n">Breed</span> <span class="n">selectedBreed</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">selectedBreed</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_imageApi</span><span class="p">.</span><span class="nf">GetByBreed</span><span class="p">(</span><span class="n">selectedBreed</span><span class="p">.</span><span class="n">Id</span><span class="p">).</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">image</span> <span class="p">=</span> <span class="n">result</span><span class="p">.</span><span class="nf">First</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_favoritesApi</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">image</span><span class="p">).</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">response</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">&&</span> <span class="n">response</span><span class="p">.</span><span class="n">Message</span> <span class="p">==</span> <span class="s">"SUCCESS"</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">favoriteResult</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_favoritesApi</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">Id</span><span class="p">).</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">favoriteResult</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">await</span> <span class="nf">DispatchAsync</span><span class="p">(()</span> <span class="p">=></span> <span class="n">Favorites</span><span class="p">.</span><span class="nf">Insert</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">favoriteResult</span><span class="p">)).</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">finally</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>There is a bit more going on in this method, but it follows a familiar pattern. If we have a <strong>selectedBreed</strong>, we set the <strong>IsBusy</strong> property and then use the <strong>ImageApi</strong> to retrieve a collection of images for the breed. If we find any (some breeds don't have images... you would show a message in a production app), we just grab the first image that is returned. We then call the <strong>FavoritesApi</strong> to add the image. We then check the <strong>response</strong> value and, if successful, use the ID that was returned via the <strong>response</strong> to retrieve the new <strong>Favorite</strong>. We then insert the favorite at the beginning of our <strong>Favorites</strong> collection.</p>
<p>You should notice the use of the <code>.ConfigureAwait(false)</code> code throughout to ensure the UI is not blocked. You will also notice the use of the <strong>DispatchAsync</strong> helper to ensure the <strong>CollectionChanged</strong> event raised by the insertion is raised on the UI thread so any bound control updates correctly.</p>
</li>
<li>
<p>Finally, to add support for deleting a favorite, locate the <strong>// Insert AddFavorite below here</strong> comment and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Insert DeleteFavorite below here</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">DeleteFavorite</span><span class="p">(</span><span class="n">Favorite</span> <span class="n">selectedFavorite</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">selectedFavorite</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_favoritesApi</span><span class="p">.</span><span class="nf">Delete</span><span class="p">(</span><span class="n">selectedFavorite</span><span class="p">).</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">Message</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">await</span> <span class="nf">DispatchAsync</span><span class="p">(()</span> <span class="p">=></span> <span class="n">Favorites</span><span class="p">.</span><span class="nf">Remove</span><span class="p">(</span><span class="n">selectedFavorite</span><span class="p">)).</span><span class="nf">ConfigureAwait</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">finally</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Similar to the flow of the <strong>AddFavorite</strong> method, this implementation calls the delete operation, and then this time removes the <strong>selectedFavorite</strong> from thee <strong>Favorites</strong> collection.</p>
</li>
</ol>
<p>You have now updated the view-model - the final task will have you update the UI to include the favorites.</p>
<h2 id="task-10---add-favorites-to-the-main-view">Task 10 - Add favorites to the main view</h2>
<p>In this final task you will bring together the new <strong>FavoritesApi</strong> with the updated <strong>MainViewModel</strong> to add a list of favorites to the UI.</p>
<ol>
<li>
<p>In the <strong>Solution Explorer</strong>, in the <strong>TheCatApiClient.Shared</strong> project, open the <strong>MainPage.xaml</strong> file.</p>
</li>
<li>
<p>If the <strong>MainPage.xaml</strong> file has opened in the <strong>Design</strong> view, switch to the <strong>XAML</strong> view.</p>
</li>
<li>
<p>To add support for clicking a breed in the search results, locate the <code><!-- ROW 2 - Search Results --></code> comment and replace all of the row 2 XAML with the following:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- ROW 2 - Search Results --></span>
<span class="nt"><GridView</span> <span class="na">Grid.Row=</span><span class="s">"2"</span> <span class="na">ItemsSource=</span><span class="s">"{x:Bind ViewModel.SearchResults, Mode=OneWay}"</span> <span class="na">Margin=</span><span class="s">"4"</span>
<span class="na">SelectionMode=</span><span class="s">"None"</span>
<span class="na">IsItemClickEnabled=</span><span class="s">"True"</span>
<span class="na">ItemClick=</span><span class="s">"SearchResults_ItemClick"</span><span class="nt">></span>
<span class="nt"><GridView.ItemTemplate></span>
<span class="nt"><DataTemplate</span> <span class="na">x:DataType=</span><span class="s">"datamodels:Breed"</span><span class="nt">></span>
<span class="nt"><StackPanel</span> <span class="na">Width=</span><span class="s">"300"</span> <span class="na">Padding=</span><span class="s">"4"</span><span class="nt">></span>
<span class="nt"><TextBlock</span> <span class="na">Text=</span><span class="s">"{x:Bind Name}"</span> <span class="na">Style=</span><span class="s">"{StaticResource TitleTextBlockStyle}"</span><span class="nt">/></span>
<span class="nt"><TextBlock</span> <span class="na">Text=</span><span class="s">"{x:Bind Description}"</span> <span class="na">Style=</span><span class="s">"{StaticResource BodyTextBlockStyle}"</span> <span class="na">TextWrapping=</span><span class="s">"WrapWholeWords"</span>
<span class="na">Height=</span><span class="s">"70"</span> <span class="na">TextTrimming=</span><span class="s">"CharacterEllipsis"</span><span class="nt">/></span>
<span class="nt"></StackPanel></span>
<span class="nt"></DataTemplate></span>
<span class="nt"></GridView.ItemTemplate></span>
<span class="nt"></GridView></span>
</code></pre></div></div>
<p>Notice that two attributes have been added to the <strong>GridView</strong> - <strong>IsItemClickEnabled</strong> configures the <strong>GridView</strong> to raise ItemClick events (by default this is disabled) and then the <strong>ItemClick</strong> event is directed to the code-behind. You will add the code-behind implementation shortly.</p>
</li>
<li>
<p>To add the favorites list, locate the <code><!-- ROW 4 - Favorites --></code> comment and replace it with the following XAML:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- ROW 4 - Favorites --></span>
<span class="nt"><GridView</span> <span class="na">Grid.Row=</span><span class="s">"4"</span> <span class="na">ItemsSource=</span><span class="s">"{x:Bind ViewModel.Favorites, Mode=OneWay}"</span> <span class="na">Margin=</span><span class="s">"4"</span>
<span class="na">SelectionMode=</span><span class="s">"None"</span>
<span class="na">Name=</span><span class="s">"Favorites"</span>
<span class="na">IsItemClickEnabled=</span><span class="s">"True"</span>
<span class="na">ItemClick=</span><span class="s">"Favorites_ItemClick"</span><span class="nt">></span>
<span class="nt"><GridView.ItemTemplate></span>
<span class="nt"><DataTemplate</span> <span class="na">x:DataType=</span><span class="s">"datamodels:Favorite"</span><span class="nt">></span>
<span class="nt"><Grid</span> <span class="na">Padding=</span><span class="s">"4"</span> <span class="na">Height=</span><span class="s">"100"</span> <span class="na">Width=</span><span class="s">"100"</span> <span class="na">Background=</span><span class="s">"#EEEEEE"</span><span class="nt">></span>
<span class="nt"><Image</span> <span class="na">Source=</span><span class="s">"{x:Bind Image.Url}"</span> <span class="na">Stretch=</span><span class="s">"Uniform"</span><span class="nt">/></span>
<span class="nt"></Grid></span>
<span class="nt"></DataTemplate></span>
<span class="nt"></GridView.ItemTemplate></span>
<span class="nt"></GridView></span>
</code></pre></div></div>
<p>This <strong>GridView</strong> is configured in a similar way to the search results - item click is enabled with a code-behind handler, etc. The primary differences are that <strong>ItemsSource</strong> is bound to the <strong>ViewModel.Favorites</strong> property, the data template is bound to the <strong>Favorite</strong> data model and the template displays an image.</p>
<p>That's it for the XAML. Now to update the code-behind.</p>
</li>
<li>
<p>To add the code-behind functionality for favorites, switch to the code view by either opening <strong>MainPage.xaml.cs</strong> or by pressing the default keyboard shortcut <strong>F7</strong>.</p>
</li>
<li>
<p>Locate the <strong>// insert favorites below here</strong> comment and and replace it with the following code:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// insert favorites below here</span>
<span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">Favorites_ItemClick</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">ItemClickEventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">await</span> <span class="n">ViewModel</span><span class="p">.</span><span class="nf">DeleteFavorite</span><span class="p">((</span><span class="n">Favorite</span><span class="p">)</span><span class="n">e</span><span class="p">.</span><span class="n">ClickedItem</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">async</span> <span class="k">void</span> <span class="nf">SearchResults_ItemClick</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">ItemClickEventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">await</span> <span class="n">ViewModel</span><span class="p">.</span><span class="nf">AddFavorite</span><span class="p">((</span><span class="n">Breed</span><span class="p">)</span><span class="n">e</span><span class="p">.</span><span class="n">ClickedItem</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>These methods are very straightforward - they merely pass on the clicked item to the view-model.</p>
</li>
<li>
<p>You have now completed the addition of favorites to the client. In the <strong>Solution Explorer</strong>, right-click the <strong>TheCatApiClient.UWP</strong> project and select <strong>Set as Startup project</strong>.</p>
</li>
<li>
<p>To run the UWP application, press <strong>F5</strong>.</p>
</li>
<li>
<p>In the application, in the <strong>Search for breed</strong> search box, enter <strong>S</strong> and click the search icon.</p>
<p>The <strong>Downloading data...</strong> UI should be shown and then the results should be populated. You should be able to scroll the results and select one (nothing will happen yet).</p>
<p><img src="https://darenm.github.io/assets/uwp-ui-preview.png" alt="UWP Search UI preview" /></p>
</li>
<li>
<p>In the search results, click <strong>Abyssinian</strong> and you should see an image of a cat added to the <strong>Favorites</strong> list.</p>
<p><img src="https://darenm.github.io/assets/client-with-favorite.png" alt="UWP Search UI preview" /></p>
</li>
<li>
<p>Repeat the above, adding images for other breeds.</p>
</li>
<li>
<p>In the <strong>Favorites</strong> list, click an image and you should see it be removed.</p>
</li>
<li>
<p>Ensure you have one or more images remaining in your <strong>Favorites</strong> list and exit the UWP application.</p>
</li>
<li>
<p>To test the app in Wasm, in the <strong>Solution Explorer</strong>, right-click the <strong>TheCatApiClient.Wasm</strong> project and select <strong>Set as Startup project</strong>.</p>
</li>
<li>
<p>To run the application, press <strong>F5</strong>.</p>
<p>Visual Studio will open a browser and display the application. You should see the same favorites you had listed in the UWP app.</p>
</li>
<li>
<p>Search for additional breeds and add and remove favorites - you should see the same functionality as the UWP app.</p>
</li>
<li>
<p>Try the app on the other platforms. Below is an image of app running on the iPhone and Android simulators.</p>
<p><img src="https://darenm.github.io/assets/favorites-on-phone.png" alt="app running on iphone and android simulators" /></p>
</li>
</ol>
<h2 id="summary">Summary</h2>
<p>In this how-to, you built a multi-platform application that leverages web services. Specifically you:</p>
<ul>
<li>Created a simple Uno application</li>
<li>Registered and obtained a key for The Cat API web service.</li>
<li>Built a number of data models that utilize JSON serialization</li>
<li>Built a web service base class that supports REST service operations and then implemented a number of services that derived from it
<ul>
<li>You will have noted that WASM requires the use of <code>Uno.UI.Wasm.WasmHttpHandler()</code> rather than the default <code>HttpClientHandler</code></li>
</ul>
</li>
<li>Leveraged the services in a view-model
<ul>
<li>You built a base class that uses the dispatcher to ensure bound properties are updated on the UI thread</li>
</ul>
</li>
<li>Built a XAML UI that utilizes the view-model</li>
<li>Tested the application in UWP and WASM (and optionally on iOS and Android)</li>
</ul>
<p>The full source code for this tutorial is available here - <a href="https://github.com/unoplatform/Uno.Samples/tree/master/UI/TheCatApiClient">Tutorial Source Code - TheCatApiClient</a></p>
<h2 id="references">References</h2>
<ul>
<li>Uno Platform:
<ul>
<li><a href="https://platform.uno/docs/articles/get-started-vs.html">Using Uno - getting started on Visual Studio</a></li>
</ul>
</li>
<li>Uno Platform Samples:
<ul>
<li><a href="https://github.com/unoplatform/Uno.Samples">unoplatform/Uno.Samples</a></li>
<li><a href="https://github.com/unoplatform/Uno.Samples/tree/master/UI/TheCatApiClient">Tutorial Source Code - TheCatApiClient</a></li>
</ul>
</li>
<li>Microsoft Documentation
<ul>
<li><a href="https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.1">HttpClient class</a></li>
<li><a href="https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/console-webapiclient">REST Client tutorial</a></li>
<li><a href="https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to">How to serialize and deserialize (marshal and unmarshal) JSON in .NET</a></li>
<li><a href="https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.1#remarks">HttpClient Remarks</a></li>
<li><a href="https://josef.codes/httpclient-error-handling-a-test-driven-approach/">HttpClient - Error handling, a test driven approach</a></li>
<li><a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/mvvm">The Model-View-ViewModel Pattern</a></li>
<li><a href="https://docs.microsoft.com/en-us/windows/uwp/debug-test-perf/keep-the-ui-thread-responsive">Keep the UI thread responsive</a></li>
<li><a href="https://docs.microsoft.com/dotnet/csharp/language-reference/attributes/caller-information">Reserved attributes: Determine caller information</a></li>
<li><a href="https://docs.microsoft.com/dotnet/csharp/language-reference/operators/lambda-expressions">Lambda expressions</a>
*<a href="https://docs.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.observablecollection-1?view=netcore-3.1">ObservableCollection<T> Class</a></li>
</ul>
</li>
<li>The Cat API Documentation
<ul>
<li><a href="https://thecatapi.com/signup">Key Signup</a></li>
<li><a href="https://docs.thecatapi.com/">API documentation</a></li>
<li><a href="https://docs.thecatapi.com/api-reference/breeds/breeds-search">GET /breeds/search</a></li>
<li><a href="https://docs.thecatapi.com/api-reference/images/">/images</a></li>
<li><a href="https://docs.thecatapi.com/api-reference/favourites/">/favorites</a></li>
</ul>
</li>
</ul>Daren MayThis how-to will walk you through the process of creating a multi-platform application that leverages web services. Specifically you will be:Customizing the Visual Studio Code Integrated Terminal2020-06-23T05:00:00+00:002020-06-23T05:00:00+00:00https://darenm.github.io/vscode/%22visual/studio/code%22/2020/06/23/CustomizingVsCode<p>I continually forget how to customize the integrated terminal in VSCode, so I will capture it here for posterity.</p>
<h2 id="configure-default-powershell-version">Configure default PowerShell version</h2>
<p>Before configuring Shell Launcher, I want to set PowerShell Core as the default PowerShell version. To do so, open up the VS Code user settings.json file by clicking on <strong>file > preferences > settings, select ...</strong> and then <strong>Open settings.json</strong>.</p>
<p>Modify the settings.json file to include terminal.integrated.shell.windows. The update must be well formed json. If you have other settings in your settings.json file, you may need to adjust the following example.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"terminal.integrated.shell.windows"</span><span class="p">:</span><span class="w"> </span><span class="s2">"c:/Program Files/PowerShell/7/pwsh.exe"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h2 id="change-terminal-colors">Change Terminal Colors</h2>
<p>Open user settings, switch to JSON and add something similar to:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.colorCustomizations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"terminal.background"</span><span class="p">:</span><span class="s2">"#FEFBEC"</span><span class="p">,</span><span class="w">
</span><span class="nl">"terminal.foreground"</span><span class="p">:</span><span class="s2">"#6E6B5E"</span><span class="p">,</span><span class="w">
</span><span class="err">...</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Check <a href="https://glitchbone.github.io/vscode-base16-term">https://glitchbone.github.io/vscode-base16-term</a> for some presets.</p>Daren MayI continually forget how to customize the integrated terminal in VSCode, so I will capture it here for posterity.Convert HTML to Markdown2020-06-23T05:00:00+00:002020-06-23T05:00:00+00:00https://darenm.github.io/markdown/html/2020/06/23/convert-html-to-markdown<p>I came across a sweet tool for converting HTML pages to Markdown - it's not perfect, but is a good start:</p>
<p><a href="http://heckyesmarkdown.com/">HeckYesMarkdown</a></p>Daren MayI came across a sweet tool for converting HTML pages to Markdown - it's not perfect, but is a good start:CustomMayd Training Videos2020-06-23T05:00:00+00:002020-06-23T05:00:00+00:00https://darenm.github.io/custommayd/developer/training/videos/2020/06/23/custommayd-training-videos<p>I have consolidated many of the training videos I have either created or participated in over the years on the <a href="https://www.youtube.com/channel/UC-Pl7BhRPcInqv-V1_O3bSA/playlists">CustomMayd YouTube Channel</a>.</p>
<p><img src="https://darenm.github.io/assets/2020-06-23-youtube-channel.png" alt="CustomMayd YouTube Channel" /></p>
<p>They have been grouped into various playlists:</p>
<ul>
<li>The ever popular <a href="https://www.youtube.com/playlist?list=PLYnF3WBIsGTrcWyVjzwMYSAQsLTVyhKlR">Programming in C#</a> series</li>
<li><a href="https://www.youtube.com/playlist?list=PLYnF3WBIsGTpSoCNrhIdSQ80wiTtM8yII">XAML for Windows 10</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLYnF3WBIsGTr8QAR-S0lrQJk22ot0jK59">Template 10</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLYnF3WBIsGTos7wAKl8XKToClHv5SQ8JV">UWP</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLYnF3WBIsGTrzwox6iNDQbRYYWFtPy501">UWP on XBox</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLYnF3WBIsGTraP3P5LnMdTQ5vukg-5FsT">Windows 10 Composition</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLYnF3WBIsGTrYhJ8lghNwmcCBX0dloK3J">Various Conference Sessions</a></li>
</ul>Daren MayI have consolidated many of the training videos I have either created or participated in over the years on the CustomMayd YouTube Channel.How to Embed YouTube Videos in Markdown2020-06-23T05:00:00+00:002020-06-23T05:00:00+00:00https://darenm.github.io/custommayd/developer/training/videos/markdown/2020/06/23/embed-youtube-video-in-markdown<p>I can never remember the best way to embed a video in Markdown, so here, for posterity is the information.</p>
<p>If we combine the markup for an embedded image and a link, we can insert an image with a link:</p>
<pre><code>[![alt text](http://darenmay.com/headshot.png)](http://darenmay.com/post "post title")
</code></pre>
<p>Taking this approach, we can create clickable images that navigate to a YouTube video:</p>
<pre><code>[![Blinking LEDs](http://img.youtube.com/vi/XAMVzS13HY0/0.jpg)](http://www.youtube.com/watch?v=XAMVzS13HY0 "Blinking LEDs")
</code></pre>
<p>Which results in:</p>
<p><a href="http://www.youtube.com/watch?v=XAMVzS13HY0" title="Blinking LEDs"><img src="http://img.youtube.com/vi/XAMVzS13HY0/0.jpg" alt="Blinking LEDs" /></a></p>
<p>There is a great app that does this for you: <a href="http://embedyoutube.org/">http://embedyoutube.org/</a></p>Daren MayI can never remember the best way to embed a video in Markdown, so here, for posterity is the information..NET Core and GPIO on the Raspberry PI - LEDs and GPIO2019-10-16T05:00:00+00:002019-10-16T05:00:00+00:00https://darenm.github.io/iot/.netcore/c%23/raspberrypi/2019/10/16/net-core-and-gpio-on-the-raspberry-pi---leds-and-gpio<p>In the previous article, I explored installing and running .NET Core 3.0 on a Raspberry PI running Raspbian. In this article I intend to create a simple console application that will interact with the GPIO and flash an LED.</p>
<h2 id="what-is-a-gpio">What is a GPIO?</h2>
<p>GPIO stands for General Purpose Input Output. A GPIO pin has no predefined purpose - i.e. they are unused by the main device and can be configured as input or output and can be controlled by an application. The Raspberry Pi has two rows of GPIO pins, as well as others.</p>
<p><img src="https://darenm.github.io/assets/PiGPIO_637068541768653013.png" alt="PI GPIO" /></p>
<p>Output pins are like switches that your app running on the Raspberry Pi can turn on or off - common uses include turning on LEDs, but they can be used for more advanced communication with devices.</p>
<p>Input pins can be turned on and off by external devices such as switches, however they too can be used for more advanced communication with devices, such as receiving information from sensors.</p>
<h2 id="net-core-iot-libraries">.NET Core IoT Libraries</h2>
<p>So how can we interact with the GPIO? Fortunately, there are a set of NuGet packages that do the heavy lifting for us the source is hosted here - <a href="https://github.com/dotnet/iot">https://github.com/dotnet/iot</a>.</p>
<p>To quote the <a href="https://github.com/dotnet/iot">repository</a>:</p>
<blockquote>
<p>.NET Core can be used to build applications for IoT devices and scenarios. IoT applications typically interact with sensors, displays and input devices that require the use of GPIO pins, serial ports or similar hardware.</p>
<p>This repository contains the System.Device.Gpio library and implementations for various boards like Raspberry Pi and Hummingboard.</p>
<p>The repository also contains IoT.Device.Bindings, a growing set of community-maintained device bindings for IoT components.</p>
<p><strong>NOTE</strong>: This repository is still in experimental stage and all APIs are subject to changes.</p>
</blockquote>
<p>This library provides the means to interact with sensors via GPIO, SPI, I2C and PWM. You can checkout the documentation <a href="https://github.com/dotnet/iot/blob/master/Documentation/README.md">here</a>.</p>
<p>For this article, we will focus on plain vanilla GPIO.</p>
<h2 id="create-a-simple-led-flasher">Create a Simple LED Flasher</h2>
<p>Let's create a .NET Core Console app that uses GPIO to flash an LED.</p>
<ol>
<li>
<p>Connect to your PI via a terminal.</p>
</li>
<li>
<p>Navigate to a directory where you want to create your project folder.</p>
</li>
<li>
<p>To create you project folder, enter the following command:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mkdir</span><span class="w"> </span><span class="nx">SimpleFlasher</span><span class="w">
</span></code></pre></div></div>
</li>
<li>
<p>To navigate into the project folder, enter the following command:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cd</span><span class="w"> </span><span class="nx">SimpleFlasher</span><span class="w">
</span></code></pre></div></div>
</li>
<li>
<p>To create the initial console application project, enter the following command:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dotnet</span><span class="w"> </span><span class="nx">new</span><span class="w"> </span><span class="nx">console</span><span class="w">
</span></code></pre></div></div>
<p>After a few moments, you will have a simple hello world app.</p>
</li>
<li>
<p>To test the app, enter the following command:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dotnet</span><span class="w"> </span><span class="nx">run</span><span class="w">
</span></code></pre></div></div>
<p>After a few moments compiling, etc. you will see <strong>Hello World!</strong>.</p>
</li>
<li>
<p>To add the package references we will be using for the GPIO communication, enter the following commands:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dotnet</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="nx">package</span><span class="w"> </span><span class="nx">System.Device.Gpio</span><span class="w"> </span><span class="nt">--source</span><span class="w"> </span><span class="nx">https://dotnetfeed.blob.core.windows.net/dotnet-iot/index.json</span><span class="w">
</span><span class="n">dotnet</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="nx">package</span><span class="w"> </span><span class="nx">Iot.Device.Bindings</span><span class="w"> </span><span class="nt">--source</span><span class="w"> </span><span class="nx">https://dotnetfeed.blob.core.windows.net/dotnet-iot/index.json</span><span class="w">
</span></code></pre></div></div>
<p>The <strong>SimpleFlasher.csproj</strong> file will now look like:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Project</span> <span class="na">Sdk=</span><span class="s">"Microsoft.NET.Sdk"</span><span class="nt">></span>
<span class="nt"><PropertyGroup></span>
<span class="nt"><OutputType></span>Exe<span class="nt"></OutputType></span>
<span class="nt"><TargetFramework></span>netcoreapp3.0<span class="nt"></TargetFramework></span>
<span class="nt"></PropertyGroup></span>
<span class="nt"><ItemGroup></span>
<span class="nt"><PackageReference</span> <span class="na">Include=</span><span class="s">"IoT.Device.Bindings"</span> <span class="na">Version=</span><span class="s">"1.0.0"</span> <span class="nt">/></span>
<span class="nt"><PackageReference</span> <span class="na">Include=</span><span class="s">"System.Device.Gpio"</span> <span class="na">Version=</span><span class="s">"1.0.0"</span> <span class="nt">/></span> <span class="nt"></ItemGroup></span>
<span class="nt"></Project></span>
</code></pre></div></div>
</li>
</ol>
<p>Before we go any further though, let's explore something that will make our development much simpler - <a href="https://code.visualstudio.com/docs/remote/remote-overview">VS Code Remote Development</a>.</p>
<h2 id="vs-code-remote-development">VS Code Remote Development</h2>
<p>To quote the <a href="https://code.visualstudio.com/docs/remote/remote-overview">VS Code Remote Development</a> page:</p>
<blockquote>
<p>Visual Studio Code Remote Development allows you to use a container, remote machine, or the Windows Subsystem for Linux (WSL) as a full-featured development environment. You can:</p>
<ul>
<li>Develop on the same operating system you deploy to or use larger or more specialized hardware.</li>
<li>Sandbox your development environment to avoid impacting your local machine configuration.</li>
<li>Make it easy for new contributors to get started and keep everyone on a consistent environment.</li>
<li>Use tools or runtimes not available on your local OS or manage multiple versions of them.</li>
<li>Develop your Linux-deployed applications using the Windows Subsystem for Linux.</li>
<li>Access an existing development environment from multiple machines or locations.</li>
<li>Debug an application running somewhere else such as a customer site or in the cloud.</li>
</ul>
</blockquote>
<p>We will focus on using the <a href="https://code.visualstudio.com/docs/remote/ssh">Remote - SSH</a> part of the extension to aid in our development.</p>
<ol>
<li>
<p>Open Visual Studio Code and press <strong>CTRL + SHIFT + X</strong> to open the Extensions pane.</p>
</li>
<li>
<p>To install the Remote Development extension pack, enter <strong>Remote Development</strong> and click <strong>Install</strong>.</p>
<blockquote>
<p><strong>Note:</strong> The extension has the extension identifier <code>ms-vscode-remote.vscode-remote-extensionpack</code> so you can be sure you are installing the correct one.</p>
</blockquote>
</li>
<li>
<p>To open the command pane, press <strong>F1</strong> and enter <strong>Remote-SSH: Connect to host</strong></p>
</li>
<li>
<p>When prompted to <strong>Select configured SSH host or enter user@host</strong>, enter <strong>pi@</strong>.</p>
<p>A new Visual Studio Code window will open and you will be prompted to enter the user password... do so.</p>
<p>You will see a notification that informs you the SSH host is being configured.</p>
<p>Once the configuration is complete, Visual Studio Code will be connected to the PI and you can browse the PI filesystem.</p>
</li>
<li>
<p>To open the SimpleFlasher folder, select <strong>File > Open Folder</strong> and browse to the location you created the project folder. Select <strong>SimpleFlasher</strong>.</p>
</li>
<li>
<p>Once you choose the folder, you may be prompted for the password again - enter it.</p>
<p>The explorer window will now display the contents of the folder.</p>
</li>
<li>
<p>Open <strong>Program.cs</strong>.</p>
<p>It will look something like this:</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">namespace</span> <span class="nn">SimpleFlasher</span>
<span class="p">{</span>
<span class="k">class</span> <span class="nc">Program</span>
<span class="p">{</span>
<span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"Hello World!"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
</li>
<li>
<p>Update the <code>Console.WriteLine("Hello World!");</code> line to <code>Console.WriteLine("Hello World - updated from VS Code!");</code> and save the changes.</p>
</li>
<li>
<p>To run the app from within Visual Studio Code, press <strong>CTRL + SHIFT `</strong></p>
<p>You should see a <strong>bash</strong> terminal open connected to your PI with the current directory set to the location you have open in the file explorer.</p>
</li>
<li>
<p>To run your revised app, enter <code>dotnet run</code>.</p>
</li>
</ol>
<p>Success! We now have the convenience of editing our code in Visual Studio Code.</p>
<h2 id="simple-flashing-sample">Simple Flashing Sample</h2>
<p>We'll now update our application to interact with the GPIO bus and flash an LED... I know, global electronic domination follows... BTW, I bought a kit from Freenove via Amazon that has a huge variety of components: <a href="https://www.amazon.com/Freenove-Raspberry-Processing-Tutorials-Components/dp/B06W54L7B5?ref_=ast_sto_dp">Freenove Ultimate Starter Kit for Raspberry Pi 4 B 3 B+, 434 Pages Detailed Tutorials, Python C Java, 223 Items, 57 Projects, Learn Electronics and Programming, Solderless Breadboard</a></p>
<ol>
<li>
<p>In Visual Studio Code, replace the contents of the <strong>Program.cs</strong> file with the following:</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.Device.Gpio</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Threading</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Threading.Tasks</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">SimpleFlasher</span> <span class="p">{</span>
<span class="k">class</span> <span class="nc">Program</span> <span class="p">{</span>
<span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span> <span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// GPIO 17 which is physical pin 11</span>
<span class="kt">int</span> <span class="n">ledPin1</span> <span class="p">=</span> <span class="m">17</span><span class="p">;</span>
<span class="n">GpioController</span> <span class="n">controller</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">GpioController</span> <span class="p">();</span>
<span class="c1">// Sets the pin to output mode so we can switch something on</span>
<span class="n">controller</span><span class="p">.</span><span class="nf">OpenPin</span> <span class="p">(</span><span class="n">ledPin1</span><span class="p">,</span> <span class="n">PinMode</span><span class="p">.</span><span class="n">Output</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">lightTimeInMilliseconds</span> <span class="p">=</span> <span class="m">1000</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">dimTimeInMilliseconds</span> <span class="p">=</span> <span class="m">200</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span> <span class="p">(</span><span class="s">$"LED1 on for </span><span class="p">{</span><span class="n">lightTimeInMilliseconds</span><span class="p">}</span><span class="s">ms"</span><span class="p">);</span>
<span class="c1">// turn on the LED</span>
<span class="n">controller</span><span class="p">.</span><span class="nf">Write</span> <span class="p">(</span><span class="n">ledPin1</span><span class="p">,</span> <span class="n">PinValue</span><span class="p">.</span><span class="n">Low</span><span class="p">);</span>
<span class="n">Thread</span><span class="p">.</span><span class="nf">Sleep</span> <span class="p">(</span><span class="n">lightTimeInMilliseconds</span><span class="p">);</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span> <span class="p">(</span><span class="s">$"LED1 off for </span><span class="p">{</span><span class="n">dimTimeInMilliseconds</span><span class="p">}</span><span class="s">ms"</span><span class="p">);</span>
<span class="c1">// turn off the LED</span>
<span class="n">controller</span><span class="p">.</span><span class="nf">Write</span> <span class="p">(</span><span class="n">ledPin1</span><span class="p">,</span> <span class="n">PinValue</span><span class="p">.</span><span class="n">High</span><span class="p">);</span>
<span class="n">Thread</span><span class="p">.</span><span class="nf">Sleep</span> <span class="p">(</span><span class="n">dimTimeInMilliseconds</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>Due to the polarity of the wiring for the circuit, pulling a pin low (<code>PinValue.Low</code>) turns on the LED.</p>
</li>
<li>
<p>From the above you can see that using <code>GpioController</code> is pretty straightforward:</p>
<ul>
<li>You create an instance of <code>GpioController</code>.</li>
<li>Using the instance, you can open pins and set their modes</li>
<li>You can then write (or read) values from the pin.</li>
</ul>
</li>
<li>
<p>Next, wire up a simple circuit. You will need a breadboard, an LED, a 220 Ohm resistor and some link leads:</p>
<ul>
<li>Connect 3V3 to the positive rail of the breadboard</li>
<li>Connect GPIO 17 (physical pin 11) to the negative lead of the LED</li>
<li>Use the 220 Ohm resistor to connect the positive rail to the positive lead of the LED.</li>
</ul>
<p><img src="https://darenm.github.io/assets/SimpleFlasher_bb_637068541796302725.png" alt="SimpleFlasher_bb" /></p>
</li>
<li>
<p>To test the app, enter the following command:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dotnet</span><span class="w"> </span><span class="nx">run</span><span class="w">
</span></code></pre></div></div>
<p>After a few moments of compilation, the app will run and you should see two things:</p>
<ul>
<li>
<p>The LED flashing</p>
</li>
<li>
<p>A console log:</p>
<pre><code class="language-log">>LED1 on for 1000ms
LED1 off for 200ms
LED1 on for 1000ms
LED1 off for 200ms
LED1 on for 1000ms
LED1 off for 200ms
LED1 on for 1000ms
LED1 off for 200ms
LED1 on for 1000ms
LED1 off for 200ms
LED1 on for 1000ms
</code></pre></li>
</ul>
</li>
<li>
<p>Hit CTRL-C to stop the app.</p>
</li>
</ol>
<h2 id="implementing-a-binary-display">Implementing a Binary Display</h2>
<p>Next we will extend the sample above by adding more LEDs which we will use to display binary numbers. You can either connect individual LEDs, or an LED Bar Graph component (one is included in the kit I linked to above):</p>
<p><img src="https://darenm.github.io/assets/LedBraGraph_637068541809870760.jpg" alt="LedBraGraph" /></p>
<blockquote>
<p><strong>Note:</strong> It is hard to determine which way round to fit these, you may have to rotate it if none of the LEDs come on...</p>
</blockquote>
<ol>
<li>
<p>Update the breadboard wiring as follows:</p>
<p><img src="https://darenm.github.io/assets/LedBinary_bb_637068541827918595.png" alt="LedBinary_bb" /></p>
<p>Note that the resistors supply power to one side of the LEDs, and pulling a pin low causes current to flow, illuminating the LEDs.</p>
<p>Also, I have chosen pins based upon wiring convenience, rather than following an form of numbering convention - to be honest, the pin layout of the GPIO bus on the PI only makes sense based upon the convenience of the manufacturer.</p>
<p><img src="https://darenm.github.io/assets/PiGPIO_637068541768653013.png" alt="PI GPIO" /></p>
<p>Anyway, the pins I used are:</p>
<table>
<thead>
<tr>
<th>Physical Pin</th>
<th>GPIO</th>
<th>Used</th>
<th>Display Bit</th>
</tr>
</thead>
<tbody>
<tr>
<td>12</td>
<td>GPIO 18</td>
<td>X</td>
<td>9</td>
</tr>
<tr>
<td>14</td>
<td>GND</td>
<td></td>
<td></td>
</tr>
<tr>
<td>16</td>
<td>GPIO 23</td>
<td>X</td>
<td>8</td>
</tr>
<tr>
<td>18</td>
<td>GPIO 24</td>
<td>X</td>
<td>7</td>
</tr>
<tr>
<td>20</td>
<td>GND</td>
<td></td>
<td></td>
</tr>
<tr>
<td>22</td>
<td>GPIO 25</td>
<td>X</td>
<td>6</td>
</tr>
<tr>
<td>24</td>
<td>GPIO 8</td>
<td>X</td>
<td>5</td>
</tr>
<tr>
<td>26</td>
<td>GPIO 7</td>
<td>X</td>
<td>4</td>
</tr>
<tr>
<td>28</td>
<td>Reserved</td>
<td></td>
<td></td>
</tr>
<tr>
<td>30</td>
<td>GND</td>
<td></td>
<td></td>
</tr>
<tr>
<td>32</td>
<td>GPIO 12</td>
<td>X</td>
<td>3</td>
</tr>
<tr>
<td>34</td>
<td>GND</td>
<td></td>
<td></td>
</tr>
<tr>
<td>36</td>
<td>GPIO 16</td>
<td>X</td>
<td>2</td>
</tr>
<tr>
<td>38</td>
<td>GPIO 20</td>
<td>X</td>
<td>1</td>
</tr>
<tr>
<td>40</td>
<td>GPIO 21</td>
<td>X</td>
<td>0</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>Once you have completed the circuit, return to Visual Studio Code.</p>
</li>
<li>
<p>Replace the contents of the <strong>Program.cs</strong> file with the following:</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.Device.Gpio</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Threading</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Threading.Tasks</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">SimpleFlasher</span> <span class="p">{</span>
<span class="k">class</span> <span class="nc">Program</span> <span class="p">{</span>
<span class="c1">// a list of all the pins used to display the value</span>
<span class="c1">// pins are listed in the most significant bit order</span>
<span class="c1">// i.e. pin 18 represents the highest value</span>
<span class="k">static</span> <span class="kt">int</span><span class="p">[]</span> <span class="n">_pins</span> <span class="p">=</span> <span class="p">{</span> <span class="m">18</span><span class="p">,</span> <span class="m">23</span><span class="p">,</span> <span class="m">24</span><span class="p">,</span> <span class="m">25</span><span class="p">,</span> <span class="m">8</span><span class="p">,</span> <span class="m">7</span><span class="p">,</span> <span class="m">12</span><span class="p">,</span> <span class="m">16</span><span class="p">,</span> <span class="m">20</span><span class="p">,</span> <span class="m">21</span> <span class="p">};</span>
<span class="k">static</span> <span class="n">GpioController</span> <span class="n">_controller</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">GpioController</span> <span class="p">();</span>
<span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span> <span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// Set all the pins to output mode and</span>
<span class="c1">// ensure all the LEDs are off</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">pin</span> <span class="k">in</span> <span class="n">_pins</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_controller</span><span class="p">.</span><span class="nf">OpenPin</span> <span class="p">(</span><span class="n">pin</span><span class="p">,</span> <span class="n">PinMode</span><span class="p">.</span><span class="n">Output</span><span class="p">);</span>
<span class="n">_controller</span><span class="p">.</span><span class="nf">Write</span> <span class="p">(</span><span class="n">pin</span><span class="p">,</span> <span class="n">PinValue</span><span class="p">.</span><span class="n">High</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// play around with this value.</span>
<span class="c1">// 0 will result in a virtually instant count</span>
<span class="c1">// 10 looks cool</span>
<span class="kt">int</span> <span class="n">countDelay</span> <span class="p">=</span> <span class="m">100</span><span class="p">;</span>
<span class="kt">int</span> <span class="k">value</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="c1">// 1 << 2 is a bit shift operation</span>
<span class="c1">// 1 << 2 == 1 * 2 * 2 == 4</span>
<span class="c1">// so 1 << _pins.Length represents the max number we can display + 1</span>
<span class="k">while</span> <span class="p">(</span><span class="k">value</span> <span class="p"><</span> <span class="p">(</span><span class="m">1</span><span class="p"><<</span><span class="n">_pins</span><span class="p">.</span><span class="n">Length</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="k">value</span><span class="p">);</span>
<span class="nf">DisplayValue</span><span class="p">(</span><span class="k">value</span><span class="p">++);</span>
<span class="n">Thread</span><span class="p">.</span><span class="nf">Sleep</span> <span class="p">(</span><span class="n">countDelay</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">static</span> <span class="k">void</span> <span class="nf">DisplayValue</span><span class="p">(</span><span class="kt">int</span> <span class="k">value</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">currentBit</span> <span class="p">=</span> <span class="n">_pins</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="n">currentBit</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// _pins[10-currentBit] accesses the pin number for the current bit</span>
<span class="c1">// (value & 1< 0 ? PinValue.Low : PinValue.High);</span>
<span class="n">currentBit</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>The code is explained with inline comments.</p>
</li>
<li>
<p>Compile and run the code with the following command:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dotnet</span><span class="w"> </span><span class="nx">run</span><span class="w">
</span></code></pre></div></div>
</li>
</ol>
<p>Once complete and running, the LEDs should display an incrementing value in binary.</p>
<blockquote>
<p><strong>Note</strong>: If the LEDs don't light up, turn them around - it is easy to put them in the wrong way.</p>
</blockquote>
<p><a href="http://www.youtube.com/watch?v=XAMVzS13HY0" title="Blinking LEDs"><img src="http://img.youtube.com/vi/XAMVzS13HY0/0.jpg" alt="Blinking LEDs" /></a>
Click the image to view a video</p>
<p>OK - so now we have worked out how to flash some LEDs by using some pins in output mode. Let's add a switch to our circuit and use it to reset count.</p>
<h2 id="reading-input">Reading Input</h2>
<p>Now we are going to add a switch to the circuit and check to see if the switch is pressed each time we loop.</p>
<ol>
<li>
<p>Add the following variable above the <code>Main</code> method:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The pin we will use to reset the count</span>
<span class="k">static</span> <span class="kt">int</span> <span class="n">_switchPin</span> <span class="p">=</span> <span class="m">17</span><span class="p">;</span>
</code></pre></div></div>
</li>
<li>
<p>Replace the <code>Main</code> method with:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span> <span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// Set all the pins to output mode and</span>
<span class="c1">// ensure all the LEDs are off</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">pin</span> <span class="k">in</span> <span class="n">_pins</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_controller</span><span class="p">.</span><span class="nf">OpenPin</span> <span class="p">(</span><span class="n">pin</span><span class="p">,</span> <span class="n">PinMode</span><span class="p">.</span><span class="n">Output</span><span class="p">);</span>
<span class="n">_controller</span><span class="p">.</span><span class="nf">Write</span> <span class="p">(</span><span class="n">pin</span><span class="p">,</span> <span class="n">PinValue</span><span class="p">.</span><span class="n">High</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Set the pin mode. We are using InputPullDOwn which uses a pulldown resistor to</span>
<span class="c1">// ensure the input reads Low until an external switch sets it high,</span>
<span class="c1">// We could have used InputPullUp if we wanted it to read High until it was pulled down Low.</span>
<span class="c1">// If we just used Input, the value on the pin would wander between High and Low... not very useful in this situation.</span>
<span class="n">_controller</span><span class="p">.</span><span class="nf">OpenPin</span><span class="p">(</span><span class="n">_switchPin</span><span class="p">,</span> <span class="n">PinMode</span><span class="p">.</span><span class="n">InputPullDown</span><span class="p">);</span>
<span class="c1">// play around with this value.</span>
<span class="c1">// 0 will result in a virtually instant count</span>
<span class="c1">// 10 looks cool</span>
<span class="kt">int</span> <span class="n">countDelay</span> <span class="p">=</span> <span class="m">100</span><span class="p">;</span>
<span class="kt">int</span> <span class="k">value</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="c1">// We'll loop forever now - CTRL + C to exit</span>
<span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="k">value</span><span class="p">);</span>
<span class="c1">// _pins[10-currentBit] accesses the pin number for the current bit</span>
<span class="c1">// (value & 1<<currentBit) performs a bitwise AND operation to check if the bit is set in the value</span>
<span class="c1">// If the bit is set, the ternary expression sets the PinValue.Low (on) or if not, PinValue.High (off)</span>
<span class="nf">DisplayValue</span><span class="p">(</span><span class="k">value</span><span class="p">);</span>
<span class="c1">// don't keep incrementing the value once we hit max</span>
<span class="k">if</span> <span class="p">(</span><span class="k">value</span> <span class="p"><</span> <span class="p">(</span><span class="m">1</span><span class="p"><<(</span><span class="n">_pins</span><span class="p">.</span><span class="n">Length</span><span class="p">+</span><span class="m">1</span><span class="p">))-</span><span class="m">1</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">value</span><span class="p">++;</span>
<span class="p">}</span>
<span class="c1">// Check input value each time we loop</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_controller</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="n">_switchPin</span><span class="p">)</span> <span class="p">==</span> <span class="n">PinValue</span><span class="p">.</span><span class="n">High</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// button pressed</span>
<span class="k">value</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">Thread</span><span class="p">.</span><span class="nf">Sleep</span> <span class="p">(</span><span class="n">countDelay</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As usual, the code is heavily commented to explain what is going on. We now have an infinite loop and we poll the status of the input pin each loop to determine if the pin value is high - if so, we reset the value to zero.</p>
</li>
<li>
<p>Let's update the circuit to include a switch connected between 3.3v and GPIO 17.</p>
<p><img src="https://darenm.github.io/assets/LedBinaryReset_bb_637068541883901124.png" alt="LED Binary Reset" /></p>
</li>
<li>
<p>Compile and run the code with the following command:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dotnet</span><span class="w"> </span><span class="nx">run</span><span class="w">
</span></code></pre></div></div>
<p>You should now see the LEDs display an incrementing value in binary - if you leave it running long enough, it will reach 2047 and all LEDs will be lit. Pressing the switch will raise GPIO 17 high, which will cause the value to be reset to zero. Once you release the switch, the count will begin again.</p>
</li>
</ol>
<p>So in this article we explored using the the GPIO in both output and input modes. In the next article I will start to explore I2C.</p>Daren MayIn the previous article, I explored installing and running .NET Core 3.0 on a Raspberry PI running Raspbian. In this article I intend to create a simple console application that will interact with the GPIO and flash an LED.Exploring IoT on Raspberry PI 3 V2 with .NET/C#2019-10-15T05:00:00+00:002019-10-15T05:00:00+00:00https://darenm.github.io/iot/.netcore/c%23/raspberrypi/2019/10/15/exploring-iot-on-raspberry-pi-3-v2-with-netc-getting-started<p>While working on various courses for Azure IoT, I have had a bunch of experience working with C/C++ and Python on Linux and C#/UWP on Windows 10 IoT Core. However I have noticed that the Raspberry Pi isn't necessarily an ideal platform for Windows 10 Core - in fact the later models, including the Raspberry Pi 4, are not yet supported. So, if I want to explore IoT in C# on the Raspberry Pi, I need to look at .NET Core.</p>
<h2 id="install-net-core-30">Install .NET Core 3.0</h2>
<p>Complete the following steps to install .NET Core 3.0 on a Raspberry PI Model 3 V2 running Raspbian Buster. Raspbian Buster is a variant of Debian 10.</p>
<h3 id="prerequisites">Prerequisites</h3>
<p>The [.NET Core github repo][1] contains an [article][2] that lists the following prerequisites for .NET Core 3.0 on Debian 10:</p>
<ul>
<li>libicu63</li>
<li>libssl1.1</li>
</ul>
<ol>
<li>
<p>In a terminal session on your Raspberry PI, to install .NET Core runtime dependencies, run the following commands:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nt">-y</span> update
<span class="nb">sudo </span>apt-get <span class="nt">-y</span> <span class="nb">install </span>wget libicu63 libssl1.1
</code></pre></div></div>
</li>
</ol>
<p>We will use <code>wget</code> to download the distro on the Raspberry Pi.</p>
<h3 id="download-the-linux-net-core-distro">Download the Linux .NET Core Distro</h3>
<p>At the moment, there is no simple way to install .Net Core via the <code>apt-get</code> command - instead we will need to download the <code>tar</code> for the distribution and install it ourselves.</p>
<p>Microsoft maintains a download page for .NET Core 3.0 here</p>
<ol>
<li>
<p>In a browser, open <a href="https://dotnet.microsoft.com/download/dotnet-core/3.0">https://dotnet.microsoft.com/download/dotnet-core/3.0</a>.</p>
</li>
<li>
<p>Find the latest version of the Linux ARM32 SDK and click the URL.</p>
<p>This will open the download page as well as automatically start the download. Useful, if you are using a browser on the PI, but not so useful if you are running on the desktop. Fortunately, the page also provides a click here to download manually link as well as the ability to copy a direct link to the download. Copy that URL.</p>
<p>This is the URL I see: <a href="https://download.visualstudio.microsoft.com/download/pr/8ddb8193-f88c-4c4b-82a3-39fcced27e91/b8e0b9bf4cf77dff09ff86cc1a73960b/dotnet-sdk-3.0.100-linux-arm.tar.gz">https://download.visualstudio.microsoft.com/download/pr/8ddb8193-f88c-4c4b-82a3-39fcced27e91/b8e0b9bf4cf77dff09ff86cc1a73960b/dotnet-sdk-3.0.100-linux-arm.tar.gz</a></p>
</li>
<li>
<p>In a terminal session on your Raspberry PI, to download the distribution, run the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget <url-from-above>
</code></pre></div></div>
<p>For example:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://download.visualstudio.microsoft.com/download/pr/8ddb8193-f88c-4c4b-82a3-39fcced27e91/b8e0b9bf4cf77dff09ff86cc1a73960b/dotnet-sdk-3.0.100-linux-arm.tar.gz
</code></pre></div></div>
</li>
<li>
<p>To create a directory to extract the tar archive, enter the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /usr/share/dotnet
</code></pre></div></div>
<p>The -p argument creates parent directories as necessary.</p>
<blockquote>
<p><strong>Note</strong>: The dotnet runtime expects to be located in <strong>/usr/share/dotnet</strong>. Alternatively, you can set the <strong>DOTNET_ROOT</strong> environment variable to specify the runtime location or register the runtime location in <strong>/etc/dotnet/install_location</strong>.</p>
</blockquote>
</li>
<li>
<p>To extract the archive, enter the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo tar</span> <span class="nt">-zxf</span> dotnet-sdk-3.0.100-linux-arm.tar.gz <span class="nt">-C</span> /usr/share/dotnet
</code></pre></div></div>
<blockquote>
<p><strong>Note</strong>: This will run silently and take a few minutes.</p>
</blockquote>
<p>The arguments mean:</p>
<ul>
<li>z - filter the archive through gzip (as the file name ends in .gz we know gzip was used)</li>
<li>x - extract the archive</li>
<li>f - means the archive is a file, not from tape</li>
</ul>
</li>
<li>
<p>To create a symbolic link to the dotnet command so it is accessible by the existing path settings, enter the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo ln</span> <span class="nt">-s</span> /usr/share/dotnet/dotnet /usr/bin
</code></pre></div></div>
</li>
<li>
<p>To verify the dotnet command is available, enter the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet <span class="nt">--version</span>
</code></pre></div></div>
<p>For the version I have installed, you should see the following:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet <span class="nt">--version</span>
3.0.100
</code></pre></div></div>
</li>
</ol>
<h2 id="create-a-sample-project">Create a Sample Project</h2>
<p>In these steps we will create a simple "Hello, World" app and run it. We will then create a single file deployment, copy the output and demonstrate that it runs "standalone".</p>
<ol>
<li>
<p>Change directory to the location you wish to create the project.</p>
</li>
<li>
<p>To create a directory for your project and then navigate into it, enter the following commands:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>helloworld
<span class="nb">cd </span>helloworld
</code></pre></div></div>
</li>
<li>
<p>To create a new console application, enter the following command:</p>
<pre><code>dotnet new console
</code></pre>
<p>This command will create the source file and project file for a simple console application. It will also automatically run dotnet restore to download any dependencies.</p>
<p>This is a very simple app:</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">namespace</span> <span class="nn">helloworld</span>
<span class="p">{</span>
<span class="k">class</span> <span class="nc">Program</span>
<span class="p">{</span>
<span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"Hello World!"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
</li>
<li>
<p>To run the app, enter the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet run
</code></pre></div></div>
<p>After a moment, the application will compile, run and then write the following to the console:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet run
Hello World!
</code></pre></div></div>
<blockquote>
<p><strong>Note</strong>: If you see the error below, ensure the SDK was installed in the correct location.
<em>A fatal error occurred. The required library libhostfxr.so could not be found.</em></p>
</blockquote>
</li>
<li>
<p>To create a self-contained release build, enter the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet publish <span class="nt">-r</span> linux-arm <span class="nt">-c</span> Release <span class="nt">--self-contained</span>
</code></pre></div></div>
<p>You will see something similar tp the following:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet publish <span class="nt">-r</span> linux-arm <span class="nt">-c</span> Release <span class="nt">--self-contained</span>
Microsoft <span class="o">(</span>R<span class="o">)</span> Build Engine version 16.3.0-preview-19455-02+4a2d77107 <span class="k">for</span> .NET Core
Copyright <span class="o">(</span>C<span class="o">)</span> Microsoft Corporation. All rights reserved.
Restore completed <span class="k">in </span>26.79 sec <span class="k">for</span> /home/pi/helloworld/helloworld.csproj.
You are using a preview version of .NET Core. See: https://aka.ms/dotnet-core-preview
helloworld -> /home/pi/helloworld/bin/Release/netcoreapp3.0/linux-arm/helloworld.dll
helloworld -> /home/pi/helloworld/bin/Release/netcoreapp3.0/linux-arm/publish/
</code></pre></div></div>
<p>The publish command will build the app in release mode and will publish the self contained app. It will create a new folder with the name of publish which will have a standalone copy of the .NET Core runtime with the .exe file. To run the app on another machine (even one with out .NET Core installed) just requires copying this folder and running the executable file.</p>
</li>
<li>
<p>To view the files in the publish folder, run the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-l</span> bin/Release/netcoreapp3.0/linux-arm/publish
</code></pre></div></div>
<p>You will see there are <strong>a lot</strong> of files.</p>
<p>To just view the application executable, enter the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-l</span> bin/Release/netcoreapp3.0/linux-arm/publish/helloworld
</code></pre></div></div>
<p>You will see something similar to:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-l</span> bin/Release/netcoreapp3.0/linux-arm/publish/helloworld
<span class="nt">-rwxr--r--</span> 1 pi pi 73400 Sep 18 17:35 bin/Release/netcoreapp3.0/linux-arm/publish/helloworld
</code></pre></div></div>
<p>The size of the executable is pretty small, as you would expect for our small app - of course, the folder does also contain the entire set of libraries for the .NET Core runtime as well. However, with .NET Core 3.0 we can go one better and produce a single executable file.</p>
</li>
<li>
<p>To delete the Publish folder, enter the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm</span> <span class="nt">-r</span> bin/Release
</code></pre></div></div>
</li>
<li>
<p>To create a single file, run the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet publish <span class="nt">-r</span> linux-arm <span class="nt">-c</span> Release /p:PublishSingleFile<span class="o">=</span><span class="nb">true</span>
</code></pre></div></div>
</li>
<li>
<p>To view the files in the publish folder, run the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-l</span> bin/Release/netcoreapp3.0/linux-arm/publish
</code></pre></div></div>
<p>You will see something similar to:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-l</span>
total 76476
<span class="nt">-rwxr--r--</span> 1 pi pi 78289466 Sep 18 18:30 helloworld
<span class="nt">-rw-r--r--</span> 1 pi pi 416 Sep 18 17:35 helloworld.pdb
</code></pre></div></div>
<p>You can see we now have a single executable helloworld. However, check out the size of the file - it is massive! That is because it now contains all of the .NET Core runtime as well as our tiny hello world app.</p>
<p>Thankfully, .NET Core 3.0 now allows us to trim unnecesary files before we create the single file.</p>
</li>
<li>
<p>To delete the Publish folder, enter the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm</span> <span class="nt">-r</span> bin/Release
</code></pre></div></div>
</li>
<li>
<p>To create a single, trimmed file, run the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet publish <span class="nt">-r</span> linux-arm <span class="nt">-c</span> Release /p:PublishSingleFile<span class="o">=</span><span class="nb">true</span> /p:PublishTrimmed<span class="o">=</span><span class="nb">true</span>
</code></pre></div></div>
</li>
<li>
<p>To view the files in the publish folder, run the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-l</span> bin/Release/netcoreapp3.0/linux-arm/publish
</code></pre></div></div>
<p>You will see something similar to:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-l</span> bin/Release/netcoreapp3.0/linux-arm/publish
total 30076
<span class="nt">-rwxr--r--</span> 1 pi pi 30791588 Sep 18 18:41 helloworld
<span class="nt">-rw-r--r--</span> 1 pi pi 416 Sep 18 17:35 helloworld.pdb
</code></pre></div></div>
<p>You can see we still have a single executable helloworld. However, check out the size of the file - it is now half the size of the untrimmed file. It's not perfect - but certainly better!</p>
</li>
</ol>
<p>So, to sum up - in this article we installed .NET Core 3.0, created a simple console app, and then explored various ways to compile and publish that app.</p>
<p>In the next article, I am going to explore interacting with some simple electronics via GPIO.</p>Daren MayWhile working on various courses for Azure IoT, I have had a bunch of experience working with C/C++ and Python on Linux and C#/UWP on Windows 10 IoT Core. However I have noticed that the Raspberry Pi isn't necessarily an ideal platform for Windows 10 Core - in fact the later models, including the Raspberry Pi 4, are not yet supported. So, if I want to explore IoT in C# on the Raspberry Pi, I need to look at .NET Core.