Formatting the display for a MudRangeSlider

The MudRangeSlider control is an extension to MudBlazor by CodeBeam. It is a very nice control that allows you to use a slider control to set both a minimum and maximum value. It works great, and the only issue I had was that I couldn’t find a way to change the numbers display underneath the control. The default behaviors show the numbers as integers:

For my purposes, I needed to display them as formatted currency, like this:

Thus, I turned to JavaScript.

I’m going to start on the Index.cshtml screen. I define the MudRangerSlider control and use the IJSRuntime interface to link Blazor to the JavaScript code I need. I do this in the OnAfterRender event. You may need to play with the exact placement of the call to JsRuntime.InvokeAsync, depending on your code.

@inject IJSRuntime JsRuntime

<MudRangeSlider @bind-Value="@_value" @bind-UpperValue="@_upperValue" Size="Size.Medium" Variant="Variant.Filled" ValueLabel="_valueLabel" Range="_range" TickMarks="_tickmarks" Min="_min" Max="_max" Step="_step" Display="_display" MinDistance="_minDistance"></MudRangeSlider>

@code {
    private int? _value = 0;
    private int? _upperValue = 100000000;
    string _label = "Range";
    bool _valueLabel = true;
    bool _display = true;
    bool _range = true;
    bool _tickmarks = false;
    bool _disableMin = false;
    bool _disableMax = false;
    int _min = 0;
    int _max = 100000000;
    int _step = 100000;
    int _minDistance = 100000;

    protected override void OnAfterRender(bool firstRender)
    {
        if (!firstRender) {
            JsRuntime.InvokeAsync<string>("attachSliderListener");
        }
    }
}

Now the fun stuff. In the _Host.cshtml file, I defined the JS code that works the magic. I’ll include it all at once and explain afterwards:

        const formatter = new Intl.NumberFormat('en-US', {
            style: 'currency',
            currency: 'USD',
            maximumFractionDigits: 0
        });

        function attachSliderListener() {
            var items = document.getElementsByClassName("mud-slider-input");
            var rightSlider = items[0];
            var leftSlider = items[1];
            rightSlider.addEventListener("input", function () {
                formatSliderNumbers(leftSlider, rightSlider);
            }, false);
            leftSlider.addEventListener("input", function () {
                formatSliderNumbers(leftSlider, rightSlider);
            }, false);
        }

        function formatSliderNumbers(leftSlider, rightSlider) {
            var display = document.getElementsByClassName('mud-range-display')[0];
            display.innerText = formatter.format(leftSlider.value) + ' - ' + formatter.format(rightSlider.value);
        }

The formatter formats the number I’ll be passing into it as US currency.

The attachSliderListener method finds the HTML input controls that MudRangeSlider uses to define the upper and lower range slider circles. There are two, and I find them using the class that is assigned to each, “mud-slider-input”. I then pass those controls into the formatSliderNumbers method.

The formatSliderNumbers method finds the <p> element that the MudRangeSlider control uses to display the values. It doesn’t have an ID, so I also pull it using its class, “mud-range-display”. I then change change the innerText property of that element the formatted values of the minimum and maximum values.

That’s it!

, , , , ,

Leave a comment

Returning users in groups from Azure AD using MS Graph

We store our user and group accounts in Azure AD, and I needed to retrieve the members of a group in order to email them. We use Microsoft Graph to do such things as these. The “User” class referenced below is the Microsoft.Graph.Models.User class.

You first will need the ID of the group. To do this, run the query below in Graph Explorer. You will also need to add a request header with a key of ConsistencyLevel with a value of eventual. (The “Request headers” section is just below where you type in the query of Graph Explorer):

https://graph.microsoft.com/v1.0/groups?$search="displayName:Coaster Users"&$count=true

With the ID that is returned, plug it into your query (the commandString variable below). You’ll probably want to put it in appSettings.json or a client secret. I have defined a class called GraphService where the ReturnUsersInADGroup is defined:

public async Task<List<User>> ReturnUsersInADGroup()
{
        var token = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "User.Read", "Directory.Read.All" });
        _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        List<User> usersInGroup = new List<User>();
        string commandString = "https://graph.microsoft.com/v1.0/groups/aaaaaaaa-d277-3e5e-39e7-fffff4349437?$expand=members";

        var usersRequest = await _httpClient.GetAsync(commandString);
        var usersRaw = System.Text.Json.JsonDocument.Parse(await usersRequest.Content.ReadAsStreamAsync());
        var usersValue = usersRaw.RootElement.GetProperty("members");
        User[] users = JsonConvert.DeserializeObject<User[]>(usersValue.GetRawText());
        usersInGroup.AddRange(users);

        return usersInGroup;
}

Finally, define a method that pulls the values. I’m working with a Blazor app, so I have defined the service in the Program.cs file and use dependency injection:

builder.Services.AddScoped<GraphService>();

And in the Razor file:

@inject GraphService graphService

private void GetEmailAddresses() 
{
    var users = await graphService.ReturnUsersInADGroup();
    List<string> emailAddresses = new List<string>();
    emailAddresses = users.Select(u => u.Mail).ToList();
}

Leave a comment

Highlight list item with Blazor and JavaScript

I had an unordered list (<ul>) HTML element, and for each of the list items (<li>) in the list, I wanted to change the background color of that item while clearing the background color of other items when the item was clicked. Oh, and this was a Blazor application.

First, the JavaScript method, which I put in the _Host.cshtml file:

function highlightListItemRow(id) {
     var cbs = document.getElementsByClassName('cb');
     for (var i = 0; i < cbs.length; i++) {
         cbs.item(i).style = "background-color: #FFFFFF";
     }
     var item = document.getElementById(id);
     item.style = "background-color: #AD744C";
}

This looks for a particular class (“cb”). For every item with that class, it sets the background color to white, the background color of the screen. This clears the background color of any list item that was previously highlighted. Then the method finds the list item that was clicked using the id and sets the background color to a copper color.

In the razor file, I have the code that generates the list. It loops through the “coasters” collection. It assigns the ID to that item, and also assigns a class called “cb”. The class “cb” isn’t defined anywhere, I just needed that to uniquely identify all the list items in JavaScript:

<ul style="cursor: pointer;border:1px solid black;border-radius:3px;width:50%;padding:5px;font-size:16px">
     @foreach (var item in coasters)
     {
       <li id="@item.ID" class="cb" @onclick="(() => Show(item))">@item.Name</li>
     }
</ul>

The “Show” method takes the Coaster class and uses the ID property of that class which then uses the IJSRuntime class to call the highListItemRow JS method defined in the host file:

@inject IJSRuntime JsRuntime
...
void Show(Coaster coaster)
{
   JsRuntime.InvokeAsync<object>("highlightListItemRow", coaster.ID);
}

, ,

Leave a comment

Highlighting a select MudTable row

To highlight a row in a MudTable when clicking that row, define an “OnRowClick” event to select the row, and a “RowStyleFunc” to do the row formatting, and assign those methods to the table:

    <MudTable T="Coaster" Items="coasters" OnRowClick="RowClicked" RowStyleFunc="RowStyleFunc">
        <HeaderContent>
            <MudTh>Park</MudTh>
            <MudTh>Coaster</MudTh>
        </HeaderContent>
        <RowTemplate>
            <MudTd>@context.Park</MudTd>
            <MudTd>@context.Coaster</MudTd></MudTd>
        </RowTemplate>
    </MudTable>

In the RowClicked method, I set a variable of the type of class to which the table is bound to value of the selected row.

The RowStyleFunc function runs for every row in the table and will evaluate if the selected row matches the current row and change the background color based on that result:

code {
    Coaster selectedCoaster = null;

    public void RowClicked(TableRowClickEventArgs<Coaster> c)
    {
        selectedCoaster = c.Item;
    }

    private string RowStyleFunc(SubcontractPhase arg1, int index)
    {
        if (selectedCoaster != null && arg1.Coaster == selectedSP.Coaster)
        {
            return "background-color:#AD744C75";
        }
        else
        {
            return "background-color:white";
        }
    }
}

, ,

Leave a comment

Azure Application keeps prompting to authenticate

You know that familiar box when trying to authenticate to an Azure application?

Well, I deployed a site and the popup kept coming up again and again – a never-ending loop of annoying authentication windows. I thought I had all my stuff configured properly, but I found that I hadn’t properly created an application secret. The fix for me was go to the “Certificates & Secrets” section of the Azure portal, add a new client secret, and then copy the secret:

I then went into the appSettings.json file and updated the “ClientSecret” value in the “AzureAd” section:

  "AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "coasters.com",
"TenantId": "1a6942e9-1234-1234...",
"ClientId": "5b883ae3-4321-4321...",
"CallbackPath": "/signin-oidc",
"ClientSecret": "my super secret",
"ClientCertificates": []
},

That did the trick.

Leave a comment

MudRadio cannot be inferred error

Should you encounter the error below when using the MudRadio and MudRadioGroup controls:

RZ10001	The type of component 'MudRadio' cannot be inferred based on the values provided. Consider specifying the type arguments directly using the following attributes: 'T'.

Make sure to assign the type of object to the control. In my case, I wanted the radio button to be bound to a string:

<MudRadioGroup T="string" @bind-Value="@SelectedOption">

<MudRadio T="string" Value="CP">Cedar Point</MudRadio>
<MudRadio T="string" Value="KI">King's Island</MudRadio>
<MudRadio T="string" Value="Both">Both</MudRadio>
</MudRadioGroup>

Leave a comment

Using a row span in an iText PDF cell

If you want to use a rowspan or colspan in an iText Cell (iText.Layout.Element.Cell), then you have to define those values in the constructor – there is no property to assign values to. In the example below, the cell will span one row and two columns:

Table coasterTable = new Table(new float[] { 73, 200 });
Cell cell = new Cell(1, 2);
cell.Add(new Paragraph("Coaster Ride Count");
coasterTable.AddCell(cell);

Leave a comment

Entity Framework changes staying in memory

I have an instance where I want certain text to be prepended to my string if the user is accessing the data from one page, but not if they access it from another. For the first case, I have an “if” (statement not shown in the code below) some text modifications to my “coasterComments” collection in my “coasterVM” view model:

CoasterVM coasterVM = new CoasterVM();
coasterVM.Coasters = _context.Coasters.AsQueryable().Where(c => c.CoasterId == coasterIdVariable).ToList();

for (int i = 0; i < coasterVM.Coasters.Count; i++)
{
    var coasterComment = coasters.Where(c => c.ID == coasterVM.Coasters[i].ID).FirstOrDefault();

    coasterVM.Comments[i].Text = $"[{coasterComment.Page} page] {coasterVM.Comments[i].Text}"
}

After clicking on the first page, the text appeared correctly. However, if I went to another page, the page that I didn’t want the text added to, it still appeared despite my text-adding logic not being hit. Entity Framework Core was keeping my changes in memory despite pulling from the Coasters table each time the page was run.

To get around this, I modified how EF tracks changes for this transaction only. I wasn’t interested in updating the whole application behavior, just this one instance. So before the code that I showed above, I set the tracking to “NoTracking”, and immediately after, I change it back to “TrackAll”:

_context.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTracking;
// My code above
_context.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.TrackAll;

This showed the behavior I wanted.

Leave a comment

The pesky “There is already an object named ‘Audit’ in the database” error

Yes, we’ve all been there. We are happily adding a new model to be added to our Entity Framework-based application and “BAM!!!”, we get that error. It caused much weeping and gnashing of teeth.

I’m a simple man with simple needs, such as getting my application working. In my case, this error happened early on in the lifecycle of my project. I had my initial migration, and then a second migration, and during the “Update-Database” statement of the second migration was when I received this message.

My solution was the comment-out the entire “Up” method of the initial migration:

        protected override void Up(MigrationBuilder migrationBuilder)
        {
            //migrationBuilder.CreateTable(
            //    name: "Coaster",
            //    columns: table => new
            //    {
            //        Id = table.Column<int>(type: "int", nullable: false)
            //            .Annotation("SqlServer:Identity", "1, 1"),
            //        Name = table.Column<string>(type: "nvarchar(max)", nullable: false)
            //    },
            //    constraints: table =>
            //    {
            //        table.PrimaryKey("PK_Coaster", x => x.Id);
            //    });
         }

Perhaps a blunt force approach. But then again, it worked.

Leave a comment

Give focus to a control in Blazor

Hack alert! Hack alert!

I was using DataAnnotationsValidator and ValidationSummary in Blazor to show the fields where validation failed using data annotations, along with using an EditForm with submit method:

<EditForm Model="coaster" OnInvalidSubmit="ValidationFailed" OnValidSubmit="ValidationSuccessful">
    <DataAnnotationsValidator />
    <Validator @ref="validator" />
    <ValidationSummary />

This part was fine, but I was showing the validations in a modal window, and my attempts to scroll to the top of the window failed, so when a user clicked the “Save” button at the bottom of the window, they wouldn’t see the errors. I had tried a couple of things to scroll to the top, but this is what worked in the modal window.

At the top of the window, just below the ValidationSummary control, I put an HTML input control with an ElementReference. In the ValidationFailed method, I then used the FocusAsync method to give that element focus, and that worked.

<EditForm Model="coaster" OnInvalidSubmit="ValidationFailed" OnValidSubmit="ValidationSuccessful">
    <DataAnnotationsValidator />
    <Validator @ref="validator" />
    <ValidationSummary />

    <input @ref="textInput" style="border: 1px solid white; height: 1px" />
    ....
    <input type="submit" value="Submit">
</EditForm>

@code {
   ElementReference textInput;

   void ValidationFailed()
    {
        textInput.FocusAsync();
    }
}

,

Leave a comment