5 // Konstantin Triger <kostat@mainsoft.com>
6 // Atsushi Enomoto <atsushi@ximian.com>
8 // (C) 2007 Mainsoft, Inc. http://www.mainsoft.com
9 // Copyright (C) 2009 Novell, Inc. http://novell.com
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections.Generic;
34 using System.Collections.Specialized;
36 using System.Web.Services;
37 using System.Reflection;
38 using System.Collections;
39 using System.Web.Script.Serialization;
41 using System.Xml.Serialization;
44 using System.ServiceModel;
45 using System.ServiceModel.Description;
48 namespace System.Web.Script.Services
50 internal sealed class JsonResult
52 public readonly object d;
53 public JsonResult (object result) {
58 internal abstract class LogicalTypeInfo
60 public static LogicalTypeInfo CreateTypeInfo (Type t, string filePath)
63 if (t.GetCustomAttributes (typeof (ServiceContractAttribute), false).Length > 0)
64 return new WcfLogicalTypeInfo (t, filePath);
67 return new AsmxLogicalTypeInfo (t, filePath);
70 internal abstract class LogicalMethodInfo
72 readonly MethodInfo _methodInfo;
73 internal readonly ParameterInfo [] _params;
74 internal readonly Dictionary<string, int> _paramMap;
75 LogicalTypeInfo _typeInfo;
77 protected LogicalMethodInfo (LogicalTypeInfo typeInfo, MethodInfo method)
80 _params = MethodInfo.GetParameters ();
84 _paramMap = new Dictionary<string, int> (_params.Length, StringComparer.Ordinal);
85 for (int i = 0; i < _params.Length; i++)
86 _paramMap.Add(_params[i].Name, i);
91 public abstract bool UseHttpGet { get; }
92 public abstract bool EnableSession { get; }
93 public abstract ResponseFormat ResponseFormat { get; }
94 public abstract string MethodName { get; }
95 public MethodInfo MethodInfo { get { return _methodInfo; } }
96 public bool HasParameters { get { return _params != null && _params.Length > 0; } }
97 public IEnumerable<Type> GetParameterTypes () {
99 for (int i = 0; i < _params.Length; i++)
100 yield return _params [i].ParameterType;
102 yield return MethodInfo.ReturnType;
105 public void GenerateMethod (StringBuilder proxy, bool isPrototype, bool isPage) {
107 string service;// = isPage ? "PageMethods" : MethodInfo.DeclaringType.FullName;
109 _typeInfo.GetNamespaceAndServiceName (MethodInfo.DeclaringType, isPage, out ns, out service);
110 string useHttpGet = UseHttpGet ? "true" : "false";
111 string paramMap = GenerateParameters (true);
112 string paramList = GenerateParameters (false);
117 {1}:function({4}succeededCallback, failedCallback, userContext) {{
118 return this._invoke({0}.get_path(), '{1}',{2},{{{3}}},succeededCallback,failedCallback,userContext); }}",
119 service, MethodName, useHttpGet, paramMap, paramList);
124 {0}.{1}= function({2}onSuccess,onFailed,userContext) {{{0}._staticInstance.{1}({2}onSuccess,onFailed,userContext); }}",
125 service, MethodName, paramList);
128 string GenerateParameters (bool isMap) {
132 StringBuilder builder = new StringBuilder ();
134 for (int i = 0; i < _params.Length; i++) {
135 builder.AppendFormat (isMap ? "{0}:{0}" : "{0}", _params [i].Name);
136 builder.Append (',');
142 return builder.ToString ();
145 public abstract void Invoke (HttpRequest request, HttpResponse response);
149 static Hashtable _type_to_logical_type = Hashtable.Synchronized (new Hashtable ());
151 const string type_to_logical_type_key = "System.Web.Script.Services.LogicalTypeInfo";
152 static Hashtable _type_to_logical_type {
154 Hashtable hash = (Hashtable) AppDomain.CurrentDomain.GetData (type_to_logical_type_key);
159 AppDomain.CurrentDomain.SetData (type_to_logical_type_key, Hashtable.Synchronized (new Hashtable ()));
162 return (Hashtable) AppDomain.CurrentDomain.GetData (type_to_logical_type_key);
167 static internal LogicalTypeInfo GetLogicalTypeInfo (Type t, string filePath) {
168 Hashtable type_to_manager = _type_to_logical_type;
169 LogicalTypeInfo tm = (LogicalTypeInfo) type_to_manager [t];
174 tm = CreateTypeInfo (t, filePath);
175 type_to_manager [t] = tm;
180 protected static string EnsureNamespaceRegistered (string ns, string name, StringBuilder proxy, List<string> registeredNamespaces) {
181 if (String.IsNullOrEmpty (ns))
182 return "var " + name;
184 if (!registeredNamespaces.Contains (ns)) {
185 registeredNamespaces.Add (ns);
188 Type.registerNamespace('{0}');",
194 protected virtual void GetNamespaceAndServiceName (Type type, bool isPage, out string ns, out string service)
196 ns = isPage ? String.Empty : type.Namespace;
197 service = isPage ? "PageMethods" : type.FullName;
202 internal readonly Type _type;
203 readonly string _proxy;
204 internal readonly Hashtable _methodMap;
206 protected LogicalTypeInfo (Type t, string filePath)
209 bool isPage = _type.IsSubclassOf (typeof (System.Web.UI.Page));
211 var logicalMethods = GetLogicalMethods (isPage);
212 //_logicalMethods = (LogicalMethodInfo []) list.ToArray (typeof (LogicalMethodInfo));
214 _methodMap = new Hashtable (logicalMethods.Count);
215 for (int i = 0; i < logicalMethods.Count; i++)
216 _methodMap.Add (logicalMethods [i].MethodName, logicalMethods [i]);
220 GetNamespaceAndServiceName (t, isPage, out ns, out service);
222 StringBuilder proxy = new StringBuilder ();
223 List<string> registeredNamespaces = new List<string> ();
224 string scriptTypeDeclaration = EnsureNamespaceRegistered (ns, service, proxy, registeredNamespaces);
227 " + scriptTypeDeclaration + @"=function() {{
228 {0}.initializeBase(this);
230 this._userContext = null;
231 this._succeeded = null;
237 for (int i = 0; i < logicalMethods.Count; i++) {
240 logicalMethods [i].GenerateMethod (proxy, true, isPage);
245 {0}.registerClass('{0}',Sys.Net.WebServiceProxy);
246 {0}._staticInstance = new {0}();
247 {0}.set_path = function(value) {{ {0}._staticInstance.set_path(value); }}
248 {0}.get_path = function() {{ return {0}._staticInstance.get_path(); }}
249 {0}.set_timeout = function(value) {{ {0}._staticInstance.set_timeout(value); }}
250 {0}.get_timeout = function() {{ return {0}._staticInstance.get_timeout(); }}
251 {0}.set_defaultUserContext = function(value) {{ {0}._staticInstance.set_defaultUserContext(value); }}
252 {0}.get_defaultUserContext = function() {{ return {0}._staticInstance.get_defaultUserContext(); }}
253 {0}.set_defaultSucceededCallback = function(value) {{ {0}._staticInstance.set_defaultSucceededCallback(value); }}
254 {0}.get_defaultSucceededCallback = function() {{ return {0}._staticInstance.get_defaultSucceededCallback(); }}
255 {0}.set_defaultFailedCallback = function(value) {{ {0}._staticInstance.set_defaultFailedCallback(value); }}
256 {0}.get_defaultFailedCallback = function() {{ return {0}._staticInstance.get_defaultFailedCallback(); }}
257 {0}.set_path(""{1}"");",
260 for (int i = 0; i < logicalMethods.Count; i++)
261 logicalMethods [i].GenerateMethod (proxy, false, isPage);
263 GenerateTypeRegistrationScript (proxy, registeredNamespaces);
266 _proxy = proxy.ToString ();
269 protected IEnumerable<MemberInfo> GetGenerateScriptTypes () {
270 foreach (LogicalMethodInfo lmi in _methodMap.Values)
271 yield return lmi.MethodInfo;
276 protected static void GenerateTypeRegistrationScript (StringBuilder proxy, Type scriptType, string scriptTypeId, List<string> registeredNamespaces) {
277 string className = scriptType.FullName.Replace ('+', '_');
278 string ns = scriptType.Namespace;
279 string scriptTypeDeclaration = EnsureNamespaceRegistered (ns, className, proxy, registeredNamespaces);
282 if (typeof({0}) === 'undefined') {{", className);
283 if (scriptType.IsEnum) {
286 {0} = function() {{ throw Error.invalidOperation(); }}
288 {0}.registerEnum('{0}', {2});",
290 // This method is also used for WCF, but for enum this should work ...
291 AsmxLogicalTypeInfo.JSSerializer.Serialize(GetEnumPrototypeDictionary (scriptType)),
292 Attribute.GetCustomAttribute (scriptType, typeof (FlagsAttribute)) != null ? "true" : "false");
296 string typeId = String.IsNullOrEmpty (scriptTypeId) ? scriptType.FullName : scriptTypeId;
299 " + scriptTypeDeclaration + @"=gtc(""{1}"");
300 {0}.registerClass('{0}');",
306 static IDictionary <string, object> GetEnumPrototypeDictionary (Type type)
308 var ret = new Dictionary <string, object> ();
309 string [] names = Enum.GetNames (type);
310 Array values = Enum.GetValues (type);
311 for (int i = 0; i < names.Length; i++)
312 ret.Add (names [i], values.GetValue (i));
317 static readonly Type typeOfIEnumerable = typeof (IEnumerable);
318 static readonly Type typeOfIDictionary = typeof (IDictionary);
320 protected static bool ShouldGenerateScript (Type type, bool throwIfNot) {
324 if (Type.GetTypeCode (type) != TypeCode.Object)
327 if (type == typeof (void))
330 if (typeOfIEnumerable.IsAssignableFrom (type) ||
331 typeOfIDictionary.IsAssignableFrom (type) ||
332 type.IsAbstract || type.IsInterface) {
334 ThrowOnIncorrectGenerateScriptAttribute ();
338 // LAMESPEC: MS never create proxies for GenericTypes
339 //&& type.GetGenericTypeDefinition ().GetGenericArguments ().Length > 1
340 if (type.IsGenericType)
343 ConstructorInfo ci = type.GetConstructor (Type.EmptyTypes);
344 if (ci == null || !ci.IsPublic) {
346 ThrowOnIncorrectGenerateScriptAttribute ();
353 static void ThrowOnIncorrectGenerateScriptAttribute () {
354 throw new InvalidOperationException (
355 "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.");
358 protected abstract void GenerateTypeRegistrationScript (StringBuilder proxy, List<string> registeredNamespaces);
360 protected abstract List<LogicalMethodInfo> GetLogicalMethods (bool isPage);
362 public string Proxy { get { return _proxy; } }
364 public LogicalMethodInfo this [string method] {
365 get { return (LogicalMethodInfo) _methodMap [method]; }
369 internal sealed class AsmxLogicalTypeInfo : LogicalTypeInfo
371 #region LogicalMethodInfo
373 public sealed class AsmxLogicalMethodInfo : LogicalTypeInfo.LogicalMethodInfo
375 readonly LogicalTypeInfo _typeInfo;
377 readonly WebMethodAttribute _wma;
379 readonly ScriptMethodAttribute _sma;
381 readonly XmlSerializer _xmlSer;
383 public AsmxLogicalMethodInfo (LogicalTypeInfo typeInfo, MethodInfo method)
384 : base (typeInfo, method)
386 _typeInfo = typeInfo;
388 _wma = (WebMethodAttribute) Attribute.GetCustomAttribute (method, typeof (WebMethodAttribute));
390 _sma = (ScriptMethodAttribute) Attribute.GetCustomAttribute (method, typeof (ScriptMethodAttribute));
392 _sma = ScriptMethodAttribute.Default;
394 if (ScriptMethod.ResponseFormat == ResponseFormat.Xml
395 && MethodInfo.ReturnType != typeof (void)) {
396 Type retType = MethodInfo.ReturnType;
397 if (Type.GetTypeCode (retType) != TypeCode.String || ScriptMethod.XmlSerializeString)
398 _xmlSer = new XmlSerializer (retType);
402 IDictionary<string,object> BuildInvokeParameters (HttpRequest request)
404 return "GET".Equals (request.RequestType, StringComparison.OrdinalIgnoreCase) ?
405 GetNameValueCollectionDictionary (request.QueryString) :
406 (IDictionary<string, object>) JavaScriptSerializer.DefaultSerializer.DeserializeObjectInternal (new StreamReader (request.InputStream, request.ContentEncoding));
409 IDictionary <string, object> GetNameValueCollectionDictionary (NameValueCollection nvc)
411 var ret = new Dictionary <string, object> ();
413 for (int i = nvc.Count - 1; i >= 0; i--)
414 ret.Add (nvc.GetKey (i), nvc.Get (i));
419 public override void Invoke (HttpRequest request, HttpResponse response) {
420 var writer = response.Output;
421 IDictionary<string, object> @params = BuildInvokeParameters (request);
428 pp = new object [_params.Length];
430 foreach (KeyValuePair<string, object> pair in @params) {
431 if (!_paramMap.TryGetValue (pair.Key, out i))
435 ptype = _params [i].ParameterType;
436 if (ptype == typeof (System.Object))
439 pp [i] = AsmxLogicalTypeInfo.JSSerializer.ConvertToType (value, ptype);
443 object target = MethodInfo.IsStatic ? null : Activator.CreateInstance (_typeInfo._type);
444 object result = MethodInfo.Invoke (target, pp);
445 if (_xmlSer != null) {
446 XmlTextWriter xwriter = new XmlTextWriter (writer);
447 xwriter.Formatting = Formatting.None;
448 _xmlSer.Serialize (xwriter, result);
452 result = new JsonResult (result);
453 AsmxLogicalTypeInfo.JSSerializer.Serialize (result, writer);
457 public override string MethodName { get { return String.IsNullOrEmpty (WebMethod.MessageName) ? MethodInfo.Name : WebMethod.MessageName; } }
459 public ScriptMethodAttribute ScriptMethod { get { return _sma; } }
460 public WebMethodAttribute WebMethod { get { return _wma; } }
461 public override bool UseHttpGet { get { return ScriptMethod.UseHttpGet; } }
462 public override bool EnableSession { get { return WebMethod.EnableSession; } }
463 public override ResponseFormat ResponseFormat { get { return ScriptMethod.ResponseFormat; } }
468 //readonly LogicalMethodInfo [] _logicalMethods;
469 internal static readonly JavaScriptSerializer JSSerializer = new JavaScriptSerializer (null, true);
471 protected override List<LogicalMethodInfo> GetLogicalMethods (bool isPage)
473 BindingFlags bindingAttr = isPage ? (BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.Public) : (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
474 MethodInfo [] all_type_methods = _type.GetMethods (bindingAttr);
475 List<LogicalMethodInfo> logicalMethods = new List<LogicalMethodInfo> (all_type_methods.Length);
476 foreach (MethodInfo mi in all_type_methods) {
478 mi.GetCustomAttributes (typeof (WebMethodAttribute), false).Length > 0)
479 logicalMethods.Add (new AsmxLogicalMethodInfo (this, mi));
481 foreach (Type ifaceType in _type.GetInterfaces ()) {
482 if (ifaceType.GetCustomAttributes (typeof (WebServiceBindingAttribute), false).Length > 0) {
483 MethodInfo found = FindInInterface (ifaceType, mi);
485 if (found.GetCustomAttributes (typeof (WebMethodAttribute), false).Length > 0)
486 logicalMethods.Add (new AsmxLogicalMethodInfo (this, found));
494 return logicalMethods;
497 internal AsmxLogicalTypeInfo (Type t, string filePath)
502 IEnumerable<GenerateScriptTypeAttribute> GetGenerateScriptTypeAttributes () {
503 Hashtable generatedTypes = new Hashtable ();
505 foreach (MemberInfo mi in GetGenerateScriptTypes ()) {
506 GenerateScriptTypeAttribute [] gstas = (GenerateScriptTypeAttribute []) mi.GetCustomAttributes (typeof (GenerateScriptTypeAttribute), true);
507 if (gstas == null || gstas.Length == 0)
510 for (int i = 0; i < gstas.Length; i++) {
511 if (!generatedTypes.Contains (gstas [i].Type)) {
512 if (ShouldGenerateScript (gstas [i].Type, true)) {
513 generatedTypes [gstas [i].Type] = gstas [i].Type;
514 yield return gstas [i];
520 foreach (LogicalMethodInfo lmi in _methodMap.Values) {
521 foreach (Type t in lmi.GetParameterTypes ()) {
522 Type param = GetTypeToGenerate (t);
523 if (!generatedTypes.Contains (param)) {
524 if (ShouldGenerateScript (param, false)) {
525 generatedTypes [param] = param;
526 yield return new GenerateScriptTypeAttribute (param);
533 static Type GetTypeToGenerate (Type type) {
535 return type.GetElementType ();
536 if (type.IsGenericType) {
537 while (type.IsGenericType && type.GetGenericArguments ().Length == 1)
538 type = type.GetGenericArguments () [0];
544 static MethodInfo FindInInterface (Type ifaceType, MethodInfo method) {
545 int nameStartIndex = 0;
546 if (method.IsPrivate) {
547 nameStartIndex = method.Name.LastIndexOf ('.');
548 if (nameStartIndex < 0)
551 if (String.CompareOrdinal (
552 ifaceType.FullName.Replace ('+', '.'), 0, method.Name, 0, nameStartIndex) != 0)
558 foreach (MethodInfo mi in ifaceType.GetMembers ()) {
559 if (method.ReturnType == mi.ReturnType &&
560 String.CompareOrdinal (method.Name, nameStartIndex, mi.Name, 0, mi.Name.Length) == 0) {
561 ParameterInfo [] rpi = method.GetParameters ();
562 ParameterInfo [] lpi = mi.GetParameters ();
563 if (rpi.Length == lpi.Length) {
565 for (int i = 0; i < rpi.Length; i++) {
566 if (rpi [i].ParameterType != lpi [i].ParameterType) {
581 protected override void GenerateTypeRegistrationScript (StringBuilder proxy, List<string> registeredNamespaces)
585 foreach (GenerateScriptTypeAttribute gsta in GetGenerateScriptTypeAttributes ()) {
586 if (!gtc && !gsta.Type.IsEnum) {
589 var gtc = Sys.Net.WebServiceProxy._generateTypedConstructor;");
592 GenerateTypeRegistrationScript (proxy, gsta.Type, gsta.ScriptTypeId, registeredNamespaces);
598 internal class WcfLogicalTypeInfo : LogicalTypeInfo
600 ContractDescription cd;
602 public WcfLogicalTypeInfo (Type type, string filePath)
603 : base (type, filePath)
607 ContractDescription Contract {
610 cd = ContractDescription.GetContract (_type);
615 IEnumerable<KeyValuePair<Type,string>> GetDataContractTypeInfos ()
617 foreach (var od in Contract.Operations) {
618 foreach (var md in od.Messages) {
619 foreach (var pd in md.Body.Parts) {
620 if (ShouldGenerateScript (pd.Type, false))
621 yield return new KeyValuePair<Type,string> (pd.Type, null);
623 if (md.Body.ReturnValue != null && ShouldGenerateScript (md.Body.ReturnValue.Type, false))
624 yield return new KeyValuePair<Type,string> (md.Body.ReturnValue.Type, null);
630 protected override void GetNamespaceAndServiceName (Type type, bool isPage, out string ns, out string service)
632 string name = type.Namespace;
633 int dot = name.LastIndexOf ('.');
635 name = name.Substring (dot + 1);
637 service = name + "." + type.Name;
640 protected override void GenerateTypeRegistrationScript (StringBuilder proxy, List<string> registeredNamespaces)
644 foreach (KeyValuePair<Type,string> pair in GetDataContractTypeInfos ()) {
645 if (!gtc && !pair.Key.IsEnum) {
648 var gtc = Sys.Net.WebServiceProxy._generateTypedConstructor;");
651 GenerateTypeRegistrationScript (proxy, pair.Key, pair.Value, registeredNamespaces);
655 protected override List<LogicalMethodInfo> GetLogicalMethods (bool isPage)
658 throw new NotSupportedException ();
660 var l = new List<LogicalMethodInfo> ();
661 foreach (var od in Contract.Operations)
662 l.Add (new WcfLogicalMethodInfo (this, od));
666 internal class WcfLogicalMethodInfo : LogicalMethodInfo
668 OperationDescription od;
670 public WcfLogicalMethodInfo (LogicalTypeInfo typeInfo, OperationDescription od)
671 : base (typeInfo, od.SyncMethod)
676 public override bool UseHttpGet { get { return true; } } // always
678 // FIXME: could this be enabled?
679 public override bool EnableSession {
680 get { return false; }
683 public override ResponseFormat ResponseFormat {
684 get { return ResponseFormat.Json; } // always
687 public override string MethodName {
688 get { return od.Name; }
691 public override void Invoke (HttpRequest request, HttpResponse response)
693 // invocation is done in WCF part.
694 throw new NotSupportedException ();