GenoPro Developer Home
GenoPro Home  |  SDK Home  |  Report Generator  |  Support  |  Search  |  Help  |  Site Map

Skip Navigation Links.

GenoPro offers a mechanism to store your .gno files to an external storage such as saving files to your private website with an SQL database.

How do I use the External Storage?

The External Storage is as simple as saving to disk. Instead of using Save As, just select Save to Database.  Until GenoPro Gamma is officially released, you need to download and install http://www.genopro.com/download/InstallGenoProExternalStorage.exe to use the External Storage feature.

Storing document to an external database

You will then see the following dialog. Just enter the filename and click on the Save button. GenoPro will take care of the rest, by uploading the content of your document to the server.

 Saving to a database

How does External Storage works?

You specify the URL of the address of the server handling external storage requests.  This URL must handle four requests:

  1. FilesList - To list all the files available on the server.  This method is called when the user wants open a file from the database.
  2. FileOpen - To open a specific file.  The server must send the file data on the network wire so GenoPro can receive it.
  3. FileSave - When you click on the Save button, GenoPro calls FileSave with the file data.  The server receives the data and write it on its internal storage, either a file on the server or a database.
  4. FileDelete - The user may click on the Delete File button to delete a specific file on the server.

By default, the URL for external storage is https://www.genopro.com/sdk/External-Storage/GenoProStorage.asmx, however you can specify your own.  To change the URL, just visit the Options dialog from the Tools menu.

Change the URL for external storage

The URL must respond (handle) the four methods, FilesList, FileOpen, FileSave and FileDelete.  To get started, just copy & paste the code below as file GenoProStorage.asmx and you will have a file-based external storage on your web server, identical to http://www.genopro.com/sdk/External-Storage/GenoProStorage.asmx.  You are welcome to customize this code to store the files into an SQL database or some other type of storage.

Source code of the web service

<%@ WebService Language="C#" Class="GenoProStorage" %>
/* The purpose of this project simulating a Web service for storing GenoPro
 * files into a third-party external storage.
 * If you copy & paste this code on your web server, you will get EXACTLY the same
 * behaviour as the page http://www.genopro.com/sdk/External-Storage/GenoProStorage.asmx.
 * 
 * A typical use for this project is storing the GenoPro files into an SQL
 * database.  In a nutshell, you only need to implement the four web methods 
 * (FilesList, FileOpen, FileSave, FileDelete) to interact with
 * your database and GenoPro will take care of the rest, calling the
 * appropriate web method when necessary.
 * 
 * In this example, the "database" is the filesystem.  If you wish to use an SQL database,
 * just modify the web methods by the appropriate SQL statements.  For instance, the web
 * method FilesList would consist of a "SELECT * from MyDatabase".
 *
 * Copyright:  There are no restrictions, including removing this copyright
 * comment and/or selling this code for profit.
 * You are welcome to copy, modify, and distribute this code as you wish.
 * */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Web.Services;

/// <summary>
/// This class represents a GenoPro file from the storage system.
/// </summary>
public class GenoFile
{
  public string ID; 	            // Unique identifier of a file.  It can be anything, including accents and punctuation, as long as it is unique.
  public string FileName;           // Filename to display to the user.  The filename can be the same as the ID, if the ID is meaningful to the user
  public DateTime DateCreated;      // Date when the file was first created (in GMT format).
  public DateTime DateLastModified; // Date when the file was last modified (in GMT format).

	[DefaultValue(0)] // to prevent sending 0 for folder
  public Int64 Size;                // Size in bytes of the file
  // Add your own fields here and GenoPro will display them in the dialog for opening a file from the database.
  // For instance, you may add the fields FileDescription, CreatedBy and LastModifiedBy to display a description of the file,
  // as well as who created and modified the file.  Of course, to support such features, you need a database because the file system does not
  // provide such facility.
  /*public string FileDescription; 
  public string CreatedBy;
  public string LastModifiedBy;  */
}

[WebService(Namespace = "http://genopro.com/webservices/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class GenoProStorage : WebService
{
  public char FolderCharacterEnding {
    get { return '\\'; }
  }

  private const string AttachmentFolderName = "attachments";

	[WebMethod(Description = "Provide a listing of all the GenoPro files on the storage system.")]
	public GenoFile[] FilesList(string IDFolder) {
		List<GenoFile> lstFiles = new List<GenoFile>();

		// For the simplicity, we simply return all files ending with .gno.
		// You are welcome to write the code to connect to an SQL database and fill in the array of file info.
	  string folderName = GetFullPathFromId(IDFolder);

		DirectoryInfo currentDirectory = new DirectoryInfo(folderName);
	  if (currentDirectory.Exists) {
	    if (!string.IsNullOrWhiteSpace(IDFolder)) {
	      // send the ".." directory of the parent
	      DirectoryInfo parent = currentDirectory.Parent;
				GenoFile folder = new GenoFile();
				folder.ID = GetRelativePath(parent.FullName) + FolderCharacterEnding;
				folder.FileName = ".. [Parent Folder]"; // the character / tell GenoPro it's a directory an not a file
	      lstFiles.Add(folder);
	    }
	    foreach (DirectoryInfo directoryInfo in currentDirectory.GetDirectories()) {
	      if ((directoryInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) {
	        continue;
	      }
	      GenoFile folder = new GenoFile();
				folder.ID = GetRelativePath(directoryInfo.FullName) + FolderCharacterEnding;
	      folder.FileName = '/' + directoryInfo.Name; // the character / tell GenoPro it's a directory an not a file
	      folder.DateCreated = directoryInfo.CreationTimeUtc;
	      folder.DateLastModified = directoryInfo.LastWriteTimeUtc;
	      lstFiles.Add(folder);
	    }
			foreach (FileInfo file in currentDirectory.GetFiles("*.gno")) {
				GenoFile genoFile = new GenoFile();
				genoFile.ID = GetRelativePath(file.FullName); // The ID is the filename for simplicity      
				genoFile.FileName = file.Name;
				genoFile.DateCreated = file.CreationTimeUtc;
				genoFile.DateLastModified = file.LastWriteTimeUtc;
				genoFile.Size = file.Length;
				// Add your extra fields here, with real values!!!
				/*genoFile.FileDescription = "This is a file description";
			genoFile.CreatedBy = "Bob";
			genoFile.LastModifiedBy = "Alice"; */
				lstFiles.Add(genoFile);
			}
	  }
	  return lstFiles.ToArray();
	}

	[WebMethod(Description ="Create a sub directory, IdDirectoryParent can be omited if it's a directory in the root folder")]
  public GenoResult FolderCreate(string Name, string IdFolderParent) {
		GenoResult result = new GenoResult();
		if (string.IsNullOrWhiteSpace(Name)) {
		  result.Error = "ErrNameNotSpecified";
		  return result;
		}
	  try {
	    string parentFolder = GetFullPathFromId(IdFolderParent);
	    string folder = parentFolder.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar + Name;
			if (Directory.Exists(folder)) {
				result.Error = "ErrDirectoryAlreadyExist";
				return result;
			}
			DirectoryInfo dir = Directory.CreateDirectory(folder);
	    result.ID = GetRelativePath(dir.FullName);
	    result.FileName = dir.Name;
	  } catch (Exception ex) {
			result.Error = ex.Message;
	  }
		return result;
	}

  /// <summary>
  /// Open a GenoPro file from the storage system, by returning an UTF-8 string.
  /// </summary>
  /// <param name="ID">Unique identifier for the file. It can be the filename or an ID in another database</param>
  /// <returns>The content of the file in UTF-8, or an empty string if the file is not found.</returns>
	[WebMethod(Description = "Open a GenoPro file from the storage system, by returning an UTF-8 string.")]
	public GenoResult FileOpen(string ID) {
		GenoResult result = new GenoResult();
		if (string.IsNullOrWhiteSpace(ID)) {
			result.Error = "ErrIdNotSpecified";
			return result;
		}
		try {
		  string path = GetFullPathFromId(ID);
			FileInfo file = new FileInfo(path);
			if (file.Exists) {
				using (StreamReader reader = file.OpenText()) {
					result.Data = reader.ReadToEnd();
				}
				result.FileName = file.Name;
			} else {
				result.Error = "ErrFileNotFound";
			}
		} catch (Exception ex) {
			result.Error = ex.Message;
		}
		return result;
	}

  /// <summary>
  /// Save/update a GenoPro file into the storage system with the UTF-8 content of the file.
  /// </summary>
  /// <param name="ID">Unique identifier of the file to save/update.  Leave this value empty to create a new file</param>
  /// <param name="FileName">Unique identifier of the file to save/update.  Leave this value empty to create a new file</param>
	/// <param name="Data">The content of the xml file in UTF-8 format [cannot be binary]</param>
  /// <returns>the id of the file, if new file the id will be assigned</returns>
  [WebMethod(Description = "Save/update a GenoPro file into the storage system with the UTF-8 content of the file.")]
	public string FileSave(string ID, string FileName, string Data, string IDFolder) {
		if (string.IsNullOrEmpty(ID)) {
			// The file identifier is empty, meaning we are creating a new file
			// In this implementation, the file identifier is the filename, however it could be an identifier assigned by the database.
		  if (string.IsNullOrEmpty(FileName)) {
		    ID = Path.GetRandomFileName() + ".gno"; // The filename is also empty, so generate a name
		  }  else {
		    ID = FileName;
		  }
		  if (!string.IsNullOrWhiteSpace(IDFolder)) {
		    ID = IDFolder + ID;
		  }
		}

    string path = GetFullPathFromId(ID);
		
		// In our implementation, we write the content of the GenoPro file on disk as a text file.
    // You could store the file content into a database as an UTF-8 string or a BLOB.
		FileInfo file = new FileInfo(path);
		using (StreamWriter textWriter = file.CreateText()) {
			textWriter.Write(Data);
			textWriter.Close();
		}
    // Return the identifier of the file to acknowledge the file was saved successfully.
    // If the file was created, GenoPro will use this identifier for subsequent saves.
    return ID;
    }

  /// <summary>
  /// Delete a GenoPro file from the storage system
  /// </summary>
  /// <param name="ID">Unique identifier of the file to delete</param>
  /// <returns>true if the file was deleted successfully, otherwise false if an error such as file not found or access denied</returns>
	[WebMethod(Description = "Delete a GenoPro file from the storage system")]
	public GenoResult FileDelete(string ID) {
	  GenoResult result = new GenoResult();
    string path = GetFullPathFromId(ID);
		FileInfo file = new FileInfo(path);
    if ((file.Attributes & FileAttributes.Directory) == FileAttributes.Directory) {
      if (!Directory.Exists(path)) {
				result.Error = "ErrFolderNotFound";
				return result;
      }
      // folder
      var files = Directory.EnumerateFiles(path);
      foreach (string fileInsideFolder in files) {
				// oups the folder is not empty, reject the deletion
				result.Error = "ErrFolderNotEmpty";
        return result;
      }
			var folders = Directory.EnumerateDirectories(path);
			foreach (string folderInsideFolder in folders) {
				// oups the folder is not empty, reject the deletion
				result.Error = "ErrFolderNotEmpty";
				return result;
			}
      Directory.Delete(path);

      return result;
    }
		if (!file.Exists) {
			result.Error = "ErrFileNotFound";
			return result;
		}
		file.Delete();
		return result;
	}

  [WebMethod(Description = "Rename file or directory")]
  public GenoResult FileRename(string ID, string Name) {
	  GenoResult result = new GenoResult();
    try {
      if (string.IsNullOrWhiteSpace(Name)) {
        result.Error = "ErrNameNotSpecified";
        return result;
      }
      if (Name.Contains("..")) {
				result.Error = "ErrNameInvalid";
				return result;
      }
			string path = GetFullPathFromId(ID);
			FileInfo file = new FileInfo(path);
			if ((file.Attributes & FileAttributes.Directory) == FileAttributes.Directory) {
				// it's a directory
				DirectoryInfo dir = new DirectoryInfo(path);
				string newName = dir.Parent.FullName + FolderCharacterEnding + Name;
				dir.MoveTo(newName);
			} else {
			  // it's a file
				string newName = file.Directory.FullName + FolderCharacterEnding + Name;
			  file.MoveTo(newName);
			}
    } catch (Exception ex) {
      result.Error = ex.Message;
    }
    return result;
  }

  /// <summary>
  /// Returns the directory where the .gno file are stored.
  /// The current implementation stores the .gno files in the same directory as the file GenoProStorage.asmx, however 
  /// you are welcome to hardcode the directory to @"C:\MyGenoProFiles\" or something else.
	/// </summary>
	private string StorageDirectory {
		get {
			string directory = Path.GetDirectoryName(Context.Request.PhysicalPath);
			if (directory.EndsWith("\\"))
				return directory;
			return directory + "\\";
		}
	}

	/// <summary>
	/// Get the relative of the file from the StorageDirectory
	/// </summary>
	/// <param name="fullPath">the full path of the file or directory</param>
	/// <returns>a relative path from the Storage directory path</returns>
  public string GetRelativePath(string fullPath) {
	  string rootPath = Path.GetFullPath(StorageDirectory);
	  if (rootPath.Length >= fullPath.Length) {
	    return string.Empty;
	  }
		return fullPath.Substring(rootPath.Length); 
	}

  public string GetFullPathFromId(string fileId) {
		return GetFullPathFromId(null, fileId);
  }


  public string GetFullPathFromId(string folderId, string fileId) {
    if (fileId.Contains("..")) {
      return string.Empty;
    }
		return string.Format("{0}{1}{2}", StorageDirectory, folderId, fileId);
  }

  [WebMethod(Description = "Download an attachement from it's md5 (picture, images...)")]
  public GenoResult AttachmentDownload(string MD5) {
    GenoResult result = new GenoResult();
    try {
			if (string.IsNullOrWhiteSpace(MD5)) {
				result.Error = "ErrMD5NotProvided";
				return result;
			}

			if (!System.Text.RegularExpressions.Regex.IsMatch(MD5, Md5RegEx)) {
				result.Error = "ErrInvalidMD5";
				return result;
			}

			string filename = AttachmentMd5ToFileName(MD5);
			if (!File.Exists(filename)) {
				result.Error = "ErrFileNotFound";
				return result;
			}

			byte[] data = File.ReadAllBytes(filename);
      result.Data = Convert.ToBase64String(data);
    } catch (Exception ex) {
      result.Error = ex.Message;
    }
   

    return result;
  }

	[WebMethod(Description = "Upload an attachment providing it's MD5")]
  public GenoResult AttachmentUpload(string MD5, byte[] Data) {

		GenoResult result = new GenoResult();
		try {
			if (string.IsNullOrWhiteSpace(MD5)) {
				result.Error = "ErrMD5NotProvided";
				return result;
			}

			if (!System.Text.RegularExpressions.Regex.IsMatch(MD5, Md5RegEx)) {
				result.Error = "ErrInvalidMD5";
				return result;
			}

			string filename = AttachmentMd5ToFileName(MD5);
			if (File.Exists(filename)) {
				result.Error = "ErrFileAlreadyUploaded";
				return result;
			}
		  GenoProLib.Util.CreateDirectory(Server.MapPath(AttachmentFolderName + @"\"));
		  File.WriteAllBytes(filename, Data);
		} catch (Exception ex) {
			result.Error = ex.Message;
		}
		return result;
  }

	[WebMethod(Description = "Query the server for a list of missing that are not present")]
	public GenoResult AttachmentsQueryMissing(string MD5s) {

		GenoResult result = new GenoResult();
		try {
			if (string.IsNullOrWhiteSpace(MD5s)) {
				result.Error = "ErrMD5NotProvided";
				return result;
			}
		  string[] md5Splitted = MD5s.Split(' ');
			result.Data = result.Error = string.Empty; // to allow concatenation
			
		  HashSet<string> filesPresent = new HashSet<string>();
			string folder = Server.MapPath(AttachmentFolderName + @"\");
		  if (Directory.Exists(folder)) {
				// read all files in the attachement folder and setup them in a hash
				foreach (string filename in Directory.EnumerateFiles(folder)) {
					filesPresent.Add(Path.GetFileNameWithoutExtension(filename));
				}
		  }


		  System.Text.StringBuilder missingMd5 = new System.Text.StringBuilder();

		  foreach (string md5 in md5Splitted) {
				if (!System.Text.RegularExpressions.Regex.IsMatch(md5, Md5RegEx, System.Text.RegularExpressions.RegexOptions.IgnoreCase)) {
					result.Error += "ErrInvalidMD5";
					return result;
				}

				if (!filesPresent.Contains(md5)) {
				  missingMd5.Append(md5);
				  missingMd5.Append(' ');
				}
		  }
		  result.Data = missingMd5.ToString().TrimEnd();

		} catch (Exception ex) {
			result.Error = ex.Message;
		}
		return result;
  }


  public string AttachmentMd5ToFileName(string md5) {
		return Server.MapPath(string.Format(@"{1}\{0}.att", md5, AttachmentFolderName));
  }

  //     string[] md5s = MD5.Split(' ');

  private const string Md5RegEx = "[0-9a-fA-F]{32}";

} // end of class


/// <summary>
/// This class represent a resultset for a file open
/// </summary>
public class GenoResult
{
  public string ID;         // Unique identifier of the GenoPro file.  This ID is used for opening, saving and deleting files.
  public string FileName;   // Friendly name of the GenoPro file stored on the external storage.  This name is displayed to the user.
  public string Data;    // XML data of the GenoPro file in UTF-8 format or base64 of binary file
  public string Error;      // Error code (if any).  GenoPro will display this error to the user.
}

 

Copyright ┬ę 1998-2014. All rights reserved. GenoPro┬« and the GenoPro logo are registered trademarks.