1 //------------------------------------------------------------------------------ 2 // <copyright file="ScriptComponentDescriptor.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 //------------------------------------------------------------------------------ 6 7 namespace System.Web.UI { 8 using System; 9 using System.Collections.Generic; 10 using System.Collections.ObjectModel; 11 using System.Diagnostics; 12 using System.Diagnostics.CodeAnalysis; 13 using System.Globalization; 14 using System.Text; 15 using System.Text.RegularExpressions; 16 using System.Web; 17 using System.Web.Resources; 18 using System.Web.Script.Serialization; 19 20 public class ScriptComponentDescriptor : ScriptDescriptor { 21 // PERF: In the ScriptControl/Properties/Scenario.aspx perf test, SortedList is 2% faster than 22 // SortedDictionary. 23 private string _elementIDInternal; 24 private SortedList<string, string> _events; 25 private string _id; 26 private SortedList<string, Expression> _properties; 27 private bool _registerDispose = true; 28 private JavaScriptSerializer _serializer; 29 private string _type; 30 ScriptComponentDescriptor(string type)31 public ScriptComponentDescriptor(string type) { 32 if (String.IsNullOrEmpty(type)) { 33 throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "type"); 34 } 35 _type = type; 36 } 37 38 // Used by ScriptBehaviorDescriptor and ScriptControlDesriptor ScriptComponentDescriptor(string type, string elementID)39 internal ScriptComponentDescriptor(string type, string elementID) 40 : this(type) { 41 if (String.IsNullOrEmpty(elementID)) { 42 throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "elementID"); 43 } 44 _elementIDInternal = elementID; 45 } 46 47 [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")] 48 public virtual string ClientID { 49 get { 50 return ID; 51 } 52 } 53 54 internal string ElementIDInternal { 55 get { 56 return _elementIDInternal; 57 } 58 } 59 60 private SortedList<string, string> Events { 61 get { 62 if (_events == null) { 63 _events = new SortedList<string, string>(StringComparer.Ordinal); 64 } 65 return _events; 66 } 67 } 68 69 [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")] 70 public virtual string ID { 71 get { 72 return _id ?? String.Empty; 73 } 74 set { 75 _id = value; 76 } 77 } 78 79 private SortedList<string, Expression> Properties { 80 get { 81 if (_properties == null) { 82 _properties = new SortedList<string, Expression>(StringComparer.Ordinal); 83 } 84 return _properties; 85 } 86 } 87 88 internal bool RegisterDispose { 89 get { 90 return _registerDispose; 91 } 92 set { 93 _registerDispose = value; 94 } 95 } 96 97 private JavaScriptSerializer Serializer { 98 get { 99 if (_serializer == null) { 100 _serializer = new JavaScriptSerializer(); 101 } 102 return _serializer; 103 } 104 } 105 106 [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", 107 Justification = "Refers to a script element, not to Object.GetType()")] 108 public string Type { 109 get { 110 return _type; 111 } 112 set { 113 if (String.IsNullOrEmpty(value)) { 114 throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "value"); 115 } 116 _type = value; 117 } 118 } 119 120 [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")] AddComponentProperty(string name, string componentID)121 public void AddComponentProperty(string name, string componentID) { 122 if (String.IsNullOrEmpty(componentID)) { 123 throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "componentID"); 124 } 125 AddProperty(name, new ComponentReference(componentID)); 126 } 127 128 [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")] AddElementProperty(string name, string elementID)129 public void AddElementProperty(string name, string elementID) { 130 if (String.IsNullOrEmpty(elementID)) { 131 throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "elementID"); 132 } 133 AddProperty(name, new ElementReference(elementID)); 134 } 135 AddEvent(string name, string handler)136 public void AddEvent(string name, string handler) { 137 if (String.IsNullOrEmpty(name)) { 138 throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "name"); 139 } 140 if (String.IsNullOrEmpty(handler)) { 141 throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "handler"); 142 } 143 Events[name] = handler; 144 } 145 AddProperty(string name, object value)146 public void AddProperty(string name, object value) { 147 AddProperty(name, new ObjectReference(value)); 148 } 149 AddProperty(string name, Expression value)150 private void AddProperty(string name, Expression value) { 151 if (String.IsNullOrEmpty(name)) { 152 throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "name"); 153 } 154 Debug.Assert(value != null); 155 Properties[name] = value; 156 } 157 AddScriptProperty(string name, string script)158 public void AddScriptProperty(string name, string script) { 159 if (String.IsNullOrEmpty(script)) { 160 throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "script"); 161 } 162 AddProperty(name, new ScriptExpression(script)); 163 } 164 AppendEventsScript(StringBuilder builder)165 private void AppendEventsScript(StringBuilder builder) { 166 // PERF: Use field directly to avoid creating Dictionary if not already created 167 if (_events != null && _events.Count > 0) { 168 builder.Append('{'); 169 bool first = true; 170 171 // Can't use JavaScriptSerializer directly on events dictionary, since the values are 172 // JavaScript functions and should not be quoted. 173 foreach (KeyValuePair<string, string> e in _events) { 174 if (first) { 175 first = false; 176 } 177 else { 178 builder.Append(','); 179 } 180 builder.Append('"'); 181 builder.Append(HttpUtility.JavaScriptStringEncode(e.Key)); 182 builder.Append('"'); 183 builder.Append(':'); 184 builder.Append(e.Value); 185 } 186 187 builder.Append("}"); 188 } 189 else { 190 builder.Append("null"); 191 } 192 } 193 AppendPropertiesScript(StringBuilder builder)194 private void AppendPropertiesScript(StringBuilder builder) { 195 bool first = true; 196 197 // PERF: Use field directly to avoid creating Dictionary if not already created 198 if (_properties != null && _properties.Count > 0) { 199 foreach (KeyValuePair<string, Expression> p in _properties) { 200 if (p.Value.Type == ExpressionType.Script) { 201 if (first) { 202 builder.Append("{"); 203 first = false; 204 } 205 else { 206 builder.Append(","); 207 } 208 builder.Append('"'); 209 builder.Append(HttpUtility.JavaScriptStringEncode(p.Key)); 210 builder.Append('"'); 211 builder.Append(':'); 212 p.Value.AppendValue(Serializer, builder); 213 } 214 } 215 } 216 217 if (first) { 218 // If we didn't see any properties, append "null" 219 builder.Append("null"); 220 } 221 else { 222 // Else, close the JSON object 223 builder.Append("}"); 224 } 225 } 226 AppendReferencesScript(StringBuilder builder)227 private void AppendReferencesScript(StringBuilder builder) { 228 bool first = true; 229 230 // PERF: Use field directly to avoid creating Dictionary if not already created 231 if (_properties != null && _properties.Count > 0) { 232 foreach (KeyValuePair<string, Expression> p in _properties) { 233 if (p.Value.Type == ExpressionType.ComponentReference) { 234 if (first) { 235 builder.Append("{"); 236 first = false; 237 } 238 else { 239 builder.Append(","); 240 } 241 builder.Append('"'); 242 builder.Append(HttpUtility.JavaScriptStringEncode(p.Key)); 243 builder.Append('"'); 244 builder.Append(':'); 245 p.Value.AppendValue(Serializer, builder); 246 } 247 } 248 } 249 250 if (first) { 251 // If we didn't see any references, append "null" 252 builder.Append("null"); 253 } 254 else { 255 // Else, close the JSON object 256 builder.Append("}"); 257 } 258 } 259 GetScript()260 protected internal override string GetScript() { 261 const string separator = ", "; 262 263 if (!String.IsNullOrEmpty(ID)) { 264 AddProperty("id", ID); 265 } 266 267 StringBuilder builder = new StringBuilder(); 268 builder.Append("$create("); 269 270 builder.Append(Type); 271 272 builder.Append(separator); 273 AppendPropertiesScript(builder); 274 275 builder.Append(separator); 276 AppendEventsScript(builder); 277 278 builder.Append(separator); 279 AppendReferencesScript(builder); 280 281 if (ElementIDInternal != null) { 282 builder.Append(separator); 283 builder.Append("$get(\""); 284 builder.Append(HttpUtility.JavaScriptStringEncode(ElementIDInternal)); 285 builder.Append("\")"); 286 } 287 288 builder.Append(");"); 289 290 return builder.ToString(); 291 } 292 RegisterDisposeForDescriptor(ScriptManager scriptManager, Control owner)293 internal override void RegisterDisposeForDescriptor(ScriptManager scriptManager, Control owner) { 294 if (RegisterDispose && scriptManager.SupportsPartialRendering) { 295 // If partial rendering is supported, register a JavaScript statement 296 // that will dispose this component if the UpdatePanel it is inside is 297 // getting refreshed. Only components need this; controls are associated 298 // with DOM elements so they get disposed through their 'dispose' expando. 299 scriptManager.RegisterDispose(owner, "$find('" + ID + "').dispose();"); 300 } 301 } 302 303 private abstract class Expression { 304 public abstract ExpressionType Type { get; } AppendValue(JavaScriptSerializer serializer, StringBuilder builder)305 public abstract void AppendValue(JavaScriptSerializer serializer, StringBuilder builder); 306 } 307 308 private enum ExpressionType { 309 Script, 310 ComponentReference 311 } 312 313 private sealed class ComponentReference : Expression { 314 private string _componentID; 315 ComponentReference(string componentID)316 public ComponentReference(string componentID) { 317 _componentID = componentID; 318 } 319 320 public override ExpressionType Type { 321 get { 322 return ExpressionType.ComponentReference; 323 } 324 } 325 AppendValue(JavaScriptSerializer serializer, StringBuilder builder)326 public override void AppendValue(JavaScriptSerializer serializer, StringBuilder builder) { 327 builder.Append('"'); 328 builder.Append(HttpUtility.JavaScriptStringEncode(_componentID)); 329 builder.Append('"'); 330 } 331 } 332 333 private sealed class ElementReference : Expression { 334 private string _elementID; 335 ElementReference(string elementID)336 public ElementReference(string elementID) { 337 _elementID = elementID; 338 } 339 340 public override ExpressionType Type { 341 get { 342 return ExpressionType.Script; 343 } 344 } 345 AppendValue(JavaScriptSerializer serializer, StringBuilder builder)346 public override void AppendValue(JavaScriptSerializer serializer, StringBuilder builder) { 347 builder.Append("$get(\""); 348 builder.Append(HttpUtility.JavaScriptStringEncode(_elementID)); 349 builder.Append("\")"); 350 } 351 } 352 353 private sealed class ObjectReference : Expression { 354 private object _value; 355 ObjectReference(object value)356 public ObjectReference(object value) { 357 _value = value; 358 } 359 360 public override ExpressionType Type { 361 get { 362 return ExpressionType.Script; 363 } 364 } 365 AppendValue(JavaScriptSerializer serializer, StringBuilder builder)366 public override void AppendValue(JavaScriptSerializer serializer, StringBuilder builder) { 367 // DevDiv Bugs 96574: pass SerializationFormat.JavaScript to serialize to straight JavaScript, 368 // so date properties are rendered as dates 369 serializer.Serialize(_value, builder, JavaScriptSerializer.SerializationFormat.JavaScript); 370 } 371 } 372 373 private sealed class ScriptExpression : Expression { 374 private string _script; 375 ScriptExpression(string script)376 public ScriptExpression(string script) { 377 _script = script; 378 } 379 380 public override ExpressionType Type { 381 get { 382 return ExpressionType.Script; 383 } 384 } 385 AppendValue(JavaScriptSerializer serializer, StringBuilder builder)386 public override void AppendValue(JavaScriptSerializer serializer, StringBuilder builder) { 387 builder.Append(_script); 388 } 389 } 390 } 391 } 392