pr0g33k

 collapse all
  1. Creating a RSS Feed Using a Custom ActionResult

    When it came time to create an RSS feed for this blog, I almost fell back on what I'd done in the past - create a generic handler to output the XML. Since I'm doing everything I can to use MVC these days, I wanted to find the "MVC way" to output an RSS feed. I figured there was a way to use a controller but when I used Bing to Google it (special thanks to Scott Hanselman for that joke), all I could find was people using a controller to pass data to a view and then run a loop in the markup to assemble the XML. That just seemed gross to me. I wanted more of a C# code approach - especially since the good folks at Microsoft went to the trouble of creating the System.ServiceModel.Syndication namespace. Finally I stumbled across a forum post that referenced a video of Scott Hanselman using System.Web.Mvc.FileResult as a base to create a custom ActionResult. Bingo! The RssController looks like this:

    public class RssController : Controller
    {
        public ActionResult Index()
        {
            return new RssResult();
        }
    }
        

    That's just sexy. The RssResult looks like this:

    public class RssResult : FileResult
    {
        public RssResult() : base("application/rss+xml") { }
    
        protected override void WriteFile(HttpResponseBase response)
        {
            List<SyndicationItem> syndicationItems = new List<SyndicationItem>();
            List<Blog> blogs = BlogManager.Instance.GetEntitiesForRss(HttpContext.Current.Request.Url.Host, 1, 25);
            String url;
    
            foreach (Blog blog in blogs)
            {
                url = String.Format("http://www.pr0g33k.com/blog/{0}/{1}", blog.Id, blog.Title.ToSlug());
                syndicationItems.Add(new SyndicationItem(blog.Title, String.Format("{0}<p><a href=\"{1}\">Read more...</a></p>", blog.Body, url), new Uri(url), url, blog.CreateDate.ToUniversalTime())
                {
                    PublishDate = blog.CreateDate.ToUniversalTime()
                });
            }
    
            SyndicationFeed syndicationFeed = new SyndicationFeed("pr0g33k's Blog", "pr0g33k (Robert Gaut) is a Dallas-area Microsoft .NET developer who specializes in C#, MVC 4, WPF, Silverlight, WCF, HTML5, jQuery/JavaScript, and code generation. pr0g33k's blog covers those technologies as well as other technologies that he finds cool and interesting.", new Uri("http://www.pr0g33k.com/rss"), syndicationItems);
            syndicationFeed.Authors.Add(new SyndicationPerson("pr0g33k@hotmail.com", "Robert Gaut", "http://www.pr0g33k.com"));
            SyndicationFeedFormatter feedFormatter = new Rss20FeedFormatter(syndicationFeed);
    
            using (XmlWriter xmlWriter = XmlWriter.Create(response.Output))
            {
                feedFormatter.WriteTo(xmlWriter);
            }
        }
    }
        

    The BlogManager.Instance.GetEntitiesForRss() function is part of my code-generated DAL. Everything else is pretty standard. You can see the output here. This is also very testable using automated testing if you're into that sort of thing - much moreso than generating the RSS/XML in a view.

    Posted on 5/14/2013 at 08:05 PM , Edited on 5/24/2013 at 06:05 PM
    Tags: C#MVC
  2. Convert a Unix Timestamp to System.DateTime

    I'm working on a scheduler using WebApi and FullCalendar. (I'll post it as soon as I finish.) When populating the events in an agenda view with JSON, FullCalendar will send a start date and an end date for the displayed calendar days. The dates are sent using a Unix timestamp, though. Not being a Unix guy, I had to look it up. It turns out that Unix Epoch is the 1st of January 1970 00:00:00 GMT. A Unix timestamp is the number of seconds since that date and time. So, to convert the date to a System.DateTime date, use the following:

    DateTime startDate = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(start);
    DateTime endDate = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(end);
        

    Simple!

  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. Managing Files Using Microsoft Azure Blob Storage (Part 1)

    Since I'm using Microsoft Azure to host this blog, it just makes sense that I use Azure Blob Storage to store my images for this blog. I had a tough time finding information about configuring and using Azure Blob Storage with an existing MVC Web application, though. So that's what this little walkthrough is about.

    Before we get to the code, you're going to want to download the Azure Storage Emulator. You can download the Azure Storage Emulator (v1.8) here

    To access the Azure Storage Emulator from your Web application, add the following to your Web.config connectionStrings section:

    <connectionStrings>
        <add name="StorageConnection" connectionString="UseDevelopmentStorage=true" />
    </connectionStrings>
        

    Once your project is deployed, you're going to need a different StorageConnection. I use a transformation in my Web.Release.config to handle this.

    <connectionStrings>
        <add name="StorageConnection" connectionString="DefaultEndpointsProtocol=http;AccountName=[YOUR_ACCOUNT_NAME];AccountKey=[YOUR_ACCOUNT_KEY]" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
    </connectionStrings>
        

    Next, install the Windows Azure Storage NuGet package to your MVC Web project.

    That's all you should need to set up your project.

    For my blog, I wanted to store images in one folder and other files in another folder. Azure Blob Storage doesn't really have a concept of "folders," though (not in the same way as the Windows file system, anyway). You can, however, set up "containers" so I'm going to create a container named "images" and a container named "files."

    Now add a controller named "FileManagerController" and a view for Index().

    public class FileManagerController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
        

    We'll fill in the Index.cshtml view in part 2 but for now just create an empty view.

    @{
        ViewBag.Title = "File Manager";
    }
        

    Let's add some code to upload files. To help pass values to my views, I created the following class:

    public class UploadModel
    {
        public String Container { get; set; }
        public String[] Accept { get; set; }
    }
        

    The Container property holds the Azure Blob Storage container name. The Accept string array property contains the file extensions that the user is allowed to upload. I know that I could have used the HTML input tag's "accept" property but I wanted to limit the image types that could be uploaded as well as limit the file types to a specific set. It makes more sense for my application to handle the validation on the server.

    Now let's create a controller action in the FileManagerController to begin the upload process. I use the "id" Route Parameter to pass the container name to the controller. For now, I just want to have 2 containers - images and files - so I enforce that by only accepting those two container names. I also populate the Accept array with the extensions I'll allow to be uploaded.

    public ActionResult Upload(String id)
    {
        switch (id.ToLower())
        {
            case "images":
                return View(new UploadModel() { Container = "images", Accept = new String[] { ".jpg", ".png", ".gif" } });
            case "files":
                return View(new UploadModel() { Container = "files", Accept = new String[] { ".zip", ".txt", ".docx", ".pdf" } });
        }
    
        return RedirectToAction("Index");
    }
        

    The Upload.cshtml looks like this:

    @model RobertGaut.Pr0g33k.Web.Areas.Admin.Models.UploadModel
    @{
        ViewBag.Title = "Upload";
    }
    @using (Html.BeginForm("UploadResults", "FileManager", FormMethod.Post, new { enctype = "multipart/form-data" }))
    { 
        <fieldset>
            <legend>Upload @String.Join(", ", Model.Accept)</legend>
            <ol>
                <li>
                    <input name="input1" type="file">
                </li>
                <li>
                    <input name="input2" type="file">
                </li>
                <li>
                    <input name="input3" type="file">
                </li>
                <li>
                    <input name="input4" type="file">
                </li>
                <li>
                    <input name="input5" type="file">
                </li>
            </ol>
            @Html.HiddenFor(m => m.Container)
            @for (Int32 i = 0; i < Model.Accept.Length; i++)
            {
                @Html.HiddenFor(m => m.Accept[i])
            }
            <input type="submit" value="Upload" />
        </fieldset>
    }
        

    There are a few things to take note of here. First, the form posts using enctype = "multipart/form-data" to ensure that the file data is property encoded in the post. Second, I want to pass the file extensions through to the UploadResults controller action and @Html.HiddenFor(m => m.Accept) will not work the way you might expect because Accept is a String array. To post the contents of the array, you need to loop through the array and output each item in its own hidden form field.

    On the receiving end of the form post we have the UploadResults controller function:

    [HttpPost]
    public ActionResult UploadResults(UploadModel uploadModel)
    {
        CloudBlobContainer cloudBlobContainer = GetCloudBlobContainer(uploadModel.Container);
        HttpPostedFileBase httpPostedFile;
        CloudBlockBlob cloudBlockBlob;
        List<UploadResult> uploadResults = new List<UploadResult>();
        String fileName;
    
        foreach (String file in Request.Files)
        {
            httpPostedFile = Request.Files[file] as HttpPostedFileBase;
            fileName = String.Concat(Path.GetFileNameWithoutExtension(httpPostedFile.FileName).ToSlug().ToLower(), Path.GetExtension(httpPostedFile.FileName));
    
            if (httpPostedFile.ContentLength > 0 && uploadModel.Accept.Contains(Path.GetExtension(fileName)))
            {
                cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(fileName);
                cloudBlockBlob.Properties.ContentType = httpPostedFile.ContentType;
                cloudBlockBlob.UploadFromStream(httpPostedFile.InputStream);
    
                uploadResults.Add(new UploadResult() { FileName = fileName, ContentLength = httpPostedFile.ContentLength, Url = cloudBlockBlob.Uri.ToString() });
            }
            else if (!String.IsNullOrEmpty(fileName))
                uploadResults.Add(new UploadResult() { FileName = fileName, ContentLength = httpPostedFile.ContentLength, Message = "The file is either empty or the file type (extension) is not allowed." });
        }
    
        ViewBag.ResourceType = uploadModel.Container;
    
        return View(uploadResults);
    }
        

    There are a few places where we'll need to get a CloudBlobContainer so I refactored that functionality out to its own private method:

    private CloudBlobContainer GetCloudBlobContainer(String container)
    {
        CloudStorageAccount cloudStorageAccount;
    
        if (Request.IsLocal)
            cloudStorageAccount = CloudStorageAccount.DevelopmentStorageAccount;
        else
            cloudStorageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["StorageConnection"].ConnectionString);
    
        CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
        CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference(container.ToLower());
    
        if (cloudBlobContainer.CreateIfNotExists())
        {
            BlobContainerPermissions permissions = cloudBlobContainer.GetPermissions();
            permissions.PublicAccess = BlobContainerPublicAccessType.Container;
            cloudBlobContainer.SetPermissions(permissions);
        }
    
        return cloudBlobContainer;
    }
        

    If the container does not yet exist, it is created when GetCloudBlobContainer is called. I also set the permissions to allow public access when the container is created.

    The Azure Blob Storage is kind of picky about the characters it allows for file names. I wanted to use URL-friendly names - without a lot of URL encoding - so I used my extension method, .ToSlug(), to accomplish that in the UploadResults controller action. I have another post here about that extension method.

    The UploadResults view displays a list of hyperlinks for each file that was successfully uploaded. It also displays a list for any files that were not uploaded because their file size was 0 bytes or because the file type was not allowed.

    @model List<RobertGaut.Pr0g33k.Web.Areas.Admin.Models.UploadResult>
    @using RobertGaut.Core.Extensions
    @{
        ViewBag.Title = "Upload";
    }
    <p>@Html.ActionLink(String.Format("Upload {0}", ((String)ViewBag.ResourceType).ToTitleCase()), "Upload", new { id = ((String)ViewBag.ResourceType).ToTitleCase() })</p>
    @if (Model.Count > 0)
    {
        <text>
        @if (Model.Where(m => String.IsNullOrEmpty(m.Message)).Count() > 0)
        {
            <p>The following files were uploaded:</p>
            <ul>
                @foreach (var uploadResult in Model.Where(m => String.IsNullOrEmpty(m.Message)))
                {
                    <li><a href="@uploadResult.Url" target="_blank">@uploadResult.FileName</a> [@uploadResult.ContentLength bytes]</li>
                }
            </ul>
        }
        <br />
        @if (Model.Where(m => !String.IsNullOrEmpty(m.Message)).Count() > 0)
        {
            <p>The following files were <strong>not</strong> uploaded:</p>
            <ul>
                @foreach (var uploadResult in Model.Where(m => !String.IsNullOrEmpty(m.Message)))
                {
                    <li>@uploadResult.FileName [@uploadResult.ContentLength bytes]</li>
                }
            </ul>
        }
        </text>
    }
        

    To keep my names consistent, I use the following extension method to convert the container name to title case if it was changed:

    public static String ToTitleCase(this String s)
    {
        if (!String.IsNullOrEmpty(s))
        {
            String[] words = s.Split(new Char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            StringBuilder stringBuilder = new StringBuilder();
            Char[] letters;
    
            foreach (String word in words)
            {
                letters = word.ToLower().ToCharArray();
                letters[0] = Char.ToUpper(letters[0]);
                stringBuilder.AppendFormat("{0} ", new String(letters));
            }
    
            return stringBuilder.ToString().Trim();
        }
    
        return s;
    }
        

    In part 2, we'll create a view to display the files we uploaded.