Wednesday, November 6, 2024

How to Nest Blazor's .razor Files in Visual Studio Code

When working with Blazor in Visual Studio Code, you may encounter some nuanced differences from working in Visual Studio, and would like greater feature parity. One such feature is the default file nesting as shown below from Visual Studio when it comes to Razor component files:

Visual Studio Code has the capability to nest files, but by default Blazor's files are not nested and appear in parallel. 

To update/fix this, we can update the 'File Nesting Patterns' in the Command Palette.

Open the Command Palette in Visual Studio Code by pressing Ctrl+Shift+P (PC) and then add the following search string for direct access: 

@id:explorer.fileNesting.patterns

Select 'Add Item' with the key: 

*.razor
and use the following Value:

${capture}.razor.cs, ${capture}.razor.css, ${capture}.razor.scss, ${capture}.razor.less, ${capture}.razor.js, ${capture}.razor.ts

You can add any applicable file extensions to the list above as needed. At this point you can close the settings and see the Blazor files nested correctly:

Thursday, May 23, 2024

How to Enable 'Hey Code!' Voice Interactivity for GitHub Copilot Chat

With all the smart devices around the house that can be queued with the likes of 'Hey Google!' wouldn't it be great if we could queue up GitHub Copilot in the same manner either for ease of use or required for accessibility needs? Thankfully this isn't too difficult to configure in Visual Studio Code. Once configured you can say, 'Hey Code!' and use a voice prompt to interact with GitHub Copilot chat.

GitHub Copilot Chat 'Hey Code!' Configuration Steps

  1. Open the Command Palette via Ctrl+Shift+P or F1
  2. Type in 'accessibility' to access configuration options and select, 'Preferences: Open Accessibility Settings'
  3. Add 'voice' to the configuration filter and select 'Accessibility > Voice: Keyword Activation'
  4. Select an option for where Copilot Chat interacts with you after saying aloud, 'Hey Code!' in the IDE:
    • chatInView: start a voice chat session in the chat view (i.e. the Copilot Chat main window)
    • quickChat: start a voice chat session in the quick chat (i.e. Command Palette input)
    • inlineChat: start a voice chat session in the active editor if possible (i.e. inline Copilot Chat dialog)
    • chatInContext: start a voice chat session in the active editor or view depending on keyboard focus (i.e. if the current cursor is focused within code in a file, the inline Copilot Chat dialog is used, and if the active cursor is in the Copilot Chat main window this will be used to capture the dialog)
My preference is to use chatInContext as it will toggle inline vs window chat based on current focus, but play around with the options to see which is best for you.

A quick way to access these settings once configured is to press the microphone icon in the bottom task bar of Visual Studio Code which will immediately pull up these same settings to modify directly.


Now try it out and say, "Hey Code! Help me create a new Blazor web application!"

Tuesday, February 6, 2024

How to Fix the GitHub Copilot Chat Error: 'Cannot read properties of undefined (reading 'split')'

If you're using GitHub Copilot Chat within Visual Studio code, you may begin to see an error unexpectedly after an IDE update that reads as follows when using Copilot Chat:
Cannot read properties of undefined (reading 'split')

This is caused by the Copilot Chat extension requiring a reload which can be seen from the extensions menu:

Once selecting, 'Reload Required,' Visual Studio Code will reload, and Copilot Chat will begin working as expected again.

Sunday, November 12, 2023

Blazor WebAssembly Lazy Loading Changes from .NET 7 to .NET 8

Lazy Loading is an essential tool used in web client development to defer loading of resources until requested by the user, as opposed to loading everything up-front which is expensive. Blazor has had the ability to lazy load Razor Class Libraries for the last several versions of .NET, but there are some updates in .NET 8 that aren't well documented.

To not be completely repetitive, here are the basic steps for implementing lazy loading in your Blazor WebAssembly application direct from the Microsoft docs: Lazy load assemblies in ASP.NET Core Blazor WebAssembly

The issue is the Project Configuration and Router Configuration sections of the docs as of this post are still not up to date. With .NET 8, the WASM assemblies are now built as .wasm files not .dll files and therefore the main update you'll need to make are inside the .csproj file and within the definition of the LazyAssemblyLoader routing code to use the .wasm file extension for the referenced .dlls:

.csproj file updates


Routing file updates


If continuing to use the old code with the .dll extension you would get the following error on building your application:
Unable to find 'EngineAnalyticsWebApp.TestLazy.dll' to be lazy loaded later. Confirm that project or package references are included and the reference is used in the project" error on build
Upon making the required updates to your .NET 8 application to prevent the error above, your app should successfully build, and you'll see the correct deferred execution in the browser.



Friday, November 3, 2023

Dealing with Time Skew and SAS Azure Blob Token Generation: 403 Failed to Authenticate Error

If you're working with the Azure.Storage.Blobs SDK in .NET and generating SAS tokens, you might come across an error like the following when making a call to GetUserDelegationKeyAsync():
Status 403: The request is not authorized to perform this operation using this permission

Here is a sample of code that might throw this error in which the startsOn value is set to the current time:

Before you start double checking all of the user permissions in Azure and going through all the Access Control and Role Assignments in Blob Storage (assuming you do have them configured correctly), this might be a red herring for a different issue with the startsOn value for the call to GetUserDelegationKeyAsync(). The problem is likely with clock skew and differences in times from the server and client.

It is documented in SAS best practices from the Azure docs (found here), the following issue with clock skew and how to remedy the issue:

Be careful with SAS start time. If you set the start time for a SAS to the current time, failures might occur intermittently for the first few minutes. This is due to different machines having slightly different current times (known as clock skew). In general, set the start time to be at least 15 minutes in the past. Or, don't set it at all, which will make it valid immediately in all cases. The same generally applies to expiry time as well--remember that you may observe up to 15 minutes of clock skew in either direction on any request. For clients using a REST version prior to 2012-02-12, the maximum duration for a SAS that does not reference a stored access policy is 1 hour. Any policies that specify a longer term than 1 hour will fail.

There is a simple fix for this as instructed to modify the startsOn value to be either not set or 15 minutes (or greater) in the past. With this updated code, the 403 error is fixed and will proceed as expected.

Wednesday, October 18, 2023

Fixing PowerShell Scripting Error in C# Code: "Exception calling ""ToString"" with ""0"" argument(s). There is no Runspace available to run scripts in this thread."

A bit of a niche post here, but maybe someone is searching the interwebs and this will help save some time. When running a PowerShell script in C# leveraging the System.Management.Automation library, you might run into some neuanced issues that behave differently than when running PowerShell commands directly via the command line. Here is one instance, take the following PowerShell command to install a new Windows Service:
New-Service -Name "MyWindowsService" -BinaryPathName "C:\SomePath\1.0.1\MyWindowsService.exe"
When running this in a PowerShell terminal, it will generate the following output which as documented is an object representing the new service:
Status   Name               DisplayName
------   ----               -----------
Stopped  MyWindowsService   MyWindowsService
This is actually helpful output for a human, and maybe even for logging, bu when running this same command in C# via a PowerShell instance, the command will technically work, but you'll get the following cryptic error:
"The following exception occurred while retrieving the string: ""Exception calling ""ToString"" with ""0"" argument(s): ""There is no Runspace available to run scripts in this thread. You can provide one in the DefaultRunspace property of the System.Management.Automation.Runspaces.Runspace type. The script block you attempted to invoke was: $this.ServiceName""""", System.Object,System.ServiceProcess.ServiceController,System.WeakReference`1[System.Management.Automation.Runspaces.TypeTable], System.Management.Automation.PSObject+AdapterSet,None,System.Management.Automation.DotNetAdapter,"",System.Management.Automation.PSObject+AdapterSet, System.ServiceProcess.ServiceController,System.ServiceProcess.ServiceController,"PSStandardMembers {DefaultDisplayPropertySet}",False,False,False,False,False, "","",None
OK not very helpful. It took a while but I deduced the output from the New-Service cmdlet was causing this issue. There are 2 ways I found to handle this.
  1. Suppress the Output: Since this is running in C# without human intervention, pipe in the option to suppress the output if you are not logging it or using it for some meaningful purpose. Remember, you can still get the status by using Get-Service to inspect the newly installed service, as opposed to reading the output. Use the updated command to accomplish this:
    New-Service -Name "MyWindowsService" 
    -BinaryPathName "C:\SomePath\1.0.1\MyWindowsService.exe" | out-null
    
  2. Return the Output to a Variable: The other option is simply to return the output of the cmdlet typically for use to inspect. This could be done with the following updated statement:
    $newServiceResult = New-Service -Name "MyWindowsService" 
    -BinaryPathName "C:\SomePath\1.0.1\MyWindowsService.exe"
    
    Keep in mind he return value is of type System.ServiceProcess.ServiceController, so you can use that as needed.
For my needs I prefer option #1, as I don't need to inspect the return of New-Service and as mentioned can use Get-Service to further inspect any Windows Service status at this point. With either method though you will no longer get the cryptic error as previously shown.

Thursday, September 21, 2023

How to Disable Either Classic or YAML Pipelines in Azure DevOps (ADO)

If you have pipelines in Azure DevOps that you don't want to trigger automatically either because they are legacy and remain for reference, not currently being used, or just need to temporarily disable, there are different ways to accomplish this based on the type of pipeline.

Classic Pipelines

  1. Select the Classic Pipeline and press the 'Edit' button


  2. Select 'Triggers'


  3. Uncheck the box for 'Enable Continuous Integration'


  4. Save the Pipeline



YAML Pipelines

  1. Select the Classic Pipeline and press the 'Edit' button


  2. Select the ellipsis (3 dots) in the top-right hand corner, and select 'Settings'


  3. Select the 'Disabled' radio button, and press 'Save'


  4. The pipeline is confirmed disabled via the label next to the name