1 //------------------------------------------------------------------------------ 2 // <copyright file="AdRotator.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 //------------------------------------------------------------------------------ 6 7 namespace System.Web.UI.WebControls { 8 using System.IO; 9 using System.Web.UI.HtmlControls; 10 using System.Web.UI.WebControls; 11 using System.Web.UI; 12 using System.Web.Caching; 13 using System.Web; 14 using System; 15 using System.Collections; 16 using System.Collections.Specialized; 17 using System.ComponentModel; 18 using System.ComponentModel.Design; 19 using System.Drawing.Design; 20 using System.Xml; 21 using System.Globalization; 22 using System.Web.Util; 23 using System.Reflection; 24 using System.Text; 25 26 27 /// <devdoc> 28 /// <para>Displays a randomly selected ad banner on a page.</para> 29 /// </devdoc> 30 [ 31 DefaultEvent("AdCreated"), 32 DefaultProperty("AdvertisementFile"), 33 Designer("System.Web.UI.Design.WebControls.AdRotatorDesigner, " + AssemblyRef.SystemDesign), 34 ToolboxData("<{0}:AdRotator runat=\"server\"></{0}:AdRotator>") 35 ] 36 public class AdRotator : DataBoundControl { 37 38 private static readonly object EventAdCreated = new object(); 39 40 private const string XmlDocumentTag = "Advertisements"; 41 private const string XmlDocumentRootXPath = "/" + XmlDocumentTag; 42 private const string XmlAdTag = "Ad"; 43 44 private const string KeywordProperty = "Keyword"; 45 private const string ImpressionsProperty = "Impressions"; 46 47 // static copy of the Random object. This is a pretty hefty object to 48 // initialize, so you don't want to create one each time. 49 private static Random _random; 50 51 private String _baseUrl; 52 private string _advertisementFile; 53 private AdCreatedEventArgs _adCreatedEventArgs; 54 55 private AdRec [] _adRecs; 56 private bool _isPostCacheAdHelper; 57 private string _uniqueID; 58 59 private static readonly Type _adrotatorType = typeof(AdRotator); 60 private static readonly Type[] _AdCreatedParameterTypes = {typeof(AdCreatedEventArgs)}; 61 62 63 /// <devdoc> 64 /// <para>Initializes a new instance of the <see cref='System.Web.UI.WebControls.AdRotator'/> class.</para> 65 /// </devdoc> AdRotator()66 public AdRotator() { 67 } 68 69 70 /// <devdoc> 71 /// <para>Gets or sets the path to the XML file that contains advertisement data.</para> 72 /// </devdoc> 73 [ 74 Bindable(true), 75 WebCategory("Behavior"), 76 DefaultValue(""), 77 Editor("System.Web.UI.Design.XmlUrlEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)), 78 UrlProperty(), 79 WebSysDescription(SR.AdRotator_AdvertisementFile) 80 ] 81 public string AdvertisementFile { 82 get { 83 return((_advertisementFile == null) ? String.Empty : _advertisementFile); 84 } 85 set { 86 _advertisementFile = value; 87 } 88 } 89 90 91 [ 92 WebCategory("Behavior"), 93 DefaultValue(AdCreatedEventArgs.AlternateTextElement), 94 WebSysDescription(SR.AdRotator_AlternateTextField) 95 ] 96 public String AlternateTextField { 97 get { 98 String s = (String) ViewState["AlternateTextField"]; 99 return((s != null) ? s : AdCreatedEventArgs.AlternateTextElement); 100 } 101 set { 102 ViewState["AlternateTextField"] = value; 103 } 104 } 105 106 /// <devdoc> 107 /// The base url corresponds for mapping of other url elements such as 108 /// imageUrl and navigateUrl. 109 /// </devdoc> 110 internal String BaseUrl { 111 get { 112 if (_baseUrl == null) { 113 // Deal with app relative syntax (e.g. ~/foo) 114 string tplSourceDir = TemplateControlVirtualDirectory.VirtualPathString; 115 116 // For the AdRotator, use the AdvertisementFile directory as the base, and fall back to the 117 // page/user control location as the base. 118 String absoluteFile = null; 119 String fileDirectory = null; 120 if (!String.IsNullOrEmpty(AdvertisementFile)) { 121 absoluteFile = UrlPath.Combine(tplSourceDir, AdvertisementFile); 122 fileDirectory = UrlPath.GetDirectory(absoluteFile); 123 } 124 125 _baseUrl = string.Empty; 126 if (fileDirectory != null) { 127 _baseUrl = fileDirectory; 128 } 129 if (_baseUrl.Length == 0) { 130 _baseUrl = tplSourceDir; 131 } 132 } 133 return _baseUrl; 134 } 135 } 136 137 /// <internalonly/> 138 /// <devdoc> 139 /// Font property. Has no effect on this control, so hide it. 140 /// </devdoc> 141 [ 142 Browsable(false), 143 EditorBrowsableAttribute(EditorBrowsableState.Never), 144 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), 145 ] 146 public override FontInfo Font { 147 get { 148 return base.Font; 149 } 150 } 151 152 153 [ 154 WebCategory("Behavior"), 155 DefaultValue(AdCreatedEventArgs.ImageUrlElement), 156 WebSysDescription(SR.AdRotator_ImageUrlField) 157 ] 158 public String ImageUrlField { 159 get { 160 String s = (String) ViewState["ImageUrlField"]; 161 return((s != null) ? s : AdCreatedEventArgs.ImageUrlElement); 162 } 163 set { 164 ViewState["ImageUrlField"] = value; 165 } 166 } 167 168 private bool IsTargetSet { 169 get { 170 return (ViewState["Target"] != null); 171 } 172 } 173 174 internal bool IsPostCacheAdHelper { 175 get { 176 return _isPostCacheAdHelper; 177 } 178 set { 179 _isPostCacheAdHelper = value; 180 } 181 } 182 183 184 /// <devdoc> 185 /// <para>Gets or sets a category keyword used for matching related advertisements in the advertisement file.</para> 186 /// </devdoc> 187 [ 188 Bindable(true), 189 WebCategory("Behavior"), 190 DefaultValue(""), 191 WebSysDescription(SR.AdRotator_KeywordFilter) 192 ] 193 public string KeywordFilter { 194 get { 195 string s = (string)ViewState["KeywordFilter"]; 196 return((s == null) ? String.Empty : s); 197 } 198 set { 199 // trim the filter value 200 if (String.IsNullOrEmpty(value)) { 201 ViewState.Remove("KeywordFilter"); 202 } 203 else { 204 ViewState["KeywordFilter"] = value.Trim(); 205 } 206 } 207 } 208 209 210 [ 211 WebCategory("Behavior"), 212 DefaultValue(AdCreatedEventArgs.NavigateUrlElement), 213 WebSysDescription(SR.AdRotator_NavigateUrlField) 214 ] 215 public String NavigateUrlField { 216 get { 217 String s = (String) ViewState["NavigateUrlField"]; 218 return((s != null) ? s : AdCreatedEventArgs.NavigateUrlElement); 219 } 220 set { 221 ViewState["NavigateUrlField"] = value; 222 } 223 } 224 225 226 private AdCreatedEventArgs SelectedAdArgs { 227 get { 228 return _adCreatedEventArgs; 229 } 230 set { 231 _adCreatedEventArgs = value; 232 } 233 } 234 235 236 /// <devdoc> 237 /// <para>Gets 238 /// or sets the name of the browser window or frame to display the advertisement.</para> 239 /// </devdoc> 240 [ 241 Bindable(true), 242 WebCategory("Behavior"), 243 DefaultValue("_top"), 244 WebSysDescription(SR.AdRotator_Target), 245 TypeConverter(typeof(TargetConverter)) 246 ] 247 public string Target { 248 get { 249 string s = (string)ViewState["Target"]; 250 return((s == null) ? "_top" : s); 251 } 252 set { 253 ViewState["Target"] = value; 254 } 255 } 256 257 258 protected override HtmlTextWriterTag TagKey { 259 get { 260 return HtmlTextWriterTag.A; 261 } 262 } 263 264 public override string UniqueID { 265 get { 266 if (_uniqueID == null) { 267 _uniqueID = base.UniqueID; 268 } 269 return _uniqueID; 270 } 271 } 272 273 274 /// <devdoc> 275 /// <para>Occurs once per round trip after the creation of the 276 /// control before the page is rendered. </para> 277 /// </devdoc> 278 [ 279 WebCategory("Action"), 280 WebSysDescription(SR.AdRotator_OnAdCreated) 281 ] 282 public event AdCreatedEventHandler AdCreated { 283 add { 284 Events.AddHandler(EventAdCreated, value); 285 } 286 remove { 287 Events.RemoveHandler(EventAdCreated, value); 288 } 289 } 290 CheckOnlyOneDataSource()291 private void CheckOnlyOneDataSource() { 292 int numOfDataSources = ((AdvertisementFile.Length > 0) ? 1 : 0); 293 numOfDataSources += ((DataSourceID.Length > 0) ? 1 : 0); 294 numOfDataSources += ((DataSource != null) ? 1 : 0); 295 296 if (numOfDataSources > 1) { 297 throw new HttpException(SR.GetString(SR.AdRotator_only_one_datasource, ID)); 298 } 299 } 300 301 // Currently this is designed to be called when PostCache Substitution is being initialized CopyFrom(AdRotator adRotator)302 internal void CopyFrom(AdRotator adRotator) { 303 _adRecs = adRotator._adRecs; 304 305 AccessKey = adRotator.AccessKey; 306 AlternateTextField = adRotator.AlternateTextField; 307 Enabled = adRotator.Enabled; 308 ImageUrlField = adRotator.ImageUrlField; 309 NavigateUrlField = adRotator.NavigateUrlField; 310 TabIndex = adRotator.TabIndex; 311 Target = adRotator.Target; 312 ToolTip = adRotator.ToolTip; 313 314 string id = adRotator.ID; 315 if (!String.IsNullOrEmpty(id)) { 316 ID = adRotator.ClientID; 317 } 318 319 // Below are properties that need to be handled specially and saved 320 // to private variables. 321 _uniqueID = adRotator.UniqueID; 322 _baseUrl = adRotator.BaseUrl; 323 324 // Special copy to properties that cannot be assigned directly 325 if (adRotator.HasAttributes) { 326 foreach(string key in adRotator.Attributes.Keys) { 327 Attributes[key] = adRotator.Attributes[key]; 328 } 329 } 330 331 if (adRotator.ControlStyleCreated) { 332 ControlStyle.CopyFrom(adRotator.ControlStyle); 333 } 334 } 335 336 CreateAutoGeneratedFields(IEnumerable dataSource)337 private ArrayList CreateAutoGeneratedFields(IEnumerable dataSource) { 338 if (dataSource == null) { 339 return null; 340 } 341 342 ArrayList generatedFields = new ArrayList(); 343 PropertyDescriptorCollection propertyDescriptors = null; 344 345 if (dataSource is ITypedList) { 346 propertyDescriptors = 347 ((ITypedList)dataSource).GetItemProperties(new PropertyDescriptor[0]); 348 } 349 350 if (propertyDescriptors == null) { 351 352 IEnumerator enumerator = dataSource.GetEnumerator(); 353 if (enumerator.MoveNext()) { 354 355 Object sampleItem = enumerator.Current; 356 if (IsBindableType(sampleItem.GetType())) { 357 // Raise error since we are expecting some record 358 // containing multiple data values. 359 throw new HttpException(SR.GetString(SR.AdRotator_expect_records_with_advertisement_properties, 360 ID, sampleItem.GetType())); 361 } 362 else { 363 propertyDescriptors = TypeDescriptor.GetProperties(sampleItem); 364 } 365 } 366 } 367 if (propertyDescriptors != null && propertyDescriptors.Count > 0) { 368 369 foreach (PropertyDescriptor pd in propertyDescriptors) { 370 if (IsBindableType(pd.PropertyType)) { 371 generatedFields.Add(pd.Name); 372 } 373 } 374 } 375 376 return generatedFields; 377 } 378 379 380 // 381 382 383 384 385 386 387 388 DoPostCacheSubstitutionAsNeeded(HtmlTextWriter writer)389 internal bool DoPostCacheSubstitutionAsNeeded(HtmlTextWriter writer) { 390 if (!IsPostCacheAdHelper && SelectedAdArgs == null && 391 Page.Response.HasCachePolicy && 392 (int)Page.Response.Cache.GetCacheability() != (int)HttpCacheabilityLimits.None) { 393 394 // The checking of the cacheability is to see if the page is output cached 395 AdPostCacheSubstitution adPostCacheSubstitution = new AdPostCacheSubstitution(this); 396 adPostCacheSubstitution.RegisterPostCacheCallBack(Context, Page, writer); 397 return true; 398 } 399 return false; 400 } 401 402 /// <devdoc> 403 /// <para>Select an ad from ad records and create the event 404 /// argument object.</para> 405 /// </devdoc> GetAdCreatedEventArgs()406 private AdCreatedEventArgs GetAdCreatedEventArgs() { 407 IDictionary adInfo = SelectAdFromRecords(); 408 AdCreatedEventArgs adArgs = 409 new AdCreatedEventArgs(adInfo, 410 ImageUrlField, 411 NavigateUrlField, 412 AlternateTextField); 413 return adArgs; 414 } 415 416 GetDataSourceData(IEnumerable dataSource)417 private AdRec [] GetDataSourceData(IEnumerable dataSource) { 418 419 ArrayList fields = CreateAutoGeneratedFields(dataSource); 420 421 ArrayList adDicts = new ArrayList(); 422 IEnumerator enumerator = dataSource.GetEnumerator(); 423 while(enumerator.MoveNext()) { 424 IDictionary dict = null; 425 foreach (String field in fields){ 426 if (dict == null) { 427 dict = new HybridDictionary(); 428 } 429 dict.Add(field, DataBinder.GetPropertyValue(enumerator.Current, field)); 430 } 431 432 if (dict != null) { 433 adDicts.Add(dict); 434 } 435 } 436 437 return SetAdRecs(adDicts); 438 } 439 440 441 /// <devdoc> 442 /// Gets the ad data for the given file by loading the file, or reading from the 443 /// application-level cache. 444 /// </devdoc> GetFileData(string fileName)445 private AdRec [] GetFileData(string fileName) { 446 447 // VSWhidbey 208626: Adopting similar code from xml.cs to support virtual path provider 448 449 // First, figure out if it's a physical or virtual path 450 VirtualPath virtualPath; 451 string physicalPath; 452 ResolvePhysicalOrVirtualPath(fileName, out virtualPath, out physicalPath); 453 454 // try to get it from the ASP.NET cache 455 string fileKey = CacheInternal.PrefixAdRotator + ((!String.IsNullOrEmpty(physicalPath)) ? 456 physicalPath : virtualPath.VirtualPathString); 457 CacheStoreProvider cacheInternal = System.Web.HttpRuntime.Cache.InternalCache; 458 AdRec[] adRecs = cacheInternal.Get(fileKey) as AdRec[]; 459 460 if (adRecs == null) { 461 // Otherwise load it 462 CacheDependency dependency; 463 try { 464 using (Stream stream = OpenFileAndGetDependency(virtualPath, physicalPath, out dependency)) { 465 adRecs = LoadStream(stream); 466 Debug.Assert(adRecs != null); 467 } 468 } 469 catch (Exception e) { 470 if (!String.IsNullOrEmpty(physicalPath) && HttpRuntime.HasPathDiscoveryPermission(physicalPath)) { 471 // We want to catch the error message, but not propage the inner exception. Otherwise we can throw up 472 // logon prompts through IE; 473 throw new HttpException(SR.GetString(SR.AdRotator_cant_open_file, ID, e.Message)); 474 } 475 else { 476 throw new HttpException(SR.GetString(SR.AdRotator_cant_open_file_no_permission, ID)); 477 } 478 } 479 480 // Cache it, but only if we got a dependency 481 if (dependency != null) { 482 using (dependency) { 483 // and store it in the cache, dependent on the file name 484 cacheInternal.Insert(fileKey, adRecs, new CacheInsertOptions() { Dependencies = dependency }); 485 } 486 } 487 } 488 return adRecs; 489 } 490 GetRandomNumber(int maxValue)491 private static int GetRandomNumber(int maxValue) { 492 if (_random == null) { 493 _random = new Random(); 494 } 495 return _random.Next(maxValue) + 1; 496 } 497 GetXmlDataSourceData(XmlDataSource xmlDataSource)498 private AdRec [] GetXmlDataSourceData(XmlDataSource xmlDataSource) { 499 Debug.Assert(xmlDataSource != null); 500 501 XmlDocument doc = xmlDataSource.GetXmlDocument(); 502 if (doc == null) { 503 return null; 504 } 505 return LoadXmlDocument(doc); 506 } 507 IsBindableType(Type type)508 private bool IsBindableType(Type type) { 509 return(type.IsPrimitive || 510 (type == typeof(String)) || 511 (type == typeof(DateTime)) || 512 (type == typeof(Decimal))); 513 } 514 IsOnAdCreatedOverridden()515 private bool IsOnAdCreatedOverridden() { 516 bool result = false; 517 Type type = this.GetType(); 518 if (type != _adrotatorType) { 519 MethodInfo methodInfo = type.GetMethod("OnAdCreated", 520 BindingFlags.NonPublic | BindingFlags.Instance, 521 null, 522 _AdCreatedParameterTypes, 523 null); 524 if (methodInfo.DeclaringType != _adrotatorType) { 525 result = true; 526 } 527 } 528 return result; 529 } 530 LoadFromXmlReader(XmlReader reader)531 private AdRec [] LoadFromXmlReader(XmlReader reader) { 532 ArrayList adDicts = new ArrayList(); 533 534 while (reader.Read()) { 535 if (reader.Name == "Advertisements") { 536 if (reader.Depth != 0) { 537 return null; 538 } 539 break; 540 } 541 } 542 543 while (reader.Read()) { 544 if (reader.NodeType == XmlNodeType.Element && reader.Name == "Ad" && reader.Depth == 1) { 545 546 IDictionary dict = null; 547 reader.Read(); 548 while (!(reader.NodeType == XmlNodeType.EndElement)) { 549 if (reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement) { 550 if (dict == null) { 551 dict = new HybridDictionary(); 552 } 553 dict.Add(reader.LocalName, reader.ReadString()); 554 } 555 reader.Skip(); 556 } 557 558 if (dict != null) { 559 adDicts.Add(dict); 560 } 561 } 562 } 563 564 AdRec [] adRecs = SetAdRecs(adDicts); 565 return adRecs; 566 } 567 568 /// <devdoc> 569 /// Loads the given XML stream into an array of AdRec structures 570 /// </devdoc> LoadStream(Stream stream)571 private AdRec [] LoadStream(Stream stream) { 572 573 AdRec [] adRecs = null; 574 try { 575 // Read the XML stream into an array of dictionaries 576 XmlReader reader = XmlUtils.CreateXmlReader(stream); 577 578 // Perf: We use LoadFromXmlReader instead of LoadXmlDocument to 579 // do the text parsing only once 580 adRecs = LoadFromXmlReader(reader); 581 } 582 catch (Exception e) { 583 throw new HttpException( 584 SR.GetString(SR.AdRotator_parse_error, ID, e.Message), e); 585 } 586 587 if (adRecs == null) { 588 throw new HttpException( 589 SR.GetString(SR.AdRotator_no_advertisements, ID, AdvertisementFile)); 590 } 591 592 return adRecs; 593 } 594 LoadXmlDocument(XmlDocument doc)595 private AdRec [] LoadXmlDocument(XmlDocument doc) { 596 // Read the XML data into an array of dictionaries 597 ArrayList adDicts = new ArrayList(); 598 599 if (doc.DocumentElement != null && 600 doc.DocumentElement.LocalName == XmlDocumentTag) { 601 602 XmlNode elem = doc.DocumentElement.FirstChild; 603 604 while (elem != null) { 605 IDictionary dict = null; 606 if (elem.LocalName.Equals(XmlAdTag)) { 607 XmlNode prop = elem.FirstChild; 608 while (prop != null) { 609 if (prop.NodeType == XmlNodeType.Element) { 610 if (dict == null) { 611 dict = new HybridDictionary(); 612 } 613 dict.Add(prop.LocalName, prop.InnerText); 614 } 615 prop = prop.NextSibling; 616 } 617 } 618 if (dict != null) { 619 adDicts.Add(dict); 620 } 621 elem = elem.NextSibling; 622 } 623 } 624 625 AdRec [] adRecs = SetAdRecs(adDicts); 626 return adRecs; 627 } 628 629 /// <devdoc> 630 /// Used to determine if the advertisement meets current criteria. Does a comparison with 631 /// KeywordFilter if it is set. 632 /// </devdoc> MatchingAd(AdRec adRec, string keywordFilter)633 private bool MatchingAd(AdRec adRec, string keywordFilter) { 634 Debug.Assert(keywordFilter != null && keywordFilter.Length > 0); 635 return(String.Equals(keywordFilter, adRec.keyword, StringComparison.OrdinalIgnoreCase)); 636 } 637 638 639 /// <devdoc> 640 /// <para>Raises the <see cref='System.Web.UI.WebControls.AdRotator.AdCreated'/> event for an <see cref='System.Web.UI.WebControls.AdRotator'/>.</para> 641 /// </devdoc> OnAdCreated(AdCreatedEventArgs e)642 protected virtual void OnAdCreated(AdCreatedEventArgs e) { 643 AdCreatedEventHandler handler = (AdCreatedEventHandler)Events[EventAdCreated]; 644 if (handler != null) handler(this, e); 645 } 646 647 OnInit(EventArgs e)648 protected internal override void OnInit(EventArgs e) { 649 base.OnInit(e); 650 651 // VSWhidbey 419600: We just always need binding data every time since 652 // AdRotator doesn't store the entire Ad data in ViewState for selecting 653 // Ad during postbacks. It's too big for storing in ViewState. 654 RequiresDataBinding = true; 655 } 656 657 /// <internalonly/> 658 /// <devdoc> 659 /// <para>Gets the advertisement information for rendering in its parameter, then calls 660 /// the OnAdCreated event to render the ads.</para> 661 /// </devdoc> OnPreRender(EventArgs e)662 protected internal override void OnPreRender(EventArgs e) { 663 base.OnPreRender(e); 664 665 // If after PreRender (which would call DataBind if DataSource or DataSourceID available) 666 // and no _adRecs created, it must be the normal v1 behavior which uses ad file. 667 if (_adRecs == null && AdvertisementFile.Length > 0) { 668 PerformAdFileBinding(); 669 } 670 671 // If handler is specified, we don't do any post-cache 672 // substitution because the handler code would not be executed. 673 // 674 // VSWhidbey 213759: We also don't want any post-cache substitution 675 // if OnAdCreated has been overridden 676 if (Events[EventAdCreated] != null || IsOnAdCreatedOverridden()) { 677 // Fire the user event for further customization 678 SelectedAdArgs = GetAdCreatedEventArgs(); 679 OnAdCreated(SelectedAdArgs); 680 } 681 } 682 PerformAdFileBinding()683 private void PerformAdFileBinding() { 684 // Getting ad data from physical file is V1 way which is not supported 685 // by the base class DataBoundControl so we had above code to handle 686 // this case. However, we need to support DataBound control events 687 // in Whidbey and since above code doesn't go through the event 688 // raising in the base class DataBoundControl, here we mimic them. 689 OnDataBinding(EventArgs.Empty); 690 691 // get the ads from the file or app cache 692 _adRecs = GetFileData(AdvertisementFile); 693 694 OnDataBound(EventArgs.Empty); 695 } 696 PerformDataBinding(IEnumerable data)697 protected internal override void PerformDataBinding(IEnumerable data) { 698 if (data != null) { 699 // We retrieve ad data from xml format in a specific way. 700 XmlDataSource xmlDataSource = null; 701 object dataSource = DataSource; 702 if (dataSource != null) { 703 xmlDataSource = dataSource as XmlDataSource; 704 } 705 else { // DataSourceID case, we know that only one source is available 706 xmlDataSource = GetDataSource() as XmlDataSource; 707 } 708 709 if (xmlDataSource != null) { 710 _adRecs = GetXmlDataSourceData(xmlDataSource); 711 } 712 else { 713 _adRecs = GetDataSourceData(data); 714 } 715 } 716 } 717 PerformSelect()718 protected override void PerformSelect() { 719 // VSWhidbey 141362 720 CheckOnlyOneDataSource(); 721 722 if (AdvertisementFile.Length > 0) { 723 PerformAdFileBinding(); 724 } 725 else { 726 base.PerformSelect(); 727 } 728 } 729 730 // PickAd()731 internal AdCreatedEventArgs PickAd() { 732 AdCreatedEventArgs adArgs = SelectedAdArgs; 733 if (adArgs == null) { 734 adArgs = GetAdCreatedEventArgs(); 735 } 736 adArgs.ImageUrl = ResolveAdRotatorUrl(BaseUrl, adArgs.ImageUrl); 737 adArgs.NavigateUrl = ResolveAdRotatorUrl(BaseUrl, adArgs.NavigateUrl); 738 return adArgs; 739 } 740 741 742 /// <internalonly/> 743 /// <devdoc> 744 /// <para>Displays the <see cref='System.Web.UI.WebControls.AdRotator'/> on the client.</para> 745 /// </devdoc> Render(HtmlTextWriter writer)746 protected internal override void Render(HtmlTextWriter writer) { 747 if (!DesignMode && !IsPostCacheAdHelper && 748 DoPostCacheSubstitutionAsNeeded(writer)) { 749 return; 750 } 751 752 AdCreatedEventArgs adArgs = PickAd(); 753 RenderLink(writer, adArgs); 754 } 755 RenderLink(HtmlTextWriter writer, AdCreatedEventArgs adArgs)756 private void RenderLink(HtmlTextWriter writer, AdCreatedEventArgs adArgs) { 757 Debug.Assert(writer != null); 758 Debug.Assert(adArgs != null); 759 760 HyperLink bannerLink = new HyperLink(); 761 762 bannerLink.NavigateUrl = adArgs.NavigateUrl; 763 bannerLink.Target = Target; 764 765 if (HasAttributes) { 766 foreach(string key in Attributes.Keys) { 767 bannerLink.Attributes[key] = Attributes[key]; 768 } 769 } 770 771 string id = ID; 772 if (!String.IsNullOrEmpty(id)) { 773 bannerLink.ID = ClientID; 774 } 775 776 if (!Enabled) { 777 bannerLink.Enabled = false; 778 } 779 780 // WebControl's properties use a private flag to determine if a 781 // property is set and does not return the value unless the flag is 782 // marked. So here we access those properites (inherited from WebControl) 783 // directly from the ViewState bag because if ViewState bag reference 784 // was copied to the helper class in the optimized case during the 785 // Initialize() method, the flags of the properties wouldn't be set 786 // in the helper class. 787 string accessKey = (string) ViewState["AccessKey"]; 788 if (!String.IsNullOrEmpty(accessKey)) { 789 bannerLink.AccessKey = accessKey; 790 } 791 792 object o = ViewState["TabIndex"]; 793 if (o != null) { 794 short tabIndex = (short) o; 795 if (tabIndex != (short) 0) { 796 bannerLink.TabIndex = tabIndex; 797 } 798 } 799 800 bannerLink.RenderBeginTag(writer); 801 802 // create inner Image 803 Image bannerImage = new Image(); 804 // apply styles to image 805 if (ControlStyleCreated) { 806 bannerImage.ApplyStyle(ControlStyle); 807 } 808 809 string alternateText = adArgs.AlternateText; 810 if (!String.IsNullOrEmpty(alternateText)) { 811 bannerImage.AlternateText = alternateText; 812 } 813 else { 814 // 25914 Do not render empty 'alt' attribute if <AlternateText> tag is never specified 815 IDictionary adProps = adArgs.AdProperties; 816 string altTextKey = (AlternateTextField.Length != 0) 817 ? AlternateTextField : AdCreatedEventArgs.AlternateTextElement; 818 string altText = (adProps == null) ? null : (string) adProps[altTextKey]; 819 if (altText != null && altText.Length == 0) { 820 bannerImage.GenerateEmptyAlternateText = true; 821 } 822 } 823 824 // Perf work: AdRotator should have resolved the NavigateUrl and 825 // ImageUrl when assigning them and have UrlResolved set properly. 826 bannerImage.UrlResolved = true; 827 string imageUrl = adArgs.ImageUrl; 828 if (!String.IsNullOrEmpty(imageUrl)) { 829 bannerImage.ImageUrl = imageUrl; 830 } 831 832 if (adArgs.HasWidth) { 833 bannerImage.ControlStyle.Width = adArgs.Width; 834 } 835 836 if (adArgs.HasHeight) { 837 bannerImage.ControlStyle.Height = adArgs.Height; 838 } 839 840 string toolTip = (string) ViewState["ToolTip"]; 841 if (!String.IsNullOrEmpty(toolTip)) { 842 bannerImage.ToolTip = toolTip; 843 } 844 845 bannerImage.RenderControl(writer); 846 bannerLink.RenderEndTag(writer); 847 } 848 ResolveAdRotatorUrl(string baseUrl, string relativeUrl)849 private string ResolveAdRotatorUrl(string baseUrl, string relativeUrl) { 850 851 if ((relativeUrl == null) || 852 (relativeUrl.Length == 0) || 853 (UrlPath.IsRelativeUrl(relativeUrl) == false) || 854 (baseUrl == null) || 855 (baseUrl.Length == 0)) { 856 return relativeUrl; 857 } 858 859 // make it absolute 860 return UrlPath.Combine(baseUrl, relativeUrl); 861 } 862 863 /// <devdoc> 864 /// <para>Selects an advertisement from the a list of records based 865 /// on different factors.</para> 866 /// </devdoc> SelectAdFromRecords()867 private IDictionary SelectAdFromRecords() { 868 if (_adRecs == null || _adRecs.Length == 0) { 869 return null; 870 } 871 872 string keywordFilter = KeywordFilter; 873 bool noKeywordFilter = String.IsNullOrEmpty(keywordFilter); 874 if (!noKeywordFilter) { 875 // do a lower case comparison 876 keywordFilter = keywordFilter.ToLower(CultureInfo.InvariantCulture); 877 } 878 879 // sum the matching impressions 880 int totalImpressions = 0; 881 for (int i = 0; i < _adRecs.Length; i++) { 882 if (noKeywordFilter || MatchingAd(_adRecs[i], keywordFilter)) { 883 totalImpressions += _adRecs[i].impressions; 884 } 885 } 886 887 if (totalImpressions == 0) { 888 return null; 889 } 890 891 // select one using a random number between 1 and totalImpressions 892 int selectedImpression = GetRandomNumber(totalImpressions); 893 int impressionCounter = 0; 894 int selectedIndex = -1; 895 for (int i = 0; i < _adRecs.Length; i++) { 896 // Is this the ad? 897 if (noKeywordFilter || MatchingAd(_adRecs[i], keywordFilter)) { 898 impressionCounter += _adRecs[i].impressions; 899 if (selectedImpression <= impressionCounter) { 900 selectedIndex = i; 901 break; 902 } 903 } 904 } 905 Debug.Assert(selectedIndex >= 0 && selectedIndex < _adRecs.Length, "Index not found"); 906 907 return _adRecs[selectedIndex].adProperties; 908 } 909 SetAdRecs(ArrayList adDicts)910 private AdRec [] SetAdRecs(ArrayList adDicts) { 911 if (adDicts == null || adDicts.Count == 0) { 912 return null; 913 } 914 915 // Create an array of AdRec structures from the dictionaries, removing blanks 916 AdRec [] adRecs = new AdRec[adDicts.Count]; 917 int iRec = 0; 918 for (int i = 0; i < adDicts.Count; i++) { 919 if (adDicts[i] != null) { 920 adRecs[iRec].Initialize((IDictionary) adDicts[i]); 921 iRec++; 922 } 923 } 924 Debug.Assert(iRec == adDicts.Count, "Record count did not match non-null entries"); 925 926 return adRecs; 927 } 928 929 930 931 /// <devdoc> 932 /// Structure to store ads in memory for fast selection by multiple instances of adrotator 933 /// Stores the dictionary and caches some values for easier selection. 934 /// </devdoc> 935 private struct AdRec { 936 public string keyword; 937 public int impressions; 938 public IDictionary adProperties; 939 940 941 /// <devdoc> 942 /// Initialize the stuct based on a dictionary containing the advertisement properties 943 /// </devdoc> InitializeSystem.Web.UI.WebControls.AdRotator.AdRec944 public void Initialize(IDictionary adProperties) { 945 946 // Initialize the values we need to keep for ad selection 947 Debug.Assert(adProperties != null, "Required here"); 948 this.adProperties = adProperties; 949 950 // remove null and trim keyword for easier comparisons. 951 // VSWhidbey 114634: Be defensive and only retrieve the keyword 952 // value if it is in string type 953 object keywordValue = adProperties[KeywordProperty]; 954 if (keywordValue != null && keywordValue is string) { 955 keyword = ((string) keywordValue).Trim(); 956 } 957 else { 958 keyword = string.Empty; 959 } 960 961 // get the impressions, but be defensive: let the schema enforce the rules. Default to 1. 962 string impressionsString = adProperties[ImpressionsProperty] as string; 963 if (String.IsNullOrEmpty(impressionsString) || 964 !int.TryParse(impressionsString, NumberStyles.Integer, 965 CultureInfo.InvariantCulture, out impressions)) { 966 impressions = 1; 967 } 968 if (impressions < 0) { 969 impressions = 1; 970 } 971 } 972 } 973 } 974 } 975