Showing posts with label mvc3. Show all posts
Showing posts with label mvc3. Show all posts

Friday, July 15, 2011

ASP.NET MVC3 App Using plupload to Upload Directly to Amazon S3

UPDATE 8/15/2011:
I can't recommend using this for large files (video files). It's very unreliable. Web size image files and normal email attachment sized files would probably do fine. What I ended up doing to get around it was creating a web app on AppHarbor (which runs on EC2) that I upload the files to and it saves them to S3. Since the AppHarbor app is on a different domain you are still limited to Flash and Silverlight support but you do gain file chunking which brings back some reliability. Alternatively to AppHarbor you could setup your own server on Amazon as well.

I created a working example of using plupload to upload files directly to Amazon S3 and threw it on github, since the .NET examples I found on the web we're incomplete or broken.
https://github.com/crdeutsch/MVC3PluploadToAmazonS3

The files that are being uploaded do not even touch your web server. They are posted directly to Amazon. At the moment plupload only supports the Flash and Silverlight plugins as far as I can tell.

The AmazonS3Helper.cs library originated from Cary Abramoff off of this plupload forum thread.

I've made some changed based on iwasrobbed's excellent Ruby on Rails example.

Usage

Upload the crossdomain.xml found in the root of the site to your Amazon S3 Bucket.

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" secure="false" />
</cross-domain-policy>

Modify the settings in HomeController.cs to use your Amazan credentials and set your S3 bucket.

public ActionResult Index()
{
 string acl = "private";
 string bucket = "YOUR S3 BUCKET NAME";
 string accessKeyId = "YOUR AMAZON ACCESS KEY ID";
 string secret = "YOUR AMAZON SECRET ACCESS KEY";
 string policy = AmazonS3Helper.ConstructPolicy(bucket, DateTime.UtcNow.Add(new TimeSpan(0, 10, 0, 0)), acl, accessKeyId);
 string signature = AmazonS3Helper.CreateSignature(policy, secret);

 var model = new PluploadAmazonS3Model()
 {
  AWSAccessKeyId = accessKeyId,
  Policy = policy,
  Signature = signature,
  Bucket = bucket,
  Acl = acl
 };

 return View(model);
}

On the View side you could just use Razor to dump the Model variables directly into your javascript, but I decided to use hidden form variables instead in case you want to move the javascript to its own file where embedding Razor syntax won't work.

Hope this saves somebody else some time!

Tuesday, April 19, 2011

Using RazorJS with MVC3

I've been working on a project where I've managed to accumulate 1200+ lines of javascript in my View file. There are two reasons for this:
  1. Since the application is still in development I don't have to worry about the browser caching JS files if the code is in the View.
  2. I often have Url.Action and other snippets of Razor mixed in with my javascript. 
I was just getting to the point where I was ready to move this code to its own .js file but I was worried about how to deal with the Razor that was mixed in. Thanks to John Katsiotis's (aka djsolid) RazorJS project I now have a solution!

Installation is easy, just use Nuget to add RazorJS to your solution in Visual Studio. The command line to do is:

Install-Package RazorJS

By default @Url.Action will not work and you'll get the error:
error CS0103: The name 'Url' does not exist in the current context

To get it to work add the following at the top of your javascript file.
@{
    var Url = new System.Web.Mvc.UrlHelper(System.Web.HttpContext.Current.Request.RequestContext);
}

If you run into other errors you should be able to use similar workarounds. If you do use other workarounds, let me know and I can add them to this post.

UPDATE: In version 0.4.2 you no longer need this work around for the UrlHelper. Thanks to John for adding this so fast!

Wednesday, February 16, 2011

Plupload and ASP.NET MVC3

This post will explain howto integrate Plupload into an ASP.NET MVC3 project. It will probably work with lesser version of MVC without too many changes.

Integrating it into my project went smoother then any upload plugin I've ever used before. My previous favorite was NeatUpload but I've been having issues with it with large files, and since Plupload supports chunked uploads I figured I'd give it a try.

Here's what I did to get the Custom Upload example working in MVC.

First add the following to one of your Controllers or create a new Controller to hold this Action. (most of the credit goes to these two Stackoverflow questions: Question 1, Question 2)

/// <summary>
/// Handles chuncked file uploads like the ones from plupload.
/// </summary>
/// <param name="chunk"></param>
/// <param name="name"></param>
/// <returns></returns>
[HttpPost]
public ActionResult Upload(int? chunk, string name)
{
    var fileUpload = Request.Files[0];
    var uploadPath = Server.MapPath("~/App_Data/Uploads");
    chunk = chunk ?? 0;

    //UPDATE 2/17/2011: Removed this since it doesn't work. I recommend setting the unique_names param client side if you want unique names.
    ////find a free filename if this is the first chunk
    //if (!chunk.HasValue || chunk < 1)
    //{
    //    int xx = 1;
    //    while (System.IO.File.Exists(uploadedFilePath))
    //    {
    //        uploadedFilePath = Path.Combine(uploadPath, Path.GetFileNameWithoutExtension(name) + "_" + xx + Path.GetExtension(name));
    //        xx++;
    //    }
    //}
    
    //TODO: cleanup old files

    //write chunk to disk.
    string uploadedFilePath = Path.Combine(uploadPath, name);
    using (var fs = new FileStream(uploadedFilePath, chunk == 0 ? FileMode.Create : FileMode.Append))
    {
        var buffer = new byte[fileUpload.InputStream.Length];
        fileUpload.InputStream.Read(buffer, 0, buffer.Length);
        fs.Write(buffer, 0, buffer.Length);
    }

    return Content("Success", "text/plain");
}

NOTE: If you want to secure this Action and you're using Forms Authentication add the [Authorize] attribute above [HttpPost]

NOTE 2: I'm uploading files to ~/App_Data/Uploads. Create this folder or change the code above.

Now open or create a View file that you want to add the upload to and add the following HTML:

<div id="container">
    <div id="filelist">No runtime found.</div>
    <br />
    <a id="pickfiles" href="#">[Select files]</a>
    <a id="uploadfiles" href="#">[Upload files]</a>
</div>

Add the following Javascript to your View assuming you've placed everything from the js folder in the Plupload zip into the ~/Scripts/plupload/ folder of your project:

<!--Load 3rd party plupload scripts-->
<script src="@Url.Content("~/Scripts/plupload/gears_init.js")" type="text/javascript"></script>
<script type="text/javascript" src="http://bp.yahooapis.com/2.4.21/browserplus-min.js"></script>
<!--Load plupload and all its runtime scripts-->
<script src="@Url.Content("~/Scripts/plupload/plupload.full.min.js")" type="text/javascript"></script>

<script type="text/javascript">

    $(document).ready(function () {
        var uploader = new plupload.Uploader({
            runtimes: 'gears,html5,flash,silverlight,browserplus',
            browse_button: 'pickfiles',
            container: 'container',
            max_file_size: '2048mb',
            url: '@Url.Action("upload", "home")',
            flash_swf_url: '@Html.ScriptPath("plupload/plupload.flash.swf")',
            silverlight_xap_url: '@Html.ScriptPath("plupload/plupload.silverlight.xap")',
            filters : [
                {title : "Image files", extensions : "jpg,gif,png"},
                {title : "Zip files", extensions : "zip"}
            ]
        });

        uploader.bind('Init', function (up, params) {
            $('#filelist').html("<div>Current runtime: " + params.runtime + "</div>");
        });

        $('#uploadfiles').click(function (e) {
            uploader.start();
            e.preventDefault();
        });

        uploader.init();

        uploader.bind('FilesAdded', function (up, files) {
            $.each(files, function (i, file) {
                $('#filelist').append('<div id="' + file.id + '">' + file.name + ' (' + plupload.formatSize(file.size) + ') <b></b>' + '</div>');
            });

            up.refresh(); // Reposition Flash/Silverlight
        });

        uploader.bind('UploadProgress', function (up, file) {
            $('#' + file.id + " b").html(file.percent + "%");
        });

        uploader.bind('Error', function (up, err) {
            $('#filelist').append("<div>Error: " + err.code + ", Message: " + err.message + (err.file ? ", File: " + err.file.name : "") + "</div>");

            up.refresh(); // Reposition Flash/Silverlight
        });

        uploader.bind('FileUploaded', function (up, file) {
            $('#' + file.id + " b").html("100%");
        });
    });

</script>

NOTE: I'm using Razor as my View Engine. If you are not do a search for @* in the HTML above and replace with <%= %> type syntax.

NOTE 2: Update the url param in the javascript to point to where your Upload Action is. Mine is in the Home Controller

You will also need to have jQuery included. I left that out since most people already do, but if you don't you can use this:

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
    google.load("jquery", "1.3");
</script>

The only other thing you may want to immediately do is adjust the filters option in the javascript to allow/disallow file types as needed.

That's it! Hope this was as easy for you as it was for me!

Sunday, January 16, 2011

MVC3 Boilerplate

Every time I create a new ASP.NET MVC3 project there are certain libraries and code I re-use regularly. One of them being HTML5 Boilerplate which I love. I did some googling for "MVC3 Boilerplate" and didn't find anything, so I decided to start my own and placed it here on GitHub.


With every release MVC3 included more awesome out of the box (finally has jQuery AND jQuery UI!), but I'd like to see the option to go further. One of the things I love about Ruby on Rails is it includes more of what you need to get you straight to coding like a de facto ORM (ActiveRecord). I also don't like how complicated AspNetSqlMembershipProvider is and the fact it doesn't store data in cleanly named "Users" table so I decided to add my own simple User class that can be modified and extended.


I'd love to see someone with more experience then me clean up, take over, re-do or otherwise improve on this idea. So feel free to fork my project or make suggestions. I'm not always the best and keeping projects up to date but I'll see what I can do.


Some of the features I've included are:
  • HTML5 Boilerplate
  • Elmah (error logging)
  • JSON Parser (comes in handy when making JSON based ajax calls)
  • Modernizr (part of HTML5 Boilerplate, but awesome enough to warrant its own mention)
  • AntiXSS Library (Most of the places this is used was based on the Tekpub MVC2 Starter Site, I'm probably doing it wrong and/or not using it enough)
  • Ninject (dependency injection)
  • SquishIt (used to compress and minimize javascript and CSS)
  • Sql Server CE (included so you don't need full MS SQL or SQL Express)
  • EF Code First (used as the ORM)
  • Bits from Tekpub MVC 2 Starter Site
  • Basic User Signup using simple POCO User object

I really don't know how much I'll keep this project up to date but even if it helps one person that's enough for me. ;)

    Tuesday, November 9, 2010

    WatchedIt - An MVC3, Razor, EF4 Code First Production

    One of the features I love about applications like Boxee is it treats the content you're watching like email. Instead of "read" and "unread" it's "watched" and "unwatched". This is great if you don't have a significant other who tends to watch all of your shows before you. ;)

    So I decided to make an application I call WatchedIt to track where I left off watching shows. What a perfect opportunity to try out some new technologies! For this project I used the following fairly new technologies:

    So what did I think of the experience? MVC3 is a promising improvement over MVC2 and Web Forms. Outside of a radical paradigm change (like being more dynamic like Ruby) I'd pick MVC3 for my next project without hesitating. It integrated nicely with jQuery by offering ways to produce and consume JSON.

    HTML5 gets a big thumbs up! I've already begun using as many features as I can that are backward compatible with older browsers. If you want to know what you can and can't use now, I recommend HTML5 and CSS3 by Brian Hogan.

    I really liked Razor! But the lack of intellisense (auto completion, etc) in Visual Studio was a bit painful. Hopefully this will get added soon and it will be my preferred View Engine hands down. It was a little hard to figure out when you don't have to use the @ symbol to prefix code but the error messages were very helpful.
    UPDATE 11/9/2010: And 3 hours after posting this, Intellisense has been added in MVC 3 RC!

    EF4 Code First didn't go nearly as smooth as the other new features even though I love the concept. The parts that worked are great but it still has a few too many limitations for me to pick it again without hesitation over other options. Some of the limitations:
    • I couldn't find a simple, elegant way to do a cascading delete of all child objects when you delete a parent.
    • You can't have more then one DbContext share the same database (not a major issue)
    • Deploying to production didn't go very smoothly. I had to configure the connection string to use the "sa" account to get the DB created.
    Keep in mind these .NET offerings are all fairly new and I think Microsoft is on the right track with all of them.

    I've decided to give back to the community and put the source code on GitHub. I'm sure there are some things I could have done better or that didn't follow best practice so if you find something that would have been much easier to do another way let me know. I'd love to hear it.

    Some 3rd party libraries that made this application possible:
    Elmah - my favorite error logging library
    tvdblib - they did an awesome job of wrapping the TVDB API!
    TheTVDB - without this DB this application wouldn't be possible. Please consider contributing info and artwork.

    Thursday, October 7, 2010

    Upgrade ASP.NET MVC 3 Preview to MVC Beta

    I previously blogged about upgrading the TekPub ASP.NET 2 Starter Site from MVC2 to MVC3 Preview:
    http://blog.cdeutsch.com/2010/08/upgrade-mvc2-to-mvc3.html

    Today I upgrade that project to MVC3 Beta.

    It was pretty easy. Biggest change was with Dependency Injection. IMvcServiceLocator no longer exits and has been replaced with IDependencyResolver.

    My NinjectServiceLocator file has been replaced with NinjectResolver (thanks Jedidja from StackOverflow!) and looks like this:

    namespace Mvc3Ninject.Utility
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Web.Mvc;
        using Ninject;
    
        [System.Diagnostics.DebuggerStepThrough]
        public class NinjectResolver : IDependencyResolver
        {
            private static IKernel kernel;
    
            public NinjectResolver()
            {
                kernel = new StandardKernel();
                RegisterServices(kernel);
            }
    
            public NinjectResolver(IKernel myKernel)
            {
                kernel = myKernel;
                RegisterServices(kernel);
            }
    
            public static void RegisterServices(IKernel kernel)
            {
                //kernel.Bind<IThingRepository>().To<SqlThingRepository>();
            }
    
            public object GetService(Type serviceType)
            {
                return kernel.TryGet(serviceType);
            }
    
            public IEnumerable<object> GetServices(Type serviceType)
            {
                return kernel.GetAll(serviceType);
            }
        }
    
    }
    

    And then in the Global.asax.cs file I changed this line....

    //// Tell ASP.NET MVC 3 to use our Ninject DI Container 
    MvcServiceLocator.SetCurrent(new NinjectServiceLocator(_container));
    

    ...to...

    //// Tell ASP.NET MVC 3 to use our Ninject DI Container 
    DependencyResolver.SetResolver(new NinjectResolver(_container));
    

    And that's that! Have fun coding!

    Here's a complete working sample project based on my Code-First Entity Framework upgrade of the TekPub Starter Site.