pr0g33k

 collapse all
  1. 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!

  2. Could not load file or assembly WebMatrix.WebData or one of its dependencies

    One thing that you might run into when setting up SimpleMembership in a Blank MVC 4 project is this error:

    Could not load file or assembly 'WebMatrix.WebData' or one of its dependencies

    I've encountered this several times and now I'm putting here so I won't forget.

    If you capture the exception, it will probably complain that it's trying to load an assembly specified in the Web.config. If you're trying to set up SimpleMembership, your Web.Config probably resembles this:

    <membership>
      <providers>
        <add name="DefaultMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
      </providers>
    </membership>
    <roleManager enabled="true">
      <providers>
        <add name="DefaultRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData" />
      </providers>
    </roleManager>
        

    If you haven't already, add a reference to WebMatrix.WebData. But there's one more step you'll need to take. Right-click on the reference to WebMatrix.WebData and select "Properties." In the properties explorer, find "Copy Local" and change the value to "True."

    That should clear it up.

    Posted on 4/29/2013 at 09:04 PM , Edited on 4/30/2013 at 04:04 PM
    Tags: MVC
  3. Using PATINDEX to parse HTML with a Regular Expression

    When I created the RSS feed for this blog, I didn't want to return each entire blog post but, rather, just the first paragraph. I tend to get a little wordy (it's a personal flaw; I'm working on it) and I wanted to display as many entries in the RSS as I could without it taking too long to download to the client. I first thought I would query the blog posts from the database and use a regular expression to extract the first paragraph since SQL doesn't have native support for regular expressions. (There's the SQL Server CLR route, sure, but since I'm using SQL Azure, I don't have that option.) Then I remembered PATINDEX. According to the online documentation, PATINDEX returns "the starting position of the first occurrence of a pattern in a specified expression, or zeros if the pattern is not found, on all valid text and character data types." Perfect! The pattern language is pretty limited but so is the HTML I'm wanting to match. Here's a little test script I threw together to test it out:

    Declare @Html table
    (
    	Markup varchar(1000)
    )
    
    Insert @Html (Markup) Select '<p>This is what I want.</p><p>I want to ignore this.</p>'
    Insert @Html (Markup) Select '<div>I do not want this.</div><pre>Some preformatted text I do not want.</pre><p class="my-css">This is what I want.</p>'
    Insert @Html (Markup) Select '<div>I do not want this.</div><span>I do not want this, either</span>'
    Insert @Html (Markup) Select '<param>A param I do not want.</param><progress>I do not want progress (well, not this progress)</progress><p>This is what I want!</p>'
    
    Declare @Begin varchar(50) = '%<p[^re,aram,rogress]%'
    Declare @End varchar(50) = '%</p>%'
    
    Select PATINDEX(@Begin, Markup) As FirstMarker
    	, Case When PATINDEX(@End, Markup) > 0 Then PATINDEX(@End, Markup) + 3 Else 0 End As SecondMarker
    	, SUBSTRING(Markup, PATINDEX(@Begin, Markup), Case When PATINDEX(@End, Markup) > 0 Then PATINDEX(@End, Markup) + 3 Else 0 End) As Body
    From @Html
        

    The @Begin variable holds a pattern that matches all HTML tags starting with "p" but ignores <pre>, <param>, and <progress> which just leaves <p>. I left the begin tag "open" (notice there's no closing ">") so that I could capture any attributes on the paragraph tag. I'm only interested in a part of the string that begins with "<p" and ends with "</p>". Using SUBSTRING, I can extract the first paragraph of the HTML. Now I just have to remember to put a summary in the first paragraph of each blog post...

  4. 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.

  5. 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.