Search

Atalasoft Knowledge Base

HOWTO: Implement a Custom Web Document Viewer Handler

Administrator
DotImage

This article goes over how to create a custom Web Document Viewer handler. This allows you to provide custom image sources to the Web Document Viewer, whether it is a document loaded from a database, a procedurally generated image or simply a stream that doesn't map directly to a file in the relative web path.

The first step is to make sure you have the relevant references in your project. Make sure you have Atalasoft.dotImage.dll, Atalasoft.dotImage.Lib.dll, Atalasoft.dotImage.WebControls.dll, Atalasoft.dotImage.Lib.dll and Atalasoft.Shared.dll added to your project. You will also need any additional libraries you may be using.

The next step is to either create or include a "Generic Handler" (an .ashx file.) It can be either C# or VB.Net. I've attached pre-made handlers for both C# and VB.Net to this KB, so you can skip most of this and jump straight to grabbing those .ashx files.

Once you have that added to your project, open it up and update the definition of the class. Instead of Implementing the IHttpHandler interface we're going to inherit from Atalasoft.Imaging.WebControls. WebDocumentRequestHandler

(Please note that from here on out, you will need to replace our default " Handler " with whatever class name you have chosen. Also, I'm not going to assume you've added the relevant using or Imports statements to your handler and will include the full class name of all the classes we're using. Feel free to add them and save yourself some space.)

C#
public class Handler : WebDocumentRequestHandler

VB.Net
Public Class CustomWebDocumentViewer_VB
     Inherits WebDocumentRequestHandler

Then in your class constructor, add the handlers for the two events we're going to be working with, DocumentInfoRequested  and ImageRequested .

C#
   public CustomWebDocumentViewer()
    {
        this .DocumentInfoRequested += new   DocumentInfoRequestedEventHandler (CustomWebDocumentViewer_DocumentInfoRequested);
        this
.ImageRequested += new
  ImageRequestedEventHandler (CustomWebDocumentViewer_ImageRequested);
    }

VB.Net
    Public Sub
CustomWebDocumentViewer_VB()
     
AddHandler Me .DocumentInfoRequested, AddressOf Me .CustomWebDocumentViewer_DocumentInfoRequested
      AddHandler
Me
.ImageRequested, AddressOf
Me
.CustomWebDocumentViewer_ImageRequested
   End
Sub

Okay, so all we have left to do is to implement those two events. Before we jump in to that, let me take a moment to explain what each event does. The DocumentInfoRequested event fires only once when .openUrl is called on the viewer or thumbnailer to build a basic document outline for the viewer. It gets the page count and page size before the viewer starts requesting pages. The ImageRequested event fires once per document page, and handles serving the page to the user.

C#
void CustomWebDocumentViewer_DocumentInfoRequested( object sender, DocumentInfoRequestedEventArgs e)
{
   //e.FilePath //Requested image
   //e.PageCount //We need to return how many pages to load
   //e.PageSize //We need to return default page size.

   e.PageCount = CodeHereToGetActualNumberOfPages();
   e.PageSize = CodeHereToGetSizeOfFirstPage()
;
}

void
CustomWebDocumentViewer_ImageRequested( object sender, ImageRequestedEventArgs e)
{
   //e.FilePath //Tells you what image is requested
   //e.FrameIndex //What page is requested(0-indexed)
   //e.Image //Return the image to display to client here.

   e.Image = CodeHereToGetAtalaImageOfSpecificFrameDesired());
}

VB.Net
Public Sub CustomWebDocumentViewer_DocumentInfoRequested( ByVal sender As Object , ByVal e As Atalasoft.Imaging.WebControls.DocumentInfoRequestedEventArgs)
    'e.FilePath 'Requested image
    'e.PageCount 'We need to return how many pages to load
    'e.PageSize 'We need to return default page size.

    e.PageCount = CodeHereToGetActualNumberOfPages()
    e.PageSize = CodeHereToGetSizeOfFirstPage()

End Sub

Public Sub CustomWebDocumentViewer_ImageRequested( ByVal sender As Object , ByVal e As ImageRequestedEventArgs)
    'e.FilePath 'Tells you what image is requested
    'e.FrameIndex 'What page is requested(0-indexed)
    'e.Image 'Return the image to display to client here.

    e.Image = CodeHereToGetAtalaImageOfSpecificFrameDesired()
End Sub

In the DocumentInfoRequested we're telling the viewer to expect a single page at 800, 600 and in the ImageRequested simply serving a new 800, 600 red AtalaImage. You will need to pass in the required information to either get or make your image(s) in the FilePath field. It should be fairly easy with this to use your own image repository to serve to the Web Document Viewer.

The last step is to go in to your .aspx file where you have the WebDocumentViewer and change the _serverUrl to point to your new custom handler.

< script type ="text/javascript" language ="javascript" >
    var _docUrl = 'document12345' ;
    var _serverUrl = 'CustomWebDocumentViewer.ashx' ;
    //You get the idea.

That _docUrl will be passed to your handler in the e.FilePath to both the DocumentInfoRequested and ImageRequested events. In your client-side javascript, you can change the document using _viewer.OpenUrl("newDocument") to change the currently loaded document, again whatever you pass in that Open will get passed directly as the e.FilePath

Example Handler with All Events

C#

using System;
using
System.Web;
using
Atalasoft.Imaging.Codec;
using
Atalasoft.Imaging.Codec.Pdf;
using
Atalasoft.Imaging.WebControls;

public class WebDocViewerHandler : WebDocumentRequestHandler
{

    static WebDocViewerHandler()
    {
        // for PDF support you need to reference Atalasoft.dotImage.PdfReader.dll and have a PdfReader license

        RegisteredDecoders .Decoders.Add( new PdfDecoder ()
        {
            Resolution = 200,
            RenderSettings =
new RenderSettings () 
             {
                AnnotationSettings =
AnnotationRenderSettings .RenderNone
            }
        });

        //// This adds the OfficeDecoder .. you need proper licensing and additional OfficeDecoder
        //// dependencies (perceptive filter dlls)
        //RegisteredDecoders.Decoders.Add(new OfficeDecoder() { Resolution = 200 });
    }

    public WebDocViewerHandler()
    {

        //// *************************** BASE DOCUMENT VIEWING EVENTS ***************************
        //// these two events are the base events for the loading of pages
        //// the documentInfoRequested fires to ask for the number of pages and size of the first page (minimum requirements)
        //// then, each page needed (and only when it's needed - lazy loading) will fire an ImageRequested event
        //this.DocumentInfoRequested += new DocumentInfoRequestedEventHandler(WebDocViewerHandler_DocumentInfoRequested);
        //this.ImageRequested += new ImageRequestedEventHandler(WebDocViewerHandler_ImageRequested);

        //// *************************** ADDITIONAL DOCUMENT VIEWING EVENTS ***************************
        //// These events are additional/optional events ..
        //this.AnnotationDataRequested += WebDocViewerHandler_AnnotationDataRequested;
        //this.PdfFormRequested += WebDocViewerHandler_PdfFormRequested;

        //// this is the event that would be used to manually handle page text requests 
        //// (if left unhandled, page text requested wil be handled by the default engine which will provide searchabel text for Searchable PDF and office files)
        //// Manually handling this event is for advanced use cases only
        //this.PageTextRequested += WebDocViewerHandler_PageTextRequested;

        //// *************************** DOCUMENT SAVING EVENTS ***************************
        //this.DocumentSave += WebDocViewerHandler_DocumentSave;
        //this.DocumentStreamWritten += WebDocViewerHandler_DocumentStreamWritten;
        //this.AnnotationStreamWritten += WebDocViewerHandler_AnnotationStreamWritten;

        //// This event is save related but conditional use - see comments in handler
        //this.ResolveDocumentUri += WebDocViewerHandler_ResolveDocumentUri;
        //this.ResolvePageUri += WebDocViewerHandler_ResolvePageUri;

        //// *************************** OTHER EVENTS (usually ignored) ***************************
        //this.ReleaseDocumentStream += WebDocViewerHandler_ReleaseDocumentStream;
        //this.ReleasePageStream += WebDocViewerHandler_ReleasePageStream
    }

    #region BASE DOCUMENT VIEWING EVENTS
    ///
<summary>
    ///
The whole DocumentOpen process works like this:
    ///
On initial opening of the document, DocumentInfoRequested fires once for the document
    ///
The AnnotationDataRequested event may also fire if coditions merit\
    /// then the ImageRequested event will fire for subsequent pages..
    ///
 
    ///
This event fires when the first request to open the document comes in
    ///
you can either set e.FilePath to a different value (useful for when the 
    ///
paths being opened simply alias to physial files somewhere on the server)
    ///
or you can manually handle the request in qhich case you MUST set both e.PageSize and e.PageCount
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
    void
WebDocViewerHandler_DocumentInfoRequested( object sender, DocumentInfoRequestedEventArgs e)
    {
        ////THESE TWO MUST BE RESOLVED IF YOU ARE MANUALLY HANDLING
        //// set e.PageCount to the number of pages this document has
        //e.PageCount = SomeMethodThatGetsPageCountForDocumentRequested(e.FilePath);
        //// e.PageSize to the System.Drawing.Size you want pages forced into
        //e.PageSize = SomeMethodThatGetsSizeOfFirstPageOfDocument(e.FilePath);
    }

    /// <summary>
    ///
you can use e.FilePath and e.FrameIndex to work out what image to return
    ///
set e.Image to an AtalaImage to send back the desired image
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
    void WebDocViewerHandler_ImageRequested (
object
sender,
ImageRequestedEventArgs e)
    {
        //// NOTE: If you are using file-based viewing, but the incoming FilePath is an 
        //// alias/ needs parsing but ends up just pointing to a valid file you can just set e.FilePath to the FULL PATH (on the server) or full UNC Path
        //// Example:
        //// Incoming request for "docId-foo" needs to resolve to C:\SomeDirectory\MyDoc.tif
        //e.FilePath = @"C:\SomeDirectory\MyDoc.tif";
        //// or more likely:
        // e.FilePath = SomeMethodToReturnFullPathForIncomingRequest(e.FilePath);
        //// This is an approximation of what the default ImageRequested event would do if you didn't handle it manually
        //e.Image = new AtalaImage(HttpContext.Current.Request.MapPath(e.FilePath), e.FrameIndex, null);
        //// When you manually handle it, you need to pass an AtalaImage back to e.Image
        // e.Image = SomeMethodThatReturnsAtalaImage(e.FilePath, e.FrameIndex);
        //// but there's no reason that e.FilePath couldn't be a database PKID or similar..
        //// your method would look up the record and get the data needed to construct and return a valid AtalaImage
     }
    #endregion
BASE DOCUMENT VIEWING EVENTS

    #region ADDITIONAL DOCUMENT VIEWING EVENTS
    ///
<summary>
    ///
When an OpenUrl that includes an AnnotationUri is called, the viewer default action is to go to the 
    ///
url specified ... which an XMP file containing all annotation layers (serialized LayerData[]) is read and loaded
    ///
Manual handling of this event would be needed if one were to be loading annotations from a Byte[] or from a databasae, etc
    ///
NOTE: this event WILL NOT FIRE if there was no annotationsUrl or a blank /null AnnotaitonsUrl was passed in the OpenUrl call
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
    void
WebDocViewerHandler_AnnotationDataRequested(
object
sender,
AnnotationDataRequestedEventArgs e)
    {
        // READ-ONLY INPUT VALUE
        //e.FilePath = the passed in FilePath of where to find the annotations (when the OpenUrl is called, this will populate with the AnnotationsUrl value
        
       
// WHAT YOU NEED TO HANDLE THIS EVENT
        // To successfully handle this event, you must populate the e.Layers
        //e.Layers = a LayerData[] containing ALL Annotation Layers for the whole document
    }

    /// <summary>
    /// This event lets you intercept requests for page text to provide your own
    ///
manually handling this is not recommended, but the hook is here
    ///
</summary>
    ///
<param name="sender"></param>
    /// <param name="e"></param>
    void
WebDocViewerHandler_PageTextRequested(
object
sender,
PageTextRequestedEventArgs e)
    {
        // You can use the e.FilePath and e.Index to know which document and page are being requested respectively
        // you can then go extract text/ get the text to the control you must ultimately set
        // e.Page = .. an Atalasoft.Imaging.WebControls.Text.Page object containing the page text and position data
    }

    /// <summary>
    ///  
This event only fires if the allowforms: true was set and you have a license for the PdfDoc addon
    ///
 
    ///
It is used to provide the PDF document needed by the form filling components
    ///
If left unhandled it will still fire internally and will simply 
    ///
 
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
    void
WebDocViewerHandler_PdfFormRequested(
object
sender,
PdfFormRequestedEventArgs e)
    {
        //// READ ONLY VALUE
        //e.FilePath contains the original File path in the intial request use this to figure out which file/document/record to fetch
        //// Required if handling you must provide an Atalasoft.PdfDoc.Generating.PdfGeneratedDocument 
        //// containing the PDF with the fillable form if you manually handle this event
        //FileStream fs = new System.IO.FileStream(e.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
        //e.PdfDocument = new Atalasoft.PdfDoc.Generating.PdfGeneratedDocument(fs);
        // If you return NULL for e.PdfDocument then the system will treat the PDF as not being a fillable PDF form
    }
    #endregion
ADDITIONAL DOCUMENT VIEWING EVENTS

    #region DOCUMENT SAVING EVENTS
    /// <summary>
   
///
This event fires initially on DocumentSave
    ///
Document saving rundown:
    ///
The DocumentSave fires first.. it gives you the chance to provide alternate streams for 
    ///
e.DocumentStream and e.AnnotationStream
    ///
Then when the Document is written the DocumentStreamWritten event will fire.. e.DocumentStream will give oyu access to the 
    ///
document stream
    ///
Then the AnnotationStreamWritten will fire (if there were annotations to save) and e.AnnotationStream will give you access
    ///
to the annotation stream.
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
    void
WebDocViewerHandler_DocumentSave(
object
sender,
DocumentSaveEventArgs e)
    {

        //// If you want to handle Annotations / Document into a database or similar, 
        //// do this and then handle the writing to db in the AnnotationStreamWritten / DocumentStreamWritten event
        //e.AnnotationStream = new MemoryStream();
        //e.DocumentStream = new MemoryStream();
        //// If you want to provide an alternate location.. you can't modify e.FilePath - its read only
        //// so instead, do this
        //e.AnnotationStream = new FileStream("FullyQualifiedPathForAnnotationsToWriteToHere", FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
        //e.DocumentStream = new FileStream("FullyQualifiedPathForDocumentToWriteToHere", FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
     }

    /// <summary>
   
///
e.AnnotationStream will contain the annotations that were written
    /// The DocumentStreamWritten will ALWAYS fire before the AnnotationStreamWritten
    ///
even thought this event has e.DocumentStream.. it will always be null when 
    ///
AnnotationStreamWritten fires.. use the DocumentStreamWritten event to handle the DocumentStream
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
    void
WebDocViewerHandler_AnnotationStreamWritten(
object
sender,
DocumentSaveEventArgs e)
    {
        //// You should rewind first, then you can store in a DB or similar
        //// EXAMPLE: passed an e.MemoryStream for writing to a DB:
        //MemoryStream annoStream = e.AnnotationStream as MemoryStream
        //if (annoStream != null)
        //{
        //     annoStream.Seek(0, SeekOrigin.Begin);
        //     SomeMethodToStoreByteArrayToDb(annoStream.ToArray());
        //}

        //// NOTE: if you set e.AnnotationStream to a file stream in the DocumentSave you can skip handling 
        //// this event and the system will take care of closing it. Y ou would only need to handle this event 
        //// if you are doing something else with it other than letting it write to where you specified in that 
        //// FileStream, such as post-processing or similar
    }

    /// <summary>
    ///
e.DocumentStream will contain the entire document being saved
    ///
the DocumentStreamWritten will ALWAYS fire before the AnnotationStreamWritten
    ///
Even thought this event has e.AnnotationStream.. it will always be null when
    ///
DocumentStreamWritten fires.. use the AnnotationStreamWritten event to handle the AnnotationStream
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
    void
WebDocViewerHandler_DocumentStreamWritten(
object
sender,
DocumentSaveEventArgs e)
    {
        //// You should rewind first, then you can store in a DB or similar
        //// EXAMPLE: passed am e.MemoryStream for writing to a DB:

        //MemoryStream docStream = e.DocumentStream as MemoryStream
        //if (docStream != null)
        //{
        // docStream.Seek(0, SeekOrigin.Begin);
        // SomeMethodToStoreByteArrayToDb(docStream.ToArray());
        //}

        //// NOTE: if you set e.DocumentStream to a file stream in the DocumentSave you can skip handling 
        //// this event and the system will take care of closing it you would only need to handle this event 
        //// if you are doing something else with it other than letting it write to where you specified in that 
        //// FileStream such as post-processing or similar
    }

    /// <summary>
    ///
The ResolveDocumentUri event is only needed if you've manually handled DocumentInfoRequested and ImageRequested 
    ///
so that the e.FilePath is not pointing to the original document (before any alterations like rotation/deletion/reordering etc..)
    ///
The event fires on save
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
    void
WebDocViewerHandler_ResolveDocumentUri(
object
sender,
ResolveDocumentUriEventArgs
e)
    {
        ////EXAMPLE:
        // e.DocumentStream = SomeMethodThatReturnsAValidStreamObjectContainingTheFullOriginal(e.DocumentUri);
    }

    /// <summary>
    ///
Fires when a source page stream is requested while performing save operation
    ///
During document save it is necessary to get the source document pages to combine them into the destination stream.
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
    void
WebDocViewerHandler_ResolvePageUri(
object
sender,
ResolvePageUriEventArgs
e)
    {
        //// it is often best to leave this unhandled.. the ResolveDocumentUri is usually sufficient for situations where 
        //// The time to use this event is if your original opened document is a 
        //// c ombination of multiple different source streams/documents: it is for very advanced use cases
    }
    #endregion
DOCUMENT SAVING EVENTS

    #region OTHER EVENTS
    ////     The events in this section are almost never used manually by customers..
     //// They may have some use in extremely difficult/complex use cases, but for the most part should be left
     //// un-handled in your custom handler .. let the control use its defaults
    /// <summary>
    ///
Fires when a document release stream occurs on document save. This event is raised only for streams that were provided in ResolveDocumentUri event.
    ///
After document save operation it is necessary to release the obtained in ResolveDocumentUri event document streams. Fires once for each stream.
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
     void
WebDocViewerHandler_ReleaseDocumentStream( object sender, ResolveDocumentUriEventArgs e)
    {
         //// Usually just leaving this to the default works fine...  
         //// e.DocumentUri contains the original request uri (path) for the document
         //// The default activity will pretty much be
         //e.DocumentStream.Close();
         //e.DocumentStream.Dispose();
     }

    /// <summary>
    ///
Fires when a page release stream occurs on document save.
    ///
This event is raised only for streams that were provided in ResolvePageUri event.
    ///
</summary>
    ///
<param name="sender"></param>
    ///
<param name="e"></param>
    void
WebDocViewerHandler_ReleasePageStream(
object
sender,
ResolvePageUriEventArgs
e)
    {
        //// Consider leaving this event alone as the default built in handling works well
        //// e.DocumentPageIndex - The index of the page in the DocumentStream if it is a multi-page document. Default value is 0.
        //// e.DocumentUri - the original document location / path that was passed in
        //// e.SourcePageIndex - The requested index of the page in the document.
        //// e.DocumentStream - Stream used to override the default file system save while saving the document.
        //// Setting this property will take precedence over using the DocumentUri property.

        //// Manually handling: if the original ResolvePageUri was used and you set e.DocumentUri to an alias value.. do that here
        //// if you provided a DocumentStream in ResolvePageUri, then you may need to call
        //e.DocumentStream.Close();
        //e.DocumentStream.Dispose();
    }
    #endregion OTHER EVENTS
}

A note about .NET Core support

With the release of DotImage 11.0, we have added support for .NET Core web applications under .NET Framework.

.NET Core does not use generic handlers... however, you can still get at the WDV middleware to handle the same types of events.

Please see the following articles for more:
FAQ: Support for ASP.NET Core / .NET Core
INFO: Changes Introduced in DotImage 11.0

Original Article:
Q10347 - HOWTO: Implementing a Custom Web Document Viewer Handler

Details
Last Modified: 6 Years Ago
Last Modified By: Administrator
Type: HOWTO
Rated 1 star based on 1 vote
Article has been viewed 3.4K times.
Options
Also In This Category