2 // ScriptHandlerFactory.cs
5 // Konstantin Triger <kostat@mainsoft.com>
7 // (C) 2007 Mainsoft, Inc. http://www.mainsoft.com
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections.Generic;
33 using System.Web.Services;
34 using System.Reflection;
35 using System.Collections;
36 using System.Web.Script.Serialization;
38 using System.Xml.Serialization;
41 namespace System.Web.Script.Services
43 internal sealed class LogicalTypeInfo
45 #region LogicalMethodInfo
47 public sealed class LogicalMethodInfo
49 readonly LogicalTypeInfo _typeInfo;
50 readonly MethodInfo _methodInfo;
52 readonly WebMethodAttribute _wma;
54 readonly ScriptMethodAttribute _sma;
56 readonly ParameterInfo [] _params;
57 readonly Dictionary<string, int> _paramMap;
58 readonly XmlSerializer _xmlSer;
60 public LogicalMethodInfo (LogicalTypeInfo typeInfo, MethodInfo method) {
64 _wma = (WebMethodAttribute) Attribute.GetCustomAttribute (method, typeof (WebMethodAttribute));
66 _sma = (ScriptMethodAttribute) Attribute.GetCustomAttribute (method, typeof (ScriptMethodAttribute));
68 _sma = ScriptMethodAttribute.Default;
70 _params = MethodInfo.GetParameters ();
73 _paramMap = new Dictionary<string, int> (_params.Length, StringComparer.Ordinal);
74 for (int i = 0; i < _params.Length; i++)
75 _paramMap.Add(_params[i].Name, i);
78 if (ScriptMethod.ResponseFormat == ResponseFormat.Xml
79 && MethodInfo.ReturnType != typeof(void))
80 _xmlSer = new XmlSerializer (MethodInfo.ReturnType);
83 public void Invoke (IDictionary<string, object> @params, TextWriter writer) {
86 pp = new object [_params.Length];
88 foreach (KeyValuePair<string, object> pair in @params) {
89 int i = _paramMap [pair.Key];
90 pp [i] = LogicalTypeInfo.JSSerializer.ConvertToType (_params [i].ParameterType, pair.Value);
94 object target = MethodInfo.IsStatic ? null : Activator.CreateInstance (_typeInfo._type);
95 object result = MethodInfo.Invoke (target, pp);
96 if (_xmlSer != null) {
97 XmlTextWriter xwriter = new XmlTextWriter (writer);
98 xwriter.Formatting = Formatting.None;
99 _xmlSer.Serialize (xwriter, result);
102 LogicalTypeInfo.JSSerializer.Serialize (result, writer);
105 bool HasParameters { get { return _params != null && _params.Length > 0; } }
106 public string MethodName { get { return String.IsNullOrEmpty (WebMethod.MessageName) ? MethodInfo.Name : WebMethod.MessageName; } }
108 public ScriptMethodAttribute ScriptMethod { get { return _sma; } }
109 public MethodInfo MethodInfo { get { return _methodInfo; } }
110 public WebMethodAttribute WebMethod { get { return _wma; } }
111 public IEnumerable<Type> GetParameterTypes () {
113 for (int i = 0; i < _params.Length; i++)
114 yield return _params [i].ParameterType;
116 yield return MethodInfo.ReturnType;
119 public void GenerateMethod (StringBuilder proxy, bool isPrototype, bool isPage) {
120 string service = isPage ? "PageMethods" : MethodInfo.DeclaringType.FullName;
122 string useHttpGet = ScriptMethod.UseHttpGet ? "true" : "false";
123 string paramMap = GenerateParameters (true);
124 string paramList = GenerateParameters (false);
129 {1}:function({4}succeededCallback, failedCallback, userContext) {{
130 return this._invoke({0}.get_path(), '{1}',{2},{{{3}}},succeededCallback,failedCallback,userContext); }}",
131 service, MethodName, useHttpGet, paramMap, paramList);
136 {0}.{1}= function({2}onSuccess,onFailed,userContext) {{{0}._staticInstance.{1}({2}onSuccess,onFailed,userContext); }}",
137 service, MethodName, paramList);
140 string GenerateParameters (bool isMap) {
144 StringBuilder builder = new StringBuilder ();
146 for (int i = 0; i < _params.Length; i++) {
147 builder.AppendFormat (isMap ? "{0}:{0}" : "{0}", _params [i].Name);
148 builder.Append (',');
154 return builder.ToString ();
161 static Hashtable _type_to_logical_type = Hashtable.Synchronized (new Hashtable ());
163 const string type_to_logical_type_key = "System.Web.Script.Services.LogicalTypeInfo";
164 static Hashtable _type_to_logical_type {
166 Hashtable hash = (Hashtable) AppDomain.CurrentDomain.GetData (type_to_logical_type_key);
171 AppDomain.CurrentDomain.SetData (type_to_logical_type_key, Hashtable.Synchronized (new Hashtable ()));
174 return (Hashtable) AppDomain.CurrentDomain.GetData (type_to_logical_type_key);
179 //readonly LogicalMethodInfo [] _logicalMethods;
180 readonly Hashtable _methodMap;
182 readonly string _proxy;
183 static readonly JavaScriptSerializer JSSerializer = new JavaScriptSerializer ();
185 private LogicalTypeInfo (Type t, string filePath) {
187 bool isPage = _type.IsSubclassOf (typeof (System.Web.UI.Page));
188 BindingFlags bindingAttr = isPage ? (BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.Public) : (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
189 MethodInfo [] all_type_methods = _type.GetMethods (bindingAttr);
190 List<LogicalMethodInfo> logicalMethods = new List<LogicalMethodInfo> (all_type_methods.Length);
191 foreach (MethodInfo mi in all_type_methods) {
193 mi.GetCustomAttributes (typeof (WebMethodAttribute), false).Length > 0)
194 logicalMethods.Add (new LogicalMethodInfo (this, mi));
196 foreach (Type ifaceType in _type.GetInterfaces ()) {
197 if (ifaceType.GetCustomAttributes (typeof (WebServiceBindingAttribute), false).Length > 0) {
198 MethodInfo found = FindInInterface (ifaceType, mi);
200 if (found.GetCustomAttributes (typeof (WebMethodAttribute), false).Length > 0)
201 logicalMethods.Add (new LogicalMethodInfo (this, found));
210 //_logicalMethods = (LogicalMethodInfo []) list.ToArray (typeof (LogicalMethodInfo));
212 _methodMap = new Hashtable (logicalMethods.Count);
213 for (int i = 0; i < logicalMethods.Count; i++)
214 _methodMap.Add (logicalMethods [i].MethodName, logicalMethods [i]);
216 string ns = isPage ? String.Empty : t.Namespace;
217 string service = isPage ? "PageMethods" : t.FullName;
219 StringBuilder proxy = new StringBuilder ();
220 if (String.IsNullOrEmpty (ns))
226 @"Type.registerNamespace('{0}');
231 {0}.initializeBase(this);
233 this._userContext = null;
234 this._succeeded = null;
240 for (int i = 0; i < logicalMethods.Count; i++) {
243 logicalMethods [i].GenerateMethod (proxy, true, isPage);
249 {0}.registerClass('{0}',Sys.Net.WebServiceProxy);
250 {0}._staticInstance = new {0}();
251 {0}.set_path = function(value) {{ {0}._staticInstance.set_path(value); }}
252 {0}.get_path = function() {{ return {0}._staticInstance.get_path(); }}
253 {0}.set_timeout = function(value) {{ {0}._staticInstance.set_timeout(value); }}
254 {0}.get_timeout = function() {{ return {0}._staticInstance.get_timeout(); }}
255 {0}.set_defaultUserContext = function(value) {{ {0}._staticInstance.set_userContext(value); }}
256 {0}.get_defaultUserContext = function() {{ return {0}._staticInstance.get_userContext(); }}
257 {0}.set_defaultSucceededCallback = function(value) {{ {0}._staticInstance.set_succeeded(value); }}
258 {0}.get_defaultSucceededCallback = function() {{ return {0}._staticInstance.get_succeeded(); }}
259 {0}.set_defaultFailedCallback = function(value) {{ {0}._staticInstance.set_failed(value); }}
260 {0}.get_defaultFailedCallback = function() {{ return {0}._staticInstance.get_failed(); }}
261 {0}.set_path(""{1}"");",
264 {0}.registerClass('{0}',Sys.Net.WebServiceProxy);
265 {0}._staticInstance = new {0}();
266 {0}.set_path = function(value) {{ {0}._staticInstance._path = value; }}
267 {0}.get_path = function() {{ return {0}._staticInstance._path; }}
268 {0}.set_timeout = function(value) {{ {0}._staticInstance._timeout = value; }}
269 {0}.get_timeout = function() {{ return {0}._staticInstance._timeout; }}
270 {0}.set_defaultUserContext = function(value) {{ {0}._staticInstance._userContext = value; }}
271 {0}.get_defaultUserContext = function() {{ return {0}._staticInstance._userContext; }}
272 {0}.set_defaultSucceededCallback = function(value) {{ {0}._staticInstance._succeeded = value; }}
273 {0}.get_defaultSucceededCallback = function() {{ return {0}._staticInstance._succeeded; }}
274 {0}.set_defaultFailedCallback = function(value) {{ {0}._staticInstance._failed = value; }}
275 {0}.get_defaultFailedCallback = function() {{ return {0}._staticInstance._failed; }}
276 {0}.set_path(""{1}"");",
280 for (int i = 0; i < logicalMethods.Count; i++)
281 logicalMethods [i].GenerateMethod (proxy, false, isPage);
285 foreach (GenerateScriptTypeAttribute gsta in GetGenerateScriptTypeAttributes ()) {
286 if (!gtc && !gsta.Type.IsEnum) {
289 var gtc = Sys.Net.WebServiceProxy._generateTypedConstructor;");
292 GenerateScript (proxy, gsta);
296 _proxy = proxy.ToString ();
299 IEnumerable<MemberInfo> GetGenerateScriptTypes () {
300 foreach (LogicalMethodInfo lmi in _methodMap.Values)
301 yield return lmi.MethodInfo;
306 IEnumerable<GenerateScriptTypeAttribute> GetGenerateScriptTypeAttributes () {
307 Hashtable generatedTypes = new Hashtable ();
309 foreach (MemberInfo mi in GetGenerateScriptTypes ()) {
310 GenerateScriptTypeAttribute [] gstas = (GenerateScriptTypeAttribute []) mi.GetCustomAttributes (typeof (GenerateScriptTypeAttribute), true);
311 if (gstas == null || gstas.Length == 0)
314 for (int i = 0; i < gstas.Length; i++) {
315 if (!generatedTypes.Contains (gstas [i].Type)) {
316 if (ShouldGenerateScript (gstas [i].Type, true)) {
317 generatedTypes [gstas [i].Type] = gstas [i].Type;
318 yield return gstas [i];
324 foreach (LogicalMethodInfo lmi in _methodMap.Values) {
325 foreach (Type param in lmi.GetParameterTypes ()) {
326 if (!generatedTypes.Contains (param)) {
327 if (ShouldGenerateScript (param, false)) {
328 generatedTypes [param] = param;
329 yield return new GenerateScriptTypeAttribute (param);
336 static readonly Type typeOfIEnumerable = typeof (IEnumerable);
337 static readonly Type typeOfIDictionary = typeof (IDictionary);
339 static bool ShouldGenerateScript (Type type, bool throwIfNot) {
343 if (Type.GetTypeCode (type) != TypeCode.Object)
346 // LAMESPEC: MS never create proxies for GenericTypes
347 //&& type.GetGenericTypeDefinition ().GetGenericArguments ().Length > 1
348 if (type.IsGenericType)
351 if (typeOfIEnumerable.IsAssignableFrom (type) ||
352 typeOfIDictionary.IsAssignableFrom (type) ||
353 type.IsAbstract || type.IsInterface) {
355 ThrowOnIncorrectGenerateScriptAttribute ();
359 ConstructorInfo ci = type.GetConstructor (Type.EmptyTypes);
362 ThrowOnIncorrectGenerateScriptAttribute ();
369 static void ThrowOnIncorrectGenerateScriptAttribute () {
370 throw new InvalidOperationException (
371 "Using the GenerateScriptTypes attribute is not supported for types in the following categories: primitive types; DateTime; generic types taking more than one parameter; types implementing IEnumerable or IDictionary; interfaces; Abstract classes; classes without a public default constructor.");
374 static void GenerateScript (StringBuilder proxy, GenerateScriptTypeAttribute gsta) {
375 string className = gsta.Type.FullName.Replace ('+', '_');
378 if (typeof({0}) === 'undefined') {{", className);
379 if (gsta.Type.IsEnum) {
382 {0} = function() {{ throw Error.invalidOperation(); }}
384 {0}.registerEnum('{0}', {2});",
386 JSSerializer.Serialize(new EnumPrototypeSerializer(gsta.Type)),
387 Attribute.GetCustomAttribute (gsta.Type, typeof (FlagsAttribute)) != null ? "true" : "false");
391 string typeId = String.IsNullOrEmpty (gsta.ScriptTypeId) ? gsta.Type.FullName : gsta.ScriptTypeId;
395 {0}.registerClass('{0}');",
401 sealed class EnumPrototypeSerializer : JavaScriptSerializer.LazyDictionary
404 public EnumPrototypeSerializer (Type type) {
407 protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator () {
408 String [] names = Enum.GetNames (_type);
409 Array values = Enum.GetValues (_type);
410 for (int i = 0; i < names.Length; i++)
411 yield return new KeyValuePair<string, object> (names [i], values.GetValue (i));
415 static MethodInfo FindInInterface (Type ifaceType, MethodInfo method) {
416 int nameStartIndex = 0;
417 if (method.IsPrivate) {
418 nameStartIndex = method.Name.LastIndexOf ('.');
419 if (nameStartIndex < 0)
422 if (String.CompareOrdinal (
423 ifaceType.FullName.Replace ('+', '.'), 0, method.Name, 0, nameStartIndex) != 0)
429 foreach (MethodInfo mi in ifaceType.GetMembers ()) {
430 if (method.ReturnType == mi.ReturnType &&
431 String.CompareOrdinal (method.Name, nameStartIndex, mi.Name, 0, mi.Name.Length) == 0) {
432 ParameterInfo [] rpi = method.GetParameters ();
433 ParameterInfo [] lpi = mi.GetParameters ();
434 if (rpi.Length == lpi.Length) {
436 for (int i = 0; i < rpi.Length; i++) {
437 if (rpi [i].ParameterType != lpi [i].ParameterType) {
452 public string Proxy { get { return _proxy; } }
454 public LogicalMethodInfo this [string method] {
455 get { return (LogicalMethodInfo) _methodMap [method]; }
458 static internal LogicalTypeInfo GetLogicalTypeInfo (Type t, string filePath) {
459 Hashtable type_to_manager = _type_to_logical_type;
460 LogicalTypeInfo tm = (LogicalTypeInfo) type_to_manager [t];
465 tm = new LogicalTypeInfo (t, filePath);
466 type_to_manager [t] = tm;