[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);
}
extRS for useful common logic, reference data and extending SSRS
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.
"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.
- 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> }
{
"car":
{
"passenger": null
}
}
- There is also ASP.NET CoreWCF for interfacing with legacy XML SOAP-based web services:
- For getting our logic into the hands of JavaScript and NodeJS developers we can create and host an NPM package:
- For Python developers we can publish a python libraries to PyPI which makes it download-able via pip:
- 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 🤷🏽♂️
- 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:
- 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:
- 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:
- 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:
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. 🤦♂️
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.
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
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 = { "#", "#", "@", "%", "=", "+", "*", ":", "-", ".", " " };
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();
}
- 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 "%".
extRS Portal: a modern SSRS client
tickertapes
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