﻿<?xml version="1.0" encoding="utf-8"?>
<rss
  version="2.0">
  <channel>
    <title>RTUR.NET</title>
    <link>http://rtur.net/</link>
    <description>This site is all about developing web applications with focus on designing and building open source blogging solutions. Technologies include ASP.NET Core, C#, Angular, JavaScript and more.</description>
    <lastBuildDate>Fri, 22 May 2020 02:35:03 Z</lastBuildDate>
    <item>
      <guid
        isPermaLink="true">http://rtur.net/posts/adding-blazor-webassembly-to-existing-mvc-application</guid>
      <link>http://rtur.net/posts/adding-blazor-webassembly-to-existing-mvc-application</link>
      <title>Adding Blazor WebAssembly to Existing MVC Application</title>
      <description>&lt;p&gt;If you watched brilliant as always &lt;a href="https://t.co/VdXzq71lV6?amp=1"&gt;presentation&lt;/a&gt; Steve Sanderson gave at recent Build about Blazor WebAssembly release to the world, you probably getting curious and tempted to kick the tires on that new shiny thing. And if you already have existing MVC application - how hard it would be to add client-side Blazor to it?&lt;/p&gt;
&lt;h3 id="get-up-to-date"&gt;Get up to date&lt;/h3&gt;
&lt;p&gt;First of all, Blazor web assembly requires at least .NET Core version &lt;code&gt;3.1.300&lt;/code&gt; installed, so install it from &lt;a href="https://dotnet.microsoft.com/download"&gt;MS site&lt;/a&gt; and, for Visual Studio, upgrade it to &lt;code&gt;16.6&lt;/code&gt; and up.&lt;/p&gt;
&lt;h3 id="mvc-application"&gt;MVC Application&lt;/h3&gt;
&lt;p&gt;Blazor is cross-platform .NET Core framework, so MVC site should be compatible. There is a link to Github repo in the end of this post to verify versions if in doubt. For the new app, I would assume any version greater than &lt;code&gt;3.1.300&lt;/code&gt; will do.&lt;/p&gt;
&lt;h3 id="add-blazor-web-assembly-app"&gt;Add blazor web assembly app&lt;/h3&gt;
&lt;p&gt;For my test, I just added default Blazor application to the solution, so that I have &lt;code&gt;MvcApp&lt;/code&gt; and &lt;code&gt;BlazorApp&lt;/code&gt; in the same folder under the same solution. As described in the &lt;code&gt;up to date&lt;/code&gt; section, you need VS version &lt;code&gt;16.6&lt;/code&gt; for this project to be available in the selection.
&lt;img src="/data/rtur/2020/5/blazor-project.jpg" alt="data/rtur/2020/5/blazor-project.jpg" /&gt;&lt;/p&gt;
&lt;h3 id="configure-blazor-app"&gt;Configure Blazor App&lt;/h3&gt;
&lt;p&gt;Blazor WebAssembly is a client-side technology, to host it in the server-side MVC application get Http Extensions package:
&lt;img src="/data/rtur/2020/5/blazor-app-proj.jpg" alt="data/rtur/2020/5/blazor-app-proj.jpg" /&gt;
And then configure base address so MVC application knows what endpoint should be called:
&lt;img src="/data/rtur/2020/5/blazor-program.jpg" alt="data/rtur/2020/5/blazor-program.jpg" /&gt;&lt;/p&gt;
&lt;h3 id="configure-mvc-app"&gt;Configure MVC App&lt;/h3&gt;
&lt;p&gt;MVC application needs &lt;code&gt;WebAssembly.Server&lt;/code&gt; package and reference to the &lt;code&gt;BlazorApp&lt;/code&gt; project:
&lt;img src="/data/rtur/2020/5/mvc-app-proj.jpg" alt="data/rtur/2020/5/mvc-app-proj.jpg" /&gt;
And now just add Blazor framework in the &lt;code&gt;Configure&lt;/code&gt; method and add endpoint to the routes:
&lt;img src="/data/rtur/2020/5/mvc-startup.jpg" alt="data/rtur/2020/5/mvc-startup.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;With this, you should be able to browse your MVC application as you would normally do and, in addition, any valid route in the Blazor project would also be available. For example &lt;code&gt;/home/privacy&lt;/code&gt; will load MVC server-side page served by controller and &lt;code&gt;/counter&lt;/code&gt; will load from web assembly straight from the browser. Crazy.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;For real app you would probably want at least to tweak routing and add shared authorization, but that's outside of scope here. This is just to get up and running quickly with minimum effort so that you can check and decide if you really need it and if it works for you.&lt;/p&gt;
&lt;p&gt;The solution &lt;a href="https://github.com/rxtur/blazor2mvc"&gt;available on Github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/rxtur/blazor2mvc/commit/1e9453baf31e07379c51446694c8f4794c6a3d69"&gt;This commit&lt;/a&gt; includes all the changes described in the post.&lt;/p&gt;
</description>
      <pubDate>Fri, 22 May 2020 02:35:03 Z</pubDate>
    </item>
    <item>
      <guid
        isPermaLink="true">http://rtur.net/posts/open-source-project-structure</guid>
      <link>http://rtur.net/posts/open-source-project-structure</link>
      <title>Open Source Project Structure</title>
      <description>&lt;h3 id="do-as-the-romans-do"&gt;Do as the Romans do&lt;/h3&gt;
&lt;p&gt;Most open source projects follow common pattern for directory structure which looks somewhat like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;build
dist
samples
src
tests
readme.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes sense and easy to understand - you put your source files under &lt;code&gt;src&lt;/code&gt;, distribution files go to &lt;code&gt;dist&lt;/code&gt;, unit tests under &lt;code&gt;tests&lt;/code&gt;. If you have a build utility it goes under &lt;code&gt;build&lt;/code&gt;, samples take their own place and so on. Nothing wrong with that, so I mostly follow it for Blogifier and ended up with this structure:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;docs
plugins
    common
src
    app
    core
tests
    core.tests
readme.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;docs&lt;/code&gt; has documentation in markdown format. I decided against using Github's wiki, because documentation is version specific and keeping it in the wiki would require maintain all the version differences. This way, you pull docs along with source code and, at least in theory, they match.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;plugins&lt;/code&gt; would have any extensions to the core functionality, like custom themes and widgets. The &lt;code&gt;common&lt;/code&gt; project has all the common plugins, to build new developer would add another project here. Resulted DLL could be side-loaded at run time, so down the road it could be pulled from common store or repository.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src&lt;/code&gt; has two projects. One is &lt;code&gt;app&lt;/code&gt; which is web application or blog itself. It is ASP.NET Core application utilizing MVC for the blog features and Razor Pages for admin panel. Distinction is because MVC ideal for manipulating blog routes and plug in on the fly themes and widgets. Razor Pages are great in separating application by features so it can be structured really nicely. Second project here is &lt;code&gt;core&lt;/code&gt; - class library with core back-end functionality (services, data access etc.).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tests&lt;/code&gt; currently has unit tests mostly covering logic in services. If there need for example to have integration testing, the &lt;code&gt;app.tests&lt;/code&gt; project can be added here. Same goes for plugins - you write your plugin - you can add tests for it here, in &lt;code&gt;plugin-name.tests&lt;/code&gt; project.&lt;/p&gt;
&lt;h3 id="solution-folders"&gt;Solution Folders&lt;/h3&gt;
&lt;p&gt;Visual Studio has a feature called &lt;code&gt;solution folders&lt;/code&gt; - whenever you add new project to solution it is added as virtual folder, no physical folder actually added to the file system. At least not where you would expect it. This can be very misleading and, when you switch to file-based editor, like VS Code or Atom, your projects look messed up. To avoid this, I always create physical folder and select it for new project so that virtual and physical match.&lt;/p&gt;
&lt;h3 id="deep-nesting"&gt;Deep nesting&lt;/h3&gt;
&lt;p&gt;Not a fan of deep nesting, that's why &lt;code&gt;tests&lt;/code&gt; and &lt;code&gt;plugins&lt;/code&gt; at top level instead of nesting under &lt;code&gt;src&lt;/code&gt;. Just a personal preference.&lt;/p&gt;
&lt;h3 id="documentation"&gt;Documentation&lt;/h3&gt;
&lt;p&gt;I started by creating wiki pages in Github, but quickly realized this is going to be hard to keep up with changes in every version. So instead of having &lt;code&gt;this only applies for version 1.2.3&lt;/code&gt; statements, all documentation is going into &lt;code&gt;docs&lt;/code&gt; folder and naturally version specific. Just as &lt;code&gt;readme.md&lt;/code&gt; it is all in markdown format.&lt;/p&gt;
&lt;p&gt;Hope this makes sense and easy to follow, if you have better ideas comments are welcome.&lt;/p&gt;
</description>
      <pubDate>Fri, 14 Sep 2018 02:33:34 Z</pubDate>
    </item>
    <item>
      <guid
        isPermaLink="true">http://rtur.net/posts/asp.net-core-on-digitalocean-jumpstart</guid>
      <link>http://rtur.net/posts/asp.net-core-on-digitalocean-jumpstart</link>
      <title>ASP.NET Core on DigitalOcean Jumpstart</title>
      <description>&lt;p&gt;There is decent amount of documentation on how to install, configure and run .NET Core on Linux, but most of it pretty generic and may be a little overwhelming when you trying to accomplish specific and relatively simple task. So I decided to put this together in case I need to go over it again or maybe someone else can use it. This not by any means an extensive tutorial on how to setup things in production secure environment, it is how to get you started quickly and (relatively) easily so you won't drop it discouraged and frustrated half way through.&lt;/p&gt;
&lt;h3 id="your-digitalocean-account"&gt;Your DigitalOcean account&lt;/h3&gt;
&lt;p&gt;I&amp;quot;m using &lt;a href="https://www.digitalocean.com"&gt;DigitalOcean&lt;/a&gt; here as example, but obviously this can be any Linux based hosting. Get your hosting account, in my case I got cheapest &amp;quot;droplet&amp;quot; available running Ubuntu 18.04 ($5 monthly). Once you'll get this, host should provide info on how to connect to your server remotely, usually using SSH. To make things simpler, I run Linux subsystem on my Windows machine so I can use &amp;quot;Bash on Ubuntu&amp;quot; utility and run SSH command straight from there.&lt;/p&gt;
&lt;h3 id="installing.net-core"&gt;Installing .NET Core&lt;/h3&gt;
&lt;p&gt;Microsoft likes to change links, so this may on may not work, but you can go to &lt;a href="https://www.microsoft.com/net/learn/get-started-with-dotnet-tutorial"&gt;this tutorial&lt;/a&gt;, select your OS and run through it all the way, installing latest version of .NET Core and testing it with &amp;quot;hello world&amp;quot; console application. Make sure you can run it and all works fine.&lt;/p&gt;
&lt;h3 id="installing-sqlite-optionally"&gt;Installing SQLite (optionally)&lt;/h3&gt;
&lt;p&gt;Because my web application requires SQLite database, I'm also installing it here.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;sudo apt-get update
sudo apt-get install sqlite3 libsqlite3-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="testing-your-app-locally"&gt;Testing your app locally&lt;/h3&gt;
&lt;p&gt;Publish your ASP.NET Core application to local folder, so you can run it locally before deployment and verify it works correctly from command line. In Visual Studio, you simply select your app in solution explorer, right-click and publish to folded. When done, open command line terminal (CMD utility) and navigate to that folder. Given my application name is &amp;quot;App&amp;quot; and it compiled into &amp;quot;App.dll&amp;quot; this will look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;dotnet App.dll
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should start application running and you should be able open web browser and navigate to &lt;code&gt;http://localhost:5000&lt;/code&gt;. If your app runs and looks right, congrats - you ready to deploy it to remote host server!&lt;/p&gt;
&lt;h3 id="deployment"&gt;Deployment&lt;/h3&gt;
&lt;p&gt;Filezilla is still the best FTP client for me, but you can use whatever you want - create new folder in your host server, I used &amp;quot;blog&amp;quot;, and copy all files from your local published directory to your server. For ASP.NET Core important thing is, your published directory must include &lt;code&gt;refs&lt;/code&gt; and &lt;code&gt;runtimes&lt;/code&gt; folders. So somewhat like this:&lt;/p&gt;
&lt;img src="/data/rtur/2018/9/published-dir.png" alt="published-dir.png" style="width:100%" /&gt;
&lt;p&gt;(you may not have other folders, like &amp;quot;views&amp;quot;, if you choose to pre-compile app on publish, but I personally like to have them)&lt;/p&gt;
&lt;h3 id="testing-remote-app"&gt;Testing remote app&lt;/h3&gt;
&lt;p&gt;Now we want to test remote app just as we did locally, to make sure it works fine on your host server. So back to command line, remote to server and go to your app directory, for me it is &lt;code&gt;cd blog&lt;/code&gt;. Run app:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;dotnet App.dll
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, you should see familiar trace and app should start and run on port &lt;code&gt;5000&lt;/code&gt;. But if you go to web browser and navigate to it using &lt;code&gt;http://&amp;lt;your.ip&amp;gt;:5000&lt;/code&gt; you won't see it - your server is not set for it just yet. So to verify it is really working and serving pages, you can request page locally - open another remote terminal and run this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;curl http://locahost:5000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see HTML output in the terminal screen, your app is working properly. It is just not available for outside world yet. Moving on.&lt;/p&gt;
&lt;h3 id="installing-nginx"&gt;Installing Nginx&lt;/h3&gt;
&lt;p&gt;To access our web application we need to run a web server, Nginx is a good candidate for the job. Installing it simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;sudo -s
nginx=stable
add-apt-repository ppa:nginx/$nginx
apt-get update
apt-get install nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To verify install:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;nginx start
http://&amp;lt;your.ip.here&amp;gt;/index.nginx-debian.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you should see welcome screen:&lt;/p&gt;
&lt;p&gt;&lt;img src="/data/rtur/2018/9/nginx.PNG" alt="data/rtur/2018/9/nginx.PNG" /&gt;&lt;/p&gt;
&lt;p&gt;Almost there! Now we need to tell Nginx to run our application. For that, we need to edit configuration file.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;nano /etc/nginx/sites-available/default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The end result for edited file should look similar to this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;server {
    listen 80;
    location / {
        proxy_pass http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This basically telling Nginx to use your application running on port &lt;code&gt;5000&lt;/code&gt; as web root, and you finally can go in the browser to your server's address (&lt;code&gt;http://&amp;lt;your.ip.here&amp;gt;&lt;/code&gt;) and see application in all glory.&lt;/p&gt;
&lt;h3 id="run-as-a-service"&gt;Run as a Service&lt;/h3&gt;
&lt;p&gt;Now it all works, but starting application from command line every time is annoying at best. To make is start automatically it has to run as Linux service. First thing is to create service - fortunately, this as simple as creating a text file in specific directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;cd /etc/systemd/system
sudo nano blog.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This opens up text editor, here is content I used for my blog app:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;[Unit]
Description=Blog Application

[Service]
WorkingDirectory=/root/blog
ExecStart=/usr/bin/dotnet /root/blog/App.dll
Restart=always
RestartSec=10
SyslogIdentifier=blog
User=root
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save this, and now to test whole thing with DigitalOcean I enable service and then just go and reboot:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;systemctl enable blog.service
reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When server comes live, I should just navigate to my site in web browser and, if all works fine, it'll just load.
That's quick and dirty and this is all for now.&lt;/p&gt;
</description>
      <pubDate>Thu, 06 Sep 2018 02:52:36 Z</pubDate>
    </item>
    <item>
      <guid
        isPermaLink="true">http://rtur.net/posts/note-taking-apps</guid>
      <link>http://rtur.net/posts/note-taking-apps</link>
      <title>Note Taking Apps</title>
      <description>&lt;p&gt;These days we all overwhelmed with information, and keeping what's important within reach is essential for functioning as successful human being. Some still use paper notebooks, like animals, but I won't go there. Here is what I used over time to keep up with everything I need to remember.&lt;/p&gt;
&lt;h2 id="evernote"&gt;Evernote&lt;/h2&gt;
&lt;p&gt;Back in the day, it was no-brainer - the &lt;a href="https://evernote.com/"&gt;best-in-class&lt;/a&gt; (arguably, it created this class), innovative, fun, and enjoyable to use. Unfortunately, they couldn't figure out how to monetize it, and eventually, they chose the dumbest way possible by making the free version completely useless and the paid version prohibitively expensive. Usage plummeted, people went away, and stopped paying attention. Every time I read about it in the news later on, it's all pain and struggle.&lt;/p&gt;
&lt;h2 id="onenote"&gt;OneNote&lt;/h2&gt;
&lt;p&gt;After the Evernote disaster, I moved to Microsoft's &lt;a href="https://onenote.com/"&gt;OneNote&lt;/a&gt;. It is solid, well integrated with Office and feature rich. A little boring, perhaps, but professional and familiar to every Office user, which is practically every adult on this planet. I would probably stop there, but it has one weird issue which is a deal breaker for me - it can't handle multiple accounts. Just like Evernote, it is cloud-based, meaning all content is saved somewhere on the Internet, and you just log in with your MS account and sync it to your computer's folder. That works great when you have single account, you just set and forget, all works fine. But if you have multiple and need to switch, bad things happen. And I have both work and personal accounts, to start with.&lt;/p&gt;
&lt;p&gt;Another bizarre thing is the schizophrenic development with two different client implementations fighting each other. There is old desktop OneNote program that looks like it was built for Windows 98 and new modern version, stupidly named &amp;quot;OneNote for Windows 10&amp;quot; - this looks pretty and consistent with apps on phones and tablets. Apparently, according to MS, the old one is the future. Go figure...
So my solution was to keep using OneNote at work and find something else for personal use.&lt;/p&gt;
&lt;h2 id="notion"&gt;Notion&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.notion.so/"&gt;This one&lt;/a&gt; is also cloud-based and, probably, most popular. Their marketing team is really, really good. YouTube is filled with videos on &amp;quot;how I plan my life with Notion&amp;quot; sponsored by, you guessed it, Notion. But for some reason it never clicked with me. I don't know why, it is good, but I always felt like I'm spending way too much time organizing my notes and then trying to find them. Felt a little too slow and complex for my taste, probably I'm not enough detailed-oriented for this workflow. Anyway, I honestly tried to make it work and failed. But it should work just fine for most people I think.&lt;/p&gt;
&lt;h2 id="mem"&gt;Mem&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://mem.ai/"&gt;Mem&lt;/a&gt;, it seems, was specifically designed to use with AI. It differ from others like early Google differed from Yahoo &amp;quot;internet directory&amp;quot;. Instead of relying on structure with folder tree, tags, links and so on, it just heavily leans on search. You pretty much throw all into big pile and then have various ways to fish it out with smart queries. When used with AI, it is like you ask a person to find something, even vaguely related, and it does pretty good job displaying all relevant notes. Nice.&lt;/p&gt;
&lt;p&gt;The catch is, AI part is not free and without AI it is not that impressive. So my current approach is to wait and see if it is useful at all after free AI trial expires.&lt;/p&gt;
&lt;h2 id="obsidian"&gt;Obsidian&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://obsidian.md/"&gt;Obsidian&lt;/a&gt; is not perfect, sometimes it drives me nuts, but it has lots of things I really like.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local first. It is basically glorified Markdown editor that works with local folder. All your data belongs to you.&lt;/li&gt;
&lt;li&gt;Private and secure. You can keep all your notes on the hard drive, thumb-drive, your own NAS or whatever. No one parses your data, sells it to advertisers or our robot overlords (looking at your, Chat GPT).&lt;/li&gt;
&lt;li&gt;Future proof. You can keep current version forever and ever, and it will always work with your notes.&lt;/li&gt;
&lt;li&gt;Crazy customizable - detailed settings, many themes and plugins. You can even build your own having limited programming skills.&lt;/li&gt;
&lt;li&gt;Very powerful, specially with growing number of plugins created by community.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There some cons, too.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To monetize, project provides cloud-based storage with sync and some other services as paid subscription. It means, syncing across devices for free is limited. I would want to use my own NAS as &amp;quot;cloud&amp;quot;, but iCloud is the only option to sync to my phone or tablet. This defeats most privacy and security advantages, and iCloud has some issues - like creating duplicate notes once in a while, for example.&lt;/li&gt;
&lt;li&gt;As project grows, it becomes more complex and, as a result, more buggy and harder to use. I'm running into issues with UI and sync more often lately.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just as Notion, as you invest time and effort into bending it to your needs, it is getting better, easier to use and harder to justify the switch to another note taking app. Overall, Obsidian's pros overweight cons by far and I'll keep using it for time being.&lt;/p&gt;
</description>
      <pubDate>Sun, 30 Jul 2023 15:52:28 Z</pubDate>
    </item>
    <item>
      <guid
        isPermaLink="true">http://rtur.net/posts/on-dangers-and-joys-of-modernizing-your-net-application</guid>
      <link>http://rtur.net/posts/on-dangers-and-joys-of-modernizing-your-net-application</link>
      <title>On Dangers and Joys of Modernizing your .NET Application</title>
      <description>&lt;p&gt;With all the buzz around social media lately, where people jumping on the next Twitter Killer every week or so, I thought it would be appropriate to shake the dust off my old trusty blog. It may be not the money-maker as hot account at huge social site, but no one is going to shadow ban my RSS feed. I hope.&lt;/p&gt;
&lt;p&gt;Sadly, I neglected it for a while and it stuck at ancient .NET 3.1. I needed to bring it to this century somehow. I do have some experience upgrading to newer, latest and greatest version. That didn't feel me with confidence, but a man's gotta do what a man's gotta do. My approach was naive and childish, I guess:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pull old code from Github. I don't have 3.1 on my machine, so VS freaked out with all reds.&lt;/li&gt;
&lt;li&gt;Flip version number in the project files to 7.0.&lt;/li&gt;
&lt;li&gt;Upgrade all NuGet packages to the latest.&lt;/li&gt;
&lt;li&gt;Restore and rebuild.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That should not work under any circumstances, and it didn't. I got about dozen warnings and 2 (two!) errors. I fixed them expecting to get more and more, but it strangely went green and loaded web application just fine. If it's not a miracle, then definitely a success story that I want to share.&lt;/p&gt;
&lt;p&gt;Few things, I think, contributed to it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;At the time of this writing, .NET 7 is a mature platform and all third-party code libraries had enough time to adjust. Pretty sure if I jumped on update when it was new hotness, that wouldn't go well.&lt;/li&gt;
&lt;li&gt;There is nothing fancy and &amp;quot;advanced&amp;quot; in the code, except maybe early server-side Blazor implementation - but it held surprisingly well. Simplicity pays off.&lt;/li&gt;
&lt;li&gt;There is advantage in using enterprise tech - tell what you want about Microsoft, but it will take care of old versions no matter how ugly and bloated new version becomes. Not necessarily a good thing, but in cases like mine here, it helps.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So if there some really old pet project you afraid to touch, it may be not as bad as you might think. Just give it a try.&lt;/p&gt;
</description>
      <pubDate>Sat, 15 Jul 2023 05:37:17 Z</pubDate>
    </item>
    <item>
      <guid
        isPermaLink="true">http://rtur.net/posts/dotnet-application-vs-standard-library</guid>
      <link>http://rtur.net/posts/dotnet-application-vs-standard-library</link>
      <title>DotNet Application vs Standard Library</title>
      <description>&lt;p&gt;Back to basics here. When you have a .NET application and want to add class library to the solution, it will be added as a standard class library. What it means is the project file will have this markup right on top.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt; &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;TargetFramework&amp;gt;netstandard2.1&amp;lt;/TargetFramework&amp;gt;
    &amp;lt;RazorLangVersion&amp;gt;3.0&amp;lt;/RazorLangVersion&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is for a good reason, this project will only have access to basic class library classes (BCL), nothing related to web or GUI which makes it minimalistic and portable across different types of applications. The architecture guidelines for a long time favors layered approach that looks somewhat like this&lt;/p&gt;
&lt;img src="/data/rtur/2020/6/Layers2.jpg" class="center" /&gt;
&lt;p&gt;It does not have to be this straightforward, for example standard Blazor Webassembly template has these project dependencies.&lt;/p&gt;
&lt;img src="/data/rtur/2020/6/wasm.jpg" class="center" /&gt;
&lt;p&gt;Here server-side &lt;code&gt;MVC application&lt;/code&gt; references both &lt;code&gt;Client&lt;/code&gt; and &lt;code&gt;Shared&lt;/code&gt; library projects. But basic principles stay intact - shared library has no clue what is happening upstairs and client has no knowledge of server existence. Well, almost - it still needs to know base &lt;code&gt;API&lt;/code&gt; address to be able to communicate with back-end, but this is loose coupling that does not require hard project reference.&lt;/p&gt;
&lt;p&gt;Now if you look at &lt;code&gt;Blogifier&lt;/code&gt; projects, this looks like this.&lt;/p&gt;
&lt;img src="/data/rtur/2020/6/bf-layers.jpg" class="center" /&gt;
&lt;p&gt;Nice and simple, but... what? Why all three projects are applications? Well, fist of all, &lt;code&gt;Blogifier&lt;/code&gt; supports two different UIs, which is a problem in its own rights but I won't go there just yet. To support multiple UIs you would either have application logic implemented in both, or have a shared application logic that does not fit into standard library. So here we have &lt;code&gt;Blogifier.Core&lt;/code&gt;. At least for now, I'm ok with that. The &lt;code&gt;Blogifier.Widgets&lt;/code&gt;, though, should be a standard library. Why it is not? Because components in the library have CRUD logic and need reference to the Core library, and you can't reference application from standard library project.  Bomber..&lt;/p&gt;
&lt;p&gt;So how to get out of the corner I painted myself into? For starters, I'll need to rethink strategy I get used to - that shared code is where all the heavy lifting and all the smarts should go, so that client can stay light-weight and simple. That makes sense, it promotes building client code by making it easy. But it breaks fundamental principals where application layers have different responsibilities and should not have multiple dependencies.&lt;/p&gt;
&lt;p&gt;I think for the next iteration Blogifier should look similar to the picture below - the Core, at least for some time, will remain an application status, but I want to move models out of the data/domain into its own shared library and, very likely, convert admin panel to web assembly at some point in the near future. Long term, Core should also free itself of any high-level functionality and become standard library, but that can wait. Very basic stuff indeed, but so easy to overlook.&lt;/p&gt;
&lt;img src="/data/rtur/2020/6/bf-wasm.jpg" class="center" /&gt;
</description>
      <pubDate>Wed, 24 Jun 2020 02:17:02 Z</pubDate>
    </item>
    <item>
      <guid
        isPermaLink="true">http://rtur.net/posts/how-to-find-tracking-script-in-google-analytics</guid>
      <link>http://rtur.net/posts/how-to-find-tracking-script-in-google-analytics</link>
      <title>How To Find Tracking Script in Google Analytics</title>
      <description>&lt;p&gt;Every time I go to &lt;a href="https://google.com/analytics"&gt;Google Analytics&lt;/a&gt; to find my tracking script when I renew the site and loose it, I just can't figure out where the damn thing is hiding and have to Google for it. Pretty sure it's not the last hunt, so until the next time let's save it here: Admin -&amp;gt; Tracking info -&amp;gt; Tracking Code. My future self - you are welcome!&lt;/p&gt;
&lt;img src="/data/rtur/2020/5/tracking-script.jpg" class="center" width="750" &gt;
</description>
      <pubDate>Mon, 11 May 2020 14:22:47 Z</pubDate>
    </item>
    <item>
      <guid
        isPermaLink="true">http://rtur.net/posts/html5-starter-template</guid>
      <link>http://rtur.net/posts/html5-starter-template</link>
      <title>HTML5 Starter Template</title>
      <description>&lt;p&gt;There is new theme added to Blogifier that has only two purposes in life - to provide clean, easiest possible HTML5 template for building your own theme from scratch and to run optimization tests. It is a bit ugly and simplistic, so I don't expect anyone using it for a site without customization.
&lt;img src="/data/rtur/2020/5/html5-theme.jpg" alt="data/rtur/2020/5/html5-theme.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Speaking about themes in general, I think it would be best to have a variety ranging from as simple as this one to pretty complex and multimedia rich - like Philosophy and Vizew. I think Blogifier close to having good set of built-in themes to choose from, maybe couple more to come but really close to be done in this department.&lt;/p&gt;
&lt;p&gt;By optimization tests I mean tests to verify if application itself optimized and configured for speed and standards. Most other themes add lots of noise to these tests - not optimized images in the content, custom fonts, JS plug-ins and much more. So, this bare-bones theme would allow to check &lt;strong&gt;application&lt;/strong&gt; performance rather than theme related features, content and your favorite JavaScript / CSS libraries. And although I haven't done much optimizing yet, with all theme-related distractions out of the way Blogifier easily hits a perfect score at Google's page speed test. As I said, there ways to go and tests to run - but its a good start.&lt;/p&gt;
&lt;img src="/data/rtur/2020/5/speed-test.jpg" class="center" &gt;
</description>
      <pubDate>Fri, 08 May 2020 00:55:23 Z</pubDate>
    </item>
    <item>
      <guid
        isPermaLink="true">http://rtur.net/posts/custom-fields-convention</guid>
      <link>http://rtur.net/posts/custom-fields-convention</link>
      <title>Custom Fields Convention</title>
      <description>&lt;p&gt;The custom fields is not a new concept in Blogifier, I've &lt;a href="http://rtur.net/posts/custom-fields"&gt;wrote about it&lt;/a&gt; long ago and it exists and used in the current code base. It looks somewhat like this:&lt;/p&gt;
&lt;img src="/data/rtur/2020/5/custom-fields-table.png" class="center" width="520"&gt;
&lt;p&gt;This is a &lt;code&gt;CustomFields&lt;/code&gt; table and &lt;code&gt;AuthorId&lt;/code&gt; column is actually convention based - if it is &lt;code&gt;0&lt;/code&gt; it means this field is for the blog, if it has value greater than &lt;code&gt;0&lt;/code&gt; then it belongs to the author with specified ID. On the back-end, there methods to get and save blog settings object, where each setting pulled from this table, so that blog theme saved and retrieved for author ID = 0 and field name = &lt;code&gt;blog-theme&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is very generic and it works in simple scenarios, but what if there need for more structural data? For example, I want to add custom field to the post? Well, could add another column like &lt;code&gt;PostId&lt;/code&gt;, but then what if a theme needs a field saved for customization? It can go on and on, resulting in the large, complex and, most importantly, constantly changing table.&lt;/p&gt;
&lt;p&gt;The other way is to add a conventions, just like &lt;code&gt;0&lt;/code&gt; author ID means owned by a blog, same can apply to the field name to be in specific format. For example, for a very common list of social buttons, I want a UI to collect user input. User should be able to enter social account name, like &lt;code&gt;facebook&lt;/code&gt; or &lt;code&gt;twitter&lt;/code&gt;, and link to go when visitor clicks the button. There is also a &lt;code&gt;rank&lt;/code&gt;, to sort buttons as you like it.
&lt;img src="/data/rtur/2020/5/social-field-ui.jpg" alt="data/rtur/2020/5/social-field-ui.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;To make this possible, I would need to follow a convention like &lt;code&gt;social|&amp;lt;name&amp;gt;|&amp;lt;rank&amp;gt;&lt;/code&gt;: &lt;code&gt;social|facebook|1&lt;/code&gt;. And back-end could expose social accounts via helper method. The helper method in the custom fields repository would return convention-based &lt;code&gt;List&amp;lt;SocialField&amp;gt;&lt;/code&gt;, where each item would be extended custom field, with extended properties pulled by parsing the &lt;code&gt;Name&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public class CustomField
{
  public int Id { get; set; }
  public int AuthorId { get; set; }
  public string Name { get; set; }
  public string Content { get; set; }
}

public class SocialField : CustomField
{
  public string Title { get; set; }
  public string Icon { get; set; }
  public int Rank { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the parser itself to map custom field to extended social field.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;foreach	(CustomField field in customFields){
  var fieldArray = field.Name.Split('|');
  if(fieldArray.Length &amp;gt; 2){
    socials.Add(new SocialField{
      Title = fieldArray[1].Capitalize(),
      Icon = $&amp;quot;fa-{fieldArray[1]}&amp;quot;,
      Rank = int.Parse(fieldArray[2]),
      Id = field.Id,
      Name = field.Name,
      AuthorId = field.AuthorId,
      Content = field.Content
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is what social field looks like in the debug watch window:
&lt;img src="/data/rtur/2020/5/social-field-debug.jpg" class="center"&gt;&lt;/p&gt;
&lt;p&gt;On the front-end, theme can add social buttons very easily now.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cmd"&gt;@inject IDataService DataService
@{
  var social = await DataService.CustomFields.GetSocial();
}
&amp;lt;div class=&amp;quot;social-area&amp;quot;&amp;gt;
  @foreach (var field in social)
  {
    &amp;lt;a href=&amp;quot;@field.Content&amp;quot;&amp;gt;&amp;lt;i class=&amp;quot;fa @field.Icon&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;
  }
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If later there a need to add a custom fields to the post, it can be done too, with something like &lt;code&gt;post|&amp;lt;id&amp;gt;|&amp;lt;title&amp;gt;&lt;/code&gt;. Then, similarly, add override with post ID and title properties and, optionally, helper to parse and return new field to make it easier to use in UI. Basic rules stay the same: define a convention and follow it whenever you need custom functionality for your blog. Some common things like social buttons and few others can be added as features to the core application, but others can be done at individual blog or at the theme level, with no need for core application to change.&lt;/p&gt;
</description>
      <pubDate>Tue, 05 May 2020 00:13:17 Z</pubDate>
    </item>
    <item>
      <guid
        isPermaLink="true">http://rtur.net/posts/the-big-reshuffle</guid>
      <link>http://rtur.net/posts/the-big-reshuffle</link>
      <title>The Big Reshuffle</title>
      <description>&lt;p&gt;I thought it will take a whole lot more, but it was surprisingly easy. The big restructure I wanted to do for the Blogifier repository took just a couple of days to complete; splitting into multiple repositories, cleaning and making it all work with each other and publishing to Nuget.org as reusable libraries. So now this all exactly as outlined in the diagram.&lt;/p&gt;
&lt;p&gt;&lt;img src="/data/rtur/2020/4/blogifier-full-diagram2.png" alt="data/rtur/2020/4/blogifier-full-diagram2.png" /&gt;&lt;/p&gt;
&lt;p&gt;One of the reasons for this move is to separate rarely changing functionality from user facing front-end that changes all the time. This also incapsulates things that should not be customized/modified into external packages, where it is harder to change and application gets more stable. It is sort of experimental, I don't know if it will prove successful or not, it could change in the future. For example, I quickly realized that having widgets in its own repository simply inconvenient and leads to duplication and time waste with little advantages, and pushed widgets project back into the web application. Good learning experience nevertheless.&lt;/p&gt;
&lt;p&gt;To sum up, Blogifier is a personal blog application written in ASP.NET Core MVC to serve pages quickly and to make theming easy. The admin panel is all Blazor, so it can be maintained and improved pretty easily given Blazor is very productive and fun to work with.&lt;/p&gt;
&lt;p&gt;The Angular version is not going anywhere - it is a SPA CLI solution running on top of REST APIs, it can be useful beyond it current implementation, with React and Mobile been obvious candidates. So it can evolve along with default MVC version. They could get closer down the road, with just a checkbox for a user to select one or the other. Or they could go completely different routes, time will tell. Bottom line, they both loved and supported.&lt;/p&gt;
</description>
      <pubDate>Thu, 09 Apr 2020 16:22:33 Z</pubDate>
    </item>
  </channel>
</rss>