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." -https://www.informit.com/articles/article.aspx?p=3089304


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

extRS Portal: a modern SSRS client



ExtRS Portal provides a blueprint for extending the functionality of Reporting Services 


 
extRS (pronounced, "extras") is a modern SSRS client for distributing and reading reports; with some extras. A demo of the app is linked here: https://extrs.net
   
The audience is SSRS report users (you know, the people you need to justify having enterprise reporting in the first place). So things like applying item-level RS security, managing users, and adding, editing and deleting SSRS catalog items and other system-level properties are not part of this client- at least not yet.

The aim here is to make SSRS at least slightly more interesting, accessible and useable for information consumers. This particuliar deployment of the extRS.Portal web client is connected to a report server with custom authentication (extRSAuth) which gets passed the normally required "Windows authentication" hamstring of the default SSRS installation. 

This wrapper and extension UI not only improve the user authentication experience and dynamism of SSRS parameter behaviors in the UI but also provide SSRS admins and other users with rich enterprise reporting usage and delivery data.

Enabled are the most of the features contained in Reporting Service's built-in Report Portal at /reports.

I have disabled some things like deleting and uploading items for the sake of keeping my demo of the app small and simple.

The source code can be found here: https://github.com/sonrai-LLC/extRS



tickertapes

Users can search for any word or phrase and opt for news of common financial market indexes


Originally implemented as "Twickertapes" and utilizing the original Twitter API (v2.0), this app is merely a demonstration of what can be done with a little text input, and API (the Google News API) and scrolling text and ASCII art.

You can find it on the web here: https://tickertapes.net