65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ASP.NET Imaging SDK 构建基于 Web 的图像查看器

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2009年11月2日

CPOL

15分钟阅读

viewsIcon

28948

本文讨论了软件技术和文档管理行业的发展趋势。在此过程中,我们将构建一个简单但功能丰富的基于 Web 的应用程序,用于查看 PDF 文档。它将基于我们的 ASP.NET 图像查看技术,该技术包含在 ImageGear for .NET 中。

作为市场领导者,Accusoft Pegasus 经常与开发人员和 IT 人员沟通他们面临的问题。以下是我们听到的一些陈述:

“我们的网站上有大量的产品手册。我们希望客户能在与我们网站主题一致的查看器中查看它们,而不是使用 Adobe 的 Acrobat Reader。”

“我在一家服务公司工作,该公司为其他公司扫描文档。我们正在构建一个系统,该系统将允许我们为客户托管文档管理系统,以便我们进入 SaaS 市场。”

“我们开发了自己的客户端内容管理系统,现在我们的用户需要能够通过 Web 快速查看 PDF、TIFF 和 JPEG 文档。”

这听起来像你吗?越来越多的情况下,这个问题的答案是“是的”。曾经仅限于特定行业“内容管理系统”的工具和技术,正迅速成为大多数人日常生活的一部分。

本文讨论了软件技术和文档管理行业的发展趋势。在此过程中,我们将构建一个简单但功能丰富的基于 Web 的应用程序,用于查看 PDF 文档。它将基于我们 ImageGear for .NET 产品 17 版中的 ASP.NET 图像查看技术。您可以从 Accusoft Pegasus 网站 下载 ImageGear for .NET。

趋势

本文讨论了两个主要趋势,并提供详细信息和示例:面向浏览器、客户端开发的趋势,以及功能更强大、更易于使用的产品的趋势。

过去一年,许多不同的努力(HTML 5 的 Canvas 对象、SVG、JavaScript 性能提升和硬件渲染)汇聚在一起,使得构建一个在浏览器中运行、服务器支持最少且功能强大的查看器成为可能。ImageGear for .NET 仍然使用服务器来执行读取 PDF 和 JPEG 2000 图像等操作,但它在浏览器中执行缩放、平移和旋转等操作,从而实现了一个响应速度非常快、服务器负担较小的应用程序。

软件开发行业一直在努力以更少的开发工作量完成更多任务。Web 是这一过程中的一个重要进步,它使您能够轻松构建跨越全球的复杂应用程序。一些最大的节省包括您无需创建安装程序(通常称为“零占用空间”应用程序),并且您可以更轻松地针对各种客户端平台。

这是应用程序开发世界激动人心的时刻;变化正在发生,未来看起来比以往任何时候都更加光明。虽然变化会带来困难,但在这种情况下,它也为企业和软件开发人员带来了丰富的选择。其中最好的选择之一就是 ASP.NET 开发。本文的重点是我们 ASP.NET 图像查看技术的最新版本,该技术包含在我们 ImageGear for .NET 产品 17 版中。

应用程序描述

学习某项内容最好的方法之一就是“直接动手”,所以我们将构建一个真实的、尽管简单的应用程序。此应用程序将允许用户浏览和查看一系列白皮书,但很容易想象这个集合可以是产品手册、工程图纸、书籍或医疗保险文件。

对于几乎任何此类系统,最重要的功能是搜索和查看。最终,用户想要做的就是查看他们感兴趣的文档。搜索功能至关重要,但它仍然只是达到目的的手段:用户需要某种方式告诉系统他们想要查看哪个文档。

虽然某种形式的搜索功能在大多数此类系统中都很常见,但该搜索功能的细节可能差异很大。对于提供产品手册的系统,用户通常会知道产品的型号或名称。对于提供医疗保险记录的系统,用户通常会知道特定人员的 ID,并有一个他们感兴趣的日期范围。在我们的例子中,用户通常会有一个他们感兴趣的主题。他们也可以知道作者或产品的名称。目前,我们将实现简单的按主题搜索,并将其他选项留给系统的未来版本。

虽然我们可以将系统实现为一个具有搜索和查看模式的单个网页,但创建单独的搜索和查看页面会更容易。搜索页面将显示一个关键词列表,以及匹配所选关键词的白皮书缩略图。用户可以单击列表中的任何一个关键词,页面将立即过滤白皮书库以进行匹配。我们将定义一个特殊关键词“Recent”,并将其设置为初始关键词。下面的屏幕截图显示了选择“Recent”关键词的初始状态。

image001.jpg

图 1:搜索页面

对于查看页面,我们将采用相当简洁的用户界面,一次显示白皮书的一页,底部边缘有一组按钮。对于一个健壮的应用程序,您会想要一些可选模式,例如一次显示两页(通常称为“2-up”视图),或者可能是每页的缩略图列表。

image002.jpg

图 2:查看页面

构建应用程序

在开始编码之前,您应该下载并安装 ImageGear for .NET 17 版。您可以在 Accusoft Pegasus 网站上下载评估版。安装后,您应该查看 ImageGear 帮助文件第 1 卷的“入门”部分中的教程(安装程序会在您的“开始”菜单中放置一个链接)。该教程将向您展示如何为应用程序获得许可和部署。本文不会重复这些信息。

我们将从查看页面开始编码。我将页面固定大小,以便我们专注于查看器而不是 CSS,但您应该考虑使用流式布局。

我们将页面命名为 View.aspx,内容如下。

列表 1:View.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="View.aspx.cs"
    Inherits="View" %>
<%@ Register Assembly="ImageGear17.Web" Namespace="ImageGear.Web.UI"
    TagPrefix="imgear" %>

<!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>Resource Center Document Viewer</title>

    <script src="js/jquery-1.3.2.js" type="text/javascript"></script>
    <script src="js/View.js" type="text/javascript"></script>
    <link href="css/Common.css" rel="stylesheet" type="text/css" />
    <link href="css/View.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div class="Application">
        <asp:ScriptManager ID="ScriptManager1" runat="server" />
        <div id="PageHeader">
            <img class="SectionLeft" src="images/HeaderLeft.gif" />
            <div class="SectionCenter">
                <img src="images/HeaderLogo.jpg" />
                <span id="PageHeaderTitle1" runat="server"></span>
                <span id="PageHeaderTitle2" runat="server">
                </span>
            </div>
            <img class="SectionRight" src="images/HeaderRight.gif" />
        </div>
        <div id="PageBody">
            <img class="SectionLeft" src="images/BodyLeft.gif" />
            <div class="SectionCenter">
                <imgear:ImageView ID="Viewer" runat="server"
                    CssClass="Viewer" />
                <div class="Toolbar">
                    <img src="images/ButtonSelectAndZoom.png"
                        id="ActionSelectAndZoom" />
                    <img src="images/ButtonHandPan.png"
                        id="ActionHandPan" />
                    <img src="images/ButtonMagnifier.png"
                        id="ActionMagnifier" />
                    <img src="images/ButtonPreviousPage.png"
                        id="ActionPreviousPage" />
                    <img src="images/ButtonNextPage.png"
                        id="ActionNextPage" />
                    <img src="images/ButtonZoomIn.png"
                        id="ActionZoomIn" />
                    <img src="images/ButtonZoomOut.png"
                        id="ActionZoomOut" />
                    <img src="images/ButtonRotateLeft.png"
                        id="ActionRotateLeft" />
                    <img src="images/ButtonRotateRight.png"
                        id="ActionRotateRight" />
                    <img src="images/ButtonFullImage.png"
                        id="ActionFullImage" />
                    <img src="images/ButtonFullWidth.png"
                        id="ActionFullWidth" />
                    <img src="images/ButtonActualSize.png"
                        id="ActionActualSize" />
                </div>
            </div>
            <img class="SectionRight" src="images/BodyRight.gif" />
        </div>
        <div id="PageFooter">
            <img class="SectionLeft" src="images/FooterLeft.gif" />
            <div id="FooterCenter" class="SectionCenter">
                <img src="images/FooterText.gif" />
            </div>
            <img class="SectionRight" src="images/FooterRight.gif" />
        </div>
    </form>
</body>
</html>

此页面最重要的部分是 ImageView 控件,它将构建一个客户端控件,让您查看图像。查看页面的最重要的任务是打开正确的文档。我们将通过在页面的代码隐藏中打开文档的第一页来启动该过程。

列表 2:View.aspx.cs

using System;
using System.Linq;
using System.Web.Configuration;

public partial class View : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string cs = WebConfigurationManager
            .ConnectionStrings["ImageDatabaseConnectionString"]
            .ConnectionString;
        using (ImageDatabase db = new ImageDatabase(cs))
        {
            // Pull the database ID of the requested document out of
            // the query string.
            Guid Identifer = new
                Guid(Request.QueryString["Identifier"]);

            // Get the full information for the specified document.
            var doc =
                (from document in db.Documents
                where document.DocumentID == Identifer
                select document).First();

            // Place the document title and author in the page header.
            PageHeaderTitle1.InnerText = doc.Title;
            PageHeaderTitle2.InnerText = doc.Author;

            // Tell the viewer which document to load.
            Viewer.ImageIdentifier = doc.DocumentID.ToString();
        }
    }
}

我们将使用查询字符串来指示要查看哪个文档。显然,还有许多其他选项可用于将该信息传递给页面。最后三行代码最重要,它们设置页面标题和 ImageIdentifier 属性。由于此应用程序将文档保存在数据库中,因此您需要将图像标识符设置为保存文档的表的键字段。

虽然这可能看起来太简单了,但实际上并非如此。ImageGear for .NET 所需要的就是数据库键以及 web.config 中的少量配置(这将在本文的结尾部分介绍)。ImageGear for .NET 包括一个 ASP.NET 处理程序,该处理程序将查询数据库以获取您的图像。它还会自动确定文档的文件类型(PDF、TIFF、JPEG 等)并进行相应处理。

我们需要一些 CSS 来布局页面。部分 CSS 是两个页面共有的,因此我们将将其放入一个名为 Common.css 的单独文件中。此 CSS 用于布局页面的页眉、正文和页脚。它与本文的主题没有直接关系,所以我省略了它以节省空间。

其余 CSS 进入一个名为 View.css 的文件中。它只是指示查看页面各个元素的定位、大小和字体。

列表 3:View.css

.Viewer {
    position:absolute;
    width:928px;
    height:500px;
}
.Toolbar {
    position:absolute;
    top:510px;
    left:240px;
}
#PageHeaderTitle1 {
    position:absolute;
    left:240px;
    top:8px;
    font:normal normal bolder 26px Verdana;
}
#PageHeaderTitle2 {
    position:absolute;
    left:240px;
    top:76px;
    font:normal normal bolder 24px Verdana;
    color:#83847B;
}

最后,这个页面的核心是运行查看器的 JavaScript。我们正在使用 jQuery 使其更轻松。说到 jQuery,它是我之前提到的那些趋势之一。如今,大多数 JavaScript 开发都依赖于一个或多个 JavaScript 工具包。jQuery 是最受欢迎的之一,特别是对于 ASP.NET 开发人员,因为 Microsoft 正式支持它。jQuery 在隐藏浏览器之间的差异方面做得很好,因此您可以专注于更重要的问题。它还具有一些强大但易于使用的函数,用于对 HTML 进行动画处理和调整。

我非常喜欢 jQuery,因为它使浏览器管理起来容易得多。无论您使用 jQuery 还是其他 JavaScript 库,都有一种明显的趋势是构建依赖于 jQuery 等其他库的网页,以使生活更轻松。

jQuery 也是另一个趋势的例子:更强大、更易于使用的 API。查看下面的 JavaScript,可以明显看出 ImageView 控件也遵循了这一趋势。在设计 API 时,我们从您的角度出发,努力使 API 满足您的需求。与 jQuery 一样,我们在后台做了很多困难的事情,但我们尽力隐藏了这些,以便您可以专注于应用程序的其余部分。

列表 4:View.js

// This variable will be an alias for the ImageGear namespace.
// Using an alias makes the rest of the JavaScript shorter.
var ig;

// Handle the toolbar click events.
function toolbarClickHandler() {
    var cs;
    // Use the ASP.NET 3.5 function, $find(), to get a reference to the
    // ImageView client-side control.
    var iv = $find('Viewer');

    switch (this.id) {
        case 'ActionSelectAndZoom':
            // Set the viewer's left-mouse-button tool to zoom in on a
            // selected rectangle. The user will be able to click and
            // drag a rectangle. When the user releases the mouse
            // button, the viewer will zoom in on the selected area.
            iv.set_mouseTool(ig.MouseTool.RectangleZoom);
            break;

        case 'ActionHandPan':
            // Set the viewer's left-mouse-button tool to pan the
            // image. The user will be able to click and drag the
            // image.
            iv.set_mouseTool(ig.MouseTool.HandPan);
            break;

        case 'ActionMagnifier':
            // Set the viewer's left-mouse-button tool to show the
            // magnifying glass. When the user clicks the mouse button,
            // the viewer will display a magnifying glass. The
            // magnifying glass will follow the cursor until the user
            // releases the mouse button.
            iv.set_mouseTool(ig.MouseTool.Magnifier);
            break;

        case 'ActionPreviousPage':
            if (iv.get_imageIsOpen()) {
                // Get the current state of the viewer.
                // The current state includes the page index of the
                // most-recently-opened image.
                cs = iv.get_currentState();

                if (cs.imagePageIndex > 0) {
                    // Open the previous page, using the full width of
                    // the viewer.
                    iv.openImage({
                        imageIdentifier: cs.imageIdentifier,
                        imagePageIndex: cs.imagePageIndex - 1,
                        viewFitType: ig.FitType.FullWidth
                    });
                }
            }
            break;

        case 'ActionNextPage':
            if (iv.get_imageIsOpen()) {
                // Get the current state of the viewer.
                // The current state includes the page index of the
                // most-recently-opened image.
                cs = iv.get_currentState();

                if (cs.imagePageIndex < iv.get_imagePageCount() - 1) {
                    // Open the next page, using the full width of the
                    // viewer.
                    iv.openImage({
                        imageIdentifier: cs.imageIdentifier,
                        imagePageIndex: cs.imagePageIndex + 1,
                        viewFitType: ig.FitType.FullWidth
                    });
                }
            }
            break;

        case 'ActionZoomIn':
            if (iv.get_imageIsOpen()) {
                // Zoom in 50%.
                // The portion of the image in the center of the viewer
                // will remain in the center of the viewer. The user
                // can also zoom in using control-mouse-wheel and the
                // portion of the image under the cursor will remain in
                // the same position.
                iv.zoomIn(1.5);
            }
            break;

        case 'ActionZoomOut':
            if (iv.get_imageIsOpen()) {
                // Zoom out 50%.
                // The portion of the image in the center of the viewer
                // will remain in the center of the viewer. The user
                // can also zoom out using control-mouse-wheel and the
                // portion of the image under the cursor will remain in
                // the same position.
                iv.zoomOut(1.5);
            }
            break;

        case 'ActionRotateLeft':
            if (iv.get_imageIsOpen()) {
                // Rotate counter-clockwise 90 degrees.
                // The portion of the image in the center of the viewer
                // will remain in the center of the viewer.
                iv.rotate(-90);
            }
            break;

        case 'ActionRotateRight':
            if (iv.get_imageIsOpen()) {
                // Rotate clockwise 90 degrees.
                // The portion of the image in the center of the viewer
                // will remain in the center of the viewer.
                iv.rotate(90);
            }
            break;

        case 'ActionFullImage':
            if (iv.get_imageIsOpen()) {
                // Display the full image in the viewer's area.
                iv.fitImage(ig.FitType.FullImage);
            }
            break;

        case 'ActionFullWidth':
            if (iv.get_imageIsOpen()) {
                // Adjust the scale to use the full width of the
                // viewer's area. The top of the image will be scrolled
                // into view, but the bottom may not be visible. If the
                // image is currently rotated, it will remain rotated.
                iv.fitImage(ig.FitType.FullWidth);
            }
            break;

        case 'ActionActualSize':
            if (iv.get_imageIsOpen()) {
                // Display the image actual size. The top-left corner
                // of the image will be scrolled into view, but the
                // right and bottom may not be visible. If the image is
                // currently rotated, it will remain rotated.
                iv.fitImage(ig.FitType.ActualSize);
            }
            break;
    }
}

// This jQuery function will run when the page is ready.
// It is similar to the ASP.NET 3.5 pageLoad() function.
$(function() {
    // Create the ImageGear namespace alias.
    ig = ImageGear.Web.UI;

    // Using jQuery, assign an event handler to each toolbar button.
    // Handling double-click makes the button behave properly when a
    // user clicks quickly in IE.
    $('.Toolbar > img')
        .click(toolbarClickHandler)
        .dblclick(toolbarClickHandler);
});

JavaScript 真的没多少内容。您需要做的就是将用户界面连接到查看器,即可开始使用。

搜索页面会更有趣一些。我们将在客户端生成大部分内容,而无需进行页面刷新或使用 ASP.NET UpdatePanel。Web 开发中最令人兴奋和最有前途的趋势之一是构建在浏览器中运行的完整应用程序的趋势,只需服务器提供少量协助。这有时被称为“富 Internet 应用程序”或 RIA。即使没有服务器,现代 Web 浏览器也足够快、足够强大,可以被视为一个开发环境,就像 Windows、Linux 或 Mac 一样。搜索页面将完全是这种“页面中的应用程序”。

ASP.NET 3.5 引入了一个强大的客户端框架来构建控件,而 ImageGear for .NET 完全支持这种以客户为中心的方法。这使得构建客户端应用程序而无需进行更传统的 Web 应用程序所需的全部或部分页面刷新变得非常容易。ASP.NET MVC 更进一步,消除了服务器端 Web Forms 控件。由于 ImageGear for .NET 包含一个独立的客户端控件,因此您可以像使用 ASP.NET WebForms 一样轻松地将其与 ASP.NET MVC 结合使用。

再次,让我们从页面开始。将其命名为 Search.aspx 并将以下内容放入其中。

列表 5:Search.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Search.aspx.cs"
    Inherits="Search" %>
<%@ Register Assembly="ImageGear17.Web" Namespace="ImageGear.Web.UI"
    TagPrefix="imgear" %>

<!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>Resource Center</title>
    <script src="js/jquery-1.3.2.js" type="text/javascript"></script>
    <script src="js/Search.js" type="text/javascript"></script>
    <link href="css/Common.css" rel="stylesheet" type="text/css" />
    <link href="css/Search.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server"
            EnablePageMethods="true" />
        <div id="PageHeader">
            <img class="SectionLeft" src="images/HeaderLeft.gif" />
            <div class="SectionCenter">
                <img src="images/HeaderLogo.jpg" />
                <span id="PageHeaderTitle1">Resource Center</span>
            </div>
            <img class="SectionRight" src="images/HeaderRight.gif" />
        </div>
        <div id="PageBody">
            <img class="SectionLeft" src="images/BodyLeft.gif" />
            <div class="SectionCenter">
                <div id="Header">
                    <div>Keyword:
                        <span id="SelectedKeyword"></span>
                    </div>
                </div>
                <div id="Keywords">
                    <p id="KeywordsTitle">Keyword List:</p>
                    <hr/>
                    <asp:ListView runat="server" ID="KeywordListView">
                        <LayoutTemplate>
                            <div id="KeywordList">
                                <p runat="server"
                                    id="itemPlaceholder"/>
                            </div>
                        </LayoutTemplate>
                        <ItemTemplate>
                            <span class='<%# Eval("FontClass") %>'>
                                <%# Eval("Keyword") %></span>
                        </ItemTemplate>
                    </asp:ListView>
                </div>
                <div id="Thumbnails">
                    <div></div>
                </div>
            </div>
            <div id="ThumbnailTemplate" style="display:none">
                <imgear:ThumbnailView ID="ThumbnailViewTemplate"
                    runat="server" CssClass="ThumbnailView"/>
                <div class="Thumbnail">
                    <div>
                        <a class="ThumbnailTitle"></a>
                        <div class="ThumbnailImage"></div>
                        <div class="ThumbnailAuthor"></div>
                    </div>
                </div>
            </div>
            <img class="SectionRight" src="images/BodyRight.gif" />
        </div>
        <div id="PageFooter">
            <img class="SectionLeft" src="images/FooterLeft.gif" />
            <div id="FooterCenter" class="SectionCenter">
                <img src="images/FooterText.gif" />
            </div>
            <img class="SectionRight" src="images/FooterRight.gif" />
        </div>
    </div>
    </form>
</body>
</html>

此页面中有几个有趣的地方。第一个是 ListView 控件,它将代表我们的关键词。它将在页面发送到浏览器之前在服务器上生成,但我们稍后会介绍这一点。下一个最有趣的地方是页面中心部分(Thumbnails div)是空的;我们将仅在客户端更新此部分。每个匹配所选关键词的文档都将显示在一个块中,该块的格式由 Thumbnail div 中定义的模板决定。此页面的最后一个有趣功能是 ThumbnailView 控件。我们不会在客户端页面中使用此 ThumbnailView 控件;它只是为了确保 ThumbnailView 的 JavaScript 被下载到浏览器。使用 FireBug 或 Fiddler 捕获该 JavaScript 并将其存储在单独的文件中几乎一样容易。我们将使用的所有 ThumbnailView 控件都将在浏览器中通过 JavaScript 代码构建。

接下来,我们需要一些 CSS 来布局页面。其中大部分只是设置页面各个元素的尺寸、颜色和位置。值得关注的是四种关键词样式。这些样式将区分普通关键词和不常见的关键词。

列表 6:Search.css

.Viewer {
    display:none;
}
#PageHeaderTitle1 {
    position:absolute;
    left:350px;
    top:30px;
    font:normal normal bolder 32px Verdana;
}
#Header {
    width:928px;
    height:70px;
    background-image:url(../images/SearchHeader.gif);
}
#Header > div {
    position:absolute;
    left:18px;
    top:20px;
    font:normal normal bold 16px Verdana;
}
#Header #SelectedKeyword {
    color:#373534;
}
#Keywords {
    position:absolute;
    left:0px;
    top:70px;
    padding: 0px 18px;
    float: left;
    width: 152px;
    height: 405px;
    overflow: auto;
    background-image: url(../images/SearchKeywordList.gif);
}
#KeywordsTitle {
    font:normal normal bold 16px Verdana;
}
#KeywordList {
    color:#373534;
}
#Thumbnails {
    position:absolute;
    left:188px;
    top:70px;
    width:740px;
    height:469px;
    overflow: auto;
}
.Thumbnail {
    position:relative;
    float:left;
    width:354px;
    height:378px;
    background-image:url(../images/SearchThumbnail.gif);
    font:normal normal bold 12px Verdana;
}
.Thumbnail > div {
    padding:18px 18px;
}
.ThumbnailTitle 
{
    display:block;
    height:40px;
}
.ThumbnailImage {
    height:290px;
}
.ThumbnailAuthor {
    color: #83847B;
    height:30px;
}
.HugeKeyword {
    font-size:28px;
    font-weight:800;
}
.LargeKeyword {
    font-size:22px;
    font-weight:600;
}
.MediumKeyword {
    font-size:16px;
    font-weight:400;
}
.SmallKeyword {
    font-size:12px;
    font-weight:200;
}
.Keyword {
    cursor:pointer;
}
.Keyword:hover {
    text-decoration: underline;
}

要理解这个页面,您需要查看我们的数据库模式。它实际上非常简单;我们有两个主要表:一个用于白皮书,一个用于关键词。还有一个表处理关键词和白皮书之间的多对多关系。这是 E-R 图

image003.jpg

图 3:实体-关系图

在进入有趣的部分之前,让我们先处理一下关键词框。在浏览器中生成它并不难,但它在页面生命周期内不会改变,所以我们最好在服务器上创建它。以下是执行此操作的 C# 代码。

列表 7:Search.aspx.cs(第 1 部分)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Configuration;

public partial class Search : System.Web.UI.Page
{
    // This function converts a count of documents into a CSS class.
    private string FontClass(int count, int small, int medium,
        int large)
    {
        if (count > large)
            return "Keyword HugeKeyword";
        else if (count > medium)
            return "Keyword LargeKeyword";
        else if (count > small)
            return "Keyword MediumKeyword";

        return "Keyword SmallKeyword";
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        string cs = WebConfigurationManager 
            .ConnectionStrings["ImageDatabaseConnectionString"]
            .ConnectionString;
        using (ImageDatabase db = new ImageDatabase(cs))
        {
            // Get the keywords and the number of associated documents.
            var keywords =
                from relation in db.DocumentKeywords
                group relation by relation.Keyword into grouping
                select new 
                {
                    Keyword = grouping.Key,
                    Count = grouping.Count()
                };

            // Sort the keywords in descending order by the count.
            var sortedKeywords =
                from keyword in keywords
                orderby keyword.Count descending
                select keyword;

            // Pick the thresholds between the four different sized
            // fonts. One fourth of all the keywords will be huge, one
            // fourth large, etc.
            var arrayOfKeywords = sortedKeywords.ToArray();
            var length = arrayOfKeywords.Length;
            int large = arrayOfKeywords[length / 4].Count;
            int medium = arrayOfKeywords[length * 2 / 4].Count;
            int small = arrayOfKeywords[length * 3 / 4].Count;

            // Sort the keywords in alphabetical order and add the CSS
            // class for each font size.
            var keywordsAndFonts =
                from keyword in sortedKeywords
                orderby keyword.Keyword.Text
                select new
                {
                    Keyword = keyword.Keyword.Text,
                    FontClass = FontClass(keyword.Count, small, medium,
                        large)
                };

            // Data bind the list to the ListView control.
            KeywordListView.DataSource = keywordsAndFonts;
            KeywordListView.DataBind();
        }
    }
}

代码使用 LINQ to SQL 来获取关键词列表以及每个关键词对应的白皮书数量,按数量排序。接下来,我们使用少量过程式代码来确定每个字体大小所需的匹配文档数量,并将更大的字体分配给具有更多相关文档的关键词。实际字体大小通过 CSS 设置。最后,我们将关键词按字母顺序排序,并将它们绑定到关键词 ListView。ListView 控件将处理其余工作。

现在我们需要一些 JavaScript。虽然我们的页面比传统的网页更复杂,但它实际上只需要两件事:一个用于更新页面以匹配所选关键词的函数,以及一种导航到查看器页面的方法。

更新页面的函数是最复杂的。它需要通过复制缩略图块模板来构建显示缩略图块的 HTML,并且需要创建 ThumbnailView 控件来显示每张图像的第一页。但该函数最独特的部分是它需要向 Web 服务器请求与关键字匹配的文档列表。我们将以 ASP.NET 页面方法的形式来实现这一点,以便 ASP.NET 会自动创建一个 JavaScript 代理,让我们能够调用它。我们可以使用 jQuery 进行该 Ajax 调用,但使用 ASP.NET 更简单。

列表 8:Search.js(第 1 部分)

// This variable will be an alias for the ImageGear namespace.
// Using an alias makes the rest of the JavaScript shorter.
var ig;

// This variable holds the most recently selected keyword. It is
// possible for a user to click several keywords then have the page
// methods finish out of order. This variable solves that problem by
// letting the completion function ignore any returned results other
// than the last one. 
var selectedKeyword;

// This variable holds the absolute URL back to the server so the
// ThumbnailView can request images.
var imageHandlerUrl;

// This variable holds a number that increments each time a control is
// created. The value of the number is appended to the ID of the
// control so each has a unique ID.
var controlCounter = 0;

function selectKeyword(keyword)
{
    // Save the new keyword so any other pending select operations can
    // cancel themselves.
    selectedKeyword = keyword;

    var prepareThumbnails = function(result)
    {
        if (keyword !== selectedKeyword)
        {
            // This is not the last selected keyword, so just ignore
            // the result. The user must have clicked on more than one
            // keyword before the server responded.
            return;
        }

        // Use jQuery to create a new div.
        // All the new thumbnails will go into that div.
        var newThumbnails = $('<div></div>');
        var thumbnail;

        $(result).each(function() {
            thumbnail = $('#ThumbnailTemplate > .Thumbnail').clone();
            thumbnail.find('.ThumbnailTitle')
                .text(this.title)
                .attr('href', 'View.aspx?Identifier=' +
                this.documentID);
            var controlDiv = thumbnail.find('.ThumbnailImage').get(0);
            controlDiv.id = 'ThumbnailView' +
               (controlCounter++).toString();
            controlDiv.imageIdentifer = this.documentID;
            thumbnail.find('.ThumbnailAuthor').text(this.author);
            newThumbnails.append(thumbnail);
        });

        // Dispose of all the existing ThumbnailView controls (if any.)
        $('#Thumbnails .ThumbnailImage').each(function() {
            if (this.control) {
                this.control.dispose();
            }
        });

        // Replace the existing thumbnails with a new list
        $('#Thumbnails > div').replaceWith(newThumbnails);

        // Create and open the ThumbnailView controls.
        $('#Thumbnails .ThumbnailImage').each(function() {
            if (this.imageIdentifer) {
                $create(ig.ThumbnailView,
                    { imageHandlerUrl: imageHandlerUrl },
                    null,
                    null,
                    this);
                this.control.openImage({
                    imageIdentifier: this.imageIdentifer,
                    imagePageIndex: 0
                });
            }
        });

        // Place the keyword into the header.
        $('#SelectedKeyword').text(keyword);
    }

    // Call the page method to get a list of documents that match the
    // keyword. The page method is running on the server, so ASP.NET
    // will use Ajax to call it. When the page method finishes, ASP.NET
    // will call prepareThumbnails().
    PageMethods.GetDocumentsForKeyword(keyword, prepareThumbnails);
}

听起来工作量很大,但代码并不算太糟糕。该函数首先保存选定的关键词,然后调用我们的 ASP.NET 页面方法 `GetDocumentsForKeyword()`。我们将很快介绍该方法的代码。保存关键词很重要,可以避免 Ajax 应用程序中常见的细微问题。服务器调用需要一些时间,并且是异步运行的,因此用户可能会在第一个请求返回到服务器之前点击几个关键词。一个选项是在请求未决期间禁用关键词选择,但让后续点击覆盖之前的操作更好。保存最近选定的关键词可以方便我们在这些早期操作完成后忽略它们。

页面方法完成后,`prepareThumbnails()` 函数使用 jQuery 为每个匹配的文档复制缩略图模板。接下来,我们处理掉任何现有的 ThumbnailView 控件,并将现有页面内容与新缩略图进行交换。最后,我们使用 ASP.NET 客户端框架创建我们的 ThumbnailView 控件,并将它们打开到文档的第一页。

接下来,我们需要创建客户端用于确定哪些页面与给定关键词匹配的 ASP.NET 页面方法。这相当于一个简单的数据库查询。同样,我们将使用 LINQ to SQL。

列表 9:Search.aspx.cs(第 2 部分)

    [System.Web.Services.WebMethod]
    public static object GetDocumentsForKeyword(string keyword)
    {
        string cs = WebConfigurationManager
            .ConnectionStrings["ImageDatabaseConnectionString"]
            .ConnectionString;
        using (ImageDatabase db = new ImageDatabase(cs))
        {
            // Get the documents that match the given keyword.
            var documents =
                from row in db.DocumentKeywords
                where row.Keyword.Text == keyword
                orderby row.Document.Title
                select new
                {
                    documentID = row.Document.DocumentID,
                    title = row.Document.Title,
                    author = row.Document.Author
                };

            // Return the array of matching documents.
            return documents.ToArray();
        }
    }

我们需要的最后一段代码是运行搜索页面的代码。我们有初始化页面的函数以及用户选择关键词时浏览器调用的事件处理程序。

列表 10:Search.js(第 2 部分)

// Handle the keyword click events.
function keywordClickHandler() {
    selectKeyword($(this).text());
}

// ASP.NET 3.5 will call this function when the page is loaded and
// ready to be scripted. 
function pageLoad() {
    // Create the ImageGear namespace alias.
    ig = ImageGear.Web.UI;

    // Get the absolute URL back to the image handle, running on the
    // server. This has to be an absolute (due to a problem in FireFox
    // 3.0) URL back to the image handler running on the server.
    imageHandlerUrl = $find('ThumbnailViewTemplate')
        .get_imageHandlerUrl();

    // Set the default keyword to "Recent".
    selectKeyword('Recent');

    // Using jQuery, assign an event handler to each keyword.
    $('.Keyword').click(keywordClickHandler);
}

我们已经完成了所有代码;剩下的就是配置 ImageGear for .NET 的 web.config 设置以从数据库读取图像。您只需重新配置默认的 SqlImageDataProvider 以从数据库读取。具体来说,这涉及将 sqlCommand 设置为“SELECT ImageData FROM dbo.Document WHERE [DocumentID] = @Image_key”。

我们的 SQL 配置允许您使用几乎任何数据库架构。我们有很多客户拥有预先存在的数据库,因此要求某人更改其数据库以匹配产品可能会成为障碍。还有一种方法可以处理更复杂的存储机制,但这超出了本文档的范围。

摘要

好了,就是这样:一个用于查看 PDF 文档的简单 Web 应用程序。显然,该应用程序可以匹配您为网站选择的任何主题。最好的部分是,很少有代码与主要任务相关:文档查看。ImageGear 拥有大量的文档查看代码,您只需将其连接到您的应用程序即可。

今天就在 Accusoft Pegasus 下载并亲自尝试。它功能强大,易于使用,并且其架构使其能够拥抱最新的开发趋势。

您可以在 www.accusoft.com 找到 Accusoft Pegasus 产品下载和功能。有关更多信息,请联系我们 sales@accusoft.com 或 support@accusoft.com。

关于 Accusoft Pegasus

Accusoft Pegasus 于 1991 年以 Pegasus Imaging 的公司名成立,总部位于佛罗里达州坦帕,是成像软件开发工具包 (SDK) 和图像查看器的最大来源。成像技术解决方案包括条形码、压缩、DICOM、编辑、表单处理、OCR、PDF、扫描、视频和查看。技术交付给 Microsoft .NET、ActiveX、Silverlight、AJAX、ASP.NET、Windows Workflow 和 Java 环境。支持多个 32 位和 64 位平台,包括 Windows、Windows Mobile、Linux、Solaris x86、Solaris SPARC、Mac OS X 和 IBM AIX。访问 www.accusoft.com 了解更多信息。

© . All rights reserved.