Archive

Archive for the ‘c#’ Category

CrazyBeavers.Geodesy

November 19th, 2010 No comments

I just finished uploading my geodesy library (CrazyBeavers.Geodesy) to CodePlex. It has some various functions for working with coordinates (converting between coordinate systems and calculating distances and such). It can be found here: http://beavergeodesy.codeplex.com/.

Give me a shout if you want to help me with this project (just adding more grid projections would be awesome!) or if you just want to thank me for it :) .

Categories: .Net, c# Tags: , , , ,

Geocoding with Google Maps API

October 22nd, 2010 2 comments

It has been many months since I posted something here so finally today I wrote a small function that I thought I could share to get this blog going again. It’s a small snippet that takes an address and tries to geocode it (convert it to coordinates) using the Google Maps API.

public class Coordinate
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }

    public static Coordinate[] GeocodeAddress(string address)
    {
        List<Coordinate> coordinates = new List<Coordinate>();

        string response = new WebClient().DownloadString(string.Format("http://maps.googleapis.com/maps/api/geocode/xml?address={0}&sensor=false", HttpUtility.UrlEncode(address)));

        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(response);

        string status = xmlDoc.DocumentElement.SelectSingleNode("status").InnerText;

        switch (status.ToLowerInvariant())
        {
            case "ok":
                // Everything went just fine
                break;
            case "zero_results":
                return coordinates.ToArray();
            case "over_query_limit":
            case "invalid_request":
            case "request_denied":
                throw new Exception("An error occured when contacting the Google Maps API."); // Should probably be refined to something more useful like throwing specific exceptions for each error
        }

        XmlNodeList nodeCol = xmlDoc.DocumentElement.SelectNodes("result");
        foreach (XmlNode node in nodeCol)
        {
            double lat = Convert.ToDouble(node.SelectSingleNode("geometry/location/lat").InnerText, CultureInfo.InvariantCulture);
            double lng = Convert.ToDouble(node.SelectSingleNode("geometry/location/lng").InnerText, CultureInfo.InvariantCulture);

            Coordinate wgs84 = new Coordinate() { Latitude = lat, Longitude = lng };
            coordinates.Add(wgs84);
        }

        return coordinates.ToArray();
    }
}

For more info on the Google Maps Geocoding API you can visit http://code.google.com/apis/maps/documentation/geocoding/.

I’ve been working heavly with Google Maps and transforming coordinates at work this summer and when I get the time I’m going to make a nice package of the things I’ve created. I have a few helper functions in .Net for transforming between grid-based coordinates (X, Y) and geodesic coordinates (latitude, longitude) using the GaussKruger-formula as well as some snippets for Google Maps to handle large amounts of markers on a map (by hiding markers outside of the viewport) and some basic layer support for the markers.

ActionFilters in MVC

October 20th, 2009 No comments

I’ve been working on two fairly large MVC-projects lately. ImagerGallery.Net and a webshop-application suitably named BeaverShop. While working on those projects I’ve realized that I just love being able to use ActionFilters in MVC. Basically, they are attributes that you append to action-methods. They can be used to validate input, parse data into more suitable structures, check security and lots more.

One of my most used filters in both ImagerGallery and BeaverShop is one I named PagingAttribute which I thought I should show to you today.

Any ActionFilter need to inherit the ActionFilterAttribute and implement at least one of the following methods: OnActionExecuting, OnActionExecuted, OnResultExecuting or OnResultExecuted. They are all called during different stages of the execution of an action-method and can be used for various things. The one I’ll be using is OnActionExecuting since it is executed before the action so that I can pass extra arguments to the action.

The code is quite easy to understand. There is only one property to be set (except for the default property Order which is defined in ActionFilterAttribute) which is which field to be parsed. I check if the field is null or empty, if not ten I use a regular expression to see check if the selected field ends with “page” and a numerical value. The numerical value is then returned as a argument for the action called “pageNo”. This value is defaulted to 1 to make sure that we always get a number for our page (and not specifying anything should mean the first page). In my case I also want the field to be modified so it doesn’t contain the “page” part but that might not be suitable for everyone. If not, then you can easily remove that part by commenting the line looking like this: filterContext.ActionParameters[Field] = id;

using System;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;

namespace BeaverShopMvc.Attributes
{
    /// <summary>
    /// An ActionFilter to parse a page number from a field value
    /// </summary>
    public class PagingAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// The name of the field to parse
        /// </summary>
        public string Field { get; set; }

        // A compiled regular expression to speed things up
        private static Regex _pageRegex = new Regex("page(?<PageNo>\\d+)$", RegexOptions.CultureInvariant | RegexOptions.Compiled);

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            string id = filterContext.ActionParameters[Field] as string;
            Int32 pageNo = 1;

            // We never need an ending / so remove it
            id = id.TrimEnd('/');

            // Check so that the field isn't empty and while at it also that it
            // contains "page"
            if (!string.IsNullOrEmpty(id) && id.ToLowerInvariant().Contains("page"))
            {
                Match match = _pageRegex.Match(id);
                if (match.Success)
                {
                    pageNo = Convert.ToInt32(match.Groups["PageNo"].Value);
                    if (id.Contains("/"))
                        id = id.Substring(0, id.LastIndexOf('/'));
                    else
                        id = string.Empty;
                }
            }

            filterContext.ActionParameters["pageNo"] = pageNo;
            // The line below replaces the old field value with a
            // new one without the "page" part
            filterContext.ActionParameters[Field] = id;

            base.OnActionExecuting(filterContext);
        }
    }
}

Using the attribute is really simple and the great thing about this is that it is easily reusable on any actions you need it to. Below is a little sample of it in action.

        [Paging(Field = "id")]
        public ActionResult ListProducts(string id, Int32 pageNo)
        {
            return View();
        }

I have a few more filters that I really like as well (AkismetValidatorAttribute for example) that I might share some other day. I’m still learning a lot about working with MVC and will keep you posted with what other interesting things I can find.

Reading EXIF with extension methods

August 20th, 2009 2 comments

I’m still putting quite a lot of work into Imager Gallery.Net. I have a alpha up an running at www.kallesbildarkiv.se though it still lacks quite a lot of features.

Keeping my earlier commitment to release interesting code created for the project I can now present you with my next piece of code. I’ve had problems with finding a good class to read EXIF-information from images that isn’t written in VB.Net, really poorly written or licensed under GPL so I decided I had to write my own. After looking into the subject I found that it would be quite easy to implement this as extension methods on the regular Image class. I’ve decided to release the code under the Creative Commons Attribution-Share Alike license to allow people to use it anywhere as long as they can admit that they are using it.

/*
 * Creative Commons Attribution-Share Alike 3.0 Unported
 * You are free:
 *   to Share — to copy, distribute and transmit the work
 *   to Remix — to adapt the work
 *
 * Under the following conditions:
 *   Attribution — You must attribute the work in the manner
 *   specified by the author or licensor (but not in any way
 *   that suggests that they endorse you or your use of the work).
 *
 *   Share Alike — If you alter, transform, or build upon this work,
 *   you may distribute the resulting work only under the same, similar
 *   or a compatible license.
 *
 * For more information, see http://creativecommons.org/licenses/by-sa/3.0/
 */
using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Text;
using System.Drawing;

/*
 * This class is completely based on the documentation
 * of the EXIF format found here: http://www.exif.org/Exif2-2.PDF
 */

namespace CrazyBeavers.Extensions
{
    public static partial class ImageExtensions
    {
        #region Enumerations
        #pragma warning disable 1591

        private enum PropertyType
        {
            ByteArray = 1,
            AsciiString = 2,
            UnsignedShort = 3,
            UnsignedLong = 4,
            UnsignedLongPair = 5,
            Any = 6,
            SignedLongArray = 7,
            UnsignedLongPairArray = 10
        }

        public enum PropertyTag
        {
            ImageWidth = 0x100,
            ImageLength = 0x101,
            NBitsPerSample = 0x102,
            Compression = 0x103,
            PhotometricInterpretation = 0x106,
            Orientation = 0x112,
            SamplesPerPixel = 0x115,
            PlanarConfiguration = 0x11C,
            YCbCrSubSampling = 0x212,
            YCbCrPositioning = 0x213,
            XResolution = 0x11A,
            YResolution = 0x11B,
            ResolutionUnit = 0x128,
            StripOffsets = 0x111,
            RowsPerStrip = 0x116,
            StripByteCounts = 0x117,
            JPEGInterchangeFormat = 0x201,
            JPEGInterchangeFormatLength = 0x202,
            TransferFunction = 0x12D,
            WhitePoint = 0x13E,
            PrimaryChromaticities = 0x13F,
            YCbCrCoefficients = 0x211,
            ReferenceBlackWhite = 0x214,
            DateTime = 0x132,
            ImageDescription = 0x10E,
            Make = 0x10F,
            Model = 0x110,
            Software = 0x131,
            Artist = 0x13B,
            Copyright = 0x8298,
            ExifVersion = 0x9000,
            FlashpixVersion = 0xA000,
            ColorSpace = 0xA001,
            ComponentsConfiguration = 0x9101,
            CompressedBitsPerPixel = 0x9102,
            PixelXDimension = 0xA002,
            PixelYDimension = 0xA003,
            MakerNote = 0x927C,
            UserComment = 0x9286,
            RelatedSoundFile = 0xA004,
            DateTimeOriginal = 0x9003,
            DateTimeDigitized = 0x9004,
            SubSecTime = 0x9290,
            SubSecTimeOriginal = 0x9291,
            SubSecTimeDigitized = 0x9292,
            ImageUniqueID = 0xA420,
            ExposureTime = 0x829A,
            FNumber = 0x829D,
            ExposureProgram = 0x8822,
            SpectralSensitivity = 0x8824,
            ISOSpeedRatings = 0x8827,
            OECF = 0x8828,
            ShutterSpeedValue = 0x9201,
            ApertureValue = 0x9202,
            BrightnessValue = 0x9203,
            ExposureBiasValue = 0x9204,
            MaxApertureValue = 0x9205,
            SubjectDistance = 0x9206,
            MeteringMode = 0x9207,
            LightSource = 0x9208,
            Flash = 0x9209,
            FocalLength = 0x920A,
            SubjectArea = 0x9214,
            FlashEnergy = 0xA20B,
            SpatialFrequencyResponse = 0xA20C,
            FocalPlaneXResolution = 0xA20E,
            FocalPlaneYResolution = 0xA20F,
            FocalPlaneResolutionUnit = 0xA210,
            SubjectLocation = 0xA214,
            ExposureIndex = 0xA215,
            SensingMethod = 0xA217,
            FileSource = 0xA300,
            SceneType = 0xA301,
            CFAPattern = 0xA302,
            CustomRendered = 0xA401,
            ExposureMode = 0xA402,
            WhiteBalance = 0xA403,
            DigitalZoomRatio = 0xA404,
            FocalLengthIn35mmFilm = 0xA405,
            SceneCaptureType = 0xA406,
            GainControl = 0xA407,
            Contrast = 0xA408,
            Saturation = 0xA409,
            Sharpness = 0xA40A,
            DeviceSettingDescription = 0xA40B,
            SubjectDistanceRange = 0xA40C
        }

        public enum Compression
        {
            NotSpecified = 0,
            Uncompressed = 1,
            JpegCompression = 6
        }

        public enum PhotometricInterpretation
        {
            RGB = 2,
            YCbCr = 6
        }

        public enum Orientation
        {
            TopLeft = 1,
            TopRight = 2,
            BottomRight = 3,
            BottomLeft = 4,
            LeftTop = 5,
            RightTop = 6,
            RightBottom = 7,
            LeftBottom = 8
        }

        public enum ExposureMode
        {
            AutoExposure = 0,
            ManualExposure = 1,
            AutoBracket = 2
        }

        public enum ExposureProgram
        {
            Undefined = 0,
            Manual = 1,
            Normal = 2,
            AperturePriority = 3,
            ShutterPriority = 4,
            CreativeProgram = 5,
            ActionProgram = 6,
            PortraitMode = 7,
            LandscapeMode = 8
        }

        public enum MeteringMode
        {
            Unknown = 0,
            Average = 1,
            CenterWeightedAverage = 2,
            Spot = 3,
            MultiSpot = 4,
            Pattern = 5,
            Partial = 6,
            Other = 255
        }

        public enum Flash
        {
            FlashDidNotFire = 0x0000,
            FlashFired = 0x0001,
            StrobeReturnNotDetected = 0x0005,
            StrobeReturnDetected = 0x0007,
            FlashFiredCompulsoryMode = 0x0009,
            FlashFiredCompulsoryModeStrobeReturnNotDetected = 0x000D,
            FlashFiredCompulsoryModeStrobeReturnDetected = 0x000F,
            FlashDidNotFireCompulsoryMode = 0x0010,
            FlashDidNotFireAutoMode = 0x0018,
            FlashFiredAutoMode = 0x00019,
            FlashFiredAutoModeStrobeReturnNotDetected = 0x001D,
            FlashFiredAutoModeStrobeReturnDetected = 0x001F,
            NoFlash = 0x0020,
            FlashFiredRedEyeReduction = 0x0041,
            FlashFiredRedEyeReductionStrobeReturnNotDetected = 0x0045,
            FlashFiredRedEyeReductionStrobeReturnDetected = 0x0047,
            FlashFiredCompulsoryModeRedEyeReduction = 0x0049,
            FlashFiredCompulsoryModeRedEyeReductionStrobeReturnNotDetected = 0x004D,
            FlashFiredCompulsoryModeRedEyeReductionStrobeReturnDetected = 0x004F,
            FlashFiredAutoModeRedEyeReduction = 0x0059,
            FlashFiredAutoModeRedEyeReductionStrobeReturnNotDetected = 0x005D,
            FlashFiredAutoModeRedEyeReductionStrobeReturnDetected = 0x005F
        }

        public enum WhiteBalance
        {
            Auto = 0,
            Manual = 1
        }

        public enum SceneCaptureType
        {
            Standard = 0,
            Landscape = 1,
            Portrait = 2,
            NightScene = 3
        }

        public enum LightSource
        {
            Unkown = 0,
            Daylight = 1,
            Fluorescent = 2,
            Tungsten = 3,
            Flash = 4,
            FineWeather = 9,
            CloudyWeather = 10,
            Shade = 11,
            DaylightFluorescent = 12,
            DayWhiteFluorescent = 13,
            CoolWhiteFluorescent = 14,
            WhiteFluorescent = 15,
            StandardLightA = 17,
            StandardLightB = 18,
            StandardLightC = 19,
            D55 = 20,
            D65 = 21,
            D75 = 22,
            D50 = 23,
            ISOStudioTungsten = 24,
            OtherLightSource = 255
        }

        public enum GainControl
        {
            None = 0,
            LowGainUp = 1,
            HighGainUp = 2,
            LowGainDown = 3,
            HighGainDown = 4
        }

        public enum Contrast
        {
            Normal = 0,
            Soft = 1,
            Hard = 2
        }

        public enum Saturation
        {
            Normal = 0,
            LowSaturation = 1,
            HighSaturation = 2
        }

        public enum Sharpness
        {
            Normal = 0,
            Soft = 1,
            Hard = 2
        }

        public enum SubjectDistanceRange
        {
            Unknown = 0,
            Macro = 1,
            CloseView = 2,
            DistantView = 3
        }

        #pragma warning restore 1591
        #endregion

        #region Properties

        public static DateTime ExifDateTime(this Image image)
        {
            return GetPropertyDateTime(image, PropertyTag.DateTime);
        }

        public static DateTime ExifDateTimeDigitized(this Image image)
        {
            return GetPropertyDateTime(image, PropertyTag.DateTimeDigitized);
        }

        public static DateTime ExifDateTimeOriginal(this Image image)
        {
            return GetPropertyDateTime(image, PropertyTag.DateTimeOriginal);
        }

        public static string ExifMake(this Image image)
        {
            return GetPropertyString(image, PropertyTag.Make);
        }

        public static string ExifModel(this Image image)
        {
            return GetPropertyString(image, PropertyTag.Model);
        }

        public static string ExifImageDescription(this Image image)
        {
            return GetPropertyString(image, PropertyTag.ImageDescription);
        }

        public static string ExifSoftware(this Image image)
        {
            return GetPropertyString(image, PropertyTag.Software);
        }

        public static string ExifArtist(this Image image)
        {
            return GetPropertyString(image, PropertyTag.Artist);
        }

        public static Orientation ExifOrientation(this Image image)
        {
            return (Orientation)GetPropertyUInt16(image, PropertyTag.Orientation);
        }

        public static Flash ExifFlash(this Image image)
        {
            return (Flash)GetPropertyUInt16(image, PropertyTag.Flash);
        }

        public static SceneCaptureType ExifSceneCaptureType(this Image image)
        {
            return (SceneCaptureType)GetPropertyUInt16(image, PropertyTag.SceneCaptureType);
        }

        public static WhiteBalance ExifWhiteBalance(this Image image)
        {
            return (WhiteBalance)GetPropertyUInt16(image, PropertyTag.WhiteBalance);
        }

        public static MeteringMode ExifMeteringMode(this Image image)
        {
            return (MeteringMode)GetPropertyUInt16(image, PropertyTag.MeteringMode);
        }

        public static Compression ExifCompression(this Image image)
        {
            return (Compression)GetPropertyUInt16(image, PropertyTag.Compression);
        }

        public static ExposureMode ExifExposureMode(this Image image)
        {
            return (ExposureMode)GetPropertyUInt16(image, PropertyTag.ExposureMode);
        }

        public static ExposureProgram ExifExposureProgram(this Image image)
        {
            return (ExposureProgram)GetPropertyUInt16(image, PropertyTag.ExposureProgram);
        }

        public static PhotometricInterpretation ExifPhotometricInterpretation(this Image image)
        {
            return (PhotometricInterpretation)GetPropertyUInt16(image, PropertyTag.PhotometricInterpretation);
        }

        public static LightSource ExifLightSource(this Image image)
        {
            return (LightSource)GetPropertyUInt16(image, PropertyTag.LightSource);
        }

        public static GainControl ExifGainControl(this Image image)
        {
            return (GainControl)GetPropertyUInt16(image, PropertyTag.GainControl);
        }

        public static Contrast ExifLightContrast(this Image image)
        {
            return (Contrast)GetPropertyUInt16(image, PropertyTag.Contrast);
        }

        public static Saturation ExifSaturation(this Image image)
        {
            return (Saturation)GetPropertyUInt16(image, PropertyTag.Saturation);
        }

        public static Sharpness ExifSharpness(this Image image)
        {
            return (Sharpness)GetPropertyUInt16(image, PropertyTag.Sharpness);
        }

        public static SubjectDistanceRange ExifSubjectDistanceRange(this Image image)
        {
            return (SubjectDistanceRange)GetPropertyUInt16(image, PropertyTag.SubjectDistanceRange);
        }

        public static Rational ExifExposureTime(this Image image)
        {
            return GetPropertyRational(image, PropertyTag.ExposureTime);
        }

        public static double ExifFNumber(this Image image)
        {
            return GetPropertyRational(image, PropertyTag.FNumber).ToDouble();
        }

        public static double ExifAperture(this Image image)
        {
            return GetPropertyRational(image, PropertyTag.ApertureValue).ToDouble();
        }

        public static Rational ExifSubjectDistance(this Image image)
        {
            return GetPropertyRational(image, PropertyTag.SubjectDistance);
        }

        public static double ExifFocalLength(this Image image)
        {
            return GetPropertyRational(image, PropertyTag.FocalLength).ToDouble();
        }

        public static UInt16 ExifISOSpeedRating(this Image image)
        {
            return GetPropertyUInt16(image, PropertyTag.ISOSpeedRatings);
        }

        public static Rational ExifDigitalZoomRatio(this Image image)
        {
            return GetPropertyRational(image, PropertyTag.DigitalZoomRatio);
        }

        #endregion

        public static bool HasProperty(this Image image, PropertyTag property)
        {
            return Array.IndexOf(image.PropertyIdList, (Int32)property) > -1;
        }

        public static DateTime GetPropertyDateTime(this Image image, PropertyTag property)
        {
            if (!image.HasProperty(property))
                return DateTime.MinValue;

            string value = GetPropertyString(image, property);

            // If first char is blank or string is empty no DateTime is present
            if (value == string.Empty || value[0] == ' ')
                return DateTime.MinValue;

            // DateTime is written as YYYY:MM:DD HH:MM:SS
            // and needs to be converted to YYYY-MM-DD HH:MM:SS
            // to be convertable to a .Net DateTime
            string datePart = value.Substring(0, 10).Replace(':', '-');
            string timePart = value.Substring(11);
            return Convert.ToDateTime(datePart + ' ' + timePart);
        }

        public static string GetPropertyString(this Image image, PropertyTag property)
        {
            if (!image.HasProperty(property))
                return string.Empty;

            PropertyItem item = image.GetPropertyItem((Int32)property);
            if (item.Type != (short)PropertyType.AsciiString)
                return string.Empty;

            byte[] buffer = item.Value;
            return Encoding.ASCII.GetString(buffer).Trim('\0');
        }

        public static UInt32 GetPropertyUInt32(this Image image, PropertyTag property)
        {
            if (!image.HasProperty(property))
                return 0;

            PropertyItem item = image.GetPropertyItem((Int32)property);
            if (item.Type != (short)PropertyType.UnsignedLong)
                return 0;

            byte[] buffer = item.Value;
            return BitConverter.ToUInt32(buffer, 0);
        }

        public static UInt16 GetPropertyUInt16(this Image image, PropertyTag property)
        {
            if (!image.HasProperty(property))
                return 0;

            PropertyItem item = image.GetPropertyItem((Int32)property);
            if (item.Type != (short)PropertyType.UnsignedShort)
                return 0;

            byte[] buffer = item.Value;
            return BitConverter.ToUInt16(buffer, 0);
        }

        public static Rational GetPropertyRational(this Image image, PropertyTag property)
        {
            if (!image.HasProperty(property))
                return Rational.Zero;

            PropertyItem item = image.GetPropertyItem((Int32)property);
            if (item.Type != (short)PropertyType.UnsignedLongPair)
                return Rational.Zero;

            byte[] buffer = item.Value;
            Int32 numerator = BitConverter.ToInt32(buffer, 0);
            Int32 denominator = BitConverter.ToInt32(buffer, 4);

            Rational rational = new Rational(numerator, denominator);
            return rational;
        }
    }

    public struct Rational
    {
        private Int32 _numerator;
        public Int32 Numerator
        {
            get
            {
                return _numerator;
            }
            set
            {
                _numerator = value;
            }
        }

        private Int32 _denominator;
        public Int32 Denominator
        {
            get
            {
                return _denominator;
            }
            set
            {
                if (value == 0)
                    throw new ArgumentOutOfRangeException("Denominator", 0, "Denominator can't be zero.");

                _denominator = value;
            }
        }

        public static Rational Zero
        {
            get
            {
                return new Rational(0, 1);
            }
        }

        public Rational(Int32 numerator, Int32 denominator)
        {
            _numerator = 0;
            _denominator = 1;
            Numerator = numerator;
            Denominator = denominator;
        }

        public override string ToString()
        {
            return string.Format("{0}/{1}", Numerator, Denominator);
        }

        public double ToDouble()
        {
            return (double)Numerator / Denominator;
        }
    }
}

This introduces several new methods to the Image class starting with “Exif” such as ExifDateTime, ExifMake and ExifExposureTime. The Rational class was inserted into the same code block in this sample, in the main project it has it’s own file under CrazyBeavers.Math. Hope you enjoy this piece of code!

RFC2047 decoder in C#

June 17th, 2009 2 comments

Just spent the evening writing a class to decode RFC2047 strings in C#. It was requested by a user on ASPSidan, a Swedish site dedicated to ASP, ASP.Net and other Microsoft technologies but before I was done someone had already found another implementation on the net. Anyway, the code shouldn’t go to waste so I’m posting it here if anyone needs it.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Globalization;

namespace CrazyBeavers.Net.Mail
{
    public static class RFC2047Decoder
    {
        public static string Parse(string input)
        {
            StringBuilder sb = new StringBuilder();
            StringBuilder currentWord = new StringBuilder();
            bool readingWord = false;

            Int32 i = 0;
            while (i < input.Length)
            {
                char currentChar = input[i];
                char peekAhead;
                switch (currentChar)
                {
                    case '=':
                        peekAhead = (i == input.Length - 1) ? ' ' : input[i + 1];

                        if (peekAhead == '?')
                            readingWord = true;
                        break;

                    case '?':
                        peekAhead = (i == input.Length - 1) ? ' ' : input[i + 1];

                        if (peekAhead == '=')
                        {
                            readingWord = false;

                            currentWord.Append(currentChar);
                            currentWord.Append(peekAhead);

                            sb.Append(ParseEncodedWord(currentWord.ToString()));
                            currentWord = new StringBuilder();

                            i += 2;
                            continue;
                        }
                        break;
                }

                if (readingWord)
                {
                    currentWord.Append(currentChar);
                    i++;
                }
                else
                {
                    sb.Append(currentChar);
                    i++;
                }
            }

            return sb.ToString();
        }

        private static string ParseEncodedWord(string input)
        {
            StringBuilder sb = new StringBuilder();

            if (!input.StartsWith("=?"))
                return input;

            if (!input.EndsWith("?="))
                return input;

            // Get the name of the encoding but skip the leading =?
            string encodingName = input.Substring(2, input.IndexOf("?", 2) - 2);
            Encoding enc = Encoding.GetEncoding(encodingName);

            // Get the type of the encoding
            char type = input[encodingName.Length + 3];

            // Start after the name of the encoding and the other required parts
            Int32 i = encodingName.Length + 5;

            switch (char.ToLowerInvariant(type))
            {
                case 'q':
                    while (i < input.Length)
                    {
                        char currentChar = input[i];
                        char[] peekAhead = new char[2];
                        switch (currentChar)
                        {
                            case '=':
                                peekAhead = (i >= input.Length - 2) ? null : new char[] { input[i + 1], input[i + 2] };

                                if (peekAhead == null)
                                {
                                    sb.Append(currentChar);
                                    i++;
                                    break;
                                }

                                string decodedChar = enc.GetString(new byte[] { Convert.ToByte(new string(peekAhead, 0, 2), 16) });
                                sb.Append(decodedChar);
                                i += 3;
                                break;
                            case '?':
                                if (input[i + 1] == '=')
                                    i += 2;
                                break;
                            default:
                                sb.Append(currentChar);
                                i++;
                                break;
                        }
                    }
                    break;
                case 'b':
                    string baseString = input.Substring(i, input.Length - i - 2);
                    byte[] baseDecoded = Convert.FromBase64String(baseString);
                    sb.Append(enc.GetString(baseDecoded));
                    break;
            }
            return sb.ToString();
        }
    }
}
Categories: .Net, c#, Code samples Tags: , , , ,

Disabling “Missing XML comment for publicly visible type or member XX”

May 24th, 2009 No comments

Adding descriptive comments to your code is something that everyone will always tell you to do though in reality many people simply don’t do it. Some are just lazy, some doesn’t see the need of it, some are to stressed out to do it and some things that the c# compiler will warn you about just doesn’t need commenting. Looking at the last category, things that just doesn’t need commenting, we many times find enums. Look at this as a sample.

/// <summary>
///  Datatypes used by the EXIF specification
/// </summary>
public enum ExifDataTypes
{
	UnsignedByte = 1,
	AsciiString = 2,
	UnsignedShort = 3,
	UnsignedLong = 4,
	UnsignedRational = 5,
	SignedByte = 6,
	Undefined = 7,
	SignedShort = 8,
	SignedLong = 9,
	SignedRational = 10,
	SingleFloat = 11,
	DoubleFloat = 12
}

What useful comments could added to this? When compiling I’m getting twelve warnings about this code not being documented though I do have a hard time finding out what I should write about each option since I feel that the names are quite self explanatory. Just adding a dummy comment to each line would get rid of the warnings but also clutter up my code which isn’t really helping me. I could also tell the compiler to ignore all warnings of this type (“/nowarn 1591″ when compiling) though there might be other places in my code where I want to know that I missed a comment.

So no luck just yet. There is however a third method to use. Going back to C there has been a way to communicate with the compiler from your code using directives called “pragmas”. C# have it as well though the standard compiler (csc.exe) only understands two commands, warning and checksum. For my problem, warning was the one I could use.

The code below has a pragma added that tells the compiler not to raise warnings with id 1591 (missing comments) from line 6 to line 19.

/// <summary>
///  Datatypes used by the EXIF specification
/// </summary>
public enum ExifDataTypes
{
#pragma warning disable 1591
	UnsignedByte = 1,
	AsciiString = 2,
	UnsignedShort = 3,
	UnsignedLong = 4,
	UnsignedRational = 5,
	SignedByte = 6,
	Undefined = 7,
	SignedShort = 8,
	SignedLong = 9,
	SignedRational = 10,
	SingleFloat = 11,
	DoubleFloat = 12
#pragma warning restore 1591
}

It still gives me lots of warnings (still have some huge enums for the EXIF class that needs this) but now I have a chance of finding the real warnings and still document the parts of my code that really needs documenting.

Towerdefence in XNA

December 25th, 2008 No comments

Just thought I should write a short one about a current project of mine. I decided that I wanted to try to make a game (a real game, not just that old pong-clone I made while learning C++ all those years ago) and since I work mostly with managed code it felt quite natural to build my game using a managed framework. For those who don’t know about the XNA Framework I would recommend a visit to XNA Creators Club for some more information.

The game I’m working on (with a tiny bit of help of a friend of mine who still has about 70 lines of code that I haven’t touched in the project) is a realtime strategygame built around the towerdefence concept. Some sort of enemies tries to get from point A to point B without getting killed. To kill them you build different types of towers along the way that hurt them in different ways and hopefully enough to not let them through. For each creature that get to point B you lose a life and when enough get there it’s game over for you. If you want to know more about this type of game http://www.towerdefence.net/ is a great place to go looking.

Below is a short video I recorded from the latest build of the game. It currently only features one type of tower and one type of enemy and they both use some placeholder graphics I made in Paint.Net so don’t expect to much of that. If you happen to have some graphics skills and would like to help out then you are very welcome!

httpvh://www.youtube.com/watch?v=Q5wPSRH5xTk

And another thing, it’s also best viewed in fullscreen mode since the video was recorded in 800×600 resolution. Hope you enjoy it!

Categories: .Net, Beaver, c#, xna Tags: , , , ,

ASP.Net and metadata

December 19th, 2008 No comments

Building great websites have several parts to it. First of all you need a great idea of what to fill the site with, then you’ll need some great technology in the background (sloppy code can achieve lots of great things aswell, but you won’t want to go in there and add things afterwards) and then a great presentation to the users of your site. In this article I will touch two parts of this, the technology (ASP.Net) and the presentation (metadata).

First of all I just want to state that I’m not a believer in all the SEO crap thats out there. I simply believe that if you have a great site that people visit and link to it will rank higher in search engines. A great site though will incorporate lots of things that “SEO experts” are chattering about like validating markup, good titles and things like that but that doesn’t mean it is those parts that put it high in the searchresults.

When working on a new version the CMS I use for CrazyBeaver Softwares website I ran into a really disturbing problem with how ASP.Net handles metadata. Look at the sample below.

<head runat="server">
    <title>My website</title>
    <meta name="keywords" content="" />
</head>

It looks alright doesn’t it? Well if you browse to that page you’ll see that the generated source is different.

<head>
    <title>My website</title>
    <meta name="keywords" />
</head>

Where did my content go? Just because it is empty doesn’t mean it can be removed just like that. If you pay a quick visit to www.w3.org and look at the xhtml specification you’ll see that the content attribute is a REQUIRED attribute of the meta element and therefore shouldn’t be removed. Now I hear you say that it shouldn’t matter since it didn’t convey any information anyway and while that is true the removal of that attribute also made sure that the page won’t generate valid html. Sure, it’s a very small error and won’t stop anything that doesn’t require a strict DTD validation to display the page (and no browsers do that since they wouldn’t be able to browse large parts of the web) but start out small and soon you won’t even bother to close tags properly since the page doesn’t validate anyway.

So, this is a behavior of ASP.Net that we need to work around somehow. There are a few choices here. You could simply loop through the HtmlMeta controls during Page_Load and check if they have empty content. It would look something like this.

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        Control[] headerControls = new Control[Page.Header.Controls.Count];
        Page.Header.Controls.CopyTo(headerControls, 0);

        foreach (Control control in headerControls)
        {
            HtmlMeta meta = control as HtmlMeta;

            if (meta == null)
                continue;

            if (string.IsNullOrEmpty(meta.Content))
            {
                Page.Header.Controls.Remove(control);
                break;
            }
        }
    }
}

Does this look as a good enough solution for you? It will do the work needed for most parts (it won’t remove a completely empty meta for some reason though so a <meta name=”" content=”" />will still fail your validation..) but it isn’t the nicest solution since you’ll need to loop through all controls in your header for every pageview that isn’t a postback. Also, maybe you want to specify an empty content for some reason since you have some other piece of code or application that relies on them being there. So what would be a nicer solution? Well writing an adapter of course! In ASP.Net 2.0 they introduced a cool thing called control adapters which basicly let you override the rendering of an existing webcontrol. In this case we will use it to force the HtmlMeta control to always render a content attribute.

using System.Web.UI;
using System.Web.UI.Adapters;
using System.Web.UI.HtmlControls;

namespace CrazyBeavers.Web.UI.Adapters
{
    public class HtmlMetaAdapter : ControlAdapter
    {
        private static string[] _i8nAttributes = new string[] { "lang", "xml:lang", "dir" };

        protected override void Render(HtmlTextWriter writer)
        {
            HtmlMeta meta = this.Control as HtmlMeta;

            writer.WriteBeginTag("meta");

            if (!string.IsNullOrEmpty(meta.HttpEquiv))
                writer.WriteAttribute("http-equiv", meta.HttpEquiv);

            if (!string.IsNullOrEmpty(meta.Scheme))
                writer.WriteAttribute("scheme", meta.Scheme);

            if (!string.IsNullOrEmpty(meta.Name))
                writer.WriteAttribute("name", meta.Name);

            foreach (string attr in meta.Attributes.Keys)
                foreach (string i18n in _i8nAttributes)
                    if (attr.Equals(i18n, System.StringComparison.InvariantCultureIgnoreCase))
                        writer.WriteAttribute(i18n, meta.Attributes[i18n]);

            writer.WriteAttribute("content", meta.Content);

            writer.Write(HtmlTextWriter.SelfClosingTagEnd);
        }
    }
}

Just drop that piece of code in your App_Code or compile it as an assembly and put it in the Bin-folder on your site. There is still one step that needs to be completed for this to work though. All control adapters needs to be referenced via an browser-file in App_Browsers aswell (which is a really cool thing since you can set conditions and use adapters on certain controls for certain browsers, but more on that some other time). Below is a short sample of my Default.browser-file, it doesn’t use any extra features of the browser-file and just say that I want to use my adapter everywhere.

<browsers>
  <browser refID="Default">
    <controladapters>
      <adapter controlType="System.Web.UI.HtmlControls.HtmlMeta"
               adapterType="CrazyBeavers.Web.UI.Adapters.HtmlMetaAdapter" />
    </controladapters>
  </browser>
</browsers>

There you go, now all your MetaCotrol should always render properly for you even though you don’t put any data at all in them. Feel free to comment on this article if you have other solutions or ideas on how to work around this problem.

Recursive generic extensionmethods are fun! (Recursive FindControl)

November 12th, 2008 No comments

Just thought I should share a little piece of code with you this evening. It’s a little extension method (i.e it requires .Net 3.5) that extends Control and helps you find all instances of a certain controltype under the Control on which you invoke the method. Just put the following code in an assembly (or a .cs-file in App_Code).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;

/// <summary>
/// Holderclass for extension methods
/// </summary>
public static class ExtensionMethods
{
    /// <summary>
    /// Recursively searchs for all instances of a specified control type.
    /// </summary>
    /// <typeparam name="T">The type of control to search for.</typeparam>
    /// <param name="control">The control to search from.</param>
    /// <returns>A List<> with the found controls.</returns>
    public static List<T> FindControls<T>(this Control control) where T : Control
    {
        List<T> myList = new List<t>();
        FindControls</t><t>(control, ref myList);
        return myList;
    }

    private static void FindControls<T>(Control control, ref List<T> controlList) where T : Control
    {
        for (int i = 0; i < control.Controls.Count; i++)
        {
            Control currentControl = control.Controls[i];
            if (currentControl is T)
            {
                T typedControl = control.Controls[i] as T;
                controlList.Add(typedControl);
            }

            FindControls<T>(currentControl, ref controlList);
        }

    }
}

To call it you just invoke FindControls<Control>() on any object that derives from Control (i.e. Page, Label, Calendar, the list can go on forever), see the sample below. Hope you find some use for it!

List<HtmlMeta> meta = Page.FindControls<HtmlMeta>();

CrazyBeavers YouTube Downloader

April 19th, 2008 No comments

It’s been a while now since there was a new release from CrazyBeaver Software so I thought I should make some fuzz about it and talk about it here as well.

A few weeks ago a friend of mine asked if I knew any way to save a video from YouTube onto his harddrive so he could use it in his school presentation. A quick search on google turned up few services that could download the .flv for you which was pretty neat though not enough since it would require that the schools computers could play .flv, which they can’t. My idea then was to make a simple wrapper for FFmpeg (a great open-source tool for recording and converting audio and video streams) and let him use that for conversion. The goal at this time was to make it easy for him to do this without my help in the future and maybe to let my other classmates do it as well. It worked out pretty well in the end and I now have a really neat C# wrapper for FFmpeg.exe which I hope to release some day for everyone to use. It was however not really satisfied with this, my application only did half the job, and the easy part was what was missing. I went looking around the net a bit for solutions on how to download the .flv-files from YouTube and found several ways which had their advantages and drawbacks. In the end, I borrowed a few ideas from most of them and got my own working solution for getting the .flv-files.

CrazyBeavers YouTube Downloader 1.01

The result of this was the brand new CrazyBeavers YouTube Downloader which was released yesterday as version 1.0 and earlier today as 1.01 (just love the early bugs, so simple, so devastating). I’ve seen another program out there that does the same as mine but seeing the page I found it on i didn’t really want to download and run that .exe so hopefully this will be of use to more then just my class now.

With this release I also returned to NSIS (NullSoft Install System) for the installation which works a ton better then the Visual Studio setup projects I’ve used for BeaverSFV. I’ve scripted it to check for .Net 2.0 and if not it will download and install it. It also checks if the system is x86 or x64 to decide which package to get. Hopefully I’ll get around to discuss that one some day as well since it turned out really great. But until then, enjoy my new application!

Categories: .Net, Beaver, c# Tags: