|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionGallery Server Pro is a powerful and easy-to-use ASP.NET web application that lets you share and manage photos, video, audio, and other files over the web.
You can play with an online demo of Gallery Server Pro to get a sense of its capabilities. A pre-compiled version is available here along with additional documentation and a support forum. BackgroundThis project started in 2002 from my desire to share my photos over the web. I wanted my photos to remain on my own server, not somebody else's like EasyShare's or Shutterfly's. Since there weren't any free solutions to choose from at the time, I wrote my own. It turned out so well that I released version 1 to the world in January 2006, and it was downloaded over 30,000 times. I spent most of 2006 and 2007 working on version 2, rewriting the code from the ground up to use the new features of .NET 2.0. This includes ASP.NET Membership, Roles, Profiles, generics, the data provider model, and several well known design patterns (strategy, iterator, factory, template method, and composite). In this article I present the overall architecture and major features of Gallery Server Pro. The topics presented here can help if you want to learn more about:
Using Gallery Server ProGallery Server Pro is a fully functional and stable web application ready for production use.
Gallery Server Pro stores media objects such as photos, video, audio, and documents in albums. These files and albums are stored in a directory named mediaobjects within the web application. (This can be changed to any location on the web server.) An album is really just a directory, so an album named Vacation Photos is stored as a similarly named directory. There are two main techniques for adding media objects:
When adding a media object, the following steps occur:
Media objects are streamed to the browser through an HTTP handler. Below you can see a photo and a video being displayed. If watermarking is enabled, the watermark is applied to the in-memory version of the image just before it is sent.
If you click the View metadata toolbar item above a media object, a popup DIV window displays the image's metadata, as shown below.
By default, everyone can browse the media objects. However, you must log on to perform any action that modifies an album or media object. Authorization to modify data is configured by type of permission and the albums to which it applies. For example, you can set up user Soren to have edit permission to the album Soren's photos. Another user Margaret is given edit permission to the album Margaret's photos. Each user can administer his or her own album but cannot edit albums outside his or her domain. To learn more about how to use Gallery Server Pro from an end-user perspective, read the Administrator's Guide. Otherwise, read on to learn about the architecture and programming techniques. Solution ArchitectureThe Visual Studio solution in the download contains ten projects. They are:
User Management and SecurityUser accounts are managed through the ASP.NET Membership, Roles, and Profile APIs. By default, Gallery Server Pro is configured to use Media Object, Albums and the Composite PatternRecall that each media object (photo, video, etc) is stored in an album. Albums can be nested within other albums, with no restriction on the number of levels. This is similar to how files and directories are stored on a hard drive. It turns out that albums and media objects have a lot in common. They both have properties such as
The public string Title
{
get
{
VerifyObjectIsInflated(this._title);
return this._title;
}
set
{
value = ValidateTitle(value);
this._hasChanges = (this._title == value ? _hasChanges : true);
this._title = value;
}
}
Now that the common functionality is defined in the abstract base class, I can create concrete classes to represent albums, images, video, audio, and other types of media objects:
With this approach there is very little duplicate code, the structure is maintainable, and it is easy to work with. For example, when Gallery Server Pro wants to display the title and thumbnail image for all the objects in an album, there might be any combination of child albums, images, video, audio, and other documents. But I don't need to worry about all the different classes or about casting problems. All I need is the following code: // Assume we are loading an album with ID=42
IAlbum album = Factory.LoadAlbumInstance(42, true);
foreach (IGalleryObject galleryObject in album.GetChildGalleryObjects())
{
string title = galleryObject.Title;
string thumbnailPath = galleryObject.Thumbnail.FileNamePhysicalPath;
}
Beautiful, isn't it? But what happens when the functionality is slightly different between two types of objects? For example, Gallery Server Pro needs to enforce a maximum length of 200 characters for an album title and 1000 characters for the title of a media object (image, video, etc). Both types of objects need a Not at all! Refer back to the property definition for protected virtual string ValidateTitle(string title)
{
// Validate that the title is less than the maximum limit.
// Truncate it if necessary.
int maxLength =
GalleryServerPro.Configuration.ConfigManager.
GetGalleryServerProConfigSection().DataStore.MediaObjectTitleLength;
if (title.Length > maxLength)
{
title = title.Substring(0, maxLength).Trim();
}
return title;
}
The procedure is defined as virtual, allowing a derived class to override it if needed. In fact, that is exactly what the protected override string ValidateTitle(string title)
{
int maxLength =
GalleryServerPro.Configuration.ConfigManager.
GetGalleryServerProConfigSection().DataStore.AlbumTitleLength;
if (title.Length > maxLength)
{
title = title.Substring(0, maxLength).Trim();
}
return title;
}
The end result is that there is a base implementation in the base class that provides functionality for most cases, and code that is unique to albums is contained in the Using the Strategy Pattern for Persisting to the Data StoreWe just saw how to override a method in the base class when we need to alter its behavior. I could have done something similar when it comes to saving the albums and media objects to the database. The I could eliminate the problem of duplicate code by providing a default implementation in the You might argue that I violated this rule when I provided a default implementation of the Getting back to our challenge of persisting data to the data store, the approach I came up with was to use the "strategy" pattern to encapsulate behavior. First, I defined an interface public interface ISaveBehavior { void Save(); }
Then I wrote two classes that implemented the interface: public void Save()
{
if (this._albumObject.IsVirtualAlbum)
return; // Don't save virtual albums.
// Save to disk.
PersistToFileSystemStore(this._albumObject);
// Save to the data store.
GalleryServerPro.Provider.DataProviderManager.Provider.Album_Save
(this._albumObject);
}
Notice that there is a call to OK, we have two classes for saving data to the data store — one for albums and one for media objects. How do we invoke the appropriate Recall that the this.SaveBehavior = Factory.GetAlbumSaveBehavior(this);
The public static ISaveBehavior GetAlbumSaveBehavior(IAlbum albumObject)
{
return new AlbumSaveBehavior(albumObject);
}
The The This is an example of using the strategy pattern. Specifically, the strategy pattern is defined as a family of algorithms that are encapsulated and interchangeable. In our case, we have two save behaviors that are self-contained and can both be assigned to the same property (interchangeable). It is a powerful pattern and has many uses. Rendering HTML for Video, Images, Audio and MoreDisplaying an image in a web browser is easy because all browsers recognize the The solution was to use a combination of HTML templates stored in a configuration file and automatic browser sniffing provided by ASP.NET. The configuration file galleryserverpro.config (stored in the config directory) contains HTML templates for each major type of media object. For example, rendering the HTML for an image is pretty straightforward. Here is the relevant entry in galleryserverpro.config: <galleryObject>
<mediaObjects>
<mediaObject mimeType="image/*">
<browsers>
<browser id="default" htmlOutput="<div class="op1">
<div class="op2"><div class="sb">
<div class="ib"><img id="mo_img"
src="{MediaObjectUrl}" class="{CssClass}"
alt="{TitleNoHtml}" title="{TitleNoHtml}"
style="height:{Height}px;width:{Width}px;" />
</div></div></div></div>" />
The <div class="op1">
<div class="op2">
<div class="sb">
<div class="ib">
<img id="mo_img" src="{MediaObjectUrl}"
class="{CssClass}" alt="{TitleNoHtml}"
title="{TitleNoHtml}" style="height:{Height}px;width:{Width}px;" />
</div>
</div>
</div>
</div>
Note: In the configuration file the <, >, and " characters within the The four When Gallery Server Pro uses the above template to render an image to the browser, it ends up looking something like this: <div class="op1">
<div class="op2">
<div class="sb">
<div class="ib">
<img id="mo_img"
src="http://www.codeproject.com/gs/handler/getmediaobject.ashx?
moid=2064&aid=169&mo=D%3A%5Cgs%5Cmediaobjects%
5CzThumb_100_1768.jpeg&mtc=1&dt=1" alt="Grand Canyon"
style="width:86px;height:115px;" />
</div>
</div>
</div>
</div>
If desired, one can tweak this template to change how <browser id="default"
htmlOutput="<div class="op1"><div class="op2">
<div class="sb"><div class="ib">
<img id="mo_img" src="{MediaObjectUrl}"
class="{CssClass}" alt="{TitleNoHtml}"
title="{TitleNoHtml}" width="{Width}" height="{Height}" />
</div></div></div></div>" />
Image rendering is pretty straight forward, and the technique presented here would be an overkill if all we were doing was displaying images. But it becomes worthwhile once we get into the more complicated media types. For example, let's look at the relevant section of galleryserverpro.config that describes how video is rendered: <mediaObject mimeType="video/*">
<browsers>
<browser id="default"
htmlOutput="<object type="{MimeType}"
data="{MediaObjectUrl}" style="width:{Width}px;height:{Height}px;" >
<param name="src" value="{MediaObjectUrl}" />
<param name="autostart" value="{AutoStartMediaObjectInt}" />
</object>" />
Notice there are two Take a closer look at the browser entries inside the video/* template. There are two browser elements here – one with These two elements specify one HTML template for the default browser, and another for IE. If you look at the HTML templates closely, you will notice they are identical except that one specifies If, for example, you discovered that the Safari browser wasn't working quite right because of HTML incompatibilities, you could add a browser element with Data Provider ModelOne of the cool new features of ASP.NET 2.0 is the "provider model." In Gallery Server Pro, I used the provider model to define the API for reading and writing data to the data store. This allows one to use any source for data storage as long as a provider is written for it. Gallery Server Pro 2.0 includes The diagram below shows the
To use an alternative data store such as MySQL, Oracle, Microsoft Access, or something else, write a new class that inherits from the public override void Album_Delete(IAlbum album)
{
}
Gallery Server Pro will call this method whenever an Note: Refer to the code in the Once you have implemented all the methods and compiled your code, you are ready to configure Gallery Server Pro to use your provider. Copy the DLL containing your provider into the bin directory of the Gallery Server Pro web application. Update the data provider section of galleryserverpro.config. For example, if your provider is in a class named <dataProvider defaultProvider="OracleDataProvider">
<providers>
<add name="OracleDataProvider"
type="GalleryServerPro.Data.Oracle.OracleDataProvider,
GalleryServerPro.Data.Oracle" />
</providers>
</dataProvider>
Image Metadata ExtractionImage files, most commonly JPGs, can contain metadata such as camera model and shutter speed. In addition, utilities such as Vista's Photo Gallery allow users to add keywords, titles, ratings, and more. Gallery Server Pro can extract this data in any of the following formats: EXIF, XMP, tEXt, IFD, and IPTC. The code to extract metadata is based on the Code Project article A Library to Simplify Access to Image Metadata, which itself was based on the article Photo Properties. I'd like to thank these authors for their hard work. The techniques in these articles are based on parsing the metadata that is accessible through the The introduction of the So now there are two ways to get metadata from an image — the .NET 2.0 way and the .NET 3.0 way. While the .NET 3.0 technique is better, I wanted Gallery Server Pro to work on a system without .NET 3.0 installed. As a result, the metadata is extracted using the following process:
In other words, if the same metadata item is extracted using both techniques, we keep the data from the .NET 3.0 method and discard the .NET 2.0 version. The logic for extracting metadata is hidden behind the business layer class Metadata.MediaObjectMetadataExtractor metadata =
new Metadata.MediaObjectMetadataExtractor(imageFile.FullName);
this.MetadataItems.AddRange(metadata.GetGalleryObjectMetadataItemCollection());
The variable // Assume we are loading an image with ID=27
IGalleryObject image =
GalleryServerPro.Business.Factory.LoadMediaObjectInstance(27);
foreach (IGalleryObjectMetadataItem metadataItem in image.MetadataItems)
{
string name = metadataItem.Description; // e.g. Camera model, Shutter speed
string value = metadataItem.Value; // e.g. F5.7, 1/350 sec
}
SummaryThis has been a brief introduction to the architecture and programming techniques used in Gallery Server Pro. Feel free to download the source code and use the bits to help in your own project. Cheers! Article History2007 Oct 28: Article release. 2008 May 6: Updated to include latest source files and minor content updates.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||