Creating Media Galleries with ASP.NET 4 Social Networking

Exclusive offer: get 50% off this eBook here
ASP.NET 4 Social Networking

ASP.NET 4 Social Networking — Save 50%

A truly hands-on book for ASP.NET 4 Developers

$29.99    $15.00
by Andrew Siemer Atul Gupta Sudhanshu Hate | April 2011 | .NET Web Development

This article by Atul Gupta, Sudhanshu Hate and Andrew Siemer, authors of ASP.NET 4 Social Networking, covers details on how to build a generic media management system that will allow you to host video, photos, resumes, or any number of physical files with minimal tweaking. It also addresses the issue of multi-file uploads via RIA technologies like Flash and Silverlight.

In addition to that, we will build the concept of having user-specific sets of files as well as content that can be shared among many users. This article will create a basic framework from which you can easily grow to suit the file management needs of just about any community site.

 

ASP.NET 4 Social Networking

ASP.NET 4 Social Networking

A truly hands-on book for ASP.NET 4 Developers

        Read more about this book      

(For more resources on ASP.NET, see here.)

In order to create the file management software for our website, we need to consider topics such as a single or multi-file upload, file system management, and image manipulation in the case of photos. In addition to this we will cover creation of pages for displaying the user's photo albums, their friends' photo albums, as well as a few data management pages.

Problem

Apart from the standard infrastructure issues that we have to consider when building a system such as this, one of the core issues in any web-based file management system is file upload. As we all know, most server side technologies allow only one file to be uploaded at a time and ASP.NET is no different. And while we could easily buy a third-party plug-in to handle multiple files at once, we decided to provide for options to upload the files either via Silverlight or via Flash.

Once we get our file upload process working we are only one-third of the way there! As we are going to be mostly concerned with uploading images, we need to consider that we will need to provide some image manipulation. With each file that is uploaded to our system we need to create a handful of different sizes of each image to be used in various scenarios across our site. To start with, we will create thumbnail, small, medium, large, and original size photos.

Now while creating different size files is technically working with the file storage system, we wanted to take an extra breath with regards to the file storage concepts. We can choose to store the files on the file system or in a database. For avatars it made sense to store each with the profile data whereas for image galleries it makes more sense to store the file on the file system. While storing files to the file system we need to be very cautious as to how the file structure is defined and where and how the individual files are stored. In our case we will use system-generated GUIDs as our file names with extensions to define the different sizes that we are storing. We will dig into this more as we start to understand the details of this system.

Once we have uploaded the files to the server and they are ready for our use across the system, we will take up the concept of user files versus system files. If we build the system with some forethought regarding this topic we can have a very generic file management system that can be extended for future use. We will build a personal system in this article. But as you will see with some flags in just the right places, we could just as easily build a system file manager or a group file manager.

Design

Let's take a look at the design for this feature.

Files

For our site, as we are not storing our files in the database, we need to take a closer look at what actually needs to be managed in the database so as to keep track of what is going on in the file system. In addition to standard file metadata, we need to keep a close eye on where the file actually lives—specifically which file system folder (directory on the hard drive) the file will reside in. We also need to be able to maintain which accounts own which files, or in the case of system files, which files can be viewed by anyone.

Folders

You may be wondering why we have a separate section regarding folders when we just touched upon the fact that we will be managing which file system folder we will be storing files in. In this section we are going to discuss folder management from a site perspective rather than a file system perspective—user folders or virtual folders if you desire.

Very similar to file storage, we will be storing various metadata about each folder. We will also have to keep track of who owns which folder, who can see which folder, or in the case of system folders whether everyone can see that folder. And of course as each folder is a virtual container for a file, we will have to maintain the relationship between folders and files.

File upload

The file upload process will be handled by a Silverlight/Flash client. While this is not really an article about either Silverlight or Flash, we will show you how simple it is to create this Flash client, that is really just providing a way to store many files that need to be uploaded, and then uploading them one at a time in a way that the server can handle each file. For the Silverlight option, we are using code from Codeplex—http://silverlightfileupld.codeplex.com/.

File system management

Managing the file system may seem like a non-issue to begin with. However, keep in mind that for a community site to be successful we will need at least 10,000 or so unique users. Given that sharing photos and other files is such a popular feature of most of today's community sites, this could easily translate into a lot of uploaded files.

While you could technically store a large number of files in one directory on your web server, you will find that over time your application becomes more and more sluggish. You might also run into files being uploaded with the same name using this approach. Also, you may find that you will have storage issues and need to split off some of your files to another disk or another server.

Many of these issues are easily handled if we think about and address them up front. In our case we will use a unique file name for each uploaded file. We will store each file in subdirectories that are also uniquely named based on the year and month in which the file was uploaded. If you find that you have a high volume of files being uploaded each day, you may want to store your files in a folder with the year and month in the name of the folder and then in another subdirectory for each day of that month.

In addition to a good naming convention on the file system, we will store the root directory for each file in the database. Initially you may only have one root for your photos, one for videos, and so on. But storing it now will allow you to have multiple roots for your file storage—one root location per file. This gives you a lot of extensibility points over time meaning that you could easily relocate entire sections of your file gallery to a separate disk or even a separate server.

Data management screens

Once we have all of the infrastructure in place we will need to discuss all the data management screens that will be needed—everything from the UI for uploading files to the screens for managing file metadata, to screens for creating new albums. Then we will need to tie into the rest of the framework and allow users to view their friends' uploaded file albums.

Solution

Let's take a look at our solution.

Implementing the database

First let's take a look at the tables required for these features (see the following screenshot).

Files

The most important thing to consider storing while in the database is of course our primary interest files. As with most other conversations regarding a physical binary file we always have to consider if we want to store the file in the database or on the file system. In this case we think it makes sense to store the file (and in the case of a photo, its various generated sizes) on the file system. This means that we will only be storing metadata about each file in our database.

The most important field here to discuss is the FileSystemName. As you can see this is a GUID value. We will be renaming uploaded files to GUIDs in addition to the original extension. This allows us to ensure that all the files in any given folder are uniquely named. This removes the need for us to have to worry about overwriting other files.

Then we see the FileSystemFolderID. This is a reference to the FileSystemFolders table, that lets us know the root folder location where the file is stored.

Next on our list of items to discuss is the IsPublicResource flag. By its name it is quite clear that this flag will set a file as public or private and can therefore be seen by all or by its owner (AccountID).

We then come to a field that may be somewhat confusing: DefaultFolderID. This has nothing to do with the file system folders. This is a user created folder. When files are uploaded initially they are put in a virtual folder. That initial virtual folder becomes the file's permanent home. This doesn't mean that it is the file's only home. As you will see later we have the concept that files can live in many virtual folders by way of subscription to the other folders.

File system folders

As mentioned previously, the FileSystemFolders table is responsible for letting us know where our file's root directory is. This allows us to expand our system down the road to have multiple roots, that could live on the same server but different disks, or on totally different servers. The fields in the table are Key, Path (URL), and a Timestamp.

File types

The FileTypes table will help us to keep track of what sort of files we are storing and working with. This is a simple lookup table that tells us the extension of a given file.

Folders

Folders are virtual in this case. They provide us with a way to specify a container of files. In our case we will be containing photos, in which case folders will act as photo albums. The only field worth explaining here is the flag IsPublicResource, which allows us to specify whether a folder and its resources are public or private, that is, viewable by all or viewable only by the owner.

Folder types

The FolderTypes table allows us a way to specify the type of folder. Currently this will simply be Name, photos, movies, and so on. However, down the road you may want to specify an icon for each folder type in which case this is the place where you would want to assign that specification.

Account folders

In the AccountFolders table we are able to specify additional ownership of a folder. So in the case that a folder is a public resource and external resources can own folders, we simply create the new ownership relationship here. This is not permanent ownership. It is still specified with the Folders table's AccountID. This is a temporary ownership across many Accounts.

As you can see in the previous screenshot we have the owner (AccountID) and the folder that is to be owned (FolderID).

Account files

Similar to the AccountFolders table, the AccountFiles table allows someone to subscribe to a specific file. This could be used for the purposes of Favorites or similar concepts. The makeup of this table is identical to AccountFolders. You have the owner and the file being owned.

Folder files

The FolderFiles table allows an Account to not only subscribe to a file, similar to the Favorites concept, but it also allows a user to take one of my files and put it into one of their folders as though the file itself belonged to them.

As you can see in the previous screenshot this is primarily a table that holds the keys to the other tables. We have the FolderID, FileID, and AccountID for each file. This clearly specifies who is taking ownership of what and where they want it to be placed.

Creating the relationships

Once all the tables are created we can then create all the relationships.

For this set of tables we have relationships between the following tables:

  • Files and FileSystemFolders
  • Files and FileTypes
  • Files and Folders
  • Files and Accounts
  • Folders and Accounts
  • Folders and FolderTypes
  • AccountFolders and Accounts
  • AccountFolders and Folders
  • AccountFiles and Accounts
  • AccountFiles and Files
  • FolderFiles and Accounts
  • FolderFiles and Folders
  • FolderFiles and Files


ASP.NET 4 Social Networking A truly hands-on book for ASP.NET 4 Developers
Published: March 2011
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on ASP.NET, see here.)

Setting up the data access layer

We need to open Fisharoo.edmx (download code-ch:7) file, right-click and select Update Model from Database, and select the new tables. Save the file and you now have the domain objects to work with for this article.

Building repositories

With the addition of new tables will come the addition of new repositories so that we can get the data stored in those tables. We will be creating the following repositories to support our needs.

  • FileRepository
  • FolderRepository

Each of our repositories will generally have a method for selecting by ID, select all by parent ID, save, and delete. Once you have seen one repository you have pretty much seen them all. Review the included code (download code-ch:7), for examples of a standard repository. Here we will discuss anything that varies from the standard!

FileRepository

Other than the normal methods that all the XRepository classes have, the FileRepository also has a couple of additional more interesting methods.

  • UpdateDescriptions
  • DeleteFilesInFolder
  • DeleteFileFromFileSystem

The UpdateDescriptions() method is an interesting concept. This method is the first example of performing multiple updates all at once rather than doing them one at a time. This obviously will be much more performance-oriented than individual update executions.

//Fisharoo/DataAccess/Repositories/FileRepository.cs
public void UpdateDescriptions(Dictionary<int,string>
fileDescriptions)
{
using(FisharooDataContext dc = conn.GetContext())
{
List<Int64> fileIDs = fileDescriptions.Select(f => Convert.
ToInt64(f.Key)).Distinct().ToList();
IEnumerable<File> files = dc.Files.Where(f => fileIDs.
Contains(f.FileID));
foreach (File file in files)
{
file.Description = fileDescriptions.Where(f=>f.Key==file.FileID)
.Select(f=>f.Value).FirstOrDefault();
}
dc.SaveChanges();
}
}

As you can see, this method accepts a Dictionary collection, that contains a list of FileIDs and fileDescriptions. We then open up the DataContext. Then from the Dictionary collection we get a list of unique FileIDs. We then make one trip to the database to get all of the File objects we need using the Contains() method of the FileID collection. With the collection of appropriate files in hand we can then iterate through each of them setting the description to the value passed in via the Dictionary collection. Once all this leg work is done, we can call the SaveChanges method. Keep in mind that we have one working DataContext for this entire operation. It is why this method of updation works!

//Fisharoo/DataAccess/Repositories/FileRepository.cs
public void DeleteFilesInFolder(string filePath, Folder folder)
{
using (FisharooDataContext dc = conn.GetContext())
{
List<File> files = GetFilesByFolderID(folder.
FolderID);
foreach (File file in files)
{
DeleteFileFromFileSystem(filePath, folder, file);
}
//Entity Framework 4 does not support any method to
bulk delete, hence only way is to iterate through
resultset
//and delete one by one, another way is to use
dc.ExecuteStoreQuery() and pass SQL String
//dc.ExecuteStoreQuery() is efficient but does not use
LINQ syntax
var ListOffiles = dc.Files.Where(f =>
f.DefaultFolderID == folder.FolderID).ToList();
foreach (File f in ListOffiles)
{
dc.Files.DeleteObject(f);
}
dc.SaveChanges();
}
}

The next method that is not a normal Repository method is the DeleteFilesInFolder. This method takes in a Folder object. With this folder object in hand we get a collection of related files. We then iterate over each file and call the DeleteFileFromFileSystem method. We then retrieve all the files for the specific FolderID and then iterate through the list of files and call the DeleteObject method to delete the File records. We then call SaveChanges to execute the changes.

Having mentioned the DeleteFileFromFileSystem, you probably understood what that method is responsible for. Essentially, it is responsible for removing the physical files that are stored on the file system. This is important as we have just allowed the data to be deleted, so we could end up with huge file stores of random unwanted files.

//Fisharoo/DataAccess/Repositories/FileRepository.cs
private void DeleteFileFromFileSystem(string filePath,
Folder folder, File file)
{
string path = "";
switch (file.FileTypeID)
{
case 1:
case 2:
case 7:
path = "Photos\\";
break;
case 3:
case 4:
path = "Audios\\";
break;
case 5:
case 8:
case 6:
path = "Videos\\";
break;
}
string fullPath = filePath + "Files\\" + path +
folder.CreateDate.Year.ToString() +
folder.CreateDate.Month.ToString() + "\\";
if (Directory.Exists(fullPath))
{
if (System.IO.File.Exists(fullPath + file.FileSystemName +
"__o." + file.Extension))
System.IO.File.Delete(fullPath + file.FileSystemName +
"__o." + file.Extension);
if (System.IO.File.Exists(fullPath + file.FileSystemName +
"__t." + file.Extension))
System.IO.File.Delete(fullPath + file.FileSystemName +
"__t." + file.Extension);
if (System.IO.File.Exists(fullPath + file.FileSystemName +
"__s." + file.Extension))
System.IO.File.Delete(fullPath + file.FileSystemName +
"__s." + file.Extension);
if (System.IO.File.Exists(fullPath + file.FileSystemName +
"__m." + file.Extension))
System.IO.File.Delete(fullPath + file.FileSystemName +
"__m." + file.Extension);
if (System.IO.File.Exists(fullPath + file.FileSystemName +
"__l." + file.Extension))
System.IO.File.Delete(fullPath + file.FileSystemName +
"__l." + file.Extension);

if(Directory.GetFiles(fullPath).Count() == 0)
Directory.Delete(fullPath);
}
}

This method starts off first by attempting to identify the FileType that we are dealing with, which gets us the root folder for that type of file.

Keep in mind that if you want to store files at multiple locations and use the FileSystemFolders, then this will need to be tweaked a bit. The concept shown here is for a single root file folder!

Another point worth noting is that the file deletion may run into issues if the file is locked. If you run into issues, you can possibly create an entry into a separate table and have a job running that regularly scans the table and deletes the files.

With the root path identified we then create the fullPath. This variable is created by making a call into our WebContext class, that determines the root path on the server. We then add Files, which is the files' root folder. Then comes the path that we just configured. The next portion may seem odd at the moment. When we upload files (coming later) we upload to a directory with the name of the folder's creation date, year, and month.

With the fullPath configured appropriately we then check to make sure that the directory actually exists. Now we can step through each file that we created. We check to see if the file exists at the specified location and then call System.IO.File.Delete. We do this for each type of file we create in our upload process. Keep in mind that the upload and delete processes are fairly tied at the hip. So if we change something on one end we need to make the changes at the other end too!

With the files deleted we take the file system beautification one step further. We need to check to see if the folder is now empty. If it is, then we delete the folder too!

FolderRepository

There are a couple of methods in the FolderRepository that are worth covering.

  • GetFoldersByAccountID
  • GetFriendsFolders

The GetFoldersByAccountID does just what it says! It takes in an AccountID and performs a search to get all the folders by the passed in AccountID. It does go a bit beyond that. It then iterates through each folder in the resulting list and generates the file system path for the folder's cover image. This determination is then assigned to the FullPathToCoverImage. If there is no cover image found then the default image is assigned. Once we have this taken care of, we return the list of folders.

//Fisharoo/DataAccess/Repositories/FolderRepository.cs
public List<Folder> GetFoldersByAccountID(Int32 AccountID)
{
List<Folder> result = new List<Folder>();
using(FisharooDataContext dc = conn.GetContext())
{
var account = dc.Accounts.Where(a => a.AccountID ==
AccountID).FirstOrDefault();

IEnumerable<Folder> folders = (from f in dc.Folders
where f.AccountID == AccountID
orderby f.CreateDate
descending
select f);
foreach (Folder folder in folders)
{
var fullPath = (from f in dc.Files.Include("FileType")
join ft in dc.FileTypes
on f.FileTypeID equals ft.FileTypeID
where f.DefaultFolderID == folder.FolderID
select f).FirstOrDefault();
if (fullPath != null)
{
String FullPathToCoverImage = fullPath.CreateDate.
Year.ToString() + fullPath.CreateDate.Month.
ToString() + "/" + fullPath.FileSystemName.ToString()
+ "__S." + fullPath.FileType.Name;
folder.FullPathToCoverImage = FullPathToCoverImage;
}
else
folder.FullPathToCoverImage = "default.jpg";
if(account != null)
folder.Username = account.Username;
}
result = folders.ToList();
}
return result;
}

The next interesting method in this repository is the GetFriendsFolders. This method introduces a new LINQ concept—Union. The Union takes all the items from one collection and merges it with another collection of items. The items that are the same in both the lists are merged so that the resulting list of items is unique.

//Fisharoo/DataAccess/Repositories/FolderRepository.cs
public List<Folder> GetFriendsFolders(List<Friend> Friends)
{
List<Folder> result = new List<Folder>();
foreach (Friend friend in Friends)
{
if (result.Count < 50)
{
List<Folder> folders =
GetFoldersByAccountID(friend.MyFriendsAccountID);
IEnumerable<Folder> result2 = result.
Union(folders);
result = result2.ToList();
}
else
break;
}
return result;
}

In our method we take in a list of our Friends. We then iterate over this collection and with each pass we check to see if we have less than 50 folders (we chose a random number of items to show on our album homepage). If we have less than 50, then we get all the folders for the current friend and merge it into our result list using the Union method. We continue to do this until we are either out of Friends or have 50 folders to show on the homepage.

This method could be made better in a couple of ways. Firstly we could move the 50 to a configuration file or administration panel. Also, this method should return the folders that are the latest, or contain the latest files from our friends. If we really want to be flexible this method should take into account that the user may have more friends than the current limitation. In this case we should really create pagination functionality allowing our user to see all their friends and their friends' folders. We can do all this later though!

Now that we have a place for our data and ways to interact with it, let's move out one more layer closer to the UI. We will now discuss the services' layer to help us get the data out to the front of the application.

Implementing the services/application layer

Once all the repositories are built for a single serving purpose, we can begin to create the services' layer. Again, this layer is responsible for assembling aggregates and performing complex actions with our entities. The only main service to look at here is the FolderService.

FolderService

This service is fairly simple. Its sole responsibility for the time being is to interact with various repositories to get the list of Friend's folders for display on the album homepage for our users. Check the code associated with this chapter for implementation details.

That's it! We move on to the presentation layer.

ASP.NET 4 Social Networking A truly hands-on book for ASP.NET 4 Developers
Published: March 2011
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on ASP.NET, see here.)

Implementing the presentation layer

With the entire backend created and ready to go let's turn our attention to getting the presentation up and running. We will get started with building the file upload section first as it will be difficult to get the other sections to run successfully without uploaded files.

File upload

As you may already know ASP.NET is very handy at browsing a file, selecting it, and then uploading it. There are very few modifications that need to be made to your application to enable this functionality, such as you need to alter the form to include enctype="multipart/form-data", add a file browse box to your page, and handle the uploaded file. Done!

As stated earlier we will be building a Flash-based file upload client. This client will be responsible for selecting a group of files on the local file system. It will then pass the files one at a time to our server side receiving page. When we discuss photo files (JPG, GIF, and so on) we will also look at some basic image processing concepts. We will also provide a Silverlight upload control as an alternate option and will reuse the control already available on codeplex (http://silverlightfileupld.codeplex.com/) for this. There are other options like AJAX-based controls as well, but we wanted to bring in a bit of Rich Internet Application (RIA) flavor and hence the use of Flash and Silverlight.

Multiple file upload

Our file receiver will be able to receive multiple files at any one time. In our case we will be primarily concerned with receiving image files. But we will set it up to receive other files as well. The receiver in our case (ReceiveFiles.aspx.cs) will be housed inside a webpage in our Files directory. We will explain the code here, but will only show the relevant parts here and not the entire code.

The beginning of our receiver will be responsible for spinning up some variables and objects. The only interesting thing to note here is the sizesToMake collection. This will hold all the sizes, which we will generate further down the line. If we want to add an additional size or modify one of our existing sizes we would do that here.

Dictionary<string,int> sizesToMake = new Dictionary<string,int>();

Then in the Page_Load() method we have the initialization of our objects and our sizes collection. We then interrogate a query string value to see what sort of file we are dealing with. In our case we will be working with photos, but we might deal with something else later.

//determine save-to folder
switch (_webContext.FileTypeID)
{
case 1:
saveToFolder = "Photos/";
break;
case 2:
saveToFolder = "Videos/";
break;
case 3:
saveToFolder = "Audios/";
break;
}

Once we have decided which folder we are working with, we would then need to check to make sure that folder is actually on the file system. If not, then we may want to create it.

//make sure the directory is ready for use
saveToFolder += DateTime.Now.Year.ToString() +
DateTime.Now.Month.ToString() + "/";
if (!Directory.Exists(Server.MapPath(saveToFolder)))
Directory.CreateDirectory(Server.MapPath(saveToFolder));

We then have a few other variables that we want to set up. We need to get the Account that we are working with. We also need to receive the uploaded files. And finally, we will need to obtain the full path to the folder that we are saving our files to.

Account account = _accountService.GetAccountByID(
_webContext.AccountID);
HttpFileCollection uploadedFiles = Request.Files;
string Path = Server.MapPath(saveToFolder);

Now that we have a collection of uploaded files we need to work with each file one at a time. To do this we will start a for loop. At the top of each iteration we will need to get a single file that we want to process.

for(int i = 0 ; i < uploadedFiles.Count ; i++)
{
HttpPostedFile F = uploadedFiles[i];

We then need to initialize some more variables to be set up where the files will be stored and how we will do it. We will get the folder ID from the query string. Notice that we are currently assuming that we are playing with image files with a static fileType of 1. If we were to create another page to upload say audio files, we would want to pass in the fileType. Next we attempt to get the uploadedFileName by parsing the end of the file name of the uploaded file. Once we have the file name we get the file extension. As we will be saving the files to the file system, we run the risk of overwriting files if we do not ensure that the file has a unique name. To do this, we create a new GUID string that will act as our file system's file name. With all this data in place we then have enough to create the final file name. We then create the domain object file.

string folderID = _webContext.AlbumID.ToString();
string fileType = "1";
string uploadedFileName =
F.FileName.Substring(F.FileName.LastIndexOf("\\") + 1);
string extension =
uploadedFileName.Substring(uploadedFileName.LastIndexOf(".") + 1);
Guid guidName = Guid.NewGuid();
string fullFileName = Path + "/" + guidName.ToString() + "__O." +
extension;
bool goodFile = true;
//create the file
File file = new File();

Next, we look at the fileType that was set and determine the File object's FileTypeID. This is an enum that was set up in the File's partial class. Note that at the end of each inner switch statement we are setting a flag for goodFile to determine if we have successfully found our FileTypeID. Do note that this isn't a foolproof method as corrupt files can still be uploaded with these valid extensions. However, we will trust our users and keep things simple for now.

#region "Determine file type"
switch (fileType)
{
case "1":
file.FileSystemFolderID =
(int)FileSystemFolder.Paths.Photos;
switch (extension.ToLower())
{
case "jpg":
file.FileTypeID = (int)File.Types.JPG;
break;
case "gif":
file.FileTypeID = (int)File.Types.GIF;
break;
case "jpeg":
file.FileTypeID = (int)File.Types.JPEG;
break;
default:
goodFile = false;
break;
}
break;
case "2":
file.FileSystemFolderID = (int)FileSystemFolder.Paths.Videos;
switch (extension.ToLower())
{
case "wmv":
file.FileTypeID = (int)File.Types.WMV;
break;
case "flv":
file.FileTypeID = (int)File.Types.FLV;
break;
case "swf":
file.FileTypeID = (int)File.Types.SWF;
break;
default:
goodFile = false;
break;
}
break;
case "3":
file.FileSystemFolderID = (int)FileSystemFolder.Paths.Audios;
switch (extension.ToLower())
{
case "wav":
file.FileTypeID = (int)File.Types.WAV;
break;
case "mp3":
file.FileTypeID = (int)File.Types.MP3;
break;
case "flv":
file.FileTypeID = (int)File.Types.FLV;
break;
default:
goodFile = false;
break;
}
break;
}

Next, we attempt to populate the domain File object with all its properties such as the size of the uploaded file, the account that it belongs to, its file system name, and so on.

file.Size = F.ContentLength;
file.AccountID = account.AccountID;
file.DefaultFolderID = Convert.ToInt32(folderID);
file.FileName = uploadedFileName;
file.FileSystemName = guidName;
file.Description = "";
file.IsPublicResource = false;

Now we are ready to start the work. If the goodFile flag is still true then we can commit our file object to the database. We then save the actual uploaded file to the file system with its new file name. And finally if the fileType is a Picture, we scrub the file against our Resize() method to generate the various sizes of files we want to end up with.

if (goodFile)
{
_fileService.SaveFile(file);
F.SaveAs(fullFileName);
if(Convert.ToInt32(fileType) == ((int)Folder.Types.Photo))
{
Resize(F,saveToFolder,guidName,extension);
}
}

To get into the Resize() method we have to pass in the uploaded file that we are working with, the folder that we want to save the files to, the generated GUID for the file system name, and the extension of the uploaded file. Once in the Resize() method, we can create all the size variations that we need for our uploaded photo. We start off by setting up a foreach loop that will iterate through the dictionary in the class wide sizesToMake collection.

public void Resize(HttpPostedFile F, string SaveToFolder, Guid
SystemFileNamePrefix, string Extension)
{
//Makes all the different sizes in the sizesToMake collection
foreach (KeyValuePair<string, int> pair in sizesToMake)
{

Inside of our loop we will start up a new System.Drawing.Image and we will initialize it from the uploaded files input stream. We then move to creating a new Bitmap that is initialized from the newly created Image. With the Bitmap we can then create new variations on the size of that uploaded image. First we look to see if the file that was uploaded is longer on the top or on the side so that we know how to appropriately determine the ratio of the image's height and width.

using(System.Drawing.Image image =
System.Drawing.Image.FromStream(F.InputStream))
//determine the thumbnail sizes
using(Bitmap bitmap = new Bitmap(image))
{
decimal Ratio;
if(bitmap.Width > bitmap.Height)
{
Ratio = (decimal) pair.Value / bitmap.Width;
NewWidth = pair.Value;
decimal Temp = bitmap.Height * Ratio;
NewHeight = (int)Temp;
}
else
{
Ratio = (decimal) pair.Value / bitmap.Height;
NewHeight = pair.Value;
decimal Temp = bitmap.Width * Ratio;
NewWidth = (int)Temp;
}
}

Once we have our sizes determined we can resize it and save it to the file system. We do this by again setting a reference to the uploaded file and reconstructing a new Bitmap. With this in hand we can then save the bitmap to the file system with its new dimensions. Do note though that in this case when we save the file to the file system, we are not adding a section to the name using the key name from the dictionary. This results in a file name that is made up of {GUID}__{key}.{extension}. This means that every file will be unique and will have uniquely named files of various sizes.

using(System.Drawing.Image image =
System.Drawing.Image.FromStream(F.InputStream))
using(Bitmap bitmap = new Bitmap(image, NewWidth, NewHeight))
{
bitmap.Save(Server.MapPath(SaveToFolder + "/" +
SystemFileNamePrefix.ToString() + "__"
+ pair.Key + "." + Extension),
image.RawFormat);
}

We continue to loop through all the different file sizes in our dictionary creating new files for each one. Once complete we will move to the next file that was uploaded. Let's now write a test page to test our receiving page.

Receiving files with Flash uploader

Now that we have a way of receiving uploaded files, let's create the UI to upload a handful of files. This is a Flash-based UI. You can either download a trial copy of Flash or look at alternatives in a product such as Swish or Flex.

As this is not so much a Flash article, we will skim over this topic quickly. We will create a simple Flash UI that has a browse box, a couple of dynamic text boxes, and some labels. We will build the entire UI on one frame and the UI looks something as shown in the following screenshot:

Creating Media Galleries with ASP.NET 4 Social Networking

Once we have the UI done, we will need to plug the Flash uploader onto a page. To do this, we will need to add the following code on a page that will house the Flash uploader.

//Fisharoo/Web/Photos/AddPhotos.aspx
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/
swflash.cab#version=8,0,0,0"
width="550" height="510" id="FileUpload">
<param name="allowScriptAccess" value="sameDomain" />
<param name="movie" value="../Files/FileUpload.
swf?SiteRoot=<%Response.Write(ctx.RootUrl);%>&AlbumID=<%Response.
Write(ctx.AlbumID); %>&FileType=<%Response.Write(ctx.FolderType);
%>&AccountID=<%Response.Write(ctx.AccountID); %>" />
<param name="quality" value="high" />
<param name="bgcolor" value="white" />
<embed src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="../Files/FileUpload.swf?SiteRoot=<%Response.
Write(ctx.RootUrl);%>&AlbumID=<%Response.Write(ctx.
AlbumID); %>&FileType=<%Response.Write(ctx.FolderType);
%>&AccountID=<%Response.Write(ctx.AccountID); %>"
quality="high" bgcolor="#ffffff" width="550" height="220"
name="FileUpload" align="middle"
allowscriptaccess="sameDomain" type="application/xshockwave-
flash" pluginspage="http://www.macromedia.com/go/
getflashplayer" />
</object>

Once you have the UI showing on our upload page and have the appropriate variables plugged in you should be good to go. Similar to Flash UI, we will also provide for a Silverlight UI, which as mentioned earlier is a control that we have taken from the codeplex site.

Receiving files with Silverlight uploader

The UI when integrated with the application looks as shown in the following screenshot. There are three files currently selected for upload (desert.jpg, jellyfish.jpg and penguins.jpg). We can enable thumbnail previews while the files are being uploaded, but it will slow down the upload process. The code for file upload control (as taken from http://silverlightfileupld.codeplex.com/) can be found in Fisharoo/FileUpload and Fisharoo/SilverlightFileUpload folders.

The code to include the Silverlight XAP package in the UI is:

//Fisharoo/Web/Photos/AddPhotos.aspx
<object data="data:application/x-silverlight-2," type="application/
xsilverlight-2" width="550" height="510">
<param name="source" value="/ClientBin/FileUpload.xap" />
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.50401.0" />
<param name="autoUpgrade" value="true" />
<param name="initParams" value="UploadPage=/Handlers/
FileUpload.ashx?AlbumID=<%Response.Write(ctx.AlbumID);
%>&FileTypeID=<%Response.Write(ctx.FolderType);
%>&AccountID=<%Response.Write(ctx.AccountID); %>,Filter=Images
(*.jpg)|*.jpg" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156
&v=4.0.50401.0" style="text-decoration: none">
<img src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="http://go.microsoft.com/fwlink/?LinkId=161376"
alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object>

Most of this is the standard code that is used in any Silverlight application while including a Silverlight control in the application. The only main point of interest is the initParams parameter, to which we pass the value of the httpHandler that will help with the file upload. The code for this handler can be found in Fisharoo/Web/Handlers/Fileupload.ashx.cs file. Usually httpHandlers need to be registered in the web.config file, however since we have the handler as an .ashx file, it is managed automatically by the ASP.NET runtime and we don't need to explicitly register it.

This handler hands over the processing to FileProcessor class (Web/HelperClasses/FileProcessor.cs) that manages the GUID creation, creation of the various sized images, and appropriate entries in the backend. The code here is similar to that already discussed in earlier section: Multiple file upload.

Now that we are uploading files, let's move on to discussing the display of those photos.

Photo albums

There are many things we can do now as we have the files uploaded to the server in various sizes! We are going to show you the MyPhotos page, which will list all the galleries for the logged in account and link to the ViewAlbum page. This should demonstrate how to work with the photo albums and the photos. You can look at other pages that are in the code to see how to create albums, link to the file uploader, and so on—basically all the data management tasks that surround the photo album concepts.

MyPhotos

In this page we will display all the photo albums that an account has. We will use a ListView to do this.

//Fisharoo/Web/Photos/MyPhotos.aspx
<asp:ListView id="lvAlbums" runat="server"
OnItemDataBound="lbAlbums_ItemDataBound">
<LayoutTemplate>
<ul class="albumsList">
<asp:PlaceHolder ID="itemPlaceholder"
runat="server"></asp:PlaceHolder>
</ul>
</LayoutTemplate>

<ItemTemplate>
<li>
<asp:HyperLink CssClass="albumsActionLink"
ID="linkEditAlbum" NavigateUrl="
~/Photos/EditAlbum.aspx" Text="Edit"
runat="server"></asp:HyperLink>
<asp:HyperLink CssClass="albumsActionLink"
ID="linkViewAlbum" NavigateUrl=
"~/Photos/ViewAlbum.aspx" Text="View"
runat="server"></asp:HyperLink>
<asp:LinkButton CssClass="albumsActionLink"
ID="linkDeleteAlbum" Text="Delete"
OnClick="linkDeleteAlbum_Click"
runat="server"></asp:LinkButton><br />
<asp:Label CssClass="albumsTitle" ID="lblName"
Text='<%#((PEFolder)Container.DataItem).Name%>'
runat="server"></asp:Label><br />
<img src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="<%#_webContext.RootUrl
%>files/photos/<%#((PEFolder)
Container.DataItem).FullPathToCoverImage %>" /><br />
<asp:Label CssClass="albumsLocation" Text="in - "
runat="server"></asp:Label>
<asp:Label CssClass="albumsLocation" ID="lblLocation"
Text='<%#((PEFolder)Container.DataItem).Location%>'
runat="server"></asp:Label><br />
<asp:Label CssClass="albumsDescription"
ID="lblDescription" Text='<%#
((PEFolder)Container.DataItem).Description%>'
runat="server"></asp:Label>
<asp:Literal Visible="false" ID="litFolderID"
Text='<%#((PEFolder)Container.DataItem).
FolderID.ToString() %>'
runat="server"></asp:Literal>
</li>
</ItemTemplate>
<EmptyDataTemplate>
Sorry, you don't seem to have any albums at this time!
</EmptyDataTemplate>
</asp:ListView>

Note that the ListView has an OnItemDataBound="lbAlbums_ItemDataBound" event hooked up. This will become important later on as it controls how we handle each item that is bound to the ListView. Also note the use of PEFolder class, which is a presentation entity and is bound to the various controls inside the ListView template.

The first item you will see inside of the ListView is the LayoutTemplate. This template defines the iterating items. In our case we have chosen to use an unordered list to display our albums. With some CSS we have full control over how each list item is rendered.

Inside the list you will see that we have a PlaceHolder defined with an ID of itemPlaceholder. You must use this ID as you see it here! This PlaceHolder is responsible for holding everything in the ItemTemplate as we do our iteration.

The next section you will see is the ItemTemplate. This template actually defines what goes into each item of our list. In our case we have some metadata about each album, the default image for the album, and some link to other functionalities.

Finally we have the EmptyDataTemplate. This template is responsible for showing something when there is no data to iterate through. In our case we are displaying a message stating that there are no albums to be displayed.

We can now turn to the code behind this page (keep in mind that we are still using the MVP pattern). Most of our code behind is driven by our presenter file. In our code behind we have the LoadUI method, which is passed a list of Folder (Albums). This is the key DataSource for our ListView control.

//Fisharoo/Web/Photos/MyPhotos.aspx.cs
public void LoadUI(List<PEFolder> folders)
{
if (!IsPostBack)
{
lvAlbums.DataSource = folders;
lvAlbums.DataBind();
}
}

Next we have the lbAlbums_ItemDataBound. This is the method that we were hooked up to in the ListView to handle each item as it is bound to our ListView. In this method we are referencing the controls in our ItemTemplate so that we can work with them. We make sure that the Album's description is not too long. If it is, we concatenate it to fit our current display. Then we are constructing our links so that they work as expected for each Album that we are binding.

//Fisharoo/Web/Photos/MyPhotos.aspx.cs
protected void lbAlbums_ItemDataBound(object sender,
ListViewItemEventArgs e)
{
if (e.Item.ItemType == ListViewItemType.DataItem)
{
HyperLink linkEditAlbum = e.Item.FindControl("linkEditAlbum")
as HyperLink;
LinkButton linkDeleteAlbum =
e.Item.FindControl("linkDeleteAlbum") as LinkButton;
HyperLink linkViewAlbum = e.Item.FindControl("linkViewAlbum")
as HyperLink;
Literal litFolderID = e.Item.FindControl("litFolderID") as
Literal;
Label lblDescription = e.Item.FindControl("lblDescription")
as Label;
if (lblDescription.Text.Length > 150)
{
lblDescription.Text = lblDescription.Text.Substring(0, 149);
lblDescription.Text += "...";
}
linkEditAlbum.NavigateUrl += "?AlbumID=" + litFolderID.Text;
linkDeleteAlbum.Attributes.Add("OnClick","javascript:return(co
nfirm('Are you sure you want to delete this album?'));");
linkDeleteAlbum.Attributes.Add("FolderID",litFolderID.Text);
linkViewAlbum.NavigateUrl += "?AlbumID=" + litFolderID.Text;
}
}

And finally, we have a method to handle our delete link's click event. This method calls into our presenter to handle the deletion of an Album.

//Fisharoo/Web/Photos/MyPhotos.aspx.cs
protected void linkDeleteAlbum_Click(object sender, EventArgs e)
{
LinkButton linkDeleteAlbum = sender as LinkButton;
_presenter.DeleteFolder(Convert.ToInt64(linkDeleteAlbum.Attributes
["FolderID"]));
}

ViewAlbum

The ViewAlbum page is exactly the same as the MyPhotos page in that it uses the ListView to handle its rendering of the data. The only difference is that we use PEFile instead of PEFolder.

In the code behind we have a similar layout as the MyPhotos code behind. We load the photos for the given album. We also load some details about the album itself so that we can show things like the album's description and the like. There are a few button controls that we have our events hooked to so that we can handle things like navigation via the redirector class. We are showing only the main parts of the code below:

//Fisharoo/Web/Photos/ViewAlbum.aspx.cs
protected void lvAlbum_ItemDataBound(object sender,
ListViewItemEventArgs e)
{
if(e.Item.ItemType == ListViewItemType.DataItem)
{
HyperLink linkImage = e.Item.FindControl("linkImage") as
HyperLink;
Literal litImageName = e.Item.FindControl("litImageName") as
Literal;
Literal litFileExtension =
e.Item.FindControl("litFileExtension") as Literal;
string pathToImage = "~/files/photos/" +
linkImage.NavigateUrl + "/" + litImageName.Text;
linkImage.NavigateUrl = pathToImage + "__o." +
litFileExtension.Text;
linkImage.ImageUrl = pathToImage + "__s." +
litFileExtension.Text;
}
if(e.Item.ItemType == ListViewItemType.EmptyItem)
{
HyperLink linkAddPhotos = e.Item.FindControl("linkAddPhotos")
as HyperLink;
linkAddPhotos.NavigateUrl =
"~/photos/AddPhotos.aspx?AlbumID=" +
_webContext.AlbumID.ToString();
}
}

public void LoadAlbumDetails(Folder folder)
{
lblAlbumName.Text = folder.Name;
lblLocation.Text = folder.Location;
lblDescription.Text = folder.Description;
lblCreateDate.Text = folder.CreateDate.ToString();
if(folder.AccountID != _userSession.CurrentUser.AccountID)
{
btnEditPhotos.Visible = false;
btnEditAlbum.Visible = false;
btnAddPhotos.Visible = false;
}
}

public void LoadPhotos(List<PEFile> files)
{
lvGallery.DataSource = files;
lvGallery.DataBind();
}

Take a look in the photos folder of the website project. There are many other pages for editing photos, editing albums, and so on that demonstrate additional functionality with regards to interacting with our files and photo album data.

Summary

In this article we looked at the infrastructure and decisions that go into a media management application. We focused heavily on processing and storing images. But most of the principles that we looked at apply to all sorts of different file types. The only real part that would need to be tweaked is how the file is processed once it is stored on the server. In the case of audio and video files you would most likely want to transfer them to a Flash format from a WAV or WMV format so that they become more accessible to your web users or you can use Silverlight along with IIS Smooth Streaming options to stream WMV files. One aspect to keep in mind will be the content growth and space management along with it. You may want to look up some content distribution network.

This article has added the ability to create photo albums. We then discussed the ability to upload and manipulate photos. We also discussed how to handle multiple file uploads and the most appropriate way to store them on the file system. Finally, we created a way for our users to interact with their albums and uploaded files.


Further resources on this subject:


About the Author :


Andrew Siemer

Andrew Siemer is currently the enterprise architect at OTX Research. He has worked as a software engineer, enterprise architect, trainer, and author since 1998 when he got out of the Army. Andrew has consulted with many companies on the topics of e-commerce, social networking, and business systems. To name a few, he has worked with eUniverse (AllYouCanInk.com), PointVantage (MyInks.com), Callaway Golf (CallawayConnect.com), Guidance Software (GuidanceSoftware.com), and Intermix Media (Grab.com, AmericanIdol.com, FoxSports.com, FlowGo.com). In addition to his daily duties he also offers various classes in .NET, C#, and other web technologies to local students in his area as well as blogging in his *free* time.

Atul Gupta

Atul Gupta is Principal Technology Architect at the Microsoft Technology Center, Infosys Technologies Limited. He has over 15 years of experience working on Microsoft technologies. His expertise spans User Interface technologies and he has worked extensively withASP.NET. His current focus is on Windows Presentation Foundation (WPF), Silverlight and other technologies like Surface, Pivot, Windows Touch and rich data visualization.

In his career spanning over 15 years, Atul has also worked on COM, DCOM, C, C++ before getting onto .NET in year 2001. He has worked on all .NET version and alongside worked on Server products like Commerce Server and BizTalk Server. He has authored papers and handbooks that are available at Infosys’ Technology Showcase (http://www.infosys.com/microsoft/resour ... wcase.aspx). He also blogs along with other Infosys colleagues at http://blogs.infosys.com/microsoft.

His involved in community activities like forums, speaking session as part of Virtual TechDays and other such events got him the Microsoft Most Valuable Professional Award for 6 years in a row.

Sudhanshu Hate

Sudhanshu Hate is Senior Technology Architect with Microsoft Technology Center (MTC), Infosys Technologies Limited. He has over 12 years of industry experience with last seven years on Microsoft .NET

Currently his technology area of interestare .NET 4core framework and server side technologies such as WCF 4, WF 4, Entity Framework 4, WCF Data Services and App Fabric to name a few.

Sudhanshu has authored papers that are available at Infosys’ Technology Showcase (http://www.infosys.com/microsoft/resour ... wcase.aspx), presented in external forums such as Microsoft Virtual TechDays, 3rdIndiaSoftware Engineering Conference (ISEC 2010) and blogs at http://blogs.infosys.com/microsoft

Prior to this, Sudhanshu led Business Intelligence and Legacy Modernization solution development initiatives, and consulted to fortune 500 customers in EMEA and US across .NET, J2EE, ORACLE and Delphi technologies.

Books From Packt


ASP.NET MVC 2 Cookbook
ASP.NET MVC 2 Cookbook

ASP.NET Site Performance Secrets
ASP.NET Site Performance Secrets

Microsoft Windows Workflow Foundation 4.0 Cookbook
Microsoft Windows Workflow Foundation 4.0 Cookbook

.NET Compact Framework 3.5 Data Driven Applications
.NET Compact Framework 3.5 Data Driven Applications

Elgg 1.8 Social Networking
Elgg 1.8 Social Networking

Pligg 1.1 Social Networking
Pligg 1.1 Social Networking

PHP 5 Social Networking
PHP 5 Social Networking

ASP.NET jQuery Cookbook
ASP.NET jQuery Cookbook


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
2
B
T
C
D
3
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software