tag:blogger.com,1999:blog-18821657645067991212024-03-19T06:45:33.732+01:00The art of simplicityBart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comBlogger3442125tag:blogger.com,1999:blog-1882165764506799121.post-11341179130537316652024-03-19T06:45:00.004+01:002024-03-19T06:45:00.133+01:00NuGet–Change the global-packages cache location<p>Just before the start of my weekend, I got a message from one of our administrators, the disk space on one of our build servers was filling up. Whoops! </p> <p> A took a look at the server and noticed that a lot of the disk space was eaten up by the .nuget folder:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT0oG9gjnwfvLjxh1QiUYRMamvH6Gf9KSY60Y_AsFGF1iVK33BzphsYdmVdkVf96FBPViiwEe4vI0R-3QhCXPF6EB7NzI3zL5nI6QmqcXfL5HZfneaRTUavIiws1HFMtzy4IH8Ncn8JX_4ING_kQxJbDJ1QCJHSYQIWBLfjLQxxmzTdYl5TeZTajp_1gK-/s904/NuGetCache.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="114" data-original-width="904" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT0oG9gjnwfvLjxh1QiUYRMamvH6Gf9KSY60Y_AsFGF1iVK33BzphsYdmVdkVf96FBPViiwEe4vI0R-3QhCXPF6EB7NzI3zL5nI6QmqcXfL5HZfneaRTUavIiws1HFMtzy4IH8Ncn8JX_4ING_kQxJbDJ1QCJHSYQIWBLfjLQxxmzTdYl5TeZTajp_1gK-/s16000/NuGetCache.png" /></a></div> <h1>What is this .nuget folder?</h1> <p>The .nuget folder is the default global packages folder. It is the location where NuGet installs any downloaded package. In the first NuGet versions packages were installed as part of the solution tree where the packages were used. </p> <p>But since a long time, this got replaced by the global packages folder avoiding having package copies found everywhere on your local file system.</p> <p>The default location is:</p> <ul> <li>Windows: <code>%userprofile%\.nuget\packages</code></li> <li>Mac/Linux: <code>~/.nuget/packages</code></li> </ul> <h1>How to change the global packages location?</h1> <p>The easiest way to change this location is by setting the NUGET_PACKAGES environment variable to a different path:</p> <p><font face="Courier New">$Env:NUGET_PACKAGES = "d:\.nuget\packages"</font></p> <h1>More information</h1> <p><a href="https://learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders">How to manage the global packages, cache, temp folders in NuGet | Microsoft Learn</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-72781307220874015542024-03-18T07:05:00.000+01:002024-03-18T07:05:00.134+01:00Eloquent JavaScript 4th edition is released<p>If you are new to JavaScript or want to refresh you knowledge with the latest and greatest that is inside the language, I can only recommend to spend some time with the 4th edition of Marijn Haverbeke's book; <a href="https://eloquentjavascript.net/">Eloquent JavaScript</a>. </p> <p><a href="https://eloquentjavascript.net/"><img src="https://eloquentjavascript.net/img/cover.jpg" /></a></p> <p>I’ve read it during the weekend and I have to admit that it revived my interest(love?) in the language.  After spending most of my time using C# and TypeScript it is great to see how far we can get with modern JavaScript.</p> <p>The book is well written with a lot of examples, handling both basic and more advanced feature of the language. It includes some (small) projects that are fun to build and try and allow you to apply the knowledge you’ve acquired.</p> <p>So if you want to learn how to write ‘modern’ JavaScript, this is a must read!</p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-15811151336847609022024-03-15T06:51:00.041+01:002024-03-15T06:51:00.248+01:00NuGet– Offline support<p>With big traffic jams everywhere, I try to use public transport as much as possible for my daily commute. This allows me to work already before I arrive at the office. As connectivity is not that great during travel, I tend to work offline. Most of the time this works great, but I get into trouble the moment I need to add or update a NuGet package to a project in Visual Studio.</p> <p>Visual Studio starts to complain when connectivity is lost and I want to add a NuGet Package:</p><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL_K4ao-0HWx491lWqoWnyv6gHO1m4WdVRfP8NB87YSkHmHl-nWV1she3K19XI-A4zsdUJevxtBzM8fbNa9s8E1edsuQ7u_SLJ68mNSQD7nZ7wM2NK_ymAnz02FH6j7T1Ers-tB4oHtYOGXwSZKjdxnDXV0GXqlD7QJRZClP7xWh-NLKpl6Rz_Bp6DidJn/s1742/NuGetOffline.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="1742" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL_K4ao-0HWx491lWqoWnyv6gHO1m4WdVRfP8NB87YSkHmHl-nWV1she3K19XI-A4zsdUJevxtBzM8fbNa9s8E1edsuQ7u_SLJ68mNSQD7nZ7wM2NK_ymAnz02FH6j7T1Ers-tB4oHtYOGXwSZKjdxnDXV0GXqlD7QJRZClP7xWh-NLKpl6Rz_Bp6DidJn/s16000/NuGetOffline.png" /></a></div> <p>Of course, this is not related to Visual Studio directly, if I try do this from the command line, I also get errors:</p> <p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLCVHe9Az8nprMnybjFc9kUrIdLir-FM8gYRjPjR6i0OtZM0zAhryD5K5K0rBgpSFiFg0jh7z6AW_aBUpkz1DgB0F0aU_cWRW2JPugo0kYH9ogpHerukrm9pK9vgRo6xeZpI7phEwwI0ntp4wsU_6XZxZh3hlatXGQ4XKecynmq6aCbZHlyfYOYk73yVcg/s2350/NuGetOffline2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1226" data-original-width="2350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLCVHe9Az8nprMnybjFc9kUrIdLir-FM8gYRjPjR6i0OtZM0zAhryD5K5K0rBgpSFiFg0jh7z6AW_aBUpkz1DgB0F0aU_cWRW2JPugo0kYH9ogpHerukrm9pK9vgRo6xeZpI7phEwwI0ntp4wsU_6XZxZh3hlatXGQ4XKecynmq6aCbZHlyfYOYk73yVcg/s16000/NuGetOffline2.png" /></a></div>The stupid thing is that I do have the package available offline. If you go to %userprofile%\.nuget\packages, you’ll find a local copy of every package you have installed in your projects:<p></p> <p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiwDFYeE6pL1b0kGbBQ_PiiAn-_iNML9wxV416qMau1MWr7rwmEXggkQW-KW0-oHPf-WSxQ6k5rMWVUKEfwqLFXHplJKKSUWV86sQRrP0MRGGrgpvqbYDEipG9beB72oIibtMqJ3eeK6Wv3BcIPkr2Uy6y27FAyc499nt-SvSvnf56NPaz2JhkXeUyPXTb/s2472/NuGetOffline3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1524" data-original-width="2472" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiwDFYeE6pL1b0kGbBQ_PiiAn-_iNML9wxV416qMau1MWr7rwmEXggkQW-KW0-oHPf-WSxQ6k5rMWVUKEfwqLFXHplJKKSUWV86sQRrP0MRGGrgpvqbYDEipG9beB72oIibtMqJ3eeK6Wv3BcIPkr2Uy6y27FAyc499nt-SvSvnf56NPaz2JhkXeUyPXTb/s16000/NuGetOffline3.png" /></a></div>Instead of failing back to the cache when the package source is not accessible, it just fails. <p></p> <h1>Local feeds</h1> <p> What you can do, is setup a local feed. This will use a simple hierarchical folder structure on your file system to store packages. </p> <p>To use a local folder as source, add its pathname (such as <code>d:\packages</code>) to the list of sources using the <a href="https://learn.microsoft.com/en-us/nuget/consume-packages/install-use-packages-visual-studio#package-sources">Package Manager UI</a> or the <a href="https://learn.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-sources"><code>nuget sources</code></a> command</p> <p>Just to test I added one package to the local feed:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhD70cRFaDlbnuXlijNzRxbxBUVw9iEzSiKwtvWyz7tHf2xAbZnLXfR3Rhuc7F4CPWAtGWUolzK8GZzY0Ee7AoE66_v-2h2149qsDq4DyUl9Phm27O7J3WdwhsU8FlHYHIm0QFo786TuEO1Wh34qzlKgP14azmR-mRCKNeNpkQtppa4AtQZlInN3kZ6rN9P/s2350/NuGetOffline4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1226" data-original-width="2350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhD70cRFaDlbnuXlijNzRxbxBUVw9iEzSiKwtvWyz7tHf2xAbZnLXfR3Rhuc7F4CPWAtGWUolzK8GZzY0Ee7AoE66_v-2h2149qsDq4DyUl9Phm27O7J3WdwhsU8FlHYHIm0QFo786TuEO1Wh34qzlKgP14azmR-mRCKNeNpkQtppa4AtQZlInN3kZ6rN9P/s16000/NuGetOffline4.png" /></a></div> <p>This create a nested folder structure on disk:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1WSYiItAdVz-bHDXsUyvYp_9rs__MKhW57TePNLdTEqRNHaS3P45FJ4HduH-ALHSjX8RkGxTAQWuiyplG175G7eiIzbzkwPchSroK9fAZwkFZyzNksWShCzv9XfLY-mguuJJlAZQaFJHT52_he7FTQKa1IpDI-0dOzJ6k79pOShOea3FUhKnR1yQGPgxl/s2444/NuGetOffline5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="608" data-original-width="2444" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1WSYiItAdVz-bHDXsUyvYp_9rs__MKhW57TePNLdTEqRNHaS3P45FJ4HduH-ALHSjX8RkGxTAQWuiyplG175G7eiIzbzkwPchSroK9fAZwkFZyzNksWShCzv9XfLY-mguuJJlAZQaFJHT52_he7FTQKa1IpDI-0dOzJ6k79pOShOea3FUhKnR1yQGPgxl/s16000/NuGetOffline5.png" /></a></div><p></p> <p>If I now try to use this package source in Visual Studio, our installed package is found:</p> <p> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBaOCanpltlGtQ5hJZs4SIiXpVfGrdv3QDOvuh3pyGouha31QUJMyJd_InTYxmoTK7uJbaSqu2MTkLa03sOsI1ZxJpsON6Ethg7eS5TI0sJ8jl3IL1fwu_iJuGN1zjpwebEsVJ6i37KdkCfuruPqVIVSYyyw3zzTs260FUPcgw4PhqHf6bCtXlS6-fyk1V/s2098/NuGetOffline6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="908" data-original-width="2098" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBaOCanpltlGtQ5hJZs4SIiXpVfGrdv3QDOvuh3pyGouha31QUJMyJd_InTYxmoTK7uJbaSqu2MTkLa03sOsI1ZxJpsON6Ethg7eS5TI0sJ8jl3IL1fwu_iJuGN1zjpwebEsVJ6i37KdkCfuruPqVIVSYyyw3zzTs260FUPcgw4PhqHf6bCtXlS6-fyk1V/s16000/NuGetOffline6.png" /></a></p><h1>The .nuget folder as the local feed? </h1> <p>This made me wonder, could I use the local nuget cache as the source folder for our local feed?</p> <p>I created a new package source and specified the %userprofile%\.nuget\packages folder as the source folder.</p> <p>I could already discover the packages, that is a good start:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5LJCT_jPmezYtbCHK26ypQo0w-wti752pALhLzahxxHGLIJ4Djbc5jNvQaZJjBM4IF20mDbJnXMdLF3FLCA6kBGQXHsp9-xb_nczs-bN8iBxSBCZsYWlUNDZgyQSAYePQPMgR7i72yZx-jZKZPGdOnePSdOjASZvAlh4JZVnraGnc0MxMnGQLVZNZxuzU/s2094/NuGetOffline7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="890" data-original-width="2094" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5LJCT_jPmezYtbCHK26ypQo0w-wti752pALhLzahxxHGLIJ4Djbc5jNvQaZJjBM4IF20mDbJnXMdLF3FLCA6kBGQXHsp9-xb_nczs-bN8iBxSBCZsYWlUNDZgyQSAYePQPMgR7i72yZx-jZKZPGdOnePSdOjASZvAlh4JZVnraGnc0MxMnGQLVZNZxuzU/s16000/NuGetOffline7.png" /></a></div> <p>Also search seems to work although it is quite slow:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDkfWdzGX1vXLcRmyMp1mAMinSWhVHtKKW-5Qdxs9histPOFs2wuEvAX0lKMC7wcU9PhgPZCTWxHtuuEu7RNKNMIUwEMKPnK6MpeZcpzwAk9uGcbmkzJoNGph_SY5xmmlo8E1wQLdE03vLCTSvWXXXJcKBzWpq8CdG_F0bTxHlgX9dVj7trC1wh497qpXK/s2104/NuGetOffline8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="892" data-original-width="2104" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDkfWdzGX1vXLcRmyMp1mAMinSWhVHtKKW-5Qdxs9histPOFs2wuEvAX0lKMC7wcU9PhgPZCTWxHtuuEu7RNKNMIUwEMKPnK6MpeZcpzwAk9uGcbmkzJoNGph_SY5xmmlo8E1wQLdE03vLCTSvWXXXJcKBzWpq8CdG_F0bTxHlgX9dVj7trC1wh497qpXK/s16000/NuGetOffline8.png" /></a></div><p></p> <p>And even install does what it is supposed to do:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtS0ut_c2ZnD-GyxS39EDwA1d1f0KKXLXJQd8J7b0EhwqxBtYhBOd8wcBfq-il3cLhKPp2UJJN41vIZxvnM-UsZuhNPzMQfpddCyU67iVKn7UNor9DYb5T6WqPFMC_-01m_Mv6Av33Y8boXe93oq4C-CX82MiePOz-4UWLu5PLr4B3HUGvNNGiUKGLk_Sc/s2076/NuGetOffline9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="830" data-original-width="2076" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtS0ut_c2ZnD-GyxS39EDwA1d1f0KKXLXJQd8J7b0EhwqxBtYhBOd8wcBfq-il3cLhKPp2UJJN41vIZxvnM-UsZuhNPzMQfpddCyU67iVKn7UNor9DYb5T6WqPFMC_-01m_Mv6Av33Y8boXe93oq4C-CX82MiePOz-4UWLu5PLr4B3HUGvNNGiUKGLk_Sc/s16000/NuGetOffline9.png" /></a></div><p></p> <p><strong>Remark:</strong> Although I was able to install offline packages this way, I still got NuGet errors for the other packages but that didn’t prevent me to successfully build the solution. </p> <p>Another thing I noticed, is that for the SDKs another local feed is available:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvQbdjyNHP_F6noODlq8LPt66pB2FuJG5NscU2sf52-AjEXEqMzPR0FBckjTpOEjJHB8wFPDn5EJFYDCdhBB8xyXym_9yLs0cItbmwklEB2VuH7P9v89e6Xumh-293R0NNFpFdbGr9-H_F1Ee5bmwMOwG6GFRk7blTESWDcobowGI5AkohS5JCmOm5ScnN/s1262/NuGetOffline11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="855" data-original-width="1262" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvQbdjyNHP_F6noODlq8LPt66pB2FuJG5NscU2sf52-AjEXEqMzPR0FBckjTpOEjJHB8wFPDn5EJFYDCdhBB8xyXym_9yLs0cItbmwklEB2VuH7P9v89e6Xumh-293R0NNFpFdbGr9-H_F1Ee5bmwMOwG6GFRk7blTESWDcobowGI5AkohS5JCmOm5ScnN/s16000/NuGetOffline11.png" /></a></div><div class="separator" style="clear: both; text-align: left;">I took a look at the configured folder and found a lot of framework related packages:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwvl9EdKPn0a219z4EsCOwS4lCSFRkC4kdohnyqRDnXvgZCRBO7fWf7GFH1gZlCTHcQ509i37i3cnsJ6ldrkTHWuaNQ7nbbdalq6CUC4IU_yo_202J3DiE6dJM6ugvg3SwdHoZruIynwxcBvZ39vn6Fl3MDUqI_0zslrpwDoa7t5msZjnlTEL2XFGUOsk_/s2472/NuGetOffline10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1524" data-original-width="2472" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwvl9EdKPn0a219z4EsCOwS4lCSFRkC4kdohnyqRDnXvgZCRBO7fWf7GFH1gZlCTHcQ509i37i3cnsJ6ldrkTHWuaNQ7nbbdalq6CUC4IU_yo_202J3DiE6dJM6ugvg3SwdHoZruIynwxcBvZ39vn6Fl3MDUqI_0zslrpwDoa7t5msZjnlTEL2XFGUOsk_/s16000/NuGetOffline10.png" /></a></div></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Interesting. Is anyone using this as an offline approach?</div><p></p> <h1>More information</h1> <p><a href="https://learn.microsoft.com/en-us/nuget/hosting-packages/local-feeds">Setting up Local NuGet Feeds | Microsoft Learn</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-81292866531748739682024-03-14T07:34:00.039+01:002024-03-14T07:34:00.133+01:00Github Copilot– Some experimentation<p>I was reading the following <a href="https://blog.jetbrains.com/dotnet/2024/02/27/critical-thinking-in-an-ai-powered-world/">blog post</a> when I thought:<em> "How good (or bad) would Github Copilot handle the scenario's mentioned in the post?"</em> This post is the answer on this question. </p><p>But before we dive in, I would suggest to first read the original post on the JetBrains blog: <a href="https://blog.jetbrains.com/dotnet/2024/02/27/critical-thinking-in-an-ai-powered-world/">Critical Thinking in an AI-Powered World | The .NET Tools Blog (jetbrains.com)</a>. </p> <p>Back?</p> <h2 style="text-align: left;">Let’s get started!</h2> <p>I already created an XUnit Test project targeting .NET 8 and pasted the first snippet used in the post in a test class:</p> <script src="https://gist.github.com/wullemsb/7275b4a385f1114984c9f10ed20ebae8.js"></script> <p>Now let’s ask the same question but not to the JetBrains AI assistant but to Github Copilot in Visual Studio:</p> <pre>Suggest a way to refactor the variable `now` so that I can control the value without depending on `DateTime.UtcNow`</pre>
<p>Here is the response I got:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_rOzPuwVeOV5dyVtnlPDPVc5j_5plJmOIn2Js6nGJEnoftL-xnATqkag4C9IMbGU3lWrl6mInkkxCpR0FUesi19NsHedzzneWS14BBan8iu3E6eHn9ZFeCYfYvyH21HGzTZ6w_7eiEK-WN8HZl9RgAfyK5q6-YJGhKyk1bTXCri6K6f-eNpz0dCnLOR3k/s1640/CopilotWhatIf1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1242" data-original-width="1640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_rOzPuwVeOV5dyVtnlPDPVc5j_5plJmOIn2Js6nGJEnoftL-xnATqkag4C9IMbGU3lWrl6mInkkxCpR0FUesi19NsHedzzneWS14BBan8iu3E6eHn9ZFeCYfYvyH21HGzTZ6w_7eiEK-WN8HZl9RgAfyK5q6-YJGhKyk1bTXCri6K6f-eNpz0dCnLOR3k/s16000/CopilotWhatIf1.png" /></a></div>
<p>Similar to the JetBrains AI Assistant it suggests me to create my own abstraction and create an IDateTimeProvider interface. Too bad! Let us also mention the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.timeprovider?view=net-8.0"><code>TimeProvider</code></a> class and see if we get a better result:</p>
<pre>Please use the ‘System.TimeProvider’ class found in .NET 8 and C# 12 instead</pre>
<p>But again, the results turn out quite similar:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ5HvvJMzQ7KhcR_Mu73fTcwjuJx2HFGIuNw-kTv-Ng9if-I9bcNpbv2UXgEPRTiIbYuUvkBNbyMtH43rXWmNAlxwek7sRcpCOjDO1BquO5ZdxyktA706ITHCcHNciwXMpHSj94TzZTCF3hyphenhyphenaO4JaykUIpHynW9DKW1xGmQjWBvlxtS_GbBBazL0Xk8Lk5/s1652/CopilotWhatIf2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="740" data-original-width="1652" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ5HvvJMzQ7KhcR_Mu73fTcwjuJx2HFGIuNw-kTv-Ng9if-I9bcNpbv2UXgEPRTiIbYuUvkBNbyMtH43rXWmNAlxwek7sRcpCOjDO1BquO5ZdxyktA706ITHCcHNciwXMpHSj94TzZTCF3hyphenhyphenaO4JaykUIpHynW9DKW1xGmQjWBvlxtS_GbBBazL0Xk8Lk5/s16000/CopilotWhatIf2.png" /></a></div><p></p>
<p>So far, we cannot make a different conclusion as in the original post.</p><h2 style="text-align: left;">Take 2!</h2><p>Ok, let’s refocus our attention on the second part of the post and let us see how Copilot helps wht the implementation of the <code>CalculateFallTimeAndVelocity</code> method:</p>
<script src="https://gist.github.com/wullemsb/453a1a95fd770f2adadac5bbf1b23bc3.js"></script>
<p>Let’s see what the suggestion is that the system comes up with:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiibKVuZSgDL5NnA_CvoXEWLLzqCN7Ydbz03zMQH18PfVFre303zqkDKbknct2gzlLzJI-JmKr2T4DHDei_qUWRSKQrWcNgapVVozeasfgpFgFNybXYBbr_saB8xccLKS_wRUB9ek0YWFuoqY2BloddU6VjbgIptxKuAY-uf_a1A5KNMetXjTAiopyaVvvZ/s2206/CopilotWhatIf3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="826" data-original-width="2206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiibKVuZSgDL5NnA_CvoXEWLLzqCN7Ydbz03zMQH18PfVFre303zqkDKbknct2gzlLzJI-JmKr2T4DHDei_qUWRSKQrWcNgapVVozeasfgpFgFNybXYBbr_saB8xccLKS_wRUB9ek0YWFuoqY2BloddU6VjbgIptxKuAY-uf_a1A5KNMetXjTAiopyaVvvZ/s16000/CopilotWhatIf3.png" /></a></div>
<p>Not bad either! </p>
<p>But let us improve our understanding of the magic values by using the following prompt:</p>
<pre>Move all constants to descriptive variables.</pre>
<p>I just apply the suggestion and the result looks like this: </p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZQYbWwC33Inziw0balr2hikfaIYPw_eyQK4_Q-iSUk5u1mEhTjPjVr-1tyhO95RQTgTpztP-5s7H_CRi_xe10yMXNOArqqMVyX8MFfu672MBYEDna0nYCpfP77pRVkD_bq-FDpLQwFiKPDxKAuvnfouByeFdkBo0ysBqH42SmSGNtatlatGVHqLqUGC2O/s2072/CopilotWhatIf4.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="444" data-original-width="2072" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZQYbWwC33Inziw0balr2hikfaIYPw_eyQK4_Q-iSUk5u1mEhTjPjVr-1tyhO95RQTgTpztP-5s7H_CRi_xe10yMXNOArqqMVyX8MFfu672MBYEDna0nYCpfP77pRVkD_bq-FDpLQwFiKPDxKAuvnfouByeFdkBo0ysBqH42SmSGNtatlatGVHqLqUGC2O/s16000/CopilotWhatIf4.png" /></a></div><p></p><p>She(he?) didn’t isolate the Factor value but with the comment in place I can understand the role of the 2 in the code above. </p><p>We continue with the next prompt:</p>
<pre>Set the value of Gravity to Earth’s gravity up to four decimal places of precision</pre>
<p>This gives us the following result:</p>
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjneTcSqMAPkdEp1TE2v3UFFDhJimxp4HvpX49LVhbJXAWyLx6jzaVVHeGWCTa_wZ7iqoebHPscTERc3hLc8Yt9si5kD7rUOQLDo8GIZEJ7FjwBIeD7_JgVQJ_ufCsasbCfhuLulEVCw4KRnt9YsGsz1As9lS40pGickwqwhHtbqL4BofW-WGwNGIt34UUG/s2222/CopilotWhatIf5.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="428" data-original-width="2222" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjneTcSqMAPkdEp1TE2v3UFFDhJimxp4HvpX49LVhbJXAWyLx6jzaVVHeGWCTa_wZ7iqoebHPscTERc3hLc8Yt9si5kD7rUOQLDo8GIZEJ7FjwBIeD7_JgVQJ_ufCsasbCfhuLulEVCw4KRnt9YsGsz1As9lS40pGickwqwhHtbqL4BofW-WGwNGIt34UUG/s16000/CopilotWhatIf5.png" /></a></div>Too bad! Although the value itself is correct, it gives me a suggestion with 5 decimal places of precision:<p></p>
<p>We end with the last prompt:</p>
<pre>Comment each line with valuable information that explains what’s happening</pre>
<p>And this is our final result:</p>
<script src="https://gist.github.com/wullemsb/ff8ca5d4b4c1d9d0b03b38e3bc2a0938.js"></script>
<h2 style="text-align: left;">Conclusion</h2><p>Based on this I could only agree with the conclusion of the original post but applied to Github Copilot:</p>
<p><em>Github Copilot can help you solve a new fascinating set of problems but does not claim to be infallible. Since it uses models trained on human data, it can sometimes be wrong. That’s why you should think critically about responses and always take steps to understand and verify the results of any LLM-based product.</em></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-28372785801146134582024-03-13T06:25:00.004+01:002024-03-13T06:25:00.144+01:00Everyone should be an architect<p>In the world of software development, the role of the architect often looms large. Yet, what if I told you that architecture is not just the domain of a select few, but rather the responsibility of every member on the team?</p> <p>Let's delve into why this shift in perspective is crucial for the success of your projects.</p> <p>Gone are the days when software architecture was solely the concern of a designated architect. In today's landscape, it's imperative that every team member, from developers to testers, possesses a solid understanding of the architectural principles guiding their work.</p> <p>As an architect, your primary aim should be to ensure that everyone comprehends the architecture as thoroughly as you do. Each member should be equipped to answer the fundamental "why" questions about the architecture, breaking down barriers that often lead to miscommunication and inefficiency.</p> <p>Too often, we see organizations where the architect stands as the solitary guardian of the system's architecture. This isolation not only stifles collaboration but also hinders the development team's ability to fully grasp the architectural vision. </p> <h2 style="text-align: left;">The result? </h2> <p>Ivory tower architects struggling to bridge the gap between their ideas and the practical realities faced by developers.</p> <p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuCEaXuP9a2qDcmXTXRIdWi0mHmVZpfR8rATEAAx140q-EDM3GGAKCoMK93oV382mOJJx-0ZKT9vrQG6GJFWCl57hN2qQ0UXMP1HEznWqwlSeNdQ2OOyWaooDfwxngmkHVTNJ4DzyY3P1XdMcrua_ACsIDh4EBnZ_x8XhE1yA0wp6NU0nqGgy2HCVnNVUN/s1024/_7bb21ee5-3a0c-4210-847a-673d42d45db2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1024" data-original-width="1024" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuCEaXuP9a2qDcmXTXRIdWi0mHmVZpfR8rATEAAx140q-EDM3GGAKCoMK93oV382mOJJx-0ZKT9vrQG6GJFWCl57hN2qQ0UXMP1HEznWqwlSeNdQ2OOyWaooDfwxngmkHVTNJ4DzyY3P1XdMcrua_ACsIDh4EBnZ_x8XhE1yA0wp6NU0nqGgy2HCVnNVUN/s16000/_7bb21ee5-3a0c-4210-847a-673d42d45db2.jpg" /></a></div><br /><p></p> <p>Imagine a scenario where every developer possesses architecture skills. Communication becomes streamlined, with team members adeptly navigating discussions around architecture models and design decisions. They're empowered to align their work with overarching architectural goals, making informed choices that uphold the desired quality attributes of the system.</p> <p>Furthermore, developers, being intimately familiar with the codebase, serve as invaluable sources of insight for architects. Even seemingly minor details in the code can have profound architectural implications, underscoring the importance of a collaborative approach to architecture.</p> <p>In this paradigm, developers understand when to adhere to the established architecture and when to advocate for necessary changes. By fostering a culture of shared ownership over architecture, teams can navigate the inevitable challenges of software development with agility and confidence.</p> <p>So let's challenge the notion that architecture is the sole purview of architects. To design and build better software we’ll have to embrace a collective mindset where every team member plays a pivotal role in shaping and maintaining the architectural integrity of your software.</p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-58982723758374348862024-03-12T09:02:00.011+01:002024-03-12T09:02:00.250+01:00MassTransit–Disable fault publishing<p>By default when a message consumer in <a href="https://masstransit.io/">MassTransit</a> throws an exception, the exception is caught by middleware in the transport (the ErrorTransportFilter to be exact), and the message is moved to an _error queue (prefixed by the receive endpoint queue name).</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCbNo6PAsPG-I1cidwUpPnsQPiDzpPg9yhNoQ8YZqxMYM9qnjJMLHI-KBFSa5M86DCXMhMInDPaovDcfNeOQCI1dUljTlYrvQxrGpAwWQ2LqALfYtkAPrwG2iLO4Glh6VzXqV2mdtfZ4j-VCjS8Ofq3ujdWkVFVHf660LV0om1aCD5WZblVZ5N9YWbhPfB/s2376/Faults2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="880" data-original-width="2376" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCbNo6PAsPG-I1cidwUpPnsQPiDzpPg9yhNoQ8YZqxMYM9qnjJMLHI-KBFSa5M86DCXMhMInDPaovDcfNeOQCI1dUljTlYrvQxrGpAwWQ2LqALfYtkAPrwG2iLO4Glh6VzXqV2mdtfZ4j-VCjS8Ofq3ujdWkVFVHf660LV0om1aCD5WZblVZ5N9YWbhPfB/s16000/Faults2.png" /></a></div><p></p> <p>The exception details are stored as headers with the message for analysis and to assist in troubleshooting the exception. </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4wwlAK91By7zOYDywKz4cxBCCFhwh38DeNe-blIclC1-wAWTBxVtJhLKpxOn3JkoUhR4Ms9VOFhrdO1OH4Ttp1VQCDtOyecrkCtB_vD7byz55xdya_ppxaK3BJLzRyUdiyEZyPAkCNkhpD4_8SzoglOCnNo6PtInNTe30u91mFDJx-1G41YFzrc59rHLk/s2626/Faults3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1228" data-original-width="2626" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4wwlAK91By7zOYDywKz4cxBCCFhwh38DeNe-blIclC1-wAWTBxVtJhLKpxOn3JkoUhR4Ms9VOFhrdO1OH4Ttp1VQCDtOyecrkCtB_vD7byz55xdya_ppxaK3BJLzRyUdiyEZyPAkCNkhpD4_8SzoglOCnNo6PtInNTe30u91mFDJx-1G41YFzrc59rHLk/s16000/Faults3.png" /></a></div><p></p> <p>Next to moving the message to an error, MassTransit also produces a <code>Fault<T></code> event. If the message headers specify a <code>FaultAddress</code>, the fault is sent directly to that address. If the <code>FaultAddress</code> is not present, but a <code>ResponseAddress</code> is specified, the fault is sent to the response address. Otherwise, the fault is published, giving you the option to consume these fault messages if you want to:</p> <script src="https://gist.github.com/wullemsb/e7011536d76d745e217274dd2660845a.js"></script> <p>However this can lead to a lot of extra exchanges that could be created. </p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZpT71ct-8tr4HgHsQjL7TY070Se5aP8kIwqAKigVIJ1nGGp-kGWKxaWMd5yRdBQ_be4zswiRgRqrISwHFIW2oId7O7Y9OVB8ni119x7srn4pAjhjTBGmx7GCxU0gKnGcghkrv_IgWuPNx_Zs7llBouYwnAbPOM3rPMpprLO-NYpH4vRr1ufeBdHnbLun9/s2308/Faults.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1026" data-original-width="2308" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZpT71ct-8tr4HgHsQjL7TY070Se5aP8kIwqAKigVIJ1nGGp-kGWKxaWMd5yRdBQ_be4zswiRgRqrISwHFIW2oId7O7Y9OVB8ni119x7srn4pAjhjTBGmx7GCxU0gKnGcghkrv_IgWuPNx_Zs7llBouYwnAbPOM3rPMpprLO-NYpH4vRr1ufeBdHnbLun9/s16000/Faults.png" /></a></div><p></p><p>If you don’t want to publish a Fault event, you can do this by setting the <code>PublishFaults</code> property to <code>false</code> in your MassTransit configuration:</p> <script src="https://gist.github.com/wullemsb/8421e1fc8baaa05d557cc33f1ee8c265.js"></script> <h1>More information</h1> <p><a href="https://masstransit.io/documentation/concepts/exceptions">Exceptions · MassTransit</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-59800310132736001192024-03-11T06:33:00.006+01:002024-03-11T06:33:00.261+01:00AspNetCore.Http.Abstractions is deprecated<p>While working on some class library code in C#, I noticed deprecation warnings in Visual Studio for the following NuGet packages:</p> <ul> <li><a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Http.Abstractions/2.2.0?_src=template">NuGet Gallery | Microsoft.AspNetCore.Http.Abstractions 2.2.0</a></li> <li><a href="https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.Abstractions">NuGet Gallery | Microsoft.AspNetCore.Authentication.Abstractions 2.2.0</a></li> </ul> <p>A look at the NuGet website confirmed this:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpQcNIzddBXvZd0sclOCw5gx6Eohyphenhyphen4UyqvbfIy5_d-AMPYzYE32NtUvle032BpC9YCUj1tDEy-qhRgskhsvXtQscdCbfNDy6SsEGVoYpz2KKsiT22VfSeIhk9v0t_9a8WrAQ3fwfWVNVskZUC0p8VKg8r76W8FrcJA-t6SyD7oGaL3gCzqx3K8uxO_Zef_/s1780/Deprecated.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="804" data-original-width="1780" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpQcNIzddBXvZd0sclOCw5gx6Eohyphenhyphen4UyqvbfIy5_d-AMPYzYE32NtUvle032BpC9YCUj1tDEy-qhRgskhsvXtQscdCbfNDy6SsEGVoYpz2KKsiT22VfSeIhk9v0t_9a8WrAQ3fwfWVNVskZUC0p8VKg8r76W8FrcJA-t6SyD7oGaL3gCzqx3K8uxO_Zef_/s16000/Deprecated.png" /></a></div> <p>But what now? It looked like that no alternative was mentioned anywhere…</p> <p>I first tried to just remove these 2 packages but now my library no longer compiled!?</p> <p>In the end I was able to solve it by adding a framework reference to Microsoft.AspNetCore.app inside the csproj file of my class library:</p> <script src="https://gist.github.com/wullemsb/469c0bb37fd3bbf7d59dc9bee9e33c36.js"></script>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-38874846128874632122024-03-08T14:53:00.005+01:002024-03-08T14:53:00.147+01:00Model based testing in C#<p>In my continuous journey to design and write better code I try and experiment with multiple testing techniques. A while ago I wrote <a href="https://bartwullems.blogspot.com/2023/02/property-based-testing-in-cpart-5.html">a whole series of blog posts</a> about Property based testing, an addition to the traditional example-based testing. </p> <p>A property based test is meant to be fairly succinct and verify that simple properties hold true for all possible inputs given a set of preconditions. Libraries like <a href="https://fscheck.github.io/FsCheck/">FSCheck</a> and <a href="https://github.com/AnthonyLloyd/CsCheck">CSCheck</a> by randomizing the inputs for a particular operation.</p> <p>But what if we need to test a more complicated scenario where it is not so easy to identify the separate properties of our system? This is where model based testing can help us. Instead of randomizing the input for one operation, we execute an arbitrary combination of operations against our system and compare it to a simplified model. </p> <p> Let me show you how to do this using <a href="https://github.com/AnthonyLloyd/CsCheck">CsCheck</a>.</p> <p>I have created a small Counter with two operations: Increase() and Decrease():</p> <script src="https://gist.github.com/wullemsb/da50aeb4b608c121d8844a0d94ba77ac.js"></script> <p>We now define a “simplified” model based on our mental model on how we think the system should work: </p> <script src="https://gist.github.com/wullemsb/b12a732be3341e3f12e7d8bec8a0144d.js"></script> <p><strong>Remark:</strong> The example I using is quite contrived as the real “complex” system and the “simplified” model are in this case the same.</p> <p>Now we need to specify a list of operations that can be executed in arbitrary combinations:</p> <script src="https://gist.github.com/wullemsb/394e96bdcf8a72663c17c60090c464ad.js"></script> <p>The last step is configure CSCheck to execute the operations against both the real system and the model and compare the results.</p> <script src="https://gist.github.com/wullemsb/15230e4ab155c8b9dcc20992bef3e4ba.js"></script> <p>As you can see the test fails because there is a difference between how the real system and the model handles negative values:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnFVIJUzwSztyEH5W4HFFNVzztxzkaAuorqbSvyDTste1KV2sk6tWTn4Ya6rCrXAwOuDAx8YHecGDRGGVH5YMJNkLzwL-u8rBgLOtUTl8t9Hql3na8V3p_wSspZFuGSTSEF5s0FhlfZ2p1LDH-BCLygFYn-MaHfXqYu8vM1f0awo0CBPeqGGG5lzBmEDVb/s3004/Counter.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="816" data-original-width="3004" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnFVIJUzwSztyEH5W4HFFNVzztxzkaAuorqbSvyDTste1KV2sk6tWTn4Ya6rCrXAwOuDAx8YHecGDRGGVH5YMJNkLzwL-u8rBgLOtUTl8t9Hql3na8V3p_wSspZFuGSTSEF5s0FhlfZ2p1LDH-BCLygFYn-MaHfXqYu8vM1f0awo0CBPeqGGG5lzBmEDVb/s16000/Counter.png" /></a></div> <p>The full example can be found here: <a href="https://github.com/wullemsb/modelbasedtesting">wullemsb/modelbasedtesting: A small example demonstrating how to use model based testing with CSCheck (github.com)</a></p> <p>For a more realistic example using FsCheck, check out this blog post by Arron Stannard:</p> <p><a href="https://aaronstannard.com/fscheck-property-testing-csharp-part2/" title="https://aaronstannard.com/fscheck-property-testing-csharp-part2/">https://aaronstannard.com/fscheck-property-testing-csharp-part2/</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-17139020502755624722024-03-07T06:42:00.014+01:002024-03-07T06:42:00.246+01:00Performance test your ASP.NET Core application using NBomber<p>Yesterday I talked about Bombardier, an HTTP benchmarking tool written in Go and how you can use it to test the performance of your ASP.NET Core applications. While discussing the usage, a colleague mentioned another load testing tool, <a href="https://github.com/PragmaticFlow/NBomber">NBomber</a>.</p> <p><a href="https://github.com/PragmaticFlow/NBomber"><img src="https://github.com/PragmaticFlow/NBomber/raw/dev/assets/nbomber_logo.png" /></a></p> <p>NBomber is a load-testing framework for Pull and Push scenarios, designed to test any system regardless of a protocol (HTTP/WebSockets/AMQP, etc) or a semantic model (Pull/Push).</p> <p>It goes a lot further than what Bombardier has to offer. One of the things I find nice is that load test scenario’s can be written using C#. 😉</p> <p><strong>Remark:</strong> NBomber comes with a free personal license, if you want to use it inside your organization, you’ll have to buy a business license.</p> <p>Let’s write a simple load test to test our API!</p> <ul> <li>We start by creating a new Console application and adding the NBomber nuget package:</li> <ul> <li><font face="Courier New">dotnet add package NBomber</font></li> </ul> <li>As we want to test an HTTP endpoint, let’s also add the NBomber.Http plugin to simplify defining and handling of HTTP load tests:</li> <ul> <li><font face="Courier New">dotnet add package NBomber.Http</font></li> </ul> <li>Now we can start writing our test scenario. Here is a simple example scenario where we call our API endpoint simulating 100 users:</li> </ul> <p> </p> <ul></ul> <script src="https://gist.github.com/wullemsb/b9dd7a03052c78fde963a2dbb8c56152.js"></script> <ul> <li>To run the scenario, we only have to run the console app:</li></ul><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPQQCGg_m4mhl-sePEij2Lze10G5j-9sf-XMedGocZTNUMYdRV3kkRgIb4Z54iUVc9Q0A8d81N-udAZiRpn3sqWFl5TUYjx8BOlzGxbs8ZIbOhQZi9f4jOyWuQYPhLk75eAGqIKxm135R25kKK_r0J5rC9xRgC-PZWDLxmPud7T8gnQSM0UtcdO1x1EowW/s2350/Nbomber1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1226" data-original-width="2350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPQQCGg_m4mhl-sePEij2Lze10G5j-9sf-XMedGocZTNUMYdRV3kkRgIb4Z54iUVc9Q0A8d81N-udAZiRpn3sqWFl5TUYjx8BOlzGxbs8ZIbOhQZi9f4jOyWuQYPhLk75eAGqIKxm135R25kKK_r0J5rC9xRgC-PZWDLxmPud7T8gnQSM0UtcdO1x1EowW/s16000/Nbomber1.png" /></a></div><ul><li>After test completion results, results are written in multiple formats to a folder.<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaP4LAKhslvMWxuQ06q7G9Q3Ix3pL0VRXnKOfw69LS7k4oIdGCQNf3pbEYaWQ92aS4jpSyPpS074QvMW0E8G3KdK7Fyrp1QXZzZgGDk5MSM9RCmFcT0z-PJeuL1nHVM1zrkH4HyLmAlABUn-931bhwpAaXb6gPcZA9GFEq7uFM7jU9aVdbomKyzE9BFc13/s2350/NBomber2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1226" data-original-width="2350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaP4LAKhslvMWxuQ06q7G9Q3Ix3pL0VRXnKOfw69LS7k4oIdGCQNf3pbEYaWQ92aS4jpSyPpS074QvMW0E8G3KdK7Fyrp1QXZzZgGDk5MSM9RCmFcT0z-PJeuL1nHVM1zrkH4HyLmAlABUn-931bhwpAaXb6gPcZA9GFEq7uFM7jU9aVdbomKyzE9BFc13/s16000/NBomber2.png" /></a></div></li></ul> <ul> <li>Here is how the HTML report looks like after the test run;</li> <ul></ul> </ul> <p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqIj9dNEIhL5P-fl-1b3vjofS3_6h4q6o25ya8vOl9wka7468_PZ4FXs8bhHPIhPx7kUftgUSpgJgjG6Wi-LHnGa4lnK9NkaQvG9NKYctFvBEj7Ux5ue7I4KnclprID5DOxTExpZDqrSerO1k9M71-4_qnmarCkHcXdIqMbBeGM8pvznJHMYk3eNMd_rwh/s3708/NBomber3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1502" data-original-width="3708" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqIj9dNEIhL5P-fl-1b3vjofS3_6h4q6o25ya8vOl9wka7468_PZ4FXs8bhHPIhPx7kUftgUSpgJgjG6Wi-LHnGa4lnK9NkaQvG9NKYctFvBEj7Ux5ue7I4KnclprID5DOxTExpZDqrSerO1k9M71-4_qnmarCkHcXdIqMbBeGM8pvznJHMYk3eNMd_rwh/s16000/NBomber3.png" /></a></div> <div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlNdAQPFRXrSbQ89QaXNdOenq8y0CCavEi6sFv0FDFly74g7b4sx4Ma0s4xtbOej94civpP1O-B-ZXWVRxLuUvDfxPlOE0heQ-cZtiix9VGwZdzhUDfNlZZ6DO_7xzld-BNVHfltL6Q4tL9_0EDQP9EekNXab0W5qabUVHNE_RxoB7JtW9J1ydMfEgH_rH/s3672/NBomber4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1888" data-original-width="3672" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlNdAQPFRXrSbQ89QaXNdOenq8y0CCavEi6sFv0FDFly74g7b4sx4Ma0s4xtbOej94civpP1O-B-ZXWVRxLuUvDfxPlOE0heQ-cZtiix9VGwZdzhUDfNlZZ6DO_7xzld-BNVHfltL6Q4tL9_0EDQP9EekNXab0W5qabUVHNE_RxoB7JtW9J1ydMfEgH_rH/s16000/NBomber4.png" /></a></div><p></p> <p>Want to learn more? Check out this video introduction:</p> <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" frameborder="0" height="630" src="https://www.youtube.com/embed/Z51PyZvZNF8?si=uNfULUYzPY23PkC4" title="YouTube video player" width="1120"></iframe>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-87668533173895295842024-03-06T06:57:00.014+01:002024-03-06T06:57:00.159+01:00Performance test your ASP.NET Core app using Bombardier<p>In the past I’ve always used <a href="https://httpd.apache.org/docs/2.4/programs/ab.html">Apache Bench</a>, Fiddler or Visual Studio Load Testing to test the performance of my ASP.NET (Core) applications and API's. Recently I made the switch to <a href="https://github.com/codesenberg/bombardier">Bombardier</a>, a versatile HTTP benchmarking tool written in Go.</p> <p><a href="https://github.com/codesenberg/bombardier"><img src="https://raw.githubusercontent.com/codesenberg/bombardier/master/img/logo.png" /> </a>If you have Go <strong>Go</strong> installed on your system, you can install Bombardier using the following command:</p> <p><code>go get -u github.com/codesenberg/bombardier</code></p> <p>But you can also download a binary compatible to your OS directly from the <a href="https://github.com/codesenberg/bombardier/releases">releases</a> without the need to install Go.</p> <p>I already started my ASP.NET Core application:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYxgNvYh9igZjKmxKpDXm_nPTQlJV4FF02P8TWv77PhyphenhyphenKM_qnUm8VhrDWkGXRfPAekv9OYmxIcND1_hrLNuAxPS4_cd81cDvqvL86CazYutpJYXjWEucsBgfdKB_tMYo74s-6GfZMOFFrtNPzF5h0pAMRGqhCJFWmMT1_O3Z6tIuhaAcYPxF_4sTUOKvPV/s2350/Bombardier1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1226" data-original-width="2350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYxgNvYh9igZjKmxKpDXm_nPTQlJV4FF02P8TWv77PhyphenhyphenKM_qnUm8VhrDWkGXRfPAekv9OYmxIcND1_hrLNuAxPS4_cd81cDvqvL86CazYutpJYXjWEucsBgfdKB_tMYo74s-6GfZMOFFrtNPzF5h0pAMRGqhCJFWmMT1_O3Z6tIuhaAcYPxF_4sTUOKvPV/s16000/Bombardier1.png" /></a></div> <p>Running the tool is quite easy. Invoke the bombardier command with the target URL and some extra parameters:</p> <p><code>bombardier -c 100 -n 1000 –l http://localhost:5042/WeatherForecast</code></p> <p>This command will send 1000 requests with 100 concurrent connections to your application.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHjw-jlLrNO69bly2Lj2-jAkpxkIrJdLo3BIFi8nMvXsdOle_L9LALz_9IuUcMYmxEa_k-FVpg4sorKb2q04xSLWzcs57sxj0Z_LP-ljEScQ8kW2ZY8xlfjl47HpT6pAa6HqagYV8HHazTr55z2BQyAqac89RlpZoqnxWlNs7-GXxLYmaMDhsUZZNcjdHV/s2350/Bombardier2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1226" data-original-width="2350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHjw-jlLrNO69bly2Lj2-jAkpxkIrJdLo3BIFi8nMvXsdOle_L9LALz_9IuUcMYmxEa_k-FVpg4sorKb2q04xSLWzcs57sxj0Z_LP-ljEScQ8kW2ZY8xlfjl47HpT6pAa6HqagYV8HHazTr55z2BQyAqac89RlpZoqnxWlNs7-GXxLYmaMDhsUZZNcjdHV/s16000/Bombardier2.png" /></a></div><p></p> <p>Bombardier will provide detailed statistics:</p> <ul> <li><strong>Reqs/sec</strong>: Requests per second. </li> <li><strong>Latency</strong>: Average response time. </li> <li><strong>Latency Distribution</strong>: Percentiles (50%, 75%, 90%, 95%, 99%) of response times.</li> </ul> <p>You can customize Bombardier by adjusting parameters like concurrency, duration, and headers. Next step is to integrate Bombardier into our CI/CD pipeline or use it at scale with tools like <a href="https://github.com/dotnet/crank">Crank</a>, but that is something I’ll leave for another post.</p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-91883980378020537512024-03-05T12:57:00.000+01:002024-03-05T12:57:00.145+01:00Using parameters in BenchmarkDotNet<p>Most of the code I write is library code aimed to help other developers be more productive. As a consequence, a lot of this code is part of the critical path of an application and is execute a lot. This means that performance is critical. To measure the impact of a code change, I like to use <a href="https://benchmarkdotnet.org/index.html">BenchmarkDotNet</a>, a powerful library for benchmarking .NET code.</p> <p><a href="https://benchmarkdotnet.org/index.html"><img src="https://raw.githubusercontent.com/dotnet/BenchmarkDotNet/ec962b0bd6854c991d7a3ebd77037579165acb36/docs/logo/logo-wide.png" /></a></p> <p>When of the benchmark tests I got was to compare the impact between logging enabled and disabled.</p> <script src="https://gist.github.com/wullemsb/1a6637672ae9d9fc4aab605af87b504f.js"></script> <p>I removed most the implementation in the code above but what is important is that the implementation of both benchmark methods was completely the same with only the addition of an <code>EnableLogging()</code> call as a difference.</p> <p>An alternative(read better) way to implement this, is by using parameters. I added an extra property <code>IsLoggingEnabled</code> and specified the values using the <code>[<a href="https://benchmarkdotnet.org/api/BenchmarkDotNet.Attributes.ParamsAttribute.html">Params</a>]</code> attribute:</p> <script src="https://gist.github.com/wullemsb/b559500c4acab70108572d0912f83256.js"></script> <p>The benchmark is run for every combination of parameters you’ve provided and nicely visualized in the results:</p> <p><font face="Courier New">// * Summary *</font></p> <font face="Courier New"></font> <p><font face="Courier New">BenchmarkDotNet v0.13.10, Windows 10 (10.0.19044.4046/21H2/November2021Update) (Hyper-V) <br /> Intel Xeon Gold 6154 CPU 3.00GHz, 1 CPU, 4 logical and 4 physical cores <br /> .NET SDK 8.0.101 <br />  [Host]     : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 <br />  DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2</font></p> <font face="Courier New"></font> <p> <br /><font face="Courier New"> | Method      | IsLoggingEnabled | Mean      | Error     | StdDev     | Gen0       | Gen1     | Allocated | <br /> |------------ |----------------- |----------:|----------:|-----------:|-----------:|---------:|----------:| <br /> | GetProducts | False            |  91.78 ms |  1.830 ms |   3.481 ms |   750.0000 | 250.0000 |   9.65 MB | <br /> | GetProducts | True             | 345.69 ms | 58.352 ms | 172.051 ms | 29333.3333 |        - | 362.61 MB |</font></p> <p>If you need more values or want to generate them dynamically, you can also use <code>[ParamsSource]</code>. You need to specify the name of a public method or property that provides the values (implementing <code>IEnumerable</code>). The source must be within the benchmarked type:</p> <script src="https://gist.github.com/wullemsb/37451cdb245d02c01c6f6f0c4c433cd6.js"></script> <h1>More information</h1> <p><a href="https://benchmarkdotnet.org/articles/features/parameterization.html">Parameterization | BenchmarkDotNet</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-31012073978048917892024-03-04T07:06:00.000+01:002024-03-04T07:06:00.147+01:00ConfigureAwaitOptions in .NET 8<p>If you have ever used <code>async</code>/<code>await</code> in C#, you probably also used the <code>ConfigureAwait</code> method. In this post I want you to show how this method got some enhancements in .NET 8. But before I do that let's first recap the original behavior (just in case you forgot). </p> <h4>Understanding ConfigureAwait in .NET</h4> <p>The <strong>ConfigureAwait</strong> method in .NET plays a crucial role when awaiting tasks. It allows developers to configure how asynchronous operations resume after completion. Specifically, it determines whether the continuation should happen on the original captured context or on an available thread pool thread.</p> <h4>Original ConfigureAwait Behavior</h4> <p>When you await a task (such as <strong>Task</strong>, <strong>Task<T></strong>, <strong>ValueTask</strong>, or <strong>ValueTask<T></strong>), the default behavior is to capture a “context.” This context is either <strong>SynchronizationContext.Current</strong> or <strong>TaskScheduler.Current</strong> (falling back to the thread pool context if none is provided). The async method then resumes execution in that context when the task completes.</p> <p>You could originally use the <strong>ConfigureAwait</strong> in two ways:</p> <ul> <li><strong>ConfigureAwait(true)</strong>: This explicitly continues on the captured context. </li> <li><strong>ConfigureAwait(false)</strong>: Useful when you don’t want to resume on the captured context. It allows the async method to resume on any available thread pool thread.</li> </ul> <p><strong>ConfigureAwait(false) </strong>was typically used in library code because a library author could not make any assumptions on how the code would be consumed but you should generally avoid it in application code.. </p> <p>With improvements like dropping SynchronizationContext in ASP.NET Core, there has been a move away from <strong>ConfigureAwait(false)</strong>.</p> <p>Still there are some places where this method can be useful and with .NET 8,a new type <a href="https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.configureawaitoptions?view=net-8.0"><code>ConfigureAwaitOptions</code></a> is added that provides all the different ways to configure awaitables:</p> <ul> <li><strong>None</strong>: No specific configuration Equivalent to <strong>ConfigureAwait(false)</strong>.. </li> <li><strong>ContinueOnCapturedContext</strong>: Equivalent to <strong>ConfigureAwait(true)</strong>. </li> <li><strong>SuppressThrowing</strong>: Suppresses exceptions during await.</li> <li><strong>ForceYielding</strong>: Forces the await to always behave asynchronously</li> </ul> <script src="https://gist.github.com/wullemsb/453dbbc0e01859894bcbd18f5a666db8.js"></script> <p>Developers can now fine-tune their await behavior using these options. Whether you’re building libraries or applications, understanding and leveraging <strong>ConfigureAwaitOptions</strong> will empower you to write more efficient and context-aware asynchronous code.</p> <p>For more details, I would recommend to check out Stephen Cleary’s insightful blog post on <a href="https://blog.stephencleary.com/2023/11/configureawait-in-net-8.html">ConfigureAwait in .NET 8</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-72017248019158367092024-03-01T21:42:00.000+01:002024-03-01T21:48:49.461+01:00Azure Static Web App–Assign roles through an Azure Function<p>As a follow-up on the presentation I did at <a href="https://bartwullems.blogspot.com/2023/12/cloudbrew-2023azure-static-web-apps.html">CloudBrew</a> about Azure Static Web Apps I want to write a series of blog posts.</p> <ul> <li><a href="https://bartwullems.blogspot.com/2023/12/azure-static-web-apps-vs-code-extension.html">Part I - Using the VS Code Extension</a></li> <li><a href="https://bartwullems.blogspot.com/2023/12/azure-static-web-apps-using-astro.html">Part II - Using the Astro Static Site Generator</a></li> <li><a href="https://bartwullems.blogspot.com/2023/12/azure-static-web-appdeploying-to.html">Part III  – Deploying to multiple environments</a></li> <li><a href="https://bartwullems.blogspot.com/2023/12/azure-static-web-apppassword-protect.html">Part IV – Password protect your environments</a></li> <li><a href="https://bartwullems.blogspot.com/2023/12/azure-static-web-apptraffic-splitting.html">Part V – Traffic splitting</a></li> <li><a href="https://bartwullems.blogspot.com/2024/01/azure-static-web-app-authentication.html">Part VI – Authentication using pre-configured providers</a></li> <li><a href="https://bartwullems.blogspot.com/2024/01/azure-static-web-app-application.html">Part VII – Application configuration using staticwebapp.config.json</a></li> <li><a href="https://bartwullems.blogspot.com/2024/01/azure-static-web-appapi-configuration.html">Part VIII – API Configuration</a></li> <li><a href="https://bartwullems.blogspot.com/2024/01/azure-static-web-appinject-snippets.html">Part IX – Injecting snippets</a></li> <li><a href="https://bartwullems.blogspot.com/2024/02/azure-static-web-appcustom.html">Part X – Custom authentication</a></li> <li><a href="https://bartwullems.blogspot.com/2024/02/azure-static-web-appauthorization.html">Part XI – Authorization</a></li> <li>Part XII(this post) -  Assign roles through an Azure function</li> </ul> <p>We ended last post about Azure Static Web Apps talking about authorization and you can use role based security by assigning either a built-in role or a custom role. I showed how you could use invitations to assign a custom role.</p> <p>Today I want to show a second option to assign a custom role using an Azure Function.</p> <p>We start by creating an Azure Function that will be responsible for assigning roles. Every time a user successfully authenticates with an identity provider, the POST method calls the specified function. The function passes a JSON object in the request body that contains the user's information from the provider. </p> <script src="https://gist.github.com/wullemsb/697e9eeff1d266635d5ad2dd211c9bd0.js"></script> <p>Once we have our function, we need to configure the static web app to use this function. This can be done by setting the <code>rolesSource</code> value of the <code>auth</code> section in our <code>staticwebapp.config.json</code> file:</p> <script src="https://gist.github.com/wullemsb/936047c620dc2f528cb2b4431c3e2cde.js"></script> <p>If we now authenticate inside our application and call the .auth/me endpoint afterwards, we should see the custom roles coming from the api:</p> <script src="https://gist.github.com/wullemsb/3c2c8b14a70611a8c7010a588d15d63a.js"></script> <h1>More information</h1> <p><a href="https://learn.microsoft.com/en-us/azure/static-web-apps/authentication-custom?tabs=aad%2Cfunction#manage-roles">Custom authentication in Azure Static Web Apps | Microsoft Learn</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-62805336403897290702024-02-29T06:57:00.000+01:002024-02-29T06:57:00.130+01:00EF Core–.NET 8 update<p>The .NET 8 release of Entity Framework Core offers a large list of new features. The goal of this post is not to walk you through all these features, therefore you can have a look at the <a href="https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew">What's new page on Microsoft Learn</a>, instead I want to talk about a specific feature as a follow-up on the previous posts about EF Core I did this week.</p> <p>In those posts I talked about the <a href="https://learn.microsoft.com/en-us/ef/core/querying/sql-queries">FromSql</a> method to use your handwritten SQL statements to fetch EF Core entities. What I didn’t mention is that this only worked for entities that were registered as an entity to the DbContext. </p> <p>Starting with the EF Core 8 release, this condition has been removed, allowing us to create any SQL statements you want and map them to C# objects.</p> <p>This means that EF Core can now become an alternative to micro-ORM’s like <a href="https://github.com/DapperLib/Dapper">Dapper</a>. Of course there is maybe still a performance difference(I’ll do a benchmark and share the results) but feature wise this is a great addition.</p> <p>To use this feature, we need to call the <code>SqlQuery<T></code> method:</p> <script src="https://gist.github.com/wullemsb/2753fd9b10aba836559b8e2d9df6073a.js"></script> <p>The cool thing is that you can combine this with Linq statements. EF Core will generate a SQL query that combines your handwritten statement with generated SQL:</p> <script src="https://gist.github.com/wullemsb/8a2ab42ffed18fccf0a98044d2029f24.js"></script> <h1>More information</h1> <p><a href="https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew">What's New in EF Core 8 | Microsoft Learn</a></p> <p><a href="https://learn.microsoft.com/en-us/ef/core/querying/sql-queries">SQL Queries - EF Core | Microsoft Learn</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-36062037005346966012024-02-28T06:59:00.000+01:002024-02-28T06:59:00.131+01:00EF Core - System.InvalidOperationException : The required column 'Id' was not present in the results of a 'FromSql' operation.<p>Yesterday I talked about an error I got when using the <code>FromSql</code> method in Entity Framework Core(EF Core). It allows you to execute raw SQL queries against a relational database. Here is the example I was using yesterday:</p> <script src="https://gist.github.com/wullemsb/795aed6e9f898e5c56639f6a6c85a2a8.js"></script> <p><strong>Remark:</strong> The <code>FromSql</code> method was introduced in EF Core 7.0. In older versions, you should use <code>FromSqlInterpolated</code> instead.</p> <p>However when we tried to execute the code above it failed with the following error message:</p> <p><font face="Courier New">System.InvalidOperationException : The required column 'Id' was not present in the results of a 'FromSql' operation.</font></p> <p>You typically get the  error message above when there isn’t a matching column found in the SQL result for every property in your entity type. </p> <p>In our case the query was using a ‘SELECT *’ so I couldn’t be that we missed a column. To explain what was causing the issue, I first have to show our entity type and mapping class:</p> <script src="https://gist.github.com/wullemsb/166cabba22e31620b38a8db614ab98d8.js"></script> <p>As you can see there is a difference between the name of one of our columns(ProductID) and the property (Id). When you are using the LINQ syntax to query your data, EF Core will correctly translate between the two. However when you try to use the <code>FromSql</code> method, it will not use the mapping configuration and the returned column names should perfectly match with the property names.</p> <p>To fix it, I updated the query to return an Id column:</p> <script src="https://gist.github.com/wullemsb/98045e19843298ab5ed35573f1dbe4ec.js"></script>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-46410163962043801452024-02-27T06:49:00.000+01:002024-02-27T06:49:00.149+01:00EF Core - Cannot convert from 'string' to 'System.FormattableString'<p>While doing a pair programming session with a new developer in one of my teams, we ended up with a compiler error after writing the following code:</p> <script src="https://gist.github.com/wullemsb/224fa5c246d1f81f1341946827d800ff.js"></script> <p>This is the error message we got:</p> <p><font face="Courier New">Argument 2: cannot convert from 'string' to 'System.FormattableString'</font>  </p> <p>The fix was easy just add a ‘$’ before the query:</p> <script src="https://gist.github.com/wullemsb/795aed6e9f898e5c56639f6a6c85a2a8.js"></script> <p>However it would not have been a good pair programming session if we didn’t drill down further into this.</p> <h1>What is a FormattableString?</h1> <p>A <a href="https://learn.microsoft.com/en-us/dotnet/api/system.formattablestring?view=net-8.0">FormattableString</a><strong></strong> in C# is a type introduced in .NET 4.6. It represents a composite format string, which consists of fixed text intermixed with indexed placeholders (format items). These placeholders correspond to the objects in a list. The key features of FormattableString are:</p> <ol> <li> <p><strong>Capturing Information Before Formatting</strong>:</p> <ul> <li>A FormattableString captures both the format string (similar to what you’d pass to <code>string.Format</code>, e.g., <code>"Hello, {0}"</code>) and the arguments that would be used to format it. </li> <li>Crucially, this information is captured <strong>before</strong> actual formatting occurs.</li> </ul> </li> <li> <p><strong>Adjustable Formatting</strong>:</p> <ul> <li>You can adjust the formatting behavior of a FormattableString, such as performing formatting in the invariant culture. </li> <li>This is useful when you want to control how the string is formatted without actually performing the formatting immediately.</li> </ul> </li> <li> <p><strong>Usage with Interpolated Strings</strong>:</p> <ul> <li>When you use an interpolated string (e.g., <code>$"Hello, {name}"</code>), the compiler will create a FormattableString if you assign it to an <code>IFormattable</code> variable. </li> <li>The <code>IFormattable.ToString(string, CultureInfo)</code> implementation of the interpolated string will use FormattableString</li> </ul> </li> </ol> <p>The last key feature explains why adding a ‘$’ sign and creating an interpolated string fixes the compiler error. </p> <h1>FormattableString and EF Core</h1> <p>But that doesn’t explain yet why the EF Core team decided to expect a <code>FormattableString</code> instead of a ‘normal’ string when calling the <code>FromSql()</code> method. </p> <p>The reason a <code>FormattableString</code> is expected is because it helps you to safe guard against SQL injection, as they integrate parameter data as separate SQL parameters. </p> <p>Let's have a look at the following example:</p> <p><script src="https://gist.github.com/wullemsb/c471b87020a0dacae30937f8cd15635a.js"></script></p> <p>Although the query above looks like regular C# string interpolation, the categoryId value is wrapped in a DbParameter and the generated parameter name inserted where the placeholder was specified. </p> <p>This is the query that got executed in the database:</p> <script src="https://gist.github.com/wullemsb/5af06a1f2e672f00284ef4f6eea03563.js"></script> <p>This makes FromSql safe from SQL injection attacks, and sends the value efficiently and correctly to the database.</p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-43554990929004231642024-02-26T06:27:00.001+01:002024-02-26T06:27:00.122+01:00Dapper - Return dynamic data<p>For simple data fetching scenario's, I tend to keep away from Entity Framework and go the micro-ORM approach using libraries like <a href="https://github.com/DapperLib/Dapper">Dapper</a>.</p> <p>Today I had a small use case where I wanted to return some data using <a href="https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/interop/using-type-dynamic">dynamic types</a> in C# and handle it like a list of key-value pairs instead of using strongly typed objects.  The good news is that this is supported out-of-the-box in Dapper. </p> <p>Dapper gives you multiple overloads that returns a <code>dynamic</code> type. Here is an example using <code>Query()</code>:</p> <script src="https://gist.github.com/wullemsb/586d93232a78638720668419478f54dc.js"></script> <p>Simple but effective!</p> <p><strong>Remark: </strong>If you are not really looking for a dynamic result set but don’t want to create a class or record type, you can also use value tuples to map the returned rows:</p> <script src="https://gist.github.com/wullemsb/48bd9ce15dd6cbb167243c538588b4ba.js"></script> <h1>More information</h1> <p><a href="https://dappertutorial.net/result-anonymous">Dapper Anonymous Result - Learn How to Map Result to Dynamic Object (dappertutorial.net)</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-77326731281926207682024-02-23T07:07:00.003+01:002024-02-23T07:07:00.127+01:00Implementing the decorator pattern using System. Reflection.DispatchProxy<p>If you are new to the decorator pattern, let me start with a short explanation:</p> <blockquote> <p><strong>Decorator</strong> is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.</p> </blockquote> <p>A common use case for decorators is to implement <strong>Aspect Oriented Programming</strong>(AOP) which allows you to implement cross-cutting concerns like logging, caching, …</p> <p><img src="https://lh3.googleusercontent.com/-0iMZbMWESmqUvqDAAkutqIjoqw0ErVKaZq-bFltx2eZITyS78rdLt7rijLCNnqgrSekF1ZK3QN4UpgYJfqqixo86RzkjSuqEjZM5q0EaciPnTfQg80qkzpwcA0A4gAX-owYVfhEv3ahxkIkNlo4ZPn3YBXbWsiqMqzMqxcFmTXwVuGJ2cNOPulYM7lKNqXEEOpViMMcpjXz7ddvflt8olOl2wm4ylSP9dvUn36HsAnwk0PG1TOXuaWT75lSFL-v4gpdnBXTmnAukJezukHI_9BJi8mFSiNXGByzUFBdVeppPxOB3tz2dt2JMds_lLd3iDG8Hves1QB5mC4GKCWjYn0PZlRP_bg0Q3etSNkFd_F2SCf-7tqvBZJzmVh3wE5omlO-AD828qkf-5aHPddfLqnSSBTkOH18KwbGFApgabXeocTMmqBSZ3b9sOx30ia8XyQlab4sYut8JdUSToCM9Jen-EvWXENHsAAqIrJXB1nsQU_NdWwT3PZe6Xui2xO0SmvpnS7kCStVWfK9m_byMLlRbJ7HyBFENHzgf1EwKaYfs-nPtLi6qc--aiiHSM41uGgUN_JO5N0m8zcv_8jtnXMmVCDIKDivPche8cheQsDFHwIkVqDC0pS_I2woQAlTjmxrGCvLgicw7RGP6Du8zXroggorRVkko59Bpr_5DHOVZplTbqx6zqxNlBaJEplLEDu_BFVLMNvP3GtUYQHAek36DmAQXguYPmLOUy6S7OaX1XczaBmRcy7Wva0Rn4ErRExJ4HGpbtwuiBR9AYTC2Qgy2PEwajpbuefQrv6-QbPz_mlrgqop8uqLjG1ylOxjBob6A_iju2GUZefRC9Qmx8DwmoraN6puEzuh_Z6AjYnvjxP3AOst5pApyKkuob-D29dc5wgHEkS2HIfwHfMtwjAmf45co1alMgFTXK9AnbQQ=w1280-h800-s-no?authuser=0" /></p> <p>There are multiple ways to implement this pattern, you can manually implement it, <a href="https://bartwullems.blogspot.com/2020/03/autofac-decorators.html">use your DI container</a>, <a href="https://bartwullems.blogspot.com/2022/12/use-power-of-source-generators-with.html">use a source generator</a> to write the boilerplate code or <a href="https://bartwullems.blogspot.com/2023/04/applying-decorator-pattern-in-net-core.html">use a dynamic proxy</a> that wraps call to the original class. </p> <p>It’s this last approach I want to focus on in this blog post. You could use the great <a href="http://www.castleproject.org/projects/dynamicproxy/"><code>Castle.DynamicProxy</code></a> library but for simpler use cases, there is a built-in alternative through the <a href="https://www.nuget.org/packages/System.Reflection.DispatchProxy/4.5.1"><code>System.Reflection.DispatchProxy</code></a> class. </p> <p>Let’s have a look at a small code example on how to use this class.</p> <p>First we need to create a Decorator class that implements <code>DispatchProxy</code>:</p> <script src="https://gist.github.com/wullemsb/2efbc9001a3db35599f675a73130b54a.js"></script> <p><strong>Remark:</strong> Be aware that we cannot use constructor injection to inject parameters(like the logger in the example above).</p> <p>We could now use the <code>DispatchProxy.Create()</code> method but I typically create a static method that allows me to wrap an existing class instance</p> <script src="https://gist.github.com/wullemsb/0164ce249668ec9e1943301b4a654785.js"></script> <p>Let's create an interface, corresponding class and apply the decorator:</p> <script src="https://gist.github.com/wullemsb/a5ef0b6a05b00f457cf8eb104c4eeb80.js"></script> <p>If we now run this code, the result looks like this:</p> <p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiRxXeANA2YEzpGmjf56q2WXjhGqytsaRa8sam8fGOqmdiSG5HmgQegB_mbKoq85UkH3MmXMFdUnjErp9fuMVb87YmAEJfCZND7lPMzqELNLLqESnimMN-AZpX5rM13Rh1u22IHusDJ8ZrgEEAhyphenhyphenIgRYW348lrnDDme9FIIKJLybjHgaKkP7u6bRmCZ7YQ/s2350/Decorator.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1286" data-original-width="2350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiRxXeANA2YEzpGmjf56q2WXjhGqytsaRa8sam8fGOqmdiSG5HmgQegB_mbKoq85UkH3MmXMFdUnjErp9fuMVb87YmAEJfCZND7lPMzqELNLLqESnimMN-AZpX5rM13Rh1u22IHusDJ8ZrgEEAhyphenhyphenIgRYW348lrnDDme9FIIKJLybjHgaKkP7u6bRmCZ7YQ/s16000/Decorator.png" /></a></div><p></p> <p>Sweet!</p><p>There are some pros and cons when using this approach:</p> <ul> <li><strong>Pros</strong> <ul> <li>Works with .NET Standard 1.3+ (so it works with both .NET Framework 4.6+ and .NET Core/.NET 5). </li> <li>No 3th party libraries needed.</li> <li>Proxies by wrapping the target object, so a proxy can be created around an already existing object.</li> </ul> </li> <li><strong>Cons</strong> <ul> <li>Proxies interfaces, not classes, so proxied types must implement an interface and access to any members not in the interface (like fields) is complicated. </li> <li>Uses reflection, so not usable with AOT. </li> <li>No support for async methods/functions.</li> </ul> </li> </ul> <h1>More information</h1> <p><a href="https://learn.microsoft.com/en-us/dotnet/api/system.reflection.dispatchproxy?view=net-8.0">DispatchProxy Class (System.Reflection) | Microsoft Learn</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-38970147714284004522024-02-22T07:36:00.004+01:002024-02-22T07:36:00.140+01:00AddConsole is obsolete: This method is retained only for compatibility<p>While working on a POC I got the following warning/error when trying to add some logging:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUizLVeu1ETaJ8nshK-pMxIn1UlxXDmPoQjgcXcHFguWfUbNf1cVEMXb2nqcJ93oE_bVUp44Y7D3qNamuAY4V9VBiRqEYrdOvL3V2ziXPXVx0MXl290hJiJDKyOoMVBFWvOeN_BrooLGPWHwrgzrNnpGW2TjmYY2ZAEX-DT9nKGA2g_6UOVstdTANPo0Js/s3840/ObsoleteWarning.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2066" data-original-width="3840" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUizLVeu1ETaJ8nshK-pMxIn1UlxXDmPoQjgcXcHFguWfUbNf1cVEMXb2nqcJ93oE_bVUp44Y7D3qNamuAY4V9VBiRqEYrdOvL3V2ziXPXVx0MXl290hJiJDKyOoMVBFWvOeN_BrooLGPWHwrgzrNnpGW2TjmYY2ZAEX-DT9nKGA2g_6UOVstdTANPo0Js/s16000/ObsoleteWarning.png" /></a></div> <p>Here is the exact error message:</p> <p><font face="Courier New">CS0619: ‘ConsoleLoggerExtensions.AddConsole(ILoggerFactory)’ is obsolete: ‘This method is retained only for compatibility. The recommended alternative is AddConsole(this ILoggingBuilder builder).</font></p> <p>There are multiple ways to get rid of this warning, but I solved it by using the following code:</p> <script src="https://gist.github.com/wullemsb/cc656273034620d6b0345a93fa509c8f.js"></script> <p>Happy coding!</p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-48242303742552478222024-02-21T07:20:00.002+01:002024-02-21T07:20:00.125+01:00Visual Studio Presentation Mode<p>I think we all have seen presentations where Visual Studio was used during the demos but were we had a hard time because the fonts were too small in the editor, Solution Explorer and menu items, I have to admit that I have been guilty making this same mistake.</p> <p>I recently discovered that Visual Studio has a Presentation Mode. Presentation Mode is a feature that lets you open an instance of Visual Studio that looks like a fresh install, without any customizations, extensions, or settings synchronization. This way, you can avoid any distractions or confusion that may arise from your personal preferences or environment. You can then optimize any settings that are relevant for your presentation, such as font sizes, themes, window layouts, and keyboard shortcuts. These settings will be preserved for the next time you use Presentation Mode.</p> <p>To enter presentation mode, open a Developer Command Prompt and execute the following command:</p> <p><code>devenv /RootSuffix DemoMode</code></p> <p><strong>Remark:</strong> You can swap the word <code>DemoMode</code> with whatever other word you want to create yet another isolated instance type.</p> <p>This will launch a new instance of Visual Studio with all the default settings. Now we customize our Visual Studio settings to optimize them for presentation purposes (e.g. increase the font size, change the window layout, …). These settings will be saved for the next time you open a Visual Studio instance using the same RootSuffix.</p> <p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgS1hD3SEwcn-aD-aoWgLnFgRyBr-bAhe3G1Ay7jf0Ouf9o3EWeBg-hApIPc4fS1tT_HUMCHX2cXSRX-sEU7_EaSDoQC61lI91tGMm1KI0ghi_Sk_PXxkg30bIQQEZZdfLY4dpZGbbkNa2KLR4_nmhyphenhyphenl3pWfhmbTzeawWdFiJIFL3Ad7pqqnEp3LMWH9Ghd/s1796/PresentationMode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1180" data-original-width="1796" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgS1hD3SEwcn-aD-aoWgLnFgRyBr-bAhe3G1Ay7jf0Ouf9o3EWeBg-hApIPc4fS1tT_HUMCHX2cXSRX-sEU7_EaSDoQC61lI91tGMm1KI0ghi_Sk_PXxkg30bIQQEZZdfLY4dpZGbbkNa2KLR4_nmhyphenhyphenl3pWfhmbTzeawWdFiJIFL3Ad7pqqnEp3LMWH9Ghd/s16000/PresentationMode.png" /></a></div><br /><p></p> <p>Nice!</p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-72406184780755881202024-02-20T06:55:00.027+01:002024-02-20T06:55:00.146+01:00Visual Studio–Share your settings<p>In VSCode you can share your settings <a href="https://bartwullems.blogspot.com/2023/09/vs-codeshare-your-settings-using.html">through profiles</a>. This allows you to easily apply your UI layout, settings and extensions to multiple VSCode instances. </p> <p>A similar thing is possible in Visual Studio. Settings can be exported through the Import and Export Settings Wizard:</p> <ul> <li>Go to<strong> Tools</strong> –> <strong>Import and Export Settings</strong></li></ul><ul> <li>Choose <strong>Export selected environment settings</strong> and click on <strong>Next ></strong><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib6Lf2c_G1ALzKGsPaghByB-SSf6BUGMBMxy9rgaSagQFprQPW1NchDs3twE5MnJPq7-uI_9kJgx-FnpsWxVo6HkJnXR2y7PipHFqAu4cLI3e-8ZlVjRqlQJX1mfEotgEI5iDREOBsmEo8orUXMB7JX-GfhZVkzuvnA7RjyxVSVIlbvunPjTYrNYnQVzBK/s1116/Settings1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1062" data-original-width="1116" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib6Lf2c_G1ALzKGsPaghByB-SSf6BUGMBMxy9rgaSagQFprQPW1NchDs3twE5MnJPq7-uI_9kJgx-FnpsWxVo6HkJnXR2y7PipHFqAu4cLI3e-8ZlVjRqlQJX1mfEotgEI5iDREOBsmEo8orUXMB7JX-GfhZVkzuvnA7RjyxVSVIlbvunPjTYrNYnQVzBK/s16000/Settings1.png" /></a></div></li></ul><ul> <li>Now you can choose which settings should be exported. Check or uncheck the settings you want to export and click on <strong>Next ></strong><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBgwCMEAtsZhx0xZjWFmBkJ-e53XaU3jU4aeL1QoljzdGkyetbc2SiRTpSMNGoMROEY9eSkjTUMsiUUrXY2n28sibEVeWYF_Z-jtzmtFWUXvKsDNABCkxclcNcIq6vI7sgRltpiTEFIhKPc_Fgkar2tGZUyUxa5QHHhyphenhyphenmTSbFlaYJ1O1YcNvzCMsNGEOW4/s1116/Settings2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1062" data-original-width="1116" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBgwCMEAtsZhx0xZjWFmBkJ-e53XaU3jU4aeL1QoljzdGkyetbc2SiRTpSMNGoMROEY9eSkjTUMsiUUrXY2n28sibEVeWYF_Z-jtzmtFWUXvKsDNABCkxclcNcIq6vI7sgRltpiTEFIhKPc_Fgkar2tGZUyUxa5QHHhyphenhyphenmTSbFlaYJ1O1YcNvzCMsNGEOW4/s16000/Settings2.png" /></a></div></li></ul><ul> <li>Specify where you want to store your .vssettingsfile file and click on <strong>Finish</strong></li></ul><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiT3a_LMOJ5TfVV4PBgYRIzM7GexRuNdt-w8FWsgtFNXmfLgq5cwfm1VAQ4SOQQVSORXdA7PQltSOglXbc-Ar7wT410KM0FouaoMGuEDAYwd6jVRCRnRX3RHb6s857efJJmIhqd_l1o4iYt1flfXJMKJ0sJCId9n1YPS6mI7S4eln5woamI9hFllC14aQCy/s1116/Settings3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1062" data-original-width="1116" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiT3a_LMOJ5TfVV4PBgYRIzM7GexRuNdt-w8FWsgtFNXmfLgq5cwfm1VAQ4SOQQVSORXdA7PQltSOglXbc-Ar7wT410KM0FouaoMGuEDAYwd6jVRCRnRX3RHb6s857efJJmIhqd_l1o4iYt1flfXJMKJ0sJCId9n1YPS6mI7S4eln5woamI9hFllC14aQCy/s16000/Settings3.png" /></a></div></div><ul> <li>You can now close the wizard. </li> </ul> <p><strong>Remark:</strong> When you sign in to Visual Studio on multiple computers using the same personalization account, your settings can be synchronized across the computers.</p> <p>Although the .vssettings file allows you to share a lot of configuration settings, it cannot be used to share the installed features and extensions. However this is possible through an installation configuration file(.vsconfig) and the Visual Studio Installer. </p> <ul> <li>To export a configuration open the <strong>Visual Studio Installer<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuDcOdwaxUwtPNVRXHR-YmDxyrvx-8Km-rQyhvRKbsuReDUNWHOzwpjCysh8hIXOFix_8e3nlyuTvi_A7kB_SvdFEO6JAIOIJg-eWOOSbe2CVH0316mU0hZ115dr7akOlw1Wg8GnAlDC4KtDM6X8e2ie8QRJLqpYWQ_D2H-LpZM7XimGzp3vQJQpudz0HK/s2560/Settings4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1440" data-original-width="2560" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuDcOdwaxUwtPNVRXHR-YmDxyrvx-8Km-rQyhvRKbsuReDUNWHOzwpjCysh8hIXOFix_8e3nlyuTvi_A7kB_SvdFEO6JAIOIJg-eWOOSbe2CVH0316mU0hZ115dr7akOlw1Wg8GnAlDC4KtDM6X8e2ie8QRJLqpYWQ_D2H-LpZM7XimGzp3vQJQpudz0HK/s16000/Settings4.png" /></a></div></strong></li></ul><ul> <li>Click on <strong>More</strong> on the product card and choose <strong>Export configuration</strong></li></ul><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmZytIMQ-VIX4_weg6etNtHoxLLcnx08lFRPXS56gC_okw_JxLguKYdSuw1pSLz_QgG9uJLpjdNEX9WZMtB9eUvMiOea6O7tCL0TRBLatCBRgJkIGaDqMxiavrjmpGX-8FfHljMsSK2HYKUXUqxSxdqVhs7_8WXLDGQvlEAt_WKC40eAtP8r_7U9ZpaXv_/s612/Settings5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="612" data-original-width="592" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmZytIMQ-VIX4_weg6etNtHoxLLcnx08lFRPXS56gC_okw_JxLguKYdSuw1pSLz_QgG9uJLpjdNEX9WZMtB9eUvMiOea6O7tCL0TRBLatCBRgJkIGaDqMxiavrjmpGX-8FfHljMsSK2HYKUXUqxSxdqVhs7_8WXLDGQvlEAt_WKC40eAtP8r_7U9ZpaXv_/s16000/Settings5.png" /></a></div></div><ul> <li>Specify the location and click on <strong>Review details</strong><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhC_W5GqaGKM_KUjcJq1tftsC08p-TxYz0LDl6JUld2-NVF_jmB6661r4eiwskLtebrbJIZ1THky9gmZTgzucltKSaafrN-KVNi-RaPfdBXsWQPuZ1uVr434HVv7JgrDFv2Hv6n-M2NGfINcvo7V5yzTZV1z3DA1x_OIwAd0UO1vgfxvBGPtR4PrDd07Z6b/s1148/Settings6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="718" data-original-width="1148" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhC_W5GqaGKM_KUjcJq1tftsC08p-TxYz0LDl6JUld2-NVF_jmB6661r4eiwskLtebrbJIZ1THky9gmZTgzucltKSaafrN-KVNi-RaPfdBXsWQPuZ1uVr434HVv7JgrDFv2Hv6n-M2NGfINcvo7V5yzTZV1z3DA1x_OIwAd0UO1vgfxvBGPtR4PrDd07Z6b/s16000/Settings6.png" /></a></div></li></ul><ul> <li>Select or unselect specific components and click on <strong>Export<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgp9Y3OjPgvSyUEvebobuK_0eOWd8mZajM_v90RSyMUfc44Am8K7hIEcNFP9pn5DLSBByCdK_DtxSE0FckxtkgAD9mV377kalnMeNtmEUWkv52061xfbKt1tkSVnhnwdCeF_9e3SsP1RW3B95fVK7kpcbiOvrXA6mBdZq7_uYbwVHEK5u5rOdVGIurEgsRN/s2472/Settings7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1238" data-original-width="2472" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgp9Y3OjPgvSyUEvebobuK_0eOWd8mZajM_v90RSyMUfc44Am8K7hIEcNFP9pn5DLSBByCdK_DtxSE0FckxtkgAD9mV377kalnMeNtmEUWkv52061xfbKt1tkSVnhnwdCeF_9e3SsP1RW3B95fVK7kpcbiOvrXA6mBdZq7_uYbwVHEK5u5rOdVGIurEgsRN/s16000/Settings7.png" /></a></div></strong></li></ul> <p>Here is how the exported vsconfig file looks like:</p> <script src="https://gist.github.com/wullemsb/c94614c15bbc6336cbb3eaf3c6d2d3f6.js"></script> <p><strong>Remark:</strong> Support for extensions got included in <a href="https://devblogs.microsoft.com/visualstudio/visual-studio-2022-17-9-now-available/">Visual Studio 2022 v17.9</a>. As the feature is quite new, not everything is supported yet.</p> <p>Extensions can be added manually by specifying them in the extensions section in the vsconfig file:</p> <script src="https://gist.github.com/wullemsb/837927e9c2bf6c68dce7ca8e7bb60561.js"></script> <h1>More information</h1> <p><a href="https://learn.microsoft.com/en-us/visualstudio/ide/personalizing-the-visual-studio-ide?view=vs-2022">Customize & save your personal Visual Studio IDE settings - Visual Studio (Windows) | Microsoft Learn</a></p> <p><a href="https://learn.microsoft.com/en-us/visualstudio/ide/synchronized-settings-in-visual-studio?view=vs-2022">Synchronize settings across multiple computers - Visual Studio (Windows) | Microsoft Learn</a></p> <p><a href="https://learn.microsoft.com/en-us/visualstudio/install/import-export-installation-configurations?view=vs-2022">Import or export installation configurations | Microsoft Learn</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-21149238481015823072024-02-19T07:21:00.000+01:002024-02-19T07:21:00.131+01:00Property based testing in C#–CsCheck<p>Almost a year ago I wrote a series of blog posts on how to use property-based tests in C#.</p> <ul> <li><a href="https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-1.html">Part 1 – Introduction</a></li> <li><a href="https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-2.html">Part 2 – An example</a></li> <li><a href="https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-3.html">Part 3 – Finding edge cases</a></li> <li><a href="https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-4.html">Part 4 – Writing your own generators</a></li> <li><a href="https://bartwullems.blogspot.com/2023/02/property-based-testing-in-cpart-5.html">Part 5 – Locking input</a></li> </ul> <p>In this series I used FsC<a href="https://fscheck.github.io/FsCheck/">https://fscheck.github.io/FsCheck/</a>heck as the library of my choice. Although originally created for F#, it also works for C# as I have demonstrated.</p> <p><img alt="FsCheck logo - an inverted A, the mathematical symbol for forall." src="https://fscheck.github.io/FsCheck/img/logo.png" /></p> <p>However as it was originally created for F#, it sometimes feels strange when using FsCheck in C#. If you prefer a more idiomatic alternative, you can have a look at <a href="https://github.com/AnthonyLloyd/CsCheck">CsCheck</a>, also inspired by QuickCheck but specifically created for C#.</p> <p>CsCheck offers no specific integration but can be used with any testing framework(XUnit, NUnit, MSTest, …).</p> <p>Here is a small example:</p> <script src="https://gist.github.com/wullemsb/ae4f72b14f672b0440b02f8eeb092f24.js"></script> <p>CsCheck does it really well in the <a href="https://github.com/jlink/shrinking-challenge">shrinking challenge</a> and offers support for multiple types of tests including <a href="https://github.com/AnthonyLloyd/CsCheck?tab=readme-ov-file#Concurrency-testing">concurrency testing</a>.</p> <p>This is a feature I really like as concurrency related issues are really hard to find and debug. When writing this type of tests, you specify a set of operations that will be executed concurrently and then be compared with linearized execution.</p> <p>Here is an example:</p> <script src="https://gist.github.com/wullemsb/0220cd66dab91d4c428578a9e8cc0a9d.js"></script>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-39684875027976549242024-02-16T12:34:00.000+01:002024-02-16T12:35:27.131+01:00Share a private key without using passwords<p>If you follow security best practices, you are not re-using the same password for multiple purposes. As a consequence you end up with a long list of passwords that you need to secure and manage. Although the use of a password vault certainly improved the experience, I still try to avoid the usage of passwords as much as possible.</p> <p>Today I want to share a ‘trick’ I discovered that allows you to share(export/import) a PFX file without using passwords.</p> <p>No clue what a pfx is? Let me explain that first…</p> <p>A PFX file, also known as a <strong>PKCS#12 file</strong>, is a binary format used to store <strong>certificates</strong> and their associated <strong>private keys</strong>. It combines 2 parts:</p> <ol> <li><strong>A certificate part</strong>: A certificate is a digital document that contains information about an entity (such as a person, organization, or server). It is used for authentication, encryption, and secure communication. Certificates are issued by a <strong>Certificate Authority (CA)</strong>.</li> <li> <p><strong>A private key part</strong>: The private key is a cryptographic key that corresponds to the public key in the certificate. It is kept secret and is used for <strong>decrypting data</strong> encrypted with the public key, as well as for <strong>signing</strong> and <strong>authenticating</strong> messages.</p> </li> </ol> <p>A PFX file is typically password-protected to ensure security.They are commonly used for <strong>importing/exporting certificates</strong> between systems, such as web servers, application servers, and client devices.</p> <p>Of course this means another password we need to add to our list! </p> <p>The good news is that on a domain controlled Windows machine, you can use a passwordless approach.</p> <p><font face="Courier New">$a = Get-ChildItem -Path cert:\localMachine\my</font></p> <p><font face="Courier New">Export-PfxCertificate -Cert $a[1] -FilePath C:\myexport.pfx -ProtectTo "domain\username"</font></p> <p>By using <font face="Courier New">the –ProtectTo</font> argument when exporting the certificate, I can import it using the specified account without requiring a password.</p> <p><font face="Courier New">Set-Location -Path Cert:\LocalMachine\My </font></p> <p><font face="Courier New">Import-PfxCertificate -FilePath C:\mypfx.pfx</font></p> <h1>More information</h1> <p><a href="https://learn.microsoft.com/en-us/powershell/module/pki/export-pfxcertificate?view=windowsserver2022-ps">Export-PfxCertificate (pki) | Microsoft Learn</a></p> <p><a href="https://learn.microsoft.com/en-us/powershell/module/pki/import-pfxcertificate?view=windowsserver2022-ps">Import-PfxCertificate (pki) | Microsoft Learn</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-3038604688164668382024-02-15T07:06:00.004+01:002024-02-15T07:06:00.257+01:00The importance of the ubiquitous language<p>We all know that <a href="https://bartwullems.blogspot.com/2017/02/the-hardest-software-problemnaming.html">the hardest thing in software development is naming things</a>. Domain Driven Design tries to tackle this by focusing on the 'ubiquitous language'.</p> <p>The "ubiquitous language" refers to a shared language that is used by all team members, including domain experts, developers, and stakeholders, to discuss the domain and the software being developed. This language is designed to bridge the communication gap between technical and non-technical team members, ensuring that everyone has a clear understanding of the domain concepts and requirements.</p> <p>The ubiquitous language consists of domain-specific terms and concepts that are defined collaboratively and consistently used across all artifacts of the software development process, including code, documentation, and discussions. By using a common language, DDD aims to reduce misunderstandings and ambiguities, leading to more effective collaboration and better software designs.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFMqtvSIKyS0S4rxvg97U7HCUohViXz_pyzxK9NsT9dHZfYE8i8aGSTEWJ08K6vAuWxqQaOaFlyWyJP036V4ZW9HuVT5KvVM0-cN2HuRkTOB-AH6HhSFcbZgIn5NCv7A6QFWfwekZlWQcarCwWVCeokjc0mKWL4ZMUVOQ1t2gxymCo_q89EIlzSFNsdk-W/s1024/_4ca59372-3b93-42d1-8dd1-3c06fd0f1c49.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1024" data-original-width="1024" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFMqtvSIKyS0S4rxvg97U7HCUohViXz_pyzxK9NsT9dHZfYE8i8aGSTEWJ08K6vAuWxqQaOaFlyWyJP036V4ZW9HuVT5KvVM0-cN2HuRkTOB-AH6HhSFcbZgIn5NCv7A6QFWfwekZlWQcarCwWVCeokjc0mKWL4ZMUVOQ1t2gxymCo_q89EIlzSFNsdk-W/s16000/_4ca59372-3b93-42d1-8dd1-3c06fd0f1c49.jpg" /></a></div> <p>The best way to emphasize the importance of the ubiquitous language is with the following quote by <a href="https://en.wikipedia.org/wiki/Karl_Popper">Karl Popper</a>:</p> <blockquote> <p>Arguing over definitions is useless. Agreeing on definitions is priceless</p></blockquote>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.comtag:blogger.com,1999:blog-1882165764506799121.post-40971376958605594252024-02-14T07:22:00.024+01:002024-02-14T07:22:00.130+01:00Azure Static Web App–Authorization<p>As a follow-up on the presentation I did at <a href="https://bartwullems.blogspot.com/2023/12/cloudbrew-2023azure-static-web-apps.html">CloudBrew</a> about Azure Static Web Apps I want to write a series of blog posts.</p> <ul> <li><a href="https://bartwullems.blogspot.com/2023/12/azure-static-web-apps-vs-code-extension.html">Part I - Using the VS Code Extension</a></li> <li><a href="https://bartwullems.blogspot.com/2023/12/azure-static-web-apps-using-astro.html">Part II - Using the Astro Static Site Generator</a></li> <li><a href="https://bartwullems.blogspot.com/2023/12/azure-static-web-appdeploying-to.html">Part III – Deploying to multiple environments</a></li> <li><a href="https://bartwullems.blogspot.com/2023/12/azure-static-web-apppassword-protect.html">Part IV – Password protect your environments</a></li> <li><a href="https://bartwullems.blogspot.com/2023/12/azure-static-web-apptraffic-splitting.html">Part V – Traffic splitting</a></li> <li><a href="https://bartwullems.blogspot.com/2024/01/azure-static-web-app-authentication.html">Part VI – Authentication using pre-configured providers</a></li> <li><a href="https://bartwullems.blogspot.com/2024/01/azure-static-web-app-application.html">Part VII – Application configuration using staticwebapp.config.json</a></li> <li><a href="https://bartwullems.blogspot.com/2024/01/azure-static-web-appapi-configuration.html">Part VIII – API Configuration</a></li> <li><a href="https://bartwullems.blogspot.com/2024/01/azure-static-web-appinject-snippets.html">Part IX – Injecting snippets</a></li> <li><a href="https://bartwullems.blogspot.com/2024/02/azure-static-web-appcustom.html">Part X – Custom authentication</a></li> <li>Part XI(this post) – Authorization</li> </ul> <p>If we talk about authentication, we also have to talk about authorization. Authorization is role based and every user gets at least one role for free: the “anonymous” role. Once you are authenticated, a second role is assigned; the “authenticated” role.</p> <p>You can see this by browsing to the<font face="Courier New"> .auth/me</font> endpoint after authenticating. You’ll get a response back similar to this:</p> <script src="https://gist.github.com/wullemsb/539140b869793d8e53d4c1f84deab9a9.js"></script> <p>Roles can be assigned to specific routes in the <span style="font-family: courier;">staticwebapps.json.config</span> file:</p> <script src="https://gist.github.com/wullemsb/64f8a21439ebf78697d349aaaa65075e.js"></script> <p><strong>Remark: </strong>Roles are matched on an <em>OR</em> basis. If a user is in any of the listed roles, then access is granted.</p> <h1>Custom roles</h1> <p>These 2 roles are sufficient to get you started but sooner or later you want to assign custom roles. This can be done either through <strong>invitations</strong> or through an <strong>Azure Function</strong>(which I’ll show in the next post).</p> <p><strong>Invitations</strong> can be configured and sent from the Azure Portal, using provider-specific email addresses for the user. You get an invite link that can be send to potential users. Users can click on the invite link to login with that custom role assigned automatically.</p> <ul> <li>Go to your Static Web App in the Azure portal.</li> <li>Click on<strong> Role Management</strong>.</li></ul><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0mOp8pOvbRdxJ_VdlLy-4sr4nhHPTPYDQakDmNdja4XpTJO_HbeKFsd-ZKwDkumc7WmXWKsZOb-6cNkPhAA2BgL2un8YKIZaYmKNsu4gDlKcSTbXlU5_lQVMxy9QEgN4WmVO3Gntv6jkWq9at528wpdU1VXv0GFLz_nHA4sa8blW3Vwa7Rym_bpZ-poGC/s1102/Role1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1102" data-original-width="574" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0mOp8pOvbRdxJ_VdlLy-4sr4nhHPTPYDQakDmNdja4XpTJO_HbeKFsd-ZKwDkumc7WmXWKsZOb-6cNkPhAA2BgL2un8YKIZaYmKNsu4gDlKcSTbXlU5_lQVMxy9QEgN4WmVO3Gntv6jkWq9at528wpdU1VXv0GFLz_nHA4sa8blW3Vwa7Rym_bpZ-poGC/s16000/Role1.png" /></a></div><div><br /></div><ul> <li>Click on the <strong>Invite</strong> button.</li></ul><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCLSQjMkSct0MevYaCAQDbcWtsbXHMFCYmIhTGJtE2_4ujioQR3-IUNB6BYFLVEkmhOyBWV_oaDIGXZnIj0R0qPAJDmi5mukZHKDeeGR7uXY3T7sfUmlkScDlEplNaQizINZ3N0gmvjQIm0V6OYzdqjRfGTbwTOfANF-z6a1angcXUXry74CgqIbd0FY2s/s1292/Role2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="290" data-original-width="1292" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCLSQjMkSct0MevYaCAQDbcWtsbXHMFCYmIhTGJtE2_4ujioQR3-IUNB6BYFLVEkmhOyBWV_oaDIGXZnIj0R0qPAJDmi5mukZHKDeeGR7uXY3T7sfUmlkScDlEplNaQizINZ3N0gmvjQIm0V6OYzdqjRfGTbwTOfANF-z6a1angcXUXry74CgqIbd0FY2s/s16000/Role2.png" /></a></div></div><ul> <li>Now we need to enter a few details</li> <ul> <li>The built-in Authentication provider we want to use (Github or Azure AD)</li> <li>The email address of the user</li> <li>The domain name of the Static Web App</li> <li>The custom role we ant to assign</li> <li>The invitation expiration time(in hours)</li> </ul></ul><div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTvaBmqvkCZHBvKMPwojPXUqKo2ILUYTsHCMv6nshlcM04wIs-Un5KtIifBFPwllnlNmO4-Xz4koMbb1ONmOe9ewhLN5A2iWSwflt24tKmzApVzT4ki8JG4fMMHzXFGuEEjjkRgAAxP7r5tnyJwSNpv9YTVWogRnoqQ_IC5Viucrc_D0_Qt72uPRWh9drO/s1792/Role3.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1792" data-original-width="1282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTvaBmqvkCZHBvKMPwojPXUqKo2ILUYTsHCMv6nshlcM04wIs-Un5KtIifBFPwllnlNmO4-Xz4koMbb1ONmOe9ewhLN5A2iWSwflt24tKmzApVzT4ki8JG4fMMHzXFGuEEjjkRgAAxP7r5tnyJwSNpv9YTVWogRnoqQ_IC5Viucrc_D0_Qt72uPRWh9drO/s16000/Role3.png" /></a></div></div><ul> <li>After filling in all the details, click on <strong>Generate</strong> to generate a link.</li></ul> <p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil2hE9lebnhjVCkkhej-SM2I7iefyUD1ObSVvwAN-zvv_liQCAtuAF31s3ZsIkGsD4Un4eA5spVMGaJy7iyXcF44MYLwpnsfPAislJaCNQ_NhxcSBmhWAB44jfUf-U44-MwANC0MqjLgDQb_vdMjfH0nooRTbqFC1SQL9QXMNmdf8NC9aNM23fUGEvLGOH/s1258/Role4.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="312" data-original-width="1258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil2hE9lebnhjVCkkhej-SM2I7iefyUD1ObSVvwAN-zvv_liQCAtuAF31s3ZsIkGsD4Un4eA5spVMGaJy7iyXcF44MYLwpnsfPAislJaCNQ_NhxcSBmhWAB44jfUf-U44-MwANC0MqjLgDQb_vdMjfH0nooRTbqFC1SQL9QXMNmdf8NC9aNM23fUGEvLGOH/s16000/Role4.png" /></a></div>Once users click on this link, the role is assigned as you can see in the portal:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEYOf2tYuIDhEG9ix6w-87QLJQqceqp1AFJT4V56luE2Sr6PsIYGN_u-v3B6zXjYO1LcJugsySI1OMxjHyYp0-Q7UtCM2wJw375Q4DVFejMzmwW0i3E_1bydkR2jBgZblw8rDESMIRj-yyS6-ppmouHrwNlDy754C34klCH8-yazzD61hVwXGr8cG_HsGY/s2688/Role5.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="478" data-original-width="2688" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEYOf2tYuIDhEG9ix6w-87QLJQqceqp1AFJT4V56luE2Sr6PsIYGN_u-v3B6zXjYO1LcJugsySI1OMxjHyYp0-Q7UtCM2wJw375Q4DVFejMzmwW0i3E_1bydkR2jBgZblw8rDESMIRj-yyS6-ppmouHrwNlDy754C34klCH8-yazzD61hVwXGr8cG_HsGY/s16000/Role5.png" /></a></div><p></p> <p>And when calling the<font face="Courier New"> .auth/me</font> endpoint:</p> <script src="https://gist.github.com/wullemsb/c4b4d2bbf698367aaa68f5e089a653af.js"></script> <h1>More information</h1> <p><a href="https://learn.microsoft.com/en-us/azure/static-web-apps/configuration#routes">Configure Azure Static Web Apps | Microsoft Learn</a></p>Bart Wullemshttp://www.blogger.com/profile/01355004851661662785noreply@blogger.com