Saturday, November 28, 2020

Blazor Webassembly notes

Blazor Notes

I believe that Blazor Webassembly apps are now the only sensible way for .NET developers to write web applications. My recent experience with the officially released Blazor Webassembly platform confirms that it is the most productive way of writing apps to run in the web browser. I estimate I can write a typical business app in Blazor about 10 times faster that an equivalent server-side ASP.NET app, and it probably takes about one tenth the amount of code. A similar app created with a JS framework like Angular would require millions of lines of library code, special skill training and tools that are foreign to the .NET ecosystem.

The most stunning advantage of Blazor apps the fact that they are alive inside the browser and have a stateful lifetime, which frees us from the dreadful burden of the primitive request-response pipeline associated with server-side apps like ASP.NET. The server-side request-response model is as primitive as an old CB radio ... send → receive → send → receive ... over and over with a complex pipeline of events and all sorts of tricks required to maintain state and interact with JavaScript on the client.

The Blazor development and runtime model is so simple that I will never write an ASP.NET app of any sort again, they are dead to me. I also hope that JS frameworks will quickly become a footnote in the history of IT under the heading "Idiotic Fads of the 21st Century".

If you need a crash course on Blazor then I highly recommend the set of videos on Carl Franklin's Blazor Train website.

My own hobby app Blazing Hoarder browses the contents of the Nancy Street Collections Database. This app has grown from the very first working Blazor app I ever wrote back in January 2018. The first version was working after a few hours of experiments on a Sunday afternoon, which is much less time than it took to watch the 9 hour long Pluralsight video course on AngularJS (actually, I only watched the first 4 hours before giving-up in disgust at the complex idiocy of it all). The reason I could write my first Blazor app so quickly was because I was using highly familiar tools like Visual Studio, Razor syntax and C#.

Blazor has been officially released for 6 months now (at time of writing), and I can sense the rapid change in public awareness as more and more videos, articles and discussions are appearing and increasing Blazor's popularity.

There are even discussions about the Webassembly virtual machine running on practically any computer or device, which I find a bit troubling, as it wasn't invented for that purpose. I would rather see .NET become a cross-platform standard, as it's far richer and more mature.

Web Services

A Blazor app will typically need to talk to a web service of some kind to do useful work. REST style services are the most popular, and for several years I have been creating them using the Visual Studio ASP.NET Web API project template. However, I hate Web API projects for the same reason I hate ASP.NET app projects: there is a huge amount of repetitive plumbing code and countless mysterious conventions to be followed.

Azure Function Apps with HTTP triggers are an attractive alternative to web services because they are much simpler to write and deploy. They are effectively a group of service endpoints with serverless hosting.

Tuesday, October 20, 2020

Regasm in setup project

 This post is a reminder to myself (and anyone) about how to register a COM visible .NET assembly in a Visual Studio Setup Project (vdproj). Countless pages describe how to register traditional COM server libraries, but it took hours to stumble upon a tiny clue that lead me to the code below that can be placed in a CA (Custom Action) class to register a COM visible assembly.

The rather obscure RegistrationServices class seems to contain the core functionality of the regasm command, so it can be called from inside the CA during install and uninstall processing to register and unregister the library.

[RunInstaller(true)]
public partial class ComCA : Installer
{
	public ComCA()
	{
		InitializeComponent();
	}
	public override void Install(IDictionary stateSaver)
	{
		base.Install(stateSaver);
		var regsrv = new RegistrationServices();
		var asm = GetType().Assembly;
		if (!regsrv.RegisterAssembly(asm, AssemblyRegistrationFlags.SetCodeBase))
		{
			throw new InstallException($"Failed to register for COM Interop: {asm.FullName}");
		}
	}
	public override void Uninstall(IDictionary savedState)
	{
		base.Uninstall(savedState);
		var regsrv = new RegistrationServices();
		var asm = GetType().Assembly;
		if (!regsrv.UnregisterAssembly(GetType().Assembly))
		{
			throw new InstallException($"Failed to unregister for COM Interop: {asm.FullName}");
		}
	}
}

Thursday, March 26, 2020

Visual Studio document delete lines containing

Often when I'm editing a text file in the Visual Studio document editor, I want to delete all lines containing a certain string of text. I always assumed this required some bothersome technique like writing macros or extensions, so for a decade or more I just dismissed it as too hard.

Today the issue returned when I wanted to trim all the boring lines out of a large log file to leave only the interesting ones. Suddenly (and I don't know why) the answer became obvious ... just use the standard Find and Replace using Regex patterns. So for example, if I want to delete all lines containing the text Boring Line I can check the Regular Expression option and use the find pattern ^.*Boring Line.*\r\n and replace it with nothing (an empty string).

This matches from the start of the line (^) to the end of the line including the Windows line terminator characters (\r\n) if it contains the desired string anywhere inside. Then each full matching line is replaced with nothing and is effectively deleted.

In my editor it looks like the following.

Tuesday, January 28, 2020

Parsing a VDPROJ file

While writing a small utility to scan and summarise Visual Studio project files of type vcproj and vdproj I was reminded that the vdproj file is not XML, it's a custom format that's a little bit like JSON. A quick search revealed that there is no standard library to parse vdproj files, and I saw lots of ugly suggestions as workarounds. It turns out you can convert a vdproj file into an XElement with about 50 lines of quite straightforward code which I have pasted into a static method below.

There is a bit of a hack to deal with "node" names which might be either simple strings or an unpredictable pair of values separated by a semi-colon. A simple name can be turned into a <name> element, but without knowing the rules for the other types, they are simply turned into nodes that look like this sample:
<node data="BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}">
Ignoring that glitch though, you finish up with a neat XML representation of the whole vdproj file and you can use LINQ-to-XML to query it.

public static XElement ParseVDProj(string vdprojFilename)
{
  using (var reader = new StreamReader(vdprojFilename))
  {
    var stack = new Stack<XElement>();
    var root = new XElement("node", new XAttribute("name", "root"));
    stack.Push(root);
    XElement head = root;
    string line = reader.ReadLine();
    while (line != null)
    {
      if (Regex.IsMatch(line, @"^\s*}"))
      {
        // A close brace pops the stack back a level
        stack.Pop();
        head = stack.First();
      }
      else
      {
        Match m = Regex.Match(line, @"\x22(\w+)\x22 = \x22(.+)\x22");
        if (m.Success)
        {
          // A key = value is added to the current stack head node
          string name = m.Groups[1].Value;
          string val = m.Groups[2].Value;
          var elem = new XElement(name, val);
          head.Add(elem);
        }
        else
        {
          // Otherwise we must be pushing a new head node onto the stack.
          // HACK: If the name is a simple alphanum string then it's used
          // as the node name, otherwise use a fake <node> with the strange
          // name as a data attribute.
          XElement elem = null;
          string rawname = Regex.Match(line, @"^\s*\x22(.+)\x22\s*$").Groups[1].Value;
          if (Regex.IsMatch(rawname, @"^\w+$"))
          {
            elem = new XElement(rawname);
          }
          else
          {
            elem = new XElement("node", new XAttribute("data", rawname));
          }
          head.Add(elem);
          stack.Push(elem);
          head = elem;
          reader.ReadLine();  // Eat the opening brace
        }
      }
      line = reader.ReadLine();
    }
    return root;
  }
}