RAD PDF - Interactive Demonstrations

This basic implementation demonstrates how to use RAD PDF offline using the Client API and RenderAtClient.

Once this page has loaded, you can disconnect from the internet and refresh. You will see that the rest of www.radpdf.com will not properly load, but the demo RAD PDF implementation will!

When reconnected to the network, your code can send the cached save data back to the server and the PDF updated.

RAD PDF Sample Source Files

Default.aspx

<%@ Page Language="C#" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register Assembly="RadPdf" Namespace="RadPdf.Web.UI" TagPrefix="radPdf" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>RAD PDF Sample</title>
    <script type="text/javascript">
    var rpApi,
        swActive;

    //Set PdfWebControl / PdfWebControlLite property OnClientLoad="onRadPdfLoad(this)"
    function onRadPdfLoad(api)
    {
        rpApi = (rpApi || api);

        if( !rpApi || !swActive )
        {
            return;
        }

        var a = rpApi.getOfflineURLs().toCache.slice();

        //Also cache this page (your implementation may do this differently)
        a.push(window.location.href);

        swActive.postMessage(
        {
            action : "cache-urls",
            urls : a
        });
    }

    if ("serviceWorker" in navigator)
    {
        navigator.serviceWorker.register("offline-service-worker.js?ver=3.48.0.0").then(function(reg)
        {
            var sw;

            if(reg.installing)
            {
                console.log("Service worker installing");
                sw = reg.installing;
            }
            else if(reg.waiting)
            {
                console.log("Service worker installed");
                sw = reg.waiting;
            }
            else if(reg.active)
            {
                console.log("Service worker active");
                swActive = reg.active;

                onRadPdfLoad();
            }

            if( sw )
            {
                sw.addEventListener("statechange", function(e)
                {
                    if( ("activated" == e.target.state) && !swActive )
                    {
                        swActive = sw;

                        onRadPdfLoad();
                    }
                });
            }

            navigator.serviceWorker.addEventListener("message", function(evt)
            {
                // if message sent from SW is notification of caching done
                console.log("Document cached for offline use!");
            });
        }).catch(function(error)
        {
            // registration failed
            console.log("Registration failed with " + error);
        });
    }
    else
    {
        console.log("Service workers are not supported! (You may be in private browsing mode)");
    }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <radPdf:PdfWebControlLite id="PdfWebControl1" runat="server" height="600px" width="100%" RenderAtClient="true" OnClientLoad="onRadPdfLoad(this);" />
    </div>
    </form>
</body>
</html>

Default.aspx.cs

using System;

using RadPdf.Exceptions;

partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string documentKey = Request["dk"];

        if (!string.IsNullOrEmpty(documentKey))
        {
            //Attempt to use old document key
            try
            {
                this.PdfWebControl1.DocumentKey = documentKey;
            }
            catch (RadPdfDocumentKeyNotFoundException)
            {
                documentKey = null;
            }
        }

        //If a document key was used to load the document
        if (string.IsNullOrEmpty(documentKey))
        {
            //Get PDF as byte array from file (or database, browser upload, remote storage, etc)
            byte[] pdfData = System.IO.File.ReadAllBytes(@"C:\demo.pdf");

            //Load PDF byte array into RAD PDF
            this.PdfWebControl1.CreateDocument("Document Name", pdfData);

            //Add dk parameter to query string for proper offline use and reload page
            Response.Redirect("/demo/offline/?dk=" + this.PdfWebControl1.DocumentKey, true);
            return;
        }
    }
}

offline-service-worker.js


/*
RAD PDF Service Worker / Offline Cache Example

Copyright (c) 2022 - 2024, Red Software (www.redsoftware.com)
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Red Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/
const ver = (new URL(self.location)).searchParams.get("ver");

const cache_all = false; //set to true if this service should cache all HTTP requests

const cache_base = "RadPdf-v-";

const cache_name = cache_base + ver;

const handler = "RadPdf.axd";

const ignored_parameters = //to ignore the control name, set normalize_urls to true and add "cn" and "un" to this list
[
    "r"
];

const normalize_urls = false; //set to true if query string parameters might change order and should be sorted for caching

const server_resources_url = handler + "?rt=40";

var getSearchParams = function(url)
{
    let ix = url.indexOf(handler + "?");

    let search = url.substr(ix + handler.length + 1);

    return new URLSearchParams(search);
};

var normalizeURL = function(request)
{
    if(!normalize_urls)
    {
        return request;
    }

    const url = (typeof request === "string") ? request : request.url;

    if( url )
    {
        let ix = url.indexOf(handler + "?");

        //only normalize if a RAD PDF handler
        if( ix >= 0 )
        {
            let sp = getSearchParams(url);

            //remove ignored parameters
            for( let p of ignored_parameters )
            {
                if( sp.has(p) )
                {
                    sp.delete(p);
                }
            }

            //normalize parameter order
            sp.sort();

            return url.substr(0, ix + handler.length + 1) + sp.toString();
        }
    }

    return request;
};

var normalizeURLs = function(urls)
{
    let ret = [];

    for( let u of urls )
    {
        ret.push( normalizeURL(u) );
    }

    return ret;
};

self.addEventListener("install", function(event)
{
  event.waitUntil(
    //make request to get list of resources from server
    fetch(server_resources_url).then(function(response)
    {
        return response.json().then(function(data)
        {
            if( data.version != ver )
            {
                throw "Server RAD PDF version does not match version in offline-service-workers.js! Update const ver.";
            }

            return caches.open(cache_name).then(function(cache)
            {
                return cache.addAll(normalizeURLs(data.urls));
            });
        });
    })
  );
});

self.addEventListener("fetch", function(event)
{
    let normalizedURL = normalizeURL(event.request);

    event.respondWith(event.request.clone().text().then(function(bodyText)
    {
        let cacheSaveRequest = function()
        {
            let sp = getSearchParams(event.request.url);

            //if a save POST
            if( ("POST" == event.request.method) && ("9" == sp.get("rt")) && ("save" == sp.get("t")) )
            {
                caches.open(cache_name).then(function(cache)
                {
                    let res = new Response(bodyText);

                    let url = handler + "?rt=6&dk=" + sp.get("dk");

                    cache.put(normalizeURL(url), res);
                });
                return true;
            }
            return false;
        };

        return fetch(event.request).then(function(response)
        {
            cacheSaveRequest();

            //do not cache POSTs
            if( "POST" != event.request.method )
            {
                if( cache_all )
                {
                    // response may be used only once; we need to save clone to put one copy in cache and serve second one
                    let responseClone = response.clone();
        
                    caches.open(cache_name).then(function(cache)
                    {
                        cache.put(normalizedURL, responseClone);
                    });
                }
            }

            return response;

        }).catch(function()
        {
            if( "POST" == event.request.method )
            {
                if( cacheSaveRequest() )
                {
                    return new Response("1|offline");
                }

                return new Response("0|Browser offline. Please try again when you have a connection.");
            }

            return caches.open(cache_name).then(function(cache)
            {
                return cache.match(normalizedURL).then(function(response)
                {
                    // caches.match() always resolves, but in case of success response will have value
                    if( response !== undefined )
                    {
                        return response;
                    }
                    else
                    {
                        return undefined;
                    }
                });
            });
        });
    }));
});

self.addEventListener("message", function(event)
{
    var data = event.data;

    switch( data.action )
    {
        case "cache-urls":

            caches.open(cache_name).then(function(cache)
            {
                cache.addAll(normalizeURLs(data.urls)).then(function()
                {
                    //caching is done. send message back to client
                    event.source.postMessage("cache-done");
                });
            });
            break;
    }
});

Default.aspx

<%@ Page Language="VB" CodeFile="Default.aspx.vb" Inherits="_Default" %>

<%@ Register Assembly="RadPdf" Namespace="RadPdf.Web.UI" TagPrefix="radPdf" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>RAD PDF Sample</title>
    <script type="text/javascript">
    var rpApi,
        swActive;

    //Set PdfWebControl / PdfWebControlLite property OnClientLoad="onRadPdfLoad(this)"
    function onRadPdfLoad(api)
    {
        rpApi = (rpApi || api);

        if( !rpApi || !swActive )
        {
            return;
        }

        var a = rpApi.getOfflineURLs().toCache.slice();

        //Also cache this page (your implementation may do this differently)
        a.push(window.location.href);

        swActive.postMessage(
        {
            action : "cache-urls",
            urls : a
        });
    }

    if ("serviceWorker" in navigator)
    {
        navigator.serviceWorker.register("offline-service-worker.js?ver=3.48.0.0").then(function(reg)
        {
            var sw;

            if(reg.installing)
            {
                console.log("Service worker installing");
                sw = reg.installing;
            }
            else if(reg.waiting)
            {
                console.log("Service worker installed");
                sw = reg.waiting;
            }
            else if(reg.active)
            {
                console.log("Service worker active");
                swActive = reg.active;

                onRadPdfLoad();
            }

            if( sw )
            {
                sw.addEventListener("statechange", function(e)
                {
                    if( ("activated" == e.target.state) && !swActive )
                    {
                        swActive = sw;

                        onRadPdfLoad();
                    }
                });
            }

            navigator.serviceWorker.addEventListener("message", function(evt)
            {
                // if message sent from SW is notification of caching done
                console.log("Document cached for offline use!");
            });
        }).catch(function(error)
        {
            // registration failed
            console.log("Registration failed with " + error);
        });
    }
    else
    {
        console.log("Service workers are not supported! (You may be in private browsing mode)");
    }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <radPdf:PdfWebControlLite id="PdfWebControl1" runat="server" height="600px" width="100%" RenderAtClient="true" OnClientLoad="onRadPdfLoad(this);" />
    </div>
    </form>
</body>
</html>

Default.aspx.vb

Option Explicit On
Option Strict On

Imports RadPdf.Exceptions

Partial Class _Default
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Dim documentKey As String = Request("dk")

        If Not string.IsNullOrEmpty(documentKey)

            ' Attempt to use old document key
            Try

                Me.PdfWebControl1.DocumentKey = documentKey

            Catch ex As RadPdfDocumentKeyNotFoundException

                documentKey = Nothing

            End Try
        End If

        ' If a document key was used to load the document
        If string.IsNullOrEmpty(documentKey) Then

            ' Get PDF as byte array from file (or database, browser upload, remote storage, etc)
            Dim pdfData() As Byte = System.IO.File.ReadAllBytes("C:\demo.pdf")

            ' Load PDF byte array into RAD PDF
            Me.PdfWebControl1.CreateDocument("Document Name", pdfData)

            ' Add dk parameter to query string for proper offline use and reload page
            Response.Redirect("/demo/offline/?dk=" + Me.PdfWebControl1.DocumentKey, True)
            Return
        End If
    End Sub
End Class

offline-service-worker.js


/*
RAD PDF Service Worker / Offline Cache Example

Copyright (c) 2022 - 2024, Red Software (www.redsoftware.com)
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Red Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/
const ver = (new URL(self.location)).searchParams.get("ver");

const cache_all = false; //set to true if this service should cache all HTTP requests

const cache_base = "RadPdf-v-";

const cache_name = cache_base + ver;

const handler = "RadPdf.axd";

const ignored_parameters = //to ignore the control name, set normalize_urls to true and add "cn" and "un" to this list
[
    "r"
];

const normalize_urls = false; //set to true if query string parameters might change order and should be sorted for caching

const server_resources_url = handler + "?rt=40";

var getSearchParams = function(url)
{
    let ix = url.indexOf(handler + "?");

    let search = url.substr(ix + handler.length + 1);

    return new URLSearchParams(search);
};

var normalizeURL = function(request)
{
    if(!normalize_urls)
    {
        return request;
    }

    const url = (typeof request === "string") ? request : request.url;

    if( url )
    {
        let ix = url.indexOf(handler + "?");

        //only normalize if a RAD PDF handler
        if( ix >= 0 )
        {
            let sp = getSearchParams(url);

            //remove ignored parameters
            for( let p of ignored_parameters )
            {
                if( sp.has(p) )
                {
                    sp.delete(p);
                }
            }

            //normalize parameter order
            sp.sort();

            return url.substr(0, ix + handler.length + 1) + sp.toString();
        }
    }

    return request;
};

var normalizeURLs = function(urls)
{
    let ret = [];

    for( let u of urls )
    {
        ret.push( normalizeURL(u) );
    }

    return ret;
};

self.addEventListener("install", function(event)
{
  event.waitUntil(
    //make request to get list of resources from server
    fetch(server_resources_url).then(function(response)
    {
        return response.json().then(function(data)
        {
            if( data.version != ver )
            {
                throw "Server RAD PDF version does not match version in offline-service-workers.js! Update const ver.";
            }

            return caches.open(cache_name).then(function(cache)
            {
                return cache.addAll(normalizeURLs(data.urls));
            });
        });
    })
  );
});

self.addEventListener("fetch", function(event)
{
    let normalizedURL = normalizeURL(event.request);

    event.respondWith(event.request.clone().text().then(function(bodyText)
    {
        let cacheSaveRequest = function()
        {
            let sp = getSearchParams(event.request.url);

            //if a save POST
            if( ("POST" == event.request.method) && ("9" == sp.get("rt")) && ("save" == sp.get("t")) )
            {
                caches.open(cache_name).then(function(cache)
                {
                    let res = new Response(bodyText);

                    let url = handler + "?rt=6&dk=" + sp.get("dk");

                    cache.put(normalizeURL(url), res);
                });
                return true;
            }
            return false;
        };

        return fetch(event.request).then(function(response)
        {
            cacheSaveRequest();

            //do not cache POSTs
            if( "POST" != event.request.method )
            {
                if( cache_all )
                {
                    // response may be used only once; we need to save clone to put one copy in cache and serve second one
                    let responseClone = response.clone();
        
                    caches.open(cache_name).then(function(cache)
                    {
                        cache.put(normalizedURL, responseClone);
                    });
                }
            }

            return response;

        }).catch(function()
        {
            if( "POST" == event.request.method )
            {
                if( cacheSaveRequest() )
                {
                    return new Response("1|offline");
                }

                return new Response("0|Browser offline. Please try again when you have a connection.");
            }

            return caches.open(cache_name).then(function(cache)
            {
                return cache.match(normalizedURL).then(function(response)
                {
                    // caches.match() always resolves, but in case of success response will have value
                    if( response !== undefined )
                    {
                        return response;
                    }
                    else
                    {
                        return undefined;
                    }
                });
            });
        });
    }));
});

self.addEventListener("message", function(event)
{
    var data = event.data;

    switch( data.action )
    {
        case "cache-urls":

            caches.open(cache_name).then(function(cache)
            {
                cache.addAll(normalizeURLs(data.urls)).then(function()
                {
                    //caching is done. send message back to client
                    event.source.postMessage("cache-done");
                });
            });
            break;
    }
});
RAD PDF is a Red Software product - ©2007-2024 Red Software