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