pr0g33k

 collapse all
  1. HTML Encode Clipboard Contents in Visual Studio 2013

    I use Visual Studio's HTML (Web Forms) Editor to author the articles for this blog because the Split screen feature, which allows me to easily see both the Design editor and Source editor at the same time, helps me ensure the markup is as minimal as possible. When Visual Studio 2013 was released, though, I noticed that one of the features I often used, Edit -> Paste Special, was missing. In previous versions, this command would paste the clipboard contents into the HTML Source Editor as HTML-encoded text. This was especially useful when I wanted to paste HTML between <pre></pre> tags so that the formatting was preserved and any angle brackets were not rendered as actual markup. Other things like quotation marks, apostrophies, and angle brackets in C# code (like the syntax for generics) needs to be HTML encoded so that they validate and appear as expected when output as HTML. So I created a windowless C# application that launches from Visual Studio's External Tools support and HTML-encodes anything placed in the clipboard. Here's how I did it:

    Create a new C# Console Application:

    When the new project opens, right-click on the Project in Solution Explorer and select Properties. Change the Output Type to "Windows Application":

    Next, add references to System.Web and System.Windows.Forms:

    Change the code in Program.cs to this:

    using System;
    using System.Web;
    using System.Windows.Forms;
    
    namespace HtmlEncodeClipboard
    {
        class Program
        {
            [STAThread]
            static void Main()
            {
                Clipboard.SetText(HttpUtility.HtmlEncode(Clipboard.GetText()));
            }
        }
    }

    Compile the project and make sure it builds. Then switch the Solution Configuration to Release and build it again so that we can get a release build.

    Now create a new Web application (ASP.NET, MVC, whatever - it doesn't matter). When the project opens, open Tools -> External Tools...

    Click the "Add" button then type a name in the Title field. For Command, browse to the bin/Release folder of your HtmlEncodeClipboard project and select the HtmlEncodeClipboard.exe executable.

    You can move the External Tool up or down the list to position it where you want in the Tools menu. Take note of where you position it, though. We'll use that information in a minute.

    To test the external tool, add a HTML page to your project (or a new View if you're in a MVC project - we just need the HTML editor). Select some markup in the editor, copy it (Ctrl + C or Edit -> Copy), go to the Tools menu and select your new command, then paste the clipboard contents (Ctrl + V or Edit -> Paste). Hopefully the result is HTML encoded text in the editor.

    Now let's add this to Visual Studio's Edit menu. Select Tools -> Customize... Switch to the Commands tab and select "Edit" from the Menu Bar dropdown list:

    Scroll down and select "Cycle Clipboard Ring" then click "Add Command..."

    In the Add Command dialog, scroll down and select "Tools" then scroll through the Commands until you find the command you added earlier. This is where it's important that you noted in which position you placed your command. I positioned mine as the third command in the Tools list so here I would choose "External Command 3."

    Next, click "Modify Selection" and rename your new menu item:

    Now you can select your command from the Edit menu, too:

    To add a keyboard shortcut, go to Tools -> Options and select "Keyboard" under the "Environment" node. In the "Show Commands Containing:" textbox, type the name of your command (in my case, the name is "ExternalCommand3" - remember the position you chose earlier). I'm only interested in using this command in the HTML Source Editor so I selected that in the "Use new shortcut in:" dropdown list. In "Press shortcut keys:" I used the "Ctrl + Shift + Alt + E" keystroke sequence because it wasn't previously assigned to anything. Make sure you click the "Assign" button after you find a keystroke sequence that works for you.

    I hope this is helpful for you. Using Visual Studio's External Tools feature makes it very easy to enhance your productivity.

  2. Clearing Containers with overflow: hidden

    I recently completed a side-project where I needed to create two columns of text floated left and right. The text of each column was laid out in an unordered list and was dynamically generated using data from a database. This meant that one column could, at any point, be taller than the other column. This was fine until I wanted to place a vertical line separating the two columns. I could have used a border on the left or right of either unordered list but if one grew taller, the border of the other wouldn't grow with it. Then it hit me, why not use a background image in the container? Here's the markup and corresponding CSS:

    <div class="columns">
        <div class="column1">
            <ul>
                <li>Column 1, Item 1</li>
                <li>Column 1, Item 2</li>
                <li>Column 1, Item 3</li>
                <li>Column 1, Item 4</li>
            </ul>
        </div>
        <div class="column2">
            <ul>
                <li>Column 2, Item 1</li>
                <li>Column 2, Item 2</li>
                <li>Column 2, Item 3</li>
                <li>Column 2, Item 4</li>
                <li>Column 2, Item 5</li>
                <li>Column 2, Item 6</li>
                <li>Column 2, Item 7</li>
                <li>Column 2, Item 8</li>
            </ul>
        </div>
    </div>
        
    <style>
        .columns {
            background: url(/images/vertical-line.png) repeat-y 50% 0;
            width: 402px;
        }
    
        .column1 {
            float: left;
            width: 200px;
        }
    
        .column2 {
            float: right;
            width: 200px;
        }
    </style>
        

    Unfortunately, this is what I got:

    • Column 1, Item 1
    • Column 1, Item 2
    • Column 1, Item 3
    • Column 1, Item 4
    • Column 2, Item 1
    • Column 2, Item 2
    • Column 2, Item 3
    • Column 2, Item 4
    • Column 2, Item 5
    • Column 2, Item 6
    • Column 2, Item 7
    • Column 2, Item 8

    Where's my background image? It turns out that a container of floated elements does not automatically clear the floats of its child elements. The container instead assumes its default height (0px in the case of  a div) or whatever height it is assigned. I don't want to assign a height in this case, though; I want the container to size to the height of whichever child is tallest.

    The solution is to set the overflow to hidden on the container. If I change the CSS to this:

        <style>
            .columns {
                background: url(/images/vertical-line.png) repeat-y 50% 0;
                overflow: hidden;
                width: 402px;
            }
    
            .column1 {
                float: left;
                width: 200px;
            }
    
            .column2 {
                float: right;
                width: 200px;
            }
        </style>
        

    Then this is what I get:

    • Column 1, Item 1
    • Column 1, Item 2
    • Column 1, Item 3
    • Column 1, Item 4
    • Column 2, Item 1
    • Column 2, Item 2
    • Column 2, Item 3
    • Column 2, Item 4
    • Column 2, Item 5
    • Column 2, Item 6
    • Column 2, Item 7
    • Column 2, Item 8

    It also means that I don't have to follow the container with an element assigned "clear: both" to clear the preceding floats.

    Posted on 7/23/2013 at 02:07 PM , Edited on 7/23/2013 at 02:07 PM
    Tags: HTML5CSS
  3. Managing Files Using Microsoft Azure Blob Storage (Part 3)

    In part 1, we set up an MVC project to connect to Microsoft's Azure Blob Storage and also created a view to upload some files. In part 2, we created a view to list the files we uploaded. Now let's add a view to display an individual file and delete it.

    First, we need to create an object to pass aroung our data. I created the following class to handle that:

    public class FileEditModel
    {
        public String Container { get; set; }
        public String Url { get; set; }
        public String FileName { get; set; }
    }
        

    Next, add an action to the FileManagerController named Edit and create a strongly-typed (FileEditModel) view for it:

    public ActionResult Edit(String id, String container)
    {
        String url = UTF8Encoding.ASCII.GetString(HttpServerUtility.UrlTokenDecode(id));
        FileEditModel fileEditModel = new FileEditModel()
        {
            Container = container,
            Url = url,
            FileName = Path.GetFileName(url)
        };
    
        return View(fileEditModel);
    }
        

    In the List.cshtml view (part 2), there's an ActionLink that links to the Edit ActionResult/view. The "id" RouteParameter is a URL to the file which is encoded using HttpServerUtility.UrlTokenEncode(). I discuss this in more detail here. In our Edit ActionResult, we decode that "id" RouteParameter using HttpServerUtility.UrlTokenDecode(). The container is passed to the Edit ActionResult as a QueryString parameter. I debated whether I should attempt to parse the container from the URL but decided to explicitly pass it in the QueryString. I realize that it's possible for someone to tamper with the QueryString but this is, after all, intended for the "admin" section of the site and presumably limited to someone in an administrator role who knows what they're doing. If you're not able to trust the person accessing this functionality, you might consider parsing the container from the URL or finding another way to pass the container so that it's more foolproof.

    The Edit.cshtml view looks like this:

    @model RobertGaut.Pr0g33k.Web.Areas.Admin.Models.FileEditModel
    @{
        ViewBag.Title = "Edit";
    }
    <p>@Model.Url</p>
    <p><a href="@Model.Url" target="_blank">Download File/Open in a new window</a></p>
    <div id="preview"></div>
    @using (Html.BeginForm())
    {
        @Html.HiddenFor(m => m.Container)
        @Html.HiddenFor(m => m.FileName)
        @Html.HiddenFor(m => m.Url)
        <p><input name="actionType" type="submit" value="Delete" onclick="return confirm('Are you sure you want to delete this file?')" /></p>
    }
    @section scripts{
        <script>
            $(document).ready(function () {
                var href = "@Model.Url";
                var ext = href.substr(href.lastIndexOf('.') + 1);
                switch (ext) {
                    case "jpg":
                    case "png":
                    case "gif":
                        $("#preview").html("<img id='preview-image' src='" + href + "' /><div></div>");
                        $("#preview-image").load(function () {
                            $("#preview").show();
                            var h = $(this).height();
                            var w = $(this).width();
                            var h2 = h > 240 ? 240 : h;
                            var w2 = Math.floor((h2 / h) * w);
                            $(this).css({ "height": h2 + "px", "width": w2 + "px" });
                            $("#preview").css({ "width": w2 + "px" });
                            $("#preview > div").first().text(w + " X " + h);
                        });
                        break;
                }
            });
        </script>
    }
        

    The jQuery script is similar to the script used on the List.cshtml view - it just displays an icon for the file type and creates a hover effect to display an image preview for image file types.

    I didn't add any functionality to rename or otherwise edit the file, just the ability to delete it. I'm not really interested in renaming and, really, it requires deleting and uploading a new file anyway. There isn't a way in the Azure Blob Storage API to rename blobs, per se. There is a way to copy a blob with a new name but the result is the same - create a new blob with the desired name and delete the old one.

    Posting takes you here:

    [HttpPost]
    public ActionResult Edit(String actionType, FileEditModel fileEditModel)
    {
        CloudBlobContainer cloudBlobContainer = GetCloudBlobContainer(fileEditModel.Container);
        CloudBlockBlob cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(fileEditModel.FileName);
    
        switch (actionType.ToLower())
        {
            case "delete":
                cloudBlockBlob.DeleteIfExists();
                break;
        }
    
        return RedirectToAction("List", new { id = fileEditModel.Container });
    }
        

    I used a switch here to allow future growth for other features (I know it looks silly to have a switch with just one option). GetCloudBlobContainer is defined in part 1. The code should be pretty self-explanatory: get a reference to the container, use the container to get a reference to the blob, delete the blob is if exists, go back to the list.

    I've been using this for a few days now and the workflow for managing files suits my needs pretty well (for now). I generally like to keep things very simple and straightforward. If you have any suggestions on other features or how to make this better, though, I'd love to hear them.

  4. Managing Files Using Microsoft Azure Blob Storage (Part 2)

    In part 1, we set up an MVC project to connect to Microsoft's Azure Blob Storage and also created a view to upload some files. Now let's add a view to list the files we've uploaded.

    To start, add an ActionResult function to the FileManagerController named "List" and create a view for it (List.cshtml).

    Now, add some ActionLink's to the Index.cshtml view to navigate to our List view.

    @{
        ViewBag.Title = "File Manager";
    }
    <p>@Html.ActionLink("View Images", "List", new { id = "Images" })</p>
    <p>@Html.ActionLink("View Files", "List", new { id = "Files" })</p>
        

    The ActionLinks navigate to our List view - one for images and one for files. The URL's are: "/FileManager/List/Images" and "/FileManager/List/Files"

    To pass the name of the container and the list of URL's from Azure Blob Storage, I created the following class and placed it in my Models folder:

    public class ResourcesModel
    {
        public String Container { get; set; }
        public List<String> Urls { get; set; }
    }
        

    Change the List controller action to look like this:

    public ActionResult List(String id)
    {
        ResourcesModel resourcesModel = new ResourcesModel();
    
        if (!String.IsNullOrEmpty(id))
        {
            CloudBlobContainer cloudBlobContainer = GetCloudBlobContainer(id);
            BlobResultSegment blobResultSegment = cloudBlobContainer.ListBlobsSegmented(null);
    
            resourcesModel.Container = id;
            resourcesModel.Urls = blobResultSegment.Results.Select(r => r.Uri.ToString()).ToList();
    
            while (blobResultSegment.ContinuationToken != null)
            {
                blobResultSegment = cloudBlobContainer.ListBlobsSegmented(blobResultSegment.ContinuationToken);
                resourcesModel.Urls.AddRange(blobResultSegment.Results.Select(r => r.Uri.ToString()));
            }
        }
    
        return View(resourcesModel);
    }
    

    The GetCloudBlobContainer function is listed in part 1.

    I use CloudBlobContainer.ListBlobsSegmented() here because the number of blobs you can return from a single call is limited to 5000. If there are more than 5000 blobs, a continuation token is sent back with the request. The continuation token is basically a marker that can be used by subsequent calls to pick up where the last call left off. First, I call ListBlobsSegmented and pass null for the continuation token to get as many items up to the limit. Then, I loop for as long as a continuation token is returned and get those results, too. I'm only interested in the URI of each blob so I use the Linq Select() extension method to query out the URI's and add them to my Urls (List<String>) collection.

    Here's the List.cshtml view:

    @model RobertGaut.Pr0g33k.Web.Areas.Admin.Models.ResourcesModel
    @using RobertGaut.Core.Extensions
    @{
        ViewBag.Title = "Resources";
    }
    <p>@Html.ActionLink(String.Format("Upload to {0}", Model.Container.ToTitleCase()), "Upload", new { id = Model.Container.ToTitleCase() })</p>
    <ul id="resources">
        @foreach (String url in Model.Urls)
        {
            <li>@Html.ActionLink(Path.GetFileName(url), "Edit", new { id = HttpServerUtility.UrlTokenEncode(System.Text.UTF8Encoding.ASCII.GetBytes(url)), container = Model.Container }, new { data_href = url })</li>
        }
    </ul>
    <div id="preview"></div>
    @section scripts{
        <script>
            $(document).ready(function () {
                $("#resources > li").each(function () {
                    var lip = $(this).position();
                    var liw = $(this).width();
                    var a = $(this).children('a').first();
                    var href = a.attr("data-href");
                    var ext = href.substr(href.lastIndexOf('.') + 1);
                    $(this).css({ "background": "url(/images/" + ext + ".png) no-repeat 50% 0" });
                    switch (ext) {
                        case "jpg":
                        case "png":
                        case "gif":
                            a.hover(function () {
                                $("#preview").html("<img id='preview-image' src='" + href + "' /><div></div>");
                                $("#preview-image").load(function () {
                                    $("#preview").show();
                                    var h = $(this).height();
                                    var w = $(this).width();
                                    var h2 = h > 240 ? 240 : h;
                                    var w2 = Math.floor((h2 / h) * w);
                                    $(this).css({ "height": h2 + "px", "width": w2 + "px" });
                                    $("#preview").css({ "position": "absolute", "top": lip.top - h2 + 105 + "px", "left": Math.floor((lip.left + (liw / 2)) - (w2 / 2)) + "px" });
                                    $("#preview > div").first().text(w + " X " + h);
                                });
                            }, function () {
                                $("#preview").html('');
                                $("#preview").hide();
                            });
                            break;
                    }
                });
            });
        </script>
    }
        

    There's a lot going on there, jQuery-wise, but basically I check the extension of each file and set a background image to the <li> that represents that file type. Then, if the file is an image, I create a hover effect to display a smaller preview. This keeps the browser from downloading every image when this page loads. Images are only downloaded when the user hovers over its anchor tag.

    I have a small amount of CSS that accompanies this view:

    ul#resources {
        margin: 0;
        padding: 0;
    }
    
        ul#resources li {
            display: inline-block;
            height: 142px;
            margin: 5px;
            position: relative;
            min-width: 310px;
        }
    
            ul#resources li a {
                border: 0;
                top: 128px;
                display: block;
                position: relative;
                text-align: center;
                width: 100%;
            }
    
    div#preview {
        background: #000;
        border: 2px solid #000;
        display: none;
        text-align: center;
    }
    
        div#preview > div {
            color: #fff;
            height: 20px;
        }
        

    In part 3 we'll add a view to edit the file.

  5. Farewell, hgroup. We hardly knew thee...

    Alas, the <hgroup> tag is officially gone. Removed from the HTML5 specification. Extinct. Perished. It is now an ex-tag. Last week, this blog validated and the world was happy (well, my little world was happy - I can't really speak for the rest of the world). Then, this week, errors galore. It wasn't a huge change, fortunately (just 3 files and some CSS changes). I wonder what tag is next on the chopping block...