e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
975 lines
35 KiB
C#
975 lines
35 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="AdRotator.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.UI.WebControls {
|
|
using System.IO;
|
|
using System.Web.UI.HtmlControls;
|
|
using System.Web.UI.WebControls;
|
|
using System.Web.UI;
|
|
using System.Web.Caching;
|
|
using System.Web;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.ComponentModel.Design;
|
|
using System.Drawing.Design;
|
|
using System.Xml;
|
|
using System.Globalization;
|
|
using System.Web.Util;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Displays a randomly selected ad banner on a page.</para>
|
|
/// </devdoc>
|
|
[
|
|
DefaultEvent("AdCreated"),
|
|
DefaultProperty("AdvertisementFile"),
|
|
Designer("System.Web.UI.Design.WebControls.AdRotatorDesigner, " + AssemblyRef.SystemDesign),
|
|
ToolboxData("<{0}:AdRotator runat=\"server\"></{0}:AdRotator>")
|
|
]
|
|
public class AdRotator : DataBoundControl {
|
|
|
|
private static readonly object EventAdCreated = new object();
|
|
|
|
private const string XmlDocumentTag = "Advertisements";
|
|
private const string XmlDocumentRootXPath = "/" + XmlDocumentTag;
|
|
private const string XmlAdTag = "Ad";
|
|
|
|
private const string KeywordProperty = "Keyword";
|
|
private const string ImpressionsProperty = "Impressions";
|
|
|
|
// static copy of the Random object. This is a pretty hefty object to
|
|
// initialize, so you don't want to create one each time.
|
|
private static Random _random;
|
|
|
|
private String _baseUrl;
|
|
private string _advertisementFile;
|
|
private AdCreatedEventArgs _adCreatedEventArgs;
|
|
|
|
private AdRec [] _adRecs;
|
|
private bool _isPostCacheAdHelper;
|
|
private string _uniqueID;
|
|
|
|
private static readonly Type _adrotatorType = typeof(AdRotator);
|
|
private static readonly Type[] _AdCreatedParameterTypes = {typeof(AdCreatedEventArgs)};
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Initializes a new instance of the <see cref='System.Web.UI.WebControls.AdRotator'/> class.</para>
|
|
/// </devdoc>
|
|
public AdRotator() {
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Gets or sets the path to the XML file that contains advertisement data.</para>
|
|
/// </devdoc>
|
|
[
|
|
Bindable(true),
|
|
WebCategory("Behavior"),
|
|
DefaultValue(""),
|
|
Editor("System.Web.UI.Design.XmlUrlEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
|
|
UrlProperty(),
|
|
WebSysDescription(SR.AdRotator_AdvertisementFile)
|
|
]
|
|
public string AdvertisementFile {
|
|
get {
|
|
return((_advertisementFile == null) ? String.Empty : _advertisementFile);
|
|
}
|
|
set {
|
|
_advertisementFile = value;
|
|
}
|
|
}
|
|
|
|
|
|
[
|
|
WebCategory("Behavior"),
|
|
DefaultValue(AdCreatedEventArgs.AlternateTextElement),
|
|
WebSysDescription(SR.AdRotator_AlternateTextField)
|
|
]
|
|
public String AlternateTextField {
|
|
get {
|
|
String s = (String) ViewState["AlternateTextField"];
|
|
return((s != null) ? s : AdCreatedEventArgs.AlternateTextElement);
|
|
}
|
|
set {
|
|
ViewState["AlternateTextField"] = value;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// The base url corresponds for mapping of other url elements such as
|
|
/// imageUrl and navigateUrl.
|
|
/// </devdoc>
|
|
internal String BaseUrl {
|
|
get {
|
|
if (_baseUrl == null) {
|
|
// Deal with app relative syntax (e.g. ~/foo)
|
|
string tplSourceDir = TemplateControlVirtualDirectory.VirtualPathString;
|
|
|
|
// For the AdRotator, use the AdvertisementFile directory as the base, and fall back to the
|
|
// page/user control location as the base.
|
|
String absoluteFile = null;
|
|
String fileDirectory = null;
|
|
if (!String.IsNullOrEmpty(AdvertisementFile)) {
|
|
absoluteFile = UrlPath.Combine(tplSourceDir, AdvertisementFile);
|
|
fileDirectory = UrlPath.GetDirectory(absoluteFile);
|
|
}
|
|
|
|
_baseUrl = string.Empty;
|
|
if (fileDirectory != null) {
|
|
_baseUrl = fileDirectory;
|
|
}
|
|
if (_baseUrl.Length == 0) {
|
|
_baseUrl = tplSourceDir;
|
|
}
|
|
}
|
|
return _baseUrl;
|
|
}
|
|
}
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// Font property. Has no effect on this control, so hide it.
|
|
/// </devdoc>
|
|
[
|
|
Browsable(false),
|
|
EditorBrowsableAttribute(EditorBrowsableState.Never),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|
]
|
|
public override FontInfo Font {
|
|
get {
|
|
return base.Font;
|
|
}
|
|
}
|
|
|
|
|
|
[
|
|
WebCategory("Behavior"),
|
|
DefaultValue(AdCreatedEventArgs.ImageUrlElement),
|
|
WebSysDescription(SR.AdRotator_ImageUrlField)
|
|
]
|
|
public String ImageUrlField {
|
|
get {
|
|
String s = (String) ViewState["ImageUrlField"];
|
|
return((s != null) ? s : AdCreatedEventArgs.ImageUrlElement);
|
|
}
|
|
set {
|
|
ViewState["ImageUrlField"] = value;
|
|
}
|
|
}
|
|
|
|
private bool IsTargetSet {
|
|
get {
|
|
return (ViewState["Target"] != null);
|
|
}
|
|
}
|
|
|
|
internal bool IsPostCacheAdHelper {
|
|
get {
|
|
return _isPostCacheAdHelper;
|
|
}
|
|
set {
|
|
_isPostCacheAdHelper = value;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Gets or sets a category keyword used for matching related advertisements in the advertisement file.</para>
|
|
/// </devdoc>
|
|
[
|
|
Bindable(true),
|
|
WebCategory("Behavior"),
|
|
DefaultValue(""),
|
|
WebSysDescription(SR.AdRotator_KeywordFilter)
|
|
]
|
|
public string KeywordFilter {
|
|
get {
|
|
string s = (string)ViewState["KeywordFilter"];
|
|
return((s == null) ? String.Empty : s);
|
|
}
|
|
set {
|
|
// trim the filter value
|
|
if (String.IsNullOrEmpty(value)) {
|
|
ViewState.Remove("KeywordFilter");
|
|
}
|
|
else {
|
|
ViewState["KeywordFilter"] = value.Trim();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
[
|
|
WebCategory("Behavior"),
|
|
DefaultValue(AdCreatedEventArgs.NavigateUrlElement),
|
|
WebSysDescription(SR.AdRotator_NavigateUrlField)
|
|
]
|
|
public String NavigateUrlField {
|
|
get {
|
|
String s = (String) ViewState["NavigateUrlField"];
|
|
return((s != null) ? s : AdCreatedEventArgs.NavigateUrlElement);
|
|
}
|
|
set {
|
|
ViewState["NavigateUrlField"] = value;
|
|
}
|
|
}
|
|
|
|
|
|
private AdCreatedEventArgs SelectedAdArgs {
|
|
get {
|
|
return _adCreatedEventArgs;
|
|
}
|
|
set {
|
|
_adCreatedEventArgs = value;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Gets
|
|
/// or sets the name of the browser window or frame to display the advertisement.</para>
|
|
/// </devdoc>
|
|
[
|
|
Bindable(true),
|
|
WebCategory("Behavior"),
|
|
DefaultValue("_top"),
|
|
WebSysDescription(SR.AdRotator_Target),
|
|
TypeConverter(typeof(TargetConverter))
|
|
]
|
|
public string Target {
|
|
get {
|
|
string s = (string)ViewState["Target"];
|
|
return((s == null) ? "_top" : s);
|
|
}
|
|
set {
|
|
ViewState["Target"] = value;
|
|
}
|
|
}
|
|
|
|
|
|
protected override HtmlTextWriterTag TagKey {
|
|
get {
|
|
return HtmlTextWriterTag.A;
|
|
}
|
|
}
|
|
|
|
public override string UniqueID {
|
|
get {
|
|
if (_uniqueID == null) {
|
|
_uniqueID = base.UniqueID;
|
|
}
|
|
return _uniqueID;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Occurs once per round trip after the creation of the
|
|
/// control before the page is rendered. </para>
|
|
/// </devdoc>
|
|
[
|
|
WebCategory("Action"),
|
|
WebSysDescription(SR.AdRotator_OnAdCreated)
|
|
]
|
|
public event AdCreatedEventHandler AdCreated {
|
|
add {
|
|
Events.AddHandler(EventAdCreated, value);
|
|
}
|
|
remove {
|
|
Events.RemoveHandler(EventAdCreated, value);
|
|
}
|
|
}
|
|
|
|
private void CheckOnlyOneDataSource() {
|
|
int numOfDataSources = ((AdvertisementFile.Length > 0) ? 1 : 0);
|
|
numOfDataSources += ((DataSourceID.Length > 0) ? 1 : 0);
|
|
numOfDataSources += ((DataSource != null) ? 1 : 0);
|
|
|
|
if (numOfDataSources > 1) {
|
|
throw new HttpException(SR.GetString(SR.AdRotator_only_one_datasource, ID));
|
|
}
|
|
}
|
|
|
|
// Currently this is designed to be called when PostCache Substitution is being initialized
|
|
internal void CopyFrom(AdRotator adRotator) {
|
|
_adRecs = adRotator._adRecs;
|
|
|
|
AccessKey = adRotator.AccessKey;
|
|
AlternateTextField = adRotator.AlternateTextField;
|
|
Enabled = adRotator.Enabled;
|
|
ImageUrlField = adRotator.ImageUrlField;
|
|
NavigateUrlField = adRotator.NavigateUrlField;
|
|
TabIndex = adRotator.TabIndex;
|
|
Target = adRotator.Target;
|
|
ToolTip = adRotator.ToolTip;
|
|
|
|
string id = adRotator.ID;
|
|
if (!String.IsNullOrEmpty(id)) {
|
|
ID = adRotator.ClientID;
|
|
}
|
|
|
|
// Below are properties that need to be handled specially and saved
|
|
// to private variables.
|
|
_uniqueID = adRotator.UniqueID;
|
|
_baseUrl = adRotator.BaseUrl;
|
|
|
|
// Special copy to properties that cannot be assigned directly
|
|
if (adRotator.HasAttributes) {
|
|
foreach(string key in adRotator.Attributes.Keys) {
|
|
Attributes[key] = adRotator.Attributes[key];
|
|
}
|
|
}
|
|
|
|
if (adRotator.ControlStyleCreated) {
|
|
ControlStyle.CopyFrom(adRotator.ControlStyle);
|
|
}
|
|
}
|
|
|
|
|
|
private ArrayList CreateAutoGeneratedFields(IEnumerable dataSource) {
|
|
if (dataSource == null) {
|
|
return null;
|
|
}
|
|
|
|
ArrayList generatedFields = new ArrayList();
|
|
PropertyDescriptorCollection propertyDescriptors = null;
|
|
|
|
if (dataSource is ITypedList) {
|
|
propertyDescriptors =
|
|
((ITypedList)dataSource).GetItemProperties(new PropertyDescriptor[0]);
|
|
}
|
|
|
|
if (propertyDescriptors == null) {
|
|
|
|
IEnumerator enumerator = dataSource.GetEnumerator();
|
|
if (enumerator.MoveNext()) {
|
|
|
|
Object sampleItem = enumerator.Current;
|
|
if (IsBindableType(sampleItem.GetType())) {
|
|
// Raise error since we are expecting some record
|
|
// containing multiple data values.
|
|
throw new HttpException(SR.GetString(SR.AdRotator_expect_records_with_advertisement_properties,
|
|
ID, sampleItem.GetType()));
|
|
}
|
|
else {
|
|
propertyDescriptors = TypeDescriptor.GetProperties(sampleItem);
|
|
}
|
|
}
|
|
}
|
|
if (propertyDescriptors != null && propertyDescriptors.Count > 0) {
|
|
|
|
foreach (PropertyDescriptor pd in propertyDescriptors) {
|
|
if (IsBindableType(pd.PropertyType)) {
|
|
generatedFields.Add(pd.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
return generatedFields;
|
|
}
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal bool DoPostCacheSubstitutionAsNeeded(HtmlTextWriter writer) {
|
|
if (!IsPostCacheAdHelper && SelectedAdArgs == null &&
|
|
Page.Response.HasCachePolicy &&
|
|
(int)Page.Response.Cache.GetCacheability() != (int)HttpCacheabilityLimits.None) {
|
|
|
|
// The checking of the cacheability is to see if the page is output cached
|
|
AdPostCacheSubstitution adPostCacheSubstitution = new AdPostCacheSubstitution(this);
|
|
adPostCacheSubstitution.RegisterPostCacheCallBack(Context, Page, writer);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>Select an ad from ad records and create the event
|
|
/// argument object.</para>
|
|
/// </devdoc>
|
|
private AdCreatedEventArgs GetAdCreatedEventArgs() {
|
|
IDictionary adInfo = SelectAdFromRecords();
|
|
AdCreatedEventArgs adArgs =
|
|
new AdCreatedEventArgs(adInfo,
|
|
ImageUrlField,
|
|
NavigateUrlField,
|
|
AlternateTextField);
|
|
return adArgs;
|
|
}
|
|
|
|
|
|
private AdRec [] GetDataSourceData(IEnumerable dataSource) {
|
|
|
|
ArrayList fields = CreateAutoGeneratedFields(dataSource);
|
|
|
|
ArrayList adDicts = new ArrayList();
|
|
IEnumerator enumerator = dataSource.GetEnumerator();
|
|
while(enumerator.MoveNext()) {
|
|
IDictionary dict = null;
|
|
foreach (String field in fields){
|
|
if (dict == null) {
|
|
dict = new HybridDictionary();
|
|
}
|
|
dict.Add(field, DataBinder.GetPropertyValue(enumerator.Current, field));
|
|
}
|
|
|
|
if (dict != null) {
|
|
adDicts.Add(dict);
|
|
}
|
|
}
|
|
|
|
return SetAdRecs(adDicts);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Gets the ad data for the given file by loading the file, or reading from the
|
|
/// application-level cache.
|
|
/// </devdoc>
|
|
private AdRec [] GetFileData(string fileName) {
|
|
|
|
// VSWhidbey 208626: Adopting similar code from xml.cs to support virtual path provider
|
|
|
|
// First, figure out if it's a physical or virtual path
|
|
VirtualPath virtualPath;
|
|
string physicalPath;
|
|
ResolvePhysicalOrVirtualPath(fileName, out virtualPath, out physicalPath);
|
|
|
|
// try to get it from the ASP.NET cache
|
|
string fileKey = CacheInternal.PrefixAdRotator + ((!String.IsNullOrEmpty(physicalPath)) ?
|
|
physicalPath : virtualPath.VirtualPathString);
|
|
CacheInternal cacheInternal = System.Web.HttpRuntime.CacheInternal;
|
|
AdRec [] adRecs = cacheInternal[fileKey] as AdRec[];
|
|
|
|
if (adRecs == null) {
|
|
// Otherwise load it
|
|
CacheDependency dependency;
|
|
try {
|
|
using (Stream stream = OpenFileAndGetDependency(virtualPath, physicalPath, out dependency)) {
|
|
adRecs = LoadStream(stream);
|
|
Debug.Assert(adRecs != null);
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
if (!String.IsNullOrEmpty(physicalPath) && HttpRuntime.HasPathDiscoveryPermission(physicalPath)) {
|
|
// We want to catch the error message, but not propage the inner exception. Otherwise we can throw up
|
|
// logon prompts through IE;
|
|
throw new HttpException(SR.GetString(SR.AdRotator_cant_open_file, ID, e.Message));
|
|
}
|
|
else {
|
|
throw new HttpException(SR.GetString(SR.AdRotator_cant_open_file_no_permission, ID));
|
|
}
|
|
}
|
|
|
|
// Cache it, but only if we got a dependency
|
|
if (dependency != null) {
|
|
using (dependency) {
|
|
// and store it in the cache, dependent on the file name
|
|
cacheInternal.UtcInsert(fileKey, adRecs, dependency);
|
|
}
|
|
}
|
|
}
|
|
return adRecs;
|
|
}
|
|
|
|
private static int GetRandomNumber(int maxValue) {
|
|
if (_random == null) {
|
|
_random = new Random();
|
|
}
|
|
return _random.Next(maxValue) + 1;
|
|
}
|
|
|
|
private AdRec [] GetXmlDataSourceData(XmlDataSource xmlDataSource) {
|
|
Debug.Assert(xmlDataSource != null);
|
|
|
|
XmlDocument doc = xmlDataSource.GetXmlDocument();
|
|
if (doc == null) {
|
|
return null;
|
|
}
|
|
return LoadXmlDocument(doc);
|
|
}
|
|
|
|
private bool IsBindableType(Type type) {
|
|
return(type.IsPrimitive ||
|
|
(type == typeof(String)) ||
|
|
(type == typeof(DateTime)) ||
|
|
(type == typeof(Decimal)));
|
|
}
|
|
|
|
private bool IsOnAdCreatedOverridden() {
|
|
bool result = false;
|
|
Type type = this.GetType();
|
|
if (type != _adrotatorType) {
|
|
MethodInfo methodInfo = type.GetMethod("OnAdCreated",
|
|
BindingFlags.NonPublic | BindingFlags.Instance,
|
|
null,
|
|
_AdCreatedParameterTypes,
|
|
null);
|
|
if (methodInfo.DeclaringType != _adrotatorType) {
|
|
result = true;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private AdRec [] LoadFromXmlReader(XmlReader reader) {
|
|
ArrayList adDicts = new ArrayList();
|
|
|
|
while (reader.Read()) {
|
|
if (reader.Name == "Advertisements") {
|
|
if (reader.Depth != 0) {
|
|
return null;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (reader.Read()) {
|
|
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Ad" && reader.Depth == 1) {
|
|
|
|
IDictionary dict = null;
|
|
reader.Read();
|
|
while (!(reader.NodeType == XmlNodeType.EndElement)) {
|
|
if (reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement) {
|
|
if (dict == null) {
|
|
dict = new HybridDictionary();
|
|
}
|
|
dict.Add(reader.LocalName, reader.ReadString());
|
|
}
|
|
reader.Skip();
|
|
}
|
|
|
|
if (dict != null) {
|
|
adDicts.Add(dict);
|
|
}
|
|
}
|
|
}
|
|
|
|
AdRec [] adRecs = SetAdRecs(adDicts);
|
|
return adRecs;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Loads the given XML stream into an array of AdRec structures
|
|
/// </devdoc>
|
|
private AdRec [] LoadStream(Stream stream) {
|
|
|
|
AdRec [] adRecs = null;
|
|
try {
|
|
// Read the XML stream into an array of dictionaries
|
|
XmlReader reader = XmlUtils.CreateXmlReader(stream);
|
|
|
|
// Perf: We use LoadFromXmlReader instead of LoadXmlDocument to
|
|
// do the text parsing only once
|
|
adRecs = LoadFromXmlReader(reader);
|
|
}
|
|
catch (Exception e) {
|
|
throw new HttpException(
|
|
SR.GetString(SR.AdRotator_parse_error, ID, e.Message), e);
|
|
}
|
|
|
|
if (adRecs == null) {
|
|
throw new HttpException(
|
|
SR.GetString(SR.AdRotator_no_advertisements, ID, AdvertisementFile));
|
|
}
|
|
|
|
return adRecs;
|
|
}
|
|
|
|
private AdRec [] LoadXmlDocument(XmlDocument doc) {
|
|
// Read the XML data into an array of dictionaries
|
|
ArrayList adDicts = new ArrayList();
|
|
|
|
if (doc.DocumentElement != null &&
|
|
doc.DocumentElement.LocalName == XmlDocumentTag) {
|
|
|
|
XmlNode elem = doc.DocumentElement.FirstChild;
|
|
|
|
while (elem != null) {
|
|
IDictionary dict = null;
|
|
if (elem.LocalName.Equals(XmlAdTag)) {
|
|
XmlNode prop = elem.FirstChild;
|
|
while (prop != null) {
|
|
if (prop.NodeType == XmlNodeType.Element) {
|
|
if (dict == null) {
|
|
dict = new HybridDictionary();
|
|
}
|
|
dict.Add(prop.LocalName, prop.InnerText);
|
|
}
|
|
prop = prop.NextSibling;
|
|
}
|
|
}
|
|
if (dict != null) {
|
|
adDicts.Add(dict);
|
|
}
|
|
elem = elem.NextSibling;
|
|
}
|
|
}
|
|
|
|
AdRec [] adRecs = SetAdRecs(adDicts);
|
|
return adRecs;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Used to determine if the advertisement meets current criteria. Does a comparison with
|
|
/// KeywordFilter if it is set.
|
|
/// </devdoc>
|
|
private bool MatchingAd(AdRec adRec, string keywordFilter) {
|
|
Debug.Assert(keywordFilter != null && keywordFilter.Length > 0);
|
|
return(String.Equals(keywordFilter, adRec.keyword, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Raises the <see cref='System.Web.UI.WebControls.AdRotator.AdCreated'/> event for an <see cref='System.Web.UI.WebControls.AdRotator'/>.</para>
|
|
/// </devdoc>
|
|
protected virtual void OnAdCreated(AdCreatedEventArgs e) {
|
|
AdCreatedEventHandler handler = (AdCreatedEventHandler)Events[EventAdCreated];
|
|
if (handler != null) handler(this, e);
|
|
}
|
|
|
|
|
|
protected internal override void OnInit(EventArgs e) {
|
|
base.OnInit(e);
|
|
|
|
// VSWhidbey 419600: We just always need binding data every time since
|
|
// AdRotator doesn't store the entire Ad data in ViewState for selecting
|
|
// Ad during postbacks. It's too big for storing in ViewState.
|
|
RequiresDataBinding = true;
|
|
}
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// <para>Gets the advertisement information for rendering in its parameter, then calls
|
|
/// the OnAdCreated event to render the ads.</para>
|
|
/// </devdoc>
|
|
protected internal override void OnPreRender(EventArgs e) {
|
|
base.OnPreRender(e);
|
|
|
|
// If after PreRender (which would call DataBind if DataSource or DataSourceID available)
|
|
// and no _adRecs created, it must be the normal v1 behavior which uses ad file.
|
|
if (_adRecs == null && AdvertisementFile.Length > 0) {
|
|
PerformAdFileBinding();
|
|
}
|
|
|
|
// If handler is specified, we don't do any post-cache
|
|
// substitution because the handler code would not be executed.
|
|
//
|
|
// VSWhidbey 213759: We also don't want any post-cache substitution
|
|
// if OnAdCreated has been overridden
|
|
if (Events[EventAdCreated] != null || IsOnAdCreatedOverridden()) {
|
|
// Fire the user event for further customization
|
|
SelectedAdArgs = GetAdCreatedEventArgs();
|
|
OnAdCreated(SelectedAdArgs);
|
|
}
|
|
}
|
|
|
|
private void PerformAdFileBinding() {
|
|
// Getting ad data from physical file is V1 way which is not supported
|
|
// by the base class DataBoundControl so we had above code to handle
|
|
// this case. However, we need to support DataBound control events
|
|
// in Whidbey and since above code doesn't go through the event
|
|
// raising in the base class DataBoundControl, here we mimic them.
|
|
OnDataBinding(EventArgs.Empty);
|
|
|
|
// get the ads from the file or app cache
|
|
_adRecs = GetFileData(AdvertisementFile);
|
|
|
|
OnDataBound(EventArgs.Empty);
|
|
}
|
|
|
|
protected internal override void PerformDataBinding(IEnumerable data) {
|
|
if (data != null) {
|
|
// We retrieve ad data from xml format in a specific way.
|
|
XmlDataSource xmlDataSource = null;
|
|
object dataSource = DataSource;
|
|
if (dataSource != null) {
|
|
xmlDataSource = dataSource as XmlDataSource;
|
|
}
|
|
else { // DataSourceID case, we know that only one source is available
|
|
xmlDataSource = GetDataSource() as XmlDataSource;
|
|
}
|
|
|
|
if (xmlDataSource != null) {
|
|
_adRecs = GetXmlDataSourceData(xmlDataSource);
|
|
}
|
|
else {
|
|
_adRecs = GetDataSourceData(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void PerformSelect() {
|
|
// VSWhidbey 141362
|
|
CheckOnlyOneDataSource();
|
|
|
|
if (AdvertisementFile.Length > 0) {
|
|
PerformAdFileBinding();
|
|
}
|
|
else {
|
|
base.PerformSelect();
|
|
}
|
|
}
|
|
|
|
//
|
|
internal AdCreatedEventArgs PickAd() {
|
|
AdCreatedEventArgs adArgs = SelectedAdArgs;
|
|
if (adArgs == null) {
|
|
adArgs = GetAdCreatedEventArgs();
|
|
}
|
|
adArgs.ImageUrl = ResolveAdRotatorUrl(BaseUrl, adArgs.ImageUrl);
|
|
adArgs.NavigateUrl = ResolveAdRotatorUrl(BaseUrl, adArgs.NavigateUrl);
|
|
return adArgs;
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// <para>Displays the <see cref='System.Web.UI.WebControls.AdRotator'/> on the client.</para>
|
|
/// </devdoc>
|
|
protected internal override void Render(HtmlTextWriter writer) {
|
|
if (!DesignMode && !IsPostCacheAdHelper &&
|
|
DoPostCacheSubstitutionAsNeeded(writer)) {
|
|
return;
|
|
}
|
|
|
|
AdCreatedEventArgs adArgs = PickAd();
|
|
RenderLink(writer, adArgs);
|
|
}
|
|
|
|
private void RenderLink(HtmlTextWriter writer, AdCreatedEventArgs adArgs) {
|
|
Debug.Assert(writer != null);
|
|
Debug.Assert(adArgs != null);
|
|
|
|
HyperLink bannerLink = new HyperLink();
|
|
|
|
bannerLink.NavigateUrl = adArgs.NavigateUrl;
|
|
bannerLink.Target = Target;
|
|
|
|
if (HasAttributes) {
|
|
foreach(string key in Attributes.Keys) {
|
|
bannerLink.Attributes[key] = Attributes[key];
|
|
}
|
|
}
|
|
|
|
string id = ID;
|
|
if (!String.IsNullOrEmpty(id)) {
|
|
bannerLink.ID = ClientID;
|
|
}
|
|
|
|
if (!Enabled) {
|
|
bannerLink.Enabled = false;
|
|
}
|
|
|
|
// WebControl's properties use a private flag to determine if a
|
|
// property is set and does not return the value unless the flag is
|
|
// marked. So here we access those properites (inherited from WebControl)
|
|
// directly from the ViewState bag because if ViewState bag reference
|
|
// was copied to the helper class in the optimized case during the
|
|
// Initialize() method, the flags of the properties wouldn't be set
|
|
// in the helper class.
|
|
string accessKey = (string) ViewState["AccessKey"];
|
|
if (!String.IsNullOrEmpty(accessKey)) {
|
|
bannerLink.AccessKey = accessKey;
|
|
}
|
|
|
|
object o = ViewState["TabIndex"];
|
|
if (o != null) {
|
|
short tabIndex = (short) o;
|
|
if (tabIndex != (short) 0) {
|
|
bannerLink.TabIndex = tabIndex;
|
|
}
|
|
}
|
|
|
|
bannerLink.RenderBeginTag(writer);
|
|
|
|
// create inner Image
|
|
Image bannerImage = new Image();
|
|
// apply styles to image
|
|
if (ControlStyleCreated) {
|
|
bannerImage.ApplyStyle(ControlStyle);
|
|
}
|
|
|
|
string alternateText = adArgs.AlternateText;
|
|
if (!String.IsNullOrEmpty(alternateText)) {
|
|
bannerImage.AlternateText = alternateText;
|
|
}
|
|
else {
|
|
// 25914 Do not render empty 'alt' attribute if <AlternateText> tag is never specified
|
|
IDictionary adProps = adArgs.AdProperties;
|
|
string altTextKey = (AlternateTextField.Length != 0)
|
|
? AlternateTextField : AdCreatedEventArgs.AlternateTextElement;
|
|
string altText = (adProps == null) ? null : (string) adProps[altTextKey];
|
|
if (altText != null && altText.Length == 0) {
|
|
bannerImage.GenerateEmptyAlternateText = true;
|
|
}
|
|
}
|
|
|
|
// Perf work: AdRotator should have resolved the NavigateUrl and
|
|
// ImageUrl when assigning them and have UrlResolved set properly.
|
|
bannerImage.UrlResolved = true;
|
|
string imageUrl = adArgs.ImageUrl;
|
|
if (!String.IsNullOrEmpty(imageUrl)) {
|
|
bannerImage.ImageUrl = imageUrl;
|
|
}
|
|
|
|
if (adArgs.HasWidth) {
|
|
bannerImage.ControlStyle.Width = adArgs.Width;
|
|
}
|
|
|
|
if (adArgs.HasHeight) {
|
|
bannerImage.ControlStyle.Height = adArgs.Height;
|
|
}
|
|
|
|
string toolTip = (string) ViewState["ToolTip"];
|
|
if (!String.IsNullOrEmpty(toolTip)) {
|
|
bannerImage.ToolTip = toolTip;
|
|
}
|
|
|
|
bannerImage.RenderControl(writer);
|
|
bannerLink.RenderEndTag(writer);
|
|
}
|
|
|
|
private string ResolveAdRotatorUrl(string baseUrl, string relativeUrl) {
|
|
|
|
if ((relativeUrl == null) ||
|
|
(relativeUrl.Length == 0) ||
|
|
(UrlPath.IsRelativeUrl(relativeUrl) == false) ||
|
|
(baseUrl == null) ||
|
|
(baseUrl.Length == 0)) {
|
|
return relativeUrl;
|
|
}
|
|
|
|
// make it absolute
|
|
return UrlPath.Combine(baseUrl, relativeUrl);
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>Selects an advertisement from the a list of records based
|
|
/// on different factors.</para>
|
|
/// </devdoc>
|
|
private IDictionary SelectAdFromRecords() {
|
|
if (_adRecs == null || _adRecs.Length == 0) {
|
|
return null;
|
|
}
|
|
|
|
string keywordFilter = KeywordFilter;
|
|
bool noKeywordFilter = String.IsNullOrEmpty(keywordFilter);
|
|
if (!noKeywordFilter) {
|
|
// do a lower case comparison
|
|
keywordFilter = keywordFilter.ToLower(CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
// sum the matching impressions
|
|
int totalImpressions = 0;
|
|
for (int i = 0; i < _adRecs.Length; i++) {
|
|
if (noKeywordFilter || MatchingAd(_adRecs[i], keywordFilter)) {
|
|
totalImpressions += _adRecs[i].impressions;
|
|
}
|
|
}
|
|
|
|
if (totalImpressions == 0) {
|
|
return null;
|
|
}
|
|
|
|
// select one using a random number between 1 and totalImpressions
|
|
int selectedImpression = GetRandomNumber(totalImpressions);
|
|
int impressionCounter = 0;
|
|
int selectedIndex = -1;
|
|
for (int i = 0; i < _adRecs.Length; i++) {
|
|
// Is this the ad?
|
|
if (noKeywordFilter || MatchingAd(_adRecs[i], keywordFilter)) {
|
|
impressionCounter += _adRecs[i].impressions;
|
|
if (selectedImpression <= impressionCounter) {
|
|
selectedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Debug.Assert(selectedIndex >= 0 && selectedIndex < _adRecs.Length, "Index not found");
|
|
|
|
return _adRecs[selectedIndex].adProperties;
|
|
}
|
|
|
|
private AdRec [] SetAdRecs(ArrayList adDicts) {
|
|
if (adDicts == null || adDicts.Count == 0) {
|
|
return null;
|
|
}
|
|
|
|
// Create an array of AdRec structures from the dictionaries, removing blanks
|
|
AdRec [] adRecs = new AdRec[adDicts.Count];
|
|
int iRec = 0;
|
|
for (int i = 0; i < adDicts.Count; i++) {
|
|
if (adDicts[i] != null) {
|
|
adRecs[iRec].Initialize((IDictionary) adDicts[i]);
|
|
iRec++;
|
|
}
|
|
}
|
|
Debug.Assert(iRec == adDicts.Count, "Record count did not match non-null entries");
|
|
|
|
return adRecs;
|
|
}
|
|
|
|
|
|
|
|
/// <devdoc>
|
|
/// Structure to store ads in memory for fast selection by multiple instances of adrotator
|
|
/// Stores the dictionary and caches some values for easier selection.
|
|
/// </devdoc>
|
|
private struct AdRec {
|
|
public string keyword;
|
|
public int impressions;
|
|
public IDictionary adProperties;
|
|
|
|
|
|
/// <devdoc>
|
|
/// Initialize the stuct based on a dictionary containing the advertisement properties
|
|
/// </devdoc>
|
|
public void Initialize(IDictionary adProperties) {
|
|
|
|
// Initialize the values we need to keep for ad selection
|
|
Debug.Assert(adProperties != null, "Required here");
|
|
this.adProperties = adProperties;
|
|
|
|
// remove null and trim keyword for easier comparisons.
|
|
// VSWhidbey 114634: Be defensive and only retrieve the keyword
|
|
// value if it is in string type
|
|
object keywordValue = adProperties[KeywordProperty];
|
|
if (keywordValue != null && keywordValue is string) {
|
|
keyword = ((string) keywordValue).Trim();
|
|
}
|
|
else {
|
|
keyword = string.Empty;
|
|
}
|
|
|
|
// get the impressions, but be defensive: let the schema enforce the rules. Default to 1.
|
|
string impressionsString = adProperties[ImpressionsProperty] as string;
|
|
if (String.IsNullOrEmpty(impressionsString) ||
|
|
!int.TryParse(impressionsString, NumberStyles.Integer,
|
|
CultureInfo.InvariantCulture, out impressions)) {
|
|
impressions = 1;
|
|
}
|
|
if (impressions < 0) {
|
|
impressions = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|