2f6c2fb0ed79b9cdd11da56f630f76abc7b75591.svn-base 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. using System;
  2. using System.ServiceModel;
  3. using System.ServiceModel.Channels;
  4. using System.ServiceModel.Description;
  5. using System.ServiceModel.Dispatcher;
  6. using System.Text;
  7. using System.Xml;
  8. using System.Runtime.Serialization;
  9. namespace iBemsDataService
  10. {
  11. class JSONPSupportInspector : IDispatchMessageInspector
  12. {
  13. // Assume utf-8, note that Data Services supports
  14. // charset negotation, so this needs to be more
  15. // sophisticated (and per-request) if clients will
  16. // use multiple charsets
  17. private static readonly Encoding encoding = Encoding.UTF8;
  18. #region IDispatchMessageInspector Members
  19. public object AfterReceiveRequest( ref Message request , IClientChannel channel , InstanceContext instanceContext )
  20. {
  21. if( request.Properties.ContainsKey( "UriTemplateMatchResults" ) )
  22. {
  23. HttpRequestMessageProperty httpmsg = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
  24. UriTemplateMatch match = (UriTemplateMatch)request.Properties["UriTemplateMatchResults"];
  25. string format = match.QueryParameters["$format"];
  26. if( "json".Equals( format , StringComparison.InvariantCultureIgnoreCase ) )
  27. {
  28. // strip out $format from the query options to avoid an error
  29. // due to use of a reserved option (starts with "$")
  30. match.QueryParameters.Remove( "$format" );
  31. // replace the Accept header so that the Data Services runtime
  32. // assumes the client asked for a JSON representation
  33. httpmsg.Headers["Accept"] = "application/json;odata=verbose, text/plain;q=0.5";
  34. httpmsg.Headers["Accept-Charset"] = "utf-8";
  35. string callback = match.QueryParameters["$callback"];
  36. if( !string.IsNullOrEmpty( callback ) )
  37. {
  38. match.QueryParameters.Remove( "$callback" );
  39. return callback;
  40. }
  41. }
  42. }
  43. return null;
  44. }
  45. public void BeforeSendReply( ref Message reply , object correlationState )
  46. {
  47. if( correlationState != null && correlationState is string )
  48. {
  49. // if we have a JSONP callback then buffer the response, wrap it with the
  50. // callback call and then re-create the response message
  51. string callback = (string)correlationState;
  52. bool bodyIsText = false;
  53. HttpResponseMessageProperty response = reply.Properties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
  54. if( response != null )
  55. {
  56. string contentType = response.Headers["Content-Type"];
  57. if( contentType != null )
  58. {
  59. // Check the response type and change it to text/javascript if we know how.
  60. if( contentType.StartsWith( "text/plain" , StringComparison.InvariantCultureIgnoreCase ) )
  61. {
  62. bodyIsText = true;
  63. response.Headers["Content-Type"] = "text/javascript;charset=utf-8";
  64. }
  65. else if( contentType.StartsWith( "application/json" , StringComparison.InvariantCultureIgnoreCase ) )
  66. {
  67. response.Headers["Content-Type"] = contentType.Replace( "application/json" , "text/javascript" );
  68. }
  69. }
  70. }
  71. XmlDictionaryReader reader = reply.GetReaderAtBodyContents();
  72. reader.ReadStartElement();
  73. string content = JSONPSupportInspector.encoding.GetString( reader.ReadContentAsBase64() );
  74. if( bodyIsText )
  75. {
  76. // Escape the body as a string literal.
  77. content = "\"" + QuoteJScriptString( content ) + "\"";
  78. }
  79. content = callback + "(" + content + ")";
  80. Message newreply = Message.CreateMessage( MessageVersion.None , "" , new Writer( content ) );
  81. newreply.Properties.CopyProperties( reply.Properties );
  82. reply = newreply;
  83. }
  84. }
  85. private static string QuoteJScriptString( string s )
  86. {
  87. if( string.IsNullOrEmpty( s ) )
  88. {
  89. return string.Empty;
  90. }
  91. StringBuilder builder = null;
  92. int startIndex = 0;
  93. int count = 0;
  94. for( int i = 0 ; i < s.Length ; i++ )
  95. {
  96. char ch = s[i];
  97. if( ((((ch == '\r') || (ch == '\t')) || ((ch == '"') || (ch == '\\'))) || (((ch == '\n') || (ch < ' ')) || ((ch > '\x007f') || (ch == '\b')))) || (ch == '\f') )
  98. {
  99. if( builder == null )
  100. {
  101. builder = new StringBuilder( s.Length + 6 );
  102. }
  103. if( count > 0 )
  104. {
  105. builder.Append( s , startIndex , count );
  106. }
  107. startIndex = i + 1;
  108. count = 0;
  109. }
  110. switch( ch )
  111. {
  112. case '\b':
  113. builder.Append( @"\b" );
  114. break;
  115. case '\t':
  116. builder.Append( @"\t" );
  117. break;
  118. case '\n':
  119. builder.Append( @"\n" );
  120. break;
  121. case '\f':
  122. builder.Append( @"\f" );
  123. break;
  124. case '\r':
  125. builder.Append( @"\r" );
  126. break;
  127. case '"':
  128. builder.Append( "\\\"" );
  129. break;
  130. case '\\':
  131. builder.Append( @"\\" );
  132. break;
  133. default:
  134. if( (ch < ' ') || (ch > '\x007f') )
  135. {
  136. builder.AppendFormat( System.Globalization.CultureInfo.InvariantCulture , @"\u{0:x4}" , (int)ch );
  137. }
  138. else
  139. {
  140. count++;
  141. }
  142. break;
  143. }
  144. }
  145. string result;
  146. if( builder == null )
  147. {
  148. result = s;
  149. }
  150. else
  151. {
  152. if( count > 0 )
  153. {
  154. builder.Append( s , startIndex , count );
  155. }
  156. result = builder.ToString();
  157. }
  158. return result;
  159. }
  160. #endregion
  161. class Writer : BodyWriter
  162. {
  163. private string content;
  164. public Writer( string content )
  165. : base( false )
  166. {
  167. this.content = content;
  168. }
  169. protected override void OnWriteBodyContents( XmlDictionaryWriter writer )
  170. {
  171. writer.WriteStartElement( "Binary" );
  172. byte[] buffer = JSONPSupportInspector.encoding.GetBytes( this.content );
  173. writer.WriteBase64( buffer , 0 , buffer.Length );
  174. writer.WriteEndElement();
  175. }
  176. }
  177. }
  178. // Simply apply this attribute to a BemsDataService-derived class to get
  179. // JSONP support in that service
  180. [AttributeUsage( AttributeTargets.Class )]
  181. public class JSONPSupportBehaviorAttribute : Attribute , IServiceBehavior
  182. {
  183. #region IServiceBehavior Members
  184. void IServiceBehavior.AddBindingParameters( ServiceDescription serviceDescription , ServiceHostBase serviceHostBase , System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints , BindingParameterCollection bindingParameters )
  185. {
  186. }
  187. void IServiceBehavior.ApplyDispatchBehavior( ServiceDescription serviceDescription , ServiceHostBase serviceHostBase )
  188. {
  189. foreach( ChannelDispatcher cd in serviceHostBase.ChannelDispatchers )
  190. {
  191. foreach( EndpointDispatcher ed in cd.Endpoints )
  192. {
  193. ed.DispatchRuntime.MessageInspectors.Add( new JSONPSupportInspector() );
  194. }
  195. }
  196. }
  197. void IServiceBehavior.Validate( ServiceDescription serviceDescription , ServiceHostBase serviceHostBase )
  198. {
  199. }
  200. #endregion
  201. }
  202. }