Roland Weigelt

Born to Code

  • UI/UX for Devs: Have You Thought About the Mental Model?

    My day job sometimes involves reviewing existing or planned user interfaces of business applications outside my product team. When I give my feedback to frontend developers, I try to use the opportunity to convey knowledge on UI/UX basics. For instance, by showing them how my findings are informed by a set of questions that can they can use themselves as an early check of their work.

    One of these questions is: What is the intended mental model?

    I find it helpful because in my experience,

    • it highlights problems in a constructive way and
    • is a great starter for discussions on working towards a solution.

    So, let’s talk about mental models.

    What is a mental model?

    Generally, a mental model is a simplified, often incomplete (or even wrong) representation of the surrounding world in your mind. The model forms based on the available visible, audible, or sensory information, prior personal experience, what other people have told you or just by guessing.

    The benchmark for a mental model is how good it helps you navigate the real world, i.e. if it can explain something that has happened or can offer a reasonable prediction for what will happen.

    Take gravity for example. The simple mental model of “things falls down until they get stopped by something” is good enough for most situations in our everyday life.

    If a mental model fails to explain an observation (“why doesn’t this ‘balloon’ thing fall down?”) or when additional information becomes available (Helium is lighter than air), the model may evolve, be replaced by another model, or by several models for different situations. But the model(s) remain simple – you usually do not explicitly think about buoyancy if no water is involved, or the gravitational forces that cosmic objects exert on your car.

    Example: A mental model for scrolling

    Let’s get back to user interfaces. Imagine you have never seen something like scrolling before. Then you watch somebody drag the narrow rectangle at the right side of the window down, causing the larger part to move up:

    MentalModel1

    After you have seen some more scrolling, noticing that content that left the screen can be brought back, the mental model in your head will most likely look like this:

    MentalModel2

    Is this model correct? Do the areas in light gray really exist?

    From a purely technical point of view, this is debatable – especially when you think about virtualized scrolling, where the UI “lies” to you, creating the visible part on the fly from pure data (e.g. the text in a text editor).

    From a user’s point of view this is not important. The model explains why parts of the screen moved the way they did when you moved the scrollbar. And it allows a reliable prediction what action will bring back content that was moved off the viewport.

    In other words: The model is good enough.

    Visual cues are key

    When a user perceives an application as easy to understand, it means that he/she was able to quickly develop an initial mental model that matches the reality of the user interface. That model surely will continue to grow and evolve and may have to be adjusted over time. But what to look out for are gross misunderstandings, when a wrong mental model leads to confusion, or worse, data loss.

    Your job when designing a UI is to “nudge” the user towards a correct mental model. This is a case of “show, don’t tell”: Explaining the basic ideas of the UI in an online manual or a onboarding experience (likely to be skipped) will not reach all users. The UI needs visual cues that convey the mental model that you intended.

    For example, when you cannot show all content at once due to space constraints, you somehow must tell the user that there is more content to discover:

    • Partially visible content that gets cut off at an edge hints that the area can be scrolled or at least resized.
    • Page numbers or simply dots look like paging is possible.
    • Stack-like visuals suggest that content can be switched.

    Example: A mental model for tabbing

    Tab views show different sets of one or more controls in the same space. This user interface pattern is useful if you…

    • cannot (or do not want to) show all controls at the same time, but need fast switching and
    • you can cluster the controls into groups that make sense.

    A mental model for a tab view comes straight from the real world, just imagine e.g. three sheets of paper:

    MentalModel3

    Like other UI elements, the design of tabs has evolved over the years. Moving away from mimicking physical objects and their behavior, the overall trend went to visually reduced designs.

    While the following design still tries to convey a foreground-background relationship…

    MentalModel4

    …the second version does not care about how things would work in the real world:

    MentalModel5

    Which is not necessarily a bad thing, to be clear. In UI design, you must deal with the fact that a user has a certain “attention budget”. In your UI, each pixel different from the main background color chips away from that budget. Thus, getting rid of lines, borders and colored area is a good way to reduce “cognitive load” – until it is not. This is the point when you have removed important visual cues that help forming the mental model that you originally intended.

    How visual cues influence your mental model

    Look at the following four sketches of an application with two levels of navigation:

    MentalModel6

    Notice how the gray backgrounds influence your mental model:

    • The upper left does not give you an idea what to expect when you click an item in the top row or one of the icons on the left side.
    • The lower right is very clear about the relationships of the different parts of the UI. At the same time, the design is pretty “busy”.
    • The upper right and lower left are similar in design but convey different mental models. Which one is “better” depends on factors like scrolling, expected window sizes, etc. So, the answer is the usual “it depends”.

    Ask yourself: What is the intended mental model?

    The application

    Users do not develop a mental model in a vacuum. They look for similarities to other applications. In unfamiliar situations, their behavior is influenced by earlier experiences. While not all applications are the same, certain archetypes have developed over time.

    If you aim for a quick route of your users to familiarity with the application, you should be clear about the general model. For instance:

    • Is the application a document editor?
      Most business users know how to to deal with Office documents. If your application has “things” that can be handled like documents (regardless of whether they are actually stored in files), users can use their previous experience with documents. At the same time, this comes with certain expectations. For example, editing or deleting one “document” should not affect another document (unless there is an explicit relationship).
    • Is it something that manages one or more lists of “things”, offering views on the data at different detail levels?
      This is the model behind applications that have some kind of database at their core. Browsing, searching, or filtering, followed by viewing/editing of single data items are comparable across business applications. Users of your application could e.g. expect that after they have narrowed down the results list, and dive into a single item, that the list will be “somewhere off-screen”. This is an example where a mental model is used for predicting what will happen, i.e. that is possible somehow to come back to the result list.
    • Is it like a machine that builds something after you have specified what you want to build?
      Software that uses the concept of “projects” and “builds” is what developers work with all day. Some single-source documentation systems use the same approach. Another example are e.g. video production tools.

    Your application surely can combine different models or be something unique. And if it is similar to existing software, being different can be an advantage. The best thing users can say is “it is like X, but you no longer have to do Y manually”.

    What you must avoid is intentionally trying to be similar to other software and then offering a bad experience. For example, early web applications sometimes tried very hard to be like their desktop counterparts before the technology was reliable enough. Which could result in losing work simply by pressing a single key at the wrong time (some may remember: a page-based application, a large text field, some JavaScript that goes wild, lost focus, backspace – everything gone).

    The data

    Internally, most (.NET) applications deal with objects with properties which in turn can be objects, lists of objects or references to one or more objects. One of the challenges of UI design is to decide how much users should or should not know of these structures.

    • Is what a user experiences as a “thing” the same as an object in your code?
      Sometimes it is good to hide the complexity of composite objects, sometimes it is not. Take, for example, a form for entering a person’s data. Do you present the address as something that feels like a “thing”, or as something that is part of the person similar to its given and family name? As usual, it depends. On one hand you want a clean and simple UI, on the other hand imagine a database that tracks all addresses that a person has ever lived at.
    • How do you handle incomplete or invalid “things”?
      Let’s say a user has a concept of “things” that map pretty well to objects behind the scenes. Unfortunately, the infrastructure for storing these objects requires them to be complete and valid. Does this impact the user, forcing an “all or nothing” approach when creating a new thing? Or do you allow incomplete data as a “draft”, even if it means it must be stored separately because you e.g. have no influence on the available storage infrastructure?

    What to do

    • Prepare yourself to be asked “what’s the mental model?”
      Even if you will never get asked, this forces you to come up with a short summary that helps you communicating the basic ideas of your application (or a specific part of it).
    • Remember mental models are simplified and incomplete
      When a user writes an email and presses the “send” button, in their mind, this sends the email. Conceptually, emails are moved into the outbox, then collected and sent. But even people who know about this (and may have seen an email stuck in the outbox) tend to think of “click equals send”, because this mental model is – you may have guessed it by now – “good enough”. When you think about mental models in your application, expect users to start with the simplest model they can work with.
    • Watch yourself how you figure out how other software works
      Which visual cues do help you? Where does the software rely on known interaction patterns? Why can you use an application that you have not used before?
    • Think of instances when your mental model was wrong
      Was it your mistake? Did you have assumptions based on prior experience? Was it the application’s fault? What could the software have done differently to prevent this? What about your application? Could this happen to users of your software?
  • A Much Too Short Explanation of UI/UX/Feature/Scenario

    This is a side-product of sketches I did for upcoming talks, using an iOS app called Linea Sketch.

    User Interface

    UIUXFeatureScenario1

    The user sees a button.

    User Experience

    UIUXFeatureScenario2

    How the user feels before, while and after pressing the button.

    Feature

    UIUXFeatureScenario3

    What the product does for the user when he/she presses the button.

    Scenario

    UIUXFeatureScenario4

    Who encounters what problem and how the product will help him/her on the way to reach their goal.

    Oh, the button? You don’t think about the button when first looking at the scenario…that’s the point!

  • Developer Week 2020 verschoben auf November, Online-Konferenz DWX Home als Ersatz

    Eigentlich sollte die Developer Week 2020 vom 29. Juni bis 3. Juli 2020 in Nürnberg stattfinden. Ich wäre dort mit meinem Ganztages-Workshop “Von Null auf GUI – Design/UI/UX-Praxiswissen für Entwickler” und einem Vortrag über “User Interface Patterns” vor Ort gewesen – doch dann kam der Virus.

    Daraus folgte zum einen eine Verschiebung in den November, und zwar als Hybrid-Modell (sowohl On-Site als auch Remote). Mein Workshop ist jetzt für den 2. November, der Vortrag für den 4. November geplant.

    Darüber hinaus wird im ursprünglichen Zeitraum eine reine Online-Konferenz, die DWX Home, stattfinden. Jeweils vormittags gibt es Vorträge zu den Themen DevOps (Montag, 29. Juni 2020), .NET (Dienstag), Testen (Mittwoch), Software-Architektur und -Qualität (Donnerstag) sowie UX und UI Design (Freitag, 3. Juli 2020).

    Am UX und UI Design-Tag bin ich mit meinem Vortrag “Fragen, Fragen Fragen…- Diskussionen über UI-Designs” vertreten:

    Die richtige Frage zum richtigen Zeitpunkt kann Diskussionen über UI-Designs wertvolle Impulse geben. Dabei muss weder die Frage noch die Antwort besonders weltbewegend sein – ein kleines Stückchen vorher unbekannter Information verändert manchmal massiv den Lauf der Dinge.

    In seinem Vortrag vermittelt Roland Weigelt, dass eine analytische Denkweise auch in den vermeintlich eher von Emotionen bestimmten Bereichen Design und User Experience ein wertvolles Werkzeug ist. Und damit es nicht zu trocken wird, geht es nebenbei um Problemlösung à la Indiana Jones, außerirdische Lebensformen und den Einfluss von Rallye-Streifen auf die Geschwindigkeit von Sportwagen.

    Für Teilnehmer der Developer Week im November ist der Zugang zur DWX Home automatisch im Ticket-Preise inbegriffen. Für alle anderen gibt es kostengünstige Tagestickets (dafür auf der Ticket-Seite ganz nach unten scrollen).

  • Exercise in Thinking: Do Racing Stripes Make a Car Go Faster?

    The progress from a junior to a senior software developer shows in various ways:

    • Coding: Years of working with different languages, frameworks, libraries and architectures help developing a gut feeling for technology.
    • Planning: Having experienced a large number of projects or product releases with their milestones, last-minute changes, hotfixes, updates, etc., senior developers usually have a better sense of what to do and what to avoid.
    • Analytical thinking: (Good) senior developers tend to ask the right question at the right time, digging deep to find out what they don’t know – and whether what they think they know is true.

    For developers, improving their analytical skills is an important part of personal growth. That is why I like to cover non-technical topics both in my conference talks as well in my blog articles.

    In a previous post called “How to Approach Problems in Development (and Pretty Much Everywhere Else)”, I wrote about (among other things) what Indiana Jones can teach you about problem solving.

    The article you are reading now asks whether “racing stripes” make a car go faster. A strange question on a software development blog. This is by design, because strange things tend to be remembered more easily (fun fact: The word “strange” can be translated to German as “merkwürdig”, which in a literal translation back to English could be “worthy to be noticed” or “worthy to be remembered”).

    So: Do Racing Stripes Make a Car Go Faster?

    When being pressed for a quick answer, most of us would likely go for a “No”.

    On second thought, various considerations come into mind:

    • Is the racing stripe applied in addition to the existing paint job?
    • What about the weight of the paint for the racing stripe?
    • What if the stripe is not done in paint, but with adhesive film?
    • Or what if the whole paint job is replaced with a design including a racing stripe – and lighter paint is used?
    • How much does the weight of the paint influence the car’s performance, anyway?

    Many well-intentioned questions, unfortunately focusing too much on details. On the other hand, one important question is missing: What kind of “car” are we talking about here, exactly?

    What if the question is actually about toy cars?

    • The car in question is now significantly smaller, while the size of the air molecules stays the same. Does aerodynamics play an important role here?
    • Does the car have an engine? If not, is the car intended to roll down a hill? Maybe then more weight could be beneficial?

    But: Are we not missing something here? If “car” was not what we expected, are our assumptions about “faster” still valid?

    • What do we know about the context of the question?
    • How exactly is “faster” determined?

    Now imagine the following situation: You give two identical toy cars to a young child with an interest in cars. One car has a racing stripe (or a cool design in general), the other does not.

    Now you ask the child “which car is faster?”, and if the child answers “the one with the stripes”, then this is the correct answer (for this child).

    From our grown-up point of view, the faster car is the one that travels more units of distance per unit time. From the child’s point of view, the car that wins the race in their mind (“wroom, wroom!”) is the faster one. The laws of physics do not necessarily apply here.

    Lessons learned

    Be careful with your assumptions

    Your personal experiences are an important foundation, but they can also mislead you. Be careful when assuming facts that were not explicitly mentioned. You may be subject to a misunderstanding. And even if not, “obvious” questions can still yield helpful information. If you have the chance to ask, use the opportunity. You never know when a question like “just to be sure, we are talking about X that does Y?” is answered “yes, exactly… well, ok, Z should be mentioned, too” – with Z being a vital detail.

    Make sure terms are clearly defined

    When you talk about X, make sure that you and everybody else agrees on what X is exactly. Take the term “combo box”, for example. Even though the word “combo” clearly hints at a combination (of a dropdown list and a text field), some people use the term “combo box” also for simple dropdown boxes.

    From a technical point of view, this may be only “half wrong”, because in certain UI technologies, a combo box control can be turned into a dropdown list as an option.

    But when speaking to colleagues or customers, this may lead to unnecessary confusion or even wrong expectations.

    Be sure to know the context

    A car on the street is different from a toy car in the hands of a young child.

    A software that is a pleasure to use on your laptop while sitting on the couch can be a pain to use on a smartphone while trying to catch a train.

    And for transferring huge amounts of data from location A to location B, a truck full of hard drives can be both a viable solution as well as a terrible idea.

    Context matters. A lot.

    Fully respect the target audience and their view of the world

    This is where empathy comes into play, “the capacity to understand what another person is experiencing from within the other person's frame of reference, i.e., the capacity to place oneself in another's shoes” (quote).

    For technical-minded people, empathy may sound like a touchy-feely topic, “about being nice to people”. For me, empathy in software development is about respect towards users and about being a professional.

    If you are interested, I have published two articles about empathy with a focus on software development here on this blog (“The Importance of Empathy” and “It’s Good for You, Too!”).

  • dotnet Cologne 2020 abgesagt

    Am 29. Mai 2020 hätte die 12. dotnet Cologne-Konferenz stattfinden sollen – und dann kam der Virus. Seit heute ist es nun offiziell, die Konferenz wird für dieses Jahr abgesagt. Ich selbst habe die Community-Konferenz “von Entwicklern, für Entwickler” 2009 mitbegründet und mit viel Spaß und Begeisterung bis 2017 begleitet. Nach meinem Ausstieg aus Zeitgründen konnte ich die dotnet Cologne als Sprecher genießen und wäre auch 2020 mit einem Vortrag am Start gewesen.

    Aus eigener Erfahrung weiß ich, welche Vorlaufzeiten eine Konferenz mit 450 Teilnehmern erfordert, entsprechend kam eine Verschiebung in den Herbst nicht in Frage. Wollen wir hoffen, das wir Sars-CoV-2 gut überstehen und uns auf die dotnet Cologne 2021 am 7. Mai 2021 freuen können.

  • Launch Windows Terminal from Emaroo

    Emaroo is a free utility for browsing most recently used (MRU) file lists of programs like Visual Studio, VS Code, Word, Excel, PowerPoint, Photoshop, Illustrator and more. Quickly open files, jump to their folder in Windows Explorer, copy them (and their path) to the clipboard - or run your own tools on files and folders with custom actions! 

    Download Emaroo on www.roland-weigelt.de/emaroo

    Custom action for Windows Terminal

    This custom action will open the Windows Terminal in the directory of the currently selected item (tested with Preview v0.9.433.0)

    20200219_WT_Use

    How to add the custom action

    Copy the following text into the clipboard:

    §§!F5[*J,mvXzzR?kMYxAy.pM#x1FkZTe*n?d3a#u^[?N^ZKFdQKYe|}Qt?atakKFwNwoCXCJX*iF;lO
    tCo57uK:568nq7LG*=^ctZtQ5k6!,cjG5C2M6*snN,D48|-q:y01mlN^y(rkgxt~A_]mYA3ZkY6!fKX5
    ;WsSVpA^]I|+#|E%:DJ_LGLP.2*$M3Ad2~]cow]pWH!o!!§§

    Open Emaroo and click on the “20200219_WT_SettingsIcon” icon to switch to the settings tab.

    Emaroo recognizes the text in the clipboard as a custom action and lets you paste it into the list of actions for the selected application (in this case Visual Studio Community 2019).

    20200219_WT_Paste1

    Here’s the result:

    20200219_WT_Paste2


    How to use the custom action

    On the tab with the application’s most recently used items, the custom action is either available via the context menu of the item (shown in the first screenshot) or at the bottom of the window:

    20200219_WT_Action1

    You can either click it:

    20200219_WT_Action2

    Or press Ctrl+<number> (in this case Ctrl+1).


    How to edit the custom action

    Right-click the custom action to open the context menu or switch to the settings tab and select the custom action. In both cases, click the “20200219_WT_EditIcon” icon to open the edit dialog:

    20200219_WT_Edit

    Note: At the time of this writing, checking “Run as administrator” does not work for Windows Terminal (see issue #3145 on microsoft/terminal).

  • Quick #if … #endif in Visual Studio

    In my previous blog post “Commenting out Code in C# (Oldie but Goldie Tip)” I recommended using “#if … #endif” to disable code lines.

    A quick way to do this in Visual Studio:

    • Select the code lines you want to disable.
    • Hit Ctrl+K, Ctrl+S to open the following popup:
      20200102_Popup
    • Press the Down Arrow key, then Enter.
    • Enter an undefined symbol name (e.g. DISABLED). Note: the default is “true”, which is defined and thus does not disable the code).
  • Commenting out Code in C# (Oldie but Goldie Tip)

    I usually “comment out” code

    • when something does not work as expected and I am looking for a workaround, or
    • when I want to quickly try out an idea for an alternative solution

    and

    • I feel that using source control is too much hassle for what I’m trying to do in this situation.

    How?

    The term “comment out” obviously comes from using code comments to hide source code from the compiler. C# has two kinds of comments that can be used for this, each with their pros and cons:

    • End-of-line comments (“// this is a comment”) can be nested, i.e. you can comment a commented line. But they do not explicitly convey the start and the end of a commented section when looking at adjacent commented lines.
    • Block comments (“/* this is a comment, possibly spanning several lines */”) do have an explicit start and end, but cannot be nested; you cannot surround a block comment with a block comment.

    For disabling full lines of code, a different approach is to use the “#if … #endif” directive which tells the C# compiler to consider the code inside only if the specified symbol is defined. So to disable code, you simply specify a symbol that does not exist:

    #if SOME_UNDEFINED_SYMBOL
    	...one or more lines...
    #endif
    

    Using the preprocessor for this purpose is nothing new, especially to those with a C/C++ background. On the other hand, considering how often I watch developers use comments in situations where “#if” would have clear advantages, maybe this blog post benefits at least some people out there.

    Why #if?

    “#if” directives can be nested

    The C# compiler does not blindly look for a closing “#endif”; it understands which “#endif” belongs to which “#if”, so e.g. the following is possible:

    #if DISABLED
    	...some code...
    #if DEBUG
    	...debug code...
    #endif
    	...some code...
    #endif
    

    You can express why you disabled a specific block of code

    For example, you could use “#if TODO” when you are working on new code, but want to quickly run the program again without that code before continuing.

    Something like “#if TO_BE_DELETED” (or maybe “#if DEL” for less typing) could mark code that you intend to remove after compiling the project and running the unit tests. If you are consistent with the naming of the symbol, performing a cleanup across the project is easy, because searching for “#if SYMBOL“ works well.

    Obviously, you could choose more descriptive symbols (e.g. “#if TODO_DATA_ACCESS” and “#if TODO_CACHING") to differentiate different places of ongoing work. But if you think you need this, it could be a sign you are trying to juggle too many balls at once.

    “#else” is great while you work on replacing code

    Sometimes, when I have to deal with a non-obvious or even buggy third-party API, writing code involves a lot of experimentation. Then I like to keep old code around as a reference for a moment:

    #if OLD_WORKAROUND
    	...old code...
    #else
    	...new code...
    #endif
    

    You can easily toggle code on/off

    You can enable the disabled code simply by defining the symbol, either using “#define” in the same source file or as a conditional compilation symbol for the whole project.

    Alternatively, you can invert the condition for a specific “#if” with a “!” in front of the symbol:

    #if !DISABLED
    	...some code...
    #endif
    

    Being able to quickly switch code off and back on, or – in conjunction with “#else” – to switch between old and new code without losing the exact start and end positions of the code blocks is a huge plus. I use this e.g. when working on interactions in GUIs where I have to decide whether the old or the new code makes the user interface “feel” better.

    Notes on disabled code

    • Disabled code in a code base should always be viewed as a temporary measure, because code that never gets compiled “rots” over time. To the compiler/IDE, disabled code is plain text that is not affected by refactoring or renaming, i.e. at some point it is no longer valid.
    • Try to get rid of disabled code as soon as possible, preferably before committing to source control.
    • Last, but not least: Consider using source control for what it is good at - it exists for a reason. For instance, when experiments involve changes to many files, a new branch may be the better choice.
  • Notes on Migrating a WPF Application to .NET Core 3.0

    Recently, I migrated a WPF application from .NET Framework 4.7.2 to .NET Core 3.0, which took me about two hours in total. The application consisted of four assemblies (one EXE, one UI class library, two other library assemblies).

    A Google search for “migrate wpf to .net core 3” brings up enough helpful documentation to get started, so this post is mainly a reminder to myself what to look out for the next migration – which may be far enough in the future that I’ll have forgotten important bits by then.

    How to get started

    My starting point was the Microsoft article “Migrating WPF apps to .NET Core”. A lot of text, which makes it easy to get impatient and start just skimming for keywords. Tip: Do take your time.

    In my case, I somehow missed an important detail concerning the AssemblyInfo.cs file at first.

    AssemblyInfo.cs

    When you migrate to .NET Core and use the new project file format, you have to decide what you want to do about the AssemblyInfo.cs file.

    If you create a new .NET Core project, that file is autogenerated from information you can enter in the “Packages” section of the project properties in Visual Studio (the information is stored in the project file).

    In the .NET Framework version of the UI class library, I used the attributes XmlnsDefinition and XmlnsPrefix to make the XAML in the EXE project prettier. That’s why I wanted to keep using an AssemblyInfo.cs file I could edit manually. For this, I had to add the following property:

    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>

    PreBuild Step

    I use a pre-build step in my UI library project (calling a tool called ResMerger), After the migration, macros like (ProjectDir) were no longer resolved. The migration document does not cover pre/post build events (at the time of this writing). But the article “Additions to the csproj format for .NET Core” does, in the section “Build events”.

    PreBuild event in the old csproj file:

    <PropertyGroup>
        <PreBuildEvent>"$(SolutionDir)tools\ResMerger.exe" "$(ProjectDir)Themes\\" $(ProjectName) "Generic.Source.xaml" "Generic.xaml"
        </PreBuildEvent>
    </PropertyGroup>

    And in the new csproj file:

    <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
        <Exec Command="&quot;$(SolutionDir)tools\ResMerger.exe&quot; &quot;$(ProjectDir)Themes\\&quot; $(ProjectName) Generic.Source.xaml Generic.xaml" />
    </Target>

    Note that the text of the PreBuild step is now stored in an XML attribute, and thus needs to be XML escaped. If you have a lot of PreBuild/PostBuild steps to deal with, it’s maybe a good idea to use the Visual Studio UI to copy and paste the texts before and after migrating the project file.

    WPF Extended Toolkit

    My application uses some controls from the Extended WPF Toolkit. An official version for .NET Core 3 has been announced but is not available yet. For my purposes I had success with a fork on https://github.com/dotnetprojects/WpfExtendedToolkit, your experience may be different.

    Switching git branches

    Switching back and forth between the git branches for the .NET framework and .NET Core versions inside Visual Studio results in error messages. To get rid of them, I do the typical troubleshooting move of exiting Visual Studio, deleting all bin and obj folders, restarting Visual Studio and rebuilding the solution. Fortunately, I don’t need to maintain the .NET framework in the future. The migration is done and I can move on.

    Automating the project file conversion

    For the next application, I’ll likely use the Visual Studio extension “Convert Project To .NET Core 3” by Brian Lagunas. I installed the extension and ran it on a test project – a quick look at the result was promising.

  • JSON Serialization in .NET Core 3: Tiny Difference, Big Consequences

    Recently, I migrated a web API from .NET Core 2.2 to version 3.0 (following the documentation by Microsoft). After that, the API worked fine without changes to the (WPF) client or the controller code on the server – except for one function that looked a bit like this (simplified naming, obviously):

    [HttpPost]
    public IActionResult SetThings([FromBody] Thing[] things)
    {
        ...
    }

    The Thing class has an Items property of type List<Item>, the Item class has a SubItems property of type List<SubItem>.

    What I didn’t expect was that after the migration, all SubItems lists were empty, while the Items lists contained, well, items.

    But it worked before! I didn’t change anything!

    In fact, I didn’t touch my code, but something else changed: ASP.NET Core no longer uses Json.NET by NewtonSoft. Instead, JSON serialization is done by classes in the new System.Text.Json namespace.

    The Repro

    Here’s a simple .NET Core 3.0 console application for comparing .NET Core 2.2 and 3.0.

    The program creates an object hierarchy, serializes it using the two different serializers, deserializes the resulting JSON and compares the results (data structure classes not shown yet for story-telling purposes):

    class Program
    {
        private static Item CreateItem()
        {
            var item = new Item();
            item.SubItems.Add(new SubItem());
            item.SubItems.Add(new SubItem());
            item.SubItems.Add(new SubItem());
            item.SubItems.Add(new SubItem());
            return item;
        }
        static void Main(string[] args)
        {
            var original = new Thing();
            original.Items.Add(CreateItem());
            original.Items.Add(CreateItem());
    
    
            var json = System.Text.Json.JsonSerializer.Serialize(original);
            var json2 = Newtonsoft.Json.JsonConvert.SerializeObject(original);
            Console.WriteLine($"JSON is equal: {String.Equals(json, json2, StringComparison.Ordinal)}");
            Console.WriteLine();
    
            var instance1 = System.Text.Json.JsonSerializer.Deserialize<Thing>(json);
            var instance2 = Newtonsoft.Json.JsonConvert.DeserializeObject<Thing>(json);
    
            Console.WriteLine($".Items.Count: {instance1.Items.Count} (System.Text.Json)");
            Console.WriteLine($".Items.Count: {instance2.Items.Count} (Json.NET)");
            Console.WriteLine();
            Console.WriteLine($".Items[0].SubItems.Count: {instance1.Items[0].SubItems.Count} (System.Text.Json)");
            Console.WriteLine($".Items[0].SubItems.Count: {instance2.Items[0].SubItems.Count} (Json.NET)");
        }
    }

    The program writes the following output to the console:

    JSON is equal: True
    
    .Items.Count: 2 (System.Text.Json)
    .Items.Count: 2 (Json.NET)
    
    .Items[0].SubItems.Count: 0 (System.Text.Json)
    .Items[0].SubItems.Count: 4 (Json.NET)
    

    As described, the sub-items are missing after deserializing with System.Text.Json.

    The Cause

    Now let’s take a look at the classes for the data structures:

    public class Thing
    {
        public Thing()
        {
            Items=new List<Item>();
        }
    
        public List<Item> Items { get; set; }
    }
    
    public class Item
    {
        public Item()
        {
            SubItems = new List<SubItem>();
        }
    
        public List<SubItem> SubItems { get; }
    }
    
    public class SubItem
    {
    }

    There’s a small difference between the two list properties:

    • The Items property of class Thing has a getter and a setter.
    • The Subitems property of class Item only has a getter.

    (I don’t even remember why one list-type property does have a setter and the other does not)

    Apparently, Json.NET determines that while it cannot set the SubItems property directly, it can add items to the list (because the property is not null).

    The new deserialization in .NET Core 3.0, on the other hand, does not touch a property it cannot set.

    I don’t see this as a case of “right or wrong”. The different behaviors are simply the result of different philosophies:

    • Json.NET favors “it just works.”
    • System.Text.Json works along the principle “if the property does not have a setter, there is probably a reason for that.”

    The Takeways

    1. Replacing any non-trivial library “A” with another library “B” comes with a risk.
    2. Details. It’s always the details.
    3. Consistency in your code increases the chances of consistent behavior when something goes wrong.