HomeASP.NET Slapping Together a Photo Gallery in ASP.N...
Slapping Together a Photo Gallery in ASP.NET Part II
In the first part of this two-part series, I initiated the task of building my "Photo Gallery" by talking about the Directory() and DirectoryInfo() objects. Today, I'll take this exercise to its logical conclusion by introducing their counterparts, i.e. the File() and FileInfo() objects, and developing an ASP.NET script that uses all of these .NET objects to display my photo collection.
Contributed by Harish Kamath Rating: / 28 April 11, 2005
By now, I'm pretty sure that you've recovered from the dreaded "back-to-work-from-a-vacation" syndrome and are eager to test run the "Photo Gallery" application that I promised to put together in the last article.
Well, not so fast!
First, let me quickly review the topics covered earlier: I began the article with an outline of the folder structure required for my "Photo Gallery" application. Then, I introduced the Directory() and DirectoryInfo() objects, which allow you to traverse folders on the server. But that was only half of the story.
Today, I hope to complete the picture by demonstrating the capabilities of the File() and the FileInfo() objects, followed by the grand finale - the ASP.NET script that implements my simple, no-frills "Photo Gallery" application.
A little note before I proceed: if you are looking for a tutorial on reading files or even creating new ones, I'd suggest that you look elsewhere. To be frank, these topics are vast and deserve to be treated as a separate point of discussion.
Enough said, it's time - as they say - to walk the walk!
Once again, I have to deal with two familiar scenarios:
the specified file either exists on the server...
... or it does not.
I've already stated that the File() object is a counterpart of the Directory() object. So, it goes without saying that it is necessary to "import" the "System.IO" assembly into my ASP.NET script.
The "strPhotoFilePath" string variable is used to store the path to the file that I wish to access. The all-new File() object comes with list of static methods; I've invoked the Exists() method, whose behavior is similar to the Exists() method of the Directory() object. This method returns a "true" value if the file (or directory) exists, and a "false" value otherwise.
While the above example may not necessarily trouble your gray cells, the example in the next section will definitely demonstrate the usefulness of this innocuous File() object.
So, what are you waiting for? Flip the page and get moving!
// custom function to return the list of photographs // a.k.a. image files for a particular file system location void GetPhotos(string strFileSystemPath) {
// define array to store list of "Photos" string[] aryPhotos;
// use the GetFiles() static method // to obtain a list of image i.e. JPEG files aryPhotos = Directory.GetFiles(strFileSystemPath, "*.jpg");
// iterate over array of "Photos" to / get details of each Photo foreach(string strPhotoFilePath in aryPhotos) {
Here is a quick look at the output generated by this script before I get into the mandatory explanations:
Here, I've defined a string variable titled "strPhotoAlbumPath." This stores the path to a selected "Photo Album," i.e. file system folder. Next, I have updated my custom GetPhotos() function - it made a brief appearance in the first article - to display information about each image file that the script encounters in the folder.
Let me take you through the changes: I have defined a string array titled "aryPhotos" to store the locations of the different files. Next, I have invoked the GetFiles() method to initiate the array. A simple "foreach" loop allows me to iterate through the array and to display relevant file information using the following methods:
GetCreationTime() returns the date and time when the file, in question, was created.
GetLastWriteTime(), as the names suggests, it returns the time when the file was last modified.
GetAttributes() returns the attributes in the form of a FileAttribute() enumeration value.
A final note, before I move to the next section: a simple "if-else" loop in the Page_Load() function ensures that the server does not spit a screen full of errors if the specified folder does not exist. This is evident from the output shown below:
In the previous article, I introduced two similar objects to manage folders on the .NET platform - the Directory() and DirectoryInfo() objects. So, it should not come as a surprise to learn that a similar duality exists for managing files on the server.
It's time to say hello to the FileInfo() object, a counterpart of the DirectoryInfo() object, introduced earlier. Take a peek at the next code listing that introduces this new object and a whole lot more:
// custom function to return the list of photographs // a.k.a. image files for a particular file system location void GetPhotos(string strFileSystemPath) {
// define array to store list of "Photos" FileInfo[] objPhotos;
try {
// instantiate a DirectoryInfo() object DirectoryInfo objCurrentAlbum = new DirectoryInfo (strFileSystemPath);
// use the GetFiles() method // to obtain a list of photos i.e. image files objPhotos = objCurrentAlbum.GetFiles("*.jpg");
if(objPhotos.Length != 0) {
// there are photos in the current album // enable the photo display panel pnlPhotos.Visible = true;
foreach(FileInfo objPhoto in objPhotos) {
// check if the photo i.e. image file exists if(objPhoto.Exists) {
// if it does, add a Image() object // to Photo Panel Image objImage = new Image(); objImage.ImageUrl = objPhoto.FullName.Substring (Request.PhysicalApplicationPath.Length) ; objImage.Width = 200; objImage.Height = 145; pchPhotos.Controls.Add(objImage); pchPhotos.Controls.Add(new LiteralControl("<HR width=\"25%\" align=\"left\">"));
} }
} else { error.Text = "Sorry, there are no photographs in the current Photo Album."; }
// path to a particular Photo Album // i.e. folder on the file system string strPhotoAlbumPath = "E:\\inetpub\\wwwroot\\Gallery\\London";
// display name of current Photo Album int intStartPosition = strPhotoAlbumPath.LastIndexOf("\\") + 1; int intLength = strPhotoAlbumPath.Length - intStartPosition; output.Text += "You are currently viewing the <U>" + strPhotoAlbumPath.Substring(intStartPosition, intLength) + "</U> Photo Album.";
// check if the specified Photo Album i.e. folder exists if(Directory.Exists(strPhotoAlbumPath)) {
// get the list of photos // in a particular Photo Album GetPhotos(strPhotoAlbumPath);
Load the above example in your browser to view following output:
For the first time, I've updated the code listing to display the image files rather than just names, sizes and other useless information that I've been doing so far.
While the above example appears to be daunting at first glance, it is my job to de-mystify the complexities. Let’s do that by breaking the code listing into smaller bits. Concentrate on the HTML portion of the ASP.NET script. Here, you'll notice that I have introduced a couple of ASP.NET server controls: first, the "Panel" control allows you to group several .NET server controls together and next, the "Placeholder" control defines a placeholder to add new server controls at run-time.
The Page_Load() function has been excerpted from an example in the previous article. There are two important points to keep in mind. First, I've defined a string variable titled "strPhotoAlbumPath" to store the file system location of a selected "Photo Album." Second, I've obtained the name of the current "Photo Album," i.e. folder, using some deft string manipulation.
Next, I've revamped the ubiquitous GetPhotos() function: here, I've a defined an array titled "aryPhotos" to store a collection of FileInfo() objects, unlike the earlier example, where a similar array stored string values. Next, I've instantiated a DirectoryInfo() object and invoked its GetFiles() method. This returns a collection of FileInfo() objects that I store in the aforementioned "aryPhotos" array. Note that each object in this collection represents a "Photo," i.e. an image file.
In the above example, I've obtained the absolute file system path by using the "FullName" property of the FileInfo() object and the "Request.PhysicalApplicationPath" property. I've calculated the path of every image file, relative to the root folder of the application. This comes in handy when I assign the URL for every Image() object that I've added to my "pchPhotos" placeholder server control.
The "LiteralControl" server control allows me to insert custom text (including HTML code) within the "pchPhotos" placeholder. I've used it to insert horizontal lines after each photograph, as seen in the output.
Note the use of "if-else" and "try-catch" blocks to trap errors if something goes wrong; it is a good practice to expect the unexpected.
In the previous section, I gave you a glimpse of what the final "Photo Gallery" might look like. While I agree that the output will not win kudos on account of its bland user interface, it should have given you a firm grasp of the programming logic that I wish to implement for my "Photo Gallery" application.
But before I proceed further, I would like to refresh your memory by relisting the folder structure introduced in the first part:
It is quite obvious that the "Photo Gallery" application assumes the above folder structure is created under the root folder of the Web server, and that proper file system permissions have been assigned to these folders. Furthermore, this two part series primarily aims to demonstrate the file management capabilities of the .NET framework. So, I wouldn't recommend that you host this application in a live environment without a thorough review of the security risks involved.
Save this script as "gallery.aspx" in the "wwwroot" folder (as displayed in the hierarchy above). Now, load the script - I can access it using "http://localhost/gallery.aspx" on my local server - in order to view the following output:
Click on the one of the "Photo Albums" to view the "Photos" present in the selected album. Take a look:
Here, I would like to point out that it is possible to have nested "Photo Albums" - after all, they are just file system folders containing image files. Take a look at the following screen shot that displays a "Photo Album", which contains "Photos" as well as nested "Photo Albums":
No "Photos" in a particular "Photo Album" - no sweat, this "gallery.aspx" script is programmed to handle such situations, as seen below:
Now that I have given you a quick walk through of the application, it is time to decipher the code that has brought about this transformation. One look at that code listing and I'll bet that you are definitely not looking forward to the next section.
Well - that’s what I am paid for. So, here goes nothing!
I always said that my "Photo Gallery" was going to be a simple, no-frills application. But, I never said that the programming that went into it was going to be a piece of cake.
As usual, let me break up code listing in smaller bits; that should definitely make things easier for both you and me.
First, I have the HTML code:
<HTML>
// snip
<!-- Panel for Photo Albums --> <asp:Panel id="pnlPhotoAlbums" visible="false" runat="server" > <asp:Placeholder id="pchPhotoAlbums" runat="server" /> </asp:Panel>
There's nothing fancy here. I have created two panels; one contains a placeholder for the display of "Photo Albums," and the second one contains a placeholder for the display of "Photos" and one hyperlink control (not visible by default) that will allow the user to navigate "up one level" when browsing the "Photo Gallery."
Now, let me shift the focus to the programming section. At the onset, I've defined two variables:
<%
// snip
string strPhotoGalleryRootRelPath = "/Gallery";
int intItemsInRow = 3;
// snip
%>
The purpose of these variables is clearly documented in the code listing. The "strPhotoGalleryRootRelPath" string variable stores the root folder of the "Photo Gallery," relative to the folder where I have created the "gallery.aspx" script; and the "intItemsInRow" variable defines the number of photos or albums to be listed in a single row on the page.
Next, I have the Page_Load() function:
<% // snip
void Page_Load(Object sender, EventArgs e) {
string strWebPath = "";
// check if relative path is sent in the Query String if(Request.QueryString["strWebPath"] != null) { strWebPath += Request.QueryString["strWebPath"]; } else { // if not, then assign default value strWebPath = strPhotoGalleryRootRelPath; }
// start display of Photo Albums GetPhotoAlbums(strWebPath); }
// snip
%>
There is one little security flaw in all of the previous examples: the entire path (drive letter et. al.) to the file or folder was passed in the query string of the URL. This could serve as an open invitation to hackers to compromise the security of the server.
One alternative solution is to pass a relative path and use the Server.MapPath property to obtain the actual file system path, and that’s exactly what I've done. I've defined a string variable "strWebPath," that attempts to retrieve this relative path from the query string. If this isn't possible, then it defaults to the value stored in the "strPhotoGalleryRootRelPath" variable.
Next, I invoke my custom GetPhotoAlbums() function in order to display Photo Albums (followed by the Photos) in the folder specified in the variable "strWebPath." Time to review the updated GetPhotoAlbums() function.
<%
// snip
// custom function to display Photo Albums void GetPhotoAlbums(string strWebPath) {
// counter to keep track of cells int intCellCounter = 1;
// define array to store collection of Photo Albums DirectoryInfo[] objPhotoAlbums;
// get absolute path from the relative web path string strFSPath = Server.MapPath(strWebPath);
// get the details for current Photo Album i.e. folder DirectoryInfo objCurrentAlbum = new DirectoryInfo(strFSPath);
// use the GetDirectories() method to obtain a list of sub-folders objPhotoAlbums = objCurrentAlbum.GetDirectories();
// if there are Photo Albums i.e. sub-folders... if(objPhotoAlbums.Length != 0) {
// make the "pnlAblum" Panel control visible pnlPhotoAlbums.Visible = true;
// add new Table() to list the Photo Albums, neatly Table objPhotoAlbumTable = new Table(); objPhotoAlbumTable.Width = 640; objPhotoAlbumTable.BorderWidth = 3; objPhotoAlbumTable.CellSpacing = 10; objPhotoAlbumTable.CellPadding = 5; objPhotoAlbumTable.HorizontalAlign = HorizontalAlign.Center;
// create TableRow() and Table Cell() object instances TableRow objPhotoAlbumTableRow = new TableRow(); TableCell objPhotoAlbumTableCell = new TableCell();
// Add a header row along with cell for Photo Album table objPhotoAlbumTableCell.ColumnSpan = intItemsInRow; objPhotoAlbumTableCell.HorizontalAlign = HorizontalAlign.Center; objPhotoAlbumTableCell.Controls.Add(new LiteralControl("<h3>Photo Albums</h3>")); objPhotoAlbumTableRow.Cells.Add(objPhotoAlbumTableCell); objPhotoAlbumTable.Rows.Add(objPhotoAlbumTableRow);
// create new row for Photo Album listings objPhotoAlbumTableRow = new TableRow();
// iterate over collection of DirectoryInfo() objects // to get details of each Photo Album i.e. folder foreach(DirectoryInfo objPhotoAlbum in objPhotoAlbums) {
// three cells added to existing row // create a new TableRow() object if(intCellCounter > intItemsInRow) {
// add the TableRow() object to Table() ... objPhotoAlbumTable.Rows.Add(objPhotoAlbumTableRow);
// ... create a new TableRow() object ... objPhotoAlbumTableRow = new TableRow();
// and reinitialize cell counter intCellCounter = 1;
} else {
// increment cell counter by 1 intCellCounter++; }
// create Hyperlink() object - one for text hyperlink // and another for a image hyperlink HyperLink objAlbumTextLink = new HyperLink(); HyperLink objAlbumImageLink = new HyperLink(); objAlbumTextLink.NavigateUrl = objAlbumImageLink.NavigateUrl = "gallery.aspx?strWebPath=" + strWebPath + "/" + objPhotoAlbum.Name; objAlbumTextLink.Text = objAlbumImageLink.Text = objPhotoAlbum.Name; objAlbumImageLink.ImageUrl = "./folder.gif"; // link to a local image
// create a new TableCell() to display // Photo Album hyperlinks in browser objPhotoAlbumTableCell = new TableCell(); objPhotoAlbumTableCell.Width = 200; objPhotoAlbumTableCell.BorderWidth = 1; objPhotoAlbumTableCell.HorizontalAlign = HorizontalAlign.Center; objPhotoAlbumTableCell.VerticalAlign = VerticalAlign.Middle;
// add TableCell() to TableRow() objPhotoAlbumTableRow.Cells.Add(objPhotoAlbumTableCell);
}
// if cell counter is less than intItemsInRow // then the layout needs to be padded with some empty // table cells if(intCellCounter <= intItemsInRow) {
// add the final row (with or without padded cells objPhotoAlbumTable.Rows.Add(objPhotoAlbumTableRow);
// add the Table() to the Placeholder() pchPhotoAlbums.Controls.Add(objPhotoAlbumTable); }
// do not check for photos for Root folder if(strWebPath != "" && strWebPath != strPhotoGalleryRootRelPath) {
// start display of Photos GetPhotos(strFSPath, strWebPath);
// display "Up One Level" link hypUpOneLevel.NavigateUrl = "gallery.aspx?strWebPath=" + strWebPath.Substring(0, strWebPath.LastIndexOf("/")) ; hypUpOneLevel.Visible = true;
} }
// snip %>
Well, I'll take back my words: what I have above is not an "updated" version, but a totally revamped one!
Frankly, the majority of the code in this "revamped" version deals with the creation of Table(), TableRow() and TableCell() objects and their subsequent integration to render a listing of Photo Albums, as seen in the outputs listed in the previous section.
However, the underlying logic remains the same as before: create a DirectoryInfo() object, get a collection of DirectoryInfo() objects (one for each sub-folder) using the GetDirectories() method, iterate over the array and display information about each sub folder, along with a hyperlink to navigate down the hierarchy.
There is one more point that I would like to bring to your notice, which I've already spoken about earlier: the use of the Server.MapPath function. This allows us to convert the relative path, propagated in the query string, into the absolute file system path that is required by the DirectoryInfo() object.
Finally, I have the GetPhotos() function; this has been overhauled too. But, you will notice a striking similarity between the GetPhotoAlbums() and the GetPhotos() function. See for yourself:
<%
// snip
// custom function to display Photos void GetPhotos(string strFSPath, string strWebPath) {
// counter to keep track of cells int intCellCounter = 1;
// define array to store collection of Photos FileInfo[] objPhotos;
// display name of current Photo Album int intStartPosition = strFSPath.LastIndexOf("\\") + 1; int intLength = strFSPath.Length - intStartPosition;
// make the Photos Panel visible pnlPhotos.Visible = true;
// add new Table() to list the Photos, neatly Table objPhotosTable = new Table(); objPhotosTable.Width = 640; objPhotosTable.BorderWidth = 3; objPhotosTable.CellSpacing = 10; objPhotosTable.CellPadding = 5; objPhotosTable.HorizontalAlign = HorizontalAlign.Center;
// create TableRow() and Table Cell() object instances TableRow objPhotosTableRow = new TableRow(); TableCell objPhotosTableCell = new TableCell();
// Add a header row along with cell for Photos table objPhotosTableCell.ColumnSpan = intItemsInRow; objPhotosTableCell.HorizontalAlign = HorizontalAlign.Center; objPhotosTableCell.Controls.Add(new LiteralControl("<h3>Photos in <U>" + strFSPath.Substring(intStartPosition, intLength) + "</U> Album</h3>")); objPhotosTableRow.Cells.Add(objPhotosTableCell); objPhotosTable.Rows.Add(objPhotosTableRow);
// instantiate DirectoryInfo() object to retrieve // Photos i.e. image files present in Photo Album i.e. folder DirectoryInfo objCurrentAlbum = new DirectoryInfo(strFSPath);
// Retrieve photos i.e. JPEG image files ONLY objPhotos = objCurrentAlbum.GetFiles("*.jpg");
// create new row for Photo Album listings objPhotosTableRow = new TableRow();
// check if there are any Photos i.e. image files in current Photo Album if(objPhotos.Length != 0) {
// if there are Photos i.e. image files foreach(FileInfo objPhoto in objPhotos) {
// three cells added to existing row // create a new TableRow() object if(intCellCounter > intItemsInRow) {
// add the TableRow() object to Table() ... objPhotosTable.Rows.Add(objPhotosTableRow);
// ... create a new TableRow() object ... objPhotosTableRow = new TableRow();
// and reinitialize cell counter intCellCounter = 1;
} else {
// increment cell counter by 1 intCellCounter++; }
// create new Image() object for each Photo Image objImage = new Image(); objImage.ImageUrl = strWebPath + "/" + objPhoto.Name; objImage.Width = 150; objImage.Height = 115;
// create a new TableCell() to display // Photo Album hyperlinks in browser objPhotosTableCell = new TableCell(); objPhotosTableCell.BorderWidth = 1; objPhotosTableCell.Width = 200; objPhotosTableCell.HorizontalAlign = HorizontalAlign.Center; objPhotosTableCell.VerticalAlign = VerticalAlign.Middle;
// add TableCell() to TableRow() objPhotosTableRow.Cells.Add(objPhotosTableCell);
}
} else {
objPhotosTableCell.ColumnSpan = intItemsInRow; objPhotosTableCell.HorizontalAlign = HorizontalAlign.Center; objPhotosTableCell.Controls.Add(new LiteralControl("Sorry, there are no photos in this Photo Album.")); objPhotosTableRow.Cells.Add(objPhotosTableCell); objPhotosTable.Rows.Add(objPhotosTableRow);
}
// if cell counter is less than intItemsInRow // then the layout needs to be padded with some empty // table cells if(intCellCounter <= intItemsInRow) {
// add the final row (with or without padded cells objPhotosTable.Rows.Add(objPhotosTableRow);
// add the Table() to the Placeholder() pchPhotos.Controls.Add(objPhotosTable);
}
// snip
%>
Things are no different, as is evident from the repeated appearance of the Table(), TableRow() and TableCell() objects. As before, the programming logic remains the same: I retrieve a collection of FileInfo() object using the GetFiles() method of the parent DirectoryInfo() object. I instantiate a new TableCell() object for each image file and insert it into the TableRows() and subsequently, the Table() object. Once it has been created in memory, I've added the Table() object to the "pchPhotos" server control, and the Photos are eventually rendered it in the browser.
Whew! That's about it as far as my "Photo Gallery" application is concerned.
The effort: programming 300 odd lines of code. The cost: loads of toil and sweat. The result: a neat little "Photo Gallery" that should bring a smile to every visitor’s face. The profit: priceless.
This brings us to the end of my "Slapping a Photo Gallery Together Using ASP.NET" series. During the course of this two part series, I've attempted to make you familiar with the file management capabilities of the .NET platform, courtesy of the System.IO assembly and its bunch of classes: Directory(), File(), DirectoryInfo() and FileInfo(). Finally, I concluded the article with a simple "Photo Gallery" application developed using various .NET classes to display my digital photograph collection on the Internet, which was the original purpose for authoring this series.
Here are some links that you could check out in your free time if you want more information about what you learned in this article: