Yesterday we saw all the plumbing involved to lay a groundwork for RadUpload being used to add files to applicants. Today we actually dive into the code for getting RadUpload working in the MVVM application.

The first step in this is deciding where to add the ability to upload files. Doing this within the main Applicants grid is nice (since we're already using the RowDetails presenter), but that'll clutter up the UI there as I want to switch that into a quick and easy overview of each applicant. So instead we're modifying the AddEditApplicantView to make it tab-based, providing one tab for the regular information and the other for handling the file uploads (and project rating, since we do have a RadRating control now!). I'll spare you the markup, but here is the new UI:

New UI for Add/Edit Applicant View

Do you see the RadUpload instances? Neither do I, that's because they have Visibility set to Collapsed and we're handling all actions through the RadUploadDropPanel and attached behaviors on RadUpload. Since we now utilize DropTarget support with RadUpload, we don't even need to see the control anymore to take advantage of the fast upload capabilities it gives us. To get into the code a little, here is the XAML markup for the first instance of RadUploadDropPanel, RadUpload, and the corresponding HyperlinkButton:

 

<telerik:RadUploadDropPanel x:Name="xResumeDropPanel"
                            Grid.Row="1"
                            Grid.Column="1"
                            AllowDrop="True"
                            DragOver="xResumeDropPanel_DragOver"
                            DragLeave="xResumeDropPanel_DragLeave"
                            Drop="xResumeDropPanel_Drop"
                            RadUpload="{Binding ElementName=xResumeRadUpload}">
    <Border x:Name="xResumeUploadBorder"
            Margin="5"
            Background="LightBlue"
            Height="30">
        <TextBlock HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Text="Resume" />
    </Border>
</telerik:RadUploadDropPanel>
<telerik:RadUpload x:Name="xResumeRadUpload"
                    AllowDrop="True"
                    IsAutomaticUpload="True"
                    UploadServiceUrl="/HRUpload.ashx"
                    OverwriteExistingFiles="True"
                    TargetPhysicalFolder="{Binding UploadTargetFolder}"
                    command:UploadStartedEventClass.Command="{Binding UploadStartedCommand}"
                    command:UploadFinishedEventClass.Command="{Binding UploadFinishedCommand}"
                    command:FileUploadedEventClass.Command="{Binding FileUploadFinishedCommand}"
                    Grid.Row="1"
                    Grid.Column="3"
                    Visibility="Collapsed"
                    d:DesignHeight="10"
                    d:DesignWidth="10" />
<HyperlinkButton x:Name="xResumeUploadTextBlock"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Content="{Binding ResumeUploadStatus}"
                    Foreground="{Binding ResumeUploadStatus, Converter={StaticResource StatusToColorConverter}}"
                    NavigateUri="{Binding ActiveApplicant.Resume}"
                    TargetName="_blank"
                    Grid.Row="1"
                    Grid.Column="2" />

 

And we also get a little bit of code-behind:

// I'm not a bad MVVM programmer, but creating behaviors in order to 
//   modify the drag-over color for the RadUploadPanel content is 
//   overkill - this is strictly a view-related issue and therefore
//   it is okay to have strictly view-related code in the code-behind. :)
private void xResumeDropPanel_DragOver(object sender, DragEventArgs e)
{
    ((sender as RadUploadDropPanel).Content as Border).Background = new SolidColorBrush(Colors.Orange);
}
private void xResumeDropPanel_DragLeave(object sender, DragEventArgs e)
{
    ((sender as RadUploadDropPanel).Content as Border).Background = new SolidColorBrush(Color.FromArgb(255, 96, 194, 255));
}
private void xResumeDropPanel_Drop(object sender, DragEventArgs e)
{
    ((sender as RadUploadDropPanel).Content as Border).Background = new SolidColorBrush(Color.FromArgb(255, 96, 194, 255));
}

 

For those MVVM purists, yes, there is code in my code-behind, but I've got a very specific reason for that (explained in the comments, but I'll touch on it again). The DragOver, DragLeave, and Drop events are only effecting something in the view and have nothing to do with the overall data model for the application. So for each I could have gone the route of creating an attached behavior, setting the colors in the viewmodel to bind to the drop panels, and adding more code to my viewmodel to ensure this all works without a hitch, but why? This works, it only impacts the view, and is a more streamlined and less time-consuming approach than all that. 

Back on track, in order of appearance we see our RadUploadDropPanel that is bound to the xResumeRadUpload (therefore any files dropped on the drop panel get added to RadUpload), with a simple border and textblock being used to provide a UI that updates based on whether the user is dragging a file over it or not.

Next is RadUpload, where all the magic is going to happen:

  • IsAutomaticUpload is true so that once a file is dropped on the respective upload panel, RadUpload runs with it and attached behaviors cover the rest of our interaction
  • UploadServiceUrl points to a standard RadUpload upload handler in the .Web project (instructions here on the basic setup I used)
  • OverwriteExistingFiles is true because I trust my HR people to handle their uploads properly
  • TargetPhysicalFolder is bound to UploadTargetFolder, which gets updated whenever we switch into this view to ensure that the correct ApplicantGUID is being used for our target

The commands cover the three things that we need to ensure uploads are going as planned and reporting status back to the UI. UploadStarted will let us know, if you haven't guessed, when the upload is starting. UploadFinished is doing the same when the upload finishes, except this lets us grab the instance of RadUpload so that when FileUploadFinished runs, we know which property of the entity to 'attach' the file to. I won't go into the EventClass code (it's the same for every command I add like this, albeit with names and control references switched up), but here are the three EventBehaviors:

    public class UploadStartedEventBehavior : CommandBehaviorBase<RadUpload>
    {
        public UploadStartedEventBehavior(RadUpload element)
            : base(element)
        {
            element.UploadStarted += new EventHandler<UploadStartedEventArgs>(element_UploadStarted);
        }
  
        void element_UploadStarted(object sender, UploadStartedEventArgs e)
        {
            RadUpload ru = sender as RadUpload;
            base.CommandParameter = ru.Name;
  
            base.ExecuteCommand();
        }
    }
...
    public class UploadFinishedEventBehavior : CommandBehaviorBase<RadUpload>
    {
        public UploadFinishedEventBehavior(RadUpload element)
            : base(element)
        {
            element.UploadFinished += new EventHandler<EventArgs>(element_UploadFinished);
        }
  
        void element_UploadFinished(object sender, EventArgs e)
        {
            RadUpload ru = sender as RadUpload;
            base.CommandParameter = ru.Name;
  
            base.ExecuteCommand();
        }
    }
...
    public class FileUploadedEventBehavior : CommandBehaviorBase<RadUpload>
    {
        public FileUploadedEventBehavior(RadUpload element) 
            : base(element)
        {
            element.FileUploaded += new EventHandler<FileUploadedEventArgs>(element_FileUploaded);
        }
  
        void element_FileUploaded(object sender, FileUploadedEventArgs e)
        {
            base.CommandParameter = e;
              
            base.ExecuteCommand();
        }
    }

 

and their respective DelegateCommands on the viewmodel:

// When upload starts we update the UI
public void UploadStartCommand(object obj)
{
    string controlName = (string)obj;
    if (controlName.Contains("Resume"))
    {
        ResumeUploadStatus = "Started";
    }
    else if (controlName.Contains("CoverLetter"))
    {
        CoverLetterUploadStatus = "Started";
    }
    else // only one left is Project
    {
        ProjectUploadStatus = "Started";
    }
}
// Same when upload finishes
public void UploadFinishCommand(object obj)
{
    string controlName = (string)obj;
    // save this to use in FileUploadFinishCommand
    _uploadName = controlName;
    if (controlName.Contains("Resume"))
    {
        ResumeUploadStatus = "Complete";
    }
    else if (controlName.Contains("CoverLetter"))
    {
        CoverLetterUploadStatus = "Complete";
    }
    else // only one left is Project
    {
        ProjectUploadStatus = "Complete";
    }
}
// Use this to capture the file name on upload finishing :)
public void FileUploadFinishCommand(object obj)
{
    FileUploadedEventArgs uploadEvents = obj as FileUploadedEventArgs;
    string uploadFileNameResult = uploadEvents.SelectedFile.Name;
    if (string.IsNullOrEmpty(uploadFileNameResult))
    {
        eventAggregator.GetEvent<NotifyUserEvent>().Publish("Problem uploading file!");
    }
    else
    {
        if (_uploadName.Contains("Resume"))
        {
            ActiveApplicant.Resume = "/HR/applicants/" + ActiveApplicant.ApplicantGUID.ToString() + "/" + uploadFileNameResult;
        }
        else if (_uploadName.Contains("CoverLetter"))
        {
            ActiveApplicant.CoverLetter = "/HR/applicants/" + ActiveApplicant.ApplicantGUID.ToString() + "/" + uploadFileNameResult;
        }
        else // only one left is Project
        {
            ActiveApplicant.OverviewProject = "/HR/applicants/" + ActiveApplicant.ApplicantGUID.ToString() + "/" + uploadFileNameResult;
        }
        _uploadName = "";
    }
}

 

So the order of events is:

  • Upload Starts - Update UI
  • Upload Finishes - Update the UI and save RadUpload instance name
  • File Upload Finishes - Save the file name and location to our entity

Lastly we have the HyperlinkButton, which utilizes *UploadStatus as a tracking variable on the viewmodel to determine where we are at with our upload (and if we have one when loading an Applicant in), the foreground gets converted to show successful (green), standard (black), or error (red) states, and the NavigateUri points to the file that is accessible on the server.

So what happens here? Let me give you a short pictorial to show you how this all goes down...

RadUploadProcess

And we're good to go. :)

Next week we cover the (I hope) significantly easier approach of doing this all in the code-behind version.

Cheers!


About the Author

Evan Hutnick

works as a Developer Evangelist for Telerik specializing in Silverlight and WPF in addition to being a Microsoft MVP for Silverlight. After years as a development enthusiast in .Net technologies, he has been able to excel in XAML development helping to provide samples and expertise in these cutting edge technologies. You can find him on Twitter @EvanHutnick.

Related Posts

Comments