Tim Taurit https://taurit.pl/ Tackling Developer Challenges with Azure, .NET, and AI Mon, 18 May 2026 17:10:53 +0000 en-US hourly 1 https://wordpress.org/?v=7.0.2 https://taurit.pl/wp-content/uploads/2024/08/tim-taurit-icon-blue-150x150.png Tim Taurit https://taurit.pl/ 32 32 Bye-Bye Visual Studio: Why VS Code is my new daily driver for C# https://taurit.pl/bye-bye-visual-studio/ https://taurit.pl/bye-bye-visual-studio/#respond Mon, 18 May 2026 13:40:39 +0000 https://taurit.pl/?p=2469 Why I switched For years, Visual Studio with ReSharper was my go-to IDE for all .NET development. I tried the other ones in the past (notably Rider and VS Code) trying to target performance bottleneck in my work, but never stuck to any of them. But 2026 feels different. Like many developers, I started using ... Read more

The post Bye-Bye Visual Studio: Why VS Code is my new daily driver for C# appeared first on Tim Taurit.

]]>
Why I switched

For years, Visual Studio with ReSharper was my go-to IDE for all .NET development. I tried the other ones in the past (notably Rider and VS Code) trying to target performance bottleneck in my work, but never stuck to any of them.

But 2026 feels different. Like many developers, I started using GitHub Copilot more and more to do the job.

As a full-stack developer using VS Code for React development, I started to notice a growing gap between GitHub Copilot experience between Visual Studio Professional 2026 and Visual Studio Code. VS Code copilot experience is clearly first-class citizen, and takes a lot of space in release notes every week now. It’s also highly reliable, fast and has growing ability to debug cost and behavior of underlying LLM agents.

Evaluating the decision

A month or two after this move, I’m highly satisfied about taking the time to configure VS Code workspace for my .NET project. I think it’s a huge productivity gain given how much the development is founded on LLM chats these days.

I still fall back to Visual Studio Pro on rare occasions, like when I want to update NuGet packages using familiar GUI, but it’s rare. If you use C# and vibe code a lot, certainly a step worth considering in 2026!

What made the switch easier

I found the switch to be relatively painless thanks to:

  • Configuring the same keybindings for my most frequently used actions (like “Build project”, “Launch project”, “Rebuild project”)
  • Installing proper extensions (like C# Dev Kit) to allow debugging C# code with breakpoints and all
  • Rearranging sidebars to what I was used to (e.g. moving Solution Explorer on the right)
  • Ensuring my settings are synced on all machines I use (work and home) so I don’t have unnecessary friction, like different keyboard shortcuts or behaviors
  • And as a small accent, using Studio Icons addon (bringing familiar file icons from VS Professional to VS Code), and choosing a similar color theme 😊

I think such small investments pay back over time.

The post Bye-Bye Visual Studio: Why VS Code is my new daily driver for C# appeared first on Tim Taurit.

]]>
https://taurit.pl/bye-bye-visual-studio/feed/ 0
VS Code Dev Containers – worth the pain for added security? https://taurit.pl/vs-code-dev-containers-my-experience/ https://taurit.pl/vs-code-dev-containers-my-experience/#respond Mon, 18 May 2026 12:50:31 +0000 https://taurit.pl/?p=2457 My motivation to use Dev Containers I’ll share my experiences with an attempt to use VS Code Dev Containers. Dev containers allow to sandbox our development experience to some extent, using Docker and virtualization under the hood. I got interested in that after noticing increased incidents of supply chain attack on developer tools in 2026. ... Read more

The post VS Code Dev Containers – worth the pain for added security? appeared first on Tim Taurit.

]]>
My motivation to use Dev Containers

I’ll share my experiences with an attempt to use VS Code Dev Containers.

Dev containers allow to sandbox our development experience to some extent, using Docker and virtualization under the hood. I got interested in that after noticing increased incidents of supply chain attack on developer tools in 2026.

As many projects do, we depend on dozens of 3rd party dependencies, which transitively depend on thousands more. They are constantly updated and at risk. A malicious package can compromise developer machine and wreak havoc across the organization.

Project’s tech stack

I was curious if I could switch from the “native” development experience to “containerized” one without noticing much change.

I selected a relatively simple front-end project to try this technology, supported mainly by these tools:

  • Next.js (running on Node.js)
  • Some PowerShell scripts
  • Storybook
  • Vitest
  • Prettier
  • Eslint

What went smoothly

  • 🟒 Basic setup: I expected setting up the workspace and shortcuts to take some time.
    In reality, I already had Docker and virtualization enabled and it was quite smooth. During initialization it took some time to download base docker images, but seems like it was one-time operation unless I want to update base images or change container configuration.
  • 🟑 VS Code Startup time: I was worried that VS code startup time would jump from current 1-2 seconds to something noticeably slow. In reality, workspace is ready after about 10 seconds. It’s not a bad experience, although a bit annoying when I want to find something quickly during live meeting.
  • 🟒 Scripts pretty much worked: I expected that tasks in my package.json like build, test, prettier-write might require additional steps to work. I needed some adjustments like:
    • Fixing slash vs backslash in file paths, so app is compatible with Linux
    • Removing node_modules and reinstalling them, so Linux variants of packages were chosen instead
  • 🟒 Accessing web apps from Windows browser: I expected application’s port might change or Firewall might block access to containerized application. But it worked out of the box, on the same port.

What didn’t go well

  • 🟑 Search in VS Code is noticeably slower. Not a deal breaker, but I notice it.
  • 🟑 We use private npm feeds, and authentication failed. My global npm configuration in Windows was not accessible (which makes sense and proves the desired isolation) and I had to sign in again within container.
  • 🟑 The default terminal is now Bash and not Powershell. Switching to powershell probably requires some extra configuration.
  • 🟑 GitHub Copilot chat history was “clear” – it seems isolated from the history on Windows.
  • 🟑 Many extensions didn’t work out of the box, for example prettier. I normally sync my extensions and have flawless experience on multiple machines, but here it would require additional configuration and time spent.
  • 🟑 Initial stability issues: frequent reconnects and connection errors. Increasing WSL2 memory limit and enabling swap file resolved it. Example:
  • πŸ”΄ Apps being generally slow or timing out. This is unfortunately a deal breaker. Most of the time I am waiting and waiting for things to happen. CPU fan spins in high mode all the time, CPU usage is high, and WSL2 takes extra 12 GB of RAM it normally doesn’t need (though I’m still below my memory ceiling).

Summary

I was curious to try VS Code Dev Containers, but now I am cured because of the encountered reliability and performance issues.

I suspect that a big part of the performance hit might be caused by corporate software that we run, like antivirus or deep-packet inspection firewall. This is out of my control and I have no time to experiment further, so I am giving up experimenting at this point. My observations might not generalize, and you may still have much better experience!

I am aware of the classic tradeoff between security and convenience. Here, the convenience suffered too much πŸ₯²

The post VS Code Dev Containers – worth the pain for added security? appeared first on Tim Taurit.

]]>
https://taurit.pl/vs-code-dev-containers-my-experience/feed/ 0
VS Code GitHub Copilot “Send and Dispatch” button broken: how to diagnose https://taurit.pl/vs-code-github-copilot-send-and-dispatch-button-not-working/ https://taurit.pl/vs-code-github-copilot-send-and-dispatch-button-not-working/#respond Mon, 15 Sep 2025 10:59:57 +0000 https://taurit.pl/?p=2438 If you’re stuck with the GitHub copilot button not working in VS Code and found this article, I hope it will help you narrow down the problem. I encountered this issue today after writing an extensive prompt. The problem is that the “Send and Dispatch” button in GitHub Copilot Chat extension appears to do no ... Read more

The post VS Code GitHub Copilot “Send and Dispatch” button broken: how to diagnose appeared first on Tim Taurit.

]]>
If you’re stuck with the GitHub copilot button not working in VS Code and found this article, I hope it will help you narrow down the problem.

I encountered this issue today after writing an extensive prompt. The problem is that the “Send and Dispatch” button in GitHub Copilot Chat extension appears to do no action, regardless how many times you click πŸ˜‰

Where to find the error message?

I found out the the error message it displayed in the Output pane in the Window category:

However, depending on the cause, you might want to browse other logs too, in a hope to find the exception:

PS. Why wasn’t it working for this blogpost author?

I apparently found a little bug in the Copilot Chat extension. When pasting text while the cursor is at the beginning of the context reference, something breaks and the prompt cannot be sent.

But this blogpost is more about “where to look for error message” than “why this specific button doesn’t work?”. Hope it helps someone 😊

The post VS Code GitHub Copilot “Send and Dispatch” button broken: how to diagnose appeared first on Tim Taurit.

]]>
https://taurit.pl/vs-code-github-copilot-send-and-dispatch-button-not-working/feed/ 0
Mocking OAuth2 and OpenID Connect Login in Next.js https://taurit.pl/mock-oauth2-login-in-next-js/ https://taurit.pl/mock-oauth2-login-in-next-js/#respond Fri, 12 Sep 2025 20:38:32 +0000 https://taurit.pl/?p=2427 I’m currently developing a small application in Next.js that will be protected with a rather typical OAuth 2 login flow. The login part will most likely be delivered last-minute by an external team, and will most likely be based on Azure Entra Id. Therefore, I decided to mock the login flow to help me develop ... Read more

The post Mocking OAuth2 and OpenID Connect Login in Next.js appeared first on Tim Taurit.

]]>
I’m currently developing a small application in Next.js that will be protected with a rather typical OAuth 2 login flow. The login part will most likely be delivered last-minute by an external team, and will most likely be based on Azure Entra Id.

Therefore, I decided to mock the login flow to help me develop and authentication and authorization even before I have all the building blocks ready. But I think it might stay useful even later, to help set up automated testing or a shortcut login path in development environments.

I’ll briefly share my experience here, along with a code sample. I hope that it might be a good starting point for a similar quest, saving you some time 😊

A screenshot of the simple Oauth2 Mock Server running
A screenshot of the simple Oauth2 Mock Server running on my PC.

Selected tech stack

Here’s the setup I work with, with some reasoning behind the choices:

  • next.js: I tested on v15.3
  • next-auth: I selected this library because it seems popular, well-documented, with good track record. I use v4 and not v5 (also known by its new name Auth.js) because v5 is still not marked as stable.
  • oauth2-mock-server 8.1.0: it’s one of the easiest oauth mock tools to find. It seems trusted coming from AXA, and seems still maintained from time to time.

Since I wanted to host the OAuth2 Mock service online (to have it available for our online dev/test environments), I selected Azure App Service with containers to host the oauth2-mock-server app.

Solution

Here is a code snippet showing how I set up the server:

// filename example: mock.js
import { OAuth2Server } from 'oauth2-mock-server';

const server = new OAuth2Server();

// Simulate that we receive some custom claims in both access token and ID token
server.service.on('beforeTokenSigning', (token) => {
    const isAccessToken = 'scope' in token.payload;
    const isIdToken = 'aud' in token.payload && !isAccessToken;

    if (isAccessToken) {
        console.log('Adding claims to the access token');
        token.payload.sub = 'ABC123';
    }

    if (isIdToken) {
        console.log('Adding claims to the ID token');
        token.payload.sub = 'ABC123';
        token.payload.given_name = 'Robert';
        token.payload.family_name = 'Shephard';
        token.payload.email = 'robert.shephard@example.com';
    }
});

await server.issuer.keys.generate('RS256');
// Uncomment and adjust to host in Azure App Service as a docker container
//server.issuer.url = 'https://CENSORED.azurewebsites.net';
await server.start(80, '0.0.0.0');

console.log('OAuth 2.0 Mock Server running at:', server.issuer.url);
console.log('Configuration URL:', `${server.issuer.url}/.well-known/openid-configuration`);

Start and test with:

  • npm install oauth2-mock-server
  • node mock.js
  • go to an URL like https://localhost/.well-known/openid-configuration to confirm the mock server is running

Learnings

Here are some thoughts I have after setting this up:

  • I’m not entirely sure if using mock server saved me time compared to setting up a real authentication server.
    It took me a few hours to figure out the setup, proper deployment, and how to reliably customize the mock to add custom claims. Maybe it would be simpler to just set up a real server Azure portal? But I haven’t explored that path so I cannot compare the effort.
  • On the other hand, it’s a good way to learn a bit about the OAuth2 API, and get a refresher on how it differs from OIDC, what’s the difference between access_token and id_token etc.
  • The next-auth library seems like a solid choice. I like that it is super flexible and supports dozens of providers out-of-the-box. I like to know it’s a tool that I can use in wide range of projects. In the past I used MSAL and nextjs-auth0, which were tied to specific OAuth2 providers.

I suspect this setup might offer some convenience in development, especially if the team developing Identity Management solution turns out to be hard to reach. Good luck!

The post Mocking OAuth2 and OpenID Connect Login in Next.js appeared first on Tim Taurit.

]]>
https://taurit.pl/mock-oauth2-login-in-next-js/feed/ 0
VS Code: give GitHub Copilot access to the internet https://taurit.pl/vs-code-github-copilot-internet-access-and-search/ https://taurit.pl/vs-code-github-copilot-internet-access-and-search/#respond Fri, 01 Aug 2025 11:44:33 +0000 https://taurit.pl/?p=2409 The problem: Copilot cannot browse the web One of the limitations of the default GitHub Copilot installations in Visual Studio Code I was kind-of aware of, but never really focused on, is in its inability to access internet websites, or search the web. It turns out to be inconvenient, especially when working with niche libraries ... Read more

The post VS Code: give GitHub Copilot access to the internet appeared first on Tim Taurit.

]]>
Table of Contents

The problem: Copilot cannot browse the web

One of the limitations of the default GitHub Copilot installations in Visual Studio Code I was kind-of aware of, but never really focused on, is in its inability to access internet websites, or search the web.

An attempt to make GitHub copilot fetch a content of a website and use it.

It turns out to be inconvenient, especially when working with niche libraries and tools. Sometimes the documentation exists on the internet, and as a developer I could even point out where it is. But the AI models seem to never be trained on it, or were trained on an outdated version, or the information was too scarce and LLMs hallucinate.

Why fetching pages is more complex than it might seem

Granting models the ability to use search engines and access internet was already in preview last year. I expected it would soon land in GitHub Copilot. But it has not happened.

Here is my pure speculation why it didn’t happen. And I think it might not happen so soon, so spending time on additional configuration might be still worth the effort to gain productivity:

  • Fetching page is not as trivial as it might seem at first glance. Internet in 2025 is build on dynamic, JavaScript-based pages. It is rarely enough to download the document under the URL and parse it. The task is closer to having a text-based internet browser to fetch the page, execute JavaScript, and render the website as text, and ideally denoise it for LLM. There is a growing market for tools that help scrape the web for LLMs.
  • Searching the internet is an additional layer of complexity. Integration with Search APIs like Google Search is not free (or at least not free in the scale needed to enable it by the default for everyone). And I can see how it can be seen as beyond the scope of add-on like GitHub Copilot. With MCP standard, this can be better delegated to other addons or MCP tools.

Overcoming the problem: let the Copilot fetch URLs!

Long story short, I recommend installing a good addon that will extend the Copilot tool palette with the ability to fetch web pages. The one I found and appears trusted is Microsoft’s Web Search for Copilot.

Simply installing it adds the ability to fetch page content:

A security prompt before fetching the page
A proof that a requested page was fetched, understood, and taken into consideration while generating response.

Enabling the search

Let’s try teach it to search now! I’ll continue setting up Web Search for Copilot to add search capability. This time the extension also leads me step by step through the configuration. By default it uses the Tavily Search API dedicated for use by LLMs.

The setup was as easy as I could imagine and took 1 minute. I signed in with my Google Account, approved the login, and was instantly redirected to the dashboard with the API Key and usage stats. The pricing seems affordable, and if the search engine proves to be good, I’ll just stick with it long term.

The result looks correct. Copilot found and fetched the heavily-scripted page, and correctly parsed the data. Fresh results, and no hallucinations πŸ„πŸ€ͺ

Github Copilot with Web Search for Copilot was able to find and utilize the fresh information on the internet.

I’ve not had time to sign up and experiment with MCP servers offering Google search or other generic-purpose search engines, but regardless of the differences, Tavily is still a big improvement allowing Copilot to find and access recent documentation.

Summary

For a long time, Large Language Models had a knowledge cutoff date far in the past because of all the steps required to sanitize the data, train, and release the model. It seemed that if we wanted good support from AI tools in software development projects, we’d have to rely on older technologies and stay away from the cutting edge.

But this gap is getting narrower. Models are now being retrained more frequently, and the ones I use for coding have a knowledge cutoff only 3 to 4 months ago. Still, with web search and internet access, we can close this gap even further and give our Copilot access to up-to-date documentation and threads about known issues.

This should allow us, developers, to keep project libraries and frameworks up-to-date and still keep the power of GitHub copilot for the added productivity.

The post VS Code: give GitHub Copilot access to the internet appeared first on Tim Taurit.

]]>
https://taurit.pl/vs-code-github-copilot-internet-access-and-search/feed/ 0
All-uppercase strings and `camelCase` JSON serialization (in .NET) https://taurit.pl/camelcase-json-serialization-net/ https://taurit.pl/camelcase-json-serialization-net/#respond Thu, 17 Jul 2025 16:30:01 +0000 https://taurit.pl/?p=2396 I was recently debugging an issue where two systems expected the contract to be using the camelCase style, but miscommunicated because they assumed a different transformation result for an all-uppercase string like URL: Which of them was right, and which was buggy? The short answer is that there’s no single industry standard defining what is ... Read more

The post All-uppercase strings and `camelCase` JSON serialization (in .NET) appeared first on Tim Taurit.

]]>
I was recently debugging an issue where two systems expected the contract to be using the camelCase style, but miscommunicated because they assumed a different transformation result for an all-uppercase string like URL:

  • System 1 serialized it as url
  • System 2 serialized it as uRL

Which of them was right, and which was buggy?

The short answer is that there’s no single industry standard defining what is right here. Libraries might handle edge cases differently. If you need compatibility, it’s helpful to examine the libraries you need to ensure they are doing what you expect.

Experiment: .NET serializers vs all-uppercase strings

Acronyms and abbreviations are common in property names. Objects might contain properties containing strings like URL, API, HTTP etc. Such property names are transformed differently depending on the serializer and settings you use.

This experiment examines how popular .NET JSON serializers handle acronym casing when converting strings to camelCase during serialization.

Serializer Style URL URLPath MyURL IOStream M4AFileName
Newtonsoft.Json CamelCase url urlPath myURL ioStream m4AFileName
System.Text.Json CamelCase url urlPath myURL ioStream m4AFileName
Newtonsoft.Json Default URL URLPath MyURL IOStream M4AFileName
System.Text.Json Default URL URLPath MyURL IOStream M4AFileName
Newtonsoft.Json SnakeCase url url_path my_url io_stream m4_a_file_name
System.Text.Json SnakeCaseLower url url_path my_url io_stream m4_a_file_name
Newtonsoft.Json KebabCase url url-path my-url io-stream m4-a-file-name
System.Text.Json KebabCaseLower url url-path my-url io-stream m4-a-file-name

Conclusions

The key learnings for me are:

  1. In camelCase style, acronyms at the beginning of property namesΒ are handled differently from the ones in the middle/end of the string.
  2. System.Text.Json and Newtonsoft.Json transform acronyms to lowercase in the same way in all cases I tested, suggesting high compatibility of those style transformations.
  3. Many state-of-the-art LLM models are confused about this in 2025 and suggest the output would lowercase only the first letter (e.g.,Β URLΒ β†’Β uRL), which is not true for the popular .NET serializers.
  4. If you want to convert a string to camelCase style, don’t let LLMs trick you into oversimplified implementations that only replace the first character of the string. Instead, use a mature library like System.Text.Json to do it for you!

Good luck πŸ™‚

The post All-uppercase strings and `camelCase` JSON serialization (in .NET) appeared first on Tim Taurit.

]]>
https://taurit.pl/camelcase-json-serialization-net/feed/ 0
Optimizely CMS 12: color picker and its limitations https://taurit.pl/optimizely-cms-12-color-picker/ https://taurit.pl/optimizely-cms-12-color-picker/#respond Sun, 06 Jul 2025 10:06:23 +0000 https://taurit.pl/?p=2383 Optimizely controls: going beyond basics Optimizely CMS 12 comes with support for a few basic controls rendered for built-in property types, like a text input field or a date-time picker. Built-in property types are documented, e.g., in the official documentation. For developers’ convenience, I also wrote a blog post about basic CMS 12 controls, providing ... Read more

The post Optimizely CMS 12: color picker and its limitations appeared first on Tim Taurit.

]]>
Optimizely controls: going beyond basics

Optimizely CMS 12 comes with support for a few basic controls rendered for built-in property types, like a text input field or a date-time picker.

Built-in property types are documented, e.g., in the official documentation. For developers’ convenience, I also wrote a blog post about basic CMS 12 controls, providing a single list that shows code snippets and screenshots of rendered controls.

However, sometimes we need to go beyond basic controls and render more fancy controls.

Optimizely’s “native” color picker

I recently searched for how to display a color picker control for CMS users. I found some forum threads mentioning importing a widget from dijit, a UI library used by Optimizely. Here are two examples:

[Display(Name = "Text Color", Order = 10)]
[ClientEditor(ClientEditingClass = "dijit/ColorPalette")]
public virtual string TextColor { get; set; }

[Display(Name = "Accent Color")]
[ClientEditor(ClientEditingClass = "dijit/ColorPalette", EditorConfiguration = "{ \"palette\": \"3x4\" }")]
public virtual string BackgroundColor { get; set; }

They render like this:

The image depicts Optimizely CMS color picker control in the only two variants it supports.

Dijit color picker limitations

Dijit’s Color Palette widget documentation clearly states that this widget has only one parameter, allowing the size to be changed between 7×10 and 3×4 grid sizes. And that’s it.

There is no way to change the set of colors displayed on the palette, adjust the size of color sample squares, or tailor the palette to include only your brand colors. So, it’s pretty cool, but not very useful in the real world.

Alternative: a custom color picker

Hopefully, we can also write custom UI controls in Optimizely. And 10 years ago, someone did just that!
HΓ₯kon Nordli published a post on Optimizely forums, sharing his basic color picker for Optimizely (back then named EPiServer) versions 8 and 9.

The code is still available as an archive in the GitHub repository knowit/ColorPickerEditor. I tested that it still works fine without any adjustments needed in CMS 12 πŸŽ‰

// You need to clone the control's code to your project for it to work
[UIHint("ColorPickerEditor")]
[Display(GroupName = SystemTabNames.Content, Order = 10)]
public virtual string ColorPickerEditor { get; set; }
The custom ColorPickerEditor control was cloned to the CMS 12 Alloy solution and works fine. I verified that it’s easy to define my color palette, and there’s no limit on the number of colors or their definitions.

Note that it is still a color picker with a predefined set of colors rather than free choice of any color you like. But you can also use it as a neat starting point to develop a more complex color picker if needed. Good luck! πŸ™‚

The post Optimizely CMS 12: color picker and its limitations appeared first on Tim Taurit.

]]>
https://taurit.pl/optimizely-cms-12-color-picker/feed/ 0
Optimizely CMS 12: built-in property types (with code examples) https://taurit.pl/optimizely-cms-12-built-in-property-types/ https://taurit.pl/optimizely-cms-12-built-in-property-types/#respond Mon, 30 Jun 2025 17:53:07 +0000 https://taurit.pl/?p=2294 I’m writing this blog post as a supplement to the official documentation listing built-in Optimizely CMS 12 property types. What I miss in the official documentation is having quick access to snippets for easy copy & paste, and specifically: Code examples Text input Textarea Rich text Integer (without range constraint) Integer (with range constraint) Double ... Read more

The post Optimizely CMS 12: built-in property types (with code examples) appeared first on Tim Taurit.

]]>
I’m writing this blog post as a supplement to the official documentation listing built-in Optimizely CMS 12 property types. What I miss in the official documentation is having quick access to snippets for easy copy & paste, and specifically:

  • C# code samples with typical use,
  • Screenshots showing how the property renders in CMS edit mode.

Code examples

Text input

[Required]
[StringLength(150, MinimumLength = 5)]
public virtual string RequiredText { get; set; }

Textarea

[UIHint(UIHint.Textarea)]
[StringLength(10)]
public virtual string Textarea { get; set; }

Rich text

public virtual XhtmlString RichText { get; set; }

Integer (without range constraint)

Integer (without range)
public virtual int Integer { get; set; }

Integer (with range constraint)

[Range(1, 20)]
public virtual int RangeInt { get; set; }

Double (without range constraint)

public virtual double Double { get; set; }

Double (with range constraint)

[Range(0.00, 9999.99)]
public virtual double RangeDouble { get; set; }

DateTime

public virtual DateTime DateTime { get; set; }

The newer .NET typed, DateOnly and TimeOnly are not supported by default in CMS 12.

Boolean

public virtual bool Boolean { get; set; }

Note that if you use Nullable<bool>, it’ll still render the same: as a checkbox with two states, true/false (and not three as in true/false/null)

PageType selector

public virtual PageType PageTypeSelector { get; set; }

RegularExpressionString

[RegularExpression(@"^[A-Z]\d$")]
public virtual string RegExString { get; set; }

ImageData

[UIHint(UIHint.Image)]
[AllowedTypes(typeof(ImageData))]
public virtual ContentReference Image { get; set; }

PageData

[AllowedTypes(typeof(PageData))]
public virtual ContentReference PageReference { get; set; }

ContentArea

public virtual ContentArea ContentArea { get; set; }

StringList

public virtual IList<string> StringList { get; set; }

Enum

Creating an enum dropdown requires a few more lines of code. See my other blog post, Optimizely CMS 12: working with Enums and Content Delivery API, for a code sample 😊

Summary

The list above is not exhaustive, but it covers the typical building blocks of a custom content type. Some of these properties can be further customized using C# attributes for improved validation.

It’s also possible to introduce custom property types, as well as custom front ends for them to display almost anything you want. However, that is another story to share someday.

Enjoy and good luck!

The post Optimizely CMS 12: built-in property types (with code examples) appeared first on Tim Taurit.

]]>
https://taurit.pl/optimizely-cms-12-built-in-property-types/feed/ 0
Optimizely CMS 12: “The page may have been changed by another user (…)” error https://taurit.pl/optimizely-cms-12-the-page-may-have-been-changed-by-another-user-error/ https://taurit.pl/optimizely-cms-12-the-page-may-have-been-changed-by-another-user-error/#respond Thu, 12 Jun 2025 04:55:00 +0000 https://taurit.pl/?p=2333 What might cause this error Recently, when I was working with Optimizely CMS 12 (formerly EPIServer CMS), I encountered a confusing error message that seemed unrelated to what I was doing: Could not save property. The page may have been changed by another user. Please reload the page. I gradually reverted my recent changes to ... Read more

The post Optimizely CMS 12: “The page may have been changed by another user (…)” error appeared first on Tim Taurit.

]]>
What might cause this error

Recently, when I was working with Optimizely CMS 12 (formerly EPIServer CMS), I encountered a confusing error message that seemed unrelated to what I was doing:

Could not save property. The page may have been changed by another user. Please reload the page.

Screenshot showing error message in the Optimizely CMS admin panel
The screenshot of the error message in the CMS 12 admin panel.

I gradually reverted my recent changes to experimentally find out that the culprit was a property added to the content type I developed. Here’s a simplified version of the code:

public enum Color { Red, Green, Blue }

[SiteContentType(GUID = "11111111-2222-3333-4444-555555555555")]
public class MyNewPage : PageData
{
    [SelectOne(SelectionFactoryType = typeof(EnumSelectionFactory<Color>))]
    public virtual Color LogoColor { get; set; }

    [SelectOne(SelectionFactoryType = typeof(EnumSelectionFactory<Color>))]
    public virtual Color? LogoColorNullable { get; set; }
}

What is wrong with it? The CMS 12 platform cannot handle the Nullable variant of the enumerable types. Hence, the “?” character here causes the issue, and the error is being handled by displaying a generic message, which is unrelated to the root cause of the problem.

Other causes of this error

It looks like the above error message is displayed whenever a POST operation fails to save data and receives the 409 Conflict response from the CMS backend server, regardless of whether it was caused by another user saving the content before you did.

For future cases like that, I’m leaving us a hint that it’s worth digging intothe browser’s console logs to find a hint on the root cause there:

Edge browser showing error mesage received from the backend in the developer console
Microsoft Edge browser with developer’s console open, showing a hint about the real cause of error

The post Optimizely CMS 12: “The page may have been changed by another user (…)” error appeared first on Tim Taurit.

]]>
https://taurit.pl/optimizely-cms-12-the-page-may-have-been-changed-by-another-user-error/feed/ 0
Optimizely CMS 12: working with Enums and Content Delivery API https://taurit.pl/optimizely-cms-12-working-with-enums-and-content-delivery-api/ https://taurit.pl/optimizely-cms-12-working-with-enums-and-content-delivery-api/#respond Wed, 11 Jun 2025 17:45:00 +0000 https://taurit.pl/?p=2316 We’re currently setting up an application that utilizes a headless CMS, allowing us to decouple the front-end application from the CMS core. In a headless setup, having a simple contract between the backend and frontend has a significant impact on the developer experience. And being able to use enum is handy in C# content type ... Read more

The post Optimizely CMS 12: working with Enums and Content Delivery API appeared first on Tim Taurit.

]]>
We’re currently setting up an application that utilizes a headless CMS, allowing us to decouple the front-end application from the CMS core.

In a headless setup, having a simple contract between the backend and frontend has a significant impact on the developer experience. And being able to use enum is handy in C# content type models.

How enums behave by default

Enumerable types, by default, appear to behave just as integers. Here’s a minimal example:

public enum Color { Red, Green, Blue }

[SiteContentType(GUID = "11111111-2222-3333-4444-555555555555")]
public class MyNewPage : PageData
{
    public virtual Color LogoColor { get; set; }
}

Given the above content type definition, the property will render as a number input, giving user no hint about the allowed range of options and their meaning:

Similarly, the response from the Content Delivery REST API contains the value represented as an integer. So the API users receive incomplete information on which enum option is selected, unless they have coded the meaning of integers on their side too:

Improvement 1: display a dropdown with enum values in the CMS Admin Panel

The first improvement we can make is to display a dropdown with enum values instead of a raw number input:

To achieve that, we need a custom selection factory. For example, this implementation is pretty generic and reusable:


[SiteContentType(GUID = "11111111-2222-3333-4444-555555555555")]
public class MyNewPage : PageData
{
    [SelectOne(SelectionFactoryType = typeof(EnumSelectionFactory<Color>))]
    public virtual Color LogoColor { get; set; }
}

public class EnumSelectionFactory<TEnum> : ISelectionFactory where TEnum : Enum
{
    public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata) => Enum
        .GetValues(typeof(TEnum))
        .Cast<object>()
        .Select(value => new SelectItem()
        {
            Text = value.ToString(),
            Value = (int)value
        });
}

This change improves the experience for content editors, but the value is still returned as PropertyNumber by the Content Delivery API.

Improvement 2A: change property type from enum to string

One tradeoff we might find acceptable in some instances is to change the backing type to a string:

[SiteContentType(GUID = "11111111-2222-3333-4444-555555555555")]
public class MyNewPage : PageData
{
    [SelectOne(SelectionFactoryType = typeof(EnumSelectionFactory<Color>))]
    public virtual Color LogoColor { get; set; }

    [SelectOne(SelectionFactoryType = typeof(EnumSelectionFactoryString<Color>))]
    public virtual string LogoColorAsString { get; set; }
}

public class EnumSelectionFactoryStringValue<TEnum> : ISelectionFactory where TEnum : Enum {
    // ...
        Value = value.ToString()
    // ...
}

In this situation, the property will still render as a dropdown. We therefore keep the convenience of constraining the value to enum values.

However, the API will now return the string value, which might be preferred:

The additional benefit is that we can use the Nullable<string> type and easily code the dropdown to represent the “no selection” value. I observed that this is not possible with enums. Property types like Color and Color? are represented the same in CMS’s database. The attempt to set a null value to Color? leads to an error during content type save.

Improvement 2B: keep the enum as enum, but add annotations to swagger.json

In my task, I wanted to keep the type as an enum to guarantee compatibility during export and import operations.

For this reason, I resorted to another trick. I updated the project’s Swagger (OpenAPI) specification to expose names and descriptions for numeric enum values using the x-enum-varnames extension.

While the Content Delivery API still returns integers, the TypeScript client code generator we use (OpenApi Generator) now can map those integer values to TypeScript enum-like types.

Exposing Swagger for Optimizely CMS Content Delivery API is challenging with CMS 12, and I cannot provide a concise code example, but it can be done. Hopefully, this hint can help you see that as a viable option.

Also, a bad idea to avoid: use ContentConverter to convert enum values to string before generating API response

I’ll mention that before I settled on the solution described above, I tried one more, and it turned out to be a disaster.

I wanted to extend and override the DefaultContentConverter so that I can transform the API response just before it’s sent back to the client, and replace integer values with strings.

The concept of the conversion extension I wanted to have.

This was a dead end I spent many hours on, and I advise not trying it.

Firstly, as the linked documentation states, this is an internal API and will likely change in the future.

Secondly, coding this requires traversing the content tree returned by the API and finding enums, and it cannot be done without the use of Reflection APIs, the infamous dynamic type, and falling into a lot of pitfalls. I believe you could make it work “in most cases”, in hundreds of ugly lines of code, but it’s not worth it. Check out the easier options first.

Good luck!

The post Optimizely CMS 12: working with Enums and Content Delivery API appeared first on Tim Taurit.

]]>
https://taurit.pl/optimizely-cms-12-working-with-enums-and-content-delivery-api/feed/ 0
VS Code: display custom warnings in the Problems tab https://taurit.pl/vs-code-custom-warnings/ https://taurit.pl/vs-code-custom-warnings/#respond Tue, 01 Apr 2025 09:55:00 +0000 https://taurit.pl/?p=2301 How do you display custom issues in the Problems view during build? Visual Studio Code offers a Problems tab that displays errors and warnings from your code. While it works well with built-in linters and compilers, sometimes you need to integrate custom validation tools into your workflow. In this post, I’ll show you how to ... Read more

The post VS Code: display custom warnings in the Problems tab appeared first on Tim Taurit.

]]>
How do you display custom issues in the Problems view during build?

Visual Studio Code offers a Problems tab that displays errors and warnings from your code. While it works well with built-in linters and compilers, sometimes you need to integrate custom validation tools into your workflow.

In this post, I’ll show you how to create a custom program that emits warnings and errors that VS Code can recognize and display in the Problems tab.

VS Code displaying our custom warnings and errors in the Problems view.

The solution (using the problemMatcher feature of VS Code tasks)

I’ve created a minimal example project that demonstrates how to:

  1. Create a simple program that emits warnings in a specific format
  2. Configure VS Code to recognize these warnings
  3. Integrate this into your build process to block builds when issues are found

A complete example of how to capture custom warning messages during the build is available in my GitHub repository.

Step 1: Create a Custom Validation Program

First, we need a program to check whatever custom conditions you need and output warnings in a format VS Code can understand. Here’s a minimal C# example:

namespace ScanForSeoIssues;

internal class Program
{
  static int Main(string[] args)
  {
    // emit some fake warnings for demo purposes
    var filePath = "D:\\Projekty\\FlashcardSpace.Blog\\flashcard-space\\.vscode\\tasks.json";

    Console.WriteLine($"warning: file '{filePath}', line '15', column '17': custom warning message");
    Console.WriteLine($"error: file '{filePath}', line '16', column '18': custom error message");

    // return non-zero error code to signal that check failed and stop the build
    return 1;
  }
}

Step 2: Configure VS Code Tasks

Now, we need to set up VS Code to run our validator and parse its output. Here’s example tasks.json configuration:

{
  "version": "2.0.0",
  "tasks": [
    // Define pre-build task to detect issues with custom C# program, and emit warnings/errors if found
    {
      "label": "custom scan for quality issues",

      "type": "shell",
      "command": "dotnet run --project ./scripts/ScanForSeoIssues/ScanForSeoIssues.csproj",

      // Problem matcher instructs VS Code how to capture the errors and warnings from the program output
      "problemMatcher": [
        {
          // Unique id to help VS code clear warnings from the Problems window when you re-run the task
          "owner": "custom-seo-warning",

          // Format of the file path in error message might be auto-detected but being explicit is more reliable
          "fileLocation": "absolute",

          "pattern": {
            // This regex needs to be adjusted to how your custom tool outputs errors and warnings to the console output
            "regexp": "^(warning|error): file '([^']+)', line '(\\d+)', column '(\\d+)': (.+)$",

            // The numbers below are 1-based indexes of the regex groups
            "severity": 1,
            "file": 2,
            "line": 3,
            "column": 4,
            "message": 5
          }
        }
      ],

      "presentation": {
        // Switch to the Problems view from the Terminal view in case of detected warnings or errors
        "revealProblems": "onProblem",

        // Clear the terminal output when the task is re-run for readability
        "clear": true
      }
    },

    // Example build task  (typically started with F6 key)
    {
      "label": "Build website",
      "type": "shell",
      "command": "echo 'Building the project... (this should never be displayed in this demo because of the detected custom errors)'",
      "group": {
        "kind": "build",
        "isDefault": true
      },

      // Key line: this task depends on the custom scan task above, and requires it to succeed before running
      "dependsOn": ["custom scan for quality issues"]
    }
  ]
}

That’s basically it. You can adjust the regex to the format of your warnings and fine-tune other parameters if you like.

Conclusion

This approach allows you to integrate custom validation into your VS Code workflow.

The best part is that your custom tool can be written in any language. Just align its output with the format your problem matcher expects and return the appropriate exit code.

Good luck!

The post VS Code: display custom warnings in the Problems tab appeared first on Tim Taurit.

]]>
https://taurit.pl/vs-code-custom-warnings/feed/ 0
Solving TLS Errors in Node.js https://taurit.pl/node-tls-cert-errors/ https://taurit.pl/node-tls-cert-errors/#respond Thu, 20 Feb 2025 18:23:08 +0000 https://taurit.pl/?p=2264 Common error messages for TLS/certificate errors When we are developing a Node.js application (e.g., using the Next.js framework), we might encounter one of the errors related to invalid TLS/SSL certificates: They are all somewhat similar in that they are caused by problems with the SSL/TLS certificate served by one of the URLs we try to ... Read more

The post Solving TLS Errors in Node.js appeared first on Tim Taurit.

]]>
Common error messages for TLS/certificate errors

When we are developing a Node.js application (e.g., using the Next.js framework), we might encounter one of the errors related to invalid TLS/SSL certificates:

  • [Error: self-signed certificate]
    { code: 'DEPTH_ZERO_SELF_SIGNED_CERT' }
  • [Error: certificate has expired]
    { code: 'CERT_HAS_EXPIRED' }
  • [Error: self-signed certificate in certificate chain]
    { code: 'SELF_SIGNED_CERT_IN_CHAIN' }
  • [Error: Hostname/IP does not match certificate's altnames: ...]
    { code: 'ERR_TLS_CERT_ALTNAME_INVALID }

They are all somewhat similar in that they are caused by problems with the SSL/TLS certificate served by one of the URLs we try to connect to. They are also relatively easy to reproduce if needed: we have badssl.com, which helps simulate various certificate errors.

Typical cause of those errors

Self-signed certificates are not trusted by default

Typically, those errors occur because we rely on self-signed certificates in development and testing environments.

For instance, if we develop an app and run some service on https://localhost/..., we cannot even get a globally trusted certificate because Public Certificate Authorities (CAs) won’t issue one for localhost (since it is a special hostname not owned by anyone).

Even when we use cloud-based or VM-based environments with domains we own, obtaining globally trusted certificates for development is often a hassle. Even if initiatives like Let’s Encrypt can provide them for free, many people might need to be involved in setting them up in enterprises.

Windows certificate store is not used by Node JS

I also found that even when certificates are trusted in Windows or browsers, NodeJS still needs its own setup for self-signed certificates.

Workarounds

Since this isn’t supposed to be an essay on certificates, let’s jump to workarounds to the above problems:

Option 1: “I’m on a hackathon, and I don’t care about security at all”

The easy option is to tell Node that you don’t care about TLS security at all and set the environment variable:

NODE_TLS_REJECT_UNAUTHORIZED=0

A better name for this variable without double negation could be e.g. “ACCEPT_UNTRUSTED_CERTIFICATES” (and someone proposed it in the past), but due to inertia, we have what we have.

While it often solves most TLS-related errors, some libraries don’t utilize this environment variable and might still throw SELF_SIGNED_CERT_IN_CHAIN errors in some scenarios. In such a case, option 2 is worth trying.

Option 2: “Let’s keep TLS connections secure but add a few exceptions”

To keep TLS validation enabled but expand the list of trusted certificates beyond the standard ones, you can use another environment variable, NODE_EXTRA_CA_CERTS:

# On Windows
NODE_EXTRA_CA_CERTS=c:\SomeFolder\localhost-my-cert.crtΒ 

# On Linux or Linux-based containers it's more like:
NODE_EXTRA_CA_CERTS=/certificates/localhost-my-cert.crtΒ 

I typically obtain the certificate file by exporting it from Chrome browser (and I export the certificate for the root of the certificate chain). This setting is more secure and elegant than option 1, and doesn’t require much more effort.

Option 3: “I have plenty of free time at work”

Lastly, if your company owns the domain, you can set the enterprise’s wheels in motion and get a globally trusted certificate for your development environments.

While this requires the most time investment, it might be the most cost-effective option if it helps your team avoid TLS problems and dozens of developers looking for workarounds independently. This is especially true if you expose the web service to other teams that might seek support or if you consume it from mobile apps, where workarounds are more challenging to implement.

Good luck!

The post Solving TLS Errors in Node.js appeared first on Tim Taurit.

]]>
https://taurit.pl/node-tls-cert-errors/feed/ 0
Tools to improve sound quality of audiobooks or podcasts https://taurit.pl/denoise-audiobook-or-podcast/ https://taurit.pl/denoise-audiobook-or-podcast/#respond Wed, 19 Feb 2025 17:34:48 +0000 https://taurit.pl/?p=2246 When I search for an audiobook, my usual choice is Amazon’s Audible (or our Polish Audioteka). This usually ensures good recording quality, and I have no concerns about copyright issues. However, occasionally, I discover that the best available recording is found on LibriVox, YouTube, or a similar service with a mix of amateur and professional ... Read more

The post Tools to improve sound quality of audiobooks or podcasts appeared first on Tim Taurit.

]]>
When I search for an audiobook, my usual choice is Amazon’s Audible (or our Polish Audioteka). This usually ensures good recording quality, and I have no concerns about copyright issues.

However, occasionally, I discover that the best available recording is found on LibriVox, YouTube, or a similar service with a mix of amateur and professional content. This is especially true when searching for not-so-popular books recorded in the not-so-mainstream language I want to practice.

Amateur recording: a list of typical sins

Producing high-quality audio requires both a good microphone, setting, and knowledge. In amateur recordings, the voice acting is often terrific, yet the technical setup might be flawed. Some of the common issues are:

  • Background noise (air conditioning or computer fans)
  • Poor quality of microphone
  • Incorrect distance from the microphone
  • No postproduction that could have compensated for the above mistakes.

Idea: use AI tools to denoise audiobooks and improve quality

When I recorded my podcast a few years ago, the post-production was quite a manual process. I cannot remember any tool allowing any “Intelligent enhance” action that would produce good results back then.

But today, we have AI-powered voice synthesis systems like OpenAI text-to-speech or Azure AI Speech Studio, which produce crystal-clear AI-generated voices that sound like humans.

So, with those rapid technological advancements in mind, I decided to find out if there are any AI-powered tools for enhancing the human voice in flawed recordings.

Denoising audiobooks with AI tools

Enough talk. Let’s look at my test comparing a few tools that promise to do the job. I recommend headphones to hear nuances like the level of noise or how pleasant the text is to listen to.

Demo 1: Sir Walter Scott – Rob Roy


Demo 2: Jacob Abbott – William the Conqueror


My opinion about the quality of individual tools

I performed a blind test (on just one person, myself πŸ˜‰) of the outputs above in quality noise-canceling headphones. Here’s my subjective rating on a 0-10 scale where 0 is terrible audio quality, and 10 is excellent:

Service used to denoise and enhance the fragmentMy subjective rating of output quality (scale 0-10)
Adobe Enhance v29.0 (Great)
Auphonic6.5 (Acceptable)
Riverside4.0 (Below acceptable)
Cleanvoice3.5 (Below acceptable)
Raw recording (no postprocessing)1.0 (Bad and distracting)
Ffmpeg with simple high-pass and low-pass filter0.0 (Bad and distracting)

Additional comments

  • Adobe’s Enhance Speech v2 is an online service dedicated to podcasters. It’s easy to use: drag and drop a file to the browser, one-click “enhance,” and download the result.
    The free version allows processing only 30 minutes of audio, not enough for audiobooks. The subscription is €13/month, and even with that, it has a limit of 4 hours daily.
  • Cleanvoice AI has impressive demos. I tested it to see the quality it achieves. Pricing is relatively high; at about €1 per hour of recording, it’s suitable for publishers rather than people who’d like to improve the quality of content for a single listen.
  • Auphonicβ€”while it’s behind Adobe’s model in quality, it’s a strong second place. Unlike Adobe Podcast, it offers an API and can be used in software and automated processes.
  • Riverside Magic Audioβ€”I found the UI confusing and unreliable (some actions stuck with a progress bar on 99%). I hardly knew what I was doing. The output is better than raw recording, but with the samples I tried, it’s below my threshold of “good enough”.
  • ffmpegβ€”just for a point of reference, I also wanted to see if I could improve the raw, noisy recording with an open-source ffdshow tool and some filters. I tried some combinations, but they were worse than the input audio. FFdshow is great, but it’s not the tool for this job.

Tools not tested but with some potential in this area:

  • I know that Audacity denoises audio quite well, but I excluded it from the test because it’s far from a “one-click enhance” experience. And it isn’t that easy to automate.
  • iZotope RX series of tools is on my radar for a long time, but I found them too expensive for my needs, so I haven’t tested them.

Summary

The technology to enhance audio quality to near-studio levels has advanced in recent years. I hope this trend continues, especially since there’s so much good content created by people who are not professionals in audio production.

In a few years, we might see features like “Enhance audio with AI” and “Enhance video with AI” in players. Or we might not because I don’t know if there is enough commercial potential to do it.

Today, I think the Adobe Enhance Speech v2 model is a definite winner. The website has a startup-like feel, almost as if it were just a prototype. I also wish it had an API, was cheaper, and had no daily limits. But the quality of output is so good that it’s an obvious winner to me. I actually subscribed to this one to process some of the content I want to listen πŸ™‚

If you know any other tools I missed or want to share your opinion, don’t be scared of my poor blog comment system. Leave a few words 😁 And good luck enhancing your content!

The post Tools to improve sound quality of audiobooks or podcasts appeared first on Tim Taurit.

]]>
https://taurit.pl/denoise-audiobook-or-podcast/feed/ 0
Storybook 8: remove padding around iframe https://taurit.pl/storybook-8-remove-iframe-padding/ https://taurit.pl/storybook-8-remove-iframe-padding/#respond Thu, 13 Feb 2025 21:05:08 +0000 https://taurit.pl/?p=2239 Here’s a short recipe for removing padding added around elements in Storybook iframes. The problem Storybook adds the .sb-main-padded class to the previews. In my opinion, this padding is unnecessary because it is not part of the designed component but a part of the Storybook viewport. It might wrongly suggest that we must deal with ... Read more

The post Storybook 8: remove padding around iframe appeared first on Tim Taurit.

]]>
Here’s a short recipe for removing padding added around elements in Storybook iframes.

The problem

Storybook adds the .sb-main-padded class to the previews. In my opinion, this padding is unnecessary because it is not part of the designed component but a part of the Storybook viewport. It might wrongly suggest that we must deal with unwanted padding, e.g., on mobile devices.

The solution easily found online – for Storybook 6

It’s easy to find a solution for Storybook 6 (another blog post for Storybook 6 here):

// Preview.ts

import type { Parameters } from '@storybook/react';

export const parameters: Parameters = {
  layout: 'fullscreen'
};

Solution for Storybook 8

While not hugely different, I’ll share an updated solution for Storybook 8 because you will most likely use it in new web projects 😊 And the official documentation for story layout is here.

// Preview.ts

import type { Preview } from '@storybook/react';

const preview: Preview = {
    parameters: { layout: 'fullscreen' },
};

export default preview;

And here’s a mini proof that it solved a problem, and replaces .sb-main-padded with sb-main-fullscreen πŸ™‚

Good luck, and have fun with your design system!

The post Storybook 8: remove padding around iframe appeared first on Tim Taurit.

]]>
https://taurit.pl/storybook-8-remove-iframe-padding/feed/ 0
Optimizely CMS 12: debugging without source code and symbols https://taurit.pl/optimizely-cms-12-debugging-pdb-symbols-source-code/ https://taurit.pl/optimizely-cms-12-debugging-pdb-symbols-source-code/#respond Thu, 13 Feb 2025 18:14:04 +0000 https://taurit.pl/?p=2184 Optimizely CMS 12 status: debugging symbols are not available When working with a programming framework, we sometimes stumble upon unexpected behaviors. Sometimes, issues can be solved with documentation. Other times, we might want to debug the framework’s behavior. Optimizely CMS (formerly Episerver) used to publish source symbols for the platform, but according to the official ... Read more

The post Optimizely CMS 12: debugging without source code and symbols appeared first on Tim Taurit.

]]>
Optimizely CMS 12 status: debugging symbols are not available

When working with a programming framework, we sometimes stumble upon unexpected behaviors. Sometimes, issues can be solved with documentation. Other times, we might want to debug the framework’s behavior.

Optimizely CMS (formerly Episerver) used to publish source symbols for the platform, but according to the official Troubleshooting page, symbols are no longer published for new versions like CMS 12.

Decompiling CMS DLLs to inspect source code

Let’s examine what the lack of symbols means to developers who use the Optimizely CMS framework to build their applications.

I cloned the Optimizely project templates and created a project using the epi-alloy-mvc template to see how Visual Studio behaves by default. To work on a concrete example, let’s arbitrarily assume that we want to inspect CMS’s code related to commonly used property typeβ€”XhtmlString.

First step: finding a reference to a type defined in Optimizely CMS DLLs

When I use “Go to definition” (F12) on that type, Visual Studio decompiles the code and navigates to it.

When I started writing this blog post, I didn’t expect decompilation to automatically happen because I was used to setting up JetBrains dotTrace specifically for this purpose. But now I see that decompilation with ILSpy was added to Visual Studio 2019 some time ago. What a nice feature! So, we can read the code but cannot set breakpoints yet.

Using Visual Studio debugger to step into the CMS code

So far, so good: we can see some code without official symbols published. The C# code’s quality seems quite good despite being produced by a decompiler. But can we take it further and place working breakpoints in the Optimizely CMS code?

Option 1: use built-in ILSpy decompiler

To try, I changed the default Visual Studio setting:

  • Options -> Debugging -> General -> Enable Just My Code (change from true to false)
  • Open “Modules” window
  • Right-click on Episerver.dll (I checked that XhtmlString comes from that library) and choose Decompile source to Symbol File

The above steps allowed me to place breakpoints and actually hit them!

Breakpoints successfully hit when using the built-in ILSpy decompiler

Option 2: use JetBrains dotPeek as a decompiler

I’ll share an alternative approach using the dotPeek decompiler. Some people say it works more reliably. I have no opinion yet, as I rarely do decompilation. Also, I have only used dotPeek so far, so I cannot compare. The steps, in addition to the ones described above, are:

  1. Install JetBrains dotPeek
  2. Open dotPeek
  3. Click “Start Symbol Server”
  4. I chose “All except .NET Framework assemblies”
  5. In dotPeek Options -> Symbol Server find the URL of your local symbol server. This will be something like http://localhost:33417/
  6. In Visual Studio, go to Tools -> Options -> Debugging -> Symbols, and add the above URL as a symbol server (ensure it’s enabled)
  7. In Visual Studio’s Modules window, find the DLL that you want to debug and select Load symbols from the context menu. Select and let dotPeek generate PDB.
Breakpoints are successfully hit when using the free dotPeek decompiler from JetBrains

Summary

I was pleasantly surprised to learn that even when we work with closed platforms in Visual Studio 2022, we have free debugging tools that make it very easy to decompile and debug third-party code. I recently used dotPeek (option #2) to find a problem in Optimizely CMS code, and the debugging experience was excellent.

Good luck, and I hope you do not use this knowledge too often! 😊

The post Optimizely CMS 12: debugging without source code and symbols appeared first on Tim Taurit.

]]>
https://taurit.pl/optimizely-cms-12-debugging-pdb-symbols-source-code/feed/ 0
VS Code: open browser when application server is ready https://taurit.pl/vs-code-open-browser-when-application-server-is-ready/ https://taurit.pl/vs-code-open-browser-when-application-server-is-ready/#respond Mon, 10 Feb 2025 17:48:57 +0000 https://taurit.pl/?p=2190 A golden standard: launching a project with a single action When we set up a project, I think it’s of great value to allow to build and launch the project with a single action. Developers launch their code dozens of times a day, so it’s a worthwhile optimization. The action to launch a project would ... Read more

The post VS Code: open browser when application server is ready appeared first on Tim Taurit.

]]>
A golden standard: launching a project with a single action

When we set up a project, I think it’s of great value to allow to build and launch the project with a single action. Developers launch their code dozens of times a day, so it’s a worthwhile optimization.

Example: building and launching project with a single button in Visual Studio

The action to launch a project would typically be:

  • Clicking a single button in the IDE
  • Using a single keystroke in your IDE (like F5 in Visual Studio),
  • Or at least launching a single build file, e.g., named start.bat.

What exactly should the “launch” action do?

In front-end projects, I like the launch action to do three things:

  1. Build the application
  2. Launch a web server serving the application
  3. Open the browser with the URL most likely needed by the developer

The choice of what page to open is not always obvious and might depend on the developer’s work. It seems reasonable to open one of the following:

  • In web projects:
    • A home page of the application (if possible, the development variant with hot-reloading),
    • A Storybook instance (if our workflow is driven by such a tool)
    • A specific route useful for development on a given day (e.g., a login page or a frequently modified section)
  • In backend projects:
    • A Swagger UI endpoint, allowing crafting quick requests to test the backend
    • A GraphQL Playground (if the project uses GraphQL)
    • An admin dashboard (e.g., in Content Management Systems)
    • A monitoring page showing live logs

VS Code: wait to open the browser until the server is ready

Today, I’ll share a recipe for configuring the Visual Studio Code project so it waits for the specific message to appear in the server’s output before opening the browser window. For example, we can wait for a message like:

  VITE v6.0.11  ready in 6774 ms
  ➜  Local:   http://localhost:5173/

Waiting gives us benefits:

  • We avoid the “404 not found” page being displayed if the browser opens too early
  • We can capture the correct port number instead of hardcoding it

I’m sharing this recipe because it’s not documented comprehensively, and even modern AI tools like o3-mini had trouble implementing it correctly.

The solution: a minimal project in VS Code

Here’s a minimal Node.js project showing the problem and solution.

package.json

{
  "name": "myproject",
  "version": "1.0.0",
  "scripts": {
    "start": "timeout /t 4 && npx serve"
  }
}

Here, we declare a script named start. The script waits for 4 seconds (an artificial delay for demo purposes) and launches the npx serve tool, which serves the contents of the current directory in a browser.

launch.json

The fun part is in the launch.json file. Here, we can specify that we run the start script and wait for a specific line (that matches the pattern Regex) to appear in the terminal. When it does, we open the browser with a URL defined in uriFormat property. The %s is whatever was captured by the first regex group in the pattern:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Start debugging and open Chrome",

      "type": "node-terminal",
      "request": "launch",
      "command": "npm run start",

      "serverReadyAction": {
        "action": "debugWithChrome",
        "pattern": ".*http://localhost:(\\d*).*",
        "uriFormat": "http://localhost:%s"
      }
    }
  ]
}
The browser is launched only when the log appears in the output of the launched process.

Gotcha: The browser doesn’t open despite serverReadyAction.pattern Regex’s presence

There is one interesting issue you might encounter is that the browser might not open despite the pattern looking fine at first glance.

It’s worth noting that many web servers output their logs in color or with another kind of console formatting. In such a case, your regex will be matched against lines of text that aren’t plain text but text with various escape sequences. My example above, in reality, is matched with a line like:

β”‚ - Local: http://localhost:3000 β”‚

So, between the word Local and the URL, we do not only have whitespace but escape characters, too. Your Regex should consider that. There is no single standard to disable formatted output in all console tools, so your workaround for this problem might vary.

Example code for this blog post can be found in my GitHub repo.

The post VS Code: open browser when application server is ready appeared first on Tim Taurit.

]]>
https://taurit.pl/vs-code-open-browser-when-application-server-is-ready/feed/ 0
My top learnings from 2024: best spaces to work in Warsaw https://taurit.pl/warsaw-places-to-work/ https://taurit.pl/warsaw-places-to-work/#respond Mon, 30 Dec 2024 07:15:02 +0000 https://taurit.pl/?p=2152 During 2024, I had a few months of freedom to work from wherever I liked. In the process, I tested and discovered a few places where one might find a desk and some space to work. This article concludes the mini-series of blog posts summarizing some of my 2024 learnings. My top 5 places to ... Read more

The post My top learnings from 2024: best spaces to work in Warsaw appeared first on Tim Taurit.

]]>
During 2024, I had a few months of freedom to work from wherever I liked. In the process, I tested and discovered a few places where one might find a desk and some space to work. This article concludes the mini-series of blog posts summarizing some of my 2024 learnings.

My top 5 places to work in Warsaw (Poland)

Here are my top 5 favorite places to work, ordered from my favorites to my least favorites.

#1: WeWork (coworking space)

All WeWork offices in Warsaw are designed similarly, offering a relaxed workspace.

Although this was the last place I discovered chronologically, I put it in the first position because, so far, it is my favorite of the ones I tested. Why? It’s a combination of a few factors:

  • Pro: while not free, it’s affordable, even when starting your own business. I’m sure I’d pay more monthly for coffee and cakes if I worked in a cafe, not to mention how much more convenient it is to have your desk guaranteed for all day.
  • Pro: It’s open seven days a week until midnight. Which is perfect if your best job is done on non-standard hours. Public libraries, in contrast, usually close pretty early.
  • Pro: relaxed atmosphere and reasonable privacy (personal space). While this might not be natural initially, the place has music playing 24/7, and the vibe is somewhat like in the cafes. Each office even has a barista station (service included in the subscription)! Also, if you need privacy for your screen or calls, there are usually enough phone booths or places to sit to make you feel comfortable.
  • Con: ergonomics is below the standards of a typical office. Desks and chairs don’t have height regulations. If you want a laptop stand or a secondary monitor, you need to bring it with you.
  • Pro: fast and reliable internet. Which I could never find in any large-scale public library in Warsaw.

If you’d like to try WeWork, you can help me by using my referral link, which I can share as a member 😁 Just for clarity, I write this blog for fun, and I don’t think my opinion would be any different without this incentive πŸ™‚ Check out the WeWork coworking space in Warsaw yourself.

#2: National Library

The National Library is quite the opposite of WeWork in the vibe but has other strong points that make it my number two:

  • Pro: Access is free, as in “0 zlotys”. Hard to beat.
  • Pro: It’s beautifully located in the Pole Mokotowskie park. It was perfect to take a break in the middle of a park in the summer. It also has a garden where I spent many hours learning Spanish πŸ’ƒ
  • Pro/Con: The building has extreme sound isolation and is completely silent. This can be helpful. I found it easy to go into deep focus mode in such conditions. However, you also start to be very aware of every little noise you makeβ€”chair, clicking mouse, typing keyboard, etc. and be a bit uncomfortable in such an environment.
  • Con: As in most libraries, there are some rules about not bringing food or drinks into the reading rooms. Even though people in the cloakroom were always nice, it’s a small barrier to overcome each time you enter/leave because you need to leave some things outside.
  • Con: The Internet is not reliable. Some domains seemed blocked (e.g., I couldn’t call OpenAI API this year), and sometimes the connection expires and needs authentication. Sometimes, the network seemed overloaded by too many people using it. The only reliable solution was setting up my hotspot.
  • Pro: Air conditioning in the summer works well!
The National Library is located in the middle of a park

#3: University of Warsaw Library (BUW)

When I started my solopreneur adventure, I thought I would spend most of my time there. But I often saw it was overcrowded and had to look for another place. So, I no longer go to BUW for work.

  • Pro: beautifully designed building with a rooftop garden, located by the Vistula River, and in a lively neighborhood.
  • Pro: if you want books, you have everything available at your fingertips – free access to thousands of books of different kinds.
  • Pro: while it’s not allowed to eat/drink in the library, you can bring and carry your backpack without any issues, regardless of what’s inside – and it’s convenient.
  • Pro: usually open 24/7, with some exceptions.
  • Con: overcrowded, and during university exams, extremely overcrowded.
  • Con: the internet is a joke. And the building acts like a Faraday cage, preventing you from using your own hotspot in most areas. Also, access to electrical sockets is rare.
BUW has beautiful architecture and a garden
I find it suitable for reading books and learning but certainly not for work that requires reliable internet access, occasional Zoom calls, or a guarantee that you’ll have a chair to sit in.

#4: CIC Warsaw @ Varso Place

It might seem like an unfair comparison with other options because I’ll only describe the free (non-commercial) part of this coworking space.

  • Pro: I don’t think you can find a more centrally located coworking space than this. It’s next to the central station, in the Varso building. There are plenty of options to eat nearby, so you won’t be hungry. The design is nice and modern.
  • Pro: While coworking has commercial offices, it quite generously offers ample space to be used for free by anyone
  • Pro: easy access to the place without any registration needed.
  • Pro: good internet access.
  • Con: unpredictable hours of opening. I went there a few times, and twice they had some events going on when the space was closed. So, in my opinion, it’s not a reliable option for serious work. Also, on evenings and weekends, it’s officially closed (I’m not sure if it’s practically closed, too, because the building lobby is probably open all the time).

#5: Faculty of Modern Languages ​​at the University of Warsaw

This modern, spacious building is opposite the University of Warsaw Library I described earlier.

  • Pro: It’s in a good location, next to BUW. It has a nice design, with a modern space and rooftop garden available until 17:00.
  • Pro: Usually not crowded, finding a good spot to sit is easy.
  • Con: you don’t get internet access unless you are a student. I was a student in evening Spanish classes but didn’t qualify either. Your hotspot might be the best option.
  • Con: while no one seemed to have a problem with 30-something-year-old me working there a bit, it has a university vibe, so I felt a bit out of place, even though I was a student of evening classes myself πŸ˜…
If you are not scared by the UV light, the rooftop garden in this building is perfect. It seems not many people discovered it, and you can find a few benches to read, learn, or (as I tried) converse with ChatGPT in a foreign language to practice πŸ™‚

Honorable mentions

I’ll also mention two more places that are not meant for serious work but were a fun option from time to time:

Suntago

While the humid atmosphere might not be electronics-friendly, I used my flexible schedule to go there for a full day, read motivational books, and plan tasks for the week on a sun lounger 😁 Quite a pleasant experience.

University of Warsaw’s Faculty of Physics

I don’t think I was legally allowed to be in the student’s club there, but it’s so close to my home that I went there a few times (and in the summer, it was pretty empty and didn’t seem to bother anyone). It was a good workplace, with high-speed open internet access, air conditioning, and a pleasant design.

Places I wanted to try but didn’t find time

  • Brain Embassyβ€”I was there once for a meetup, and it looks like a great coworking space. I selected WeWork because they had more transparent pricing and offerings, but I haven’t tried Brain Embassy yet, and it might be interesting, too.
  • Public Library at Koszykowa Streetβ€”I only briefly visited it; I might like it, but I haven’t spent an entire workday there.

What do you think?

Have you tried to work in any of the places I mentioned? Can you recommend another one?

Good luck finding the perfect spot!

The post My top learnings from 2024: best spaces to work in Warsaw appeared first on Tim Taurit.

]]>
https://taurit.pl/warsaw-places-to-work/feed/ 0
My top learnings of 2024: the best tools I discovered this year https://taurit.pl/my-top-learnings-of-2024-best-tools/ https://taurit.pl/my-top-learnings-of-2024-best-tools/#respond Sun, 29 Dec 2024 19:22:10 +0000 https://taurit.pl/?p=2121 This post continues my series, presenting the most significant technology insights I discovered in 2024. I’ve already posted about my recent learnings about the world of hardware. Here, I’ll list a few discoveries related to software tools and online services that I discovered and now find indispensable. #1: GitHub Copilot: if you don’t use it ... Read more

The post My top learnings of 2024: the best tools I discovered this year appeared first on Tim Taurit.

]]>
This post continues my series, presenting the most significant technology insights I discovered in 2024. I’ve already posted about my recent learnings about the world of hardware. Here, I’ll list a few discoveries related to software tools and online services that I discovered and now find indispensable.

#1: GitHub Copilot: if you don’t use it in 2024/5, you miss a lot!

I don’t know why I stubbornly ignored every chance to try GitHub Copilot in the past. Perhaps I was confusing it with some other autocomplete feature I tried and was disappointed with.

This changed entirely for me in 2024. I just installed GitHub copilot to try it, subscribed for $10/month, and was stunned at how good it is. I never want to code without it again! It’s worth every dollar. I feel it turned me from a “10x programmer” to a “100x programmer” overnight πŸ˜‚

GitHub Copilot can greatly reduce the cognitive complexity of daily coding. Even when the code it generates requires refactoring, it usually works out of the box and helps me reach the solution more easily.

#2: Software for creating quality Generative AI images offline exists, and it’s free!

This year, I generated thousands of images for my flashcards, which I published on the Flashcard Space portal. This wouldn’t have been possible (economically) without being able to generate images locally on my hardware and GPU.

I was surprised to find that top-quality AI models for image generation, such as the Stable Diffusion model family or Flux family, are available for free and run on commodity hardware.

However, to use those models, a software wrapper is needed that exposes the models’ features as an API or UI. This year, I discovered two such programs:

  • AUTOMATIC1111 Stable Diffusion WebUI is a tool with a pleasant web interface and a list of features longer than this blog post. I typically use it for image generation and face restoration in old or blurry photos.
  • SwarmUI is also a Web UI that facilitates work with image generation models. On the surface, it’s similar to Stable Diffusion Web UI. It seems to have a smaller set of features but gets updates more quicklyβ€”I switched to it because the above tool didn’t support Stable Diffusion 3.5.
I was surprised to discover that despite the high prices of services like Dall-e or Midjourney, you can generate comparably good images for free on commodity hardware. Here’s one of the illustrations generated for my project, Flashcard Space. The AI model runs locally (for 30 seconds per image on average) and handles text correctly if given several attempts.

#3: RBTray (minimize any Windows app to the notification area)

This little tool helps minimize any running application to the system notification area (also known as tray), even if the application doesn’t explicitly support it.

It’s super easy to use: right-click the “minimize” button on the window to send the app to the notification area instead of the taskbar.

During development, I often have multiple consoles (cmd.exe) or PowerShell instances open, and finding the right one can take several attempts. So my convention is to minimize the long-running processes like local development web servers to reside in notification area, where I never even see them.

RBTray allows minimizing any app to the notification area, even when it doesn’t support it natively.

#4: Microsoft PowerToys

Microsoft PowerToys is a Swiss knife with many small tools, and I often think, “It should be a part of the operating system!”. But there are only a handful I found indispensable:

  • Quick accent. Now I can quickly type in Spanish characters without resorting to copy-paste method when I need them πŸ˜‰
  • Color selector. Every browser and graphic editor has a color picker, but this tool enables it on the system level. So you can sample the color of any pixel on the screen.
  • Always on top. This feature adds a keyboard shortcut that allows you to pin a specific window to be “always on top” of other apps. It also adds a colorful outline to that window to signal it. I sometimes use it with Sticky Notes or Notepad to have a small to-do list of current tasks that is always visible.
Among other features, Microsoft Power Toys has a “Quick accent” tool to allow users to enter foreign characters easily. If you learn Spanish or any other language, this might be the most convenient way to solve this problem!

#5: ResilioSync (rediscovered)

I’ve had a lifetime license for Resilio Sync for a few years now, but this year, I finally made good use of it.

Resilio Sync is a peer-to-peer file synchronization tool that uses BitTorrent technology to securely share and sync files directly between devices without relying on cloud storage. It is fast and efficient. When I need to synchronize folders with many files (e.g., 100,000) between my devices, it triumphs over cloud storage services like OneDrive or Google Drive with its performance.

The power and flexibility of synchronizing folders with Resilio Sync enables me to synchronize my stationary PC (where I need to do part of the work on a powerful GPU) with a laptop (which gives me the freedom to work from anywhere) to a degree which wasn’t possible with cloud sync platforms.

This way, I can share most applications, settings, and files. It’s almost like using the same system on both computers but without the problems of using a remote desktop solution like RDP or TeamViewer.

ResilioSync works cross-platform and allows you to synchronize selected folders between your devices.

I also use it to synchronize folders between my Android phone and Windows, for example, so that my photos are available on the PC without cables or 3rd party cloud storage.

#6: Making the most of chatbots: ChatGPT, Gemini, Claude

You probably don’t need me to advertise AI chatbots because they are everywhere this year. But when I mention to someone I use them a lot in my work, I often hear the question: “Which ones?“. So here’s my summary:

  1. ChatGPT (usually the 4o model, sometimes o1-mini and o1). ChatGPT is my typical first choice whenever I want to fix a problem, learn something, generate some ideas, or sample data.
  2. Google Gemini (currently v2.0 Flash is the most powerful one). I find it exceptionally good at translating text between languages while preserving the tone of the text. I think it beats Google Translate, and the last time I checked, I liked it more than ChatGPT for that purpose. Also, the last time I checked, it had the largest context window, allowing me to craft a very long prompt with lots of input data included.

    The only big downside is that it seems very sensitive to offensive keywords, and it applies the safety checks very blindly. It does this to the degree that I cannot translate a sentence like “The cat is black” to Spanish because it triggers a safety break when it encounters the n*** word.
  3. Anthropic Claude. If the other two fail, it’s a sort of a fallback for me. Still, it had the Canvas mode before other platforms implemented it, and I had it solve some programming problems when no other engines could figure out the working solution.

Further Reading

Also, this year, I found a few lists from fellow programmers who maintain lists of their favorite software. They are much more comprehensive than I posted here, so you might want to check them out!

Thanks for stopping by!

The post My top learnings of 2024: the best tools I discovered this year appeared first on Tim Taurit.

]]>
https://taurit.pl/my-top-learnings-of-2024-best-tools/feed/ 0
My top learnings of 2024: PC hardware https://taurit.pl/my-top-learnings-of-2024-pc-hardware/ https://taurit.pl/my-top-learnings-of-2024-pc-hardware/#respond Sat, 28 Dec 2024 10:39:39 +0000 https://taurit.pl/?p=2115 Hey! As 2024 ends, I’ll publish a short series of blog posts about what I have learned and discovered in the technology world over the last 12 months. 2024 is a super exciting year, with the fast rise of Generative AI. GenAI applications are hitting the market almost daily, but I’ll write about it in ... Read more

The post My top learnings of 2024: PC hardware appeared first on Tim Taurit.

]]>
Hey! As 2024 ends, I’ll publish a short series of blog posts about what I have learned and discovered in the technology world over the last 12 months. 2024 is a super exciting year, with the fast rise of Generative AI. GenAI applications are hitting the market almost daily, but I’ll write about it in one of the following posts.

However, I’ll begin with a few points on what I learned about computer hardware. Hardware is a topic where I’m a power user, but now and then, I discover something that strikes me as fundamental knowledge that somehow eluded me in the last twenty years or so 😊. So, let’s begin!

#1: The quality of the computer’s cooling can make a massive difference in the user’s experience

Until this year, I had overlooked the aspect of computer cooling. It moved into my area of focus when:

  • I started to work in public spaces (like libraries) and noticed how much the fan noises of laptops can interrupt other people in silent environments
  • I started using my GPU to nearly 100%-utilization level for long hours, and it was the noisiest thing in my apartment, begging for some solution.

Thanks to those specific problems I wanted to solve, I also learned that I could additionally reduce the noise level and improve the cooling of my PC build with pretty cheap components like:

  • a high-quality CPU cooler (I chose Endorfy Fera 5, and it’s perfect for my old Ryzen 3700X)
  • an SSD radiator (the one I chose, be quiet! MC1, is a pleasure to assemble and works magic)
  • PC case ventilators (contrary to what I imagined, adding six ventilators made the overall setup more silent because they can efficiently cool the case at very low speeds)
Replacing CPU and PC case cooling turned out to be quite time-consuming, but I enjoyed it πŸ€“
Adding a radiator to an M.2 SSD is controversial, as many say it’s unnecessary. Still, it helped me bring the temperatures of my Samsung 970 EVO Plus to the recommended operating ranges and, perhaps, reduced the risk of data loss.

It also taught me that the next time I buy a laptop, I should look at reviews of its thermal efficiency. I would seriously consider something cooled passively (fanless) that makes no noise. And if I needed a more powerful machine that cannot be cooled passively, I’d look for one with an aluminum case for better heat dissipation, so fans are only used sporadically.

Once I know how silent it can be, I don’t want to return to anything noisy!

#2: Microsoft Windows comes with Storage Spaces – a software RAID solution that might come in handy

Over the years, I have accumulated plenty of old SSD drives. They seem reliable (with no failures so far) but somewhat outdated (sizes range from 256 GB to 512 GB, and some have older interfaces like SATA III).

With a desktop computer, I could easily fit them all in the case, but on the OS level, I had to manage six small partitions, which was annoying.

This year, I discovered that Windows comes with a feature called Storage Spaces. It’s a software RAID solution that allows you to combine multiple physical disks into fewer logical disks and gain better speed or resilience.

A screenshot of the Storage Spaces configuration window in Windows 11
A screenshot of the Storage Spaces configuration window in Windows 11

I’ve been using it for over a month now. I combined three disks into one logical disk (the “parity” variant) for data resiliency. I was surprised by how easy it was to set up, and so far, it looks like a worthwhile improvement!

#3: Is 12 GB VRAM in GPU enough? In the era of Generative AI, it isn’t

After considering various options this year, I bought an ASUS NVidia RTX 4070 Super with 12 GB VRAM. The collective internet wisdom said 12GB of VRAM is more than enough in 2024. But this year has proven to me that even if it’s okay for gaming (games from Cyberpunk 2077 Phantom Liberty to Indiana Jones and the Great Circle look beautiful paired with this card), it isn’t enough to comfortably use the AI-powered tools.

When I prepared my vocabulary courses for the Flashcard Space project, I generated thousands of images using Stable Diffusion XL and Flux.1 models (check it out!). In hindsight, this could have been much faster and less frustrating if I just had 16 GB of VRAM in my GPU or more. It would also enable me to use models that won’t run with so little VRAM.

I expect we’ll see more applications of Generative AI models run locally on users’ machines. The next GPU I buy will certainly have more VRAM than 12 GB.

#4: Mechanical keyboards are not for everyone

Mechanical keyboards are a somewhat popular piece of equipment among programmers. I, too, bought into the hype and wanted to have the best tool available on the market, rationalized by the fact I use it every day for long hours.

I bought the Keychron K8 Pro, my first mechanical keyboard. Don’t get me wrong, it is a solid device and heavy enough to be used as a weapon if you need to defend yourself 😊 But it made me realize mechanical keyboards are a hobby rather than a ready-to-use premium device.

My mechanical keyboard

For a start, I didn’t like the “clicky” blue switches that were supposed to be so satisfying to type on. I had to order another set on Aliexpress and replace each one. Then, the keycap profile turned out to be very different from all the office keyboards I used, and neither my wife nor I could adapt to it after months of use. Also, the keycaps are not very strong, and I needed to use superglue a few times, or go down the rabbit hole of Aliexpress shopping for other keycaps, which seem either ugly or don’t have all the keys I need.

And I just wanted a premium keyboard that works and requires no maintenance. So, I guess customizable mechanical keyboards are not for everyone!

#5: I thought I needed a Chromebook, but I didn’t

I typically use Windows, but I got curious about Chromebooks this year, even if just to learn something new. Chromebooks looked like interesting devices because most are inexpensive yet have a reputation for being very well-optimized to run well, even on low-end hardware.

So, I bought a small netbook-sized Chromebook (HP Chromebook 11A G8 EE) to see if I liked the hardware and the whole ecosystem. And I sold it after a month or two, disappointed. My takeaways from this experience:

  • Skimping on hardware just isn’t worth it. I don’t know how people can use such a $50 laptop, enjoy it, and review it 5/5. In my experience, it was laggy and unsuitable even for writing blog posts or taking notes in Google Docs.
  • Chrome OS is a quality piece of software, hands down. Yet it is not for me. I can’t use a computer without Microsoft OneNote (it’s not compatible), and I’m not sure what I would do on a computer without Visual Studio. I also missed Total Commander πŸ˜‰
  • On the positive side, I enjoyed the 11-inch form factor: it is small, very light, and can be powered by a phone chargerβ€”perfect for travel!
I bought the $50 Chromebook (HP Chromebook 11A G8 EE) but sold it soon after that, disappointed by the performance.

#6: There are silent mouse devices on the market, and I’m happy I discovered this niche

A small but enjoyable discovery is that there are mouse devices that are super silent (both in clicking and scrolling). Buying one was a great relief when I started working in shared spaces with quiet surroundings. My choice was Silver Monkey, Silent Office Wireless Comfort Mouse. It was a well-spent $10 this Black Friday 😁

I also considered the Logitech Pebble 2, known for its silent operation and nice design (also, it looks even more mobile), but the price factor decided this time 😊

How about you?

And what was your most significant learning about computer hardware in the last months? If you fancy, share your wisdom in the comments with me and other readers! 😊

The post My top learnings of 2024: PC hardware appeared first on Tim Taurit.

]]>
https://taurit.pl/my-top-learnings-of-2024-pc-hardware/feed/ 0
Is there benefit to refining Flux.1 prompts with gpt-4o or similar? https://taurit.pl/preprocessing-flux-1-prompts-with-gpt-4o/ https://taurit.pl/preprocessing-flux-1-prompts-with-gpt-4o/#respond Mon, 09 Dec 2024 10:27:30 +0000 https://taurit.pl/?p=2095 Recently, I’ve been generating many images to serve as illustrations in my Spanish vocabulary courses published on Flashcard Space. Last week I switched from Stable Diffusion XL to Flux.1 dev. It is a significant upgrade in image quality, but I also noticed the illustrations have less variety than SDXL, at least when I use the ... Read more

The post Is there benefit to refining Flux.1 prompts with gpt-4o or similar? appeared first on Tim Taurit.

]]>
Recently, I’ve been generating many images to serve as illustrations in my Spanish vocabulary courses published on Flashcard Space.

Last week I switched from Stable Diffusion XL to Flux.1 dev. It is a significant upgrade in image quality, but I also noticed the illustrations have less variety than SDXL, at least when I use the same simple prompts as before. When I worked on a flashcard set that teaches names of professions in Spanish, it felt like most illustrations depicted the average-looking middle-aged Caucasian male with a beard as a representative of all professions, and I looked for a way to add variance to my image outputs πŸ™‚

Idea: Using AI to refine prompts for image generator model

People discover interesting prompt engineering tricks to improve outputs of every image generation model. And since we already have powerful and cheap general-purpose LLMs like gpt-4o, we can codify the knowledge about how to make a good prompt tailored to Flux.1 and use AI to preprocess our basic prompt and create a fancier, refined version of it before we pass it to Flux.

My experiments will show images generated using three prompts:

  1. Base prompt, something like The road is full of leaves
  2. AI-preprocessed base prompt created with https://flux1.ai/prompt-generator prompt generator – much more verbose and fancier
  3. AI-preprocessed base prompt created with https://shop.xerophayze.com/xerogenlite – a different prompt generator where the author put significant work into crafting good LLM prompts

Experiment #1: refining a concrete, non-abstract prompt

Base prompt: The road is full of leaves

Prompt: The road is full of leaves,
Model: Flux/flux1-dev-fp8, Seed: 123456, Steps: 20, CFG Scale: 1
Preprocessed prompt: A winding road blanketed in vibrant autumn leaves, with fiery reds and golden yellows, dappled sunlight filtering through ancient trees, casting soft shadows, evoking a serene, tranquil ambiance.,
Model: Flux/flux1-dev-fp8, Seed: 123456, Steps: 20, CFG Scale: 1
Preprocessed prompt: A serene autumn landscape with a winding road blanketed in vibrant orange and red leaves, tall trees arching overhead, casting dappled sunlight across the scene. A tranquil oil painting capturing the essence of fall, soft light filtering through the foliage.,
Model: Flux/flux1-dev-fp8, Seed: 123456, Steps: 20, CFG Scale: 1

Comment: The aesthetics differ, but there isn’t much difference in the image’s composition or idea.

Experiment #2: refining a more abstract prompt

The area where I hope to see the most significant benefit of AI-refining prompts is when they are vague and relate to abstract concepts rather than describing something visual. So as an example, let’s generate an illustration for a flashcard with the following vague sentence:

Base prompt: She is able to solve the problem.

Prompt: She is able to solve the problem.,
Model: Flux/flux1-dev-fp8, Seed: 6789, Steps: 30, CFG Scale: 1
Preprocessed prompt: A confident young woman, surrounded by notebooks and a glowing laptop, deep in thought. Soft sunlight streams through a window, casting warm, golden hues across a cozy study.,
Model: Flux/flux1-dev-fp8, Seed: 6789, Steps: 30, CFG Scale: 1
Preprocessed prompt: A woman with determination in her eyes, sitting at a desk cluttered with papers and books, surrounded by a calm and orderly office. The morning light streams through the window, illuminating her focused expression as she holds a pen poised to write. A detailed digital painting captures the moment with vibrant colors and precise details. Bright sunlight creates dynamic contrasts across the workspace.,
Model: Flux/flux1-dev-fp8, Seed: 6789, Steps: 30, CFG Scale: 1

Comment: The results differ in aesthetics, but Flux.1 had the same idea about depicting “solving a problem.” Let me perform one more to see if it can handle even more vague descriptions, this time with idioms.

Experiment #3: ultra abstract prompt with idiomatic expressions

Base prompt: He tried to kill two birds with one stone, but ended up biting off more than he could chew.

Prompt: He tried to kill two birds with one stone, but ended up biting off more than he could chew.,
Model: Flux/flux1-dev-fp8, Seed: 654654, Steps: 25, CFG Scale: 1
Preprocessed prompt: A man stands in a lush green park, humorously juggling two vibrant birds and an oversized stone, with an astonished expression. Bright sunlight filters through leafy trees, casting playful shadows. A whimsical, cartoonish style enhances the scene.,
Model: Flux/flux1-dev-fp8, Seed: 654654, Steps: 25, CFG Scale: 1
Preprocessed prompt: A whimsical scene depicting a man attempting to juggle two colorful birds in a vibrant garden, bright flowers surrounding him. The composition is dynamic and playful, illuminated by warm sunlight casting cheerful shadows. A lively digital painting showcasing vivid colors and detailed textures.,
Model: Flux/flux1-dev-fp8, Seed: 654654, Steps: 25, CFG Scale: 1

Comments: Finally, with a highly vague prompt and two idiomatic expressions, the Flux model gave up and produced useless output. The refined prompts allowed me to create pictures with reasonable aesthetics, but they failed to see too far beyond the literal meaning of idioms.

Discussion

Based on the above, I don’t have a definite conclusion on whether it’s worth adding complexity to a project to preprocess and refine prompts with an LLM model like gpt-4o before passing the prompt to Flux.1.

It seems that Flux.1 has quite a good understanding of natural language, and as long as we don’t use idioms and abstract ideas, it can handle some ambiguity in prompts. Of course, the value added by preprocessing the prompt will depend on the guidelines we provide to LLM. If we are determined to invest time in it, we can surely guide the model to understand idioms, improve the aesthetics, or enforce a concrete image style.

If you have some experience with pre-processing prompts before sending them to Flux.1 and would like to share your conclusions with other readers, please leave a comment!

The post Is there benefit to refining Flux.1 prompts with gpt-4o or similar? appeared first on Tim Taurit.

]]>
https://taurit.pl/preprocessing-flux-1-prompts-with-gpt-4o/feed/ 0
Can we safely reference .NET8 libraries from .NET9 apps? https://taurit.pl/can-we-safely-reference-net8-libraries-from-net9-apps/ https://taurit.pl/can-we-safely-reference-net8-libraries-from-net9-apps/#respond Tue, 26 Nov 2024 14:40:05 +0000 https://taurit.pl/?p=2081 With each upgrade of the .NET platform, developers may decide whether to upgrade or wait until the rest of the ecosystem upgrades first. Here is my experience when I wanted to understand if it’s ok to bump up my projects to .NET 9 while some of the NuGet libraries I depend on are still on ... Read more

The post Can we safely reference .NET8 libraries from .NET9 apps? appeared first on Tim Taurit.

]]>
With each upgrade of the .NET platform, developers may decide whether to upgrade or wait until the rest of the ecosystem upgrades first. Here is my experience when I wanted to understand if it’s ok to bump up my projects to .NET 9 while some of the NuGet libraries I depend on are still on .NET 8.

The theory: .NET versions are highly backward-compatible

In theory, each subsequent version of .NET preserves a high degree of compatibility with the previous versions, including binary compatibility in most scenarios, so we can use packages compiled with older versions of .NET tooling.

Compatibility is high but not 100%, and Microsoft publishes the documentation listing breaking changes for each platform version (e.g., the breaking changes introduced in .NET 9).

I believe having breaking changes is best because the platform couldn’t evolve without them from time to time, but let’s see how they can affect our programs when we decide to upgrade.

Experiment: referencing NET8 library from NET9 program

I created a small demo app to show what happens if we reference a .NET 8 library that uses a BinaryFormatter API removed in .NET 9.

In the .NET library, I have two methods:

  • public static int Add(int a, int b) => a+b;
    This one has no breaking changes and should be compatible with all .NET versions.
  • public static void UseBinaryFormatter() (...)
    This one internally uses the BinaryFormatter class removed in net9. It is one of the known binary incompatibilities between net8 and net9.

Here’s how the program behaves:

The code compiled and mostly worked, but I was able to cause an exception rooted in breaking changes between versions

The key observations are that:

  1. The program allowed references from the dotnet9 program to the dotnet8 library without complaining. The NET 9 part of the code did not contain warnings.
  2. There is a warning in the NET8 library code that BinaryFormatter is obsolete, but we only see it if the library is part of our solution and not from a third party.
  3. The program can be run and execute the compatible part of code just fine, even though the same class uses incompatible types in other places.
  4. The program only fails at runtime when it reaches code that is no longer compatible with .NET 9. So, we risk detecting problems in our app late in the development process as such bugs are unlikely to appear in the IDE as warnings or compilation errors.

Conclusions

Mixing .NET versions in a project comes with risks that might be missed at compile time and only manifest at runtime. They are rare, and there is a high chance that everything will work fine, but to have confidence that a program will work reliably, our tests must have enough coverage to catch problems, and we should have warnings under control.

I enjoy upgrading my projects on “day 0,” just after the new stable versions of .NET are released. However, assuming that the new version is not a game changer for us, a more rational approach would be to delay it for a while and let the ecosystem of dependencies adapt. When we upgrade our solutions, it would also simplify things if we updated all projects together.

The post Can we safely reference .NET8 libraries from .NET9 apps? appeared first on Tim Taurit.

]]>
https://taurit.pl/can-we-safely-reference-net8-libraries-from-net9-apps/feed/ 0
PiHole alternative: PiHole and NextDNS compared https://taurit.pl/pi-hole-alternative/ https://taurit.pl/pi-hole-alternative/#respond Wed, 11 Sep 2024 13:29:59 +0000 https://taurit.pl/?p=1076 Why did I look for a Pi-Hole alternative? My main motivation for moving away from PiHole was enabling ad blocking and site blocking on my smartphone, which I often use outside my home network. I described this journey in one of my older blog posts, PiHole, but hosted in the cloud. I shared the discovery ... Read more

The post PiHole alternative: PiHole and NextDNS compared appeared first on Tim Taurit.

]]>
Why did I look for a Pi-Hole alternative?

My main motivation for moving away from PiHole was enabling ad blocking and site blocking on my smartphone, which I often use outside my home network.

I described this journey in one of my older blog posts, PiHole, but hosted in the cloud. I shared the discovery of a nice alternative to PiHole, NextDNS. In this article, I’ll compare the features of both programs in a more structured way.

PiHole vs. NextDNS

Here is how I see the key similarities and differences in those applications:

Feature Pi-Hole NextDNS
Basic information
Type of Service Self-hosted DNS sinkhole Cloud-based DNS filtering
Price Free but requires own Raspberry Pi or similar hardware. Free but with a monthly limit on queries. Paid plan around $1.99/month.
Main features
Can be used for ad and tracker blocking Yes Yes
Can be used for Parental Control Yes, requires custom configuration (finding and importing relevant blocklists) Built-in (has toggles to block categories like Porn, Gambling, Dating, Piracy, or individual apps like Instagram or Netflix)
Supports blocklists/allowlist customization Yes Yes
Works outside the home network No (local network only) Yes (anywhere – smartphones, laptops etc., with the right configuration)
Security and privacy
Supports encrypted DNS queries: DoH No (requires external resolver; quite complex setup) Yes (built-in)
Supports encrypted DNS queries: DoT No (requires external resolver; quite complex setup) Yes (built-in)
Logs privacy Local logging (can be disabled) Logging in the cloud. User has control over logs and can disable logging.
Privacy assurance Best possible – open source, self-hosted, logging to local device Requires trust. Seems a legit company with clear business model and super-clear privacy policy, though.
Convenience
Setup Complexity High (requires own hardware, knowledge of networking and Linux; the maintenance was an ongoing cost and burden for me, to be honest) Low (cloud-based, minimal setup; clear instructions for users on how to set up various devices)
Analytics Users can view log of requests and blocks and stats Users can view log of requests and blocks and stats

Summary

Pi-Hole is a powerful self-hosted solution. It’s open source and operates locally on your device, giving the best privacy possible. It requires more manual setup than cloud-hosted NextDNS. Unfortunately, it doesn’t allow secure access to its service from mobile devices, limiting its utility. This is due to a lack of support for modern protocols like DNS-over-HTTPS and the general complexity of exposing Raspberry Pi to the outside world.

NextDNS is a user-friendly, cloud-based solution that doesn’t require any always-on local hardware on our side. It offers advanced features like DoH/DoT. It addresses a goal similar to PiHole but, in my opinion, wins on the convenience front. You can use a fully functional version for free, but it has monthly limits. The limit, in practice, should allow using it on one device before having to jump to a Pro plan.

I’ve used both for quite some time. Maintaining your DNS filter is a bit of a pain in the butt, regardless of the chosen solution. From time to time, there are false positives, and we need to go and add something to the allowlist.

But in my opinion, a customized DNS service is worth having if you are a power user. It offers privacy gains, reduced online tracking and advertisements, and even better habits if we use it to block sites that drain our energy.

The post PiHole alternative: PiHole and NextDNS compared appeared first on Tim Taurit.

]]>
https://taurit.pl/pi-hole-alternative/feed/ 0
Preview images from Stable Diffusion and Dall-E in Insomnia (my new add-on) https://taurit.pl/insomnia-display-base64-images/ https://taurit.pl/insomnia-display-base64-images/#respond Wed, 04 Sep 2024 19:39:14 +0000 https://taurit.pl/?p=1991 Generative AI endpoints might return Base64-encoded images Recently, I explored the possibility of generating images using locally hosted Stable Diffusion models. Thanks to the AUTOMATIC1111 stable-diffusion-WebUI project, I got the local API working pretty quickly. However, one aspect of working with such APIs is that images come base64-encoded in a JSON property, and one can’t ... Read more

The post Preview images from Stable Diffusion and Dall-E in Insomnia (my new add-on) appeared first on Tim Taurit.

]]>
Table of Contents

Generative AI endpoints might return Base64-encoded images

Recently, I explored the possibility of generating images using locally hosted Stable Diffusion models. Thanks to the AUTOMATIC1111 stable-diffusion-WebUI project, I got the local API working pretty quickly.

However, one aspect of working with such APIs is that images come base64-encoded in a JSON property, and one can’t easily see a preview of what was generated. This prevents us from experimenting easily.

// example of a response from Stable Diffusion txt2img API
// (POST http://localhost:7860/sdapi/v1/txt2img)

{
  "images": ["VERY-LONG-BASE64-BLOB-WITH-IMAGE..."],
  "parameters": {
    "prompt": "masterpiece,best quality,<lora:tbh323-sdxl:0.6>,cat in fisheye,flowers,<lora:tbh123-sdxl:0.2>,paint by Vincent van Gogh,",
    "negative_prompt": "lowres,bad anatomy,bad hands,text,error,missing fingers,extra digit,fewer digits,cropped,worst quality,low quality,normal quality,jpeg artifacts,signature,watermark,username,blurry,nsfw,",
    "batch_size": 3,
    "sampler_name": "DPM++ 2M",
    // ...
  }
}


One solution is to write a helper application to display images and work around this issue. Another is to keep using the tools we normally use to test HTTP APIs (for me, it’s Insomnia) and extend their functionality with a plug-in πŸ˜‰

Previewing generated images in Insomnia

I ended up writing a short plugin to display a preview of generated images when we receive the response from a supported endpoint. It’s called insomnia-plugin-genai-image-preview. It is now available in Insomnia’s Plugin Hub.

A screenshot of my new plugin for Insomnia that displays preview of images returned by Generative AI APIs
A screenshot of my new plugin for Insomnia that displays a preview of images returned by Generative AI APIs

If Google brought you here and this is what you were looking for, take it for a spin! It’s really just a few lines of code, and the support is limited to Stable Diffusion Web UI and Dall-E APIs. But if there is demand for it, it might support more technologies soon πŸ™‚

The post Preview images from Stable Diffusion and Dall-E in Insomnia (my new add-on) appeared first on Tim Taurit.

]]>
https://taurit.pl/insomnia-display-base64-images/feed/ 0
Personalized email address: pros and cons https://taurit.pl/personalised-email-address/ https://taurit.pl/personalised-email-address/#respond Sat, 31 Aug 2024 06:08:51 +0000 https://taurit.pl/?p=1282 Personal email domain: introduction Many years ago, I set up a personalized email domain. It was an alternative to my long-used address in the format of official.name@gmail.com. I remember at least two factors that motivated me to seek a Gmail alternative. Frankly, I don’t care about them that much today, but here they are πŸ˜‰ ... Read more

The post Personalized email address: pros and cons appeared first on Tim Taurit.

]]>
Table of Contents

Personal email domain: introduction

Many years ago, I set up a personalized email domain. It was an alternative to my long-used address in the format of official.name@gmail.com. I remember at least two factors that motivated me to seek a Gmail alternative. Frankly, I don’t care about them that much today, but here they are πŸ˜‰

  • At my university, I received a piece of feedback that my @gmail.com address doesn’t look serious in an academic paper I co-authored. I was pushed to use something else, at least a company’s email πŸ™‚
  • I have always been interested in security and privacy issues. I saw Google as “too good” at profiling and collecting data (which could theoretically leak at some point). So, I preferred to take more control.

Long story short, I decided to switch from Gmail to a service called Fastmail. It’s a top-tier quality alternative to Gmail. Fastmail is just so good in every aspect. It has a clean UI on all platforms, performance, SPAM filtering, a better calendar, and great support for custom domains.

Side note: see Fastmail features overview here. If you want to join, it’s 10% off with my link which I can share as a paying client.

A few years back, I switched from Gmail to Fastmail. Fastmail allows easy setup of the personalised email address (and I love how performant and functional it is).

Personalized email address: the bad sides

Some time ago, I shared my experiences about the downsides of using domain aliasing on Reddit. I refined that list for this post. So, I observe the following downsides:

  • Using email aliases for each service, e.g., using facebook@yourdomain.com to log in to Facebook specifically, leads to occasional issues:
    • Sometimes, people asked, “What email did you use to register for this event?” to confirm I was an attendee. I sometimes have no idea what email I used. I received a few distrustful looks from security guards at the gates of meetups πŸ˜‰
    • Very rarely, when contacting companies, they require communication from the address they have in their databases. That leads to back-and-forth email exchanges to ensure you send the message from the right email address.
    • You might create an address like youtube@yourdomain.com and find out later that it became a Google account that can also be used to sign in to Gmail, etc. Then, using such an address to sign in to little-related services is sometimes confusing.
  • Failing to renew your domain puts you in a very difficult situation. If someone else buys the domain, they will receive your future emails. Then, they can do almost everything they want (e.g., generate password reset links and sign in to your accounts).
    • Also, whoever purchases your domain gains access to all your accounts upon your death. This might be a privacy concern for you (to the degree you care) and the people around you (who might have shared personal messages or files with you).
  • You need to pay for it (both domain and the email provider)

Personalized email address: the good sides

What was out of scope of the Reddit thread were the benefits:

  • There are several theoretical and practical benefits of email aliasing (using different email addresses for different services):
    • You know exactly how someone found your address. If I receive a job offer, I immediately see if it came to github@mydomain.com, someConference2024@mydomain.com, etc.).
    • You can use this information to create highly accurate automated rules. I have over 100 rules to help me never see emails about bank regulation updates. Or spam sent to an address used to register for a free IT conference three years ago. 😎
    • It’s an additional protection in case of password leaks. No one will be able to re-use your credentials if both the password and email differ πŸ˜‰
  • When I switched from a free email service to a paid service, I appreciated the business model. I expect my data will no longer be collected and sold.
  • Last but not least, having our own email has different esthetics than a generic one. You can make it creative, fun, and cleverβ€”in other words, 100% personalized. If that’s something you want, give it a try!

Summary

A personalized email address requires some setup and maintenance, which costs money every year. If you want to try it, be cautious. When you stop paying for the domain, someone else might take over your email, so it’s a bigger commitment than it might seem at first.

If your main motivation is to find an alternative to Gmail for privacy or a more modern app experience, I recommend Fastmail (10% off with this referral link). I’ve been a paid user for a few years, and I’m sure you’d love it, too.

And if you want to share your own experience, please do so in the comment section! Thanks for reading πŸ™‚

The post Personalized email address: pros and cons appeared first on Tim Taurit.

]]>
https://taurit.pl/personalised-email-address/feed/ 0
Google Gemini: JSON mode and Structured Outputs – request examples https://taurit.pl/gemini-json-mode-and-structured-outputs/ https://taurit.pl/gemini-json-mode-and-structured-outputs/#respond Thu, 29 Aug 2024 13:51:46 +0000 https://taurit.pl/?p=1894 What is JSON Mode in Google Gemini? I recently blogged about JSON Mode and Structured Outputs in OpenAI APIs. Today, I’ve examined the OpenAI competitor Google Gemini and tested whether it can adhere to the requested schema. The JSON Mode is a feature that helps enforce the output of Chatbots to be in JSON format ... Read more

The post Google Gemini: JSON mode and Structured Outputs – request examples appeared first on Tim Taurit.

]]>
Table of Contents

What is JSON Mode in Google Gemini?

I recently blogged about JSON Mode and Structured Outputs in OpenAI APIs. Today, I’ve examined the OpenAI competitor Google Gemini and tested whether it can adhere to the requested schema.

The JSON Mode is a feature that helps enforce the output of Chatbots to be in JSON format (in contrast to natural language). This is super useful when using APIs when developing software.

Example: Gemini response without JSON mode

So, as a baseline, let’s see how the API responds when the output format is not specified:

// POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent
// x-goog-api-key: ***

{
    "contents": [
        { "parts": [ { "text": "What are the capitals of France, Spain, Poland?" } ] }
    ]
}

The response is something like:

Here are the capitals of the countries you listed:

* **France:** Paris
* **Spain:** Madrid
* **Poland:** Warsaw

Example: Gemini 1.0 response in JSON mode (the model does NOT support it)

Before I show an example of a request that works, let me share one that I expected to work, but it didn’t:

// POST https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent
// x-goog-api-key: ***

{
	"generationConfig": {
		"response_mime_type": "application/json"
	},
	"contents": [
		{ "parts": [ { "text": "What are the capitals of France, Spain, Poland?" } ] }
	]
}

The response here is:

{
	"error": {
		"code": 400,
		"message": "Json mode is not enabled for models/gemini-pro",
		"status": "INVALID_ARGUMENT"
	}
}

The learning here is that gemini-pro model ID points to the older Gemini 1.0 Pro, not Gemini 1.5 Pro, where this feature is enabled! So, you most likely want to use the gemini-1.5-pro-latest model ID instead.

Example: Gemini 1.5 response in JSON mode

When we use the model that supports JSON mode (like gemini-1.5-flash or gemini-1.5-pro), the response indeed comes nicely formatted as JSON:

// POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent
// x-goog-api-key: ***

{
	"generationConfig": {
		"response_mime_type": "application/json"
	},
	"contents": [
		{ "parts": [ { "text": "What are the capitals of France, Spain, Poland?" } ] }
	]
}

The response now was:

{
  "France": "Paris",
  "Spain": "Madrid",
  "Poland": "Warsaw"
}

This JSON mode flag is slightly more refined than OpenAI’s non-strict JSON mode. OpenAI’s JSON mode requires you to request JSON output in the prompt, and you have documented the misbehavior of getting stuck generating whitespace if you are not explicit enough.

Example: Gemini 1.5 response in JSON mode with specific JSON schema

The cherry on top is the ability to request an answer that adheres to a given JSON schema. Here, I specifically ask for an array of objects with two properties: “Country” and “Capital”. This is how you do it in an HTTP request:

// POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent
// x-goog-api-key: ***

{
  "contents": [
    { "parts": [{ "text": "What are the capitals of France, Spain, Poland?" }] }
  ],
  "generationConfig": {
    "response_mime_type": "application/json",
    "response_schema": {
      "type": "object",
      "properties": {
        "capitals": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "Country": { "type": "string" },
              "Capital": { "type": "string", "description": "Country's capital name, in ALL CAPS. A local name, not international one." }
            }
          }
        }
      }
    }
  }
}

This request produces the desired response:

{
  "capitals": [
    { "Country": "France", "Capital": "PARIS" },
    { "Country": "Spain", "Capital": "MADRID" },
    { "Country": "Poland", "Capital": "WARSZAWA" }
  ]
}

It’s interesting to note that the AI model takes the “description” property from the schema as a hint on how to fill in the value, which is super useful and allows us to define the details of the contract.

Learnings: Structured Outputs in Gemini-1.5 vs GPT-4o

It seems that, on a high level, we now have a feature parity between OpenAI’s newest GPT-4o models and Gemini-1.5 regarding structured outputs. They both can respond by adhering to the provided JSON schema.

APIs are a bit picky about the provided schema. It might require some fine-tuning to have the same schema working everywhere:

Both APIs use hints in descriptive property names or separate description parameters.

Summary

It’s great news that we can cause two independently developed AI models to generate output in precisely the same structured format. This not only makes integration with those APIs much easier but also allows us to consult two or more independently trained AI models and see if their responses match or diverge, increasing (or decreasing) confidence in the response.

The post Google Gemini: JSON mode and Structured Outputs – request examples appeared first on Tim Taurit.

]]>
https://taurit.pl/gemini-json-mode-and-structured-outputs/feed/ 0
Grammarly for Markdown? First Impressions with JetBrains Grazie https://taurit.pl/grammarly-for-markdown-jetbrains-grazie/ https://taurit.pl/grammarly-for-markdown-jetbrains-grazie/#respond Wed, 28 Aug 2024 11:23:52 +0000 https://taurit.pl/?p=1860 Grammarly: great support in browsers, but doesn’t work in IDEs I have an active Grammarly subscription, and I enjoy how it helps refine my writing. It works well in browsers and notably has plugins for Microsoft Office. Also, if you write in another application and want to use its grammar check, you can always copy ... Read more

The post Grammarly for Markdown? First Impressions with JetBrains Grazie appeared first on Tim Taurit.

]]>
Table of Contents

Grammarly: great support in browsers, but doesn’t work in IDEs

I have an active Grammarly subscription, and I enjoy how it helps refine my writing. It works well in browsers and notably has plugins for Microsoft Office. Also, if you write in another application and want to use its grammar check, you can always copy and paste the text between the app and Grammarly’s app. Although it’s inconvenient, especially when we deal with formatted text and lose formatting.

As a developer, I often edit text content Markdown (*.md) files with documentation. I typically do it in Visual Studio or VS Code. There used to be an unofficial extension for VS Code that utilized Grammarly’s SDK, but it’s now deprecated and no longer works [1][2].

My experience of using Grammarly in WordPress editor (and browsers in general) πŸ˜‰

So, is there any alternative to simplistic dictionary-based spell-checking in development IDEs? I’m already used to advanced grammar analysis and receiving suggestions for writing quality πŸ™‚

I was happy to find (I think in last month’s newsletter) that JetBrains developed a product just for that! It’s called Grazie.

JetBrains Grazie: supported environments

Grazie‘s website doesn’t seem to mention Grammarly anywhere in the text, but those products clearly compete in the same market space. Grazie has extensions for many internet browsers. But the distinct feature is that it can also be used in all JetBrains IDEs like Rider, IntelliJ IDEA, PhpStorm, and at least 12 others πŸ˜‰

While it’s impressively wide-supported, as a .NET developer primarily using Microsoft IDEs, I’ll also highlight that the extension is currently unavailable for VS Code or Visual Studio (even with ReSharper Ultimate installed).

For this reason, to test and evaluate the add-on, I installed JetBrains Writerside, an IDE dedicated to writing technical documentation currently available for free while it’s in the Early Preview stage.

A frame from the Inglourious Basterds movie scene, source: YouTube

First impressions: text checking & correction

After installing the add-on, suggestions started to appear. I asked Google Gemini AI to generate a simple story with a few typos and stylistic errors and tested the Grazie add-on on such simple text:

For this particular input, there was an absolute parity of what was detected by both Grazie and Grammarly.

It’s worth noting that Grazie comes with a content generation feature, which you can see at the end of the textβ€”it suggests what to write next. It’s certainly something that might be useful (I love how GitHub Copilot does it with code generation). The feature works reliably here, but I found the generated text was rarely usefulβ€”it usually suggests just a few words ahead. They don’t usually fit what I wanted to write. I think it might be something worth more attention when underlying models improve.

Suggestions, however, seem useful overall, and quick fixes are available, so the user needs to approve the changes, not change the text themself. I find it slightly annoying that it takes ~1 second for the suggestion hover to pop up when I hover over a word marked as an error. Maybe it’s not an issue with the add-on, but with the IDE I chose (it is still an alpha version).

The suggestions go beyond simple spell-checking and offer rewriting sentences for clarity.

At the moment, the tool can be used for free, assuming you already have a license for any JetBrains IDE (or join the Early Access Program and use beta versions) and you don’t exceed some usage limit. If your needs exceed the limits, you can still get the Pro version as a part of JetBrainsΒ AI subscription.

Summary

My goal here was to raise awareness that such a tool exists and might be worth your attention if you are looking for an advanced spell checker or grammar checker for your Markdown files.

My impressions after a few days are positive, and I’m 100% certain that the documentation I have written in the last few days is more readable than it would be otherwise πŸ™‚ Have fun exploring!

The post Grammarly for Markdown? First Impressions with JetBrains Grazie appeared first on Tim Taurit.

]]>
https://taurit.pl/grammarly-for-markdown-jetbrains-grazie/feed/ 0
Converting .png to .ico – overview of tools, which is the best? https://taurit.pl/convert-png-to-ico-tools-overview/ https://taurit.pl/convert-png-to-ico-tools-overview/#respond Mon, 26 Aug 2024 11:24:50 +0000 https://taurit.pl/?p=1847 Is the .ico format still relevant today? An .ico file format can contain multiple images at different resolutions (e.g., 16×16, 32×32, 48×48, 64×64) and color depths (e.g., 8-bit, 24-bit). This allows the system or application to choose the most appropriate image based on the display context. In 2024, we have a wide choice of newer ... Read more

The post Converting .png to .ico – overview of tools, which is the best? appeared first on Tim Taurit.

]]>
Table of Contents

Is the .ico format still relevant today?

An .ico file format can contain multiple images at different resolutions (e.g., 16×16, 32×32, 48×48, 64×64) and color depths (e.g., 8-bit, 24-bit). This allows the system or application to choose the most appropriate image based on the display context.

In 2024, we have a wide choice of newer image formats well suited for the web, like .png, .webp or .avif. So is *.ico still relevant? I believe that in web applications, it is seen as a legacy format. Still, it has some advantages over PNG files, even if rarely used:

  1. An icon in the .ico format can store different images for different sizes. Some icons don’t scale down well, and they benefit from having a different, simplified 16×16 or 32×32 image that looks better than a scaled-down image.
  2. While browsers might be up-to-speed with supporting PNG, other tools might assume the presence of the favicon.ico in the legacy format (it was the standard for a long time).
Browsers can still utilize the favicons, typically in the *.ico format. They provide value in tab bars and bookmark bars.

Testing tools that convert PNG to ICO

For a long time, when I wanted to create an .ico file, I’d google “PNG to ICO” and choose one of the many online conversion tools. One problem I noticed was that the output was sometimes hugeβ€”one of my icons was 2 megabytes! It was as if the file contained a large uncompressed bitmap or several of them.

Serving a megabyte-scale file to display a small image is super inefficient, so I took a closer look to know which tool to pick for the job next time. And here’s a short test:

A 512×512 png basketball icon is a test input (46 694 bytes).
Source: Basketball icons created by Pixel Perfect – Flaticon
Tool used to convert PNG to ICOWhat did the output icon bundle include?Output icon size
https://convertio.co/png-ico/256×256 PNG
512×512 PNG-ZIP
25 532 bytes
https://convertio.co/png-ico/customizable, but the Favicon profile generated:
16×16 PNG
24×24 PNG
32×32 PNG
48×48 PNG
64×64 PNG
72×72 PNG
96×96 PNG
512×512 PNG
192 925 bytes
https://convertico.com/customizable, but by default:
16×16 PNG
32×32 PNG
48×48 PNG
64×64 PNG
256×256 PNG
121 810 bytes
https://cloudconvert.com/png-to-ico32×32 uncompressed file4 206 bytes
https://picflow.com/convert/png-to-ico256×256 PNG29 722 bytes
https://onlineconvertfree.com/convert-format/png-to-ico/16×16 uncompressed
24×24 uncompressed
32×32 uncompressed
48×48 uncompressed
64×64 uncompressed
72×72 uncompressed
96×96 uncompressed
128×128 uncompressed
256×256 PNG
192 225 bytes
https://www.websiteplanet.com/webtools/favicon-generator/16×16 PNG-ZIP1 280 bytes
https://jpghero.com/jpg-to-ico16×16 uncompressed
32×32 uncompressed
48×48 uncompressed
64×64 uncompressed
128×128 uncompressed
256×256 uncompressed
370 070 bytes
My script (described later).
I wanted to see what the optimum achievable for this image is.
16×16 PNG
24×24 PNG
32×32 PNG
48×48 PNG
64×64 PNG
128×128 PNG
256×256 PNG
47 040 bytes
A test of what PNG-to-ICO converters generated, dated 2024-08-23. Full test data is attached here.

Which tool works best?

All the conversion tools from the first page of Google search results fail somehow:

  • Sometimes, the selection of image sizes is controversial and doesn’t cover the typical use cases for icons,
  • Often, the compression is weak or nonexistent
  • Often, the order of images in icon files is non-optimal, although it doesn’t matter in 2024 I think

The least-worst option among the listed online converters is convertico.com, which has reasonable default settings and embeds compressed PNG files (however, the compression could be much better).

With a short script, I could generate an icon that is only 38% the size of the best result above. It has lossless quality and embeds even more icon sizes inside.

Sharing my solution

In case anyone wants to convert PNG to ICO in a more optimal way, this is how it can be done (assuming Windows console):

// use ImageMagick to generate image variants
magick input.png -resize 16x16 -depth 8 -quality 100 -define png:color-type=6 output-16x16.png
magick input.png -resize 32x32 -depth 8 -quality 100 -define png:color-type=6 output-32x32.png
...

// use optipng to compress the images. With '-nc' color palette won't be reduced, which is required by the next tool
optipng.exe -o7 -nc output-16x16.png
optipng.exe -o7 -nc output-32x32.png
...

// bundle .png files into .ico file
icomake.exe output.ico output-16x16.png output-32x32.png ...

The tools I use are:

This approach seems to solve the problem quite well, and I think I won’t have to revisit this issue anytime soon πŸ™‚

The post Converting .png to .ico – overview of tools, which is the best? appeared first on Tim Taurit.

]]>
https://taurit.pl/convert-png-to-ico-tools-overview/feed/ 0
OpenAI API: Structured Outputs – generate JSON schema from a class in C# https://taurit.pl/openai-structured-outputs-generate-schema-in-csharp/ https://taurit.pl/openai-structured-outputs-generate-schema-in-csharp/#respond Fri, 23 Aug 2024 09:22:14 +0000 https://taurit.pl/?p=1833 What problem does the Structured Outputs feature solve When using GPT models from the code, we developers don’t normally want the output to be in a natural language like in ChatGPT conversations. Instead, we want it to have some structure, and the lingua franca today for data interchange is JSON format. Until recently, reliably achieving ... Read more

The post OpenAI API: Structured Outputs – generate JSON schema from a class in C# appeared first on Tim Taurit.

]]>
Table of Contents

What problem does the Structured Outputs feature solve

When using GPT models from the code, we developers don’t normally want the output to be in a natural language like in ChatGPT conversations. Instead, we want it to have some structure, and the lingua franca today for data interchange is JSON format.

Until recently, reliably achieving JSON outputs in the same format was difficult in GPT models. We had several options trying:

  1. We could be very specific about the expected output format in the prompt, but this often failed. I provided several examples in my other blog post.
  2. We could use JSON mode to ensure the response is a valid JSON. I described JSON mode and some of its pitfalls earlier.
  3. Now, we have a feature called Structured Outputs that allows us to define a strict JSON Schema for the response.

Minimal example using REST API

Here’s an example you can run using Postman, Insomnia or similar HTTP clients:

// POST https://api.openai.com/v1/chat/completions

{
  "model": "gpt-4o-2024-08-06",
  "messages": [
    {
      "role": "user",
      "content": "Provide an example of a english noun at C1 language learning level."
    }
  ],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "word_response",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "Word": {
            "type": "string"
          },
          "WordTranslatedToSpanish": {
            "type": "string",
            "description": "The word translated to Spanish, preceded by an article (el/la)."
          }
        },
        "required": ["Word", "WordTranslatedToSpanish"],
        "additionalProperties": false
      }
    }
  }
}

JSON schema is unfortunately a bit verbose by its nature, but the point of the demo is to show that even though the prompt didn’t mention anything about the output format requirements, we now receive the following response:

{
  "Word": "Hypothesis",
  "WordTranslatedToSpanish": "La hipΓ³tesis"
}

I find it super convenient, and I see two benefits here:

  1. Adherence to the schema is guaranteed, which makes applications using the API significantly more reliable without the need to implement workarounds, endlessly fine-tune the prompt, or retry requests if they fail to conform to the schema
  2. The request became more… “promptless” πŸ™‚ You can see how I described the output details in the schema while leaving the prompt very generic. I didn’t ask for the word’s translation in the prompt, yet it was filled correctly based on the schema provided. I think this declarative way of describing output will become a preferred way to use Generative AI APIs from the applications. It plays well with the Object Generative Fill programming pattern I proposed recently.

Generating schema in C# using Newtonsoft.Json.Schema

The fun part of using Structured Outputs from the code is that we can generate the JSON schema from a class. Here’s an example in C#:

// code uses the Newtonsoft.Json.Schema NuGet package to convert class to a JSON schema

// Declare the format of the GPT model's response as a C# class
public class Country
{
    [Description("Country name preferred in English language")]
    public string InternationalName { get; set; }

    public int EstimatedPopulation { get; set; }

    [Description("Names of 3 people from that country most recognizable abroad")]
    public List<string> FamousPeopleExample { get; set; }
}

internal class Program
{
    static async Task Main(string[] args)
    {
        // Prepare the JSON schema
        JSchemaGenerator generator = new JSchemaGenerator();
        generator.DefaultRequired = Required.Always; // required by OpenAI

        JSchema schema = generator.Generate(typeof(Country));
        schema.AllowAdditionalProperties = false; // required by OpenAI

        var schemaJson = schema.ToString();

        Console.WriteLine(schemaJson);
    }
}

The above outputs schema that can be used in the API call:

{
   "type": "object",
   "additionalProperties": false,
   "properties": {
      "InternationalName": {
         "description": "Country name preferred in English language",
         "type": "string"
      },
      "EstimatedPopulation": {
         "type": "integer"
      },
      "FamousPeopleExample": {
         "description": "Names of 3 people from that country most recognizable abroad",
         "type": "array",
         "items": {
            "type": "string"
         }
      }
   },
   "required": [
      "InternationalName",
      "EstimatedPopulation",
      "FamousPeopleExample"
   ]
}

And the response matched the expectation when posted with the simple prompt “Provide information about Poland.“:

{
  "InternationalName": "Poland",
  "EstimatedPopulation": 38386159,
  "FamousPeopleExample": [
    "Marie Curie",
    "FrΓ©dΓ©ric Chopin",
    "Nicolaus Copernicus"
  ]
}

A warning regarding Newtonsoft.Json.Schema library

One thing I haven’t realized is that Newtonsoft.Json.Schema limits schema generation to 10 per hour and requires a license to exceed this limit! So please consider the license limitation before committing to this library in your project.

I was surprised with the following error: Newtonsoft.Json.Schema.JSchemaException: ‘The free-quota limit of 10 schema generations per hour has been reached. Please visit http://www.newtonsoft.com/jsonschema to upgrade to a commercial license.’

The status of feature support in popular libraries

The Structured Outputs support is now available in popular libraries like Betalgo.OpenAI and the official one, OpenAI.

Here’s a usage example for Betalgo. Unfortunately, I don’t like the design. They rushed to implement the feature, re-inventing the wheel and independently modeling the JSON Schema type. This prevents library users from generating JSON Schema from a class using other libraries and makes it much less attractive.

The OpenAI library did a much better job (see docs and usage example for Structured Output here). The library’s user needs to provide schema as BinaryData (and we can easily convert it from a string), so we can use the schema generated in the earlier section of this article.

You can also find a minimal C# example of generating JSON Schema from a C# class model and using it to query OpenAI API in my GitHub repository. The source code is here.

The post OpenAI API: Structured Outputs – generate JSON schema from a class in C# appeared first on Tim Taurit.

]]>
https://taurit.pl/openai-structured-outputs-generate-schema-in-csharp/feed/ 0
OpenAI JSON mode: wrong schema in the output (fix) https://taurit.pl/openai-gpt-json-mode-wrong-schema/ https://taurit.pl/openai-gpt-json-mode-wrong-schema/#respond Fri, 23 Aug 2024 07:52:44 +0000 https://taurit.pl/?p=1552 TL/DR: JSON mode remains available as an API feature, but the newest models include the Structured Outputs feature, which solves this problem 100% without workarounds needed. JSON Mode vs Structured Output When we integrate our applications with chatbot-like API, we often prefer to work with data in a structured format like JSON rather than natural ... Read more

The post OpenAI JSON mode: wrong schema in the output (fix) appeared first on Tim Taurit.

]]>
TL/DR: JSON mode remains available as an API feature, but the newest models include the Structured Outputs feature, which solves this problem 100% without workarounds needed.

JSON Mode vs Structured Output

When we integrate our applications with chatbot-like API, we often prefer to work with data in a structured format like JSON rather than natural language.

When I first drafted this article, JSON Mode was the only option for enforcing guardrails on OpenAI’s Chat Completion API output format. Since last week, a new and more robust tool has been available for this purpose: Structured Output. So let’s clarify the difference:

  • JSON mode is a simple flag to tell the API you expect the response to be in JSON format. It improves model reliability for generating valid JSON outputs but does not guarantee that the model’s response will conform to a particular schema.
  • Structured Outputs is a newer feature designed to ensure model-generated outputs will exactly match the JSON Schema provided by the developer.

In this article, I’ll show some behaviors of JSON mode, but please be aware that there might be a better option than was available at the time of writing.

Exploring JSON mode and its pitfalls

Why simply asking for JSON output in the prompt was not enough

As a starting point, let me show how I used to solve the issue before Chat Completion API had any parameters allowing it to steer it toward generating JSON output. Here is an example prompt:

You are a teacher of English language. Explain words from a given list.
Provide response in a JSON format.
Output should be an array of objects. Each object in array should contain properties: EnglishWord, EtymologyExplanation.

Example of the correct output:

```json
[
    { "EnglishWord": "hi", "EtymologyExplanation": "Hi developed from the Middle English \"hy\", similar to \"hey\" and \"ha\". " }
]
```

The input to process is:
- cat
- mouse

And this worked okay most of the time. However, the output format wasn’t stable, and the response format could deviate from a correct JSON in a few ways. Let me combine a few of the mistakes I encountered into a single example:

Certainly! I will explain the etymology of the words "cat," and "mouse" and provide the response in the specified JSON format.

```json
[
    {
        "EnglishWord": "cat",
        "EtymologyExplanation": "The word "cat" has a rich history and comes from the Old English word 'catt,' which was influenced by the Late Latin term 'cattus.' It's thought that 'cattus' was introduced to Old English through early Christian writings. The domestication of cats dates back to ancient times, with the word 'cat' remaining relatively consistent in its form and meaning throughout its history."
    },
    {
        "EnglishWord": "mouse",
        "EtymologyExplanation": "The word 'mouse' has its origins in Middle English 'mous,' which is derived from Old English 'mΕ«s.' This word can be traced back further to Proto-Germanic 'mΕ«s,' and ultimately to the Proto-Indo-European root '*(s)meus,' meaning 'mouse' or 'small rodent.' 'Mouse' has been used to describe these small mammals for centuries, and the word has undergone minimal changes in its spelling and pronunciation over time."
    }
]
```

These explanations provide a brief overview of the etymology and historical development of the words "cat," and "mouse" in the English language.

The problems with the above output are:

  • The actual JSON is preceded and followed by unstructured text.
  • The actual JSON is wrapped in a ```json block (here, you could argue I asked for it, but it might happen regardless of it)
  • The JSON contains unescaped quote marks around the word “cat”, which makes the syntax invalid – this happens even with the recent gpt4-turbo model

We can solve the first two issues on the application side with heuristic preprocessing (even though we’ll still pay for those useless parts of the output). But the third problem – invalid syntax – isn’t easily solvable. While it might occur only sporadically, keeping apps reliable with such variance in the output is a challenge.

JSON Mode for the rescue

JSON mode made a promise to solve all those issues:

When JSON mode is enabled, the model is constrained to only generate strings that parse into valid JSON object.
(…) JSON mode will not guarantee the output matches any specific schema, only that it is valid and parses without errors.

Source: Open AI API – Text Generation – JSON mode

We still need to describe schema in a natural language, but then we get guarantees about the output adherence to JSON specification. Let’s enable it, then! It’s as easy as declaring a single option in the request:

POST https://api.openai.com/v1/chat/completions

{
	"model": "gpt-3.5-turbo-1106",
	"response_format": {
		"type": "json_object"
	},
	"messages": [
		{
			"role": "system",
			"content": "You are a teacher of English language."
		},
		{
			"role": "user",
			"content": "..."
		}
	]
}

So, what output would it generate for my previous example? Let’s see:

{
   "words":[
      {
         "EnglishWord":"cat",
         "EtymologyExplanation":"The word 'cat' comes from the Old English 'catt', which is of unknown origin. It is believed to have come from other European languages such as Latin 'cattus' and German 'katze'."
      },
      {
         "EnglishWord":"mouse",
         "EtymologyExplanation":"The word 'mouse' originated from the Old English word 'mus', and is related to similar words in other Germanic languages such as German 'maus' and Dutch 'muis'."
      }
   ]
}

That looks better, but… this is not the schema I requested! I asked for an array in response. I even gave it an example, but it responded with an object with my expected array placed in some made-up “words” property.

Learning #1: in JSON mode, we still have no guarantees of schema adherence

The first observation is that the schema might differ even if we provide an example of the expected output in the prompt.

OpenAI measures it. For the recent models, prompting alone results in correct schema only in about 85% of the responses, and enabling JSON mode improves it to about 93%. But to guarantee schema adherence, we need to use Structured Outputs.

Adherence to requested JSON schema in the cases of 1) prompting alone, 2) using JSON mode, and 3) using Structured Outputs. Source: Introducing Structured Outputs in the API

Learning #2: asking for an array as a root element is a particularly bad idea

The fact that the API cannot return an array seems to be a common observation on forums. So, to make the API more reliable, it’s better to use an object rather than an array:

// array as root element causes adherence problems
[
 {"element1": "value1"},
 {"element2": "value2"}
]

// having an object at the root can improve reliability, based on observations
{
  elements: [
    {"element1": "value1"},
    {"element2": "value2"}
  ]
}

Summary

JSON mode is a useful improvement over describing JSON output in a prompt. It increases the chance that output will conform to the provided JSON schema. Enabling it is really simple. I found it’s often reliable enough to call the Completion API thousands of times and receive a response in a proper JSON format, matching the output example I provided.

It’s still important to remember that schema adherence is not guaranteed. For the guarantee, we now have the “strict” Structured Outputs feature.

The post OpenAI JSON mode: wrong schema in the output (fix) appeared first on Tim Taurit.

]]>
https://taurit.pl/openai-gpt-json-mode-wrong-schema/feed/ 0
Object Generative Fill: a code pattern idea (with example in C#) https://taurit.pl/object-generative-fill-pattern/ https://taurit.pl/object-generative-fill-pattern/#respond Sun, 11 Aug 2024 11:21:28 +0000 https://taurit.pl/?p=1771 What is Generative Fill? You are probably familiar with the Generative Fill feature in graphic editing software like Adobe Photoshop. The idea is that well-trained AI models can guess how to fill blank areas in the picture. The model can either try to guess what to do by itself or accept prompts to steer it ... Read more

The post Object Generative Fill: a code pattern idea (with example in C#) appeared first on Tim Taurit.

]]>
Table of Contents

What is Generative Fill?

You are probably familiar with the Generative Fill feature in graphic editing software like Adobe Photoshop. The idea is that well-trained AI models can guess how to fill blank areas in the picture. The model can either try to guess what to do by itself or accept prompts to steer it toward generating something specific.

When we use a generative fill feature to add more context to the photo, the result might look like this:

Example of an input image.
Example of a Generative Fill transformation performed by the Cloudinary AI service.

Idea: Use generative Fill pattern in object-oriented programming

I want to present the idea of a pattern in the object-oriented programming world that is similar in concept to generative fill for images. The pattern is to have an abstraction over generative AI API that can fill the missing properties in an object. Let me illustrate it with an example:

// A model partially initialized by user and then filled out by an AI service
class Country(string countryName) : ObjectWithId
{
    public string CountryName { get; init; } = countryName;

    [FillWithAI]
    [FillWithAIRule("Fill with the first historically known capital of the country, not the current one!")]
    public string? FirstCapital { get; set; } = null;

    [FillWithAI]
    [FillWithAIRule("Fill the value with name of the current president of the country")]
    [FillWithAIRule("Use CAPITAL LETTERS for this value")]
    public string? President { get; set; } = null;
}

To fill out the missing fields of an object described this way, we need a simple abstraction (here named GenerativeFill) that creates a clever prompt and uses the data we provide:

// fill values in one object instance
Country country = await GenerativeFill.FillMissingProperties(new Country("USA"));

// fill values in a list of objects
List<Country> countries = await GenerativeFill.FillMissingProperties(new Country[] { new("USA"), new("Poland"), new("France") });

Running such code should give us a result like here (for the last example):

The result of running the previous example. I highlighted the data filled out by the OpenAI model in green.

Benefits

The benefit of having an abstraction like the presented GenerativeFill class is that we abstract all the ugly code into a single service with a nice and simple public API. Some more concrete benefits:

  • We create a clever, generic prompt just once, and it covers many use cases. Creating a prompt string in my apps was always a pain point.
  • We insert the input data into the prompt with a generic implementation. It’s also an opportunity to optimize this and ensure there is no unnecessary JSON indentation to reduce cost πŸ™‚
  • We can also automatically insert an example of the response schema into the prompt. We already have all the information we need in the C# model!
  • Despite its simplicity, this abstraction allows the processing of multiple object instances in a single request to the API. It reduces the overhead and cost of input tokens. I believe that easy processing of batches of input data is a significant cost optimization and convenience win!

Drawbacks

On the side of the drawbacks, what I can see already is:

  • As with all high-level abstractions, it can be leaky. Most of the time, we can forget the complexity, and it works. But sometimes networking fails, we run out of credits, or the AI model randomly breaks the contract and doesn’t return all expected results. Some of it cannot be auto-resolved and will show up as exceptions to be handled by the caller code.
    I am not currently sharing my implementation because such an abstraction needs to be battle-tested for various edge cases before someone uses it in production.
  • My proposition relies on a mutable object model. The user partially fills out the model and then passes it to the service to have the rest of the properties filled out. Many developers would prefer to design it with immutable objects to avoid pitfalls. It can be done if we have a separate input model and output model. But I decided my proposition has the advantage of simplicity, at least to convey the general idea.

Example of a working prompt

I implemented a proof of concept of the GenerativeFill abstraction described above. So, to help the imagination, here is an example generic prompt that works pretty well. The highlighted lines are ones where the service inserts user-provided data. The rest is just a constant template.

Input contains array of items to process (in the `Items` property):

```json
{"Items":[{"CountryName":"USA","Id":1},{"CountryName":"Poland","Id":2},{"CountryName":"France","Id":3}]}
```

The output should be in JSON and contain array of output items with following schema:

```json
{"Items":[{"FirstCapital":null,"President":null,"Id":1}]}
```

For each input item you should generate one output item, using the `id` property as a key linking input and output.
Your job is to replace the null values with content.

Use the following rules when filling values of properties:
- For `FirstCapital` property: Fill with the first historically known capital of the country, not the current one!
- For `President` property: Fill the value with name of the current president of the country
- For `President` property: Use CAPITAL LETTERS for this value

The response is something like here (I added indentation manually for readability):

{
  "Items": [
    { "FirstCapital": "Philadelphia", "President": "JOE BIDEN", "Id": 1 },
    { "FirstCapital": "Gniezno", "President": "ANDRZEJ DUDA", "Id": 2 },
    { "FirstCapital": "Tournai", "President": "EMMANUEL MACRON", "Id": 3 }
  ]
}

Summary

Integrating a C# application with a natural-language-based API like ChatGPT or Gemini involves several steps, which are quite repetitive. Designing a prompt, serializing input data, deserializing output data, handling errors, retrying in case of failures, optimizing the prompt to work on chunks of data: all this is a pain in the butt when you do it again and again.

Even though chat-like APIs already have ultra-high levels of abstraction, we can still build useful abstractions above them when we use them from object-oriented languages like C#. Maybe the pattern described here will make your life easier at some point, too πŸ™‚

The post Object Generative Fill: a code pattern idea (with example in C#) appeared first on Tim Taurit.

]]>
https://taurit.pl/object-generative-fill-pattern/feed/ 0
Running llama3.1 offline: my experiences https://taurit.pl/running-llama-offline-on-windows/ https://taurit.pl/running-llama-offline-on-windows/#respond Thu, 08 Aug 2024 13:41:28 +0000 https://taurit.pl/?p=1750 Discovering Llama: where does it stand compared with gpt-4o? Today, I was looking for an AI model that would perform best with a non-mainstream task: translating text between two Slavic languages. This was a good opportunity to use the LMSYS Chatbot Arena. The service allows you to perform a blind test of two random modern ... Read more

The post Running llama3.1 offline: my experiences appeared first on Tim Taurit.

]]>
Table of Contents

Discovering Llama: where does it stand compared with gpt-4o?

Today, I was looking for an AI model that would perform best with a non-mainstream task: translating text between two Slavic languages.

This was a good opportunity to use the LMSYS Chatbot Arena. The service allows you to perform a blind test of two random modern AI models and decide which produces better results before you know the models’ identities.

My default go-to model, gpt4o, was giving uneven-quality results, and made too many mistakes in this task. Models that I found performed better with my prompt were gemini-1.5-pro-exp-0801 and llama-3.1-405b-instruct.

Living happily in the OpenAI & Microsoft bubble, I never heard of the Llama model. I dug into it to learn:

  • It’s a model from Meta. I’m not a fan of Meta’s customer products like Facebook, and I don’t intend to live in Metaverse πŸ˜‰ But I admire their R&D work and technical excellence. I expect a lot of effort went into creating the model.
  • It can be downloaded and run offline! Wow, that’s something I need to see for myself. I have always considered such state-of-the-art models very large and infeasible for customer-grade computers.
  • While it’s usually ranked a bit below gpt-4o in benchmarks or comparisons (like in the mentioned LMSYS Chatbot Arena portal), it’s close in scores and occupies top places on the leaderboard in many categories.
  • The theoretical cost of running it is zero! The model could become part of my daily toolkit if it proves good enough for some of my purposes.

My experience and learnings

So, just as a matter of form, here’s the class of computer I used to run the model (performance and feasibility to run LLM models were the concerns why I haven’t even considered playing with them so far):

  • CPU: AMD Ryzen 7 3700X 8-Core Processor
  • RAM: 32,0 GB
  • GPU: NVIDIA GeForce RTX 4070 Super, 12 GB of dedicated memory
  • OS: Windows 11 Pro 23H2

The first steps are easy! There’s an app. Coding in Python is NOT needed πŸ˜‰

Here’s a scenario for a horror movie:

You want to try a cool new AI tool from some company’s R&D team. You go to the website. There are some files to download, but no instructions whatsoever. Files are either binary or far from self-explanatory. If you are lucky, the website has some Python code samples. You don’t know Python, but you try anyway. They don’t compile or don’t work on Windows by design. After a few hours, you see it’s midnight and give up.

I’ve been in the above situation several times and expected the same here. But not this time!

The first steps are easy. You download the Ollama app. It is a console app, but it hints you with ou the command needed to download the model and run it. You run it and now have an interactive chat with the model, like in ChatGPT. Easy!

The llama-3.1 model suggested by the tool gives worse results than the one I tried online. What the…?

When you run the tool for the first time, it suggests downloading the llama3.1 model.

I did that. It downloaded 4.7 GB to my SSD, then ran, and I could converse. However, the response quality was much worse than I experienced testing it online!

The default model is a “small” version (with 8 billion parameters). But the impressive one I tested online had 405 billion parameters. You have to ask for it explicitly. But don’t do it yet; I’ll soon explain why.

You need to choose the right model size for your purposes. Example command: ollama run llama3.1:70b

The 405b model won’t run on a home PC

Of course, I was excited and downloaded the best model and… it crashed as soon as I launched it.

the 405b model crashed with: “Error: llama runner process has terminated: error loading model: unable to allocate backend buffer”

So here is the sad truth. The big model is not suited to run on a home PC. This became obvious when you dig into hardware requirements in the documentation:

  • MP16 (Model Parallel 16) – This is the full BF16 weights version. (…) A minimum of 2 nodes with 8 GPUs each is required for deployment.
  • MP8 – (…) can be deployed on a single node with 8 GPUs (…)
  • FP8 (Floating Point 8) – (…) can be deployed on a single node with 8 GPUs (…)

You can’t run it without at least 8 GPUs, so long story short, for the best results, the reasonable option is to stick to the cloud services offering the API.

Will at least 70b model work on my PC?

As a tradeoff between what I wanted and what is possible, perhaps I could at least run the middle-range 70b model? Let’s see.

I downloaded the 40GB and ran it. It ran! But it is REALLY slow. See for yourself if you have 10 minutes to see how it responds to a single question πŸ˜‰

llama-3.1:70b takes its time (10 minutes) to respond to a single question on a pretty powerful home computer.

I have a few observations here:

  • It is, of course, painfully slow and impractical with this performance
  • It uses a CPU and appears not to use GPU at all. This differs from the smallest model (8b), which utilized GPU and responded quickly. I suspect that a single GPU just doesn’t have enough VRam.
  • The response is correct, so the model seems to have a good piece of knowledge about the world encoded within those 40 gigabytes πŸ˜‰

Is there a 70b model variant that responds faster on my PC?

I saw in some Reddit threads that using a different quantization (reducing the precision of model weights) will make it use less memory and perform better. How much better, though? Would it be a jump in performance? I guess it could be if the model can be run on a GPU.

I chose the smallest variant of 70b with a 2-bit quantization and a model size of 29 GB instead of 40 GB:

ollama run llama3.1:70b-text-q2_K

It ran without errors. This time response was reasonably fast, and even utilized my GPU (although it also used up 20 GB of RAM). But the precision loss of the model must have been significant, as the response was totally rubbish:

It looks like any variant of this model that performs reasonably well requires better hardware.

Summary

I explored the possibility of running Meta’s latest conversation model on a home PC. Unless you are happy with a small model that isn’t as intelligent as the ones we are used to, it’s impossible on hardware typical for home workstations.

I found this exercise quite interesting. It helped me develop intuition about the size of the models and what hardware is needed to run them. Also, it shows that not only the training of the models but also the use of trained models is compute-intensive. For the state-of-the-art LLM models, it seems best to stick to cloud services and look for other options to reduce costs.

On the positive side, if you are looking for a rationalization to buy a beefy GPU for your home PC, I just gave you a reason to buy 8 of them πŸ˜€

The post Running llama3.1 offline: my experiences appeared first on Tim Taurit.

]]>
https://taurit.pl/running-llama-offline-on-windows/feed/ 0
OpenAI – using Batch API (with 50% discount) in C# https://taurit.pl/openai-batch-api-in-csharp/ https://taurit.pl/openai-batch-api-in-csharp/#respond Thu, 08 Aug 2024 09:23:26 +0000 https://taurit.pl/?p=1735 What is OpenAI Batch API? The pricing page for OpenAI services has an annotation that drew my attention recently: *Batch API pricing requires requests to be submitted as a batch. Responses will be returned within 24 hours for a 50% discount. I pretended not to see this option for a while because using batch jobs ... Read more

The post OpenAI – using Batch API (with 50% discount) in C# appeared first on Tim Taurit.

]]>
Table of Contents

What is OpenAI Batch API?

The pricing page for OpenAI services has an annotation that drew my attention recently:

*Batch API pricing requires requests to be submitted as a batch. Responses will be returned within 24 hours for a 50% discount.

I pretended not to see this option for a while because using batch jobs generally complicates the code πŸ™‚ But 50% is a significant discount. I mostly use OpenAI GPT models for batch operations that are not time-sensitive, so it looks useful. I also wanted to see how the API behaves in the real world. Here, I’ll share what I’ve learned with you.

Learning #1: Batch API doesn’t seem a first-class citizen in .NET SDKs (at the moment)

I’m typically using one of two .NET libraries to access OpenAI APIs from my C# applications:

  • OpenAI (tested at 2.0.0-beta.8) is the official library. I use the pre-release version because the stable one is dated a few months back.
  • Betalgo OpenAIβ€”This one is a beautifully maintained community library that is up to speed and has issues that are being resolved.

I attempted to run a batch job using the official library, OpenAI. It has a class named OpenAI.Batch.BatchClient for this purpose. Here’s the code I ended up with:

static async Task Main(string[] args)
{
    // Read API Key from User Secrets (Visual Studio: right-click on project -> Manage User Secrets)
    var configuration = new ConfigurationBuilder().AddUserSecrets<Program>().Build();
    var apiKey = configuration["OpenAiDeveloperKey"] ?? throw new ArgumentException("OpenAPI Key is missing in User Secrets configuration");

    // Create a client for the OpenAI Batch API
    BatchClient batchClient = new(new ApiKeyCredential(apiKey));

    // Create a batch request
    // `OpenAI` library doesn't deliver a strongly-typed model (yet?),
    // so property names come directly from docs at/ https://platform.openai.com/docs/api-reference/batch/create
    var batchRequest = new
    {
        // file with requests was uploaded manually at https://platform.openai.com/storage/files
        // example of file content: https://platform.openai.com/docs/guides/batch/1-preparing-your-batch-file
        input_file_id = "file-f8fB0yH04nq9QmiWYYBtyYKC",
        endpoint = "/v1/chat/completions",
        completion_window = "24h", // currently only 24h is supported
        metadata = new
        {
            // available later via Batch API, although not exposed in the UI, so usefulness is questionable
            description = "C# eval job test"
        }
    };

    var response = await batchClient.CreateBatchAsync(BinaryContent.Create(BinaryData.FromObjectAsJson(batchRequest)));
    var rawResponse = response.GetRawResponse();

    Console.WriteLine(rawResponse.Content);

    // this only writes METADATA about the batch job to the output; the job might take up to 24h to complete
    // Metadata is something like:
    //{
    //    "id": "batch_lF3mjwKIDUusSrf43nCnkqOz",
    //    "object": "batch",
    //    "endpoint": "/v1/chat/completions",
    //    "input_file_id": "file-f8fB0yH04nq9QmiWYYBtyYKC",
    //    "status": "validating",
    //    "created_at": 1722955749,
    //    "expires_at": 1723042149,
    //    (...)
    //    "metadata": {
    //        "description": "C# eval job test"
    //    }
    //}

}

So currently the BatchClient class has limited functionality. The library handles authentication but not much more. It lacks a strongly typed model to help define batch jobs. It’s still a pre-release, so it’s hard to say if it stays that way by design or if developers will extend the functionality a bit.

Using it is arguably better than working with HttpClient directly. But I think there’s still an opportunity to build a good abstraction over the Files API (batch job jas to be uploaded as a file) and Batch API to make the code less verbose and more fun.

The alternative library, Betalgo OpenAI, includes a .NET type for Batch requests but is also a thin wrapper. It accepts strings (where it could accept enums) and doesn’t seem to make any higher-level abstraction over Files and Batch. I couldn’t find any examples of Batch usage in the docs, so I might not have a full picture. However, I scanned the code on GitHub and found nothing related.

So, to summarize, you can use Batch API in C# today. It works, but it’s not very different from assembling the HTTP request yourself.

Learning #2: OpenAI has good web UI for Batch jobs and Storage

On the side of good news, I discovered a neat Web UI for batch jobs and storage. You can upload the job file within your browser, monitor its status, and download the result without coding.

OpenAI has a web dashboard where you can start and monitor batch jobs.

Learning #3: results are often available pretty fast!

The most interesting observation is probably how fast the results are typically available. The deal is we get a 50% discount, but the results are only guaranteed to be there within a 24-hour window.

I only ran a few tests so far, but I was happy that the results were available within seconds!

The timeline of an example OpenAI Batch job

This doesn’t prove much. I might have been lucky to use the service outside the peak demand time. It absolutely doesn’t mean that I recommend using it in interactive applications. But it gives hope that if we use batch jobs to process some data we need later, we won’t have to pause work for 24 hours but can typically continue later that day.

Learning #4: there are some quirks

I found some unexpected behaviors, too. Just for context, this is an example input for the batch job, defining two jobs:

{"custom_id": "request1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o", "messages": [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 200}}

{"custom_id": "request2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o-mini", "messages": [{"role": "system", "content": "You are an unhelpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 200}}

All requests in batch must share the same model

Even though the batch input file requires specifying the model for each task (in my example, it’s "model": "gpt-4o-mini"), it will fail if the model is not the same for all tasks in a batch:

Probably, this is to future-proof the contract, but it makes it confusing at the moment (why allow inconsistent input in the first place?)

Some input parameters are redundant

There are some parameters in the Batch creation model and request input object which can only take one value, for example:

// in batch API input model:
{
 "completion_window": "24h", // this is the only allowed value
  ...
}

// in batch definition file
{
  "method": "POST", // Currently only POST is supported
  ...
}

It also looks like an attempt to prepare APIs for future features, although such parameters seem unnecessary from the API design perspective today.

Summary

Integrating a .NET application with OpenAI Batch API requires some time to build a higher-level abstraction over the Files and Batch APIs, but it’s not rocket science. Existing libraries help, although there is still potential to go further.

The discount is huge, so if you process a significant amount of data and don’t need an immediate response (for an interactive experience), this is worth the attention!

The post OpenAI – using Batch API (with 50% discount) in C# appeared first on Tim Taurit.

]]>
https://taurit.pl/openai-batch-api-in-csharp/feed/ 0
GitHub Copilot: selecting different suggestion https://taurit.pl/github-copilot-select-different-suggestion/ https://taurit.pl/github-copilot-select-different-suggestion/#respond Tue, 06 Aug 2024 11:41:17 +0000 https://taurit.pl/?p=1730 I’ll share a simple trick I was happy to learn only today (after using GitHub Copilot for over a month). Copilot is so good at guessing what I intend to write that pretty much all the time I accept the first solution it comes up with. But sometimes it can guess wrong, or it doesn’t ... Read more

The post GitHub Copilot: selecting different suggestion appeared first on Tim Taurit.

]]>
I’ll share a simple trick I was happy to learn only today (after using GitHub Copilot for over a month).

Copilot is so good at guessing what I intend to write that pretty much all the time I accept the first solution it comes up with.

But sometimes it can guess wrong, or it doesn’t have any way to know which of the few reasonable options I intend to write. For example, when I add a new Unit Test, I might want to write a [TestMethod] or [DataTestMethod] with very similar probability.

So if it guesses wrong, do we need to type the code ourselves? It turns out that not necessarily, as we can easily alternate between suggestions! Here’s how:

In my Visual Studio, the default keyboard shortcut is Ctrl+. but it might differ so check what is it for you πŸ™‚ I hope being aware of this feature makes your coding even more productive πŸ™‚

The post GitHub Copilot: selecting different suggestion appeared first on Tim Taurit.

]]>
https://taurit.pl/github-copilot-select-different-suggestion/feed/ 0
Workaround: Azure Portal sign-in flow stuck on “More information required” https://taurit.pl/azure-portal-login-stuck-more-information-required/ https://taurit.pl/azure-portal-login-stuck-more-information-required/#respond Thu, 01 Aug 2024 10:33:24 +0000 https://taurit.pl/?p=1636 When I visit the Azure Portal and attempt to sign in, the page often gets stuck at the following screen (“More information required”): I couldn’t find any useful solution on Google, and contacting the support team about it is low on my priority list. So I experimented and found workarounds that help unlock the access ... Read more

The post Workaround: Azure Portal sign-in flow stuck on “More information required” appeared first on Tim Taurit.

]]>
When I visit the Azure Portal and attempt to sign in, the page often gets stuck at the following screen (“More information required”):

I couldn’t find any useful solution on Google, and contacting the support team about it is low on my priority list. So I experimented and found workarounds that help unlock the access at least for some time. Maybe it can help you too?

Workaround 1: force full page refresh (Ctrl+F5)

This only works sometimes, and I think it only works in Firefox (not Edge), but it’s quick to try so why not πŸ˜‰

Workaround 2: manually remove cookies for the portal

What I typically need to do, is to:

  1. Open the browser’s developer tools
  2. Manually remove all cookies for the login site (“clear all cookies” button helps to not remove them one by one ;))

That’s the list I mean:

Once the list is empty, press Ctrl+F5 to refresh the page. The browser should perform a dozen magic redirects, and Azure Portal should load just fine this time πŸ™‚

The post Workaround: Azure Portal sign-in flow stuck on “More information required” appeared first on Tim Taurit.

]]>
https://taurit.pl/azure-portal-login-stuck-more-information-required/feed/ 0
ChatGPT seems signed out every time I visit – a fix https://taurit.pl/chatgpt-in-browser-signs-out-frequently/ https://taurit.pl/chatgpt-in-browser-signs-out-frequently/#respond Thu, 01 Aug 2024 08:37:33 +0000 https://taurit.pl/?p=1626 Here’s a short hint that helped me resolve an annoying issue of ChatGPT signing me out very often. It’s a service I have bookmarked and I use frequently, yet it seemed to be more stubborn than most apps and I was signed out every day I opened it πŸ˜‰ And especially if you use two-factor ... Read more

The post ChatGPT seems signed out every time I visit – a fix appeared first on Tim Taurit.

]]>
Here’s a short hint that helped me resolve an annoying issue of ChatGPT signing me out very often. It’s a service I have bookmarked and I use frequently, yet it seemed to be more stubborn than most apps and I was signed out every day I opened it πŸ˜‰ And especially if you use two-factor authentication, it takes time to sign in every time.

For clarity, I am talking about the web version available at: https://chat.openai.com/chat (I haven’t had this problem on Android so far).

How to make the login session last longer?

I observed that the website login session doesn’t play nice with the cookie restrictions often imposed by browsers. Loosening the restrictions resolves the problem.

I am unsure which of the settings listed below was overprotective, but disabling them all for chatgpt.com resolved the problem on my machine. I haven’t been signed out for days now πŸ™‚

I recommend disabling tracking prevention on this site
I recommend disabling the block of 3rd party cookies on this side
I also did the same on a login screen
Also, it’s worth making sure you don’t clear cookies on the browser exit. If you do, that ChatGPT domain is on the exception list.

I hope this resolves the issue for you too. Enjoy more productivity without hurdles! πŸ™‚

The post ChatGPT seems signed out every time I visit – a fix appeared first on Tim Taurit.

]]>
https://taurit.pl/chatgpt-in-browser-signs-out-frequently/feed/ 0
Azure Static Web Apps – a few deployment errors and solutions to them https://taurit.pl/azure-static-web-apps-deployment-errors/ https://taurit.pl/azure-static-web-apps-deployment-errors/#comments Wed, 31 Jul 2024 05:39:46 +0000 https://taurit.pl/?p=1614 I’ve been taking a first look at Blazor recently. I wanted to pair it with Azure Static Web Apps to test my understanding that a Blazor application can run 100% in the browser, without any backend part required (at least if I choose the right project template). If that’s true, Static Web Apps would be ... Read more

The post Azure Static Web Apps – a few deployment errors and solutions to them appeared first on Tim Taurit.

]]>
I’ve been taking a first look at Blazor recently. I wanted to pair it with Azure Static Web Apps to test my understanding that a Blazor application can run 100% in the browser, without any backend part required (at least if I choose the right project template). If that’s true, Static Web Apps would be enough and I don’t need something like Azure App Service to host the app unless I need the backend part.

So, the deployment didn’t go smoothly at first, and this post is to share a few learnings in case someone encounters similar problems.

EPERM: operation not permitted, open ‘C:\Program Files\Microsoft Visual Studio\2022\Preview\Common7\IDE\.env’

My first attempt was to deploy from Visual Studio (the Publish option), and it ended with the following error:

> swa.cmd login --tenant-id 70d3xxxx-xxxx-xxxx-xxxx-xxxxxxxx5b67 --subscription-id 6b3fzzzz-zzzz-zzzz-zzzz-zzzzzzzz4b00 --app-name tauritreview --resource-group Taurit.TodoistTools.Review --no-use-keychain
Welcome to Azure Static Web Apps CLI (1.1.10)
Checking Azure session...
βœ” Successfully logged into Azure!
βœ– Failed to setup project: EPERM: operation not permitted, open 'C:\Program Files\Microsoft Visual Studio\2022\Preview\Common7\IDE\.env'
βœ– If you believe this behavior is unexpected, please raise a GitHub issue at:
  https://github.com/Azure/static-web-apps-cli/issues/new/choose
Unable to authenticate with Static Web App 'tauritreview'.

I am not sure why Visual Studio tooling here wants to create the .env file with Static Web App settings in Visual Studio’s Program Files directory, which seems like a poor choice. The project’s directory sounds better, and there should be no problems with file write permissions. Perhaps it’s a bug.

Anyhow. A workaround that I used and which works fine is to create .env file manually in the path where it’s expected (requires Administrator privileges), and allow non-admin users to modify it (in File-> Properties):

Setting file permissions on a created file, as a workaround

No valid subscription found for tenant {Guid}

Then there was another issue. Just for clarity, when I encountered the first errors with deployment, I switched from publishing with Visual Studio to manually publishing from Windows Terminal to have more control over what CLI commands I execute. So the next issues described here occurred in a slightly different environment.

The first issue I encountered was:

>swa login

Welcome to Azure Static Web Apps CLI (1.1.10)

Using configuration "taurit.todoist-tools.review.v2" from file:
  D:\Projekty\Taurit.TodoistTools.Review\source\Taurit.TodoistTools.Review.v2\swa-cli.config.json

Checking Azure session...
βœ” Successfully logged into Azure!
βœ– Failed to setup project: No valid subscription found for tenant 70d3xxxx-xxxx-xxxx-xxxx-xxxxxxxx5b67.
  Please read https://docs.microsoft.com/azure/cost-management-billing/manage/no-subscriptions-found

βœ– If you believe this behavior is unexpected, please raise a GitHub issue at:
  https://github.com/Azure/static-web-apps-cli/issues/new/choose

I see a similar issue with Azure CLI (az login) and I don’t understand why tooling doesn’t see my Azure subscription (the only one tied to my tenant). But I found out that when I am super explicit about IDs (which you can find in Azure Portal -> Subscriptions), it works:

>swa login --tenant-id 70d3xxxx-xxxx-xxxx-xxxx-xxxxxxxx5b67 --subscription-id 6b3fzzzz-zzzz-zzzz-zzzz-zzzzzzzz4b00

Welcome to Azure Static Web Apps CLI (1.1.10)

Using configuration "taurit.todoist-tools.review.v2" from file:
  D:\Projekty\Taurit.TodoistTools.Review\source\Taurit.TodoistTools.Review.v2\swa-cli.config.json

Checking Azure session...
βœ” Successfully logged into Azure!
βœ” Saved project credentials in .env file.
βœ” Successfully setup project!

The access token is from the wrong issuer

That wasn’t the end of my challenges. When swa login worked, I wanted to deploy my app to Azure. Here’s the error I observed:

>swa deploy

(...) // ommited for brevity
Checking Azure session...
βœ” Successfully logged into Azure!

Checking project settings...
βœ– The access token is from the wrong issuer 'https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/'.
It must match the tenant 'https://sts.windows.net/70d3xxxx-xxxx-xxxx-xxxx-xxxxxxxx5b67/' associated with this subscription.

Please use the authority (URL) 'https://login.windows.net/70d3xxxx-xxxx-xxxx-xxxx-xxxxxxxx5b67' to get the token. Note, if the subscription is transferred to another tenant there is no impact to the services, but information about new tenant could take time to propagate (up to an hour). If you just transferred your subscription and see this error message, please try back later.

One observation is that f8cdef31-a31e-4b4a-93e4-5f571e91255a is a well-known tenant id (you can Google it). It suggests that the CLI logged into Microsoft’s tenant, not my Azure tenant. But I have explicitly passed the tenant ID in the swa login command (see the first error). So why the problem?

I found the hint in the GitHub discussion thread. Most likely, the CLI has some cache for credentials that don’t get invalidated when they should. The workaround is to add the following line to the .env file:

AZURE_SUBSCRIPTION_ID=6b3fzzzz-zzzz-zzzz-zzzz-zzzzzzzz4b00
AZURE_TENANT_ID=70d3xxxx-xxxx-xxxx-xxxx-xxxxxxxx5b67
SWA_CLI_LOGIN_CLEAR_CREDENTIALS=true

The .env file seems to be created by the Static Web Apps CLI either in your project directory (when I use the CLI from the command line directly), or in a path like c:\Program Files\Microsoft Visual Studio\2022\Preview\Common7\IDE.env (when I used Visual Studio for deployment).

The above setting solved the issue for me:

>swa deploy

Welcome to Azure Static Web Apps CLI (1.1.10)

(...)
Checking project settings...
√ Choose your Static Web App » Taurit.TodoistTools.Review/tauritreview
βœ” Successfully setup project!

Deploying to environment: preview

Deploying project to Azure Static Web Apps...
√ Project deployed to https://white-hill-03fd3f903-preview.westeurope.5.azurestaticapps.net πŸš€

Good luck with your deployment!

The post Azure Static Web Apps – a few deployment errors and solutions to them appeared first on Tim Taurit.

]]>
https://taurit.pl/azure-static-web-apps-deployment-errors/feed/ 3
SQLiteException: ‘no such collation sequence: unicase’ https://taurit.pl/sqliteexception-no-such-collation-sequence-unicase/ https://taurit.pl/sqliteexception-no-such-collation-sequence-unicase/#respond Thu, 18 Jul 2024 08:19:09 +0000 https://taurit.pl/?p=1600 Recently, I’ve been prototyping various plugins and tools for Anki (flashcard management system) in C#. So far, the biggest technical obstacle I encountered was the following exception thrown when I tried to query data from the database: System.Data.SQLite.SQLiteException: ‘SQL logic error, no such collation sequence: unicase‘ or, when I used Microsoft.Data.Sqlite NuGet package instead of ... Read more

The post SQLiteException: ‘no such collation sequence: unicase’ appeared first on Tim Taurit.

]]>
Recently, I’ve been prototyping various plugins and tools for Anki (flashcard management system) in C#. So far, the biggest technical obstacle I encountered was the following exception thrown when I tried to query data from the database:

or, when I used Microsoft.Data.Sqlite NuGet package instead of System.Data.SQLite:

Exception that occurs for some queries

Understanding the issue

It seems that Anki decided to use a non-standard collation named unicase for some columns.

Collation is a declaration of what algorithm the database engine should use to compare data in the column (to determine whether values are equal or one is greater than another). For string columns, collation is relevant e.g., in the case of SQL operations like DISTINCT or ORDER BY.

I tracked that the unicase collation Anki uses has its implementation hosted in GitHub (see the seanmonstar/unicase project). The value added (as compared to the standard “NOCASE” collation) seems to be that it considers strings with international characters and their transcriptions equal (like “MASSE” and “Maße”).

Here’s a demonstration showing that the collation is set for one of the columns in the database:

Making SQL queries work in .NET

To get this to work in .NET, we need to provide a .NET implementation for the “unicase” comparison to the SQLite connection.

Firstly, if you use System.Data.SQLite NuGet package, you might need to migrate to Microsoft.Data.Sqlite which provides the SqliteConnection.CreateCollation(...) method. I could not find an easy way to achieve it with the System.Data.SQLite library, so I’m not sure it’s possible – that would require further research if you don’t wish to migrate.

Then, you need to register a collation named unicase:

var ankiDatabasePath = "c:\Users\...\AppData\Roaming\Anki2\...\collection.anki2"

using var connection = new Microsoft.Data.Sqlite.SqliteConnection($"Data Source={ankiDatabaseFilePath};");
connection.Open();

connection.CreateCollation("unicase", (x, y) => String.Compare(x, y, StringComparison.OrdinalIgnoreCase));

var query = $@"SELECT DISTINCT notetypes.name FROM notetypes";
using var command = new Microsoft.Data.Sqlite.SqliteCommand(query, connection);
using var reader = command.ExecuteReader(); // this one should now work without exception

For my use cases, the simple case-ignoring collation StringComparison.OrdinalIgnoreCase is enough. If you want to mimic unicase‘s library way of sorting and comparing international strings on the database side, you might need to find or implement similar logic in C#.

At this point, queries should work. I assembled a small project on GitHub to show how to reproduce the issue and how to fix it.

Have fun hacking Anki and creating cool things!

The post SQLiteException: ‘no such collation sequence: unicase’ appeared first on Tim Taurit.

]]>
https://taurit.pl/sqliteexception-no-such-collation-sequence-unicase/feed/ 0
ChatGPT API: remove JSON indentation to reduce your bills https://taurit.pl/chatgpt-api-json-indentation/ https://taurit.pl/chatgpt-api-json-indentation/#respond Sun, 07 Jul 2024 15:10:45 +0000 https://taurit.pl/?p=1584 Observing default behavior: JSON is indented, and we pay for it While working with ChatGPT API, I noticed that the JSON response I normally receive contains many whitespace characters used for indentation. Here’s a fragment of the response as an illustration: Output tokens still cost quite a lot in 2024 (assuming we process lots of ... Read more

The post ChatGPT API: remove JSON indentation to reduce your bills appeared first on Tim Taurit.

]]>
Table of Contents

Observing default behavior: JSON is indented, and we pay for it

While working with ChatGPT API, I noticed that the JSON response I normally receive contains many whitespace characters used for indentation. Here’s a fragment of the response as an illustration:

A fragment of the response showing space-based indentation

Output tokens still cost quite a lot in 2024 (assuming we process lots of data), so I wanted to test the hypothesis that we can reduce the cost by cutting out the unnecessary whitespace.

Experiment asking ChatGPT (gpt-3.5-turbo) to return non-indented JSON

Default JSON response

My baseline is a prompt being sent using the official OpenAI NuGet package. I also set the following header to request a response in JSON format:

ChatCompletionOptions options = new ChatCompletionOptions()
{
    ResponseFormat = ChatResponseFormat.JsonObject
};
With the default setup, my query returned 713 OutputTokens. Let’s now slightly modify the prompt and ask for no indentation.

Asking explicitly for no indentation

I added the following sentence to my prompt: Please don't use indentation in a response.

This indeed changed the response to contain no whitespace:

But did the token counter change? Let’s take a look:

As you can see:

  • InputTokens increased slightly (I had to add one sentence to the prompt), but the cost here is marginal
  • OutputTokens now cost me only 57% of the original price!

Paying $57 instead of $100 doesn’t sound bad, eh? And if the JSON structure has more nesting, savings can be even higher.
I hope you spend your savings on something fun!

Appendix: can you do the same with gpt-4o?

I tested the above optimization with the gpt-3.5-turbo model. However, the same prompt used against gpt-4o didn’t succeed. The model ignored the “Please don't use indentation in a response.” command, and instead, it followed the indentation pattern from the output example in my prompt.

When I changed the output example to contain no indentation, I observed similar results – no indentation in output and much fewer output tokens billed.

The post ChatGPT API: remove JSON indentation to reduce your bills appeared first on Tim Taurit.

]]>
https://taurit.pl/chatgpt-api-json-indentation/feed/ 0
Are file operations slower in directories containing many files? NTFS and ReFS (DevDrive) tested https://taurit.pl/are-file-operations-slower-in-big-directories/ https://taurit.pl/are-file-operations-slower-in-big-directories/#respond Sun, 17 Dec 2023 13:25:37 +0000 https://taurit.pl/?p=1562 I’ll share the results of a quick benchmark testing how the number of files in a directory impacts the following filesystem operations executed from a .NET application: My motivation was to decide if it was a good design performance-wise to have a single directory containing many cache files. I’m thinking about the order of magnitude ... Read more

The post Are file operations slower in directories containing many files? NTFS and ReFS (DevDrive) tested appeared first on Tim Taurit.

]]>
I’ll share the results of a quick benchmark testing how the number of files in a directory impacts the following filesystem operations executed from a .NET application:

  • File.Exists(fileName)
  • File.WriteAllText(uniqueFileName, content)

My motivation was to decide if it was a good design performance-wise to have a single directory containing many cache files. I’m thinking about the order of magnitude of a million files in a directory. An alternative could be to split the files into many subdirectories, for example, by hashing them and randomly assigning them to one of many buckets.

Large directories take noticeably longer to load in File Explorer (it can take seconds). But does a large number of items impact operations that require access to only a single file in such a directory (without the need to list them all)? Let’s test.

File.Exists(fileName)

In this test, I checked how the number of items in a directory impacts the execution time of the File.Exist() operation. I tested both scenarios – whether the tested file existed or not. The directories had the following number of files, respectively:

  • Small: 1 file
  • Large: 20 001 files
  • Very large: 1 000 001 files

For clarity, the benchmark was a set of simple tests like:

[Benchmark]
public void FileExist_ExistingFileInAlmostEmptyDirectory_Ntfs() => File.Exists("d:\\testfiles\\small\\file.webp");

[Benchmark]
public void FileExist_ExistingFileAmong_20_000Files_Ntfs() => File.Exists("d:\\testfiles\\large\\file.webp");

[Benchmark]
public void FileExist_ExistingFileAmong_1_000_000Files_Ntfs() => File.Exists("d:\\testfiles\\verylarge\\file.webp");

// ...

Here’s a result of a test performed using Benchmark .NET:

BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.2861/23H2/2023Update/SunValley3)
AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores
.NET SDK 8.0.100
  [Host]     : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2
  DefaultJob : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2


| Method                                                   | Mean      | Error     | StdDev    |
|--------------------------------------------------------- |----------:|----------:|----------:|
| FileExist_ExistingFileInAlmostEmptyDirectory_Ntfs        |  2.531 us | 0.0496 us | 0.0772 us |
| FileExist_ExistingFileAmong_20_000Files_Ntfs             |  2.444 us | 0.0058 us | 0.0051 us |
| FileExist_ExistingFileAmong_1_000_000Files_Ntfs          |  2.468 us | 0.0209 us | 0.0195 us |
| FileExist_ExistingFileInAlmostEmptyDirectory_DevDrive    |  2.031 us | 0.0139 us | 0.0130 us |
| FileExist_ExistingFileAmong_20_000Files_DevDrive         |  2.031 us | 0.0092 us | 0.0086 us |
| FileExist_ExistingFileAmong_1_000_000Files_DevDrive      | 10.585 us | 0.0543 us | 0.0508 us |
| FileExist_NonExistingFileInAlmostEmptyDirectory_Ntfs     |  2.666 us | 0.0114 us | 0.0101 us |
| FileExist_NonExistingFileAmong_20_000Files_Ntfs          |  4.702 us | 0.0073 us | 0.0068 us |
| FileExist_NonExistingFileAmong_1_000_000Files_Ntfs       |  7.130 us | 0.0295 us | 0.0276 us |
| FileExist_NonExistingFileInAlmostEmptyDirectory_DevDrive |  2.912 us | 0.0104 us | 0.0097 us |
| FileExist_NonExistingFileAmong_20_000Files_DevDrive      |  6.001 us | 0.0328 us | 0.0307 us |
| FileExist_NonExistingFileAmong_1_000_000Files_DevDrive   | 10.652 us | 0.0252 us | 0.0197 us |

Some of my conclusions here:

  • Performance of the File.Exists() operation in C# is generally consistent and fast, even when dealing with directories containing many files. In a folder with 1,000,000 files, we’re still talking about microseconds.
  • The performance of the File.Exists() operation seems negatively impacted by how many files are present in the tested directory, but the degradation is slow and far from linear.
  • The operation typically takes longer if the file does not exist, than if it exists.
  • I don’t think comparing NTFS and ReFS (DevDrive) performance is reasonable here because partitions are located on different SSD drives and placed in different PCIe slots. However, I think it’s fair to notice that a DevDrive, which is located on my fastest SSD in a native M.2 slot, didn’t perform better than NTFS here.

File.WriteAllText(uniqueFileName, content)

The other test consisted of creating a million small (10 bytes in size) text files in a single directory, and measuring if the performance degrades with each file written.

Performance of File.WriteAllText() in a function of the number of existing files in a target directory before writing the file
Performance of File.WriteAllText() in a function of the number of existing files in a target directory before writing the file

My conclusion here is that the performance of File.WriteAllText() was consistent, regardless of whether the folder where we create a new file is empty or contains 1,000,000 files already.

Test setup

The tests were performed using:

  • A .NET 8 Console Application built in the Release mode
  • Windows 11
  • 32 GB RAM
  • Ryzen 3700X CPU
  • Two partitions:
    • One of them formatted as NTFS on Samsung SSD 850 Evo 500GB,
    • Another one was set up as a DevDrive, utilizing ReFS file system on Samsung SSD 980 Pro 1TB.

Of course, many factors can impact the performance of disk operations, like SSD caches, OS caches, or antivirus software activity, so results may vary in different configurations.

Summary

I found no evidence to support a worry that single-file operations in a folder containing many files are significantly slower than in folders containing fewer files (at least in the tested environment).

There is a small performance degradation with File.Exists() when the number of files grows. It’s small and could only be relevant if we test for files’ existence very frequently. For most of us, it seems negligible and not worth complicating the design to work around such issue.

The post Are file operations slower in directories containing many files? NTFS and ReFS (DevDrive) tested appeared first on Tim Taurit.

]]>
https://taurit.pl/are-file-operations-slower-in-big-directories/feed/ 0
C#12 primary constructors: no auto-generated properties? https://taurit.pl/csharp-12-primary-constructors-no-auto-generated-properties/ https://taurit.pl/csharp-12-primary-constructors-no-auto-generated-properties/#respond Sat, 18 Nov 2023 15:24:00 +0000 https://taurit.pl/?p=1492 .NET 8, along with C#12, is now official. Last week, I upgraded one of my projects to the new version and tried using some new features. Primary constructors in classes don’t generate auto-properties One thing that surprised me was that the primary constructors, known from record types and now supported in classes, behaved differently than ... Read more

The post C#12 primary constructors: no auto-generated properties? appeared first on Tim Taurit.

]]>
.NET 8, along with C#12, is now official. Last week, I upgraded one of my projects to the new version and tried using some new features.

Primary constructors in classes don’t generate auto-properties

One thing that surprised me was that the primary constructors, known from record types and now supported in classes, behaved differently than I expected.

Let’s consider the following example showing primary constructors used both in a record and in a class:

So, is there something wrong with my tooling? Or is this by design that an auto-property I am used to having generated by the C# compiler is present for records, but not for classes?

Different by design

It turns out that this difference in how records and classes behave is by design. Having properties auto-generated from primary constructor parameters is specific to records only:

When you declare a primary constructor on a record, the compiler generates public properties for the primary constructor parameters. The primary constructor parameters to a record are referred to as positional parameters. The compiler creates positional properties that mirror the primary constructor or positional parameters. The compiler doesn’t synthesize properties for primary constructor parameters on types that don’t have the record modifier.

Source: Records (C# reference)

After some thought, generating properties for records makes sense, and for classes – not necessarily.

Records are intended to be used to encapsulate data. For immutable structures, it is a typical pattern to have data initialized during construction, and later accessed via read-only properties. While records don’t force you to create an immutable type, primary constructors in records optimize for that use case and make syntax short and sweet.

Classes have wider use, and we can easily imagine e.g., classes that take dependencies as constructor parameters (following the dependency injection pattern). In such a case having all values passed via a constructor exposed as public properties is not desired. It seems reasonable that it’s not the default.

Can I make the class generate auto-property anyway?

While auto-properties are not generated by default for classes, it could be tempting to try to have them generated with an addition of an access modifier like public. This could be familiar from languages like TypeScript where it’s called Parameter Properties. It could look like:

// annotation like "public" could theoretically be used to inform compiler we want a field or property generated
public class C(public int i) {} 

In C#12 such a feature doesn’t exist. However, it’s described in the section on “Possible extensions,” and it might appear in future versions of the language (although the section also mentions the downsides of such design choice).

So, how do we use the value from the class’s primary constructor?

Before I conclude, let me show a simple example of how we can use values from a primary constructor in class (because we cannot use it the way it works with records). Here are the equivalent implementations of a record and a class*.

* To be precise, they are equivalent regarding how a primary constructor value is exposed. There are also differences in equality comparison and deconstruction not considered here.

Summary

Primary constructors behave differently in classes than in records. I expected parity in behavior, although, after consideration, I see that it makes more sense the way it was done.

Will primary constructors be useful in classes? Honestly, I’m not confident if this new feature will lead to a simpler code in our solutions or, rather, make it less straightforward. We’ll see ourselves soon πŸ™‚

The post C#12 primary constructors: no auto-generated properties? appeared first on Tim Taurit.

]]>
https://taurit.pl/csharp-12-primary-constructors-no-auto-generated-properties/feed/ 0
GitHub Workflow error: Canceling since a higher priority waiting request for [a concurrency group] exists https://taurit.pl/github-canceling-since-a-higher-priority-waiting-request-exists/ https://taurit.pl/github-canceling-since-a-higher-priority-waiting-request-exists/#comments Sat, 18 Nov 2023 13:22:49 +0000 https://taurit.pl/?p=1520 Hi, in this post, I’ll share learning from my recent attempt to set up GitHub workflow in a way that: We wanted to achieve such a setup to allow running end-to-end tests on a single Azure environment without causing interference between workflows running simultaneously. An attempt that failed I expected it to be as easy ... Read more

The post GitHub Workflow error: Canceling since a higher priority waiting request for [a concurrency group] exists appeared first on Tim Taurit.

]]>
Hi, in this post, I’ll share learning from my recent attempt to set up GitHub workflow in a way that:

  • At most, one workflow runs at a time,
  • The workflows starting later are queued in a FIFO manner and patiently wait for their time.

We wanted to achieve such a setup to allow running end-to-end tests on a single Azure environment without causing interference between workflows running simultaneously.

An attempt that failed

I expected it to be as easy as specifying a concurrency group in a GitHub Workflow, with something like:

on: workflow_dispatch

concurrency:
  group: workflows-sharing-e2e-environment
  cancel-in-progress: false

At first glance, this appeared to have worked! When we started two workflow runs, the first started, and the other was patiently waiting in a queue. We happily merged the solution.

Workflows getting canceled

We soon realized, that contrary to the intention, some workflows weren’t queued but canceled with an error like:

So, what is happening?

It turned out, that contrary to our intuition, and also the intuition of many other developers reporting this issue, setting concurency group on a workflow doesn’t cause the workflows to be queued, but canceled by default.

Now, you might notice there is this parameter, cancel-in-progress: false, that should solve the issue, right? Unfortunately not, it only makes exceptions for the workflow in progress, but not for pending workflows. So instead of the FIFO queue we expected, we’ve built a kind of queue that can queue at most 1 workflow πŸ€”. Let me try to explain it with a picture:

Workarounds?

Unfortunately, I didn’t find any practical workaround at the moment. Of course, there is always some way of coding a custom solution, e.g., to monitor the status of workflows and re-schedule the ones that got canceled, but it’s a high-effort one.

A request from the community that I find reasonable as well as to extend the concurrency group configuration with a new flag like:

on: workflow_dispatch

concurrency:
  group: workflows-sharing-e2e-environment
  cancel-in-progress: false
  cancel-pending: false # such option doesn't exist at the moment

This blog post doesn’t provide any solution or workaround, but I hope it helped explain the issue, and maybe it will help you progress anyway. Thanks for stopping by!

The post GitHub Workflow error: Canceling since a higher priority waiting request for [a concurrency group] exists appeared first on Tim Taurit.

]]>
https://taurit.pl/github-canceling-since-a-higher-priority-waiting-request-exists/feed/ 2
OpenAI Text To Speech (tts-1) and Polish language https://taurit.pl/openai-text-to-speech-tts-1-polish-language/ https://taurit.pl/openai-text-to-speech-tts-1-polish-language/#comments Sat, 18 Nov 2023 12:22:57 +0000 https://taurit.pl/?p=1496 Does OpenAI Text to Speech support languages other than English? OpenAI’s Text-to-Speech service transforms text into audio files with impressive quality when we consider English language. I haven’t performed a blind test, but based on a few samples I heard, I don’t think I could easily recognize if the audio were recorded by a real ... Read more

The post OpenAI Text To Speech (tts-1) and Polish language appeared first on Tim Taurit.

]]>
Does OpenAI Text to Speech support languages other than English?

OpenAI’s Text-to-Speech service transforms text into audio files with impressive quality when we consider English language. I haven’t performed a blind test, but based on a few samples I heard, I don’t think I could easily recognize if the audio were recorded by a real human or generated by an artificial intelligence model.

However, I wondered if the service could perform comparably well with languages other than English. The documentation is a bit mysterious:

The TTS model generally follows the Whisper model in terms of language support. Whisper supports the following languages and performs well despite the current voices being optimized for English.

Source: Text to speech documentation

Since there were no samples on the page other than those in English, I generated some samples in Polish to see if the model is already useful there. Polish is my native language, so it’s the one where I can judge nuances most easily. It’s also substantially different from English which makes it an interesting test ground. I’m sharing the results here, enjoy! πŸ™‚

Please note, the samples were generated on 2023-11-18 using model tts-1.

The API

I generate samples using the createSpeech API endpoint. A typical example is as simple as sending the following request, with a proper authentication token added:

POST https://api.openai.com/v1/audio/speech

{
    "model": "tts-1",
    "input": "The quick brown fox jumped over the lazy dog.",
    "voice": "alloy"
}

Interestingly, the API does not accept the language parameter, so it must rely on auto-detecting language from the context. This has no chance of working for very short texts. Should the word “no” be read as English, Spanish, Polish, etc? No way to know without more context. But it might work reasonably well for sentences and longer texts.

Example 1: Adam Mickiewicz – Pan Tadeusz (Inwokacja)

Here is our first sample:

Litwo, Ojczyzno moja! ty jesteΕ› jak zdrowie;
Ile cię trzeba cenić, ten tylko się dowie, Kto cię stracił.
DziΕ› piΔ™knoΕ›Δ‡ twΔ… w caΕ‚ej ozdobie;
WidzΔ™ i opisujΔ™, bo tΔ™skniΔ™ po tobie.

Source: Adam Mickiewicz – Pan Tadeusz – Inwokacja
Voice: echo
Voice: alloy
Voice: fable
Voice: nova
Voice: onyx
Voice: shimmer

Example 2: Grzegorz BrzΔ™czyszczykiewicz

Now let me share a tongue twister to see how the model performs with an arguably difficult input.

Grzegorz BrzΔ™czyszczykiewicz, ChrzΔ…szczyΕΌewoszyce, powiat ŁękoΕ‚ody.

Source: a tongue twister from the movie β€žJak rozpΔ™taΕ‚em II WojnΔ™ Światową”
Voice: fable
Voice: nova

I found the output truly impressive. Everything sounds correct. And the sentence is quite difficult to pronounce without mistakes even for a native speaker of the language πŸ˜‰

Example 3: a few quotes from Polish comedies

Let me end with a few random, famous quotes from Polish comedies.

“BunkrΓ³w nie ma, ale teΕΌ jest zajebiΕ›cie” generated with a voice fable
“ParΓ³wkowym skrytoΕΌercom mΓ³wimy stanowcze: NIE!” generated with a voice alloy
“CiemnoΕ›Δ‡, widzΔ™ ciemnoΕ›Δ‡! CiemnoΕ›Δ‡ widzΔ™!” generated with a voice fable

Discussion

How does it sound to a Polish native? I think it sounds like a foreigner who speaks Polish very well, maybe for years. One can stil hear the western accent, however, notably around the national characters like “Δ‡”, “Ε›”, and maybe “Δ™”. The stresses seem right, although it might be debatable, with the main sample being a poem with its own rhythm.

It’s doing quite well with interpretation as well – text written with CAPITAL LETTERS is spoken with an emotional tone, and punctuation marks direct how the whole sentence sounds.

Is any voice significantly better than other? I think nova sounds the best. I think I could mistake it for human voice if I wasn’t using good quality headphones and focusing that much. The second place goes to fable.

Is it useful? I think it depends on a use case. At this stage, I would strongly prefer human voice to demostrate correct pronounciation of words in language learning case, although… it’s probably good enough when compared with no audio sample at all. For other use cases, where realism is not a top priority, it looks fine to use already.

I don’t know much of the current competitors so I cannot compare the results to alternative models, but this API is definitely an interesting one – and not only for those generating speech in English.

Thanks for stopping by!

The post OpenAI Text To Speech (tts-1) and Polish language appeared first on Tim Taurit.

]]>
https://taurit.pl/openai-text-to-speech-tts-1-polish-language/feed/ 2
DALL-E 3: comparison of Standard vs HD quality https://taurit.pl/dalle-3-quality-standard-vs-hd/ https://taurit.pl/dalle-3-quality-standard-vs-hd/#respond Sat, 11 Nov 2023 13:36:52 +0000 https://taurit.pl/?p=1454 What is the new quality parameter in DALL-E 3 API? DALL-E 3 introduced a new API parameter, quality. The documentation explains: quality (standard or hd. Defaults to standard) The quality of the image that will be generated. hd creates images with finer details and greater consistency across the image. This param is only supported for ... Read more

The post DALL-E 3: comparison of Standard vs HD quality appeared first on Tim Taurit.

]]>
Table of Contents

What is the new quality parameter in DALL-E 3 API?

DALL-E 3 introduced a new API parameter, quality. The documentation explains:

quality (standard or hd. Defaults to standard)

The quality of the image that will be generated. hd creates images with finer details and greater consistency across the image. This param is only supported for dall-e-3.

Source: DALL-E 3 API reference

Which one should we choose, and is the difference significant? I decided to put them to a small test.

Pricing

Before we begin, let me mention that both options have different pricing, so the choice is not only about the quality but also how much we are willing to pay πŸ€‘ So the question is, is HD worth double the price for your use case? Or is it already in the diminishing results zone, and the difference would be hard to notice?

As I write it in November 2023, the pricing page has the following prices listed:

ModelQualityResolutionPrice
DALLΒ·E 3Standard1024Γ—1024$0.040 / image
Standard1024Γ—1792, 1792Γ—1024$0.080 / image
DALLΒ·E 3HD1024Γ—1024$0.080 / image
HD1024Γ—1792, 1792Γ—1024$0.120 / image

Standard and HD settings compared

To see how this particular setting affects generated images, I decided to:

  • Use the same prompts and API requests, and only modify the quality parameter.
  • Use the natural style of generated images. See my other post if you are interested in how a choice of style between natural and vivid impacts output images.
  • Measure the performance of the operation (the total time) as well, expecting that that could be an additional decision factor other than the cost.

Prompt 1: Umbrella

Prompt: An image of a couple sharing an umbrella on a quaint park bench amidst falling rain, evening, lantern light, summer.
Response time (standard): 9368 ms
Response time (hd): 15048 ms

Standard quality
HD quality

Prompt 2: Maine Coon

Prompt: A photo of a Maine Coon hunting, Sigma 24 mm f/8
Response time (standard): 10669 ms
Response time (hd): 14314 ms

Standard quality
HD quality

Prompt 3: style mimicking

Prompt: A photo of a woman playing drums in the style of Herman Leonard
Response time (standard): 15699 ms
Response time (hd): 12991 ms

Standard quality
HD quality

Prompt 4: portrait

Prompt: A portrait of a woman with long hair, outdoor, in elegant dress, posing in blooming garden, golden hour
Response time (standard): 10608 ms
Response time (hd): 21375 ms

Standard quality
HD quality

Summary

I hope you enjoyed this quick comparison! Please note that the above test is performed on a preview version of the dall-e-3 model. I’m sure this technology will be rapidly changing, and sooner or later, the above examples will be outdated.

Images vary a lot, and I don’t know how to make them vary less (ideally, we’d like to see the same picture but “rendered” with different quality settings, but I don’t think that level of control is currently possible).

Based on the examples above, I would choose the “hd” option most of the time if the price was equal, but the difference isn’t striking for me. I use image generation to help explain words in language learning, and “standard” seems good enough for this use case.

I am a bit spoiled playing with Midjourney, which raised the image quality bar very high (albeit it is much harder to control with prompts), so I’d still like to see improved results in Dall-E. But the difference between Dall-E 2 and Dall-E 3 is striking anyway.

And how about you, do you see a big difference between “standard” and “hd” yourself? πŸ™‚

The post DALL-E 3: comparison of Standard vs HD quality appeared first on Tim Taurit.

]]>
https://taurit.pl/dalle-3-quality-standard-vs-hd/feed/ 0
DALL-E 3: `vivid` vs `natural` styles compared https://taurit.pl/dalle-3-style-vivid-vs-natural/ https://taurit.pl/dalle-3-style-vivid-vs-natural/#respond Sat, 11 Nov 2023 11:53:06 +0000 https://taurit.pl/?p=1406 What is the new style parameter? OpenAI released a preview of a DALL-E 3 API this week, and I’m excited to play with it today. We can now control a new parameter named style. The documentation explains: style (defaults to vivid): The style of the generated images. Must be one of vivid or natural:– vivid ... Read more

The post DALL-E 3: `vivid` vs `natural` styles compared appeared first on Tim Taurit.

]]>
What is the new style parameter?

OpenAI released a preview of a DALL-E 3 API this week, and I’m excited to play with it today.

We can now control a new parameter named style. The documentation explains:

style (defaults to vivid):

The style of the generated images. Must be one of vivid or natural:
vivid causes the model to lean towards generating hyper-real and dramatic images.
natural causes the model to produce more natural, less hyper-real looking images.

This param is only supported for dall-e-3.

Source: DALL-E 3 API reference

Some examples of this parameter’s impact are in the cookbook, but I couldn’t get a sense of how much they differ based on a few images of a coffee set.

My current interest is in generating photorealistic images (as opposed to symbolic images or art), so I asked DALL-E to generate a few photography-like images.

Example images

The following table contains example images generated using DALL-E 3 in different ways:

  1. The image was generated using ChatGPT’s new “DALL-E” GPT. It is available as a part of ChatGPT Plus subscription. It’s only available via Web UI, and we have no direct control over low-level API parameters like style, quality, size. We can only influence the result with our prompt.
  2. Image generated using the https://api.openai.com/v1/images/generations API with quality=standard, size=1024x1024, model=dall-e-3 and style=natural
  3. Image generated using the https://api.openai.com/v1/images/generations API with quality=standard, size=1024x1024, model=dall-e-3 and style=vivid
PromptChatGPT’s stylenatural stylevivid style
A portrait of a school bus driver
Generate a photo of a surgeon performing brain operation
Generate a photorealistic image of a rock star performing on a stage of a large festival in the evening
Dancing people in the evening, sunset, city, flash photography, Ζ’/3.5
A portrait of a dog in a library, Sigma 85mm f/1.4

* acknowledgement: this and the following prompts come from an article by Merzmensch, which helped me direct DALL-E to generating more photo-realistic results. Thanks!
A bitten-into apple hanging on branch of an apple tree, Sigma 85mm f/1.4
An image of a couple sharing an umbrella on a quaint park bench amidst falling rain.

Lessons learned

Each image is unique, and comparing them is subjective by nature. My view is that:

  • ChatGPT seems to produce images close to ‘vivid’ in style. In my opinion, at this moment, it tends to make more interesting images than those returned by the API with the vivid setting.

    There are some threads in the forums, like this one, showing examples of API generating arguably lower-quality images than ChatGPT. This might be temporary, as we’re dealing with a preview product, and can probably be explained by how the prompt is differently pre-processed and rewritten in those cases.
  • The natural style produces images that I’d describe as more “photorealistic” – looking like something possible to capture with a camera rather than created by a computer game. They might look a bit more bland, but I like them, and I’ll probably use this setting a lot.
  • The default “vivid” style leads to cartoony, dramatic images. Sometimes they look fantastic, and sometimes they look artificial with too much saturation and dramatism. I like the last one with the umbrella, which could perfectly serve as an illustration for a book. But some of them are overdone for my taste. They scream, “I’m generated by AI,” much louder than the natural ones πŸ˜‰

Hope you enjoyed this short comparison!

The post DALL-E 3: `vivid` vs `natural` styles compared appeared first on Tim Taurit.

]]>
https://taurit.pl/dalle-3-style-vivid-vs-natural/feed/ 0
Karma Test Explorer in VS Code fails to load tests: “karma server quit unexpectedly” https://taurit.pl/karma-server-quit-unexpectedly-vs-code/ https://taurit.pl/karma-server-quit-unexpectedly-vs-code/#respond Tue, 08 Aug 2023 13:30:00 +0000 https://taurit.pl/?p=1379 Hey! This post is to capture the non-obvious debugging steps needed to resolve the following error: Failed to load tests – Karma server quit unexpectedly: Process exited with non-zero status code 1 At one point in our project development, team members started to notice that the Karma-based front-end tests stopped loading in the Karma Test ... Read more

The post Karma Test Explorer in VS Code fails to load tests: “karma server quit unexpectedly” appeared first on Tim Taurit.

]]>
Hey! This post is to capture the non-obvious debugging steps needed to resolve the following error:

At one point in our project development, team members started to notice that the Karma-based front-end tests stopped loading in the Karma Test Explorer plugin:

A manifestation of the problem: front-end unit tests were no longer listed in Visual Studio Code.

The error was not clear, but we got some more information when we looked in the output for the “Test Explorer” addon:

The output of the “Test Explorer” add-on says a bit more, but it is not enough to understand the issue.

The key finding that allowed us to pinpoint the issue was that Visual Studio Code keeps logs for all addons in the filesystem, even if they are not reachable from the IDE itself. The path might be different on your machine, but look for a folder like this:

In our case, the solution lay in the file named 2-Karma Test Explorer (workspace).log and was related to using // comments syntax in the angular.json file which Karma didn’t like:

Your problems most likely have a different root cause than mine, but hopefully, with the knowledge of where to look, you find the issue faster. Good luck!

The post Karma Test Explorer in VS Code fails to load tests: “karma server quit unexpectedly” appeared first on Tim Taurit.

]]>
https://taurit.pl/karma-server-quit-unexpectedly-vs-code/feed/ 0
How to convert JPEG to JPEG XL (*.jxl) and back, losslessly https://taurit.pl/convert-jpeg-to-jxl-losslessly/ https://taurit.pl/convert-jpeg-to-jxl-losslessly/#respond Tue, 06 Jun 2023 19:11:27 +0000 https://taurit.pl/?p=1284 I was recently reading about the JPEG XL format, which is one of the promising options for replacing a venerable JPEG standard. One of the aspects underlined in several sources is that it’s possible to losslessly transcode JPEG to JPEG XL and reduce the size of a file. What I found interesting is the possibility ... Read more

The post How to convert JPEG to JPEG XL (*.jxl) and back, losslessly appeared first on Tim Taurit.

]]>
I was recently reading about the JPEG XL format, which is one of the promising options for replacing a venerable JPEG standard.

One of the aspects underlined in several sources is that it’s possible to losslessly transcode JPEG to JPEG XL and reduce the size of a file. What I found interesting is the possibility of converting the file back to the source JPEG without losing even a bit of the original information.

How to actually do it?

The talk is cheap, but finding a tool that can perform such a conversion took me some time. Here are the steps, though:

  1. Download a reference implementation of JPEG XL for your OS. For me, it was the jxl-x64-windows-static package.
  2. Convert your file using cjxl.exe encoder from the package:
    cjxl.exe input.jpg output.jxl
  3. Convert your file back using djxl.exe decoder:
    djxl.exe output.jxl input-restored.jpg
  4. Note that input.jpg and input-restored.jpg are indeed bit-perfect.
The result of the experiment. The JPEG file decoded from the JPEG XL image is indeed identical to the first one.

I was also trying to achieve the same using the ImageMagick tool, which is a more practical tool to work with images, but I don’t think such lossless transcoding is supported at the moment.

Thoughts and comments

It’s not surprising that new formats can achieve much better compression ratios. But I found it interesting that there is so much redundancy in JPEG that even a lossless compression can gain us significant benefits.

To put that in context, you can compare the compression ratio with popular archive types like zip or 7zip (with their “ultra” compression mode):

When a JPEG file is losslessly compressed to the ZIP, 7zip, and JPEG XL formats, JPEG XL shines with a much lower file size

It’s uncertain if the JPEG XL format will gain traction and widespread support as it competes with quality alternatives like AVIF and WEBP. But this legacy-friendly feature could still make it useful in some use cases. For example, efficient archiving of JPEG attachments uploaded by users that we must retain unmodified for later retrieval.

The post How to convert JPEG to JPEG XL (*.jxl) and back, losslessly appeared first on Tim Taurit.

]]>
https://taurit.pl/convert-jpeg-to-jxl-losslessly/feed/ 0
Does “Allow Azure services and resources to access this server” cover GitHub Actions workflows? https://taurit.pl/allow-azure-services-and-resources-to-access-this-server-github-actions-workflows/ https://taurit.pl/allow-azure-services-and-resources-to-access-this-server-github-actions-workflows/#respond Tue, 06 Jun 2023 17:18:48 +0000 https://taurit.pl/?p=1288 There is a famous option in Azure Firewall settings labeled: “Allow Azure services and resources to access this server“ According to the UI hint, “This option configures the firewall to allow connections from IP addresses allocated to any Azure service or asset, including connections from the subscriptions of other customers.” I’ve seen this option confuse ... Read more

The post Does “Allow Azure services and resources to access this server” cover GitHub Actions workflows? appeared first on Tim Taurit.

]]>
There is a famous option in Azure Firewall settings labeled:

Allow Azure services and resources to access this server

According to the UI hint, “This option configures the firewall to allow connections from IP addresses allocated to any Azure service or asset, including connections from the subscriptions of other customers.”

I’ve seen this option confuse my team members many times. It might be because people find it not specific enough (what is an Azure service or asset?). Or maybe it’s just a bit hard to believe that a firewall allows opening gates to all Azure customers with a single checkbox.

Is GitHub an Azure service?

When I was building the GitHub Actions Workflow, I had to ask myself if this firewall exemption also covers GitHub Agents.

GitHub is a separate product from Azure, yet Microsoft bought it some time ago. Also, I know that the infrastructure of Azure DevOps and GitHub is similar and probably shared to some degree.

The short answer is: Yes. This checkbox will also allow GitHub Workflow agents access.

It was a bit hard to confirm if we can assume that and rely on it, but here are several sources to back this information:

Source 1: mention in the documentation

(…) Windows and Ubuntu runners are hosted in Azure and subsequently have the same IP address ranges as the Azure datacenters. macOS runners are hosted in GitHub’s own macOS cloud.

Source: Docs, “About GitHub-hosted Runners”

Source 1: community forums

If the Azure SQL is configured to allow access to Azure Services, that should be enough [to deploy database to Azure using GitHub CI/CD].

Source: Community thread, “GitHub CI/CD and the “deny public network access” setting”

Source 3: my own experience

Last but not least, I will share my personal experience. I ran the azure/sql-action@v1 Action in a GitHub Workflow against an Azure SQL server with a firewall enabled (with the mentioned checkbox ticked), and it was able to deploy it.

By the way, if you are tempted to maintain a list of GitHub IP addresses in your firewall configuration by yourself, be warned that the GitHub IP address list is very long and:

Since there are so many IP address ranges for GitHub-hosted runners, we do not recommend that you use these as allow-lists for your internal resources. The list of GitHub Actions IP addresses returned by the API is updated once a week.

Source: Docs, “About GitHub-hosted Runners”

Thoughts and comments

This is useful for those using GitHub Actions to deploy to Azure. At the same time, it is hard not to notice that this single checkbox is a big hole punched in the firewall that allows everyone using Azure infrastructure to contact your server.

The post Does “Allow Azure services and resources to access this server” cover GitHub Actions workflows? appeared first on Tim Taurit.

]]>
https://taurit.pl/allow-azure-services-and-resources-to-access-this-server-github-actions-workflows/feed/ 0
.NET 7 warning: use ‘GeneratedRegexAttribute’ (SYSLIB1045) https://taurit.pl/generatedregexattribute/ https://taurit.pl/generatedregexattribute/#respond Tue, 06 Jun 2023 16:45:16 +0000 https://taurit.pl/?p=1293 .NET 7 introduced the [GeneratedRegex] attribute that allows doing some regex initialization in compile time rather than in runtime. Using this attribute might help improve performance. Therefore some tooling might start showing you a warning that you miss an opportunity for free performance gains πŸ™‚ SYSLIB1045 Use ‘GeneratedRegexAttribute’ to generate the regular expression implementation at ... Read more

The post .NET 7 warning: use ‘GeneratedRegexAttribute’ (SYSLIB1045) appeared first on Tim Taurit.

]]>
.NET 7 introduced the [GeneratedRegex] attribute that allows doing some regex initialization in compile time rather than in runtime. Using this attribute might help improve performance. Therefore some tooling might start showing you a warning that you miss an opportunity for free performance gains πŸ™‚

SYSLIB1045 Use ‘GeneratedRegexAttribute’ to generate the regular expression implementation at compile-time.

Since the documentation is scarce at the moment, I’ll share a simple example of how you can resolve that warning from my own experience:

Code before the change

// OLD IMPLEMENTATION
private static readonly Regex MultipleWhitespacesRegex = new(@"\s+");
private string OldImplementation(string input) => MultipleWhitespacesRegex.Replace(input, " ");

Code after the recommended change

// NEW IMPLEMENTATION
[GeneratedRegex(@"\s+")]
private static partial Regex MultipleWhitespacesGeneratedRegex();
private string NewImplementation(string input) => MultipleWhitespacesGeneratedRegex().Replace(input, " ");

// please note, the class where this field is located must also now be declared as partial!

A quick benchmark

Even though I changed this only to resolve warnings in the codebase, I couldn’t resist seeing if there was a noticeable performance difference when the new attribute was used. And although my regular expression was extremely simple, the new approach still performed slightly better:

Benchmark results of a simple code after implementing the recommended solution for SYSLIB1045: use GeneratedRegex Attribute. See the benchmark code here.

As always, when we talk about performance, results may vary. You should not assume that something is faster just because it should be, etc. You know the drill πŸ˜‰

The post .NET 7 warning: use ‘GeneratedRegexAttribute’ (SYSLIB1045) appeared first on Tim Taurit.

]]>
https://taurit.pl/generatedregexattribute/feed/ 0
Calendar Filter – a small tool to declutter your calendar view https://taurit.pl/calendar-filter/ https://taurit.pl/calendar-filter/#respond Sun, 16 Apr 2023 18:32:03 +0000 https://taurit.pl/?p=969 Calendar Filter is an open-source service for decluttering subscribed calendars (in iCalendar, or *.ics format). Hide events and control what you see in your calendar views.

The post Calendar Filter – a small tool to declutter your calendar view appeared first on Tim Taurit.

]]>
About Calendar Filter

Calendar filter is a service that allows you to hide events in your calendar. I created it to have more control over subscribed calendars and allow decluttering them.

It’s open source, and now it’s publicly available as a free service. However, currently it’s best seen as a beta version, shared to collect feedback that might direct its future development.

Your calendar app retrieves calendar via a personalized link from the Calendar Filter service. Calendar Filter serves as a proxy between the app and the original calendar source.
The calendar filter service is basically a small, stateless web service that serves as a proxy between your calendar app and the calendar you want to subscribe to. It assumes iCalendar (*.ics) format of the calendar. It has several options to transform the original calendar.

What was the original use case?

The use case for which it was created is something like this:

  • I like to have a single view of all my personal calendars in one place. I guess it’s quite typical to use Google Calendar or other app to show multiple calendars, like:
    • Private personal calendar,
    • Work calendar,
    • Calendars shared with other people.
    • Calendars exposed by productivity apps like Todoist,
    • Personalized calendars exposed by services like Facebook, Meetup etc.
  • Some of the calendars you subscribe to are a bit cluttered, for example:
    • In your workplace calendar, you might have events like lunch or daily scrum, but want to filter them out when you glance at your overall agenda in Google Calendar or on your smartphone.
    • Your to-do app’s calendar feed might display all to-do items in the calendar, but you might want to only show tasks that have the time specific time assigned.

How to use it?

Go to the calendar.taurit.pl, where you can configure your filters and get a personalized link to your filtered calendar. Then use the link in your calendar app. That’s it!

A screenshot of a configuration page available at https://calendar.taurit.pl. This page allows you to personalize your filter.
A screenshot of the configuration page. No sign-up required.

If you found this page, I’d appreciate any feedback! Is this what you were searching for, or you expected to find a different sort of solution? I encourage leaving feedback in the comments.

You can also visit Calendar Filter on GitHub and create an Issue to submit feature ideas or bug reports. Kindly note that I do not monitor the service in any way (besides the costs it might generate), so if you notice any bugs, you need to report them if you want to see them fixed.

The post Calendar Filter – a small tool to declutter your calendar view appeared first on Tim Taurit.

]]>
https://taurit.pl/calendar-filter/feed/ 0
Azure Poland Central launched – but can you use it already? https://taurit.pl/azure-poland-central-status-2023/ https://taurit.pl/azure-poland-central-status-2023/#respond Sun, 16 Apr 2023 11:15:16 +0000 https://taurit.pl/?p=952 A new Azure region, β€œPoland Central” has recently appeared in Azure. Does it offer the same features as other regions. Is it ready for use?

The post Azure Poland Central launched – but can you use it already? appeared first on Tim Taurit.

]]>
I was happy to notice a few days ago that Azure’s β€œPoland Central” region has landed. At least according to my LinkedIn feed πŸ™‚ It looks like great news. It should enable use of Azure cloud in projects where data residency in Poland is required. And for users in Poland, it can mean reduced latency compared to other European data centers.

But is such a freshly launched region ready to use already? I don’t have any insider news, but I’ll share my experience at least.

The documentation is lagging

Currently, Azure’s documentation on geographies still shows β€œPoland Central” region with the status β€œComing soon”. There is also no info about availability zones setup and available products.

This might not be a big problem, but it can make it harder to evaluate what is available and what isn’t. Even ChatGPT won’t help as it’s always eager to mention that it has a training cutoff date of 2021 πŸ˜‰

The documentation doesn’t say anything about the recent launch yet

Some popular resource types are still missing

I tried to deploy a web application to Poland Central with the following result:

az deployment group create --resource-group MyResourceGroup --template-file appService.json

The output from the above command was:

{
    "code": "LocationNotAvailableForResourceType",
    "message": "The provided location 'polandcentral' is not available for resource type 'Microsoft.Web/serverFarms'. List of available regions for the resource type is 'southcentralus,msftwestus,msfteastus,msfteastasia,(...),westus3,eastasia,japaneast,jioindiawest'."
}

So, apparently App Service is one of the services that are not supported as I write this. Which probably makes you wonder what else is missing (or, if you prefer the glass half-full, what else is available).

What exactly is and isn’t available in Poland Central?

I hoped to make a simple list of what is and isn’t available, but here is another learning for me. As I write this, Azure has 3264 resource providers available!

It’s a bit too much to list them here in a blog post, but if you look for it, here it is on my GitHub. Along with the script that allows to refresh it:

Kindly note that resource providers are identified with their technical names, and not the names we might normally use or see in Azure Portal. So, you’ll find Microsoft.Web/serverFarms, but not App Service Plan.

A screenshot of Visual Studio Code showing the difference between lists of available resource types in two Azure regions.
You can use diff tools to compare resources available in two Azure regions (the files were generated by the script described earlier). Here we can see that KeyVaultis available in both Europe West and Poland Central. But the latter doesn’t yet offer Databricks, Microsoft Synapse or Machine Learning Services.

Summary

Whether the Poland Central region is ready for use, depends on your project’s infrastructure and the services you use. There is not much public information yet about the plans, so I am uncertain whether this region has ambitions to have feature parity with the big old regions, or it will be a bit limited.

I hope you found this short status check useful. If you know more about this new data center and would like to share it, feel welcome! πŸ™‚

The post Azure Poland Central launched – but can you use it already? appeared first on Tim Taurit.

]]>
https://taurit.pl/azure-poland-central-status-2023/feed/ 0
Move One Drive For Business folder – a workaround https://taurit.pl/move-one-drive-for-business-folder/ https://taurit.pl/move-one-drive-for-business-folder/#respond Thu, 13 Apr 2023 19:38:13 +0000 https://taurit.pl/?p=891 OneDrive is a reasonable place to keep documents at your workplace. The habit of keeping most my documents there saved me twice already. Once, when a company suffered a ransomware attack, we were only able to recover files backed up in OneDrive and other cloud services. And then another time my SSD died and OneDrive ... Read more

The post Move One Drive For Business folder – a workaround appeared first on Tim Taurit.

]]>
OneDrive is a reasonable place to keep documents at your workplace. The habit of keeping most my documents there saved me twice already. Once, when a company suffered a ransomware attack, we were only able to recover files backed up in OneDrive and other cloud services. And then another time my SSD died and OneDrive again helped me recover everything.

But as I checked last week, OneDrive for Business doesn’t seem to offer any option to move the folder to another location. The default is something like:

c:\Users\MyUserName\OneDrive - Company Name\

But I find it more easily accessible in a path like:

c:\OneDrive

Workaround

Here is a small, creative workaround I am proud of, so allow me to share it πŸ˜‰

mklink /J "c:\OneDrive" "c:\Users\MyUserName\OneDrive - Company Name\"

The command creates a junction point at c:\OneDrive that points to the directory c:\Users\MyUserName\OneDrive - Company Name.

This means that when you access the directory at c:\OneDrive, you are actually accessing the contents of c:\Users\MyUserName\OneDrive - Company Name. Any changes made to the files or folders within c:\OneDrive will be reflected in the original directory as well.

So for clarity, when we do it, there are now two references to the same folder visible in your file system. I found this solution good enough for me, though, and it’s working flawlessly so far.

The post Move One Drive For Business folder – a workaround appeared first on Tim Taurit.

]]>
https://taurit.pl/move-one-drive-for-business-folder/feed/ 0
Exclude files from VS Code search… and share it with the team! https://taurit.pl/vs-code-exclude-from-search-team-shared/ https://taurit.pl/vs-code-exclude-from-search-team-shared/#respond Thu, 13 Apr 2023 19:17:18 +0000 https://taurit.pl/?p=899 I see many developers use Visual Studio Code as a tool helping them perform a quick full-text search in their code. And for a good reason. You can open a folder in VS Code and find fragments of text in just seconds! However, sometimes we might want to ignore (hide) some files from search results. ... Read more

The post Exclude files from VS Code search… and share it with the team! appeared first on Tim Taurit.

]]>
I see many developers use Visual Studio Code as a tool helping them perform a quick full-text search in their code. And for a good reason. You can open a folder in VS Code and find fragments of text in just seconds!

However, sometimes we might want to ignore (hide) some files from search results. Developers almost always look for something in their source code, but rarely in build artifacts like cache, compiled files, or third-party modules.

The following screenshot shows a search results in a simple Angular app. Only one search result is practically useful, and 6 others are just garbage in a generated code in the cache:

Even in a simple app, irrelevant search results can clutter the view and make it more difficult to find what we need.

How to ignore folders from search results for ourselves?

Visual Studio Code allows us to exclude some files and folders from search. It’s as simple as adding a piece of configuration to your settings.json file, and it will be applied to all folders you open:

We can alter our personal VS Code settings to hide such search results.

How to share the configuration in the team?

While solving the problem for yourself is great, solving it for the whole team is much cooler 😎 And it’s as simple as adding the settings in the file named:

.vscode/settings.json

… which you check in to your git repository, instead of to your personal settings.json file. Example of the file content:

{
  "search.exclude": {
    "/.angular/cache": true,
    "/dist/": true
  }
}

And this is how it changes the search results. Much better now, eh? πŸ™‚

Decluttered search results, after files from /.angular/cache folder were excluded from search results.

The post Exclude files from VS Code search… and share it with the team! appeared first on Tim Taurit.

]]>
https://taurit.pl/vs-code-exclude-from-search-team-shared/feed/ 0
Outlook calendar: show the next 3 days https://taurit.pl/outlook-calendar-show-the-next-3-days/ https://taurit.pl/outlook-calendar-show-the-next-3-days/#comments Thu, 13 Apr 2023 17:59:34 +0000 https://taurit.pl/?p=893 Outlook allows us to easily switch between calendar views like β€œDay”, β€œWork week”, β€œWeek” and β€œMonth”. What I miss on this list is my favorite calendar view, available in Google Calendar and some other apps: β€œ3 days”. I find that the view showing the next 3 days (by which I mean today + 2 next ... Read more

The post Outlook calendar: show the next 3 days appeared first on Tim Taurit.

]]>
Outlook allows us to easily switch between calendar views like β€œDay”, β€œWork week”, β€œWeek” and β€œMonth”. What I miss on this list is my favorite calendar view, available in Google Calendar and some other apps: β€œ3 days”.

I find that the view showing the next 3 days (by which I mean today + 2 next days), is perfect. It allows noticing events in the near future that might require preparation today. But it’s less than 5–7 days, which can be a bit overwhelming.

So, how to do it? It’s actually as simple as learning a single keyboard shortcut!

Alt + 3

Or more generally, Alt + number of days between 1 and 7

This trick works both in Outlook (Desktop) and Outlook (Web). Although in the Web app, there is now also a menu item for the same action πŸ™‚

The downside is that I cannot see a way to make it β€œskip” weekends. So on a Friday you would just see Friday + Saturday + Sunday. But maybe there is no point in worrying about the next week on a Friday? πŸ˜‰

The post Outlook calendar: show the next 3 days appeared first on Tim Taurit.

]]>
https://taurit.pl/outlook-calendar-show-the-next-3-days/feed/ 1
Thunderbird add-on to warn if text is invisible (in dark or light mode) https://taurit.pl/thunderbird-dark-mode-invisible-text-warning/ https://taurit.pl/thunderbird-dark-mode-invisible-text-warning/#respond Sat, 01 Apr 2023 19:50:08 +0000 https://taurit.pl/?p=884 As Dark Mode became popular, we need to choose email font colors with more caution.

The post Thunderbird add-on to warn if text is invisible (in dark or light mode) appeared first on Tim Taurit.

]]>
A few years ago, we could reasonably assume that an email we write will be displayed as a black text on a white background. But today, the dark mode (or, dark theme) is available in pretty much all applications, including e-mail clients. Also in Thunderbird.

I recently emailed a few people and formatted the text in that email to use a white font. Everything looked fine on my side (I use a dark mode), but the recipients used Outlook with a white background and couldn’t see part of the email! This led to unnecessary confusion.

Add-on to warn about invisible text

Today, I created a small add-on as a proof-of-concept to warn me if I make a similar mistake in the future.

Presently, it’s literally just a few lines of code. It contains a simple heuristic to detect if we hard-coded white or black font, and warn about it. This is how it looks in action:

An email composed on a system with dark mode on. At first glance, everything looks fine here. But take a look at the next screenshot to see why the add-on displayed the warning.
When we switch to light mode, we can see that part of the text is invisible. And this is how recipients might see it if we don’t reset font color to the defaults.

How to use this add-on?

I published the Thunderbird Font Color Validator add-on on GitHub. If you want to try it, check the Releases section. You can find a zip file there which can be installed in Thunderbird. Since it’s just an experiment, I didn’t bother to publish it to the add-on catalog at this point.

If you found this page and find this add-on idea useful, please star the repo or leave a comment to leave me a hint that it’s worth a bit more of attention.

Happy emailing!

The post Thunderbird add-on to warn if text is invisible (in dark or light mode) appeared first on Tim Taurit.

]]>
https://taurit.pl/thunderbird-dark-mode-invisible-text-warning/feed/ 0
Lambda cold start times in .NET6: ARM vs. x64 https://taurit.pl/lambda-cold-start-net6/ https://taurit.pl/lambda-cold-start-net6/#respond Fri, 31 Mar 2023 18:44:34 +0000 https://taurit.pl/?p=832 Cold start time can be an important metric to optimize in a serverless application. Does choice of a CPU architecture impact cold start time in AWS?

The post Lambda cold start times in .NET6: ARM vs. x64 appeared first on Tim Taurit.

]]>
Why cold start time matters

Cold start time can be an important metric to optimize in a serverless application, like those using AWS Lambda. I maintain some functions with HTTP endpoints where quick response time is crucial for a good user experience. It convinced me to take a closer look and measure cold start times of a .NET application hosted on AWS Lambda service.

In the world of web performance, experts often quote that anything that happens in time shorter than 100 milliseconds feels instant to a human. But serverless functions that are not used frequently can by far exceed that threshold. Mikhail Shilkov measured cold starts in Azure Functions. His results prove that a cold start of an Azure Function written in C# and hosted in Azure’s Consumption Plan can exceed 10 seconds. That is a long request to wait for.

Does choice of a CPU architecture impact cold start time in AWS?

I decided to measure an impact of a CPU architecture on the cold start time of Lambda functions hosted in AWS. The choice between the default x86_64 (=x64 in Microsoft’s terminology) or ARM64 is one of the first choices that have to be made when creating an AWS Lambda instance. So, does it matter which one we choose if our priority is to minimize the latency?

Here are some assumptions that I made in my measurements:

  • The function code used in tests does almost nothing beyond proving that it started successfully and can handle a request. It’s mostly a boilerplate code that comes with a project Blueprint named Annotations Framework (preview).
  • I tested a range of memory settings between 128 MB and 3008 MB. This is because I know that in AWS Lambda adding more memory, proportionally increases the amount of CPU. I couldn’t assign more memory than 3008 MB to any function due to limits on my account. But as we’ll see in the results, returns are diminishing quickly. So, going beyond 3008 MB is unlikely to change much.
  • I tested only .NET 6 runtime (which is the current LTS version at the time).
  • I am interested in cold start time impact. But I really measure the end-to-end response time for lambdas, as seen on the client side. This means that the measured time includes:
    • a round trip time to the AWS data center where Lambdas are deployed (I tested it to be about 30ms).
    • cold start time, when it occurs. Variance in that one is what is interesting.
    • function’s own execution time, which I attempted to minimize by not giving it almost anything to do.

The results: cold start time in .NET6

Let me start with visualizing all collected data points:

A chart showing a relation between AWS Lambda's inactivity time and total time needed to handle a subsequent request
A chart showing a relation between .NET 6 AWS Lambda’s inactivity time and total time needed to handle a subsequent request.

Some conclusions I draw from the above chart:

  • Cold starts are observed after about 5 minutes of function’s inactivity. They result in noticeably longer response times (as anticipated).
  • The response time during cold starts is heavily impacted by the choice of amount of memory. Functions with only 128 MB of memory require about 1.5 seconds to serve the first request after a period of inactivity. Functions with 1024 MB of memory will typically require just 0.5 second.
  • Requests to functions hosted on ARM64 architecture appear to be slightly slower than their counterparts on average. But it might be misleading, as the points that stand out on a diagram might not be too representative. It deserves a closer look, so keep reading πŸ™‚

Cold starts: ARM64 vs. x86_64

I collected some more data through the night, and categorized it into two bins, ARM64 and x86_64. Then, I compared them, but for each value of memory separately. Here’s how it looks:

Time needed to process a request in a cold start scenario with 128 MB of memory.
Time needed to process a request in a cold start scenario with 128 MB of memory.
Time needed to process a request in a cold start scenario with 512 MB of memory.
Time needed to process a request in a cold start scenario with 512 MB of memory.
Time needed to process a request in a cold start scenario with 2048 MB of memory.
Time needed to process a request in a cold start scenario with 2048 MB of memory.
The observed difference of median cold start times between ARM64 and x86_64, assuming both versions run with the exact same amount of memory.
The observed difference of median cold start times between ARM64 and x86_64. I compare functions that run with the same amount of memory. The more memory we add, the smaller is the difference between CPU variants.

Discussion

So, what story does the data tell? When the amount of memory is a constant, .NET 6 lambdas running on ARM64 machines will start slightly slower than those on x86_64 machines. That difference decreases as we run on more powerful machines.

The difference could be easily compensated by adding some more memory (and thus, CPU power) to ARM64 Lambdas. As our measurements confirm, cold start time of .NET Lambdas is very sensitive to changes in the memory parameter.

The performance of .NET on ARM gets a lot of attention nowadays. The newer .NET 7 already received performance improvements in this area. I haven’t tested it here because it’s not a Long-Term Support release. It is therefore not available as a managed runtime in AWS Lambda. But the results can be different, and I’m sure there will be improvements in the absolute values for both CPU architectures.

My impression is that the differences in cold start times, while they exist, don’t seem too significant. In .NET 6 it doesn’t look like an important factor to consider when choosing the right CPU architecture.

Read more

The post Lambda cold start times in .NET6: ARM vs. x64 appeared first on Tim Taurit.

]]>
https://taurit.pl/lambda-cold-start-net6/feed/ 0
Can I mix ARM64 and x64 Lambdas in a single AWS Serverless Application project? https://taurit.pl/aws-serverless-mix-arm64-x86_64-in-project/ https://taurit.pl/aws-serverless-mix-arm64-x86_64-in-project/#respond Thu, 30 Mar 2023 10:14:28 +0000 https://taurit.pl/?p=835 Today, I spent some time trying to publish a .NET 6 AWS Serverless Application that consisted of: I made sure that each a configuration for functions targeting arm64 differed by setting the following non-default property in the serverless.template file: A problem: Internal Server Error from arm64 lambdas Unfortunately, after I published the application, I noticed ... Read more

The post Can I mix ARM64 and x64 Lambdas in a single AWS Serverless Application project? appeared first on Tim Taurit.

]]>
Today, I spent some time trying to publish a .NET 6 AWS Serverless Application that consisted of:

  • A few serverless functions that I wanted to run using the default CPU architecture, x64 (or using AWS terminology, x86_64)
  • A few other serverless functions that I wanted to run on arm64 architecture (on Graviton2 CPUs)

I made sure that each a configuration for functions targeting arm64 differed by setting the following non-default property in the serverless.template file:

    // (...)
    "Properties": {
        "Runtime": "dotnet6",
        "MemorySize": 512,
        "Architectures": [
            "arm64"
        ],
        // (...)
    }

A problem: Internal Server Error from arm64 lambdas

Unfortunately, after I published the application, I noticed that:

  • The functions that targeted the default x86_64 architecture worked flawlessly
  • The functions that targeted arm64 responded with Internal Server Error. Looking at the logs in CloudWatch I noticed the exception:
Unhandled exception. System.BadImageFormatException: Could not load file or assembly 'Amazon.Lambda.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. An attempt was made to load a program with an incorrect format.

Such an error might suggest a mismatch between the runtime environment supported by the application and the actual runtime environment.

What happened when I targeted different architectures in a single project?

The hint came from the publish operation logs. I noticed the tooling used the following command to prepare the deployment package for the first of my functions (which was x64):

dotnet publish "D:\Projekty\Benchmark.AwsServerless\Benchmark.AwsServerless." --output "D:\Projekty\Benchmark.AwsServerless\Benchmark.AwsServerless.\bin\Release\net6.0\publish" --configuration "Release" --framework "net6.0" /p:GenerateRuntimeConfigurationFiles=true --runtime linux-x64 --self-contained False

What happened next was that the compiled artifacts got packed into a ZIP package and uploaded to AWS S3 storage. However, the same artifacts were set to be re-used by subsequent functions as well. Even those configured to target arm64! Hence the mismatch in runtime.

Source of confusion: the deployment tooling assumes that all the Lambdas within the application target the same CPU architecture as the first Lambda it encounters.
Source of confusion: the deployment tooling assumes that all the Lambdas within the application target the same CPU architecture as the first Lambda it encounters.

How I solved the issue

This looks like an assumption hard-coded in the tooling that architecture is the same for all Lambdas within the project.

If that’s the case, the easiest solution seems to just split the C# project into two separate projects, one containing only x64 Lambdas, and the other containing only ARM64 Lambdas. But the projects can still share a code via a class library.

When I did that, the deployment tooling properly auto-detected the linux-arm64 target in the arm64 project, and no other code changes were needed beside the one in the serverless.template file mentioned earlier.

The post Can I mix ARM64 and x64 Lambdas in a single AWS Serverless Application project? appeared first on Tim Taurit.

]]>
https://taurit.pl/aws-serverless-mix-arm64-x86_64-in-project/feed/ 0
Is Chiang Mai good for cycling? https://taurit.pl/chiang-mai-cycling/ https://taurit.pl/chiang-mai-cycling/#comments Mon, 27 Mar 2023 15:25:47 +0000 https://taurit.pl/?p=747 Chiang Mai is one of the highest-ranked destinations for digital nomads. This year, I got tempted to see how would it be to live and work from here. In this short piece, I’ll share my opinion on one particular aspect of such life: Is Chiang Mai good for bike (bicycle) riding? I’ll admit that at ... Read more

The post Is Chiang Mai good for cycling? appeared first on Tim Taurit.

]]>
Chiang Mai is one of the highest-ranked destinations for digital nomads. This year, I got tempted to see how would it be to live and work from here. In this short piece, I’ll share my opinion on one particular aspect of such life: Is Chiang Mai good for bike (bicycle) riding?

I’ll admit that at first, I was utterly disappointed with the cycling experience here. After a few days, I adapted a bit. But if you expect an outstanding experience, maybe this text allows you to tune up expectations a bit – hopefully without killing the enthusiasm. So, allow me to elaborate πŸ™‚

Cycling in Chiang Mai: the good parts

Jungle near Chiang Mai (Doi Suthep area)
A bicycle allows exploring and reaching places that would otherwise be hard to find. Like this cool jungle near the Doi Suthep road πŸ™‚

Here is what I liked about my bike riding experience:

  • The weather (in the dry season, at least). In February and March it is just perfect. It is pleasantly warm, as presented in the diagram of average temperatures in Chiang Mai. There is no rain. The humidity level is comfortable.
  • Some outskirts and suburban areas have good roads for cycling. For example:
    • The Chiang Mai University campus feels like a city within a city. It has bicycle paths and little traffic on the roads.
    • The Doi Suthep mountain and National Park adjoin the city and have a great road to cycle (assuming you like to cycle uphill ;))
  • You can rent a bicycle cheaply. I got mine for 1500 THB for 2 weeks (which is about $3/day). There were much less bicycle rental places than I expected in a popular tourist destination, but Google Maps still shows few possibilities.
  • People are nice, also when you bike πŸ˜‰ I saw no signs of impatience on the road. Instead, I saw many smiles, thumbs up, friendly comments.

Cycling in Chiang Mai: the bad parts

A cloud of smog over Chiang Mai, as seen from a scenic Doi Suthep road. March 2023.
  • Air quality in the burning season is awful. From what I hear – this happens year after year. While I was enjoying the views, I was sadly aware that health-wise, it’d be much better to sit at home.
  • The road infrastructure in the city and how it is used is definitely not bicycle-friendly, compared to what I know best (Warsaw, Poland). I don’t want to rant about it, but it can be a challenge to walk on foot or cross the street. Using a bicycle is a level of difficulty higher. Bicycle paths are very rare. The pavements suitable for riding a bike are also very rare. Streets have high levels of traffic, and even if there are some roadsides, they are shared by parked vehicles, driving vehicles or food trucks.
  • The street system makes it difficult to go from place to place because many streets are not connected. What I mean is that there are some big main streets which I find unsuitable for cycling, and then there are side-streets branching off those main streets but often ending in a dead-end. Finding a bicycle-friendly path between two points on a map is no easy task.
  • Stray dogs (of which 99,9% seem harmless) apparently don’t like my bicycle too much. Maybe they are not accustomed to seeing one. Perhaps I was just unlucky, but one attacked me on my first day on the bike, which was terrifying. I tried to mitigate it later by carefully observing all dogs and staying at a distance (to not cross into β€œtheir” territory), and with such safety measures, I had no more incidents of this kind.

Given all the above, I used my bicycle much less than I expected, once every few days. I usually preferred to walk somewhere or use a songthaew or other taxi service if it seemed too far.

See how it looks for yourself (on a video)!

I’ve compiled a short video for you to see just how it looks. I’m not forcing any thesis here, it shows some good parts and some bad parts:

Thanks for stopping by! If you’d like to add something to the topic yourself, be my guest and comment πŸ™‚

The post Is Chiang Mai good for cycling? appeared first on Tim Taurit.

]]>
https://taurit.pl/chiang-mai-cycling/feed/ 2
Run AWS Serverless C# application locally in Visual Studio https://taurit.pl/aws-serverless-application-csharp-run-locally-in-vs/ https://taurit.pl/aws-serverless-application-csharp-run-locally-in-vs/#respond Wed, 22 Mar 2023 05:15:30 +0000 https://taurit.pl/?p=783 When I created my first AWS Serverless application from a template (a blueprint) in Visual Studio, I expected that I could just run it locally the same way as I always run Azure Functions. However, I received the following error: Unable to run your project. The “RunCommand” property is not defined. I cannot imagine working ... Read more

The post Run AWS Serverless C# application locally in Visual Studio appeared first on Tim Taurit.

]]>
When I created my first AWS Serverless application from a template (a blueprint) in Visual Studio, I expected that I could just run it locally the same way as I always run Azure Functions. However, I received the following error:

Unable to run your project. The “RunCommand” property is not defined.

An error message displayed when you attempt to start an AWS Serverless project created from a template in Visual Studio.
An error message displayed when you attempt to start an AWS Serverless application project created from a template in Visual Studio.

I cannot imagine working on a project without debugging it locally, so of course I started to dig how to start AWS Serverless application without deploying it to the cloud every time I want to test something.

Long story short, I learned that AWS Toolkit extension serves that purpose. Moreover, using any recent version, eliminates the need for manual setup. All you need to do to start a project is select the appropriate debug profile, “Mock Lambda Test Tool”, which surprisingly is not the default option.

A debugging profile that allows to start a Serverless project and debug it in Visual Studio.
A debugging profile that allows to start a Serverless project and debug it in Visual Studio.

This is trivial, but easy to miss if you expect that the default configuration works out-of-the-box. And I think that the next steps are more-or-less intuitive once you launch the test tool, and allow you to experiment until you stumble upon another issue πŸ™‚

If, for whatever reason, such a debugging profile is not available in your Lambda project after creating it from a template, you can in theory add it manually following the instructions in the AWS Lambda Test Tool documentation. But this should not be needed if you have recent tooling.

As a side note: I cannot see any use for the non-working, default profile added by Visual Studio. So, I removed it from launchSettings.json to avoid confusion about this issue:

A default debug profile added when creating a project from a template. It seems useless and redundant. I decided to remove it.
A default debug profile added when creating a project from a template. It seems useless and redundant. I decided to remove it.

Happy coding and low cold start times to you!

The post Run AWS Serverless C# application locally in Visual Studio appeared first on Tim Taurit.

]]>
https://taurit.pl/aws-serverless-application-csharp-run-locally-in-vs/feed/ 0
Update outdated images in Docker on Windows https://taurit.pl/docker-windows-update-base-images/ https://taurit.pl/docker-windows-update-base-images/#respond Tue, 21 Mar 2023 03:14:00 +0000 https://taurit.pl/?p=769 In Docker, it is common to build your containers upon a base image from a third party. For example, a Dockerfile for .NET project usually starts with a code similar to: Now, for a long time, I assumed that when I build an image from such a Dockerfile, it will use the most recent available ... Read more

The post Update outdated images in Docker on Windows appeared first on Tim Taurit.

]]>
In Docker, it is common to build your containers upon a base image from a third party. For example, a Dockerfile for .NET project usually starts with a code similar to:

FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80

#... more steps to build the project etc.

Now, for a long time, I assumed that when I build an image from such a Dockerfile, it will use the most recent available versions of base images like mcr.microsoft.com/dotnet/aspnet:7.0.

I typically built containers like here:

docker build -t myimagename:latest .

But then I noticed that a newly built image was based on a 3-months-old, cached versions of a base image!

A fresh build of a Docker image might still embed an old base image if it is was in the cache!
A fresh build of a Docker image might still embed an old base image if it is in the cache!

We would rather not run our software on an outdated system, right? Researchers constantly find vulnerabilities in base images. Whenever they patch one, they re-build the image and upload a new version to the server.

On the other hand, Docker’s behavior seems reasonable. The version tag in the base image name, aspnet:7.0, did not change. Docker has no reason to think that the cached version was any different from the identically named version on a server.

So, how do we hint Docker that we want it to use the most recent base image?

Update outdated Docker images on Linux and macOS

Goffity Corleone published a solution to update all docker images already pulled on Linux and macOS. If you use those systems, you can find an answer there!

Update outdated Docker images on Windows

So on Windows, you don’t normally have tools like grep, awk, xargs. I thought of writing an equivalent script in PowerShell that updates all images. But this would still be ugly as it would have to parse output from docker images which is formatted for humans, not machines (with no flag to control that currently). So, I don’t have such a script ready to copy and paste, sorry.

But I found 2 alternatives to achieve this goal, so I’ll share them:

Method 1: always use docker build --pull

I found that there is a --pull argument we can add to our build command:

docker build --pull -t mycontainername:latest .

I noticed that when we add this argument:

  • Docker CLI downloads (pulls) and uses the most recent base images specified in a Dockerfile during the build. Just as we want!
  • Docker CLI does not update the stored images, though. We still store an outdated image. Future builds without this flag will use the old base image.

A side note: if the Dockerfile contains any custom steps that download software or project dependencies, we might want to additionally use --no-cache to keep them current as well (at least for production builds).

Method 2: update images one by one

The docker CLI doesn’t seem to have any command to update all obsolete images, so the easiest approach is to update them one by one. You can do it from the command line (docker image pull imagename:tag). But perhaps, Docker Desktop GUI allows doing that even quicker:

Perhaps the quickest way to update docker images is with a Docker Desktop GUI.
Perhaps the quickest way to update docker images is with a Docker Desktop GUI.

Pulling the most recent image will still keep the old image in the list as a dangling image, so you might also want to delete it. You probably won’t ever need that anymore, and I would normally clean up such unused images.

If you have any thoughts on this topic, please share in the comment!

The post Update outdated images in Docker on Windows appeared first on Tim Taurit.

]]>
https://taurit.pl/docker-windows-update-base-images/feed/ 0
How to normalize audio volume in your Anki flashcards https://taurit.pl/how-to-normalize-audio-volume-in-anki-deck-media-library/ https://taurit.pl/how-to-normalize-audio-volume-in-anki-deck-media-library/#respond Sat, 05 Nov 2022 11:39:59 +0000 https://taurit.pl/?p=709 This post describes how we can normalize the volume of audio files in Anki library. I want to make a note on it while I remember. I know this is a niche need, but if Google search led you here, I believe you might appreciate it πŸ˜‰ Statement of the problem Solution: how to normalize ... Read more

The post How to normalize audio volume in your Anki flashcards appeared first on Tim Taurit.

]]>
This post describes how we can normalize the volume of audio files in Anki library. I want to make a note on it while I remember. I know this is a niche need, but if Google search led you here, I believe you might appreciate it πŸ˜‰

Statement of the problem

  • I am using Anki to learn languages with flashcards, specifically: I use AnkiDroid.
  • I have Anki decks (or, collections of flashcards) from various 3rd party sources. Those flashcards include MP3 recordings of words/sentences.
  • The volume level of MP3 files coming from different sources was significantly different.
  • During review of cards, they came in pretty much random order. I had to constantly increase/decrease volume to hear the audio sample. This is totally distracting during learning session and inconvenient.

Solution: how to normalize volume in Anki media collection

I’ll describe my solution in bullet points. This is not a step-by-step tutorial, but it shows that normalization is possible and hints where to start πŸ™‚

  1. I assume you have Anki installed on your PC. Find where Anki stores the media for all your flashcards. In my case, it was:
    C:\Users\{myUserName}\AppData\Roaming\Anki2\User 1\collection.media\
  2. Please back up the collection first! You will make mistakes on the way, guaranteed.
  3. Optionally, but it might help limit the scope of your problem. If you used Anki for some time already, you might have a lot of unused media files (e.g. from courses you imported in the past and deleted). You can easily clean up them by clicking Tools -> Check Media in Anki and confirming that you want to delete obsolete media files.
  4. Normalize your audio files in the media collection folder to the same volume. You can do it any way you want, but here are the key points I learned:
    • ffmpeg is a popular, commonly used tool that allows to achieve that.
    • One of the ffmpeg’s filters allowing to normalize volume level is loudnorm. It worked well, the only issue I encountered was that it didn’t work for audio files shorter than 1 second (But you can find a workaround if you search. Mine was to add a small silent “padding” at the end to make the audio file longer).
    • You need to choose an arbitrary target for what you consider “the optimum volume” for all your media. I chose:
      • integrated loudness target = “-20.0”
      • true peak = “-4.0”
    • At this point, the job is to iterate over files in your collection and normalize them to the same volume level. I created a script (that I won’t be sharing), which for each audio file in the media collection:
      • Launches a small tool named ffmpeg-loudnorm-helper to detect what adjustments needed to made on a file.
      • Launches ffmpeg and passes the parameters output by the above tool.
  5. Now you already have your audio files normalized on your PC. However, Anki won’t sync them, so it won’t solve your problem if you learn on Android or some other machine. To sync the modified files, you additionally need to:
    • If you use Anki 2.0, you can just get the “Refresh media references” add-on
    • If you use Anki 2.1 or newer, you need to hack that add-on a bit and remove one unsupported API call, clearMemoryCaches(), from the add-on’s code. This was suggested in the comments from one of the other users and works. It sounds scary, but it’s just removal of a single line from a text file.
    • After installing the modified add-on, in Anki go to Tools -> Refresh Media.
    • At this point, you can sync and Anki will see and re-upload the changed media files. It can take a few minutes, esp. if you have a large collection of audio files.

Summary

I’d say that while it’s not a rocket science, it still requires a few hours of tinkering. It also requires ability to script/code a simple program and some patience for trial and errors. But if you are serious about learning with your flashcards, the effect is absolutely worth it πŸ™‚

The post How to normalize audio volume in your Anki flashcards appeared first on Tim Taurit.

]]>
https://taurit.pl/how-to-normalize-audio-volume-in-anki-deck-media-library/feed/ 0
Always open Microsoft documentation in English https://taurit.pl/always-open-microsoft-documentation-in-english/ https://taurit.pl/always-open-microsoft-documentation-in-english/#respond Wed, 17 Aug 2022 17:14:41 +0000 https://taurit.pl/?p=208 The problem: Microsoft docs selects a machine-translated version of document by default If English is not your native language, you might have noticed that documentation from Microsoft opens in your native language by default. This behavior is very user-unfriendly because, unfortunately, the documentation is machine-translated. The quality of this translation makes it unreadable for every ... Read more

The post Always open Microsoft documentation in English appeared first on Tim Taurit.

]]>

The problem: Microsoft docs selects a machine-translated version of document by default

If English is not your native language, you might have noticed that documentation from Microsoft opens in your native language by default. This behavior is very user-unfriendly because, unfortunately, the documentation is machine-translated. The quality of this translation makes it unreadable for every developer I encountered πŸ˜‰

This behavior exists for years. As far as I know, this helps them position pages high in search engines (in the SEO sense) and is unlikely to change.

How can we optimize our user experience, so we don’t have to look for the “Read in English” button every time?

Microsoft Docs articles in non-English language are often machine-translated.

Solution 1: change language preferences in the browser

This option works well. The downside is that it affects the whole of your browsing experience, not just Microsoft Documentation. So if you want to buy a train ticket tomorrow, it might open in English, and not your native language.

Changing language settings in Edge browser preferences, so Microsoft Docs open in English language by default.

Solution 2: user a browser add-on to redirect to the page in English

I think that I originally learned this trick from Piotr Stapp. You can use a browser add-on like Redirector (here for Edge, Chrome, Firefox). Then you can specifically only redirect the documentation pages with a configuration patterns like:

Redirect: https://docs.microsoft.com/pl-pl/*
To: https://docs.microsoft.com/en/$1
Example URL: https://docs.microsoft.com/pl-pl/dotnet/framework/wpf/advanced/merged-resource-dictionaries
Pattern type: Wildcard

Redirect: https://msdn.microsoft.com/pl-pl/*
To: https://msdn.microsoft.com/en/$1
Example URL: https://msdn.microsoft.com/pl-pl/asdf
Pattern type: Wildcard

Redirect: https://azure.microsoft.com/pl-pl/*
To: https://azure.microsoft.com/en/$1
Example URL: https://azure.microsoft.com/pl-pl/pricing/details/app-service/windows/
Pattern type: Wildcard
Example of a Redirector addon configuration that always redirects you to the documentation in English
Example of a Redirector addon configuration that always redirects you to the documentation in English

Which one is better?

I actually use both. At work, I’m just defaulting to English with everything. At home, where I more often browse the Polish internet, I prefer to selectively switch to English only for Microsoft docs. Both methods work reliably πŸ™‚

The post Always open Microsoft documentation in English appeared first on Tim Taurit.

]]>
https://taurit.pl/always-open-microsoft-documentation-in-english/feed/ 0
Can I debug Azure DevOps pipelines locally? https://taurit.pl/debug-yaml-pipeline-locally/ https://taurit.pl/debug-yaml-pipeline-locally/#respond Thu, 11 Aug 2022 17:43:00 +0000 https://taurit.pl/?p=466 TL/DR: no. But you can make your life at least a bit easier with the right tooling. I heard this question a few times from my colleagues, so I’ll drop a short post with my answer for a wider audience πŸ™‚ Many coders I know tend to avoid tasks where they need to change something ... Read more

The post Can I debug Azure DevOps pipelines locally? appeared first on Tim Taurit.

]]>
TL/DR: no. But you can make your life at least a bit easier with the right tooling.

I heard this question a few times from my colleagues, so I’ll drop a short post with my answer for a wider audience πŸ™‚

Many coders I know tend to avoid tasks where they need to change something in Azure Pipelines. I think I understand that instinct. This area is unfamiliar for most .NET or front-end coders. There is a steep learning curve to start. YAML format is controversial. There are no code suggestions that would be on par with Visual Studio’s IntelliSense. The native languages used in pipelines are bash and PowerShell, often unfamiliar.

And the last problem: no one likes to write the code that cannot be debugged locally. Instead, it looks like it needs to be pushed to git repo whenever you want to test something. Then we need to wait for feedback until it runs, so learning by trial-and-error is harder.

This article explains how I learned to deal with it. I use Azure DevOps quite often for about 3 years now, and I learned to like it.

Debugging a DevOps pipeline on your local machine

Sorry to disappoint you, but you won’t be able to debug the pipeline on your local machine. Or at least: not in a way you are familiar with if you expected to place breakpoints, step through the code line-by-line, or inspect the state of variables in some IDE.

Debugging Azure DevPos pipeline in Visual Studio: a concept. In reality, this is not possible.
Debugging YAML pipeline on a local machine: a visualization. Unfortunately, this won’t happen. Read further to see why.

Firstly, Pipelines are described in a declarative code, a bit similar to how HTML describes page layout or how SQL describes queries. This contrasts with mostly imperative languages like C# where you describe line-by-line what the application is supposed to do. You wouldn’t expect to place breakpoints in HTML document and step through it line-by-line, because it would not make sense. It’s the same with pipelines.

Secondly, the pipeline is just a glue for smaller building blocks, tasks, which are diverse in how they work and what technologies they use. Some of them execute iterative code (e.g. PowerShell scripts, Windows batch scripts, Linux bash scripts, JavaScript applications). Those technologies are diverse, and I think it would require a very complex IDE/debugger to provide a unified debugging experience. But some tasks just wrap some binary executable (e.g. Azure CLI) and there would be nothing debuggable in the traditional sense.

Thirdly, in DevOps pipelines, context is everything. Tasks have many built-in assumptions about the environment, like what programs are installed on the agent executing the pipeline. Pipelines often connect to external service, for example to deploy resources to Azure. To make that happen, they must be executed in a context of a user who has permissions to perform actions. Or a pipeline might take artifacts from another pipeline as an input. All such context is carefully configured in DevOps with use of features like Service Connections, Library (of variable groups) and other.

How to improve your experience working with DevOps pipelines

The above arguments might sound that there is no hope. But even if you cannot debug your pipeline as a whole, here are some hints that can make your life easier:

Use Windows Subsystem for Linux (WSL)

If you write some bash scripts, you should certainly try debugging them locally. It sounds like a hell to write a bash script, and only test it after pushing to git and looking at pipeline logs.

If you use Windows, you can install Windows Subsystem For Linux feature and run your scripts on your local machine without much hassle.

Get a proper IDE support if you write bash or PowerShell scripts

We normally use IDEs like Visual Studio Professional or Rider that cost hundreds of dollars a year to get the best developer experience available. But it appears as if developers forgot about all of that when they need to code in bash, and they try to scribble some scripts in Notepad or similar.

Invest some time and get proper IDE and extensions to support you. For example, VS Code has good extensions to develop bash (see ShellCheck) and Powershell.

ShellCheck extension in Visual Studio Code is useful to detect errors and warnings in Linux bash script.
ShellCheck is an extension to Visual Studio Code that does static code analysis and shows useful warnings

Use VS Code and Azure Pipelines extension to develop YAML pipelines

The Azure Pipelines extension provides syntax highlighting and IntelliSense for YAML pipelines. You might need to fine-tune Visual Studio settings, because by default it will often auto-detect the file type as generic “YAML” and not “Azure Pipelines”.

Comment out (temporarily) parts of pipeline that are irrelevant to what you do

Sometimes you will need to test your code in a real DevOps environment. You can work on a git branch and run the pipeline from there. If there are parts of the pipeline irrelevant to what you do, you can try commenting them out. From my experience, test runs of pipelines will just happen many times during development. It’s part of the job.

If you can think of any good hints on how to make pipeline development easier, please share with us in the comments!

The post Can I debug Azure DevOps pipelines locally? appeared first on Tim Taurit.

]]>
https://taurit.pl/debug-yaml-pipeline-locally/feed/ 0
Migrate from Jetbrains.Annotations [NotNull] / [CanBeNull] to C# Nullable Reference Types https://taurit.pl/migrate-jetbrains-annotations-notnull-canbenull-to-nullable-reference-types/ https://taurit.pl/migrate-jetbrains-annotations-notnull-canbenull-to-nullable-reference-types/#respond Thu, 11 Aug 2022 15:23:09 +0000 https://taurit.pl/?p=558 The most popular feature of JetBrains.Annotations is probably the addition of two cool attributes: [NotNull] and [CanBeNull]. They add a meta-information to a marked element, describing if null is a correct value for that element, or not. Then, developers using ReSharper would get help from the IDE and could easier avoid mistakes like here: I ... Read more

The post Migrate from Jetbrains.Annotations [NotNull] / [CanBeNull] to C# Nullable Reference Types appeared first on Tim Taurit.

]]>
The most popular feature of JetBrains.Annotations is probably the addition of two cool attributes: [NotNull] and [CanBeNull]. They add a meta-information to a marked element, describing if null is a correct value for that element, or not. Then, developers using ReSharper would get help from the IDE and could easier avoid mistakes like here:

A screenshot demonstrating usage of the [CanBeNull] attribute in C#.
Using the [NotNull] attribute helps notice a mistake. Here, null is not a valid value to pass to the GetStringLength(string s) method, but developer attempted that and gets a warning.

I discovered them a few years ago when they were extensively used in a project I also worked on. At that time, there was no native support for anything similar in C#. I remember some of us always used it, and some of it avoided it and were against introducing some 3rd party library and attributes just to get some new warnings in the IDE. I have seen a high value in it. It became a standard part of my toolkit ever since.

Luckily, since C#8 we can mark types as non-nullable natively. We don’t need ReSharper or attributes from their library.

So, is there an easy, automated way, to migrate from Jetbrains.Annotations [NotNull]/[CanBeNull] usages to the modern Nullable Reference Types? Yes! But if you don’t want to replace each usage individually in the code editor, things needs to be done in the right order.

How to migrate solution using Visual Studio

I assume you start with a project that uses annotations, like here:

A screenshot demonstrating usage of the [NotNull] and [CanBeNull] attribute in C#.
Use the following steps to migrate to native nullable reference types:
  1. Don’t remove reference to the Jetbrains.Annotations package yet! It’s needed for the automated refactoring to work.
  2. Enable Nullable Reference types in the project by adding <Nullable>enable</Nullable> to your *.csproj file, or enable it in the Visual Studio’s UI.
  3. Use the refactoring named “Use type annotation syntax”, as depicted in a screenshot. You can choose the scope of a whole project or solution.
Migrating [NotNull] and [CanBeNull] to Nullable Reference Types in Visual Studio
  1. Now you can remove the reference to JetBrains.Annotations package. Check if the project still compiles. Your project might have usages of other, less popular attributes from the package that might or might not have a replacement in native C# or .NET features at this point – fix them individually.

I hope it helps you save some time πŸ™‚

The post Migrate from Jetbrains.Annotations [NotNull] / [CanBeNull] to C# Nullable Reference Types appeared first on Tim Taurit.

]]>
https://taurit.pl/migrate-jetbrains-annotations-notnull-canbenull-to-nullable-reference-types/feed/ 0
Is it a good practice to treat warnings as errors? https://taurit.pl/treat-warnings-as-errors/ https://taurit.pl/treat-warnings-as-errors/#comments Tue, 09 Aug 2022 16:24:00 +0000 https://taurit.pl/?p=479 Making Visual Studio treat warnings the same way as errors .NET projects have an interesting property that we can set. It’s named TreatWarningsAsErrors. We can set it in *.csproj files: For years, I would say that this is great! The more strict code checking rules, the better, right? Code style, warnings, conventions. And in theory, ... Read more

The post Is it a good practice to treat warnings as errors? appeared first on Tim Taurit.

]]>
Making Visual Studio treat warnings the same way as errors

.NET projects have an interesting property that we can set. It’s named TreatWarningsAsErrors. We can set it in *.csproj files:

<PropertyGroup>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<PropertyGroup>

For years, I would say that this is great! The more strict code checking rules, the better, right? Code style, warnings, conventions. And in theory, there are nice benefits to that.

If we have strict automated rules to check everything, we save hours on code review discussions about tabs, spaces, spaces around braces etc. Automated reviewer will just reject that. No one needs to waste their attention on pointing out breaking conventions. We don’t have to annoy people by picking on minor issues.

Additionally, we get a super-fast feedback loop. With modern Visual Studio and its analyzers, we can see the squiggly red line indicating error a second after we wrote the code. We are incentivized by that red line to instantly fix the issue, because we’re trained to keep the code error-free as we work. Fast feedback loop is, in general, a great thing.

But then I landed in a project where I saw such strict rules applied. And I hated it. And I think I can articulate good arguments for not being too strict, and particularly, not treating warnings as errors.

Why I think that compiler should not treat warnings the same as errors

I’ll start with an example. This is a kind of error I’ve been seeing dozens times a day now:

Example of a minor code style rule violation that is displayed as error when TreatWarningAsErrors property is enabled.
Example of a broken code style rule that, if we treat warnings as errors, will not allow the application to be compiled

Let’s assume that I agree that this code style rule is useful and that the team should not commit such lines to the repository. Should it be an error then? I think it should not, and here are my arguments:

  • Build of the whole application will fail if there are any errors like that. It’s hard to test your code if it doesn’t compile. You can’t run unit tests. You can’t launch application and see if it works.
    The nature of writing code is that before you commit your final solution, you add code. You move it, remove it, change it as you discover problems and so on. Maybe we gain immediate feedback on code style issues, but we loose quick feedback that comes from executing the code frequently and with minimum friction.
  • The IDE will constantly draw your attention to minor issues like “unused using statement”, or “two empty lines after each other”. If the editor shows the same kind of feedback (a red, squiggly line and an entry in the Errors window) for all issues, regardless of severity, you cannot focus on important issues, because all of them become equally important if they prevent the compilation.
  • It’s just a waste of time to fix code style violations in a code that you will likely delete/rewrite in the coming hours.

Should we then just ignore all warnings and code style violations?

But if we don’t prevent developers from introducing new warnings and code style violations as early as possible, how to avoid having their number grow and accumulate as a technical debt? Let my try enumerate our options:

  • On the one side of the spectrum, we can prevent code with minor issues from compiling instantly, right in the IDE like Visual Studio. We can do it with <TreatWarningsAsErrors>true</TreatWarningsAsErrors>, or by increasing severity of analyzer rules to ‘Error’. I already explained why I don’t like this idea.
  • On the other side of the spectrum, we can only check for code issues at the last moment, before we merge code to an integration branch. We can do it in Pull Request’s validation pipeline. Theoretically, you can do it even later, but it would be a really bad quality process.
    I worked in such setups. I found them pretty annoying. Everything in your code seems to be fine in the IDE. Then you create a Pull Request and wait few minutes or few hours for the pipeline to complete. Only then you get feedback that you forgotten to add a comment to a public method. The issue here is a long feedback time between making a mistake and getting feedback about it.
  • Both of those extremes approaches seem to be inefficient. So I think it’s best to find something in between.

In reality, there don’t seem to be that many options in between to hook ourselves with the code validation. This is how I see our choices:

A spectrum of options for when we can hook static code analysis tooling in the software development process.
Providing feedback about issues in code: some options

From that perspective, we could consider a git hook validating the code before each git push operation a good place to stop the bad code from going further. But from my experience, it has drawbacks. It’s hard to enforce setting up git hooks on the team. It might be inconvenient to try build solution on each commit to find out errors. The feedback might not be in a nice form. Maybe there are ways to do it, but I haven’t seen that working well yet.

How to best deal with warnings

I don’t have a definite answer, but taking the above into consideration, I would strive to make a setup where:

  • Errors are errors, and warnings remain warnings.
  • Errors are issues that make it impossible for compiler to produce a binary (library or executable)
  • I would consider selectively increasing severity of some warnings to errors. I think of warnings that normally would not prevent compilation to succeed, but would very likely lead to runtime errors (e.g., conflicts of dependencies versions within the project)
  • Warnings should remain warnings in the IDE. I’d be ok with a zero-warning policy enforced later, in the validation pipeline. Developer would still see orange squiggly line for warnings in the IDE, but would be able to ignore them until she’s ready to publish the Pull Request.
  • Minor issues that don’t break the compilation, introduce no risks to runtime and don’t dramatically decrease other coders’ experience, should have lower severity (e.g. suggestion, hint). I’d allow the team to merged them to the integration branch. Therefore I’d consciously allow them to accumulate over time in the code base. I’d not say that few years ago, but I think this is the rational way to do.

Did this essay triggered some interesting thoughts in you? Please share in the comments whether you agree with me or not πŸ™‚

The post Is it a good practice to treat warnings as errors? appeared first on Tim Taurit.

]]>
https://taurit.pl/treat-warnings-as-errors/feed/ 4
PiHole hosted in the cloud? I recommend NextDNS https://taurit.pl/pihole-in-the-cloud-nextdns/ https://taurit.pl/pihole-in-the-cloud-nextdns/#comments Mon, 08 Aug 2022 15:28:37 +0000 https://taurit.pl/?p=451 Wouldn’t it be great to have your Pi-Hole hosted in the cloud and accessible everywhere? Perhaps you could use its ad blocking on your smartphone. Or at work. Or, when your laptop connects to a Wi-Fi network when you travel. I tried to make PiHole accessible everywhere, but it was tricky. A typical Raspberry Pi ... Read more

The post PiHole hosted in the cloud? I recommend NextDNS appeared first on Tim Taurit.

]]>
Wouldn’t it be great to have your Pi-Hole hosted in the cloud and accessible everywhere? Perhaps you could use its ad blocking on your smartphone. Or at work. Or, when your laptop connects to a Wi-Fi network when you travel.

A screenshot of the Pi-Hole web interface
A screenshot from the Pi-Hole web interface. Source: https://pi-hole.net/

I tried to make PiHole accessible everywhere, but it was tricky. A typical Raspberry Pi sits behind a home router. Exposing it requires effort and has serious drawbacks.

PiHole is not designed to support modern, secure DNS protocols. Sending unencrypted DNS queries using the public internet infrastructure seems very uncomfortable in 2024. I ended up with an alternative solution, and here are my learnings.

TL/DR: if you are looking for a cloud-native PiHole alternative, NextDNS is worth a shot

PiHole’s main objective is to block domains serving advertising. It can also block other domains, like tracking, malware, or simply pages that waste your attention/time.

You can achieve the same with NextDNS. It’s modern, beautiful, and does the job.

Reaching PiHole from a smartphone. What are the options?

PiHole seems designed with the assumption that it sits on your local network. It means that your mobile devices cannot use it when they connect to other networks. For example, if your smartphone uses your carrier’s cellular network, your Raspberry Pi is unreachable to you. In such a situation, you cannot use your customized DNS server, unless you build an even more elaborate setup, like a VPN to your home network.

I got determined to set up a DNS server reachable both from my home and from my smartphone, workplace, or family home. At this point, I could think of three options:

  • Keeping PiHole on a Raspberry Pi and exposing it to the internet (with a public IP or VPN).
  • Hosting PiHole in the cloud, in some VM or container. I considered setting up a cheap Linux VM in Azure.
  • Finding a cloud-native alternative service.

Keeping PiHole on a Raspberry Pi requires exposing it to the public internet or setting up a VPN to a home network. It seemed overly complicated. I would rather not spend time configuring VPNs on all my devices. I don’t like dealing with VPNs, and the friction was enough for me to give up this option.

Hosting PiHole in the cloud, seemed tempting. However, PiHole is designed to serve as a DNS server in a local network. It lacks a security level I would want to have in the cloud. Notably, at least currently, it does not serve data using a secure protocol like DNS-over-HTTPS, DNS-over-TLS, or similar. And sending unencrypted DNS queries over the public internet is something I do not agree to. This problem could be worked around if we expose an additional HTTP server component to handle DoH requests. But with each piece, the setup becomes more and more complicated. Another issue is the cost of hosting. This would require a VM running most of the day. With the current pricing of VMs in the cloud, it would cost at least a few dollars a month.

Finding a cloud-native alternative service turned out to help me escape the pickle. I found NextDNS, and I’m delighted with this service. Please note, that I’m using affiliate links here to link to them (thanks if you use them!). But it is my honest recommendation as a user. I published the first version of this blog post before I thought of any affiliate links.

Discovering NextDNS: an appealing alternative solution

NextDNS has a similar set of features as PiHole, but it’s a software-as-a-service natively hosted in the cloud. You can access your customized DNS server from the public internet. So unlike when you use PiHole, you can set up your Android, iPhone, and laptops to use your own DNS server. And it will work regardless of where are you connecting from.

NextDNS lets you block domains with advertisements, malware, or other stuff you’d rather avoid. It’s super-easy to manage. You just choose to which blocklists you wish to subscribe. You can also create your personal blocklist and allowlist. The service allows you to use all modern secure protocols for querying DNS servers.

A screenshot of the NextDNS. NextDNS is an alternative to Pi-Hole running in the software-as-a-service model.
A screenshot from a web interface (a view corresponding to the view depicted in Pi-Hole’s screenshot above)

So if you like the experience known from Pi-Hole, my recommendation is to give it a try. I think you’ll appreciate how good a piece of software this is. I have used it for a few months now. And, with all the respect to the Pi-Hole that served me for years, I am supper happy about the transition.

The post PiHole hosted in the cloud? I recommend NextDNS appeared first on Tim Taurit.

]]>
https://taurit.pl/pihole-in-the-cloud-nextdns/feed/ 1
Convert C# top-level statements to the old-style Program class https://taurit.pl/convert-top-level-statement-to-class-csharp/ https://taurit.pl/convert-top-level-statement-to-class-csharp/#respond Sun, 07 Aug 2022 13:59:56 +0000 https://taurit.pl/?p=563 The top-level statements are a feature introduced in C# 10. The idea is to reduce the boilerplate code needed to define aplication’s entry point. So instead of the traditional: We can write the code directly in the main project’s file, which reduces the code to just one line: .NET 6 templates use this new, concise ... Read more

The post Convert C# top-level statements to the old-style Program class appeared first on Tim Taurit.

]]>
The top-level statements are a feature introduced in C# 10. The idea is to reduce the boilerplate code needed to define aplication’s entry point. So instead of the traditional:

using System;

namespace Application
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

We can write the code directly in the main project’s file, which reduces the code to just one line:

Console.WriteLine("Hello, World!");

.NET 6 templates use this new, concise form by default. But I find it rather confusing to have one file in a project follow different syntax rules or conventions. I see the traditional way as simpler, even if a bit more verbose. So how to fastest convert between one form and another?

Hopefully, if you have ReSharper 2022.2, there is an automatic conversion available, albeit a bit hard to discover. It seems to appear when you place your cursor on… an empty like in the Program.cs file with top-level statements, and click the hammer icon, or press Alt+Enter:

Converting C# program using top-level statements to a class with main method.
An action available to convert C# top-level statements to a class

When selected, the conversion works just fine:

Entry point to C# program, automatically generated by conversion from top-level statements.
A class automatically created based upon clicking on the “To explicit ‘Program’ class” action

The reverse action, transforming Program.cs class into the compact form, is similarly available. This one is a bit easier to find: it’s in the line where the Program class starts.

The post Convert C# top-level statements to the old-style Program class appeared first on Tim Taurit.

]]>
https://taurit.pl/convert-top-level-statement-to-class-csharp/feed/ 0
Azure DevOps self-hosted agent: use the same OS image as in Microsoft-hosted agents https://taurit.pl/devops-agents-vhd-vhdx-images/ https://taurit.pl/devops-agents-vhd-vhdx-images/#respond Thu, 04 Aug 2022 14:17:18 +0000 https://taurit.pl/?p=296 I’ll share my experience with an attempt to create a self-hosted Azure DevOps agent that would be identical to agents running on the Microsoft-hosted agent pool. At first, I expected that I can just google and find a VHD or VHDX files for DevOps agents. But I couldn’t find any. Why we wanted to create ... Read more

The post Azure DevOps self-hosted agent: use the same OS image as in Microsoft-hosted agents appeared first on Tim Taurit.

]]>
I’ll share my experience with an attempt to create a self-hosted Azure DevOps agent that would be identical to agents running on the Microsoft-hosted agent pool.

At first, I expected that I can just google and find a VHD or VHDX files for DevOps agents. But I couldn’t find any.

Why we wanted to create VM identical to the ones in DevOps

Why go through the hassle of setting up a custom, self-hosted agent that behaves exactly like the one in public pool? There is a difference in the maximum timeouts for DevOps pipeline jobs, which are:

  • 6 hours on Microsoft-hosted agents
  • Forever (no limit) on self-hosted agents

We found ourselves in a position where we created a tool to migrate data that was running as a DevOps pipeline and the code was tightly coupled with it. Too late we realized that it will take few days to complete, effectively ruling out the use of the convenient Microsoft-hosted agent pool.

Ubuntu used to execute your pipelines is a special Ubuntu

Agents used by Azure DevOps are not default Linux distro installations. They come with a lots of additional packages installed. As an example, see the software installed on the ubuntu-22.04 image. If you just set up a new VM and install Ubuntu on it, it will lack many of those tools. And scripts in pipelines might assume those tools are installed.

Error in DevOps pipelines logs: 'Azure CLI is not installed on this machine'
Example of an error that occurred: “Azure CLI is not installed on this machine“. A pipeline that was developed and tested on a Microsoft-hosted agent was ran on a self-hosted agent with plain Ubuntu installation.

Attempt to run exactly the same OS as the Microsoft-hosted agents

Images of systems running on Azure DevOps pools are open source. Or, precisely speaking, only scripts that create those images are open source. Images themselves are not published due to some legal issues.

It means that we would need to build images ourselves. I attempted that, but the scripts died after more than hour with an unclear error (as in the screenshot).

Failed attempt to build VHD images of Microsoft-hosted agents
My attempt to build VHD images of operating system identical to the one used in Microsoft-hosted agents in DevOps. It failed.

After spending a day trying to make this work, I gave up. It was not worth the invested time and we decided to install the required software on a clean Ubuntu VM. It’s just not easy to do. Far from straightforward. Avoid if you can.

One side comment is that if you still want to invest time in it, there is a tool named azuredevops-buildagentsthat promises to automate building VM image in a VHD form in a pipeline. I didn’t try it, as I only needed a single image build, not a pipeline.

Lessons learned

For me, the lessons learned here are that:

  • There is no easy way to run the same VM image on a self-hosted VM as Microsoft, because the image is not published for legal reasons. There seems to be no clean way to set up an agent VM and guarantee 100% compatibility with MS-hosted agent pool.
  • Perhaps the best one can do is to just create a custom VM and try to install all the dependencies that we really use in our pipeline. This, unfortunately, requires testing pipelines on the new agents and fixing the found issues.
  • You can try building VM images from source scripts. But it takes time (hours). And on my machine it just fails with errors that seem beyond my control to fix.
  • Do not design pipelines that need to run more than 6 hours! This is not the right executable environment for such things. Even when we migrated to self-hosted agents which didn’t have the timeout limit, some of the jobs lost connection with DevOps.

If you read to this point, congratulations. And feel free to briefly share in the comments what’s your use case for re-using Microsoft’s agent images. I’m curious what could have brought you here πŸ™‚

The post Azure DevOps self-hosted agent: use the same OS image as in Microsoft-hosted agents appeared first on Tim Taurit.

]]>
https://taurit.pl/devops-agents-vhd-vhdx-images/feed/ 0
Moq: Extension methods (here: LoggerExtensions.LogError) may not be used in setup / verification expressions. https://taurit.pl/moq-extension-methods-may-not-be-used-in-setup-verification-expressions/ https://taurit.pl/moq-extension-methods-may-not-be-used-in-setup-verification-expressions/#comments Wed, 03 Aug 2022 18:27:29 +0000 https://taurit.pl/?p=471 Sometimes when we develop unit tests it’s convenient to check if the tested code wrote something to the log. Is it a good practice to assert that some particular message appeared in the logs? I think there are pros and cons. On the pros side, we can make general assertions like “no errors were written ... Read more

The post Moq: Extension methods (here: LoggerExtensions.LogError) may not be used in setup / verification expressions. appeared first on Tim Taurit.

]]>
Sometimes when we develop unit tests it’s convenient to check if the tested code wrote something to the log.

Is it a good practice to assert that some particular message appeared in the logs? I think there are pros and cons. On the pros side, we can make general assertions like “no errors were written to the log”. Testing for error messages can also assure us that a specific edge case was handled. On the con side, it usually feels flaky. This is because log messages tend to change over time. This might require us to update tests every time a log message was improved a bit.

But if we decide to write assertions based on logs, then it’s time to code πŸ™‚ Since the release of .NET Core, the standard logging mechanism is ILogger which comes with the platform. And the most popular library for mocking I encountered in projects is Moq.

When we try to mock ILogger, however, we might find ourselves dealing with an error like:

System.NotSupportedException : Unsupported expression: x => x.LogError(It.IsAny<string>(), new[] {  })
Extension methods (here: LoggerExtensions.LogError) may not be used in setup / verification expressions.
   at Moq.Guard.IsOverridable(MethodInfo method, Expression expression)
   (...)
   at Moq.Mock`1.Verify(Expression`1 expression, Func`1 times)
   at MyProject.UnitTests.When_Something_Expect_Something()

This is because logging implemented in .NET heavily relies on extension methods.

Solution and a working example: before .net 8

Since we cannot mock extension methods, I’d accept the tradeoff where we execute the real extension methods, but mock the object they are operating on. Here’s a working example:

using Microsoft.Extensions.Logging;
using Moq;

namespace MyTestProject;

public class SystemUnderTest
{
    public SystemUnderTest(ILogger logger)
    {
        logger.LogError("Some log message");
    }
}

public class Tests // assuming NUnit is used for unit testing
{
    // Use with most recent version of Moq! With older versions, you might see unexpected behaviors.
    [Test]
    public void When_ObjectIsConstructed_Expect_NoErrorsLogged()
    {
        // Arrange
        var loggerMock = new Mock<ILogger>(MockBehavior.Strict);
        loggerMock.Setup(x => x.Log(
                LogLevel.Error,
                It.IsAny<EventId>(),
                It.IsAny<It.IsAnyType>(),
                It.IsAny<Exception>(),
                It.IsAny<Func<It.IsAnyType, Exception?, string>>()
            )
        );

        // Act
        var sut = new SystemUnderTest(loggerMock.Object);

        // Assert
        // We can inspect invocations and get logged messages
        var loggedErrorMessages = loggerMock.Invocations
            .Where(x => (LogLevel)x.Arguments[0] == LogLevel.Error)
            .Select(x => x.Arguments[2].ToString());

        // ... or verify that a method was/was not called
        loggerMock.Verify(
            m => m.Log(
                LogLevel.Error,
                It.IsAny<EventId>(),
                It.IsAny<It.IsAnyType>(),
                It.IsAny<Exception>(),
                It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
            Times.Never,
            "Expected no errors would be written to ILogger, but found some"
        );
    }
}

Here you can find a minimal C# project with the above code snippet that allows you to see that it compiles and works.

If you want to look for alternative solutions, here’s a good blog post that shows more options on how to unit test ILogger<T>.

Solution and a working example: since .net 8

.NET 8 simplifies things a lot by introducing the FakeLogger class. Here’s a simplistic demo showing how you can use it to test what has been logged in much fewer lines of code than before:

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;

namespace MockILoggerWithMoq.Tests;

[TestClass]
public class Tests
{
    [TestMethod]
    public void When_ObjectIsConstructed_Expect_NoErrorsLogged()
    {
        // Arrange
        var logger = new FakeLogger<SystemUnderTest>();

        // Act
        var sut = new SystemUnderTest(logger);

        // Assert
        Assert.IsFalse(logger.Collector.GetSnapshot().Any(e => e.Level == LogLevel.Error),
            "Expected no errors would be written to ILogger, but found some.");
    }
}

Have fun!

The post Moq: Extension methods (here: LoggerExtensions.LogError) may not be used in setup / verification expressions. appeared first on Tim Taurit.

]]>
https://taurit.pl/moq-extension-methods-may-not-be-used-in-setup-verification-expressions/feed/ 3
VS Code: settings synchronization doesn’t work https://taurit.pl/vs-code-settings-synchronization-problem/ https://taurit.pl/vs-code-settings-synchronization-problem/#comments Tue, 02 Aug 2022 17:10:40 +0000 https://taurit.pl/?p=402 I have recently observed that my Visual Studio Code synchronization no longer seemed to work. The configuration which I carefully changed at home was still not synced on my workplace machine. If you have a similar problem and found this blogpost in Google, read on πŸ™‚ Diagnosing it turned out to be easy once you ... Read more

The post VS Code: settings synchronization doesn’t work appeared first on Tim Taurit.

]]>
I have recently observed that my Visual Studio Code synchronization no longer seemed to work. The configuration which I carefully changed at home was still not synced on my workplace machine. If you have a similar problem and found this blogpost in Google, read on πŸ™‚

Diagnosing it turned out to be easy once you know where to look πŸ˜€ So where too look? In my case, the hint came from launching “Settings Sync: Show Log” command:

Visual Studio Code showing logs from the settings synchronization operation.
Visual Studio Code: finding logs for the settings synchronization issue

The error message I then saw in the console hinted me about the problem:

[2022-06-04 14:08:57.389] [settingssync] [info] Using settings sync service https://vscode-sync.trafficmanager.net/
[2022-06-04 14:08:57.389] [settingssync] [info] Auto Sync is enabled.
[2022-06-04 14:08:57.389] [settingssync] [info] Auto Sync: Suspended until auth token is available.

It turns out, I got logged off from VS code and didn’t notice it. The solution was therefore easy: sign in again, by clicking at the icon highlighted in green. Maybe it’s as easy for you too?

Good luck!

The post VS Code: settings synchronization doesn’t work appeared first on Tim Taurit.

]]>
https://taurit.pl/vs-code-settings-synchronization-problem/feed/ 1
Can Yubikey replace Authy? https://taurit.pl/can-yubikey-replace-authy/ https://taurit.pl/can-yubikey-replace-authy/#respond Mon, 01 Aug 2022 16:55:10 +0000 https://taurit.pl/?p=476 One of the common ways to do two-factor Authentication is to use an authenticator app like Authy, Microsoft Authenticator, 1Password or LastPass Authenticator. Authy manages one-time passwords The common denominator of Authy and other apps mentioned above is that they generate time-based one-time passwords (TOTP). They are usually set up like in the screenshot below. ... Read more

The post Can Yubikey replace Authy? appeared first on Tim Taurit.

]]>
One of the common ways to do two-factor Authentication is to use an authenticator app like Authy, Microsoft Authenticator, 1Password or LastPass Authenticator.

Authy manages one-time passwords

The common denominator of Authy and other apps mentioned above is that they generate time-based one-time passwords (TOTP). They are usually set up like in the screenshot below. You have probably seen it many times:

A screenshot showing the typical screen for setting up two-factor authentication. Here, GitHub service uses the TOTP method with characteristic QR codes.
An example of a page helping set up 2FA. The screenshot is from GitHub, which supports time-based one-time passwords as a 2FA method.

This is a good and secure solution. It appears that it’s also the most popular 2FA method in the sites I visit. Personally, I have such 2FA method configured for 27 services I use! However, I don’t know how about you, but it also generates an annoyance and friction when I need to reach for the phone, find the app, and re-type the 6-digit code to the website πŸ˜‰

YubiKey device is a bit different

YubiKey device is a bit different. You have probably bought it, or intend to buy, for a very convenient and secure single-touch login. Nowhere in the process you need to type 6-digit codes.

This is because underneath it’s an entirely different protocol called WebAuthn, and not TOTP described previously. A website might support WebAuthn, TOTP, or none, or both.

A typical two-step verification prompt on a website supporting WebAuthn protocol
Example of a WebAuthn prompt. Note, that here, the website is not asking for a 6-digit code, but pressing a button. But this is because it’s an entirely different protocol!

Unfortunately, this means that we cannot simply use YubiKey’s single push of a button wherever we used 6-digit codes. The site must implement the WebAuthn protocol to allow us to use that more convenient method of authentication. Many major sites like Google, Microsoft, GitHub, or Facebook do. But presently, it’s still less common than TOTP and you are unlikely to eliminate the use of time-based codes from your life completely.

See the following Stack Overflow post by Nate Eldredge if you want to understand why YubiKey cannot do some magic and just handle TOTP when you touch the device. The YubiKey device does not have a real-time clock on board, so it cannot generate time-based codes. Moreover, without any software assistance, it wouldn’t know which site is asking for the code and what token to generate.

Yubico Authentication App is yet another thing

It might be a bit confusing now to add that there is an application called Yubico Authentication App, released by the same company that creates YubiKeys.

It makes use of another feature of the YubiKey hardware. Credentials required to generate 2FA codes can be stored on YubiKey device instead of on mobile phone, and this way they are less likely to be compromised. You might take a look at it if you are interested in increased security. But don’t expect any increased convenience – you’ll still have to type the codes πŸ˜‰

Yubikey 5 Nano firmly sitting in a USB-A port

Even though YubiKey 5 isn’t a magic tool that will instantly eliminate time-based one-time passwords from our life, I think it is a wonderful device. Hopefully, the support for the WebAuthn protocol will steadily grow and the future of 2FA will be marked by both security and convenience it provides. If you care about security on the internet and value your convenience, it’s certainly worth to have one (or more) πŸ™‚

References and related posts

The post Can Yubikey replace Authy? appeared first on Tim Taurit.

]]>
https://taurit.pl/can-yubikey-replace-authy/feed/ 0
Error CS0618: ‘ConsoleLogger’ is obsolete https://taurit.pl/cs0618-consolelogger-is-obsolete/ https://taurit.pl/cs0618-consolelogger-is-obsolete/#respond Sat, 30 Jul 2022 16:03:20 +0000 https://taurit.pl/?p=490 This is a short note showing my fix to an error I encountered when migrating application from .NET Framework to the most recent (at the moment) .NET 6. I’m just pasting the rough code example of how this can be migrated, because: Custom console logger using old Microsoft.Extensions.Logging.Console 2.1.1 Equivalent custom console logger using newer Microsoft.Extensions.Logging.Console 6.0.0 ... Read more

The post Error CS0618: ‘ConsoleLogger’ is obsolete appeared first on Tim Taurit.

]]>
This is a short note showing my fix to an error I encountered when migrating application from .NET Framework to the most recent (at the moment) .NET 6.

##[error]src\UnitTests.Common\SomeFileName.cs(66,34): Error CS0618: 'ConsoleLogger' is obsolete: 'This type is obsolete and will be removed in a future version. The recommended alternative is ConsoleLoggerProvider.'

I’m just pasting the rough code example of how this can be migrated, because:

  • I couldn’t find it anywhere in the Internet
  • I spent some boring hours on it and I’d rather never do it again

Custom console logger using old Microsoft.Extensions.Logging.Console 2.1.1

// Usage example:
// ILogger logger = new MyCustomLogger(categoryName, (s, level) => true, true);

private class MyCustomLogger : ConsoleLogger
{
    public MyCustomLogger(string name, Func<string, LogLevel, bool> filter, bool includeScopes) : base(name, filter, includeScopes) { }
    public MyCustomLogger(string name, Func<string, LogLevel, bool> filter, IExternalScopeProvider scopeProvider) : base(name, filter, scopeProvider) { }

    public override void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception)
    {
        var result = $"{GetType().Assembly.GetName().Name} {logLevel} {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{Thread.CurrentThread.ManagedThreadId}]{message}";
        base.WriteMessage(logLevel, logName, eventId, result, exception);
    }
}

Equivalent custom console logger using newer Microsoft.Extensions.Logging.Console 6.0.0

Today, the ConsoleLogger is obsolete and is now just an internal class, so we cannot use its constructor.

// Usage example:
// In .NET Framework, creating custom ILogger implementation without using obsolete methods is a bit more verbose, probably no way around it: https://stackoverflow.com/q/53690820/889779
//
// const string myCustomLoggerFormatterName = "CustomTestLoggerFormatter";
// var configureNamedOptions = new ConfigureNamedOptions<ConsoleLoggerOptions>("", options => options.FormatterName = myCustomLoggerFormatterName);
// var optionsFactory = new OptionsFactory<ConsoleLoggerOptions>(new []{ configureNamedOptions }, Enumerable.Empty<IPostConfigureOptions<ConsoleLoggerOptions>>());
// var optionsMonitor = new OptionsMonitor<ConsoleLoggerOptions>(optionsFactory, Enumerable.Empty<IOptionsChangeTokenSource<ConsoleLoggerOptions>>(), new OptionsCache<ConsoleLoggerOptions>());
// var loggerFilterOptions = new LoggerFilterOptions { MinLevel = LogLevel.Debug };
// var consoleLoggerProvider = new ConsoleLoggerProvider(optionsMonitor, new List<ConsoleFormatter>()
// {
//     new MyCustomLoggerFormatter(myCustomLoggerFormatterName)
// });
// var loggerFactory = new LoggerFactory(new[] {consoleLoggerProvider}, loggerFilterOptions);
// ILogger logger = loggerFactory.CreateLogger(categoryName);

class MyCustomLoggerFormatter : ConsoleFormatter
{
    public MyCustomLoggerFormatter(string name) : base(name) { }
    public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter)
    {
        textWriter.WriteLine($"{GetType().Assembly.GetName().Name} {logEntry.LogLevel} {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{Thread.CurrentThread.ManagedThreadId}]{logEntry.State}"); 
        if (logEntry.Exception != null)
        {
            textWriter.WriteLine(logEntry.Exception);
        }
    }
}

Not very pretty, but I understand it’s now a lower-level class to build log formatters upon it and it’s not frequently needed to build a custom one.

If you don’t need to have too many customizations, maybe one of the pre-defined formatters like Simple Console Logger will be enough and you don’t need to write a custom one at all?

The post Error CS0618: ‘ConsoleLogger’ is obsolete appeared first on Tim Taurit.

]]>
https://taurit.pl/cs0618-consolelogger-is-obsolete/feed/ 0
.NET: specify constraints for NuGet package versions https://taurit.pl/nuget-package-version-constraints/ https://taurit.pl/nuget-package-version-constraints/#respond Sun, 05 Jun 2022 13:02:33 +0000 https://taurit.pl/?p=405 A typical reference to a NuGet package in your project might look like In the above example, we can see that we reference a package Newtonsoft.Json in a specific version, 11.0.1. We don’t specify any constraints for a person or a tool that would like to update the version in reference. Now, let’s take a ... Read more

The post .NET: specify constraints for NuGet package versions appeared first on Tim Taurit.

]]>
A typical reference to a NuGet package in your project might look like

<PackageReference Include="Newtonsoft.Json" Version="11.0.1" />

In the above example, we can see that we reference a package Newtonsoft.Json in a specific version, 11.0.1. We don’t specify any constraints for a person or a tool that would like to update the version in reference.

Now, let’s take a look how an IDE like Visual Studio will behave when we check if there are any updates to this package. As I write this, the NuGet server knows of the following versions of Newtonsoft.Json:

Exploring a list of all published package versions in NuGet.org service.

Default behavior: Visual Studio offers update to the latest stable package

Now, when a developer opens the β€œManage NuGet Packages” window in Visual Studio and looks at outdated packages, she will be offered an upgrade to the most recent stable version:

Developer experience in Visual Studio 2022: the Manage NuGet packages window when constraints are not defined
Developer experience in Visual Studio 2022: the Manage NuGet packages window when we didn’t define version constraints

The policy to stay at the most recent stable version might be a good default. But there are situations when updating to the most recent version without thinking (whether performed manually, or with automated tooling like Dependabot) can result in problems.

Adding NuGet version constraints

There are situations, where we might not want to update to the most recent version. Some examples I encountered:

Example 1: in the .NET ecosystem, the major version of packages named Microsoft.Extensions.* should typically match the version of .NET. Otherwise, you risk runtime errors.

Example 2: In projects in the maintenance mode, you might only want to update with the security patches, but avoid including any new features or including breaking changes to minimize the risk and maintenance work. So, we might want to only bump up the {patch} part in a {major}.{minor}.{patch} version.

Now, we could try to just remember about such rules, but this is a poor idea. Another person in the team might not know about the convention. We might forget, and Visual Studio will suggest us wrongly. Automatic tools like the mentioned Dependabot will not care about such convention unless we are explicit in defining constraints.

Luckily, the <PackageReference> allows defining constrains. For example, the following definition will only allow bumping the package to the most recent 12.*.* version because the end of the range is at 13.0.0 (exclusive):

<PackageReference Include="Newtonsoft.Json" Version="[11.0.1, 13.0.0)" />

Visual Studio respects such constraints:

Developer's experience in Visual Studio when NuGet version constraints are defined for the package
Developer’s experience in Visual Studio after we defined NuGet version constraints for the package

Other IDEs and tools for bumping dependency versions should also respect that, as it is a part of the specification. See the linked document for more examples. And have fun bumping the packages! πŸ™‚

The post .NET: specify constraints for NuGet package versions appeared first on Tim Taurit.

]]>
https://taurit.pl/nuget-package-version-constraints/feed/ 0
Structured Logging in Azure Application Insights https://taurit.pl/structured-logging-in-azure-application-insights/ https://taurit.pl/structured-logging-in-azure-application-insights/#respond Sat, 04 Jun 2022 18:44:47 +0000 https://taurit.pl/?p=345 Which of the following lines, in your opinion, is a better choice for logging a trace to Azure App Insights? I must admit that for a long time, I used the string interpolation (option A). It is concise, has no characters that would be redundant, and does the job. But it turns out, that the ... Read more

The post Structured Logging in Azure Application Insights appeared first on Tim Taurit.

]]>
Which of the following lines, in your opinion, is a better choice for logging a trace to Azure App Insights?

var guid = Guid.NewGuid();

// Option A
log.LogInformation($"[{guid}] Start");

// Option B
log.LogInformation("[{Guid}] Start", guid); 

I must admit that for a long time, I used the string interpolation (option A). It is concise, has no characters that would be redundant, and does the job.

But it turns out, that the option B seems to have more benefits.

Reason 1: structured logging

If we pass parameters as separate arguments, they will be conveniently available as separate properties in the logged item:

A screenshot from Application Insight Logs UI in the Azure Portal. It shows a log entry when structured logging is used in the C# code.

The application also logs the template of the log message as an independent property. It might help us a lot in writing kusto queries to retrieve the data. Queries are much simpler and more performant if we can just filter and group data by individual properties. If we don’t have such data in individual properties, we would need to parse them out from the message string. It adds complexity to queries.

Reason 2: less memory pressure

The other benefit I saw mentioned in few places is that the string interpolation (the option A) might result in more memory allocations. The impact can be significant if we have many log points. Why? The application will have to allocate memory for the combined strings, so the logger implementation gets a single string passed as an argument. But if that string will never be needed due to the later filtering by LogLevel, this is a wasted work.

The second option doesn’t have this problem because the logging library gets the template and parameters separately. Then, the library can defer combining the values into one string until it’s certain it will need it.

Tooling support

Despite those two advantages, it might look like a step back from a nice syntax provided by interpolated strings. I remember how easy it was to forget passing the correct number of arguments when we had to pass them separately from the formatting string.

Fortunately, the modern Visual Studio and ReSharper easily catch such mistakes:

A screenshot showing a fragment of C# code using structured logging. Visual Studio highlights typical errors.
Developer experience in Visual Studio 2022 with ReSharper

The post Structured Logging in Azure Application Insights appeared first on Tim Taurit.

]]>
https://taurit.pl/structured-logging-in-azure-application-insights/feed/ 0
How to suppress issues in C# code https://taurit.pl/suppress-issues-in-csharp-code/ https://taurit.pl/suppress-issues-in-csharp-code/#comments Sat, 04 Jun 2022 17:50:50 +0000 https://taurit.pl/?p=255 I like to maintain a zero-warning policy in a project. In the case of false-alarm warnings, I think it’s better to suppress them than to leave them active. When we suppress a warning, it will not draw our attention every time we look at logs. So, how can we suppress a warning like the example ... Read more

The post How to suppress issues in C# code appeared first on Tim Taurit.

]]>
I like to maintain a zero-warning policy in a project. In the case of false-alarm warnings, I think it’s better to suppress them than to leave them active. When we suppress a warning, it will not draw our attention every time we look at logs.

So, how can we suppress a warning like the example one below?

An example of a false-alarm warning that we might want to suppress

Option 1: pragma directives

Visual Studio suggests that method by default. I find it ugly because it decreases the readability of the code. Especially that in the default code style rules, the code has no indentation before the pragma directive.

    [TestMethod]
#pragma warning disable S2699 // Tests should include assertions
    public void WhenMyClassIsInstantiatedExpectNoException()
#pragma warning restore S2699 // Tests should include assertions
    {
        // Arrange, Act
        new MyClass();

        // Assertion is implicit. No exceptions means that the test passed.
    }

Option 2: the SuppressMessage attribute

I believe that the System.Diagnostics.CodeAnalysis.SuppressMessage attribute leads to much more elegant code. It doesn’t decrease readability that much when we scan the code with our eyes:

    [TestMethod]
    [SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions",
        Justification = "Assertion is implicit. No exceptions means that the test passed.")]
    public void WhenMyClassIsInstantiatedExpectNoException()
    {
        new MyClass();
    }

The only piece of that attribute which is really required is the second argument, with the code of a suppressed warning. This abbreviated form would technically work equally well:

 [SuppressMessage("Sonar", "S2699")]

Option 3: exclusions in *.csproj

If we want to globally disable some warnings in the scope of the entire project, the *.csproj file is an excellent place to do so. We can specify multiple warnings, separating them with a comma or a semicolon.

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

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <NoWarn>S2699</NoWarn>
  </PropertyGroup>

  <!-- ... the rest of the file -->
</Project>

Note: Using Directory.Build.props for project-wide configuration

For cases where you want to apply suppression across multiple projects in a solution, you could use a Directory.Build.props file. This allows you to centralize standard configurations, such as suppressing warnings:

<Project>
  <PropertyGroup>
    <!-- Don't warn about the need for .ConfigureAwait(false) CA2007 in console app projects -->
    <NoWarn>$(NoWarn);CA2007</NoWarn>
  </PropertyGroup>
</Project>

This approach is particularly useful for solutions with consistent warning suppression policies across multiple projects.

Option 4: the .editorconfig file

The severity of the warnings, including the ones from the custom code analyzers like SonarAnalyzer, can also be configured in the .editorconfig file. Visual Studio 2022 even has a nice UI for that:

Under the hood it’s still just a plain text file, and the setting translates to the following fragment in the .editorconfig file:

[*.cs]
dotnet_diagnostic.S2699.severity = none

Option 5: a suppression file

A list of exceptions can also be centrally managed in a suppression file. I don’t see many advantages to this method. From my experience, in the projects it often grows into a pile of unmaintained rubbish. There is no natural incentive to ever delete anything from this file. It’s usually generated by the IDE and has some custom conventions for Target that we might not understand at first glance.

Have I missed any other option worth mentioning? πŸ˜‰

The post How to suppress issues in C# code appeared first on Tim Taurit.

]]>
https://taurit.pl/suppress-issues-in-csharp-code/feed/ 4
Cosmos DB: how to query a specific region? https://taurit.pl/cosmos-db-query-specific-region/ https://taurit.pl/cosmos-db-query-specific-region/#respond Mon, 13 Dec 2021 21:14:57 +0000 https://taurit.pl/?p=315 Cosmos DB can be configured with a Geo-Redundancy feature enabled. In such situation, it is supposed to replicate data to multiple regions. We should also be able to read from all of those regions. In this article, you will learn how to query a specific region of an Azure Cosmos DB database. In the Azure ... Read more

The post Cosmos DB: how to query a specific region? appeared first on Tim Taurit.

]]>
Cosmos DB can be configured with a Geo-Redundancy feature enabled. In such situation, it is supposed to replicate data to multiple regions. We should also be able to read from all of those regions. In this article, you will learn how to query a specific region of an Azure Cosmos DB database.

In the Azure Portal, the geo-redundancy feature comes with a nice visualization in a form of a map:

The β€œReplicate data globally” pane in Azure Portal. It shows which regions are selected as Write Regions and Read Regions in a Cosmos DB instance.
The feature to “Replicate data globally” in Azure Portal’s UI for Cosmos DB

Looking at the above map, it appears that everything works. But I was asked for a short demo to prove that we can really query any of the regions with the expectation that we’ll receive the same results from all of them. Sounds easy, but was it really?

Surprisingly, not so easy because the following methods did not work:

  • Query specific region from the Azure Portal (from the Data Explorer pane).
    That didn’t work because there appears to be no UI element that would allow us to choose which region to query. Furthermore, after the service executes the query, there is no post-factum information about which region was queried.
  • Query specific region from Azure Storage Explorer.
    Similarly, no support in UI for such query. The UI displays the primary region as a hint, but it doesn’t display other regions nor allow querying them.

So, what method would allow us to demo that data is available in other regions?

  • Query specific region using Cosmos DB SDK. This method allowed me to query the specific region I wanted. You will find code sample is in the next section.
  • Query specific region via REST API. If we were able to issue the query via SDK, it must also be possible via REST API. I didn’t dig into that option because authorizing a REST request to Cosmos API is a challenge itself, and using SDK seems simpler.

Querying specific region from Cosmos DB SDK

So, the most practical method to demonstrate the feature that I found was with the use of Cosmos DB SDK.

Let me do a shortcut here and just provide the demo code with the key line highlighted.

// Note: project should reference Microsoft.Azure.Cosmos 3.23.0 or newer

using System;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;

namespace CosmosDbQuerySpecificRegion
{
    internal class Program
    {
        private static async Task Main()
        {
            await TryQuerySpecificRegion(Regions.WestEurope); // will contact WestEurope
            await TryQuerySpecificRegion(Regions.NorthEurope); // will contact NorthEurope
            await TryQuerySpecificRegion(Regions.AustraliaCentral); // will contact WestEurope (the default, because we don't replicate to Australia)
        }

        private static async Task TryQuerySpecificRegion(string region)
        {
            var cosmosEndpointUrl = "https://CENSORED.documents.azure.com:443/";
            var cosmosPrimaryKey = "CENSORED==";
            var cosmosClient = new CosmosClient(cosmosEndpointUrl, cosmosPrimaryKey, new CosmosClientOptions
            {
                ApplicationRegion = region // here's where we specify the region!
            });

            var container = cosmosClient.GetContainer("MyDatabaseId", "MyContainerId");
            var response = await container.GetItemQueryIterator<MyDocument>("SELECT * FROM c").ReadNextAsync();
            var regions = response.Diagnostics.GetContactedRegions();

            Console.WriteLine($"Attempted to contact region '{region}'. Actually contacted: {string.Join(", ", regions)}");
            Console.WriteLine($"Your query returned {response.Count} records"); // all regions are expected to return the same data!
            Console.WriteLine("");
        }
    }

    internal record MyDocument(string Id, string UserId);
}

The above program returns the following output when we run it against the database with 2 regions (i.e., WestEurope and NorthEurope):

The result of querying specific Cosmos DB regions for data. The result includes cases when region has the data and when it doesn't have it.
Output of a demo program

When we attempted to query a region, where I have not configured any database instance (i.e., Australia Central), we see that query doesn’t fail with an exception but is run against the primary region and succeeds anyway.

Thanks for stopping by!

The post Cosmos DB: how to query a specific region? appeared first on Tim Taurit.

]]>
https://taurit.pl/cosmos-db-query-specific-region/feed/ 0
Data Lake: filtering JSONs on the server side https://taurit.pl/data-lake-filter-jsons-server-side/ https://taurit.pl/data-lake-filter-jsons-server-side/#respond Mon, 13 Dec 2021 20:49:57 +0000 https://taurit.pl/?p=179 What is Query Acceleration Query acceleration is a feature in Azure Blob Storage that can be handy if you use Data Lake (or general Blob Storage) to keep your data. The feature allows server-side filtering of data, assuming it’s in one of the supported formats, like JSON or CSV. It can result in limiting the ... Read more

The post Data Lake: filtering JSONs on the server side appeared first on Tim Taurit.

]]>
What is Query Acceleration

Query acceleration is a feature in Azure Blob Storage that can be handy if you use Data Lake (or general Blob Storage) to keep your data.

The feature allows server-side filtering of data, assuming it’s in one of the supported formats, like JSON or CSV. It can result in limiting the data transferred between Data Lake Storage and a client application that reads it. This diagram from β€œAzure Data Lake Storage query acceleration” explains in well (see the whole document for high-level feature’s overview:

Diagram showing the basic concept of query acceleration. The client application queries the Azure Data Lake Storage for a specific subset of data. Filtering happens on the server side.
The basic concept of query acceleration. Source: Azure Data Lake Storage query acceleration.

Example: filtering data in a “JSON lines” file in Data Lake

In one of our company projects, we store our data in large Data Lake files that contain one JSON document in each line. Some people call this file format β€œJSON lines”. Here is an example of a file in that format:

// people.jsonl: an example demo file in Data Lake Storage Gen2 (or Azure Blob Storage in general)

{ "Name": "Christopher", "Address": { "City": "Cleveland", "State": "Ohio" } }
{ "Name": "Lowell", "Address": { "City": "Nashville", "State": "Tennessee" } }
{ "Name": "Adam", "Address": { "City": "Sunray", "State": "Texas" } }
{ "Name": "David", "Address": { "City": "Austin", "State": "Texas" } }

The benefit of storing multiple JSON documents in a single file comes mainly from the fact that systems like Data Lake are optimized to handle large files, rather than numerous small files. For example, the documentation recommends: β€œorganize your data into larger sized files for better performance (256 MB to 100 GB in size)”.

But what if we have a use case where we only need to read few specific lines from such a huge 100-gigabyte document? Downloading the whole file would be slow and cost-ineffective. But query acceleration comes to the rescue!

Here’s a code sample that allows to query the above document and only download records with Address.State = "Texas":

// this project references NuGet package: Azure.Storage.Blobs, 12.8.0
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;

var connectionString = "DefaultEndpointsProtocol=https;AccountName=CENSORED;AccountKey=CENSORED;EndpointSuffix=core.windows.net";
var blobContainerName = "mycontainer";
var blobName = "people.jsonl";

BlockBlobClient client = new BlockBlobClient(connectionString, blobContainerName, blobName);
var options = new BlobQueryOptions()
{
    InputTextConfiguration = new BlobQueryJsonTextOptions(),
    OutputTextConfiguration = new BlobQueryJsonTextOptions()
};
var queryText = "SELECT * FROM BlobStorage WHERE Address.State = 'Texas'";
var response = client.Query(queryText, options);

var responseContent = await new StreamReader(response.Value.Content).ReadToEndAsync();
Console.WriteLine($"The following query:\n");
Console.WriteLine($"    {queryText}\n");
Console.WriteLine($".. produced the result:\n");
Console.WriteLine(responseContent);

The result is:

The result of the Query Acceleration demo program. Server returned only the data that match the filter.

My observations

Some observations before I conclude:

  • When I tested this feature, it worked nicely in a slightly outdated version of Azure.Storage.Blobs NuGet package, 12.8.0. The most recent version, 12.10.0, gives me an unclear Azure.RequestFailedException: 'XML specified is not syntactically valid. Status: 400. Maybe my timing to learn about this was unlucky, but maybe this feature is not a first class citizen and doesn’t get enough attention? I don’t know.
  • While this is marketed as a feature of Data Lake, this naming is misleading. It’s part of the Blob Storage API. It’s available in blob storage even if we don’t enable the Data Lake hierarchical namespace feature. For the same reason, it’s only available in Data Lake Gen2 which is based on Blob Storage, and not in older Data Lake Gen1.
  • Documentation warns that accelerated queries come with additional cost. But if your use case fits the scenario, it should generally be cheaper and faster. In the end, that’s the whole point.

Thanks for stopping by!

The post Data Lake: filtering JSONs on the server side appeared first on Tim Taurit.

]]>
https://taurit.pl/data-lake-filter-jsons-server-side/feed/ 0
SonarCloud in a .NET/C# project: is it worth it? https://taurit.pl/sonarcloud-dotnet-my-opinion/ https://taurit.pl/sonarcloud-dotnet-my-opinion/#respond Sun, 31 Oct 2021 20:27:53 +0000 https://taurit.pl/?p=186 A few weeks ago, we started using SonarCloud in some of our .NET projects. We wanted to evaluate if this continuous code quality tool is something that would help us in our daily work, or rather annoy developers with loads of minor issues and false alarms. We already have ReSharper licenses, so using another tool ... Read more

The post SonarCloud in a .NET/C# project: is it worth it? appeared first on Tim Taurit.

]]>
A few weeks ago, we started using SonarCloud in some of our .NET projects. We wanted to evaluate if this continuous code quality tool is something that would help us in our daily work, or rather annoy developers with loads of minor issues and false alarms. We already have ReSharper licenses, so using another tool only seemed to make sense if it provided additional value.

How are different SonarProducts related

Before I share my experiences, let me clarify to which products I will refer to. You might be familiar with some other products from the SonarSource company, and this is how they are related:

  • SonarAnalyzer.CSharp. This is a set of code analyzers delivered via a NuGet package. You can install them in any .NET project and use for free, even without an extension or paid subscription for any other products.
  • SonarLint. This is a free extension for IDE’s like Visual Studio (I only used this one), IntelliJ, Eclipse, and VS Code.
  • SonarCloud. This is an extended web dashboard that allows to overview quality of our projects, review issues, or track the quality of code base in time. For projects that are not open-sourced, this product requires paying for the license.
  • SonarQube. This is an on-premise counterpart for SonarCloud. On the surface they look similar, but there are various differences in functionality.

My experiences with SonarAnalyzer

When you add the SonarAnalyzer NuGet package to your C# projects, you’ll very likely observe some new warnings:

Example of additional warnings enabled by adding SonarAnalyzer to a C# project.

I’ve spent a few hours going through such new warnings in various projects. In my opinion, they are pretty good. They range from simple checks of good coding standards to interesting and more advanced findings. Some examples of the latter would be:

  • β€œWhy do you have two unit tests with different names but identical implementation?” (helped me find few copy-paste errors)
  • β€œIf you use yield return, you risk less if you validate parameters in a separate method” (I found it quite advanced deduction)
  • β€œYou named argument differently in an interface and an implementation. Are they really the same thing?” (common source of errors if your method accepts multiple arguments of the same type)

All the rules have wonderful documentation with clear explanation of the problem, and code samples showing good and bad code (see an example).

On the drawbacks side: currently, there are no β€œquick automatic fix” actions for the suggestions, even though they would be possible for some of them. You need to fix the code yourself.

If you have any free CPU cycles to use, I believe this analyzer is a really valuable addition to the Visual Studio experience, even if you already use ReSharper with its awesome hints. You might disagree with me, but I think that such warning-driven development is also fun. You can learn a lot about code patterns, especially when you learn on the mistakes you yourself made.

SonarLint: what’s the added value of an IDE extension?

To use the code analysis feature, you do not need the SonarLint extension. Then why would you install it?

Well, SonarLint allows connecting your Visual Studio to SonarCloud or SonarQube portal. You might want to work in a setup, where the source of truth about how to check code is not your git repository, but the SonarCloud/SonarQube service.

This can make sense in several scenarios. In your organization, you might have multiple project in multiple code repositories. What if you want to have the same set of code analysis rules for all of them? This is where SonarLint connected mode comes in handy. It monitors changes in configuration online and applies them to your project when needed.

We were able to successfully set up this feature, but I haven’t spent much time playing with it, so I’ll end the description here.

SonarCloud: the web portal

The web portal is a place where you can most easily see quality metrics of your project(s):

Screenshot of SonarCloud. SonarCloud is a web-based dashboard showing various code quality metrics collected by SonarAnalyzer.

You can start with a high-level overview, like in the screenshot above. But you can also go deep, inspecting all the individual issues you would also see in Visual Studio.

This overview goes a bit beyond what you would see in Visual Studio’s errors/warnings window. In the web portal, you can also see code coverage. You can see how much code is duplicated in your solutions. You can see how quality metrics changed in time.

This is all pretty cool, but is it useful? I think that as long as the team works out a process where someone monitors this dashboard, and takes data-driven actions, this is valuable. For example, in my experience, as soon as a team starts measuring unit test code coverage and gains some focus on this metric, the coverage starts improving.

SonarCloud helps enforce good quality, for example by providing a quality gateway for pull requests. We have enabled this feature in Azure DevOps. It integrates really nicely. It helps us keep compliant with the rules in all new code.

Final thoughts: is Sonar ecosystem worth it for C# developer?

Let me try to summarize this with a list of pros and cons.

βœ… The analyzer package has ample library of code antipatterns. They are documented well. They can teach you code better.
βœ… The integration with Visual Studio is good. You don’t need to install an extension, just a NuGet with Roslyn analyzers. I don’t see negative impact on performance.
βœ… Integration with Azure DevOps works well. Enforcing rules is easy.
βœ… It enables a simple checklist-like flow to reduce technical debt in projects.
⚠ Currently, there are no β€œautomatic quick fixes” available for detected issues.
⚠ Might be impossible to use with pre-release version of tools. A week before the stable release of Visual Studio 2022, C#10, and .NET 6, SonarLint extension is not yet available, and code analyzers show some false alarms when I use C#10 features.
⚠ I don’t buy the sales point that the product allows you to fix features only in new/modified code. This approach doesn’t work for us. You change one line in a file, and you cannot merge your PR because you are blamed for the old issues in that file. But it’s a different discussion.

I think this is a good suite of products. I like using it both at work, and now also in my side-projects. You might benefit from giving it a try.

The post SonarCloud in a .NET/C# project: is it worth it? appeared first on Tim Taurit.

]]>
https://taurit.pl/sonarcloud-dotnet-my-opinion/feed/ 0
Migrate from .NET 5 to .NET 6: my checklist https://taurit.pl/migrate-from-net-5-to-6/ https://taurit.pl/migrate-from-net-5-to-6/#respond Sun, 17 Oct 2021 11:35:28 +0000 https://taurit.pl/?p=194 New releases of .NET often come with features that I really like to use in my daily developer work. Therefore, I like to keep projects I work on at the most recent stable version of .NET. Soon we’ll officially have stable versions .NET 6, released along with C# 10 and Visual Studio 2022. I like ... Read more

The post Migrate from .NET 5 to .NET 6: my checklist appeared first on Tim Taurit.

]]>
New releases of .NET often come with features that I really like to use in my daily developer work. Therefore, I like to keep projects I work on at the most recent stable version of .NET.

Soon we’ll officially have stable versions .NET 6, released along with C# 10 and Visual Studio 2022. I like the productivity improvements those versions introduce. As we already have a Release Candidate available, I decided to play a bit and upgrade some of my side projects to see how it goes. As a by-product of this play, I created a checklist for migration to .NET 6. I expect I’ll be doing that many more times in the future, so I wrote it down:

My checklist for migrating solutions to .NET6

  1. Change target framework in project files (*.csproj). Example: <TargetFramework>net6.0</TargetFramework>
  2. If the upgraded project uses ASP.NET Core, update NuGet packages specific to ASP.NET Core
  3. Update Dockerfiles. Example: FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build (...)
  4. Update DevOps pipelines or GitHub Actions that deploy the code to use newer .NET SDK version. Example from GitHub Action:
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 6.0.x
        include-prerelease: true
  1. (Optional) Update all NuGet package dependencies. That’s really unrelated to framework upgrade, but it’s a good opportunity, as we will need to test the whole solution anyway.
  2. (Optional) Update projects to take advantage of new .NET 6 features:
    • Refactor to use file scoped namespace declarations everywhere to reduce code indentation by 1 level.
    • Enable implicit using declarations by adding <ImplicitUsings>enable</ImplicitUsings> to the *.csproj files. Run code cleanup in Visual Studio to remove using declarations which became redundant.
    • Enable nullable reference types by adding <Nullable>enable</Nullable> to the *.csproj files. This feature was available since C#8, but was not included in project templates until .NET 6. It’s likely not present if you migrate projects, and it enables super-useful analysis of nullability-related issues.
  3. Fix all new warnings that appeared. Some APIs might now be obsolete or there could have been breaking changes, although so far, I didn’t encounter anything that wouldn’t be easy to fix.
  4. Finally, make sure everything compiles and works in runtime!

Can you see anything else worth doing as a part of target framework upgrade? Thanks for stopping by πŸ™‚

The post Migrate from .NET 5 to .NET 6: my checklist appeared first on Tim Taurit.

]]>
https://taurit.pl/migrate-from-net-5-to-6/feed/ 0
Azure Functions: timer job doesn’t fire when it should https://taurit.pl/azure-functions-timer-job-doesnt-fire/ https://taurit.pl/azure-functions-timer-job-doesnt-fire/#respond Tue, 12 Oct 2021 17:58:08 +0000 https://taurit.pl/?p=182 Our function should have been triggered by timer, but wasn’t… We recently encountered a problem in Azure Function App service. We have created a Function that should be triggered on schedule, once every day. It’s a typical case for use of a [TimerTrigger]. For example, [TimerTrigger("0 0 8 * * *")] should start the function ... Read more

The post Azure Functions: timer job doesn’t fire when it should appeared first on Tim Taurit.

]]>
Our function should have been triggered by timer, but wasn’t…

We recently encountered a problem in Azure Function App service. We have created a Function that should be triggered on schedule, once every day. It’s a typical case for use of a [TimerTrigger]. For example, [TimerTrigger("0 0 8 * * *")] should start the function every day at 8:00 AM UTC.

The setup was along these lines:

Azure function timer-triggered function in a context of App Service Plan.

However, we noticed that in some environments, the timer did not trigger the function at all when it was expected. So, what was the problem, and why it occurred only in some environments and not all of them?

Diagnosis and solution

It turned out, that the problem was in the type of App Service Plan that we used in our development environments.

Specifically, we used the following App Service Plan tiers to keep the cost of development environments low:

  • Free tier of App Service Plan
  • Shared tier of App Service Plan

In those tiers, the Function App cannot have the “Always on” setting enabled. So, the application can get unloaded when it’s idle. When that happens, timer triggers are not functional.

How can this be fixed? We can use a different Service Plan:

  • Basic tier of App Service Plan and the Always On flag enabled on a Function App. This works, but Basic tier has a noticeable, constant monthly cost. It might be an unnecessarily costly solution for development environments.
  • Consumption plan. That one was ideal. It has zero constant cost (it’s totally in pay-as-you-go model), and it supports timer triggers just fine.

A more general solution

We encountered one of the few possible causes for the problem of timer triggers not firing.

If you are stuck with a similar problem, the best way to start debugging is probably to consult an official Wiki document for troubleshooting timer-triggered function not firing. This one is pretty hard to find via search engine, but it exhausts the topic and is certainly worth reading.

The post Azure Functions: timer job doesn’t fire when it should appeared first on Tim Taurit.

]]>
https://taurit.pl/azure-functions-timer-job-doesnt-fire/feed/ 0
Remove multiple Data Lake Storage Gen1 Access Control List entries (ACLs) in one operation https://taurit.pl/remove-data-lake-storage-gen1-acls/ https://taurit.pl/remove-data-lake-storage-gen1-acls/#respond Sun, 10 Oct 2021 07:54:34 +0000 https://taurit.pl/?p=165 In large Data Lake Stores, operations on ACL entries are slow I recently wanted to simplify permissions in a Data Lake Storage file system. For folders and files in Data Lake Storage, we define permissions using Access Control Lists. Microsoft generally recommends that we should assign permissions to security groups, and not for individual users ... Read more

The post Remove multiple Data Lake Storage Gen1 Access Control List entries (ACLs) in one operation appeared first on Tim Taurit.

]]>
In large Data Lake Stores, operations on ACL entries are slow

I recently wanted to simplify permissions in a Data Lake Storage file system. For folders and files in Data Lake Storage, we define permissions using Access Control Lists.

Microsoft generally recommends that we should assign permissions to security groups, and not for individual users or apps:

32 ACLs can be set per file and per directory. Access and default ACLs each have their own 32 ACL entry limit. Use security groups for ACL assignments if possible. By using groups, you’re less likely to exceed the maximum number of ACL entries per file or directory.

Access control in Azure Data Lake Storage Gen1

There is another reason why it’s convenient to manage permissions with security groups in Active Directory. Changing ACL definitions on many objects is painfully slow. This is because files and folders don’t inherit permissions from their parent folders. We need to update many individual objects.

The experience of doing it from Azure Portal was terrible. It could be done, but a single operation took literally hours. The browser started consuming large amounts of RAM (over 10 GB). CPU usage was near 100%, and network usage very high, like the browser was constantly sending multiple individual requests.

How to remove or assign ACLs faster than from the Azure Portal’s UI?

Azure Portal UI suggests a better option than Azure Portal: a PowerShell command.

A warning in Data Lake Storage's UI in Azure Portal explaining that changing permissions is slow if there are many subfolders and files within them.

I found it easiest to run this command directly from the Cloud Shell in Azure Portal:

Using Azure Cloud Shell to change permissions on large Data Lake folders faster.

The only downside is that a session in Cloud Shell times out after 20 minutes of inactivity (by design). If the operation time exceeds 20 minutes, it might be better to run the command on your local machine.

Now to the point. How do we remove multiple ACLs in a single PowerShell operation? The example from documentation has some errors, so I publish my own in the case any of us needs to do that in the future:

# List all ACLs to remove, separated by a comma. Ids are just examples, insert your own ;)
$aclsToRemove ="user:eba73f1b-fa61-45df-ac8c-cb068836680d:rwx,default:user:eba73f1b-fa61-45df-ac8c-cb068836680d:rwx"

# Split the string into array of individual ACLs
$aclsToRemoveAsArray = $aclsToRemove.Split(",")

# Remove all in a single operation (recursively)
Remove-AzureRmDataLakeStoreItemAclEntry -AccountName "mydatalakename" -Path / -Acl $aclsToRemoveAsArray -Recurse -ShowProgress

This is much faster than a similar operation from Azure Portal’s UI. On a data set of ~1 TB and more than 1 000 000 files, it took only few minutes.

The post Remove multiple Data Lake Storage Gen1 Access Control List entries (ACLs) in one operation appeared first on Tim Taurit.

]]>
https://taurit.pl/remove-data-lake-storage-gen1-acls/feed/ 0
Can I access Azure Data Lake Storage like a local drive in Windows? https://taurit.pl/access-azure-data-lake-storage-like-local-drive-in-windows/ https://taurit.pl/access-azure-data-lake-storage-like-local-drive-in-windows/#comments Thu, 07 Oct 2021 16:50:27 +0000 https://taurit.pl/?p=147 TL/DR: If this is possible, it's hard. I was unable to achieve the goal in reasonable time. When we work with Azure Data Lake Storage Gen2 (ADLS), it feels pretty much like working with any other kind of file system, it’s just online. We have directories and files. We can organize them into a hierarchy ... Read more

The post Can I access Azure Data Lake Storage like a local drive in Windows? appeared first on Tim Taurit.

]]>
TL/DR: If this is possible, it's hard. I was unable to achieve the goal in reasonable time.

When we work with Azure Data Lake Storage Gen2 (ADLS), it feels pretty much like working with any other kind of file system, it’s just online. We have directories and files. We can organize them into a hierarchy of subdirectories. We can remove whole directories, including their content, in a single operation.

It would be a bit of a stretch to say that it’s somewhat similar to services like OneDrive, Google Drive, Dropbox, or even Azure Files. ADLS is not intended to be a general-purpose online drive. It is a specialized kind of storage optimized to store large amounts of data and works best with large files.

But what if we could mount it like a normal drive anyway? That would bring some convenience for developers. We could easily navigate through folders. We could open files in our favorite programs, even if the file would have to be downloaded under the hood before it’s actually opened.

So, can we mount ADLS Gen2 in Windows just like a local (or a network) drive?

What do the docs say?

The most promising document I found is the one that explains How to Mount Azure Blob Storage using NFS 3.0 protocol. This could work, as ADLS Gen2 is just a feature set on a Storage Account. Moreover, Windows can mount NFS 3.0 drives if we enable it.

But the documentation also focuses on Linux clients, and does not go straight to the point, so I’ll try to document my attempt here.

How I tried to mount ADLS Gen2 as a drive in Windows

The first issue, and the biggest disappointment for me, is that NFS access needs to be enabled when we create a Storage Account. There is no way to enable it later:

A screenshot from Azure Portal showing that change of NFS v3 capability in exiting Storage Accounts is impossible.

In practice, this means I will not be able to access Data Lake Storage instances I work with daily. I could therefore stop here, but for education, let’s see what would happen if I did enable NFS access.

I therefore created a new storage account, and this time enabled NFS access. When I did that, it revealed another problem. If we enable NFS, we can no longer access Data Lake Storage from the public internet. We need to place the storage account in a Virtual Network. This is where it starts to look like we won’t be able to access it from a typical developer machine.

A screenshot from Azure Portal showing Network Connectivity options.

But let’s take it further, just as an exercise. What if the setup where our Windows machine is indeed in the same Virtual Network as storage works for us? Maybe we already use Virtual Network Gateway and have a VPN connection that gives us secure acceess to the storage. Would it be easy to mount ADLS Gen2 as a drive in such situation?

To test it, I created a Windows-based VM in the same Virtual Network as storage. I enabled the NFS feature and rebooted:

And then I tried to mount the drive. I found no instruction for Windows on how to do it, so I was mostly guessing the correct URL based on conventions used by Azure Files:

After several attempts from the UI and console (net use z: \\xxx.blob.core.windows.net\...) which didn’t work, I decided to give up. It was not fun anymore.

A conclusion

It’s definitely not as easy as with File Shares. I wasn’t able to do it in reasonable time. Even if this can be done, it wouldn’t be practical in the setup I use. When I’m writing this, there’s no documentation on how this could be done in Windows.

Sorry to disappoint! But if you are determined enough, and you succeed with mounting ADLS as Windows drive, don’t forget to let us know in the comments!

The post Can I access Azure Data Lake Storage like a local drive in Windows? appeared first on Tim Taurit.

]]>
https://taurit.pl/access-azure-data-lake-storage-like-local-drive-in-windows/feed/ 1
How to query Application Insights with C# https://taurit.pl/query-application-insights-with-csharp/ https://taurit.pl/query-application-insights-with-csharp/#comments Sun, 03 Oct 2021 18:50:36 +0000 https://taurit.pl/?p=127 Update: the code example in this article uses an API that is now deprecated. It will only be useful if you are stuck using old NuGet library to query Application Insights. For new code, you should use https://www.nuget.org/packages/Azure.Monitor.Query/ instead. This article does not provide code example on how to use that newer API. Application Insights ... Read more

The post How to query Application Insights with C# appeared first on Tim Taurit.

]]>
Update: the code example in this article uses an API that is now deprecated. It will only be useful if you are stuck using old NuGet library to query Application Insights. For new code, you should use https://www.nuget.org/packages/Azure.Monitor.Query/ instead. This article does not provide code example on how to use that newer API.

Application Insights is a great tool for monitoring solutions deployed to Azure.

A user-friendly interface for Application Insights can be found in Azure Portal, and often it’s what we need. However, occasionally, we might want to automate some process that requires access to logs, and we want to query Application Insights from .NET code.

I needed it a few times, and could never easily find a code sample in Google, so I publish my blog post to have such code snippet easily available.

Prerequisites

The code requires installing the Microsoft.Azure.ApplicationInsights.Query package in your project.
You also need to generate a secret key to authenticate to Application Insights API.

Querying Application Insights via API from C#

public async Task GetMostRecentFailedRequests()
{
    // You can generate key in Azure Portal, in Application Insights' "API Access" pane
    string applicationId = "c22a7f19-b6...[censored]";
    string key = "24io8jb...[censored]";
    
    // Create client
    var credentials = new ApiKeyClientCredentials(key);
    var applicationInsightsClient = new ApplicationInsightsDataClient(credentials);

    // Query Application Insights
    var query = $"requests" +
                $" | where timestamp > ago(24h)" +
                $" | where success == false" +
                $" | order by timestamp desc" +
                $" | project timestamp, url, resultCode" +
                $" | take 3";
    var response = await applicationInsightsClient.Query.ExecuteWithHttpMessagesAsync(applicationId, query);
    
    // Parse result 
    if (response.Response.IsSuccessStatusCode)
    {
        foreach (var row in response.Body.Results)
        {
            Console.WriteLine($"[{row["timestamp"]:s}][{row["resultCode"]}] Failed request to: {row["url"]}");
        }
    }
}

// example output of the above function in console window
[2021-10-03T11:54:16][404] Failed request to: http://generatordiety.pl/apple-touch-icon.png
[2021-10-03T11:05:32][404] Failed request to: http://generatordiety.pl/.env
[2021-10-03T11:05:31][404] Failed request to: http://generatordiety.pl/.env

Bonus: how to convert DateTimeOffset to a date understood by Kusto

I found DateTimeOffset parameter to be quite often useful in queries. Here is a snippet for this specific type’s conversion that will nicely preserve time zone information:

DateTimeOffset lastMidnight = DateTimeOffset.UtcNow.Date;
string query = $"(...) | where timestamp > todatetime(\"{lastMidnight:O}\")";
// The "O" or "o" standard format specifier represents a custom date and time format string using a pattern
// that preserves time zone information and emits a result string that complies with ISO 8601. 

And that’s all here, it should be a good place to start.

The post How to query Application Insights with C# appeared first on Tim Taurit.

]]>
https://taurit.pl/query-application-insights-with-csharp/feed/ 7
How to use NordVPN + PiHole together (on Windows) https://taurit.pl/nordvpn-pihole/ https://taurit.pl/nordvpn-pihole/#comments Thu, 30 Sep 2021 04:59:19 +0000 https://taurit.pl/?p=129 If you use PiHole to block your ads, you might be unpleasantly surprised after installing NordVPN. After you connect to a VPN server, your previous DNS settings won’t be respected. In other words, DNS queries will no longer go to your PiHole resolver, but to NordVPN’s resolver. When I originally wrote this post in 2021, ... Read more

The post How to use NordVPN + PiHole together (on Windows) appeared first on Tim Taurit.

]]>
If you use PiHole to block your ads, you might be unpleasantly surprised after installing NordVPN. After you connect to a VPN server, your previous DNS settings won’t be respected. In other words, DNS queries will no longer go to your PiHole resolver, but to NordVPN’s resolver.

When I originally wrote this post in 2021, NordVPN’s software didn’t contain an option to specify a custom DNS server. Now, in 2023, there is such an option, but it still doesn’t work if the DNS server is in a local network (and that’s where your Raspberry Pi likely is).

Workaround: use OpenVPN software to connect to NordVPN infrastructure

I tried many things, and ultimately landed with a workaround for this issue. It requires a tradeoff, though. It allows you to keep using NordVPN’s infrastructure and subscription you paid for, but you would need to switch from the official NordVPN software to a more generic, open-source VPN client, which can be configured to work with PiHole.

Here are the steps. I tested that they still work in 2022 2023:

  1. Remove the official NordVPN software. Install OpenVPN GUI instead. It’s a popular open-source software.
    The downside here is that it forces us to use OpenVPN protocol for VPN connection, so we won’t benefit from improvements coming from the NordLynx protocol. It’s good enough for me, but decide if the tradeoff makes sense to you.
  2. Download NordVPN server configuration for OpenVPN client from the official NordVPN website.
    You’ll have to choose a single, specific server to use with OpenVPN. This is another downside. There won’t be an option to automatically β€œchoose the fastest server” as in the official NordVPN client.
    I selected one of the servers located near me, and UDP as a transport protocol.
  3. Import the downloaded file to OpenVPN GUI.
    I just moved the file to c:\Users\MyUserName\OpenVPN\config\.
  4. Set your PiHole device as preferred DNS server when connecting to VPN.
    Modify the downloaded configuration file and add a line similar to:
    dhcp-option DNS 192.168.0.95
    somewhere in the file. Replace the IP address with the IP address of your PiHole!
  5. Set up OpenVPN GUI to autostart with system and to start new connection when launched
Screenshot showing how to download NordVPN configuration compatible with the OpenVPN client
Step 2: downloading NordVPN configuration for OpenVPN client
Modifying the downloaded configuration of a VPN profile.
Step 4: Modifying the downloaded configuration of a VPN profile.

After following these steps, the VPN connection works and PiHole is used as a DNS server as it used to.

NordVPN and PiHole successfully working together
NordVPN and PiHole successfully working together

Appendix: lessons learned and my perspective in 2023

Since I wrote this blog post, I simplified my setup. I learned that Pi-Hole is a fun tool, but it doesn’t meet my requirements. I want to have my ads blocked not only when I’m at home, but also on other networks and on a smartphone. Furthermore, I would rather not spend more time fighting with routers and firewalls and networking, but just solve the ad-blocking problem and be done with it πŸ˜€

In the process, wrote another blog post looking at the ways to host PiHole in the cloud. Ultimately, though, I switched to a cloud-native solution alternative to Pi-Hole, called NextDNS. I’m supper happy about the change!

Regarding the VPN, I connect to it very sporadically now. I believe that with an encrypted DNS and mostly encrypted web today, VPNs don’t bring much added value in terms of privacy and security. Resigning from the daily VPN use removes one more complex layer from my networking setup, which results in less problems to solve and more time saved. But of course, this is only my current judgement.

Good luck with your endeavor! πŸ™‚

The post How to use NordVPN + PiHole together (on Windows) appeared first on Tim Taurit.

]]>
https://taurit.pl/nordvpn-pihole/feed/ 3
Azure Storage Files: what takes my space? https://taurit.pl/azure-storage-files-what-takes-space/ https://taurit.pl/azure-storage-files-what-takes-space/#respond Tue, 28 Sep 2021 18:58:56 +0000 https://taurit.pl/?p=116 Recently, I got curious what exactly takes up space in my Azure Storage Files. In the end, I’m paying for each stored byte. πŸ˜‰I tried to find out using Azure Portal, but it wasn’t practical. While Azure Portal displays size of files, it doesn’t display total size of folders: Maybe Azure Storage Explorer can do ... Read more

The post Azure Storage Files: what takes my space? appeared first on Tim Taurit.

]]>
Recently, I got curious what exactly takes up space in my Azure Storage Files. In the end, I’m paying for each stored byte. πŸ˜‰
I tried to find out using Azure Portal, but it wasn’t practical. While Azure Portal displays size of files, it doesn’t display total size of folders:

Azure Storage Account file share. Screenshot from the Azure Portal showing that a total size of folder is not displayed.

Maybe Azure Storage Explorer can do better? I found it allows requesting statistics for a specific subfolder which can help. It’s still far from a clear, useful report on what takes up space:

Calculating Azure Storage File Share total folder size using Azure Storage Explorer.

I wondered if there is some tool similar to my favorite WinDirStat application on Windows or QDirStat on Linux to visualize sizes of all files in storage. But wait… what if I mounted Azure File Share as drive in Windows and then used any of those familiar tools? It’s super-easy and it worked! Just take a look:

WinDirStat showing file size raport for an Azure Storage Account Files service.
Azure File Share mounted as a Windows drive Z:. Visualization of what takes space. Each block represents a file with a size proportional to its area.

This solution might not scale well for large file shares and might require some patience to get all the information fetched. But it looks pretty cool and seems practical for small to medium file shares. πŸ™‚

The post Azure Storage Files: what takes my space? appeared first on Tim Taurit.

]]>
https://taurit.pl/azure-storage-files-what-takes-space/feed/ 0
A cheap way to VPN into Azure Virtual Network https://taurit.pl/vpn-into-azure-virtual-network/ https://taurit.pl/vpn-into-azure-virtual-network/#comments Wed, 22 Sep 2021 20:51:28 +0000 https://taurit.pl/?p=93 VPN Gateway β€” looks great, though a bit expensive Recently, I was learning a bit about networking in Azure. As a practical exercise, I wanted to learn what is the cheapest way to achieve the following setup to protect resources in Azure Virtual Network that could be a target of a network attack, for example ... Read more

The post A cheap way to VPN into Azure Virtual Network appeared first on Tim Taurit.

]]>
VPN Gateway β€” looks great, though a bit expensive

Recently, I was learning a bit about networking in Azure. As a practical exercise, I wanted to learn what is the cheapest way to achieve the following setup to protect resources in Azure Virtual Network that could be a target of a network attack, for example Virtual Machines:

A diagram showing purpose of a VPN Gateway.
Setup that I want to achieve

In theory, I understood how VPN Gateway helps secure VNets and I saw it as a useful tool in such setup. But then I got disappointed when I saw the pricing table for VPN Gateway. The cheapest pricing tier of VPN Gateway, Basic, is about $26/month. That’s half of my monthly credit limit from Visual Studio Professional subscription. With that price, I find it impractical to use in small projects, where it would be often the most costly resource.

I wondered, how could I solve the same problem in a different (and cheaper) way?

OpenVPN Access Server β€” a cheaper alternative in my use case?

Seeking alternatives, I found OpenVPN Access Server available in Azure Marketplace. It looked good with 5/5 rating, nice reviews and open-source technology behind it. Furthermore, it advertised that if we only need 2 VPN connections at a time, the service is free. I wanted to see how it works, and here are my first experiences.

Not exactly free, but entry point is still cheaper

OpenVPN Access Servers is a Virtual Machine with a custom software, available in Azure Marketplace. It’s created in a very similar way to other Virtual Machines. Even though the software might provide free license for up to 2 VPN connections, we still need to pay for the virtual machine. When I created the service, it became clear:

A screenshot from Azure Portal showing creation of an OpenVPN Access Server Virtual Machine.
Creating “OpenVPN Access Server” resource

At first, I used the cheapest Virtual Machine available, the B1ls. However, with only 0.5 GB memory, it didn’t meet the minimum hardware requirements of the service and I observed many problems when accessing Web UI. I had to update to B1s with 1 GB memory, and it was much better.

In total, estimated cost is about $11.23 (for a VM, disk and a public IP) in the case we never shut down the VM. I estimate that the lower limit for cost optimization would be about $2.5 if we only connected occasionally and kept the VM off the rest of the time.

First experiences: configuration

The first step, as hinted in the service’s description in Azure Marketplace, should be to connect to the newly created Virtual Machine via SSH. This is fairly easy from Azure Portal.

Once we connect, we instantly get welcomed by few configuration questions:

Configuration of OpenVPN Access Server resource created in Azure.

Web UI

When we finish basic configuration, we get a URL to a friendly Web UI. It was a nice surprise that graphical UI would even be part of the service:

A Web UI of OpenVPN Access Server.

When I authorized using the account I created in SSH session, I could download a configuration profile for OpenVPN client and connect to the Virtual Network from my Windows 10 machine. I could also go deeper into the administration panel:

A control panel in Web UI of OpenVPN Access Server. It is available after logging in.

Overall impression

It looks like a user-friendly product which works out-of-the-box. I only saw the surface of it and I cannot tell much about drawbacks. One thing that surprised me a bit was that after creating the Virtual Machine, software looked pretty outdated:

OpenVPN Access Server: outdated server on a newly created Virtual Machine.

Of course, I could manually update it without problems with 2 commands. But since this service is a gateway that serves as a security measure, it triggered a thought that I would now have to maintain updates of this new Virtual Machine if I want to keep it online.

Ultimately, I don’t think I’ll be using this for my small side-projects. Maintaining VM’s, even small ones, feels like overengineering when it only protects 1 other VM πŸ™‚ But it’s a nice tool to know.

Thanks for stopping by!

The post A cheap way to VPN into Azure Virtual Network appeared first on Tim Taurit.

]]>
https://taurit.pl/vpn-into-azure-virtual-network/feed/ 2
Azure Storage: metrics like ‘Capacity’ and ‘Entity count’ are delayed https://taurit.pl/azure-storage-metrics-delayed/ https://taurit.pl/azure-storage-metrics-delayed/#comments Tue, 21 Sep 2021 20:06:34 +0000 https://taurit.pl/?p=82 Here’s a short observation that surprised me in the recent days. I just want to leave a note in case anyone stumbles upon similar issue. I noticed that metrics for Azure Storage like Table Capacity and Table Entity Count show data which isn’t too accurate. Let’s see a simple case where I started migrating & ... Read more

The post Azure Storage: metrics like ‘Capacity’ and ‘Entity count’ are delayed appeared first on Tim Taurit.

]]>
Here’s a short observation that surprised me in the recent days. I just want to leave a note in case anyone stumbles upon similar issue.

I noticed that metrics for Azure Storage like Table Capacity and Table Entity Count show data which isn’t too accurate. Let’s see a simple case where I started migrating & removing data at some point. I expected a linear chart going from 5.1 GB when I start to 0 GB when I finish. But I got this:

A screenshot showing that the 'Table Capacity' metrics of Azure Storage Table are delayed in time.
Observation: metrics didn’t show change in used capacity or entity count for nearly a day

I found it a bit surprising because I expected that observing such metrics would be the best way to monitor progress of a process like data migrating data to another storage. So, what happened here?

I see two things here:

  • A delay of about 24 hours. I found in documentation that, indeed, Azure refreshes those metrics daily. If that’s intentional, it’s ok.
  • A granularity of 1 hour. This one seems weird to me. If we know that Azure updates the metric only once a day, why pretend that we have value every hour, and not just leave much fewer data points where we have actual results? The chart would be more accurate if we just connected the points where we have data and interpolated the unknown data in between.

I wanted to dig deeper on that to understand, but the explanation in official docs didn’t clarify it for me. I therefore created a GitHub issue and asked for a better explanation.

So far, it’s sitting in the queue, so unfortunately I don’t have an explanation for whether it makes sense. But there is a chance that soon the thread will lead to a better explanation of how those metrics behave. πŸ™‚

The post Azure Storage: metrics like ‘Capacity’ and ‘Entity count’ are delayed appeared first on Tim Taurit.

]]>
https://taurit.pl/azure-storage-metrics-delayed/feed/ 1
Azure DevOps pipelines: how to set font color from C# https://taurit.pl/azure-devops-pipelines-set-font-color/ https://taurit.pl/azure-devops-pipelines-set-font-color/#comments Tue, 21 Sep 2021 18:39:50 +0000 https://taurit.pl/?p=11 When we work DevOps pipelines, pipeline logs often help us diagnose what went wrong or right. An easy way to make text stand out is to put some colors. For example, red text instantly directs our attention to errors in a haystack of text. Observation: Console.ForegroundColor does not work Let me just start with an ... Read more

The post Azure DevOps pipelines: how to set font color from C# appeared first on Tim Taurit.

]]>
When we work DevOps pipelines, pipeline logs often help us diagnose what went wrong or right. An easy way to make text stand out is to put some colors. For example, red text instantly directs our attention to errors in a haystack of text.

Observation: Console.ForegroundColor does not work

Let me just start with an observation that an intuitive approach does not work, and here is the proof πŸ˜‰

// render the default font
Console.WriteLine("Normal style");

// the standard C# way: it works in console, but not in DevOps pipelines
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Console.ForegroundColor = ConsoleColor.Cyan");
Console.BackgroundColor = ConsoleColor.Yellow;
Console.WriteLine("Console.BackgroundColor = ConsoleColor.Yellow");
Console.ResetColor();
Setting console color in C# the standard way. Unfortunately, it has no effect in Azure DevOps context.
As we can see in the screenshot, the above code sample did not work in DevOps pipelines

Option 1: use Logging Commands

In Azure DevOps, one way to write text in color is to use Logging Commands:

Screenshot of logs with custom formatting options
Text displayed using Logging Commands will by formatted in color by default:

Logging Commands are useful for highlighting specific log sections with relevant semantic meanings. However, using these commands may lead to unintended side effects, such as warnings appearing in other areas of the DevOps UI, which may be undesirable at times. If you want to write some log line in color, but not give it any semantic meaning, check the next option.

Option 2: use ANSI escape codes

We can also use ANSI escape codes. Well, maybe not all of them. In general, based on my observations, DevOps seems to currently support escape sequences that set foreground or background color and nothing moreβ€”but this is what we need here πŸ™‚

namespace WriteSomethingInColor;

internal class Program
{
    static void Main(string[] args)
    {
        // Preview foreground colors
        for (int fontColor = 30; fontColor <= 37; fontColor++)
            Console.WriteLine($"\u001b[{fontColor}m example (foreground color {fontColor})\u001b[0m");

        // Preview background colors
        Console.WriteLine();
        for (int fontColor = 40; fontColor <= 47; fontColor++)
            Console.WriteLine($"\u001b[{fontColor}m example (background color {fontColor})\u001b[0m");

        // Prove that we can mix custom font and background color
        Console.WriteLine();
        Console.WriteLine($"\u001b[46m\u001b[33ma yellow font on a blue background\u001b[0m");
        Console.WriteLine($"\u001b[47m\u001b[31ma red font on a white background\u001b[0m");

        Console.WriteLine("\u001b[0m... and now the color is reset back to the default value");
    }
}
Escape sequences work both in console and DevOps pipelines

The shades of colors rendered in DevOps and in Windows command line with default settings are close enough. I think we can expect that color 32 will be rendered as green in all environments, for example.

The post Azure DevOps pipelines: how to set font color from C# appeared first on Tim Taurit.

]]>
https://taurit.pl/azure-devops-pipelines-set-font-color/feed/ 2
Hello! (the blog starts here) https://taurit.pl/hello/ https://taurit.pl/hello/#respond Mon, 20 Sep 2021 19:01:27 +0000 https://taurit.pl/?p=61 Welcome to my new blog. I believe I already started 5 blogs in my life and later killed them for various reasons πŸ˜‰ Some of them technically still exist, for example: The other ones addressed other interests of mine, but I found that I feel better completely removing them at some point. Why again then? ... Read more

The post Hello! (the blog starts here) appeared first on Tim Taurit.

]]>
Welcome to my new blog. I believe I already started 5 blogs in my life and later killed them for various reasons πŸ˜‰ Some of them technically still exist, for example:

  • .NET Battlefield, my old blog about Sharepoint and .NET development, has last post dated to 2015.
  • Zakasane rΔ™kawy, where I published some essays about productivity, was officially closed in 2020.

The other ones addressed other interests of mine, but I found that I feel better completely removing them at some point.

Why again then? I know there’s no fame or money in blogs like that. I think I can only justify it as a form of entertainment. I enjoy writing. It helps me think. And once again I feel that I want to create something like that in my spare time, even though from the start it gets a pretty low priority among other things I want to do.

Let’s see how it goes. To improve its chances, let me keep posts brief and to the point!

The post Hello! (the blog starts here) appeared first on Tim Taurit.

]]>
https://taurit.pl/hello/feed/ 0