Photo Gallery – Data Access / Business Logic

There are lot’s of good reasons to separate applications into layers. One of the best reasons is to allow the application to easily be extended and maintained. Even in this small project, separation is a good. The roadmap for this project involves many different presentation layers. In order to re-use the data access / business logic layer implementation, this code is placed outside the Visual Studio web application project in its own class library project. The hope is that this code can be re-used when we throw away the ASP.NET presentation layer and build up a new one (with WPF for instance).

It is worthwhile summarizing the persistence layer quickly. All the photos are stored in the file system. To keep things simple (and to operate within a constraint imposed by my ISP) no database system (MS SQL server for instance) will be used. The application will store additional information about each photo:

  • Photo Thumbnail – To increase responsiveness of the application, a thumbnail is generated for each photo. These thumbnails are stored in the file system in a separate root folder (configurable) than the main image. I once used a photo gallery application that injected the thumbnails into the same folder as the image. It was very painful to later remove these thumbnails.
  • Photo Metadata – Each photo has the potential to have additional information associated with it. This metadata will be stored in an xml file.

The data access layer and business logic layer for this application are minimal and I have made the architectural decision to combine the two into one layer. In this case the added complexity of separation does not justify the additional overhead.

image

The PhotoInfo class encapsulates reading and writing of photo metadata. The metadata that can be associated with each photo includes:

  • Privacy Options – This is used in conjunction with the user type information (see below) to determine access rights to the photo.
  • Rotation Options – This is used to set the “top” of the photo. The rotating of the photo is done in memory just before it is displayed. So the original is kept in tact.

The LoadPhotoMetadata and SavePhotoMetadata methods are static helpers that handle the serialization operations. The metadata is serialize to an xml file. Not every photo will have an xml metadata file. The constructor is used in this case is used to provide default metadata.

 

[Serializable]
public class PhotoInfo
{
    public enum RotateOptions { None, Ninety, OneEighty, TwoSeventy };
    public enum PrivacyOptions { Public, Family, Private };
    public string Title { get; set; }
    public string Tags { get; set; }
    public RotateOptions Rotation { get; set; }
    public PrivacyOptions Privacy { get; set; }

    public PhotoInfo()
    {
        Title = "";
        Tags = "";
        Rotation = RotateOptions.None;
        Privacy = PrivacyOptions.Family;
    }

    public static bool SavePhotoMetadata(string photoPath, PhotoInfo photoInfo)
    {
        // Initialize return result.
        //
        bool result = false;

        // Create the path for the metadata file.
        //
        string directory = Path.GetDirectoryName(photoPath);
        string metaFile = Path.GetFileNameWithoutExtension(photoPath) + ".xml";
        metaFile = Path.Combine(directory, metaFile);

        // Open up a file stream for saving.
        //
        Stream fileStream = new FileStream(metaFile, FileMode.Create, FileAccess.Write);

        // Serialize out the data.
        //
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(PhotoInfo));
        try
        {
            xmlSerializer.Serialize(fileStream, photoInfo);
            result = true;
        }
        catch (Exception ex)
        {
        }
        finally
        {
            fileStream.Close();
        }

        return result;
    }

    public static PhotoInfo LoadPhotoMetadata(string photoPath)
    {
        // Create the path for the metadata file.
        //
        string directory = Path.GetDirectoryName(photoPath);
        string metaFile = Path.GetFileNameWithoutExtension(photoPath) + ".xml";
        metaFile = Path.Combine(directory, metaFile);

        // Validate the existence of the data file.
        //
        if (!File.Exists(metaFile))
        {
            // File is not there...create a new database.
            //
            return new PhotoInfo();
        }

        // Open a stream for reading.
        //
        Stream fileStream = new FileStream(metaFile, FileMode.Open, FileAccess.Read);

        // Deserialize the data.
        //
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(PhotoInfo));
        PhotoInfo photoInfo = (PhotoInfo)xmlSerializer.Deserialize(fileStream);

        // Close the stream.
        //
        fileStream.Close();

        return photoInfo;
    }

    #endregion
}

image

The GalleryObject class contains the information for each “object” in the gallery. The GalleryObjectTypeOptions enumeration indicates if this object is either an image or a folder.

I like providing named collection classes for objects that will generally be used in a collection. This provides a “typed” object and a cleaner development API for those utilizing the objects.

public class GalleryObject
{
    public enum GalleryObjectTypeOptions { Directory, Image };
    public GalleryObjectTypeOptions GalleryObjectType { get; set; }
    public string FullPath { get; set; }
    public PhotoInfo Info { get; set; }
}

public class GalleryObjectCollection : List<GalleryObject>
{

}

image

The main interface to the photo gallery data is provided by the GalleryManager class. All the methods & fields of this class are static.

The two fields of this class define the dimensions of the thumbnail image. After some trail and error I settled in on 130×97. Two opposing forces were at work here. I wanted the size big enough to allow many details in the thumbnail. This will hopefully prevent the need for every image to be viewed at full resolution. I also wanted my vision for what should be on the screen to fit on one page.

The GetObject methods provide various overloads to instantiate a GalleryObject. All the overloads are eventually call the method that takes the image path and gallery object type (image or directory) as parameters. This method creates a GalleryObject and loads the PhotoInfo metadata.

public static GalleryObject GetObject(string imageFullPath)
{
    // Validate existance.
    //
    if (!File.Exists(imageFullPath))
    {
        return null;
    }

    return GetObject(imageFullPath, GalleryObject.GalleryObjectTypeOptions.Image);
}

public static GalleryObject GetObject(FileInfo file)
{
    return GetObject(file.FullName, GalleryObject.GalleryObjectTypeOptions.Image);
}

public static GalleryObject GetObject(DirectoryInfo directory)
{
    return GetObject(directory.FullName, GalleryObject.GalleryObjectTypeOptions.Directory);
}

public static GalleryObject GetObject(string fullPath, GalleryObject.GalleryObjectTypeOptions type)
{
    // Create a gallery object.
    //
    GalleryObject go = new GalleryObject();
    go.GalleryObjectType = type;
    go.FullPath = Path.GetFullPath(fullPath);

    // Load the photo metadata.
    //
    go.Info = PhotoInfo.LoadPhotoMetadata(go.FullPath);

    // Ensure the photo has a title. Photos do not
    //    require metadata. For those photos, just
    //    use the file name.
    //
    if (string.IsNullOrEmpty(go.Info.Title))
    {
        string[] parts = go.FullPath.Split(Path.DirectorySeparatorChar);
        go.Info.Title = parts[parts.Length - 1];
    }

    return go;
}

public static GalleryObjectCollection GetAllObjects(string folderName)
{
    // Instantiate the return object.
    //
    GalleryObjectCollection results = new GalleryObjectCollection();

    // Validate the folder exists.
    //
    DirectoryInfo di = new DirectoryInfo(folderName);
    if (!di.Exists)
    {
        return results;
    }

    // Enumerate the child folders.
    //
    DirectoryInfo[] subFolders = di.GetDirectories();
    foreach (DirectoryInfo subDi in subFolders)
    {
        // Don't inlcude system folders.
        //
        if (subDi.Attributes != FileAttributes.System)
        {
            // Only include folders that have images.
            //
            if (subDi.GetFiles("*.jpg", SearchOption.AllDirectories).Length != 0)
            {
                // Get the gallery object and add it to the list.
                //
                GalleryObject go = GetObject(subDi);
                results.Add(go);
            }
        }
    }

    // Enumerate the files.
    //
    FileInfo[] files = di.GetFiles();
    foreach (FileInfo fi in files)
    {
        // Only show jpg images.
        //
        if (fi.Extension.IndexOf("jpg", 0, StringComparison.InvariantCultureIgnoreCase) != -1)
        {
            // Get the gallery object and add it to the list.
            //
            GalleryObject go = GetObject(fi);
            results.Add(go);
        }
    }

    return results;
}

The GetAllObjects method takes a directory path as a parameter. It enumerates all the directories and files in that folder. It uses the overloaded GetObject methods to create a GalleryObject for each of these that contain images. This method is used to populate the film strip in the presentation layer.

All GalleryObjects (both images and directories) have a thumbnail image. There are three helpers methods to convert either a directory / image path or a GalleryObject into a path to a thumbnail image. If the thumbnail does not exist, then one is created. Notice the methods take that paths to the photo root folder and the thumbnail root folder. This allows that type of configuration data to be determined outside of this implementation.

public static string GetThumbNailImageFullPath(GalleryObject obj, string thumbRootFolder, string photoRootFolder)
{
    if (obj.GalleryObjectType == GalleryObject.GalleryObjectTypeOptions.Directory)
    {
        return GetFolderImageFullPath(obj.FullPath, thumbRootFolder, photoRootFolder);
    }
    else
    {
        return GetThumbNailImageFullPath(obj.FullPath, thumbRootFolder, photoRootFolder);
    }
}

public static string GetThumbNailImageFullPath(string imageFullPath, string thumbRootFolder, string photoRootFolder)
{
    // Use the full image path to generate the path
    //    to the thumbnail.
    //
    string thumbNailFile = imageFullPath.Replace(photoRootFolder, thumbRootFolder);

    // If the thumbnail does not exist...
    //
    if (!File.Exists(thumbNailFile))
    {
        // Create it and return it.
        //
        if (CreateThumbNail(imageFullPath, thumbNailFile))
        {
            return thumbNailFile;
        }
        else
        {
            return null;
        }
    }
    else
    {
        // Return the one that exits.
        //
        return thumbNailFile;
    }
}

public static string GetFolderImageFullPath(string folderFullPath, string thumbRootFolder, string photoRootFolder)
{
    // Use the full image path to generate the path
    //    to the folder image (thumbnail).
    //
    string thumbNailFile = folderFullPath.Replace(photoRootFolder, thumbRootFolder);
    thumbNailFile = Path.Combine(thumbNailFile, "folderthumb");

    // If the thumbnail does not exist...
    //
    if (!File.Exists(thumbNailFile))
    {
        // Create it and return it.
        //
        if (CreateFolderImage(folderFullPath, thumbNailFile))
        {
            return thumbNailFile;
        }
        else
        {
            return null;
        }
    }
    else
    {
        return thumbNailFile;
    }
}

Earlier I stated that the dimensions of my chosen thumbnail size are 130×97. In other words the resulting thumbnail image is always landscape regardless of the initial image layout. To convert a landscape initial image to a landscape thumbnail presented no real issues. However, converting a portrait initial image to a landscape thumbnail required a bit of code. This code keeps the important content of the initial portrait image in the landscape thumbnail. Since most people tend to center the content in their images, the thumbnail creation code takes the center portion of portrait images when converting them to landscape.

private static bool CreateThumbNail(string imageFile, string thumbNailFile)
{
    // Create the new image.
    //
    Image thumbNail = CreateThumbNail(imageFile);

    // Try to save this file.
    //
    try
    {
        thumbNail.Save(thumbNailFile, System.Drawing.Imaging.ImageFormat.Jpeg);
    }
    catch (Exception ex)
    {
        return false;
    }

    return true;
}

private static Image CreateThumbNail(string imageFile)
{
    // Load the full size image.
    //
    System.Drawing.Image fullSizeImg;
    try
    {
        fullSizeImg = new System.Drawing.Bitmap(imageFile);
    }
    catch (Exception ex)
    {
        // If we could not load the full size image then exit early
        //
        return null;
    }

    // Create a blank thumbnail image.
    //
    Image thumbNailImg = new Bitmap(fullSizeImg, ThumbNailWidth, ThumbNailHeight);

    // Get the GDI drawing surface from the thumbnail image.
    //
    Graphics g = Graphics.FromImage(thumbNailImg);

    // Initialize the drawing surface.
    //
    g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

    // Fill the image with white.
    //
    g.FillRectangle(Brushes.White, 0, 0, ThumbNailWidth, ThumbNailHeight);

    // Create a rectanble the size of the new thumbnail
    //
    Rectangle destination = new Rectangle(0, 0, ThumbNailWidth, ThumbNailHeight);

    // Determine the width and height of the original image that we want
    //    to use to make the thumbnail. Don't take the whole thing. Take the
    //    full width of the original, but only take the height of the original
    //    that allows us to keep the aspect ratio we want.
    //
    int imageWidth = fullSizeImg.Width;
    double widthScale = (double)imageWidth / (double)ThumbNailWidth;
    int imageHeight = (int)((double)ThumbNailHeight * widthScale);

    // Create a rectangle (think sliding view into the original image). The width
    //    will line up with the original. Slide the height to the middle of the image.
    //    Sliding the height to the middle is generally a good choice because most
    //    people will center their content in the image.
    //
    Rectangle source = new Rectangle(0, (fullSizeImg.Height - imageHeight) / 2, imageWidth, imageHeight);

    // Draw the selected portion of the original onto the thumbnail surface.
    //    This will scale the image.
    //
    g.DrawImage(fullSizeImg, destination, source, GraphicsUnit.Pixel);

    // Clean up gdi resources.
    //
    g.Dispose();

    return thumbNailImg;
}

In preparing for this project, I examined a number of other photo galleries. Mostly to see what other people are doing and to accumulate ideas for requirements. I found the presentation of directories from the ASP.NET Photo Handler by Betrand LeRoy very visually pleasing. Each directory’s thumbnail consisted of a “stack” of images contained within the directory. The presentation provides a very intuitive user experience. The licensing on that application allows derivative works, but credit to where credit is due.

private static bool CreateFolderImage(string folderName, string folderImageFile)
{
    // If the folder image does not exist, then
    //    the thumbnail folder for this folder does not
    //    exist.
    //
    string folder = Path.GetDirectoryName(folderImageFile);
    if (!Directory.Exists(folder))
    {
        Directory.CreateDirectory(folder);
    }

    // Create the new image.
    //
    Image thumbNail = CreateFolderImage( false, folderName);

    // Try to save this file.
    //
    try
    {
        thumbNail.Save(folderImageFile, System.Drawing.Imaging.ImageFormat.Png);
    }
    catch (Exception ex)
    {
        return false;
    }

    return true;
}

private static Image CreateFolderImage(bool isParentFolder, string folder)
{
    // Set the return image size.
    //
    int size = ThumbNailWidth;

    // Create the return image.
    //
    Image newImage = new Bitmap(size, size, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

    // Get the graphics surface and initialize.
    //
    Graphics g = Graphics.FromImage(newImage);
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.SmoothingMode = SmoothingMode.HighQuality;
    g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;

    // Fill the image with white but fully transparent.
    //
    g.Clear(Color.Transparent);

    // Create a color matrix
    //
    ColorMatrix cm = new ColorMatrix();
    cm.Matrix00 = 1;
    cm.Matrix11 = 1;
    cm.Matrix22 = 1;
    cm.Matrix33 = 0.5f;

    // Generate a list of images to draw.
    //
    int numPicturesInStack = 3;
    List<string> imagesToDraw = new List<string>();

    // Look for default images first.
    //
    string[] images = Directory.GetFiles(folder, "*default.jpg", SearchOption.AllDirectories);
    int numToUse = Math.Min(numPicturesInStack, images.Length);
    for (int i = 0; i < numToUse; i++)
    {
        imagesToDraw.Insert(0, images[i]);
    }

    // If not enough default images to fill the stack...
    //
    if (imagesToDraw.Count < numPicturesInStack)
    {
        // Use any image to fill the stack.
        //
        images = Directory.GetFiles(folder, "*.jpg", SearchOption.AllDirectories);
        numToUse = Math.Min(numPicturesInStack - imagesToDraw.Count, images.Length);
        for (int i = 0; i < numToUse; i++)
        {
            imagesToDraw.Insert(0, images[i]);
        }
    }

    // Initialize some parameters that will be used
    //    in the main drawing loop.
    //
    int drawXOffset = size / 2;
    int drawYOffset = size / 2;
    double angleAmplitude = Math.PI / 10;
    int imageFolderSize = (int)(size / (Math.Cos(angleAmplitude) + Math.Sin(angleAmplitude)));
    Color folderBorderColor = Color.FromArgb(90, Color.Black);
    float folderBorderWidth = 2;
    Random rnd = new Random();

    // Draw each image in the stack.
    //
    foreach (string folderImagePath in imagesToDraw)
    {
        // Read the original image.
        //
        Bitmap folderImage = new Bitmap(folderImagePath);

        // Get the width and height of the original image.
        //
        int width = folderImage.Width;
        int height = folderImage.Height;

        if (imageFolderSize > 0 && folderImage.Width >= folderImage.Height && folderImage.Width > imageFolderSize)
        {
            // Landscape mode picture....scale the height to fit.
            //
            width = imageFolderSize;
            height = imageFolderSize * folderImage.Height / folderImage.Width;
        }
        else if (imageFolderSize > 0 && folderImage.Height >= folderImage.Width && folderImage.Height > imageFolderSize)
        {
            // Portrait mode picture...scale the width to fit.
            //
            width = imageFolderSize * folderImage.Width / folderImage.Height;
            height = imageFolderSize;
        }

        // Randomize the tilt angle.
        //
        double angle = (0.5 - rnd.NextDouble()) * angleAmplitude;

        // Create a pen to draw the border.
        //
        Pen borderPen = new Pen(new SolidBrush(folderBorderColor), folderBorderWidth);
        borderPen.LineJoin = LineJoin.Round;
        borderPen.StartCap = LineCap.Round;
        borderPen.EndCap = LineCap.Round;

        // Calculae the geometrical transformations necessary
        //    for drawing.
        //
        float sin = (float)Math.Sin(angle);
        float cos = (float)Math.Cos(angle);
        float sh = sin * height / 2;
        float ch = cos * height / 2;
        float sw = sin * width / 2;
        float cw = cos * width / 2;
        float shb = sin * (height + borderPen.Width) / 2;
        float chb = cos * (height + borderPen.Width) / 2;
        float swb = sin * (width + borderPen.Width) / 2;
        float cwb = cos * (width + borderPen.Width) / 2;

        // Draw the border.
        //
        g.DrawPolygon(borderPen, new PointF[] {
            new PointF(
                (float)drawXOffset - cwb - shb,
                (float)drawYOffset + chb - swb),
            new PointF(
                (float)drawXOffset - cwb + shb,
                (float)drawYOffset - swb - chb),
            new PointF(
                (float)drawXOffset + cwb + shb,
                (float)drawYOffset + swb - chb),
            new PointF(
                (float)drawXOffset + cwb - shb,
                (float)drawYOffset + swb + chb)
        });

        // Draw the current image.
        //
        g.DrawImage(folderImage, new PointF[] {
            new PointF(
                (float)drawXOffset - cw + sh,
                (float)drawYOffset - sw - ch),
            new PointF(
                (float)drawXOffset + cw + sh,
                (float)drawYOffset + sw - ch),
            new PointF(
                (float)drawXOffset - cw - sh,
                (float)drawYOffset + ch - sw)
        });

        // Free the gdi resources.
        //
        folderImage.Dispose();
        borderPen.Dispose();
    }

    // If necessary draw the navigation arrow.
    //
    if (isParentFolder)
    {
        // Set the qualities that define the arrow.
        //
        Color arrowColor = Color.FromArgb(200, Color.Beige);
        float arrowWidth = 4;
        float arrowSize = 0.125F;

        // Create a pen to draw the arrow.
        //
        Pen arrowPen = new Pen(new SolidBrush(arrowColor), arrowWidth);
        arrowPen.LineJoin = LineJoin.Round;
        arrowPen.StartCap = LineCap.Flat;
        arrowPen.EndCap = LineCap.Round;

        g.DrawLines(arrowPen, new PointF[] {
            new PointF((float)drawXOffset - arrowSize * size, (float)drawYOffset + arrowSize * size),
            new PointF((float)drawXOffset + arrowSize * size, (float)drawYOffset + arrowSize * size),
            new PointF((float)drawXOffset + arrowSize * size, (float)drawYOffset - arrowSize * size),
            new PointF((float)drawXOffset + arrowSize * size * 2 / 3, (float)drawYOffset - arrowSize * size * 2 / 3),
            new PointF((float)drawXOffset + arrowSize * size, (float)drawYOffset - arrowSize * size),
            new PointF((float)drawXOffset + arrowSize * size * 4 / 3, (float)drawYOffset - arrowSize * size * 2 / 3)
        });

        // Clean up gdi resources.
        //
        arrowPen.Dispose();
    }

    // Clean up gdi resources.
    //
    g.Dispose();

    return newImage;
}

image In addition to photo data, there is user information that is necessary for authentication and access rights to images. The UserInfo class is used to encapsulate a user instance. As before a UserInfoCollection class is provided to allow named types to be used as the development API.

image

The UserManager class provides static helper methods for all the CRUD (create, read, update, and delete) operations. User data is serialized out to an xml file. Before being used, the static property UserXmlFilePath must be set to the location of the user data xml file location. The LoadUsers and SaveUsers private methods handle the serialization of the data. The GenerateSampleUsers private method is used to create sample users for testing.

[Serializable]
public class UserInfo
{
    public enum TypeOptions { Public, Family, Admin };
    public Guid Id { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public TypeOptions UserType { get; set; }
}

[Serializable]
public class UserInfoCollection : List<UserInfo>
{
}

public static class UserManager
{
    #region Public Helper Methods

    public static string UserXmlFilePath { get; set; }

    public static UserInfo GetUser(string userName, string passWord)
    {
        UserInfoCollection users = LoadUsers();
        foreach (UserInfo user in users)
        {
            if (user.UserName == userName && user.Password == passWord)
            {
                return user;
            }
        }
        return null;
    }

    #endregion

    #region Public CRUD methods

    public static bool AddUser(UserInfo userInfo)
    {
        // Get the current book list.
        //
        UserInfoCollection users = LoadUsers();

        // Add the new book to the list.
        //
        users.Add(userInfo);

        // Write the modified collections.
        //
        return SaveUsers(users);
    }

    public static UserInfo GetUser(Guid id)
    {
        UserInfoCollection users = LoadUsers();
        foreach (UserInfo user in users)
        {
            if (user.Id == id)
            {
                return user;
            }
        }
        return null;
    }

    public static bool DeleteUser(Guid id)
    {
        UserInfoCollection users = LoadUsers();
        UserInfo userToDelete = null;
        foreach (UserInfo user in users)
        {
            if (user.Id == id)
            {
                userToDelete = user;
                break;
            }
        }
        if (userToDelete != null)
        {
            if (users.Remove(userToDelete))
            {
                return SaveUsers(users);
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }

    public static bool UpdateUser(UserInfo userInfo)
    {
        UserInfoCollection users = LoadUsers();
        UserInfo userToUpdate = null;
        foreach (UserInfo user in users)
        {
            if (user.Id == userInfo.Id)
            {
                userToUpdate = user;
                break;
            }
        }
        if (userToUpdate != null)
        {
            if (users.Remove(userToUpdate))
            {
                users.Add(userInfo);
                return SaveUsers(users);
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }

    #endregion

    #region Private Methods

    private static UserInfoCollection LoadUsers()
    {
        // Validate the xml file path
        //
        if (string.IsNullOrEmpty(UserXmlFilePath))
        {
            throw new Exception("Path to user xml data file not set!");
        }

        // Validate the existence of the data file.
        //
        if (!File.Exists(UserXmlFilePath))
        {
            // File is not there...create the default
            //    users.
            return GenerateSampleUsers();
        }

        // Open a stream for reading.
        //
        Stream fileStream = new FileStream(UserXmlFilePath, FileMode.Open, FileAccess.Read);

        // Deserialize the data.
        //
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(UserInfoCollection));
        UserInfoCollection users = (UserInfoCollection)xmlSerializer.Deserialize(fileStream);

        // Close the stream.
        //
        fileStream.Close();

        // Sort by descending dates.
        //
        users.Sort(delegate(UserInfo u1, UserInfo u2) { return u1.UserName.CompareTo(u2.UserName); });

        return users;
    }

    private static bool SaveUsers(UserInfoCollection users)
    {
        // Validate the xml file path
        //
        if (string.IsNullOrEmpty(UserXmlFilePath))
        {
            throw new Exception("Path to user xml data file not set!");
        }

        // Initialize return result.
        //
        bool result = false;

        // Open up a file stream for saving.
        //
        Stream fileStream = new FileStream(UserXmlFilePath, FileMode.Create, FileAccess.Write);

        // Serialize out the data.
        //
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(UserInfoCollection));
        try
        {
            xmlSerializer.Serialize(fileStream, users);
            result = true;
        }
        catch (Exception ex)
        {
        }
        finally
        {
            fileStream.Close();
        }

        return result;
    }

    private static UserInfoCollection GenerateSampleUsers()
    {
        UserInfo admin = new UserInfo();
        admin.Id = Guid.NewGuid();
        admin.UserName = "admin";
        admin.UserType = UserInfo.TypeOptions.Admin;
        admin.Password = "admin";
        admin.Email = "x@y.com";

        UserInfo family = new UserInfo();
        family.Id = Guid.NewGuid();
        family.UserName = "family";
        family.UserType = UserInfo.TypeOptions.Family;
        family.Password = "family";
        family.Email = "x@y.com";

        UserInfo pub = new UserInfo();
        pub.Id = Guid.NewGuid();
        pub.UserName = "public";
        pub.UserType = UserInfo.TypeOptions.Public;
        pub.Password = "public";
        pub.Email = "x@y.com";

        UserInfoCollection users = new UserInfoCollection();
        users.Add(admin);
        users.Add(family);
        users.Add(pub);

        SaveUsers(users);

        return users;
    }

    #endregion
}

Next time, I will try to wrap up the phase 1 (ASP.NET + my own JavaScript) of this project.

No Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

*