flickr 拼写扩展控件 for ASP.NET Atlas






4.96/5 (13投票s)
一个 ASP.NET Atlas 服务器扩展控件,它将控件中指定的文本转换为 flickr 中的图像。
- 下载源文件 - 556 Kb
在尝试运行示例应用程序之前,请阅读“使用 flickrSpell 扩展控件”部分。您需要获取一个 flickr API 密钥并修改 web.config 文件。
引言
不久前,我偶然发现了 Spell with flickr 网页。您可以在该网页中输入一些文本,然后使用 flickr 的 oneletter 和 onedigit 组中的图像来拼写该文本。该网页使用 PHP。我当时正在尝试使用 Atlas 中的桥接概念来开发一个混搭应用程序。在我实际的混搭应用程序中,我需要类似 Spell with flickr 网站的功能。结果就是这个扩展控件,它允许您将 HTML 元素内的文本显示为来自 oneletter 和 onedigit 组的、代表字母的随机图像。在详细介绍扩展控件之前,让我们先了解一下 Atlas 扩展控件的概念。
扩展 ASP.NET 服务器控件
Atlas 引入了扩展控件的概念,它允许您为现有的 ASP.NET 控件添加丰富的功能。Atlas 扩展控件的一个示例是 AutoCompleteExtender
,它为 ASP.NET TextBox
服务器控件添加了服务器辅助的自动完成支持。例如,您通常通过页面中的以下声明性标记在 ASP.NET 网页中使用 ASP.NET TextBox
:
<asp:TextBox ID="Textbox1" runat="server"></asp:TextBox>
通过添加 Atlas AutoCompleteExtender
控件,您可以扩展此 ASP.NET 服务器控件,使其提供类似 Google Suggest 的自动完成支持,如下所示:
<atlas:AutoCompleteExtender ID="Extender1" runat="server">
<atlas:AutoCompleteProperties TargetControlID="TextBox1" ... />
</atlas:AutoCompleteExtender>
扩展控件定义有两个方面:
- 扩展控件标签:
atlas:AutoCompleteExtender
,您可以在其中指定runat
属性和ID
属性。 - 一个或多个扩展控件属性元素,用于指定需要扩展的目标控件以及特定于扩展程序的任何其他附加属性。
扩展控件使用 Atlas 中的客户端行为概念。Atlas 行为在概念上类似于 DHTML 行为,它使用 JavaScript 和 DOM 为现有的 DHTML 元素添加丰富的功能。通常,行为通过使用 Atlas XML-Script 指定。服务器上的 Atlas 扩展控件为行为生成 XML-Script。现在我们对扩展控件有了初步的了解,让我们来看看如何使用 flickrSpell 扩展控件。
使用 flickrSpell 扩展控件
在 Atlas 项目中使用 flickrSpell 扩展控件的步骤如下:
- 构建 FlickrSpell 项目,这将生成 FlickrSpell.dll 程序集。
- 将 FlickrSpell.dll 和 Microsoft.AtlasControlExtender.dll 复制到您网站的 bin 目录。
- 在您的网页中,注册要使用的控件的标签前缀,如下所示:
<%@ Register Assembly="FlickrSpell" TagPrefix="flickrSpell" Namespace="FlickrSpell" %>
- 将 FlickrBridge.asbx 和 FlickrBridge.asbx.cs 文件复制到您的网站。这些是 Atlas 桥接文件,它们将 flickr 服务暴露给 JavaScript 代码。
- 需要扩展的控件应该是服务器控件,即,它应该将
runat
属性设置为server
。例如,以下代码片段声明了一个h1
服务器控件:<h1 id="Test" runat="server">The CodeProject</h1>
- 要扩展此服务器控件,请添加扩展控件,如下所示:
<flickrSpell:FlickrSpellExtender ID="FE1" runat="server" > <flickrSpell:FlickrSpellProperties TargetControlID="test" /> </flickrSpell:FlickrSpellExtender>
- 从 flickr 获取 API 密钥。有关更多详细信息,请查看 Flickr Services 网站。
- 获取 API 密钥后,编辑您网站的 web.config 文件,并将密钥添加到
appsettings
部分:<add key="FlickrAPIKey" value="Your API key"/>
当您使用 Web 浏览器浏览网页时,您将看到文本“The CodeProject”被图像替换。转换可能很慢,因为 flickr 网站有时会非常慢。在本文的其余部分,我们将了解该控件的工作原理。我们将从查看 flickr 网站服务开始。
访问 flickr 上的图像
flickr 是一个流行的照片分享和存储网站。flickr 网站还允许您标记和分组照片。本文中感兴趣的 flickr 网站上有两个组:oneletter 和 onedigit。flickr 中的 oneletter 组包含描绘不同字母的照片。onedigit 组对数字执行相同操作。每个字母和数字都标记为自身,少数例外。例如,B 标记为文本“b”,C 标记为文本“c”,依此类推。两个例外是字母“A”,标记为文本“aa”,以及字母 I,标记为文本“ii”。因此,使用适当的标签搜索组中的照片池将得到特定字母或数字的适当照片列表。
flickr API 提供了一种让外部应用程序访问 flickr 网站的方式。API 有许多功能,但本文中使用的功能是从组中获取图像。flickr API 可以通过三种方式调用:使用 REST、使用 SOAP 和使用 XML-RPC。我们将使用 REST。
访问 flickr 的 REST 方法是通过 URL:http://www.flickr.com/services/rest/。您可以向此 URL 提供不同的查询字符串参数来访问不同的功能。要访问的 flickr 网站的功能通过 method
参数指示。用于列出组照片池中照片的方法称为 flickr.groups.pools.getPhotos
。以下是此方法接受的参数(文本摘自 flickr 网站):
api_key
(必需)- 您的 API 应用程序密钥。
group_id
(必需)- 您希望获取照片列表的组的 ID。
tags
(可选)- 用于过滤照片池的标签。目前,一次只支持一个标签。
user_id
(可选)- 用户的 NSID。指定此参数将仅检索用户贡献给组照片池的照片。
extras
(可选)- 用于获取每条返回记录的额外信息的逗号分隔列表。目前,支持的字段包括:
license
、date_upload
、date_taken
、owner_name
、icon_server
、original_format
和last_update
。 per_page
(可选)- 每页返回的照片数。如果省略此参数,则默认为 100。允许的最大值为 500。
page
(可选)- 返回结果的页码。如果省略此参数,则默认为 1。
oneletter 照片池的组 ID 为 27034531@N00
,onedigit 组的组 ID 为 54718308@N00
。访问 flickr 上字母 A 的照片的 HTTP GET 请求如下所示:
http://www.flickr.com/services/rest?api_key=XYZ&
method=flickr.groups.pools.getPhotos
&group_id=27034531@N00&tag=aa&per_page=100
返回的响应形式如下:
<rsp stat="ok">
<photos page="1" pages="1" perpage="1" total="1">
<photo id="2645" owner="12037949754@N01" title="36679_o"
secret="a9f4a06091" server="2"
ispublic="1" isfriend="0" isfamily="0"
/>
</photos>
</rsp>
下一步是构造照片的 URL。flickr 返回不同尺寸的图像。小型图像(75px x 75px)的 URL 格式如下:http://static.flickr.com/{server}/{photo id}_{secret}_s.jpg。server
、photo_id
和 secret
都是 XML 响应中的值。这完成了访问图像的过程。让我们看看如何在 ASP.NET Atlas 中编写此过程的代码。
桥接到 flickr
Bridges(桥接)于 Atlas 的三月 CTP 版本引入,它允许客户端脚本访问与托管网页不同域的 Web 服务。AJAX 开发中的一个问题是访问跨域服务,因为浏览器(在默认配置下)不允许通过 XMLHttpRequest
对象进行跨域访问。ASP.NET Atlas 通过将 JavaScript 的跨域 Web 服务调用路由到托管 Web 服务器来解决此问题。从浏览器的角度来看,该调用是针对托管 Web 服务器的,因此从安全角度来看是安全的。托管 Web 服务器上的桥接程序会向其他域上的 Web 服务发出实际调用。此方法涉及两次网络往返:一次从客户端到托管 Web 服务器,另一次从托管 Web 服务器到提供其他域上 Web 服务的服务器。在此,可以通过在托管 Web 服务器上提供缓存机制来提高性能。Web 服务器上的缓存的一个优点是它可以被多个客户端共享。
ASP.NET Atlas 桥接程序是一个扩展名为 asbx 的 XML 文件。桥接程序可以有一个代码隐藏文件。FlickrBridge.asbx 文件的内容如下所示:
<bridge
namespace="FlickrSpell" className="FlickrBridge"
partialClassFile="~/FlickrBridge.asbx.cs">
<proxy type="Microsoft.Web.Services.BridgeRestProxy"
serviceUrl="% appsettings : FlickrRESTEndPoint %" />
<method name="getLetterImages">
<input>
<parameter name="api_key" value="% appsettings : FlickrAPIKey %"
serverOnly="true"/>
<parameter name="method" value="flickr.groups.pools.getPhotos"
serverOnly="true"/>
<parameter name="group_id" value="27034531@N00"
serveronly="true"/>
<parameter name="letter" serverName="tags" />
<parameter name="extras" serverOnly="true" value="owner_name" />
<parameter name="per_page" value="25" />
<parameter name="page" value="1" />
</input>
<caching>
<cache type="Microsoft.Web.Services.BridgeCache" />
</caching>
<transforms>
<transform type="Microsoft.Web.Services.XPathBridgeTransformer">
<data>
<attribute name="selector" value="/rsp/photos/photo" />
<dictionary name="selectedNodes">
<item name="id" value="@id" />
<item name="owner" value="@ownername" />
<item name="title" value="@title" />
<item name="secret" value="@secret" />
<item name="server" value="@server" />
</dictionary>
</data>
</transform>
</transforms>
</method>
</bridge>
桥接文件由一个自定义生成提供程序(在配置文件中指定)转换为 C# 代码。C# 代码由 ASP.NET 运行时编译成程序集。让我们来分析桥接文件的代码。
- 桥接元素中的
namespace
和className
属性指定了桥接生成提供程序将创建的类型的命名空间和名称。 partialClassFile
属性指定了提供桥接类型部分实现的文件的名称。此类应派生自Microsoft.Web.Services.BridgeHandler
。proxy
元素中的type
属性指定了桥接程序访问 Web 服务的方法。Microsoft.Web.Services.BridgeRestProxy
指示将使用 REST 来访问 Web 服务。如果您使用的是 SOAP,则可以将 wsdl.exe 生成的代理类的名称作为此属性的值。serviceURL
指定了 Web 服务可用的 URL。字符串"% appsettings : FlickrRESTEndPoint %"
表明该值应从 web.config 文件中的appsettings
配置部分获取。以下是 web.config 文件的一个片段:<appSettings> <add key="FlickrRESTEndPoint" value="http://www.flickr.com/services/rest/"/> ... </appSettings>
input
元素下的parameter
元素指定了不同的查询字符串参数。name
和value
属性不言自明。serverOnly
属性的值true
指定了该特定参数的值只能从服务器端代码中指定,而不能从 JavaScript 代码中指定。XML 文件中指定的value
属性用于指示参数的默认值。caching
部分指示了如何缓存响应。Microsoft.Web.Services.BridgeCache
类型提供了一个简单的缓存机制。可以为缓存指定任何实现Microsoft.Web.Services.IBridgeRequestCache
的类型。transoforms
部分指定了在将输出返回给客户端之前如何进行转换。桥接程序的输出以 JSON 格式返回给客户端。可以指定多个转换。转换是实现Microsoft.Web.Services.IBridgeResponseTransformer
接口的类型。- 在提供的代码片段中,使用了
Microsoft.Web.Services.XPathBridgeTransformer
类型的转换。XPath 桥接转换器从 XML 响应中提取项。一个返回节点列表的 XPath 表达式在名为attribute
的元素中指定,该元素有一个名为name
的属性,其值为selector
。在我们的代码列表中,XPath 表达式/rsp/photos/photo
选择响应中的所有photo
元素。 - 对于通过匹配 XPath 表达式得到的每个节点(如第 8 点所述),可以进一步提取值以生成键值对列表。这是在
dictionary
部分完成的。item
元素指定字典中的条目。name
属性指定条目的名称,value
属性指定条目的值。此字典被转换为 JavaScript 对象。
至此,我们完成了对桥接文件的讨论。现在,让我们分析桥接文件的代码隐藏文件。
namespace FlickrSpell
{
public partial class FlickrBridge : BridgeHandler
{
public override void TransformRequest()
{
string letter = this.BridgeRequest.Args["letter"] as string;
string replacement = letter;
switch (letter.ToLower())
{
//The special case for a and i where
//the tag to be passed to flickr is different
case "a":
replacement = "aa";
break;
case "i":
replacement = "ii";
break;
//If a digit is specified
//use the onedigit group id
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
this.BridgeRequest.Args["group_id"] = "54718308@N00";
break;
};
this.BridgeRequest.Args["letter"] = replacement;
base.TransformRequest();
}
public override void TransformResponse()
{
string responseXML = this.ServiceResponse.Response as string;
XmlDocument doc = new XmlDocument();
doc.LoadXml(responseXML);
//Check for errors returned by flickr
string responseStatus = doc.SelectSingleNode("/rsp/@stat").Value;
if (string.Compare(responseStatus, "ok", true) != 0)
{
throw new Exception(doc.SelectSingleNode("/rsp/err/@msg").Value);
}
base.TransformResponse();
}
}
}
桥接 asbx 文件和代码隐藏类都定义了名为 FlickrBridge
的类。该类派生自 BridgeHandler
,其中包含多个可重写的方法。如代码清单所示,我们重写的两个方法是 TransformRequest
和 TranformResponse
。
TransformRequest
函数在将客户端 JavaScript 代码发送的请求发送到 Web 服务之前对其进行转换;通常,TransformRequest
的实现会添加或修改要发送到 Web 服务的参数。这是可以为仅服务器参数提供值的地方之一。在上面的代码清单中,我们做了两件重要的事情:
- 桥接方法请求中的
letter
参数是 flickr 照片标签。我们之前已经看到,代表“A”的照片的标签实际上是“aa”,代表“I”的照片的标签是“ii”;因此,我们修改请求参数值以使用适当的标签值。这样做是为了简化客户端脚本。 - 如果请求的是数字,则将组 ID 的值(其默认值在 asbx 文件中指定)更改为 onedigit 组的组 ID。
我们已经了解了请求是如何修改的,现在让我们看看在 TransformResponse
方法中做了什么。在 TransformResponse
方法中,我们放置了用于检查 flickr 返回错误的通用代码。flickr 通过将 stat
属性的值设置为“ok”来指示成功;任何其他值都表示失败。如果发生错误,flickr 返回的 XML 将在根 rsp
元素内包含一个 err
元素。err
元素的两个属性 msg
和 code
提供了有关错误的更多信息。在 TransformResponse
方法中,我们检查错误并将 flickr 错误转换为 Exception
。这完成了服务器端代码的详细信息,现在让我们分析客户端代码。
客户端代码
考虑以下 HTML:
<h1>The CodeProject</h1>
这个简单的 HTML 有一个 h1
的 DOM 元素节点,其中包含文本“The CodeProject”的 DOM 文本节点。我们可以将文本节点分解为每个字符的 img
元素。另一种选择是将文本分解为 span
元素并设置每个元素的背景图像。第一种方法的缺点是文本会丢失。第二种方法的缺点是图像无法调整大小。我们将使用一种混合方法。您可以使用 AtlasControlToolkit 来生成扩展控件的入门代码。AtlasControlToolkit 有一个向导可以生成扩展控件项目。您可以从这个 链接 下载 AtlasControlToolkit。
FlickrSpellExtender.FlickrSpellExtenderBehavior = function() {
FlickrSpellExtender.FlickrSpellExtenderBehavior.initializeBase(this);
this.initialize = function() {
FlickrSpellExtender.FlickrSpellExtenderBehavior.callBaseMethod(this,
'initialize');
if (!this.control)
return;
Sys.Net.WebRequestManager.set_enableBatching(true);
var children = this.control.element.childNodes;
for(var i = 0; i < children.length; i++)
{
if(children[i].nodeType == 3)
{
replaceTextNode(children[i]);
}
}
Sys.Net.WebRequestManager.set_enableBatching(false);
}
function replaceTextNode(node)
{
var text = node.data.trim();
var list = document.createElement("span");
list.className = "flickr-text";
list.title = text;
var currentWord = null;
for(var i = 0; i < text.length; i++)
{
//TODO: Allow for word breaks
var listElem = document.createElement("span");
listElem.innerText = text.charAt(i);
list.appendChild(listElem);
listElemToFlickrImage(listElem);
}
node.parentNode.replaceChild(list, node);
}
function listElemToFlickrImage(listElem)
{
var letter = listElem.innerText.charAt(0).toUpperCase();
if (letter >= "A" && letter <="Z" ||
(letter >= "0" && letter <= "9"))
{
FlickrSpellExtender.FlickrBridge.getLetterImages({"letter" : letter},
Function.createDelegate(this, onWebServiceCompleted),
null,
null,
null,
listElem);
}
}
function onWebServiceCompleted(results, reponse, listElem)
{
if (results.length < 1)
return;
var img = document.createElement("img");
var i = Math.round(Math.random()*(results.length - 1));
img.onload = function()
{
listElem.insertBefore(img, listElem.firstChild);
img.title = results[i].title + "\nOwner: " + results[i].owner;
}
img.src = String.format("http://static.flickr.com/{0}/{1}_{2}_s.jpg",
results[i].server,
results[i].id,
results[i].secret);
}
以下是客户端代码的工作原理:
- 元素的文本节点被分解为每个字符的
span
元素。 - 对于每个
span
元素,调用 Web 服务来查找图像的 URL。 - Web 服务返回后,会创建一个图像元素来保存字母或数字的图像。
- 图像元素的
onload
事件被设置为一个添加图像元素到span
元素的功能。 - 图像的
src
属性被设置为触发异步加载。注意,我们是在设置onload
事件处理程序之后执行此操作的。有时,图像会随着src
属性的设置而立即加载。当图像已在缓存中时,就会发生这种情况。
另一点需要注意的功能是批量处理。我们将所有 Web 服务调用进行批量处理,以减少网络往返次数。
结论和未来增强
我正在将此控件用作一个更大应用程序的一部分。应用程序准备好后,我将发布它。请就如何改进此控件或为其添加功能提出建议。