File size FormatProvider#

Please stop me if this functionality already exists somewhere in the Framework. I did some searching and came up empty. If it exists, let me know and I'll delete this post and we can all have a little chuckle at my expense.

I need to be able to display file sizes to a user. The files can be anywhere from bytes to gigabytes (no terabytes, yet). If I always display them in bytes - the numbers are so big, its hard to read. If I display them in megabytes, you lose a lot of granularity, etc. I want to display the file sizes in units that make sense depending on the magnitude. I need to format the numbers in a lot of different places, so I wasn't happy with repeatedly doing the necessary division everywhere.

Instead I created a simple ICustomFormatter named FileSizeFormatProvider. I created just enough functionality to meet my needs. Potential improvements would be to allow you to specify the initial units (currently assumes bytes), and also specify the output units (always show in MB, regardless of the size). These settings could be set through properties on the provider, or through the format specification string.

Thanks to David Hayden and his post on custom formatting that inspired my solution. Hopefully by posting this code I can save someone else the embarrassment of having to waste time on something so silly.

This program:

class Program

{

    static void Main(string[] args)

    {

        Console.WriteLine(String.Format(new FileSizeFormatProvider(), "File size: {0:fs}", 100));

        Console.WriteLine(String.Format(new FileSizeFormatProvider(), "File size: {0:fs}", 2000));

        Console.WriteLine(String.Format(new FileSizeFormatProvider(), "File size: {0:fs}", 30000000));

        Console.WriteLine(String.Format(new FileSizeFormatProvider(), "File size: {0:fs}", 400000000000));

    }

}

displays this output:

File size: 100.00 B
File size: 1.95kB
File size: 28.61MB
File size: 372.53GB

using this class:

public class FileSizeFormatProvider : IFormatProvider, ICustomFormatter

{

    public object GetFormat(Type formatType)

    {

        if (formatType == typeof(ICustomFormatter)) return this;

        return null;

    }

    private const string fileSizeFormat = "fs";

    private const Decimal OneKiloByte = 1024M;

    private const Decimal OneMegaByte = OneKiloByte * 1024M;

    private const Decimal OneGigaByte = OneMegaByte * 1024M;

 

    public string Format(string format, object arg, IFormatProvider formatProvider)

    {

        if (format == null || !format.StartsWith(fileSizeFormat))

        {

            return defaultFormat(format, arg, formatProvider);

        }

        if (arg is string)

        {

            return defaultFormat(format, arg, formatProvider);

        }

        Decimal size;

        try

        {

            size = Convert.ToDecimal(arg);

        }

        catch (InvalidCastException)

        {

            return defaultFormat(format, arg, formatProvider);

        }

 

        string suffix;

        if (size > OneGigaByte)

        {

            size /= OneGigaByte;

            suffix = "GB";

        }

        else if (size > OneMegaByte)

        {

            size /= OneMegaByte;

            suffix = "MB";

        }

        else if (size > OneKiloByte)

        {

            size /= OneKiloByte;

            suffix = "kB";

        }

        else

        {

            suffix = " B";

        }

        string precision = format.Substring(2);

        if (String.IsNullOrEmpty(precision)) precision = "2";

        return String.Format("{0:N" + precision + "}{1}", size, suffix);

    }

 

    private static string defaultFormat(string format, object arg, IFormatProvider formatProvider)

    {

        IFormattable formattableArg = arg as IFormattable;

        if (formattableArg != null)

        {

            return formattableArg.ToString(format, formatProvider);

        }

        return arg.ToString();

    }

}

Friday, August 25, 2006 1:29:16 PM (Central Daylight Time, UTC-05:00) #    Comments [2]  | 

 

Formerly known as Atlas#

You've probably read the news a hundred times around the blogosphere already, but that won't stop me from joining the chorus and adding my 2 cents.

The Microsoft AJAX (formerly known as DHTML) technology, codename Atlas, will be officially named... ASP.NET 7.0!

I was very skeptical at first... this is the explanation:

"ASP.NET 7.0 aptly identifies the technology for exactly what it is – the next version of our web developer framework."

"...The change is in name only and will not affect the technologies being delivered as part of the product. ASP.NET 7.0 is still comprised of the existing ASP.NET 2.0 components, including DataSource controls, the Provider model, and Master Pages & Themes, as well as new web 2.0 developer-focused innovative technologies like UpdatePanels and JSON serialization."

"...The ASP.NET platform has always been at the core of Atlas, but the Atlas brand didn’t convey this"

"...The name conveys the maturity of the platform by emphasizing that it has evolved from proven technology in production for over a decade, while also aligning it with the rest of the Web Tools Platform (IIS 7 + IE 7)."

Over a decade you ask? Follow along - it started with Active Server Pages 1, 2, & 3.0, changed the brand slightly to ASP.NET (4.0), ASP.NET 1.1 (5.0), and then most recently ASP.NET 2.0 (6.0). The next logical name of the development platform, ASP.NET 7.0, more accurately reflects this heritage.

(I can't help but suspect a little marketeering "two-upmanship" over the PHP 5 competition).

"In the specific example of ASP.NET 7.0, there is a lot of new compelling improvements to the technology that warrant a major version number for the redist"

"...We are confident that this change will go a long way towards reducing confusion people may have about our web developer platform and the technologies in which they should invest."

Reducing confusion? Ok, never mind what I said earlier about coming around.. this is ridiculous. If any of this bugs you like it bugs me, go sign the Reverse WinFX Petition.

This post was written using Windows Live Writer Beta. My source tells me that when it comes out of Beta, it will be renamed Word 13.

Update: You should probably read the follow up post regarding the naming of the Atlas framework.

Thursday, August 24, 2006 9:27:48 PM (Central Daylight Time, UTC-05:00) #    Comments [6]  | 

 

MSBuild References#

I've been doing a lot of work with MSBuild lately. I've gone beyond using it for my automated builds, and now use it as my tool of choice for automated deployments. While MSI packaging has some nice benefits (transactional installs), I much prefer the transparency and "keep it simple" aspect of an XML script. As a way into ease into what I hope will be a series of posts on MSBuild, I'm publishing a list of the resources that I keep open while authoring MSBuild script.

MSBuild Overview - ok, I don't really have a need for this one anymore, but its a good place to start if you are brand new to MSBuild.

MSBuild Task Reference - a list of the built-in tasks, or "actions", that can be performed by an MSBuild script.

MSBuild Community Tasks - an open source project that collects a number of tasks to perform common actions that are not included out of the box (think NAntContrib). If you are doing anything non-trivial with your build, especially if you are creating deployment scripts, this will be indispensable. Download the nightly build (many new tasks/features since the last release) and keep the .chm documentation file open. I'm also a very active contributor to this project, so tell me what's missing.

MSBuild Reserved Properties - a list of the built-in properties (variables) available to your script. Also keep in mind that all environment variables are automatically available to your script using the property syntax (so you get things like $(USERNAME), $(COMPUTERNAME), and $(SystemRoot) for free).

MSBuild Well-known Item Metadata - a list of the metadata properties available on all items (variables with properties).

You'll notice that most of these are straight out of the MSBuild documentation. I'm compiling them here because these resources are used constantly when developing a script of almost any complexity.

Wednesday, August 16, 2006 10:02:53 PM (Central Daylight Time, UTC-05:00) #    Comments [0]  | 

 

HttpModule to allow a custom error page for 401.2 Access Denied in ASP.NET#
As you know, the customErrors section of web.config allows you to define your own pages to display to the user when an error occurs. It allows for a default page to display when any unhandled error occurs, and it also allows you to specify different pages to display depending on the HTTP status code.

I like to take advantage of ASP.NET's declarative security model by defining the users and roles that are authorized to execute different parts of my website. For example, I can limit access to a page named Protected.aspx by declaring that all users must be a meber of the VerySpecialUsers group. This can be accomplished by adding the following section to my web.config:

  <location path="Protected.aspx">

    <system.web>

      <authorization>

        <allow roles="VerySpecialUsers"/>

        <deny users="*"/>

      </authorization>

    </system.web>

  </location>


I like this model as it allows me to easily modify the access restrictions for a page, and since it uses ASP.NET's Role Provider framework, it is not tied to any implementation about how role membership is determined. By default in a Windows Authentication/NTLM scenario, the roles map to the group memberships of your Windows login account (Administrators, Power Users, Users, etc). But you can very easily create your own Role Provider that retrieves group membership information from your own data store.

When a user does not meet the authorization requirements to visit a certain page, ASP.NET will return a 401.2 Access Denied error. The page looks like the standard yellow screen of death (YSOD) that ASP.NET returns for all errors.The customErrors section of web.config is supposed to let you provide a friendlier response to your users so they never have to see the YSOD. You would think that all you need to do is define a page for the 401 status code in customErrors and all would be good. You would be wrong. You can even try changing the custom error page defined in your IIS settings, and it still won't work.

The newsgroups have countless unanswered posts about this issue. Fortunately, my most recent attempt to solve this problem finally found some answers. The first one I found, and the one I use for my solution, was posted by John "iSpeakGeek" on ASPFree forums. I have since discovered a CodeProject article by George Mamaladze, and even a (somewhat related) Microsoft KB article. Hopefully this post will help improve the search results for other people struggling with this issue.

The key to the solution is to intercept the EndRequest event of the page lifecycle, check for a 401 status code, and then execute your custom page. The Microsoft article suggests intercepting the Error event and checking for an UnauthroizedAccessException, but that didn't work for my scenario. It may only apply when using impersonation, as the article describes. I need to do more testing and will possibly modify my solution to cover more scenarios.

Since I need this functionality on pretty much every website I make, I created an HttpModule to encapsulate the logic. The nice thing about my module approach is that you simply register the module in your web.config, and it will automatically respect the settings you define in your customErrors section. There is no hardcoded dependence on a specific error page or need for a custom configuration section.

<httpModules>

  <add name="CustomAccessDenied" type="FlimFlan.CustomAccessDenied, FlimFlan.CustomAccessDenied"/>

</httpModules>

<customErrors mode="On" defaultRedirect="Error.htm">

  <error statusCode="401" redirect="AccessDenied.aspx"/>

</customErrors>


I've packaged up the full source and a sample website. Use it and change it as you wish. If you don't care about the source, just grab the DLL from the DemoWeb\Bin folder and put it in your own website's Bin folder.

I can't claim it is a general purpose solution yet, as I have not tested it with all possible authentication/authorization settings. If you find a scenario that doesn't work, please let me know and I'll see if I can address it.

Download FlimFlan.CustomAccessDenied
Saturday, August 05, 2006 12:02:52 PM (Central Daylight Time, UTC-05:00) #    Comments [9]  | 

 

A simple color ConsoleTraceListener#
Considering how many pre-release Whidbey demos I saw showing off the new color support in Console applications, I'm surprised this wasn't include in the box. I use a System.Diagnostics.TraceSource coupled with the ConsoleTraceListener for output in console apps. Of course, when you have verbose logging enabled, you see a LOT of gray text scroll across the screen, and nothing really stands out. Reminds me of trying to decode NAnt output after living with MSBuild for a while. I did a quick search for "color consoletracelistener" and came across this offering from Mauricio Rojas. I (probably unfairly) dimissed it because it looked much more complicated than I would expect (ok, and because it was in VB).
So I spent 10 minutes throwing this together. It works for every scenario that I need. In other words, I make no claims that it is general purpose solution for you - it may not add color to every type of trace message (depending on which methods you use to write messages). If this doesn't work for you, Mauricio's may be a better bet.

using System;

using System.Diagnostics;

using System.Collections.Generic;

 

namespace FlimFlan.Diagnostics

{

    public class ColorConsoleTraceListener : ConsoleTraceListener

    {

        Dictionary<TraceEventType, ConsoleColor> eventColor = new Dictionary<TraceEventType, ConsoleColor>();

 

        public ColorConsoleTraceListener()

        {

            eventColor.Add(TraceEventType.Verbose, ConsoleColor.DarkGray);

            eventColor.Add(TraceEventType.Information, ConsoleColor.Gray);

            eventColor.Add(TraceEventType.Warning, ConsoleColor.Yellow);

            eventColor.Add(TraceEventType.Error, ConsoleColor.DarkRed);

            eventColor.Add(TraceEventType.Critical, ConsoleColor.Red);

            eventColor.Add(TraceEventType.Start, ConsoleColor.DarkCyan);

            eventColor.Add(TraceEventType.Stop, ConsoleColor.DarkCyan);

        }

 

        public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)

        {

            TraceEvent(eventCache, source, eventType, id, "{0}", message);

        }

 

        public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)

        {

            ConsoleColor originalColor = Console.ForegroundColor;

            Console.ForegroundColor = getEventColor(eventType, originalColor);

            base.TraceEvent(eventCache, source, eventType, id, format, args);

            Console.ForegroundColor = originalColor;

        }

 

        private ConsoleColor getEventColor(TraceEventType eventType, ConsoleColor defaultColor)

        {

            if (!eventColor.ContainsKey(eventType))

            {

                return defaultColor;

            }

            return eventColor[eventType];

        }

    }

}


You can see it in action using the following app:

using System.Diagnostics;

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            TraceSource trace = new TraceSource("log");

            trace.TraceEvent(TraceEventType.Start, 0);

            trace.TraceInformation("Hello World");

            trace.TraceEvent(TraceEventType.Error, 0, "Something failed.");

            trace.TraceEvent(TraceEventType.Verbose, 0, "I like ice cream.");

            trace.TraceEvent(TraceEventType.Critical, 0, "Something went horribly wrong!");

            trace.TraceEvent(TraceEventType.Verbose, 0, "I like cherries.");

            trace.TraceEvent(TraceEventType.Warning, 0, "This program will end soon...");

            trace.TraceEvent(TraceEventType.Information, 0, "Ending program.");

            trace.TraceEvent(TraceEventType.Stop, 0);

        }

    }

}


Add these settings to the configuration section of your app.config:

  <system.diagnostics>

    <sources>

      <source name="log" switchValue="All">

        <listeners>

          <add name="Console" type="FlimFlan.Diagnostics.ColorConsoleTraceListener, ConsoleApplication1" />

        </listeners>

      </source>

    </sources>

  </system.diagnostics>


And you should see something like this:

Thursday, August 03, 2006 10:02:45 PM (Central Daylight Time, UTC-05:00) #    Comments [0]  | 

 

All content © 2010, josh
About this site
Send mail to the author(s) Contact me
Feed your aggregator (RSS 2.0)
Joshua Flanagan
I am a software developer focused on continuous improvement in the .NET community
Los Techies

On this page
Archives
Rest of the world

Acknowledgements

Powered by: newtelligence dasBlog 2.1.8209.14743

Special thanks to LosTechies.com

Site theme based on the essence design by Jelle Druyts

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.