pr0g33k

 collapse all
  1. SimpleMembershipProvider Error When Restarting Application After Authentication

    If you're using the SimpleMembershipProvider in MVC4 and using the MVC template that includes the InitializeSimpleMembershipAttribute class in the Filters directory, you might have come across an issue when you restart your application after logging in. The error that's thrown states that, “You must call the 'WebSecurity.InitializeDatabaseConnection' method before you call any other method of the 'WebSecurity' class. It appears that the filter does not initialize the database connection early enough in the lifecycle to be picked up by the SimpleRoleProvider. For example, this will occur if you have methods decorated with the [Authorize] attribute and you're limiting access to a particular set of roles (e.g. [Authorize(Roles = "Administrator")]). Attempting to access the roles provider in this case causes the error to occur if the application is restarted (e.g. as a result of making a change to the Web.Config or a recompile).

    The solution is to move the initialization to Application_Start(). In keeping with the existing code in Application_Start(), I added a new class in the App_Start directory called "WebSecurityConfig"

    public class WebSecurityConfig
    {
        public static void Initialize()
        {
            Database.SetInitializer<UsersContext>(null);
    
            try
            {
                if (!WebSecurity.Initialized)
                    WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("The database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
            }
        }
    }
        

    Then, in Application_Start(), add: WebSecurityConfig.Initialize();

    Delete the InitializeSimpleMembershipAttribute from your project as well as any [InitializeSimpleMembership] attributes on your controller methods.

  2. 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
  3. 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
  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.