2
Best way to load docs stored in DB in DocumentUltimate
Question asked by Jim Genouvrier - 2/19/2017 at 4:18 AM
Answered
hi,
having a quick test of this nice control documentUltimate ...
working great so far on some simple apps but I tested loading files stored in a DB dynamically...and then I might need your input.
First I tried setting the document source to a MVC FileStreamResult url but the previewer say that URI are not supported.
My first question is then: are uri planned to be supported sometimes?
 
If not then I can handle normal files by setting the binary in the doc source or the stream, but I wonder how this will work for large files (few hundreds megs ) will the control load everything in cache then stream it?
 
side note : I had a little crash on search within a doc (script crash in browser). didn't investigate much on it as i assumed it might be an issue with trial version.
 
Thanks for your inputs
Jim
 

7 Replies

Reply to Thread
0
Cem Alacayir Replied
Employee Post
As of current version, you can not set DocumentSource directly to a url. However as long as you get a stream or byte array, you can set DocumentSource like this:
 
protected void Page_Load(object sender, EventArgs e)
{
	var fileInfo = new FileInfo(Server.MapPath("~/App_Data/ExampleFiles/DOCX Document.docx"));

	documentViewer.Document = new DocumentSource(
		//Pass a method that returns a Stream or Byte[].
		//For the simplicity of this example, we are getting a stream from a file on disk.
		//Otherwise the stream can come from network or a database or even a zip file.
		//This parameter is implemented as callback so that it's only called when necessary
		//i.e. when the document is opened for the first time and it's not converted and cached yet.
		//For consecutive views, the document will be served from cache unless cached key is changed
		//as a result of changing any of the 3 parameters below.
		() => fileInfo.Open(FileMode.Open),

		//These 3 parameters are used for generating a unique cache key. If one of them (file extension,
		//file size, file modified date) changes, cache key changes so the source will be considered 
		//as another document file. This way DocumentViewer can know if your Stream or Byte[] source
		//is the same file or not without even calling your method and without reading whole Stream or Byte[]
		//every time this page is hit.
		//If you don't have file size or last modified date, you can pass 0 and DateTime.MinValue respectively.
		//However you should always pass a file name with correct file extension (e.g. MyFile.docx)
		//The extension is required for determining source format correctly, also only the extension
		//is used for cache key, file name before extension can be changed without causing a change 
		//in cache key.
		fileInfo.Name,
		fileInfo.Length,
		fileInfo.LastWriteTimeUtc
	);
}
 
Here is an example for setting DocumentSource to a file stored in database:
 
using System.Data.SqlClient;

protected void Page_Load(object sender, EventArgs e)
{
	//Here is an example for loading a document from database
	//GetDocumentFromDb loads the document with passed ID (176) from the database as byte array 
	//and returns a DocumentSource instance with all information filled in.
	//This sample only demonstrates raw db access with System.Data objects
	//but you can use any type of db access (e.g. Entity Framework), the idea is same.
	documentViewer.Document = GetDocumentFromDb(176);
}

public DocumentSource GetDocumentFromDb(int fileId)
{
	using (var connection = new SqlConnection("CONNECTION STRING"))
	{
		connection.Open();

		using (var command = new SqlCommand("SELECT FileBytes, FileName, FileDate, FROM FileTable WHERE FileId='" + fileId + "' ", connection))
		using (var reader = command.ExecuteReader())
		{
			if (reader.Read())
				throw  new Exception("File not found");

			var fileBytes = (byte[])reader.GetValue(0); // read the file data from the selected row (first column in above query)

			return new DocumentSource(
				() => fileBytes,
				reader.GetValue(1),  // pass the file name here (second column in above query)
				fileBytes.Length, // pass the file size here (we already know the size because we have a byte array)
				reader.GetValue(2) // pass the file date here (third column in above query) or if you don't have a date just pass DateTime.MinValue
			);
		}
	}
}
 
 I had a little crash on search within a doc (script crash in browser). didn't investigate much on it as i assumed it might be an issue with trial version.
Was it a big document with lots of pages? You may have hit a bug, make sure you try the latest v2.3.2.
 
0
Paul Farmer Replied
I am trying to use the example that get a document stored in a database. I followed the example above, I get an error "Cannot implicitly convert type 'byte[]' to 'System.IO.Stream' ". any help would be great. 
 
Document Ultimate Version 2.7.5
0
Cem Alacayir Replied
Employee Post
The stream constructor has 2 optional parameters:
 
public DocumentSource(Func<Stream> openStream, string fileName, long fileSize, DateTime fileDateModified, bool leaveStreamOpen = false, bool keepSourceCached = true)
 
The byte array constructor has 1 optional parameter:
 
public DocumentSource(Func<byte[]> getBytes, string fileName, long fileSize, DateTime fileDateModified, bool keepSourceCached = true)
 
One of your parameters in the middle (string fileName, long fileSize, DateTime fileDateModified) is probably not of correct type. And Intellisense is confused and it’s not showing the actual error for the middle parameter.
 
I guess it’s the second parameter (string fileName)
ds.Tables….
you need to cast it to string
(string)ds.Tables….
 
0
Paul Farmer Replied
Thanks that did I cast the filename ToString(), and the error cleared
0
Cem Alacayir Replied
Employee Post
For future reference, starting with v3.0:
 
Changed: Removed DocumentSource class which was confusing and added IDocumentHandler interface for better stream handling in DocumentViewer. Streams will be no more copied to the cache folder. Conversion or downloading the original will be done on the stream directly via IDocumentHandler. DocumentViewer.Document property is now a simple string, it can be any file identifier which will be passed to your custom IDocumentHandler implementation which is specified in the new DocumentViewer.DocumentHandlerType property. Also with IDocumentHandler you don't need to provide file size or file date for cache key, you only need to provide a unique ID (e.g. it can be an ID field from your database)
 
Updated code sample:
 
protected void Page_Load(object sender, EventArgs e)
{
	// The document handler type which provides a custom way of loading the input files.
	// This class should implement IDocumentHandler interface and should have a parameterless
	// constructor so that it can be instantiated internally when necessary.
	// Value of Document property will be passed to this handler which should open 
	// and return a readable stream according to that file identifier.
	// See below for DbDocumentHandler class which implements IDocumentHandler interface
	documentViewer.DocumentHandlerType = typeof(DbDocumentHandler);

	// If a custom document handler is provided via DocumentHandlerType property, then
	// this value will be passed to that handler which should open and return a readable stream according 
	// to this file identifier. 
	// So it can be any string value that your IDocumentHandler implementation understands.
	// This loads the document with passed ID (176) from the database
	documentViewer.Document = "176";
}

// Implement IDocumentHandler interface to provide a custom way of loading the input files.
// You can instruct DocumentViewer to use this handler by setting DocumentViewer.DocumentHandlerType
// property to type of this class, i.e. typeof(DbDocumentHandler)
// This sample demonstrates raw db access with System.Data objects
// but you can use any type of db access (e.g. Entity Framework), the idea is same.
public class DbDocumentHandler : IDocumentHandler
{

	// Get the document information required for the current input file.
	// This is called before loading the document for determining the cache key and document format.
	//
	// inputFile parameter will be the value that was set in DocumentViewer.Document property, i.e.
	// the input file that was requested to be loaded in DocumentViewer
	//
	// Return a DocumentInfo instance initialized with required information from this method.
	public DocumentInfo GetInfo(string inputFile)
	{
		var fileId = inputFile;
		var sql = "SELECT FileName FROM FileTable WHERE FileId=" + fileId;

		using (var connection = new SqlConnection("CONNECTION STRING"))
		{
			connection.Open();

			using (var command = new SqlCommand(sql, connection))
			using (var reader = command.ExecuteReader())
			{
				if (!reader.Read())
					throw new Exception("File not found");

				// read the file name from the selected row (first column in above query)
				var fileName = reader.GetString(0); 

				return new DocumentInfo(
					// uniqueId parameter (required):
					// The unique identifier that will be used for generating the cache key for this document.
					// For instance, it can be an ID from your database table or a simple file name; 
					// you just need to make sure this ID varies for each different document so that they are cached correctly.
					// For example for files on disk,
					// we internally use a string combination of file extension, file size and file date for uniquely
					// identifying them, this way cache collisions do not occur and we can resuse the cached file
					// even if the file name before extension is changed (because it's still the same document).				
					fileId,
					
					// fileName parameter (optional but recommended):
					// The file name which will be used for display purposes such as when downloading the document
					// within DocumentViewer> or for the subfolder name prefix in cache folder. 
					// It will also be used to determine the document format from extension if format 
					// parameter is not specified. If not specified or empty, uniqueId will be used 
					// as the file name.					
					fileName
				);
			}
		}
	}

	// Open a readable stream for the current input file.
	//
	// inputFile parameter will be the value that was set in DocumentViewer.Document property, i.e.
	// the input file that was requested to be loaded in DocumentViewer
	//
	// inputOptions parameter will be determined according to the input document format
	// Usually you will not need to check this parameter as inputFile parameter should be sufficient
	// for you to locate and open a corresponding stream.
	//
	// Return a StreamResult instance initialized with a readable System.IO.Stream object.
	public StreamResult OpenRead(string inputFile, InputOptions inputOptions)
	{
		var fileId = inputFile;
		var sql = "SELECT FileBytes FROM FileTable WHERE FileId=" + fileId ;

		using (var connection = new SqlConnection("CONNECTION STRING"))
		{
			connection.Open();

			using (var command = new SqlCommand(sql))
			using (var reader = command.ExecuteReader())
			{
				if (!reader.Read())
					throw new Exception("File not found");

				// read the file data from the selected row (first column in above query)
				var fileBytes = (byte[])reader.GetValue(0);

				return new StreamResult(new MemoryStream(fileBytes));
			}
		}
	}
}
 
0
Tony Phan Replied
I tried the suggested Document Handler method and I tried setting the DocumentSource directly like the documentation for both Memory Stream as well as the Byte Array, all gives me an exception error about Header not found.  I tried this with versions 3.0.0, 3.1.1, and 3.2.0.  I see an undocumented property "DisableHeaderIncludes", not sure what it does but if I set that to false, then I don't get the exception but nothing loads either.  Am I missing something?
Note, it works if I use the Document property and point it to a file but I'm trying to avoid that.  Thanks
0
Cem Alacayir Replied
Employee Post Marked As Answer
Did you use 3.2.0 like this?
 

Load document from a stream:

documentViewer.DocumentSource = new DocumentSource(
    new DocumentInfo(uniqueId, fileName), 
    new StreamResult(stream)
);

 

Load document from a byte array:

documentViewer.DocumentSource = new DocumentSource(
    new DocumentInfo(uniqueId, fileName),
    byteArray
);
 
"Header not found" is not related DisableHeaderIncludes property, it means the file format could not be determined from filename
 
// Either provide a file name with extension  
new DocumentInfo(uniqueId, fileName);

// Or specify an explicit document format if you don't have a file name
new DocumentInfo(uniqueId, null, DocumentFormat.Docx);

// which can also be written as
new DocumentInfo(uniqueId, format: DocumentFormat.Docx);
 
 
 

Reply to Thread