<CharlieDigital/> Programming, Politics, and uhh…pineapples

26Jan/10Off

Simple (?) AJAX Upload For ASP.NET

Posted by Charles Chen

As I was working on an AJAX upload web part for SharePoint, I looked around to see if there was anything out there that would be suitable before I rolled my own after discovering that the ASP.NET UpdatePanel doesn't play nicely with file inputs. Since I'm using jQuery, I figured I'd start there and see if there were any plugins which would meet the need.

To my dismay, it seems as if kids these days are all using flash (flash!) to implement asynchronous upload. This was wholly unacceptable to me for some reason (not to mention it might cause compatibility issues for downstream clients) so I ended up tackling it myself :-D

The basics of getting this to work are simple enough; the solution is made of three main components:

  1. The main page that is displaying the upload control. Since I was using this in a SharePoint site, I wanted to write my uploader as a web part (the control) that could be placed on any page. The main page is simply the container for your control. It does not post back.
  2. The control that basically just renders the <iframe/>. The control is relatively simple. It provides a container for the <iframe/> and also the scripts which are executed when the frame is loaded and unloaded. The control also holds the progress layer since we want this to be shown when the user starts the upload. This also does not post back.
  3. The upload page that receives the actual file. This page is the target for the <iframe/> and contains the logic that validates the posted file. This is the only page that posts back.

Before we go into the details, let's look at the screens of this in action (pay particular attention to the times):

The first screen shows the general layout of the page in the default state. In case you're wondering, I found a handy guide for hacking the file input control and used that to customize the appearance. Again, note the time.

Once you click "Upload", a progress layer is shown over the control. You can also see that we've got an unloaded time now as well.

And finally, you can see that the loaded time changed once the form upload completes.

The control ASCX file contains two very simple scripts:

    function handleFrameLoaded() {
        // Do animation here.
        $("#progress").hide();
 

        $("#load").html("<b>Loaded!</b> " + 
            (new Date()).toTimeString());
    }

    function handleFrameUnloaded() {
        // Do animation here.
        $("#progress").show().fadeTo("fast", .90);

        $("#unload").html("<b>Unloaded!</b> " + 
            (new Date()).toTimeString());
    }

The first function is called when the frame is loaded and the second function is invoked when the file upload is submitted. Note that both functions are called from the upload page in the <iframe/>. In this case, I've just added simple animation calls to show and hide a progress panel. You can hook up whatever custom code you want here.

On the upload page itself, we need to wire the events to the functions above:

    $(window).load(function() {
        parent.handleFrameLoaded();
    });
 

    $(document).ready(function() {
        $(".upload-button").click(function() {
            parent.handleFrameUnloaded();
        });

        /* simulate hover */
        $("#fake-container").hover(
            function() {
                $(this).addClass("hover");
            },
            function() {
                $(this).removeClass("hover");
            });

        /* simulate populating the file value since 
            we can't see the file input */
        $("input.file").change(function() {
            $("#fake input").val($(this).val());
        });
    });

It's important to note that the upload page gets its own set of window events since it's loaded inside of the frame. The upload page makes calls to functions in the control. I've highlighted the points of interest; you'll note that I only bind the load event of the window (I don't bind the unload). It's also possible to do this using the onbeforeunload event, but I found that this would fire the progress layer even if I was browsing away from the page (which may confuse your users). So it made more sense to just do it simply from the upload button click.

The upload page itself is remarkably simple:

<body>
    <form id="_form" runat="server">
    <div>
        <div id="fake-container">            
            <input type="file" id="_file" runat="server" class="file"/>            
            <div id="fake">
                <input type="text" />
            </div>            
        </div>
        <asp:Button runat="server" ID="_upload" 
            Text="Upload" OnClick="HandleUploadClick" 
            CssClass="upload-button"/>
    </div>
    </form>
</body>

The control isn't much more complex either:

<div id="frame">
    This is an asynchronous upload control.  
    The control load time is <asp:Label ID="_time" runat="server"/>
 
    <iframe id="upload-frame" src="Upload.aspx" 
        frameborder="0" scrolling="no" height="100px">

    </iframe>  
    <div id="progress" style="display:none;"></div>
</div>
<div>
    <div id="load"></div>
    <div id="unload"></div>
</div>

There's no codebehind for the control to speak of. The only place where you need to implement custom code is in the codebehind of the upload page to receive the posted file:

using System;
using System.Threading;
using System.Web.UI;
 

namespace AsyncUploadControlTest
{
    /// <summary>
    /// This is the actual page that handles the upload.
    /// </summary>
    public partial class Upload : Page
    {
        /// <summary>
        /// Handles the Load event of the Page control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> 
        /// instance containing the event data.</param>
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        /// <summary>
        /// Handles the upload click.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> 
        /// instance containing the event data.</param>
        protected void HandleUploadClick(object sender, EventArgs e)
        {
            // Just fake a long running upload for dramatic effect
            Thread.Sleep(2500);

            // Add your logic here
        }
    }
}

There you have it. Works in SharePoint 2007 just fine. Works in IE7 and Firefox 3.5 as well. Because the receiving upload page is just an ASPX page, you can simply output your errors or success messages to the page itself; no special hackery required.

Download the source code here: AsyncUploadControlTest.7z (17.54 KB)

Filed under: .Net, Dev, SharePoint 2 Comments
6Jan/10Off

SharePoint 2010 Content Type Publishing Setup

Posted by Charles Chen

One of the cool features of SharePoint 2010 is the simplification of content type management/synchronization.

In SharePoint 2007, site collections formed a metadata boundary meaning that content types defined in one site collection could not be used in another site collection without redeploying the content type to the other site collection. This works well enough if you are using features to deploy your content types (and I mean, who isn't!?), but what about managing those content types and keeping them synchronized after you've deployed them?

Nightmare.

While it's relatively easy to set up, I came across an immediate error when trying to publish my first content type:

No valid proxy can be found to do this operation.

Using the correlation ID, I looked through the logs and found the following:

No proxy associated with the site http://metadata.dev.com

recognizes the site as a hub site d4d22525-2e9d-4ea4-becc-fe1d42735ee4 
 


A user failed to publish or unpublish a content type

Microsoft.SharePoint.Taxonomy.ContentTypeSync.ContentTypeSyndicationException:

No valid proxy can be found to do this operation.

I came across Chakkaradeep Chandran's instructions here, but his instructions seemed to have a small gap.

To access the functionality, you need to go to Central Administration -> Application Management -> Manage service applications (under Service Applications)

In this screen, select (don't click the link) Managed Metadata Service.

Again, very important: don't click the link (that takes you to the screen for managing keywords/terms/taxonomy; click somewhere next to the text and select the row to enable the Properties button at the top.

Next click Properties:

This will bring up the pane where you can set the Content Type hub setting (scroll to the bottom of the screen). Just enter the URL of your hub application (as an example, http://metadata.dev.com)

Next, you need to select the Managed Metadata Service Connection (the indented Managed Metadata Service) and again, click Properties. This brings up the pane that will allow you to check the Consumes content types from the... setting.

As Chakaradeep mentions, you need to run two timer jobs to get the content types to sync up. You can access this via Central Administration -> Check Job Status (under Monitoring):

Then page through that list looking for Content Type hub and Content Type subscriber. Clicking on the link to the job will open it up and allow you to click the Run now button to execute the job immediately.

In any case, it's a neat feature. I wonder, though, if there are improvements for managing deployment of content types via features (i.e. an incremental feature which adds new fields to exsiting content types and pushes the changes down) as opposed to UI based management of content types (which I'm not a terribly big fan of). My guess is "no".

Hope this helps someone!

Filed under: SharePoint 3 Comments