|
//------------------------------------------------------------------------------
// <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);
CacheStoreProvider cacheInternal = System.Web.HttpRuntime.Cache.InternalCache;
AdRec[] adRecs = cacheInternal.Get(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.Insert(fileKey, adRecs, new CacheInsertOptions() { Dependencies = 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;
}
}
}
}
}
|