Embedding RS reports, HighCharts, and PBI interactives in ASP.NET Core MVC views


SSRS embed
 @model ReportView  
 <script>  
 </script>  
 <div class="container" style="background-color:#ffffff">  
   <section>  
     <div style="background-color:#ffffff; box-shadow: 5px 10px 8px #888888;">  
       <iframe height="800" width="900" src="@Model.SelectedReport.Uri" class="body-box-shadow"></iframe>  
     </div>  
   </section>  
 </div>  
This is the entire view of _Report.cshtml which renders the RS report; see the extRSAuth project for how to enable non-Windows AD SSRS client authentication



The <iframe> is rendered with the URL of the report on your report server which renders a ReportViewer control view of the report



HighCharts embed
 <script src="https://code.highcharts.com/highcharts.js"></script>  
 <script src="https://code.highcharts.com/themes/adaptive.js"></script>  
 <head>  
   <script>  
     $(document).ready(function () {  
       Highcharts.seriesTypes.line.prototype.getPointSpline = Highcharts.seriesTypes.spline.prototype.getPointSpline;  
       Highcharts.chart('potusApproval', {  
       title: {  
         text: 'Trump Job Approval',  
         align: 'left'  
       },  
       subtitle: {  
           text: 'Source: <a href="https://api.votehub.com/polls?poll_type=approval&subject=Trump" target="_blank">VoteHub</a>.',  
         align: 'left'  
       },  
       yAxis: {  
         title: {  
           text: 'Approval'  
         }  
       },  
       legend: {  
         layout: 'vertical',  
         align: 'right',  
         verticalAlign: 'middle'  
       },  
       plotOptions: {  
             area: {  
               pointStart: '1/1/2025',  
               relativeXValue: false,  
               marker: {  
                 enabled: true,  
                 symbol: 'circle',  
                 radius: 2,  
                 states: {  
                   hover: {  
                     enabled: true  
                   }  
                 }  
               }  
             }  
           },  
       series: [{  
         name: 'Approve',  
         data: [ @string.Join(", ", Model.HighChartsModel.Approves) ]  
       }, {  
         name: 'Disapprove',  
         data: [ @string.Join(", ", Model.HighChartsModel.Disapproves) ]  
       }],  
     });  
   });  
   </script>  
 </head>  
 <table style="width: 100%; height:50%">  
   <tr><td><div id="potusApproval"></div></td></tr>  
 </table>  
The same data can easily be embedded as a HighCharts visual using some JS and HTML; this example uses the simple HighCharts LineChart


The above HighCharts embed code renders this line chart



Power BI embed


The easiest Power BI embedded method is to use the Developer Playground "demo code" script; select File >> Developer Playground



Next, click the "Set up now" button on the upper right side of the following screen




Here you are presented with the embed <iframe> script that you put into your view... 



 <iframe title="PBI_Report" style="width:100%; height:73%" src="https://app.powerbi.com/reportEmbed?reportId=695fe0f1-5c9e-497d-8913-e59f0b939ac4&autoAuth=true&embeddedDemo=true" frameborder="0" allowFullScreen="true"></iframe>  



And, voila! You have a Power BI visual embedded inside your web app now



Reference: https://learn.microsoft.com/en-us/power-bi/developer/

Source: https://github.com/sonrai-LLC/extRS/tree/main/ExtRS.Portal | https://extrs.net


SQL Server 2025's sp_invoke_external_rest_endpoint with OPENJSON CTE for quickly and easily getting data from REST APIs

SQL Server 2025 introduces a convenient way to get data from a REST API endpoint directly through T-SQL and SQL Server utilities.


Outlined in lime green are the two items SQL Server 2025 handles well, discussed in this post


Prior ways of doing this usually involved using MSXML2.XMLHTTP (a COM object provided by Microsoft XML Core Services) through extended stored procedures, but with MSSQL 2025, there is a new SP, sp_invoke_external_rest_endpoint that is very readable and easy to use to get JSON (or XML) from an API response.

This brief article describes what an SP to get this data may look like, as well as the code to parse the JSON in the response to format the result as a table (vs. sending back all the JSON for the client to parse).

Here is an SP which fetches polling data for the Approval Polling data on current U.S. president Donald Trump:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:	colin fitzgerald
-- Create date: 20250625
-- Description: SP to fetch data from VoteHub API
-- =============================================
CREATE OR ALTER PROCEDURE dbo.sp_GetVoteHubPollingData
	@LongView BIT = 0
AS
BEGIN
SET NOCOUNT ON;

EXECUTE sp_configure 'external rest endpoint enabled', 1;
RECONFIGURE WITH OVERRIDE;

DECLARE @ret AS INT, @response AS NVARCHAR (MAX);

EXECUTE
    @ret = sp_invoke_external_rest_endpoint
    @url = N'https://api.votehub.com/polls?poll_type=approval&subject=Trump',
    @headers = N'{"Accept":"application/json"}',
    @method = 'GET',
    @response = @response OUTPUT;

;WITH ResponseContent AS
(SELECT [key], [value] FROM OPENJSON(@response)),
 ResponseJson AS
(SELECT [value] FROM OPENJSON((SELECT [value] FROM ResponseContent WHERE [key]='result')))

SELECT --value,
--id,
pollster,
[subject],
poll_type,
sample_size,
created_at, 
approve,
disapprove
FROM ResponseJson
OUTER APPLY OPENJSON(value) WITH(
  id nvarchar(255), 
  pollster nvarchar(255),
  [subject] nvarchar(255),
  poll_type nvarchar(255), 
  sample_size int, 
  created_at datetime,
  approve decimal '$.answers[0].pct',
  disapprove decimal '$.answers[1].pct'
)
WHERE created_at >= CASE WHEN @LongView = 0 THEN dateadd(mm, -3, getDate()) ELSE created_at END 
ORDER BY created_at DESC

EXECUTE sp_configure 'external rest endpoint enabled', 0;
RECONFIGURE WITH OVERRIDE;

END
GO


And here is the design view of the data source for a report using this polling table data:



And here is the design view of the report that will use this data:



If we CREATE dbo.sp_GetVoteHubPollingData as stored procedure on a database (in this case, I created it in 'master') that our data source connects to, then we can deploy the report to a Report Server or Power BI Report Server and run it:


This is the report as rendered within Power BI Report Server's Portal using extRSAuth for authentication and authorization





This is the report as rendered within extRSAuth custom PBIRS reporting portal on extrs.net 



Lots of neat stuff you can do with the new features of SQL Server 2025- check 'em out.

Next up: embedding a PBI version of this report in extrs.net, and embedding a HighCharts JS version of this report in extrs.net- all using this dbo.sp_GetVoteHubPollingData SP that uses sp_invoke_external_rest_endpoint.


References:

https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-invoke-external-rest-endpoint-transact-sql

https://learn.microsoft.com/en-us/sql/relational-databases/json/convert-json-data-to-rows-and-columns-with-openjson-sql-server

Customizing ASP.NET Core Identity Razor Pages via Scaffolding (in an MVC app)

If you are trying to add the built-in ASP.NET Core Identity Authentication scaffolding to an MVC application, you will notice that traversing between MVC controller actions and simple requests to Razor Pages doesn't work as transparently as you might expect.

To scaffold the Identity Razor Pages so that we can customize them, we first do the following:

Right-click the project and select, "Add >> New Scaffolded Item..."


Select the Identity scaffolding template


From the next screen, select the Razor pages that you would like to scaffold for overriding/customizing


Once you have the scaffolded pages you will need to run this from the Developer console to generate the necessary ASP.NET Core Identity database tables: dotnet ef database update

If you want to be able to get to the Login page from an MVC controller, you will; need to an [AllowAnonymous] attribute to the Login.cshtml Razor Page code-behind class, and you will need to call the Login page while referencing the Area path in which it is located within the project file structure.

The folder structure for an MVC project overriding the built-in Identity auth Razor pages via scaffolding


An MVC logout link that you want to redirect to an overridden Identity auth Login Razor page might look like this in your _Layout view: 

The view link in the MVC's _Layout.cshtml

The controller redirect (note: new { area = "Identity" }); "/Pages/" is ignored for some reason..

In order to reach the Login Razor page (when not being authenticated) you will need to add the [AllowAnonymous] attribute to the LoginModel class a la:

With [AllowAnonymous] users will be able to reach the overridden Identity Login page from any controller action in your MVC application


An that's it. You can customize all of the front-end and back-end code of the scaffolded Identity Razor pages. This is an example of customization that uses the extRS service to create SSRS user accounts when a user registers a new Identity account for the app:

Scaffolding the Identity Razor pages allows you focus only on the authentication design and logic that you need to customize 


I'm not sure why but much of this is not documented (and what is documented is not easily found).

Lastly, do keep in mind that in order to override certain items like "_LoginPartial", you need to rename to view, as was done with "_LoginPartial2.cshtml" in the extRS project, for example.


References: 

https://github.com/sonrai-LLC/extRS/tree/main/ExtRS.Portal

https://extrs.net




    

    

Implement extRSAuth on SSRS v16.0, SQL Server 2025


This short YouTube demonstration covers e2e implementation, from GitHub to your machine


extRSAuth is tested to work with traditional SQL Server Report Server and the new Power BI Report Server



GitHub: https://github.com/sonrai-LLC/extRSAuth

Asset Classes

Assets Classes are "a group of marketable financial assets that have similar financial characteristics and behave similarly in the marketplace". They represent the different types of (usually) tangible things that can be owned and be assessed a "valuation".


Public Stocks (Equities) - shares of ownership in publicly-held companies 

  • Stocks are listed on stock exchanges which are open to the public investment
  • Historically, have outperformed other investments over long periods
  • Most volatile in the short term
  • Returns and principal for a stock fluctuate over time, making funds from the eventual sale of the stock worth more or less than original cost (depends on the stock purchase price)

Private Equity (Private Stocks)shares of ownership in privately-held and unlisted companies

  • Typically, only a few large investors
  • Usually focuses on short-term capital extraction and "sale for parts"
  • Often financed by leveraged buyouts (loading the target company with new debt to help the PE firm fund the acquisition of the target company)
  • Can be designed to turn around the fortunes/profitability of a distressed company by cutting costs, replacing management and changing company direction; in this case the investment is more long-term, but ultimately the goal of PE is to extract a large profit from the sale of the acquired company once its profitability (and thus valuation) has improved
  • A "property flip" is a minor/small-scale form of PE


Bonds/Notes/Bills (Fixed Income) - guaranteed bond investments

  • Pays a set rate of interest over a given period, then return the investor's principal
  • More stability than stocks
  • Value fluctuates due to current interest and inflation rates
  • Includes "guaranteed" or "risk-free" assets
  • Also includes money market instruments (short-term fixed income investments)
  • Often comprised of federal government or municipal bonds, notes or bills
  • Can also include corporate loans
  • The term used to describe this type of debt asset ("bond/note/bill") depends on the length of the debt instrument maturity, with "bonds" typically being a maturity of 10-20 years, "notes" being a maturity of 1-10 years and "bills" (like T-Bills) being a maturity of less than 1 year


Cash and Equivalents (Liquid Assets) - assets that can be quickly and easily converted into immediately usable currency without losing significant value

  • Checking and savings accounts
  • Certificates of deposit (CDs)
  • Money market funds
  • Treasury bills
  • Treasury notes
  • Commercial paper
  • Foreign currencies which are easily convertible to the currency you need to transact in
  • Liquid assets have the advantage of giving their owner the power to buy things without incurring any debt

Cryptocurrency - a "piece" of (usually limited) digital currency

  • The backbone of "DeFi" or Decentralized Finance
  • "Digital rare earth material"
  • Relatively accessible; mineable and tradable
  • "Risk-on" asset; lacks regulation, ESG concerns, highly volatile
  • Liquidity has increased with institutional adoption
  • Supply varies, some are finite or even designed to shrink in supply and thus deflationary
  • No inherent value or utility (like NFTs)
  • But(!), cryptocurrency underlies the transactional architecture of a bulk of all untraced, "black market" commercial activity worldwide
  • Barring meaningful cryptocurrency regulation, its value is unlikely to ever "go to zero" or not hold some significant value because of its usefulness in facilitating illegal monetary transactions and digital money laundering


Recently, interest in stocks and crypto has spiked while interest in bonds and private equity has remained more muted and stable


Real Estate - investment property (houses, stores, factories, land lots, etc.) and commercial real estate investments

  • Helps protect future purchasing power as property values typically rise with inflation
  • Values tend to rise and fall more slowly than stock and bond prices.
  • It is important to keep in mind that the real estate sector is subject to various risks, including fluctuation in underlying property values, interest rates (which directly influence mortgage rates, which usually compose a large part of any real estate purchase), eminent domain law, and potential environmental liabilities
  • Can include "Infrastructure as an asset class"- a broad category including highways, airports, rail networks, energy generation (utilities), energy storage and distribution (gas mains, pipelines etc.)
  • Can provide a long-term cash flow, a hedge against inflation, and diversification (low correlation with the top two traditional asset classes: equity and fixed income)


Commodities - physical goods such as gold, copper, crude oil, natural gas, wheat, corn and electricity

  • Can serve both as a value store in and of itself (in the case of things that don't expire, like precious metals) and as raw material for the construction and delivery of downstream physical goods and services
  • Helps protect future purchasing power as commodity values rise with inflation
  • Values tend to have low correlation with stock and bond prices
  • Price dynamics are unique: commodities become more volatile as prices rise

Interest in CRE and commodities has fallen precipitously since 2004...




References:

https://www.thrivent.com/insights/investing/an-investors-guide-to-asset-classes-types-allocations-more

https://www.poems.com.sg/glossary/investment/asset-class/

https://cointelegraph.com/learn/overview-of-different-types-of-asset-classes

https://en.wikipedia.org/wiki/Asset_classes


The perils of 'minimizing' language in software development

I believe that a large part of the reason that the vast majority of software projects end up over budget and finish well past schedule is that all too often, both developers and product/project managers use minimizing language for things that turn out to not be anywhere near as minimal or as straightforward and simple as minimizing language makes them sound. 


Focus on keeping the main thing the main thing


When discussing software development, especially when discussing the estimation of time required to complete work items, whenever you speak or hear a statement that contains the phrases, "it's just", "it's only", "it's simple", ''that's just boilerplate", "it's already baked in", "that's just [insert a design pattern while completely omitting the context, data structures and design logic the pattern will be applied to]", or in the Year of our Lord 2025: "ChatGPT will answer that"- run.

Run for the hills and do not return. I'm kidding. But do be very alert to phrases like these because they indicate a potential significant piece of your project that is being glossed over because someone has thought about it abstractly, but not in concrete (code implementation) terms.

This can be developers who are either over-confident and/or feel pressured to give low estimates so that the project schedule does not seem imperiled.



This can be managers who just haven't stepped into the code or discussed the logic enough with the developers to understand the complexity behind a series of words that describe a conceptual software design.

Under-promise and always account for unknowns which will lead to unforeseen roadblocks, detours and changes. If all goes according to plan (and it never does), you over-deliver on your over-estimates. If not, you have may have given yourself enough buffer to still meet the planned schedule and will have successfully accounted for the inevitable unknown.

If you are already on a schedule that is unrealistic and bound to not be met by the deadline, then all you can do is change scope (cut or delay features). If you insist on keeping all the planned features, and have the luxury of time, then you can only increase the time (lengthen the delivery schedule to a future date).

You can certainty try to keep the same calendar date for a release deadline and "just" throw more developers and managers at the project, hoping they can all work round the clock and in parallel to increase productivity, but this never works. Domain knowledge and a cadence of solid productivity and cooperation across teams takes a significant period of time for new hires to learn.

As a quote attributed to Warren Buffet says, "You can't produce a baby in one month by getting nine women pregnant."


And I repeat: Focus on keeping the main thing the main thing


When designing and project planning any significantly complex piece of software, all parties involved (the "stakeholders") must understand that a software project is not fixed- project schedules, planned features, and the human resources to implement the features are going to change.

Many thought that the move from top-heavy waterfall/SDLC-based approaches to Agile would solve this problem. But unfortunately, when Agile refuses to actually be "agile", the waterfall becomes an unnavigable white water rapid stream that is only slightly more conducive to building great things within a certain scheduled space of business time.

And in general, God bless a true Agile craftsmanship approach and all the time-tested statistical process control concepts it is based upon, but our software industry's hyper-reliance on "estimating" and "measuring" things that can unexpectedly and rapidly evolve (and oftentimes measuring the wrong things) does not jive with realistic long-term software planning objectives and almost to a "hyper-time-boxed project"- leads to the worst of all outcomes in the software business: mismanaged (or specifically "missed") expectations. Managment of expectations is everything.

Deliver the most critical parts of a customer's needs first and deliver them as a flawless piece of beautiful software. Working software- especially the end-to-end functioning of your application's most critical workflow- is paramount; everything else should follow from that and never get in its way. 

You can iterate, make changes and add features later on.

Focus on outcomes over processes; lest you sink into the bog of minimizing language metastasized into maximally time-consuming (sometimes completely unnecessary) work items. And a project that seems to never get delivered or is (worse, because first impressions are everything...) delivered rife with show-stopping bugs.


extRS for useful common logic, reference data and extending SSRS

extRS is a .NET assembly, available as a Nuget package here, which is a utility for getting data out of SSRS, FedEx, UPS, USPS, financial markets, among several other useful functionalities.       
  
            

extRS was initially designed as a set of helper functions to access things in RS, but it evolved to contain a lot more..



Here are a couple examples of some of the useful things you can do with extRS:

Create, Get and Delete an SSRS Report Subscription
     [TestMethod]  
     public async Task CreateGetDeleteSubscriptionSucceeds()  
     {  
       string json = @"{  
       ""@odata.context"": ""https://localhost/reports/api/v2.0/$metadata#Subscriptions/$entity"",  
         ""Owner"": """ + Resources.User + @""",  
         ""IsDataDriven"": false,  
         ""Description"": ""string..."",  
         ""Report"": ""/Reports/USPolls"",  
         ""IsActive"": true,  
         ""EventType"": ""TimedSubscription"",  
         ""ScheduleDescription"": ""string..."",  
         ""LastRunTime"": ""2023-04-13T15:51:04Z"",  
         ""LastStatus"": ""string..."",  
         ""DeliveryExtension"": ""Report Server Email"",  
         ""LocalizedDeliveryExtensionName"": ""Email"",  
         ""ModifiedBy"": """ + Resources.User + @""",  
         ""ModifiedDate"": ""2023-04-13T15:51:04Z"",  
         ""Schedule"": {  
           ""ScheduleID"": null,  
           ""Definition"": {  
             ""StartDateTime"": ""2021-01-01T02:00:00-07:00"",  
             ""EndDate"": ""0001-01-01T00:00:00Z"",  
             ""EndDateSpecified"": false,  
             ""Recurrence"": {  
               ""MinuteRecurrence"": null,  
               ""DailyRecurrence"": null,  
               ""WeeklyRecurrence"": null,  
               ""MonthlyRecurrence"": null,  
               ""MonthlyDOWRecurrence"": null  
             }  
           }  
         },  
         ""DataQuery"": null,  
         ""ExtensionSettings"": {  
           ""Extension"": ""DeliveryExtension"",  
           ""ParameterValues"": [  
             {  
               ""Name"": ""TO"",  
               ""Value"": ""colin@sonrai.io"",  
               ""IsValueFieldReference"": false  
             },  
             {  
               ""Name"": ""IncludeReport"",  
               ""Value"": ""true"",  
               ""IsValueFieldReference"": false  
             },  
             {  
               ""Name"": ""Subject"",  
               ""Value"": ""true"",  
               ""IsValueFieldReference"": false  
             },  
             {  
               ""Name"": ""RenderFormat"",  
               ""Value"": ""PDF"",  
               ""IsValueFieldReference"": false  
             }  
           ]  
         },  
         ""ParameterValues"": []  
       }";  
       Subscription subscription = await ssrs.SaveSubscription(JsonConvert.DeserializeObject<Subscription>(json)!);  
       Assert.IsTrue(subscription.DeliveryExtension != null);  
       var getResponse = await ssrs.GetSubscription(subscription.Id.ToString()!);  
       Assert.IsTrue(getResponse.Id != null);  
       var delResp = await ssrs.DeleteSubscription(subscription.Id.ToString()!);  
       Assert.IsTrue(delResp);  
     }  


Getting FedEx/USPS/UPS Shipping Rates






"A meaningful and useful abstraction that is able to withstand the test of time is very difficult to design. The main difficulty is getting the right set of members—no more and no fewer. If an abstraction has too many members, it becomes difficult or even impossible to implement. If it has too few members for the promised functionality, it becomes useless in many interesting scenarios." -Designing for Extensibility in .NET (Cwalina, Abrams, Barton)


Interfaces.

When it comes to the ability to interact with the inputs and outputs of software, everything is an interface. For those who aren't aware, the Windows OS was originally named "Microsoft Interface Manager". There is interesting info in the Wikipedia entry on the origins of Windows.


The OS that started it all for Microsoft.


"In computing, an interface is a shared boundary across which two or more separate components of a computer system exchange information. The exchange can be between software, computer hardware, peripheral devices, humans, and combinations of these."

 

Without getting into the omnipresence of interfaces too much (the keyboard is one, the mouse and cursor are as well, the monitor is an interface, all of the windows on the screen are interfaces, every single OS event is taking place between two endpoints of an interface, programming languages use interfaces as "contracts" that can be adhered to, to allow for different implementations of the same kinds of data structures and methods, our own 👀 are interfaces to the world around us and on and on- and each of these relies on their own software!...), let's take a simple example.

Let's say we have some really neat library of unique functionality we want to share with as many developers as possible called "extRS". We have so many options for how to get this into the hands of other developers. But it requires assembling the working software into different languages/compilations to fulfill the needs of the different interfaces.


This is what Windows 1.01 looked like; 1985 my, my...


  • For an API (the interface for web services), we will want to present our software in the form of an API running on a web server. With a live ASP.NET Web API (esp. one that can respond to XML and JSON) you can allow clients (interfaces) to get live data from an implementation of your library's functionality via something like a simple POST to /ConvertXmlToJson with a body of:
 { <car><passenger></passenger></car> }  
...which then returns the appropriate XML-translated-to-JSON:
 {  
   "car": 
     {  
       "passenger": null  
     }    
 }  

This is an example of an API request and response (FedEx API)


  • There is also ASP.NET CoreWCF for interfacing with legacy XML SOAP-based web services:
If you want to interface with a legacy SOAP service in ASP.NET Core- this is what you can use


  • For getting our logic into the hands of JavaScript and NodeJS developers we can create and host an NPM package:
htmx and hyperform.js for validation are the neatest 2 open source things I've discovered in the past 2 years


  • For Python developers we can publish a python libraries to PyPI which makes it download-able via pip:
This shows the installation for the Python package pandas through the Windows CMD prompt


  • And we could even create a Ruby gem version, and packages for other languages to boot.... and on and on....

The problem is that, the more packages and different types of assembled or executable libraries you create, the more code bases you have to continuously test and maintain. But if your logic is simple enough, it makes sense extend the reach of your software as much as possible. Every interface that could use it, should be able to get it, and use it.

We have not even discussed console interface (CLI) which is required if you want your logic to be utilized in scripting and tooling chains that perform logic by piping the results of processes into subsequent processes all in CLI scripts like bash or PowerShell.

  • Custom CLI .exe - We can expose our extRS functionality through a command line interface. We compile a CLI program, ExtRS.CLI.exe that accepts user input such as ConvertXmlToJson ""<mouse name="Micky"><mouse>"" and performs prompts to present information to the CLI interface. I hate to disappoint, but that hasn't yet been implemented for the extRS project .sln 🤷🏽‍♂️

A simple .NET CLI program using extRS; this can easily be modified to implement virtually all of the extRS functionality


  • PowerShell Module - With a PowerShell module, DevOps and CI/CD devs can easily install the PowerShell modules through the official Microsoft PowerShell Gallery Here is the ReportingServicesTools PowerShell module:

Here is the ReportingServicesTools PS module; as you can see (errors)- I've gotta update some things in extRSAuth to get it to work with this module(!)


  • Nuget Package (compiled, portable ".dll" binaries) - allow clients to easily get your library's code into any .NET project. With this, any .NET project can reference extRS via:

This is my SSRS extension library, "extRS" on Nuget.org

  • WinForms - though a bit antiquated, there are millions of SMB and huge corporate software applications running proprietary .NET Framework code through Windows forms interfaces. It is like COBOL and FORTRAN- some things just work, and so they persist. For things like point-of-sale systems, sometimes the best solution- is something proven and established that won't surprise anyone (a WinForms POS app). An extRS WinForms app looks like this:

A mock of what an extRS interface implementation in WinForms might look like


  • MAUI - my experience is limited here, but from what I have experienced, MAUI is a faster, more capable version of Xamarin (with Xamarin and Mono under the hood). The great thing about MAUI is that, with one project, you can target all manner of mobile form factors (phones, tablets, watches- iOS and Android). An interface for my "tickertapes" Android app on MAUI for an Android phone looks like this: 

An example of one of my apps, "Tickertapes", running in an Android emulator using MAUI



As you can see we have so many ways to expose our unique (extRS, tickertapes) functionality. The only limitation on getting your functional code into operating programs is- the right interface implementation!




PS: A note on physical client interfaces as well... While we have discussed the different software interfaces (API, CLI, .dll, NPM, .psm, etc.), from a UI presentation and experience perspective, we have to consider the different types of physical interfaces that our code will be represented on- particularly the screen dimensions and how that will affect the inclusion vs. exclusion and location of certain content.

Never give short shrift to UI compatibility across devices- it is dang hard to present a "consistent" experience in myriad "different" ways. ('kinda competing, incompatible angles).


Hardware Form Factors

  • Mobile Phones
  • Desktop monitors
  • Laptop monitors
  • Tablets
  • TV monitors
  • LED scoreboards
  • Digital scrolling Marquee signs
  • Digital Billboards
  • "Smart" Watches
  • VR Headsets
  • Earbuds
  • "Smart" Eye Glasses
  • IoT devices with little or no display at all (in BAS equipment for example, typically these are used to start/stop or accelerate/slow down, based on some kind of PLC configuration and to collect and report useful data metrics about the device)



*"On November 20, 1985 (38 years ago), the first retail release, Windows 1.01, was released in the United States at a cost of US$99 (equivalent to about $280.00 in 2023)." -Wikipedia

You need to eat your own dog food

I recently realized how useless my custom .NET method, "Sonrai.ExtRS.ReferenceDataService.GetGoogleNews(string searchTerm)", really was.

I had referenced Sonrai.ExtRS via Nuget and began trying to use it to improve the display in scrolling news links, in another application I maintain, tickertapes.net.   

It was useless! I mean a complete waste of code. It returned potentially useful data related to the searchTerm parameter- data from the GoogleNews API. But that is about it. It literally returned the entire XML response. 🤦‍♂️


This was the raw oil, but not the refined gasoline a consumer of a Nuget package expects...


I was parsing all this XML in the tickertapes.net app (why I do not know or care to remember), and so all the work necessary to wrangle the XML response to produce the needed news link collection was on the consumer of the Sonrai.ExtRS Nuget package- not good!

What the Nuget library should do, is all the work. What it should return, to be actually useful, is something structured like a collection of strings, each one representing a single news article, with the news headline being the text for the news article link.

And so I moved back to Sonrai.ExtRS to correct this unfortunate oversight.  

  public static async Task<List<string>> GetGoogleNewsWithLinks(string search)  
  {  
    HttpClient client = new HttpClient();  
    var content = await client.GetStringAsync(string.Format("https://news.google.com/rss/search?q={0}", search));  
    var parser = new HtmlParser();  
    var document = parser.ParseDocument(content);  
    var newsItems = document.All.Where(m => m.LocalName == "title").ToList();  
    var linkItems = document.All.Where(x => x.LocalName == "link").ToList();  
    var newsLinkItems = new List<string>();  
    for (int i = 0; i < newsItems.Count; i++)  
    {  
      newsLinkItems.Add("<a href='" + linkItems[i].NextSibling!.NodeValue + "' target='_blank'>" + newsItems[i].InnerHtml + "</a>");  
    }  
    return newsLinkItems;  
  }  


And this provides the Nuget client with something it can actually use, "turnkey"/out-of-the-box.

A list of HTML links which we can actually use; this is more like it.


The moral of the story is one that is as old as business and manufacturing: you must eat your own dog food. If there are problems or areas that need attention it is best that you find this information out before your product is released into the wild and a customer discovers the bug (or in this case, the uselessness), thus sullying your reputation as a business and software provider.

Test, test, test- always unit and/or integration test every user interaction and data movement for every story/path imaginable or support-able.

But there is nothing that replaces simply using your own product the way it is used by real users. And really using it for the purpose it was made. What you discover may help you shore up previously unknown problems and/or inspire you to make something useful that you would never otherwise think of, unless you were thinking from a user's perspective.


Reference: https://www.nuget.org/packages/Sonrai.ExtRS


PS: Imagine if James Newton-King never used NewtonSoft.Json for his own de/serializations? Or if Stack Overflow never used Dapper for the SO app/site? You need to use your creations right at the ground level (as a user/client) to verify that the functionality you designed in the abstract exactly matches how things will play out in concrete reality. Thoughts.. 💭

How to use .NET User Secrets in MSTest classes

When developing unit and integration tests, we don't necessarily want to share the secret keys and values we use as credentials for APIs, databases, etc.

So similarly to how we implement User Secret functionality in Program.cs, we can implement User Secrets and set test class variables that use the secrets via the MSTest classes' constructor (ReferenceDataTests() below):

  public static string upsId = "";  
  public static string upsSecret = "";  
  private IConfiguration _configuration { get; }  
  
  public ReferenceDataTests()  
  {  
    // set your API ids and secrets in UserSecrets (right-click project: "Manage User Secrets")  
    var builder = new ConfigurationBuilder()  
      .AddUserSecrets<ReferenceDataTests>();  
    _configuration = builder.Build(); 
    
    var secretVals = _configuration.GetChildren().ToList();  
    upsId = secretVals.Where(x => x.Key == "upsId").First().Value!;  
    upsSecret = secretVals.Where(x => x.Key == "upsSecret").First().Value!;   
  }  




ASCII art fun with .NET

My friend Benn with a nice largemouth bass

And here we have a simple function. Many thanks to its originator, Thinathayalan Ganesan.

You can find this in the Sonrai.ExtRS Nuget project under Sonrai.ExtRS.FormattingService.ConvertToAscii(Bitmap image) and see how it is used in Sonrai.ExtRS.FormattingTests.ConvertToAsciiSucceeds.

I talked a bit about how ASCII art works in a post on a similar Python script.

  // credit (Thinathayalan Ganesan): https://www.c-sharpcorner.com/article/generating-ascii-art-from-an-image-using-C-Sharp  
  public static string ConvertToAscii(Bitmap image)  
  {  
    string[] _AsciiChars = { "#", "#", "@", "%", "=", "+", "*", ":", "-", ".", "&nbsp;" };  
    Boolean toggle = false;  
    StringBuilder sb = new StringBuilder();  
    for (int h = 0; h < image.Height; h++)  
    {  
      for (int w = 0; w < image.Width; w++)  
      {  
        Color pixelColor = image.GetPixel(w, h);  
        //Average out the RGB components to find the Gray Color  
        int red = (pixelColor.R + pixelColor.G + pixelColor.B) / 3;  
        int green = (pixelColor.R + pixelColor.G + pixelColor.B) / 3;  
        int blue = (pixelColor.R + pixelColor.G + pixelColor.B) / 3;  
        Color grayColor = Color.FromArgb(red, green, blue);  
        //Use the toggle flag to minimize height-wise stretch  
        if (!toggle)  
        {  
          int index = (grayColor.R * 10) / 255;  
          sb.Append(_AsciiChars[index]);  
        }  
      }  
      if (!toggle)  
      {  
        sb.Append("\r\n");  
        toggle = true;  
      }  
      else  
      {  
        toggle = false;  
      }  
    }  
    return sb.ToString();  
  }  

The key is the assignment, pixel-by-pixel, of values to the reb, blue and green variables (and then "grayColor" variable) under the comment "Average out the RGB components to find the Gray Color". By getting the average of all colors in the pixel you can get the grayScale RGB color from Color.FromArgb(R, G, B). This grayscale RGB color is then used to select the appropriate ASCII character to represent the shade of gray in each pixel of the image.

  • A darker pixel of an image will use an ASCII character like a "." or "-" or ":".
  • A lighter pixel of an image will use an ASCII character like a "#" or "@" or "%".

In this way we can easily convert an image from its pixel-based source representation to an ASCII character representation. Essentially, a computer image is just a mosaic, or a composite of parts (pixels usually, sometimes ASCII characters- for art and fun!). 😃


Reference: https://www.c-sharpcorner.com/article/generating-ascii-art-from-an-image-using-C-Sharp