Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

3/18/2022

ASP.NET Core Middleware to Update Response Body

I needed a quick way to update a mix of static HTML files and Razor views to change a message common to a lot of pages. In the example here I will update the Copyright message in HTML pages.

As usual I searched the web and found many examples, none that worked the way I needed (or worked at all!), but I did find an assortment of "bits" scattered throughout StackOverflow that helped me build a working solution.

At first glance this should have been a textbook example of ASP.NET Core middleware. Intercept the outgoing response, update the body HTML and forward on through the middleware pipeline. The first issue was the Stream object used by Response.Body object, its an Microsoft.WebTools.BrowserLink.Net.ScriptInjectionFilterStream object that does not support Seek. The first "bit" that I found in StackOverflow was to replace the Response.Body steam with my own stream, in particular a MemoryStream. Then we can send the Response object on down the middleware pipeline using next.Invoke. When the Response comes back to us though the pipeline we need to extract the HTML text from our custom stream object, do our updates to the HTML and finally get the original kind of stream back into the Response.Body with our changes.

The next problem was that I could not find a way to clear or reset the stream that represented the returned Body content. I was getting both the original page plus the updated page, i.e. my data was getting appended. .SetLength(0) and .Seek(0, SeekOrigin.Begin) did not work. ("Specified method is not supported"). StackOverflow to the rescue again. A quick solution was to recreate an empty stream object. ( context.Response.Body = new MemoryStream(); )

Remember! The order of middleware is important. If you want this to process your JavaScript, CSS and static HTML files, then add this before app.UseStaticFiles(). Most likely order:

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.Use(  the code below  );
            app.UseRouting();
            app.UseAuthorization();

 

So here's the solution as an inline "app.Use()" block added to Startup.cs or Program.cs (.NET 6). It replaces the Copyright date from "2021" to the current year.

 

Note: Code tested in .NET Core 3.1 and .NET 6.0 using Visual Studio 2022.


app.Use(async (context, next) =>
{
    using (var replacementStream = new MemoryStream())
    {
        // preprocessing the Body
        // replace {Microsoft.WebTools.BrowserLink.Net.ScriptInjectionFilterStream}
        // with a MemoryStream (seekable!)
        System.IO.Stream originalStream = context.Response.Body;
        context.Response.Body = replacementStream;

        // do any preprocessing of the Request or Response object here
        // none needed for this example

        // move on down the pipeline, but with our custom stream
        await next.Invoke();

        // we are now back from the rest of the pipeline and on the way out (TTN)

        // postprocessing the Body

        // read the returned Body
        replacementStream.Seek(0, SeekOrigin.Begin);
        using (var bufferReader = new StreamReader(replacementStream))
        {
            string body = await bufferReader.ReadToEndAsync();

            // Is this a web page?
            if (context.Response.ContentType.Contains("text/html"))
            {
                // make changes to the returned HTML
                // set the copyright to the current year
                body = body.Replace("© 2021", "© " + DateTime.Now.Year.ToString());
            }

            // discard anything in the existing body.                        
            context.Response.Body = new MemoryStream();

            // write the updated Body into the Response using our custom stream
            context.Response.Body.Seek(0, SeekOrigin.Begin);
            await context.Response.WriteAsync(body);

            // extract the Body stream into the original steam object
            //   in effect, covert from MemoryStream to 
            //   {Microsoft.WebTools.BrowserLink.Net.ScriptInjectionFilterStream}
            context.Response.Body.Position = 0;
            await context.Response.Body.CopyToAsync(originalStream);

            // now send put the original stream back into the Response
            // and exit to the rest of the middleware pipeline
            context.Response.Body = originalStream;
        }
    }
});


 

Here's the same solution as a Middleware class.

  1. Add the class below.
  2. Update Startup.cs or Program.cs (.NET 6) to add an app.UseReplaceText line.

 Startup.cs or Program.cs:

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseReplaceText();  // custom middleware
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => ....

The class:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System;
using System.IO;
using System.Threading.Tasks;

namespace WebAppToDemoMiddleware
{
    public class ReplaceTextMiddleware
    {
        private readonly RequestDelegate _next;

        public ReplaceTextMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        // 
        public async Task InvokeAsync(HttpContext context)
        {
            using (var replacementStream = new MemoryStream())
            {
                // preprocessing the Body
                // replace {Microsoft.WebTools.BrowserLink.Net.ScriptInjectionFilterStream}
                // with a MemoryStream (seekable!)
                System.IO.Stream originalStream = context.Response.Body;
                context.Response.Body = replacementStream;

                // do any preprocessing of the Request or Response object here
                // none needed for this example

                // move on down the pipeline, but with our custom stream
                await _next(context);

                // we are now back from the rest of the pipeline and on the way out

                // postprocessing the Body

                // read the returned Body
                replacementStream.Seek(0, SeekOrigin.Begin);
                using (var replacementStreamReader = new StreamReader(replacementStream))
                {
                    string body = await replacementStreamReader.ReadToEndAsync();

                    //if (body.Contains("Welcome"))
                    // Is this a web page?
                    if (context.Response.ContentType.Contains("text/html"))
                    {
                        // make changes to the returned HTML
                        // set the copyright to the current year
                        body = body.Replace("© 2021", "© " + DateTime.Now.Year.ToString());
                    }

                    // discard anything in the existing body.                        
                    context.Response.Body = new MemoryStream();

                    // write the updated Body into the Response using our custom stream
                    context.Response.Body.Seek(0, SeekOrigin.Begin);
                    await context.Response.WriteAsync(body);

                    // extract the Body stream into the original steam object
                    //   in effect, covert from MemoryStream to 
                    //   {Microsoft.WebTools.BrowserLink.Net.ScriptInjectionFilterStream}
                    context.Response.Body.Position = 0;
                    await context.Response.Body.CopyToAsync(originalStream);

                    // now send put the original stream back into the Response
                    // and exit to the rest of the middleware pipeline
                    context.Response.Body = originalStream;
                }
            }

        }
    }

    // Add an extension class so we can use this as app.UseReplaceText()
    public static class RequestCultureMiddlewareExtensions
    {
        public static IApplicationBuilder UseReplaceText(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<ReplaceTextMiddleware>();
        }
    }
}
..

8/03/2020

How to count lines of code using PowerShell

I've got a project... scattered through a number of folders are a bunch of C# files. How many lines of code do I have? (Bragging rights you know...)

You could open the project in Visual Studio and use the Analyze, Calculate Code Metrics. But that requires Visual Studio, and does not give me all the options I want. (But in many ways is better! Lines by file and by method are available there.)

I wanted to do this on raw files using PowerShell, so assuming your project is in C:\myProject and you only wanted to count ".cs" files...

  dir C:\myProject -Recurse *.cs | Get-Content | measure

Get-Content returns the lines from the file as an array, and Measure-Object counts things.

Want to count characters too?

  dir C:\myProject -Recurse *.cs |
    Get-Content |
      measure
-Line -Character

Your boss won't let you take credit for blank lines?

  dir C:\myProject -Recurse *.cs |
    Get-Content |
      where { $_.trim() -ne "" } |
        measure
-Line -Character

Your boss won't let you take credit for blank lines or comments? (I would pay extra for good comments!)

  dir C:\myProject -Recurse *.cs |
    Get-Content |
      where { $_.trim() -ne "" -and $_.trim() -notlike "//*" } |
        measure
-Line -Character

    (Sorry, but in this quick little exercise I did not deal with /* and */ block comments, only // comments.)

Want to include multiple file types?

  dir C:\myProject -Recurse -Include *.cs, *.cshtml |
    Get-Content |
      where { $_.trim() -ne "" -and $_.trim() -notlike "//*" } |
        measure
-Line -Character


Boy I've been working hard... and using wizards to write code... 😉

   Lines Words Characters Property
   ----- ----- ---------- --------
    2563           117084

10/22/2016

Forest and Trees Problem: "A network-related or instance-specific error occurred while establishing a connection to SQL Server"

 

A "Can't see the tree for the forest" problem.


image_thumb[11]
There's an old phrase, "Can't see the forest for the trees", that in reverse, "Can't see the tree for the forest", applies to a recent "demo fail" of mine.

During a break in a C# class I was delivering last week I typed up a little demo for accessing SQL Server, and got an error. A quick read of the error said that it couldn't find the server, and it hinted at a protocol error.  

Additional information: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: Named Pipes Provider, error: 40 - Could not open a connection to SQL Server)

    image_thumb[12]

 

Seeing the big "Named Pipes" tree standing there… I Binged and Googled and found all kinds of stuff… that did not help.

While the answer to the problem was clearly stated in the message above, "Verify the instance name is correct", I didn't see it as I was looking at all of the other "trees" in that little code forest. The "tree" that I needed to deal with in this case was a C# beginner error of putting a backslash in a string. (I copy and pasted it without looking at it!) The back slash is an escape character to flag the next character as a special code. In this case "\v" is the code for a Vertical Tab. So, I had created a connection string looking for a server named "(localdb)VerticalTab11.0".

image

What made this little error a bit painful was that in this class I had mentioned escape characters in C#, and how to deal with them, at least four times! Oh well…

To solve the problem, escape the escape character ("(localdb)\\v11.0") or mark the entire string as a literal string with the At sign ("con.ConnectionString = @"Data Source=(localdb)\v11.0 …").

   image

For a list of the C# escape characters see this MSDN article:
   https://msdn.microsoft.com/en-us/library/h21280bw.aspx

.

1/07/2015

Still exam procrastinating? Second Shot is back!

 

Time for New Year's resolutions? Or just finish some of last years…

Take any Microsoft Certified Professional (MCP) exam between January 5, 2015, and May 31, 2015. If you don't pass, get a free retake!

All of the Microsoft Certified Solutions Associate (MCSA), Microsoft Certified Solutions Expert (MCSE), Microsoft Certified Solutions Developer (MCSD), and Microsoft Specialist certification exams are eligible. Microsoft Technology Associate (MTA) exams and Microsoft Office Specialist (MOS) exams do not qualify for this promotion.

Details here: https://www.microsoft.com/learning/en-us/second-shot.aspx

 

.

12/07/2014

Just how much is PI in .Net?

 

<Silly blog article>

 

The .Net Framework includes a math library that includes a constant for PI. Just how much is PI?

The MSDN article says:

   image

 

The Visual Studio debugger says:

   image

The Visual Studio Autos and Locals windows say:

   image

And when written from Console.WriteLine:

   image

Or does it?  Reformatted with "R" WriteLine matches the value reported from the debugger.

   image

and for the final test:

   image

 

So who is right? According to Wikipedia the first 50 digits are 3.14159265358979323846264338327950288419716939937510. Based on that number the MSDN documentation describes a more accurate number than returned by Math.PI.

 

From the .Net double documentation: here and here

  • Double-precision numbers store an approximation of a real number.
  • Holds signed IEEE 64-bit (8-byte) double-precision floating-point numbers
  • A Double value has up to 15 decimal digits of precision, although a maximum of 17 digits is maintained internally
  • When you work with floating-point numbers, remember that they do not always have a precise representation in memory.
  • The documentation's examples only have 18 digits, so PI as a double should be apx 3.14159265358979324

 

In the end…

3.14159265358979323846264338327950288419716939937510  Wikipedia
3.14159265358979323846   MSDN documentation for Math.PI
3.14159265358979324        What I would guess based on the documentation for doubles
3.1415926535897931          Returned in Visual Studio debugging tools
3.1415926535897931          Returned by the ToString() conversion through WriteLine formatted with "R"
3.14159265358979             Returned by the ToString() conversion through WriteLine

and just for fun… JavaScript says:
3.141592653589793

 

So…  for the important question… is the value of PI in the documentation for Math.PI wrong?  Smile

   image

 

</Silly blog article>

Now I have to get back to some real work!

.

11/18/2014

C# Compare Arrays

 

Sometimes we get so focused on the advanced features of .NET that we (or at least I) forget the basics. The basic in this case is checking to see if two arrays are equal.

int[] a = { 1, 7, 4, 6, 2, 8, 2, 8, 4, 6, 2 };
int[] b = { 1, 7, 4, 6, 2, 8, 2, 8, 4, 6, 2 };
if (b == a) 
  { Console.WriteLine("a Equals b!"); } 
  else 
  { Console.WriteLine("a NOT Equals b!"); }

The above always returns "a NOT Equals b!".  Yes, I knew that if (a == b) (same as a.Equals(b)) will not work as arrays are objects and "a == b" is testing reference equality, i.e. to see if they are the same object. But I also figured that as .NET has a built in class or method for everything you can think of, there had to be one to do an array comparison. Turns out there is, .SequenceEqual., which is part of LINQ.

if (b.SequenceEqual(a))
  { Console.WriteLine("a SequenceEqual b!"); } 
  else 
  { Console.WriteLine("a NOT SequenceEqual b!"); }

The above works great, but as LINQ is wrapper for other code, there must be some overhead, and it turns out there's a lot! I wrote a quick and dirty CompareArrays method and did a comparison of performance between my crude code and the LINQ SequenceEqual. Here's the results:

image

The manual code was around five times faster! BUT, it took a whole lot longer to write. Unless you are writing games or intense code, developer efficiency and code readability leans towards the LINQ SequenceEqual solution.

Here's the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] a = { 1, 7, 4, 6, 2, 8, 2, 8, 4, 6, 2 };
            int[] b = { 1, 7, 4, 6, 2, 8, 2, 8, 4, 6, 2 };

            DateTime d1 = DateTime.Now;
            for (int i = 0; i < 100000; i++)
            {
                b.SequenceEqual(a);
            }

            DateTime d2 = DateTime.Now;
            for (int i = 0; i < 100000; i++)
            {
                CompareArrays(a, b);
            }

            DateTime d3 = DateTime.Now;
            Console.Write("Time for SequenceEqual:    ");
            Console.WriteLine(d2 - d1);
            Console.Write("Time for my CompareArrays: ");
            Console.WriteLine(d3 - d2);

            Console.ReadLine();
        }

        static bool CompareArrays(int[] a, int[] b)
        {
            if (a.Length != b.Length) { return false; }
            for (int i = 0; i < a.Length; i++)
            {
                if (a[i] != b[i]) { return false; }

            }
            return true;

        }

    }
}

 

When I get really bored I convert CompareArrays into generics…

 

Hey! Look! I wrote about something other than SharePoint or PowerShell!

 

.

Note to spammers!

Spammers, don't waste your time... all posts are moderated. If your comment includes unrelated links, is advertising, or just pure spam, it will never be seen.