PageRenderTime 110ms CodeModel.GetById 43ms RepoModel.GetById 0ms app.codeStats 1ms

/mcs/class/System.Data.Services.Client/Client/System/Data/Services/Client/DataServiceContext.cs

https://bitbucket.org/danipen/mono
C# | 4908 lines | 4024 code | 873 blank | 11 comment | 935 complexity | 4e5d044121c43696f18b2edfc18d037a MD5 | raw file
Possible License(s): Unlicense, Apache-2.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. //Copyright 2010 Microsoft Corporation
  2. //
  3. //Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
  4. //You may obtain a copy of the License at
  5. //
  6. //http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. //Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
  9. //"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. //See the License for the specific language governing permissions and limitations under the License.
  11. namespace System.Data.Services.Client
  12. {
  13. #region Namespaces.
  14. using System;
  15. using System.Collections;
  16. using System.Collections.Generic;
  17. using System.Collections.ObjectModel;
  18. using System.Diagnostics;
  19. using System.Globalization;
  20. using System.IO;
  21. using System.Linq;
  22. using System.Linq.Expressions;
  23. using System.Data.Services.Common;
  24. #if !ASTORIA_LIGHT
  25. using System.Net;
  26. #else
  27. using System.Data.Services.Http;
  28. #endif
  29. using System.Text;
  30. using System.Xml;
  31. using System.Xml.Linq;
  32. #endregion Namespaces.
  33. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506", Justification = "Central class of the API, likely to have many cross-references")]
  34. public class DataServiceContext
  35. {
  36. #if !TESTUNIXNEWLINE
  37. private static readonly string NewLine = System.Environment.NewLine;
  38. #else
  39. private const string NewLine = "\n";
  40. #endif
  41. private readonly System.Uri baseUri;
  42. private readonly System.Uri baseUriWithSlash;
  43. #if !ASTORIA_LIGHT
  44. private System.Net.ICredentials credentials;
  45. #endif
  46. private string dataNamespace;
  47. private Func<Type, string> resolveName;
  48. private Func<string, Type> resolveType;
  49. #if !ASTORIA_LIGHT
  50. private int timeout;
  51. #endif
  52. private bool postTunneling;
  53. private bool ignoreMissingProperties;
  54. private MergeOption mergeOption;
  55. private SaveChangesOptions saveChangesDefaultOptions;
  56. private Uri typeScheme;
  57. private bool ignoreResourceNotFoundException;
  58. #if ASTORIA_LIGHT
  59. private HttpStack httpStack;
  60. #endif
  61. #region Resource state management
  62. private uint nextChange;
  63. private Dictionary<object, EntityDescriptor> entityDescriptors = new Dictionary<object, EntityDescriptor>(EqualityComparer<object>.Default);
  64. private Dictionary<String, EntityDescriptor> identityToDescriptor;
  65. private Dictionary<LinkDescriptor, LinkDescriptor> bindings = new Dictionary<LinkDescriptor, LinkDescriptor>(LinkDescriptor.EquivalenceComparer);
  66. private bool applyingChanges;
  67. #endregion
  68. #region ctor
  69. public DataServiceContext(Uri serviceRoot)
  70. {
  71. Util.CheckArgumentNull(serviceRoot, "serviceRoot");
  72. #if ASTORIA_LIGHT
  73. if (!serviceRoot.IsAbsoluteUri)
  74. {
  75. if (XHRHttpWebRequest.IsAvailable())
  76. {
  77. serviceRoot = new Uri(System.Windows.Browser.HtmlPage.Document.DocumentUri, serviceRoot);
  78. }
  79. else
  80. {
  81. System.Net.WebClient webClient = new System.Net.WebClient();
  82. serviceRoot = new Uri(new Uri(webClient.BaseAddress), serviceRoot);
  83. }
  84. }
  85. #endif
  86. if (!serviceRoot.IsAbsoluteUri ||
  87. !Uri.IsWellFormedUriString(serviceRoot.OriginalString, UriKind.Absolute) ||
  88. !String.IsNullOrEmpty(serviceRoot.Query) ||
  89. !string.IsNullOrEmpty(serviceRoot.Fragment) ||
  90. ((serviceRoot.Scheme != "http") && (serviceRoot.Scheme != "https")))
  91. {
  92. throw Error.Argument(Strings.Context_BaseUri, "serviceRoot");
  93. }
  94. this.baseUri = serviceRoot;
  95. this.baseUriWithSlash = serviceRoot;
  96. if (!serviceRoot.OriginalString.EndsWith("/", StringComparison.Ordinal))
  97. {
  98. this.baseUriWithSlash = Util.CreateUri(serviceRoot.OriginalString + "/", UriKind.Absolute);
  99. }
  100. this.mergeOption = MergeOption.AppendOnly;
  101. this.DataNamespace = XmlConstants.DataWebNamespace;
  102. #if ASTORIA_LIGHT
  103. this.UsePostTunneling = true;
  104. #else
  105. this.UsePostTunneling = false;
  106. #endif
  107. this.typeScheme = new Uri(XmlConstants.DataWebSchemeNamespace);
  108. #if ASTORIA_LIGHT
  109. this.httpStack = HttpStack.Auto;
  110. #endif
  111. }
  112. #endregion
  113. public event EventHandler<SendingRequestEventArgs> SendingRequest;
  114. public event EventHandler<ReadingWritingEntityEventArgs> ReadingEntity;
  115. public event EventHandler<ReadingWritingEntityEventArgs> WritingEntity;
  116. internal event EventHandler<SaveChangesEventArgs> ChangesSaved;
  117. #region BaseUri, Credentials, MergeOption, Timeout, Links, Entities
  118. public Uri BaseUri
  119. {
  120. get { return this.baseUri; }
  121. }
  122. #if !ASTORIA_LIGHT
  123. public System.Net.ICredentials Credentials
  124. {
  125. get { return this.credentials; }
  126. set { this.credentials = value; }
  127. }
  128. #endif
  129. public MergeOption MergeOption
  130. {
  131. get { return this.mergeOption; }
  132. set { this.mergeOption = Util.CheckEnumerationValue(value, "MergeOption"); }
  133. }
  134. public bool ApplyingChanges
  135. {
  136. get { return this.applyingChanges; }
  137. internal set { this.applyingChanges = value; }
  138. }
  139. public bool IgnoreMissingProperties
  140. {
  141. get { return this.ignoreMissingProperties; }
  142. set { this.ignoreMissingProperties = value; }
  143. }
  144. public string DataNamespace
  145. {
  146. get
  147. {
  148. return this.dataNamespace;
  149. }
  150. set
  151. {
  152. Util.CheckArgumentNull(value, "value");
  153. this.dataNamespace = value;
  154. }
  155. }
  156. public Func<Type, string> ResolveName
  157. {
  158. get { return this.resolveName; }
  159. set { this.resolveName = value; }
  160. }
  161. public Func<string, Type> ResolveType
  162. {
  163. get { return this.resolveType; }
  164. set { this.resolveType = value; }
  165. }
  166. #if !ASTORIA_LIGHT
  167. public int Timeout
  168. {
  169. get
  170. {
  171. return this.timeout;
  172. }
  173. set
  174. {
  175. if (value < 0)
  176. {
  177. throw Error.ArgumentOutOfRange("Timeout");
  178. }
  179. this.timeout = value;
  180. }
  181. }
  182. #endif
  183. public Uri TypeScheme
  184. {
  185. get
  186. {
  187. return this.typeScheme;
  188. }
  189. set
  190. {
  191. Util.CheckArgumentNull(value, "value");
  192. this.typeScheme = value;
  193. }
  194. }
  195. public bool UsePostTunneling
  196. {
  197. get { return this.postTunneling; }
  198. set { this.postTunneling = value; }
  199. }
  200. public ReadOnlyCollection<LinkDescriptor> Links
  201. {
  202. get
  203. {
  204. return this.bindings.Values.OrderBy(l => l.ChangeOrder).ToList().AsReadOnly();
  205. }
  206. }
  207. public ReadOnlyCollection<EntityDescriptor> Entities
  208. {
  209. get
  210. {
  211. return this.entityDescriptors.Values.OrderBy(d => d.ChangeOrder).ToList().AsReadOnly();
  212. }
  213. }
  214. public SaveChangesOptions SaveChangesDefaultOptions
  215. {
  216. get
  217. {
  218. return this.saveChangesDefaultOptions;
  219. }
  220. set
  221. {
  222. ValidateSaveChangesOptions(value);
  223. this.saveChangesDefaultOptions = value;
  224. }
  225. }
  226. #endregion
  227. public bool IgnoreResourceNotFoundException
  228. {
  229. get { return this.ignoreResourceNotFoundException; }
  230. set { this.ignoreResourceNotFoundException = value; }
  231. }
  232. #if ASTORIA_LIGHT
  233. public HttpStack HttpStack
  234. {
  235. get { return this.httpStack; }
  236. set { this.httpStack = Util.CheckEnumerationValue(value, "HttpStack"); }
  237. }
  238. #endif
  239. internal Uri BaseUriWithSlash
  240. {
  241. get { return this.baseUriWithSlash; }
  242. }
  243. internal bool HasReadingEntityHandlers
  244. {
  245. [DebuggerStepThrough]
  246. get { return this.ReadingEntity != null; }
  247. }
  248. #region Entity and Link Tracking
  249. public EntityDescriptor GetEntityDescriptor(object entity)
  250. {
  251. Util.CheckArgumentNull(entity, "entity");
  252. EntityDescriptor descriptor;
  253. if (this.entityDescriptors.TryGetValue(entity, out descriptor))
  254. {
  255. return descriptor;
  256. }
  257. else
  258. {
  259. return null;
  260. }
  261. }
  262. public LinkDescriptor GetLinkDescriptor(object source, string sourceProperty, object target)
  263. {
  264. Util.CheckArgumentNull(source, "source");
  265. Util.CheckArgumentNotEmpty(sourceProperty, "sourceProperty");
  266. Util.CheckArgumentNull(target, "target");
  267. LinkDescriptor link;
  268. if (this.bindings.TryGetValue(new LinkDescriptor(source, sourceProperty, target), out link))
  269. {
  270. return link;
  271. }
  272. else
  273. {
  274. return null;
  275. }
  276. }
  277. #endregion
  278. #region CancelRequest
  279. public void CancelRequest(IAsyncResult asyncResult)
  280. {
  281. Util.CheckArgumentNull(asyncResult, "asyncResult");
  282. BaseAsyncResult result = asyncResult as BaseAsyncResult;
  283. if ((null == result) || (this != result.Source))
  284. {
  285. object context = null;
  286. DataServiceQuery query = null;
  287. if (null != result)
  288. {
  289. query = result.Source as DataServiceQuery;
  290. if (null != query)
  291. {
  292. DataServiceQueryProvider provider = query.Provider as DataServiceQueryProvider;
  293. if (null != provider)
  294. {
  295. context = provider.Context;
  296. }
  297. }
  298. }
  299. if (this != context)
  300. {
  301. throw Error.Argument(Strings.Context_DidNotOriginateAsync, "asyncResult");
  302. }
  303. }
  304. if (!result.IsCompletedInternally)
  305. {
  306. result.SetAborted();
  307. WebRequest request = result.Abortable;
  308. if (null != request)
  309. {
  310. request.Abort();
  311. }
  312. }
  313. }
  314. #endregion
  315. #region CreateQuery
  316. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "required for this feature")]
  317. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "required for this feature")]
  318. public DataServiceQuery<T> CreateQuery<T>(string entitySetName)
  319. {
  320. Util.CheckArgumentNotEmpty(entitySetName, "entitySetName");
  321. this.ValidateEntitySetName(ref entitySetName);
  322. ResourceSetExpression rse = new ResourceSetExpression(typeof(IOrderedQueryable<T>), null, Expression.Constant(entitySetName), typeof(T), null, CountOption.None, null, null);
  323. return new DataServiceQuery<T>.DataServiceOrderedQuery(rse, new DataServiceQueryProvider(this));
  324. }
  325. #endregion
  326. #region GetMetadataUri
  327. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "required for this feature")]
  328. public Uri GetMetadataUri()
  329. {
  330. Uri metadataUri = Util.CreateUri(this.baseUriWithSlash.OriginalString + XmlConstants.UriMetadataSegment, UriKind.Absolute);
  331. return metadataUri;
  332. }
  333. #endregion
  334. #region LoadProperty
  335. public IAsyncResult BeginLoadProperty(object entity, string propertyName, AsyncCallback callback, object state)
  336. {
  337. return this.BeginLoadProperty(entity, propertyName, (Uri)null, callback, state);
  338. }
  339. public IAsyncResult BeginLoadProperty(object entity, string propertyName, Uri nextLinkUri, AsyncCallback callback, object state)
  340. {
  341. LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, callback, state, nextLinkUri, null);
  342. result.BeginExecute();
  343. return result;
  344. }
  345. public IAsyncResult BeginLoadProperty(object entity, string propertyName, DataServiceQueryContinuation continuation, AsyncCallback callback, object state)
  346. {
  347. Util.CheckArgumentNull(continuation, "continuation");
  348. LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, callback, state, null, continuation);
  349. result.BeginExecute();
  350. return result;
  351. }
  352. public QueryOperationResponse EndLoadProperty(IAsyncResult asyncResult)
  353. {
  354. LoadPropertyResult response = QueryResult.EndExecute<LoadPropertyResult>(this, "LoadProperty", asyncResult);
  355. return response.LoadProperty();
  356. }
  357. #if !ASTORIA_LIGHT
  358. public QueryOperationResponse LoadProperty(object entity, string propertyName)
  359. {
  360. return this.LoadProperty(entity, propertyName, (Uri)null);
  361. }
  362. public QueryOperationResponse LoadProperty(object entity, string propertyName, Uri nextLinkUri)
  363. {
  364. LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, null, null, nextLinkUri, null);
  365. result.Execute();
  366. return result.LoadProperty();
  367. }
  368. public QueryOperationResponse LoadProperty(object entity, string propertyName, DataServiceQueryContinuation continuation)
  369. {
  370. LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, null, null, null, continuation);
  371. result.Execute();
  372. return result.LoadProperty();
  373. }
  374. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011", Justification = "allows compiler to infer 'T'")]
  375. public QueryOperationResponse<T> LoadProperty<T>(object entity, string propertyName, DataServiceQueryContinuation<T> continuation)
  376. {
  377. LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, null, null, null, continuation);
  378. result.Execute();
  379. return (QueryOperationResponse<T>)result.LoadProperty();
  380. }
  381. #endif
  382. #endregion
  383. #region GetReadStreamUri
  384. public Uri GetReadStreamUri(object entity)
  385. {
  386. EntityDescriptor box = this.EnsureContained(entity, "entity");
  387. return box.GetMediaResourceUri(this.baseUriWithSlash);
  388. }
  389. #endregion
  390. #region GetReadStream, BeginGetReadStream, EndGetReadStream
  391. public IAsyncResult BeginGetReadStream(object entity, DataServiceRequestArgs args, AsyncCallback callback, object state)
  392. {
  393. GetReadStreamResult result;
  394. result = this.CreateGetReadStreamResult(entity, args, callback, state);
  395. result.Begin();
  396. return result;
  397. }
  398. public DataServiceStreamResponse EndGetReadStream(IAsyncResult asyncResult)
  399. {
  400. GetReadStreamResult result = BaseAsyncResult.EndExecute<GetReadStreamResult>(this, "GetReadStream", asyncResult);
  401. return result.End();
  402. }
  403. #if !ASTORIA_LIGHT
  404. public DataServiceStreamResponse GetReadStream(object entity)
  405. {
  406. DataServiceRequestArgs args = new DataServiceRequestArgs();
  407. return this.GetReadStream(entity, args);
  408. }
  409. public DataServiceStreamResponse GetReadStream(object entity, string acceptContentType)
  410. {
  411. Util.CheckArgumentNotEmpty(acceptContentType, "acceptContentType");
  412. DataServiceRequestArgs args = new DataServiceRequestArgs();
  413. args.AcceptContentType = acceptContentType;
  414. return this.GetReadStream(entity, args);
  415. }
  416. public DataServiceStreamResponse GetReadStream(object entity, DataServiceRequestArgs args)
  417. {
  418. GetReadStreamResult result = this.CreateGetReadStreamResult(entity, args, null, null);
  419. return result.Execute();
  420. }
  421. #endif
  422. #endregion
  423. #region SetSaveStream
  424. public void SetSaveStream(object entity, Stream stream, bool closeStream, string contentType, string slug)
  425. {
  426. Util.CheckArgumentNull(contentType, "contentType");
  427. Util.CheckArgumentNull(slug, "slug");
  428. DataServiceRequestArgs args = new DataServiceRequestArgs();
  429. args.ContentType = contentType;
  430. args.Slug = slug;
  431. this.SetSaveStream(entity, stream, closeStream, args);
  432. }
  433. public void SetSaveStream(object entity, Stream stream, bool closeStream, DataServiceRequestArgs args)
  434. {
  435. EntityDescriptor box = this.EnsureContained(entity, "entity");
  436. Util.CheckArgumentNull(stream, "stream");
  437. Util.CheckArgumentNull(args, "args");
  438. ClientType clientType = ClientType.Create(entity.GetType());
  439. if (clientType.MediaDataMember != null)
  440. {
  441. throw new ArgumentException(
  442. Strings.Context_SetSaveStreamOnMediaEntryProperty(clientType.ElementTypeName),
  443. "entity");
  444. }
  445. box.SaveStream = new DataServiceSaveStream(stream, closeStream, args);
  446. Debug.Assert(box.State != EntityStates.Detached, "We should never have a detached entity in the entityDescriptor dictionary.");
  447. switch (box.State)
  448. {
  449. case EntityStates.Added:
  450. box.StreamState = StreamStates.Added;
  451. break;
  452. case EntityStates.Modified:
  453. case EntityStates.Unchanged:
  454. box.StreamState = StreamStates.Modified;
  455. break;
  456. case EntityStates.Deleted:
  457. default:
  458. throw new DataServiceClientException(Strings.DataServiceException_GeneralError);
  459. }
  460. }
  461. #endregion
  462. #region ExecuteBatch, BeginExecuteBatch, EndExecuteBatch
  463. public IAsyncResult BeginExecuteBatch(AsyncCallback callback, object state, params DataServiceRequest[] queries)
  464. {
  465. Util.CheckArgumentNotEmpty(queries, "queries");
  466. SaveResult result = new SaveResult(this, "ExecuteBatch", queries, SaveChangesOptions.Batch, callback, state, true);
  467. result.BatchBeginRequest(false);
  468. return result;
  469. }
  470. public DataServiceResponse EndExecuteBatch(IAsyncResult asyncResult)
  471. {
  472. SaveResult result = BaseAsyncResult.EndExecute<SaveResult>(this, "ExecuteBatch", asyncResult);
  473. return result.EndRequest();
  474. }
  475. #if !ASTORIA_LIGHT
  476. public DataServiceResponse ExecuteBatch(params DataServiceRequest[] queries)
  477. {
  478. Util.CheckArgumentNotEmpty(queries, "queries");
  479. SaveResult result = new SaveResult(this, "ExecuteBatch", queries, SaveChangesOptions.Batch, null, null, false);
  480. result.BatchRequest(false );
  481. return result.EndRequest();
  482. }
  483. #endif
  484. #endregion
  485. #region Execute(Uri), BeginExecute(Uri), EndExecute(Uri)
  486. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")]
  487. public IAsyncResult BeginExecute<TElement>(Uri requestUri, AsyncCallback callback, object state)
  488. {
  489. requestUri = Util.CreateUri(this.baseUriWithSlash, requestUri);
  490. QueryComponents qc = new QueryComponents(requestUri, Util.DataServiceVersionEmpty, typeof(TElement), null, null);
  491. return (new DataServiceRequest<TElement>(qc, null)).BeginExecute(this, this, callback, state);
  492. }
  493. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")]
  494. public IAsyncResult BeginExecute<T>(DataServiceQueryContinuation<T> continuation, AsyncCallback callback, object state)
  495. {
  496. Util.CheckArgumentNull(continuation, "continuation");
  497. QueryComponents qc = continuation.CreateQueryComponents();
  498. return (new DataServiceRequest<T>(qc, continuation.Plan)).BeginExecute(this, this, callback, state);
  499. }
  500. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")]
  501. public IEnumerable<TElement> EndExecute<TElement>(IAsyncResult asyncResult)
  502. {
  503. Util.CheckArgumentNull(asyncResult, "asyncResult");
  504. return DataServiceRequest.EndExecute<TElement>(this, this, asyncResult);
  505. }
  506. #if !ASTORIA_LIGHT
  507. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")]
  508. public IEnumerable<TElement> Execute<TElement>(Uri requestUri)
  509. {
  510. requestUri = Util.CreateUri(this.baseUriWithSlash, requestUri);
  511. QueryComponents qc = new QueryComponents(requestUri, Util.DataServiceVersionEmpty, typeof(TElement), null, null);
  512. DataServiceRequest request = new DataServiceRequest<TElement>(qc, null);
  513. return request.Execute<TElement>(this, qc);
  514. }
  515. public QueryOperationResponse<T> Execute<T>(DataServiceQueryContinuation<T> continuation)
  516. {
  517. Util.CheckArgumentNull(continuation, "continuation");
  518. QueryComponents qc = continuation.CreateQueryComponents();
  519. DataServiceRequest request = new DataServiceRequest<T>(qc, continuation.Plan);
  520. return request.Execute<T>(this, qc);
  521. }
  522. #endif
  523. #endregion
  524. #region SaveChanges, BeginSaveChanges, EndSaveChanges
  525. public IAsyncResult BeginSaveChanges(AsyncCallback callback, object state)
  526. {
  527. return this.BeginSaveChanges(this.SaveChangesDefaultOptions, callback, state);
  528. }
  529. public IAsyncResult BeginSaveChanges(SaveChangesOptions options, AsyncCallback callback, object state)
  530. {
  531. ValidateSaveChangesOptions(options);
  532. SaveResult result = new SaveResult(this, "SaveChanges", null, options, callback, state, true);
  533. bool replaceOnUpdate = IsFlagSet(options, SaveChangesOptions.ReplaceOnUpdate);
  534. if (IsFlagSet(options, SaveChangesOptions.Batch))
  535. {
  536. result.BatchBeginRequest(replaceOnUpdate);
  537. }
  538. else
  539. {
  540. result.BeginNextChange(replaceOnUpdate);
  541. }
  542. return result;
  543. }
  544. public DataServiceResponse EndSaveChanges(IAsyncResult asyncResult)
  545. {
  546. SaveResult result = BaseAsyncResult.EndExecute<SaveResult>(this, "SaveChanges", asyncResult);
  547. DataServiceResponse errors = result.EndRequest();
  548. if (this.ChangesSaved != null)
  549. {
  550. this.ChangesSaved(this, new SaveChangesEventArgs(errors));
  551. }
  552. return errors;
  553. }
  554. #if !ASTORIA_LIGHT
  555. public DataServiceResponse SaveChanges()
  556. {
  557. return this.SaveChanges(this.SaveChangesDefaultOptions);
  558. }
  559. public DataServiceResponse SaveChanges(SaveChangesOptions options)
  560. {
  561. DataServiceResponse errors = null;
  562. ValidateSaveChangesOptions(options);
  563. SaveResult result = new SaveResult(this, "SaveChanges", null, options, null, null, false);
  564. bool replaceOnUpdate = IsFlagSet(options, SaveChangesOptions.ReplaceOnUpdate);
  565. if (IsFlagSet(options, SaveChangesOptions.Batch))
  566. {
  567. result.BatchRequest(replaceOnUpdate);
  568. }
  569. else
  570. {
  571. result.BeginNextChange(replaceOnUpdate);
  572. }
  573. errors = result.EndRequest();
  574. Debug.Assert(null != errors, "null errors");
  575. if (this.ChangesSaved != null)
  576. {
  577. this.ChangesSaved(this, new SaveChangesEventArgs(errors));
  578. }
  579. return errors;
  580. }
  581. #endif
  582. #endregion
  583. #region Add, Attach, Delete, Detach, Update, TryGetEntity, TryGetUri
  584. public void AddLink(object source, string sourceProperty, object target)
  585. {
  586. this.EnsureRelatable(source, sourceProperty, target, EntityStates.Added);
  587. LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target);
  588. if (this.bindings.ContainsKey(relation))
  589. {
  590. throw Error.InvalidOperation(Strings.Context_RelationAlreadyContained);
  591. }
  592. relation.State = EntityStates.Added;
  593. this.bindings.Add(relation, relation);
  594. this.IncrementChange(relation);
  595. }
  596. public void AttachLink(object source, string sourceProperty, object target)
  597. {
  598. this.AttachLink(source, sourceProperty, target, MergeOption.NoTracking);
  599. }
  600. public bool DetachLink(object source, string sourceProperty, object target)
  601. {
  602. Util.CheckArgumentNull(source, "source");
  603. Util.CheckArgumentNotEmpty(sourceProperty, "sourceProperty");
  604. LinkDescriptor existing;
  605. LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target);
  606. if (!this.bindings.TryGetValue(relation, out existing))
  607. {
  608. return false;
  609. }
  610. this.DetachExistingLink(existing, false);
  611. return true;
  612. }
  613. public void DeleteLink(object source, string sourceProperty, object target)
  614. {
  615. bool delay = this.EnsureRelatable(source, sourceProperty, target, EntityStates.Deleted);
  616. LinkDescriptor existing = null;
  617. LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target);
  618. if (this.bindings.TryGetValue(relation, out existing) && (EntityStates.Added == existing.State))
  619. {
  620. this.DetachExistingLink(existing, false);
  621. }
  622. else
  623. {
  624. if (delay)
  625. {
  626. throw Error.InvalidOperation(Strings.Context_NoRelationWithInsertEnd);
  627. }
  628. if (null == existing)
  629. {
  630. this.bindings.Add(relation, relation);
  631. existing = relation;
  632. }
  633. if (EntityStates.Deleted != existing.State)
  634. {
  635. existing.State = EntityStates.Deleted;
  636. this.IncrementChange(existing);
  637. }
  638. }
  639. }
  640. public void SetLink(object source, string sourceProperty, object target)
  641. {
  642. this.EnsureRelatable(source, sourceProperty, target, EntityStates.Modified);
  643. LinkDescriptor relation = this.DetachReferenceLink(source, sourceProperty, target, MergeOption.NoTracking);
  644. if (null == relation)
  645. {
  646. relation = new LinkDescriptor(source, sourceProperty, target);
  647. this.bindings.Add(relation, relation);
  648. }
  649. Debug.Assert(
  650. 0 == relation.State ||
  651. IncludeLinkState(relation.State),
  652. "set link entity state");
  653. if (EntityStates.Modified != relation.State)
  654. {
  655. relation.State = EntityStates.Modified;
  656. this.IncrementChange(relation);
  657. }
  658. }
  659. #endregion
  660. #region AddObject, AttachTo, DeleteObject, Detach, TryGetEntity, TryGetUri
  661. public void AddObject(string entitySetName, object entity)
  662. {
  663. this.ValidateEntitySetName(ref entitySetName);
  664. ValidateEntityType(entity);
  665. EntityDescriptor resource = new EntityDescriptor(null, null , null , entity, null, null, entitySetName, null, EntityStates.Added);
  666. try
  667. {
  668. this.entityDescriptors.Add(entity, resource);
  669. }
  670. catch (ArgumentException)
  671. {
  672. throw Error.InvalidOperation(Strings.Context_EntityAlreadyContained);
  673. }
  674. this.IncrementChange(resource);
  675. }
  676. public void AddRelatedObject(object source, string sourceProperty, object target)
  677. {
  678. Util.CheckArgumentNull(source, "source");
  679. Util.CheckArgumentNotEmpty(sourceProperty, "propertyName");
  680. Util.CheckArgumentNull(target, "target");
  681. ValidateEntityType(source);
  682. EntityDescriptor sourceResource = this.EnsureContained(source, "source");
  683. if (sourceResource.State == EntityStates.Deleted)
  684. {
  685. throw Error.InvalidOperation(Strings.Context_AddRelatedObjectSourceDeleted);
  686. }
  687. ClientType parentType = ClientType.Create(source.GetType());
  688. ClientType.ClientProperty property = parentType.GetProperty(sourceProperty, false);
  689. if (property.IsKnownType || property.CollectionType == null)
  690. {
  691. throw Error.InvalidOperation(Strings.Context_AddRelatedObjectCollectionOnly);
  692. }
  693. ClientType childType = ClientType.Create(target.GetType());
  694. ValidateEntityType(target);
  695. ClientType propertyElementType = ClientType.Create(property.CollectionType);
  696. if (!propertyElementType.ElementType.IsAssignableFrom(childType.ElementType))
  697. {
  698. throw Error.Argument(Strings.Context_RelationNotRefOrCollection, "target");
  699. }
  700. EntityDescriptor targetResource = new EntityDescriptor(null, null, null, target, sourceResource, sourceProperty, null , null, EntityStates.Added);
  701. try
  702. {
  703. this.entityDescriptors.Add(target, targetResource);
  704. }
  705. catch (ArgumentException)
  706. {
  707. throw Error.InvalidOperation(Strings.Context_EntityAlreadyContained);
  708. }
  709. LinkDescriptor end = targetResource.GetRelatedEnd();
  710. end.State = EntityStates.Added;
  711. this.bindings.Add(end, end);
  712. this.IncrementChange(targetResource);
  713. }
  714. public void AttachTo(string entitySetName, object entity)
  715. {
  716. this.AttachTo(entitySetName, entity, null);
  717. }
  718. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", MessageId = "etag", Justification = "represents ETag in request")]
  719. public void AttachTo(string entitySetName, object entity, string etag)
  720. {
  721. this.ValidateEntitySetName(ref entitySetName);
  722. Uri editLink = GenerateEditLinkUri(this.baseUriWithSlash, entitySetName, entity);
  723. String identity = Util.ReferenceIdentity(editLink.ToString());
  724. EntityDescriptor descriptor = new EntityDescriptor(identity, null , editLink, entity, null , null , null , etag, EntityStates.Unchanged);
  725. this.InternalAttachEntityDescriptor(descriptor, true);
  726. }
  727. public void DeleteObject(object entity)
  728. {
  729. Util.CheckArgumentNull(entity, "entity");
  730. EntityDescriptor resource = null;
  731. if (!this.entityDescriptors.TryGetValue(entity, out resource))
  732. {
  733. throw Error.InvalidOperation(Strings.Context_EntityNotContained);
  734. }
  735. EntityStates state = resource.State;
  736. if (EntityStates.Added == state)
  737. {
  738. this.DetachResource(resource);
  739. }
  740. else if (EntityStates.Deleted != state)
  741. {
  742. Debug.Assert(
  743. IncludeLinkState(state),
  744. "bad state transition to deleted");
  745. resource.State = EntityStates.Deleted;
  746. this.IncrementChange(resource);
  747. }
  748. }
  749. public bool Detach(object entity)
  750. {
  751. Util.CheckArgumentNull(entity, "entity");
  752. EntityDescriptor resource = null;
  753. if (this.entityDescriptors.TryGetValue(entity, out resource))
  754. {
  755. return this.DetachResource(resource);
  756. }
  757. return false;
  758. }
  759. public void UpdateObject(object entity)
  760. {
  761. Util.CheckArgumentNull(entity, "entity");
  762. EntityDescriptor resource = null;
  763. if (!this.entityDescriptors.TryGetValue(entity, out resource))
  764. {
  765. throw Error.Argument(Strings.Context_EntityNotContained, "entity");
  766. }
  767. if (EntityStates.Unchanged == resource.State)
  768. {
  769. resource.State = EntityStates.Modified;
  770. this.IncrementChange(resource);
  771. }
  772. }
  773. public bool TryGetEntity<TEntity>(Uri identity, out TEntity entity) where TEntity : class
  774. {
  775. entity = null;
  776. Util.CheckArgumentNull(identity, "relativeUri");
  777. EntityStates state;
  778. entity = (TEntity)this.TryGetEntity(Util.ReferenceIdentity(identity.ToString()), null, MergeOption.AppendOnly, out state);
  779. return (null != entity);
  780. }
  781. public bool TryGetUri(object entity, out Uri identity)
  782. {
  783. identity = null;
  784. Util.CheckArgumentNull(entity, "entity");
  785. EntityDescriptor resource = null;
  786. if ((null != this.identityToDescriptor) &&
  787. this.entityDescriptors.TryGetValue(entity, out resource) &&
  788. (null != resource.Identity) &&
  789. Object.ReferenceEquals(resource, this.identityToDescriptor[resource.Identity]))
  790. {
  791. string identityUri = Util.DereferenceIdentity(resource.Identity);
  792. identity = Util.CreateUri(identityUri, UriKind.Absolute);
  793. }
  794. return (null != identity);
  795. }
  796. internal static Exception HandleResponse(
  797. HttpStatusCode statusCode,
  798. string responseVersion,
  799. Func<Stream> getResponseStream,
  800. bool throwOnFailure)
  801. {
  802. InvalidOperationException failure = null;
  803. if (!CanHandleResponseVersion(responseVersion))
  804. {
  805. string description = Strings.Context_VersionNotSupported(
  806. responseVersion,
  807. SerializeSupportedVersions());
  808. failure = Error.InvalidOperation(description);
  809. }
  810. if (failure == null && !WebUtil.SuccessStatusCode(statusCode))
  811. {
  812. failure = GetResponseText(getResponseStream, statusCode);
  813. }
  814. if (failure != null && throwOnFailure)
  815. {
  816. throw failure;
  817. }
  818. return failure;
  819. }
  820. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031", Justification = "Cache exception so user can examine it later")]
  821. internal static DataServiceClientException GetResponseText(Func<Stream> getResponseStream, HttpStatusCode statusCode)
  822. {
  823. string message = null;
  824. using (System.IO.Stream stream = getResponseStream())
  825. {
  826. if ((null != stream) && stream.CanRead)
  827. {
  828. message = new StreamReader(stream).ReadToEnd();
  829. }
  830. }
  831. if (String.IsNullOrEmpty(message))
  832. {
  833. message = statusCode.ToString();
  834. }
  835. return new DataServiceClientException(message, (int)statusCode);
  836. }
  837. internal void AttachIdentity(String identity, Uri selfLink, Uri editLink, object entity, string etag)
  838. {
  839. Debug.Assert(null != identity, "must have identity");
  840. this.EnsureIdentityToResource();
  841. EntityDescriptor resource = this.entityDescriptors[entity];
  842. this.DetachResourceIdentity(resource);
  843. if (resource.IsDeepInsert)
  844. {
  845. LinkDescriptor end = this.bindings[resource.GetRelatedEnd()];
  846. end.State = EntityStates.Unchanged;
  847. }
  848. resource.ETag = etag;
  849. resource.Identity = identity;
  850. resource.SelfLink = selfLink;
  851. resource.EditLink = editLink;
  852. resource.State = EntityStates.Unchanged;
  853. this.identityToDescriptor[identity] = resource;
  854. }
  855. internal void AttachLocation(object entity, string location)
  856. {
  857. Debug.Assert(null != entity, "null != entity");
  858. Uri editLink = new Uri(location, UriKind.Absolute);
  859. String identity = Util.ReferenceIdentity(editLink.ToString());
  860. this.EnsureIdentityToResource();
  861. EntityDescriptor resource = this.entityDescriptors[entity];
  862. this.DetachResourceIdentity(resource);
  863. if (resource.IsDeepInsert)
  864. {
  865. LinkDescriptor end = this.bindings[resource.GetRelatedEnd()];
  866. end.State = EntityStates.Unchanged;
  867. }
  868. resource.Identity = identity;
  869. resource.EditLink = editLink;
  870. this.identityToDescriptor[identity] = resource;
  871. }
  872. internal void AttachLink(object source, string sourceProperty, object target, MergeOption linkMerge)
  873. {
  874. this.EnsureRelatable(source, sourceProperty, target, EntityStates.Unchanged);
  875. LinkDescriptor existing = null;
  876. LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target);
  877. if (this.bindings.TryGetValue(relation, out existing))
  878. {
  879. switch (linkMerge)
  880. {
  881. case MergeOption.AppendOnly:
  882. break;
  883. case MergeOption.OverwriteChanges:
  884. relation = existing;
  885. break;
  886. case MergeOption.PreserveChanges:
  887. if ((EntityStates.Added == existing.State) ||
  888. (EntityStates.Unchanged == existing.State) ||
  889. (EntityStates.Modified == existing.State && null != existing.Target))
  890. {
  891. relation = existing;
  892. }
  893. break;
  894. case MergeOption.NoTracking:
  895. throw Error.InvalidOperation(Strings.Context_RelationAlreadyContained);
  896. }
  897. }
  898. else
  899. {
  900. bool collectionProperty = (null != ClientType.Create(source.GetType()).GetProperty(sourceProperty, false).CollectionType);
  901. if (collectionProperty || (null == (existing = this.DetachReferenceLink(source, sourceProperty, target, linkMerge))))
  902. {
  903. this.bindings.Add(relation, relation);
  904. this.IncrementChange(relation);
  905. }
  906. else if (!((MergeOption.AppendOnly == linkMerge) ||
  907. (MergeOption.PreserveChanges == linkMerge && EntityStates.Modified == existing.State)))
  908. {
  909. relation = existing;
  910. }
  911. }
  912. relation.State = EntityStates.Unchanged;
  913. }
  914. internal EntityDescriptor InternalAttachEntityDescriptor(EntityDescriptor descriptor, bool failIfDuplicated)
  915. {
  916. Debug.Assert((null != descriptor.Identity), "must have identity");
  917. Debug.Assert(null != descriptor.Entity && ClientType.Create(descriptor.Entity.GetType()).IsEntityType, "must be entity type to attach");
  918. this.EnsureIdentityToResource();
  919. EntityDescriptor resource;
  920. this.entityDescriptors.TryGetValue(descriptor.Entity, out resource);
  921. EntityDescriptor existing;
  922. this.identityToDescriptor.TryGetValue(descriptor.Identity, out existing);
  923. if (failIfDuplicated && (null != resource))
  924. {
  925. throw Error.InvalidOperation(Strings.Context_EntityAlreadyContained);
  926. }
  927. else if (resource != existing)
  928. {
  929. throw Error.InvalidOperation(Strings.Context_DifferentEntityAlreadyContained);
  930. }
  931. else if (null == resource)
  932. {
  933. resource = descriptor;
  934. this.IncrementChange(descriptor);
  935. this.entityDescriptors.Add(descriptor.Entity, descriptor);
  936. this.identityToDescriptor.Add(descriptor.Identity, descriptor);
  937. }
  938. return resource;
  939. }
  940. #endregion
  941. #if ASTORIA_LIGHT
  942. internal HttpWebRequest CreateRequest(Uri requestUri, string method, bool allowAnyType, string contentType, Version requestVersion, bool sendChunked)
  943. {
  944. return CreateRequest(requestUri, method, allowAnyType, contentType, requestVersion, sendChunked, HttpStack.Auto);
  945. }
  946. #endif
  947. #if !ASTORIA_LIGHT
  948. internal HttpWebRequest CreateRequest(Uri requestUri, string method, bool allowAnyType, string contentType, Version requestVersion, bool sendChunked)
  949. #else
  950. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "sendChunked", Justification = "common parameter not used in silverlight")]
  951. internal HttpWebRequest CreateRequest(Uri requestUri, string method, bool allowAnyType, string contentType, Version requestVersion, bool sendChunked, HttpStack httpStackArg)
  952. #endif
  953. {
  954. Debug.Assert(null != requestUri, "request uri is null");
  955. Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri");
  956. Debug.Assert(
  957. requestUri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) ||
  958. requestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase),
  959. "request uri is not for HTTP");
  960. Debug.Assert(
  961. Object.ReferenceEquals(XmlConstants.HttpMethodDelete, method) ||
  962. Object.ReferenceEquals(XmlConstants.HttpMethodGet, method) ||
  963. Object.ReferenceEquals(XmlConstants.HttpMethodPost, method) ||
  964. Object.ReferenceEquals(XmlConstants.HttpMethodPut, method) ||
  965. Object.ReferenceEquals(XmlConstants.HttpMethodMerge, method),
  966. "unexpected http method string reference");
  967. #if !ASTORIA_LIGHT
  968. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
  969. #else
  970. if (httpStackArg == HttpStack.Auto)
  971. {
  972. httpStackArg = this.httpStack;
  973. }
  974. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri, httpStackArg);
  975. #endif
  976. #if !ASTORIA_LIGHT
  977. if (null != this.Credentials)
  978. {
  979. request.Credentials = this.Credentials;
  980. }
  981. #endif
  982. #if !ASTORIA_LIGHT
  983. if (0 != this.timeout)
  984. {
  985. request.Timeout = (int)Math.Min(Int32.MaxValue, new TimeSpan(0, 0, this.timeout).TotalMilliseconds);
  986. }
  987. #endif
  988. #if !ASTORIA_LIGHT
  989. request.KeepAlive = true;
  990. #endif
  991. #if !ASTORIA_LIGHT
  992. request.UserAgent = "Microsoft ADO.NET Data Services";
  993. #endif
  994. if (this.UsePostTunneling &&
  995. (!Object.ReferenceEquals(XmlConstants.HttpMethodPost, method)) &&
  996. (!Object.ReferenceEquals(XmlConstants.HttpMethodGet, method)))
  997. {
  998. request.Headers[XmlConstants.HttpXMethod] = method;
  999. request.Method = XmlConstants.HttpMethodPost;
  1000. }
  1001. else
  1002. {
  1003. request.Method = method;
  1004. }
  1005. if (requestVersion != null && requestVersion.Major > 0)
  1006. {
  1007. request.Headers[XmlConstants.HttpDataServiceVersion] = requestVersion.ToString() + Util.VersionSuffix;
  1008. }
  1009. request.Headers[XmlConstants.HttpMaxDataServiceVersion] = Util.MaxResponseVersion.ToString() + Util.VersionSuffix;
  1010. #if !ASTORIA_LIGHT
  1011. if (sendChunked)
  1012. {
  1013. request.SendChunked = true;
  1014. }
  1015. #endif
  1016. if (this.SendingRequest != null)
  1017. {
  1018. System.Net.WebHeaderCollection requestHeaders;
  1019. #if !ASTORIA_LIGHT
  1020. requestHeaders = request.Headers;
  1021. SendingRequestEventArgs args = new SendingRequestEventArgs(request, requestHeaders);
  1022. #else
  1023. requestHeaders = request.CreateEmptyWebHeaderCollection();
  1024. SendingRequestEventArgs args = new SendingRequestEventArgs(null, requestHeaders);
  1025. #endif
  1026. this.SendingRequest(this, args);
  1027. #if !ASTORIA_LIGHT
  1028. if (!Object.ReferenceEquals(args.Request, request))
  1029. {
  1030. request = (System.Net.HttpWebRequest)args.Request;
  1031. }
  1032. #else
  1033. foreach (string key in requestHeaders.AllKeys)
  1034. {
  1035. request.Headers[key] = requestHeaders[key];
  1036. }
  1037. #endif
  1038. }
  1039. request.Accept = allowAnyType ?
  1040. XmlConstants.MimeAny :
  1041. (XmlConstants.MimeApplicationAtom + "," + XmlConstants.MimeApplicationXml);
  1042. request.Headers[HttpRequestHeader.AcceptCharset] = XmlConstants.Utf8Encoding;
  1043. #if !ASTORIA_LIGHT
  1044. bool allowStreamBuffering = false;
  1045. bool removeXMethod = true;
  1046. #endif
  1047. if (!Object.ReferenceEquals(XmlConstants.HttpMethodGet, method))
  1048. {
  1049. Debug.Assert(!String.IsNullOrEmpty(contentType), "Content-Type must be specified for non get operation");
  1050. request.ContentType = contentType;
  1051. if (Object.ReferenceEquals(XmlConstants.HttpMethodDelete, method))
  1052. {
  1053. request.ContentLength = 0;
  1054. }
  1055. #if !ASTORIA_LIGHT
  1056. // else
  1057. {
  1058. allowStreamBuffering = true;
  1059. }
  1060. #endif
  1061. if (this.UsePostTunneling && (!Object.ReferenceEquals(XmlConstants.HttpMethodPost, method)))
  1062. {
  1063. request.Headers[XmlConstants.HttpXMethod] = method;
  1064. method = XmlConstants.HttpMethodPost;
  1065. #if !ASTORIA_LIGHT
  1066. removeXMethod = false;
  1067. #endif
  1068. }
  1069. }
  1070. else
  1071. {
  1072. Debug.Assert(contentType == null, "Content-Type for get methods should be null");
  1073. }
  1074. #if !ASTORIA_LIGHT
  1075. request.AllowWriteStreamBuffering = allowStreamBuffering;
  1076. #endif
  1077. ICollection<string> headers;
  1078. headers = request.Headers.AllKeys;
  1079. #if !ASTORIA_LIGHT
  1080. if (headers.Contains(XmlConstants.HttpRequestIfMatch))
  1081. {
  1082. request.Headers.Remove(HttpRequestHeader.IfMatch);
  1083. }
  1084. #endif
  1085. #if !ASTORIA_LIGHT
  1086. if (removeXMethod && headers.Contains(XmlConstants.HttpXMethod))
  1087. {
  1088. request.Headers.Remove(XmlConstants.HttpXMethod);
  1089. }
  1090. #endif
  1091. request.Method = method;
  1092. return request;
  1093. }
  1094. internal object TryGetEntity(String resourceUri, string etag, MergeOption merger, out EntityStates state)
  1095. {
  1096. Debug.Assert(null != resourceUri, "null uri");
  1097. state = EntityStates.Detached;
  1098. EntityDescriptor resource = null;
  1099. if ((null != this.identityToDescriptor) &&
  1100. this.identityToDescriptor.TryGetValue(resourceUri, out resource))
  1101. {
  1102. state = resource.State;
  1103. if ((null != etag) && (MergeOption.AppendOnly != merger))
  1104. {
  1105. resource.ETag = etag;
  1106. }
  1107. Debug.Assert(null != resource.Entity, "null entity");
  1108. return resource.Entity;
  1109. }
  1110. return null;
  1111. }
  1112. internal IEnumerable<LinkDescriptor> GetLinks(object source, string sourceProperty)
  1113. {
  1114. return this.bindings.Values.Where(o => (o.Source == source) && (o.SourceProperty == sourceProperty));
  1115. }
  1116. internal Type ResolveTypeFromName(string wireName, Type userType, bool checkAssignable)
  1117. {
  1118. Debug.Assert(null != userType, "null != baseType");
  1119. if (String.IsNullOrEmpty(wireName))
  1120. {
  1121. return userType;
  1122. }
  1123. Type payloadType;
  1124. if (!ClientConvert.ToNamedType(wireName, out payloadType))
  1125. {
  1126. payloadType = null;
  1127. Func<string, Type> resolve = this.ResolveType;
  1128. if (null != resolve)
  1129. {
  1130. payloadType = resolve(wireName);
  1131. }
  1132. if (null == payloadType)
  1133. {
  1134. #if !ASTORIA_LIGHT
  1135. payloadType = ClientType.ResolveFromName(wireName, userType);
  1136. #else
  1137. payloadType = ClientType.ResolveFromName(wireName, userType, this.GetType());
  1138. #endif
  1139. }
  1140. if (checkAssignable && (null != payloadType) && (!userType.IsAssignableFrom(payloadType)))
  1141. {
  1142. throw Error.InvalidOperation(Strings.Deserialize_Current(userType, payloadType));
  1143. }
  1144. }
  1145. return payloadType ?? userType;
  1146. }
  1147. internal string ResolveNameFromType(Type type)
  1148. {
  1149. Debug.Assert(null != type, "null type");
  1150. Func<Type, string> resolve = this.ResolveName;
  1151. return ((null != resolve) ? resolve(type) : (String)null);
  1152. }
  1153. internal string GetServerTypeName(EntityDescriptor descriptor)
  1154. {
  1155. Debug.Assert(descriptor != null && descriptor.Entity != null, "Null descriptor or no entity in descriptor");
  1156. if (this.resolveName != null)
  1157. {
  1158. Type entityType = descriptor.Entity.GetType();
  1159. var codegenAttr = this.resolveName.Method.GetCustomAttributes(false).OfType<System.CodeDom.Compiler.GeneratedCodeAttribute>().FirstOrDefault();
  1160. if (codegenAttr == null || codegenAttr.Tool != Util.CodeGeneratorToolName)
  1161. {
  1162. return this.resolveName(entityType) ?? descriptor.ServerTypeName;
  1163. }
  1164. else
  1165. {
  1166. return descriptor.ServerTypeName ?? this.resolveName(entityType);
  1167. }
  1168. }
  1169. else
  1170. {
  1171. return descriptor.ServerTypeName;
  1172. }
  1173. }
  1174. internal void FireReadingEntityEvent(object entity, XElement data)
  1175. {
  1176. Debug.Assert(entity != null, "entity != null");
  1177. Debug.Assert(data != null, "data != null");
  1178. ReadingWritingEntityEventArgs args = new ReadingWritingEntityEventArgs(entity, data);
  1179. this.ReadingEntity(this, args);
  1180. }
  1181. #region Ensure
  1182. private static bool IncludeLinkState(EntityStates x)
  1183. {
  1184. return ((EntityStates.Modified == x) || (EntityStates.Unchanged == x));
  1185. }
  1186. #endregion
  1187. private static bool CanHandleResponseVersion(string responseVersion)
  1188. {
  1189. if (!String.IsNullOrEmpty(responseVersion))
  1190. {
  1191. KeyValuePair<Version, string> version;
  1192. if (!HttpProcessUtility.TryReadVersion(responseVersion, out version))
  1193. {
  1194. return false;
  1195. }
  1196. if (!Util.SupportedResponseVersions.Contains(version.Key))
  1197. {
  1198. return false;
  1199. }
  1200. }
  1201. return true;
  1202. }
  1203. private static string SerializeSupportedVersions()
  1204. {
  1205. Debug.Assert(Util.SupportedResponseVersions.Length > 0, "At least one supported version must exist.");
  1206. StringBuilder supportedVersions = new StringBuilder("'").Append(Util.SupportedResponseVersions[0].ToString());
  1207. for (int versionIdx = 1; versionIdx < Util.SupportedResponseVersions.Length; versionIdx++)
  1208. {
  1209. supportedVersions.Append("', '");
  1210. supportedVersions.Append(Util.SupportedResponseVersions[versionIdx].ToString());
  1211. }
  1212. supportedVersions.Append("'");
  1213. return supportedVersions.ToString();
  1214. }
  1215. private static Uri GenerateEditLinkUri(Uri baseUriWithSlash, string entitySetName, object entity)
  1216. {
  1217. Debug.Assert(null != baseUriWithSlash && baseUriWithSlash.IsAbsoluteUri && baseUriWithSlash.OriginalString.EndsWith("/", StringComparison.Ordinal), "baseUriWithSlash");
  1218. Debug.Assert(!String.IsNullOrEmpty(entitySetName) && !entitySetName.StartsWith("/", StringComparison.Ordinal), "entitySetName");
  1219. ValidateEntityTypeHasKeys(entity);
  1220. StringBuilder builder = new StringBuilder();
  1221. builder.Append(baseUriWithSlash.AbsoluteUri);
  1222. builder.Append(entitySetName);
  1223. builder.Append("(");
  1224. string prefix = String.Empty;
  1225. ClientType clientType = ClientType.Create(entity.GetType());
  1226. ClientType.ClientProperty[] keys = clientType.Properties.Where<ClientType.ClientProperty>(ClientType.ClientProperty.GetKeyProperty).ToArray();
  1227. foreach (ClientType.ClientProperty property in keys)
  1228. {
  1229. #if ASTORIA_OPEN_OBJECT
  1230. Debug.Assert(!property.OpenObjectProperty, "key property values can't be OpenProperties");
  1231. #endif
  1232. builder.Append(prefix);
  1233. if (1 < keys.Length)
  1234. {
  1235. builder.Append(property.PropertyName).Append("=");
  1236. }
  1237. object value = property.GetValue(entity);
  1238. if (null == value)
  1239. {
  1240. throw Error.InvalidOperation(Strings.Serializer_NullKeysAreNotSupported(property.PropertyName));
  1241. }
  1242. string converted;
  1243. if (!ClientConvert.TryKeyPrimitiveToString(value, out converted))
  1244. {
  1245. throw Error.InvalidOperation(Strings.Context_CannotConvertKey(value));
  1246. }
  1247. builder.Append(System.Uri.EscapeDataString(converted));
  1248. prefix = ",";
  1249. }
  1250. builder.Append(")");
  1251. return Util.CreateUri(builder.ToString(), UriKind.Absolute);
  1252. }
  1253. private static string GetEntityHttpMethod(EntityStates state, bool replaceOnUpdate)
  1254. {
  1255. switch (state)
  1256. {
  1257. case EntityStates.Deleted:
  1258. return XmlConstants.HttpMethodDelete;
  1259. case EntityStates.Modified:
  1260. if (replaceOnUpdate)
  1261. {
  1262. return XmlConstants.HttpMethodPut;
  1263. }
  1264. else
  1265. {
  1266. return XmlConstants.HttpMethodMerge;
  1267. }
  1268. case EntityStates.Added:
  1269. return XmlConstants.HttpMethodPost;
  1270. default:
  1271. throw Error.InternalError(InternalError.UnvalidatedEntityState);
  1272. }
  1273. }
  1274. private static string GetLinkHttpMethod(LinkDescriptor link)
  1275. {
  1276. bool collection = (null != ClientType.Create(link.Source.GetType()).GetProperty(link.SourceProperty, false).CollectionType);
  1277. if (!collection)
  1278. {
  1279. Debug.Assert(EntityStates.Modified == link.State, "not Modified state");
  1280. if (null == link.Target)
  1281. {
  1282. return XmlConstants.HttpMethodDelete;
  1283. }
  1284. else
  1285. {
  1286. return XmlConstants.HttpMethodPut;
  1287. }
  1288. }
  1289. else if (EntityStates.Deleted == link.State)
  1290. {
  1291. return XmlConstants.HttpMethodDelete;
  1292. }
  1293. else
  1294. {
  1295. Debug.Assert(EntityStates.Added == link.State, "not Added state");
  1296. return XmlConstants.HttpMethodPost;
  1297. }
  1298. }
  1299. private static void HandleResponsePost(LinkDescriptor entry)
  1300. {
  1301. if (!((EntityStates.Added == entry.State) || (EntityStates.Modified == entry.State && null != entry.Target)))
  1302. {
  1303. Error.ThrowBatchUnexpectedContent(InternalError.LinkNotAddedState);
  1304. }
  1305. entry.State = EntityStates.Unchanged;
  1306. }
  1307. private static void HandleResponsePut(Descriptor entry, string etag)
  1308. {
  1309. if (entry.IsResource)
  1310. {
  1311. EntityDescriptor descriptor = (EntityDescriptor)entry;
  1312. if (EntityStates.Modified != descriptor.State && StreamStates.Modified != descriptor.StreamState)
  1313. {
  1314. Error.ThrowBatchUnexpectedContent(InternalError.EntryNotModified);
  1315. }
  1316. if (descriptor.StreamState == StreamStates.Modified)
  1317. {
  1318. descriptor.StreamETag = etag;
  1319. descriptor.StreamState = StreamStates.NoStream;
  1320. }
  1321. else
  1322. {
  1323. Debug.Assert(descriptor.State == EntityStates.Modified, "descriptor.State == EntityStates.Modified");
  1324. descriptor.ETag = etag;
  1325. descriptor.State = EntityStates.Unchanged;
  1326. }
  1327. }
  1328. else
  1329. {
  1330. LinkDescriptor link = (LinkDescriptor)entry;
  1331. if ((EntityStates.Added == entry.State) || (EntityStates.Modified == entry.State))
  1332. {
  1333. link.State = EntityStates.Unchanged;
  1334. }
  1335. else if (EntityStates.Detached != entry.State)
  1336. {
  1337. Error.ThrowBatchUnexpectedContent(InternalError.LinkBadState);
  1338. }
  1339. }
  1340. }
  1341. private static void WriteContentProperty(XmlWriter writer, string namespaceName, ClientType.ClientProperty property, object propertyValue)
  1342. {
  1343. writer.WriteStartElement(property.PropertyName, namespaceName);
  1344. string typename = ClientConvert.GetEdmType(property.PropertyType);
  1345. if (null != typename)
  1346. {
  1347. writer.WriteAttributeString(XmlConstants.AtomTypeAttributeName, XmlConstants.DataWebMetadataNamespace, typename);
  1348. }
  1349. if (null == propertyValue)
  1350. {
  1351. writer.WriteAttributeString(XmlConstants.AtomNullAttributeName, XmlConstants.DataWebMetadataNamespace, XmlConstants.XmlTrueLiteral);
  1352. if (property.KeyProperty)
  1353. {
  1354. throw Error.InvalidOperation(Strings.Serializer_NullKeysAreNotSupported(property.PropertyName));
  1355. }
  1356. }
  1357. else
  1358. {
  1359. string convertedValue = ClientConvert.ToString(propertyValue, false );
  1360. if (0 == convertedValue.Length)
  1361. {
  1362. writer.WriteAttributeString(XmlConstants.AtomNullAttributeName, XmlConstants.DataWebMetadataNamespace, XmlConstants.XmlFalseLiteral);
  1363. }
  1364. else
  1365. {
  1366. if (Char.IsWhiteSpace(convertedValue[0]) ||
  1367. Char.IsWhiteSpace(convertedValue[convertedValue.Length - 1]))
  1368. {
  1369. writer.WriteAttributeString(XmlConstants.XmlSpaceAttributeName, XmlConstants.XmlNamespacesNamespace, XmlConstants.XmlSpacePreserveValue);
  1370. }
  1371. writer.WriteValue(convertedValue);
  1372. }
  1373. }
  1374. writer.WriteEndElement();
  1375. }
  1376. private static void ValidateEntityType(object entity)
  1377. {
  1378. Util.CheckArgumentNull(entity, "entity");
  1379. if (!ClientType.Create(entity.GetType()).IsEntityType)
  1380. {
  1381. throw Error.Argument(Strings.Content_EntityIsNotEntityType, "entity");
  1382. }
  1383. }
  1384. private static void ValidateEntityTypeHasKeys(object entity)
  1385. {
  1386. Util.CheckArgumentNull(entity, "entity");
  1387. if (ClientType.Create(entity.GetType()).KeyCount <= 0)
  1388. {
  1389. throw Error.Argument(Strings.Content_EntityWithoutKey, "entity");
  1390. }
  1391. }
  1392. private static void ValidateSaveChangesOptions(SaveChangesOptions options)
  1393. {
  1394. const SaveChangesOptions All =
  1395. SaveChangesOptions.ContinueOnError |
  1396. SaveChangesOptions.Batch |
  1397. SaveChangesOptions.ReplaceOnUpdate;
  1398. if ((options | All) != All)
  1399. {
  1400. throw Error.ArgumentOutOfRange("options");
  1401. }
  1402. if (IsFlagSet(options, SaveChangesOptions.Batch | SaveChangesOptions.ContinueOnError))
  1403. {
  1404. throw Error.ArgumentOutOfRange("options");
  1405. }
  1406. }
  1407. private static bool IsFlagSet(SaveChangesOptions options, SaveChangesOptions flag)
  1408. {
  1409. return ((options & flag) == flag);
  1410. }
  1411. private static void WriteOperationRequestHeaders(StreamWriter writer, string methodName, string uri, Version requestVersion)
  1412. {
  1413. writer.WriteLine("{0}: {1}", XmlConstants.HttpContentType, XmlConstants.MimeApplicationHttp);
  1414. writer.WriteLine("{0}: {1}", XmlConstants.HttpContentTransferEncoding, XmlConstants.BatchRequestContentTransferEncoding);
  1415. writer.WriteLine();
  1416. writer.WriteLine("{0} {1} {2}", methodName, uri, XmlConstants.HttpVersionInBatching);
  1417. if (requestVersion != Util.DataServiceVersion1 && requestVersion != Util.DataServiceVersionEmpty)
  1418. {
  1419. writer.WriteLine("{0}: {1}{2}", XmlConstants.HttpDataServiceVersion, requestVersion, Util.VersionSuffix);
  1420. }
  1421. }
  1422. private static void WriteOperationResponseHeaders(StreamWriter writer, int statusCode)
  1423. {
  1424. writer.WriteLine("{0}: {1}", XmlConstants.HttpContentType, XmlConstants.MimeApplicationHttp);
  1425. writer.WriteLine("{0}: {1}", XmlConstants.HttpContentTransferEncoding, XmlConstants.BatchRequestContentTransferEncoding);
  1426. writer.WriteLine();
  1427. writer.WriteLine("{0} {1} {2}", XmlConstants.HttpVersionInBatching, statusCode, (HttpStatusCode)statusCode);
  1428. }
  1429. private bool DetachResource(EntityDescriptor resource)
  1430. {
  1431. foreach (LinkDescriptor end in this.bindings.Values.Where(resource.IsRelatedEntity).ToList())
  1432. {
  1433. this.DetachExistingLink(
  1434. end,
  1435. end.Target == resource.Entity && resource.State == EntityStates.Added);
  1436. }
  1437. resource.ChangeOrder = UInt32.MaxValue;
  1438. resource.State = EntityStates.Detached;
  1439. bool flag = this.entityDescriptors.Remove(resource.Entity);
  1440. Debug.Assert(flag, "should have removed existing entity");
  1441. this.DetachResourceIdentity(resource);
  1442. return true;
  1443. }
  1444. private void DetachResourceIdentity(EntityDescriptor resource)
  1445. {
  1446. EntityDescriptor existing = null;
  1447. if ((null != resource.Identity) &&
  1448. this.identityToDescriptor.TryGetValue(resource.Identity, out existing) &&
  1449. Object.ReferenceEquals(existing, resource))
  1450. {
  1451. bool removed = this.identityToDescriptor.Remove(resource.Identity);
  1452. Debug.Assert(removed, "should have removed existing identity");
  1453. }
  1454. }
  1455. private HttpWebRequest CreateRequest(LinkDescriptor binding)
  1456. {
  1457. Debug.Assert(null != binding, "null binding");
  1458. if (binding.ContentGeneratedForSave)
  1459. {
  1460. return null;
  1461. }
  1462. EntityDescriptor sourceResource = this.entityDescriptors[binding.Source];
  1463. EntityDescriptor targetResource = (null != binding.Target) ? this.entityDescriptors[binding.Target] : null;
  1464. if (null == sourceResource.Identity)
  1465. {
  1466. Debug.Assert(!binding.ContentGeneratedForSave, "already saved link");
  1467. binding.ContentGeneratedForSave = true;
  1468. Debug.Assert(EntityStates.Added == sourceResource.State, "expected added state");
  1469. throw Error.InvalidOperation(Strings.Context_LinkResourceInsertFailure, sourceResource.SaveError);
  1470. }
  1471. else if ((null != targetResource) && (null == targetResource.Identity))
  1472. {
  1473. Debug.Assert(!binding.ContentGeneratedForSave, "already saved link");
  1474. binding.ContentGeneratedForSave = true;
  1475. Debug.Assert(EntityStates.Added == targetResource.State, "expected added state");
  1476. throw Error.InvalidOperation(Strings.Context_LinkResourceInsertFailure, targetResource.SaveError);
  1477. }
  1478. Debug.Assert(null != sourceResource.Identity, "missing sourceResource.Identity");
  1479. return this.CreateRequest(this.CreateRequestUri(sourceResource, binding), GetLinkHttpMethod(binding), false, XmlConstants.MimeApplicationXml, Util.DataServiceVersion1, false);
  1480. }
  1481. private Uri CreateRequestUri(EntityDescriptor sourceResource, LinkDescriptor binding)
  1482. {
  1483. Uri requestUri = Util.CreateUri(sourceResource.GetResourceUri(this.baseUriWithSlash, false ), this.CreateRequestRelativeUri(binding));
  1484. return requestUri;
  1485. }
  1486. private Uri CreateRequestRelativeUri(LinkDescriptor binding)
  1487. {
  1488. Uri relative;
  1489. bool collection = (null != ClientType.Create(binding.Source.GetType()).GetProperty(binding.SourceProperty, false).CollectionType);
  1490. if (collection && (EntityStates.Added != binding.State))
  1491. {
  1492. Debug.Assert(null != binding.Target, "null target in collection");
  1493. EntityDescriptor targetResource = this.entityDescriptors[binding.Target];
  1494. Uri navigationPropertyUri = this.BaseUriWithSlash.MakeRelativeUri(DataServiceContext.GenerateEditLinkUri(this.BaseUriWithSlash, binding.SourceProperty, targetResource.Entity));
  1495. relative = Util.CreateUri(XmlConstants.UriLinkSegment + "/" + navigationPropertyUri.OriginalString, UriKind.Relative);
  1496. }
  1497. else
  1498. {
  1499. relative = Util.CreateUri(XmlConstants.UriLinkSegment + "/" + binding.SourceProperty, UriKind.Relative);
  1500. }
  1501. Debug.Assert(!relative.IsAbsoluteUri, "should be relative uri");
  1502. return relative;
  1503. }
  1504. private void CreateRequestBatch(LinkDescriptor binding, StreamWriter text)
  1505. {
  1506. EntityDescriptor sourceResource = this.entityDescriptors[binding.Source];
  1507. string requestString;
  1508. if (null != sourceResource.Identity)
  1509. {
  1510. requestString = this.CreateRequestUri(sourceResource, binding).AbsoluteUri;
  1511. }
  1512. else
  1513. {
  1514. Uri relative = this.CreateRequestRelativeUri(binding);
  1515. requestString = "$" + sourceResource.ChangeOrder.ToString(CultureInfo.InvariantCulture) + "/" + relative.OriginalString;
  1516. }
  1517. WriteOperationRequestHeaders(text, GetLinkHttpMethod(binding), requestString, Util.DataServiceVersion1);
  1518. text.WriteLine("{0}: {1}", XmlConstants.HttpContentID, binding.ChangeOrder);
  1519. if ((EntityStates.Added == binding.State) || (EntityStates.Modified == binding.State && (null != binding.Target)))
  1520. {
  1521. text.WriteLine("{0}: {1}", XmlConstants.HttpContentType, XmlConstants.MimeApplicationXml);
  1522. }
  1523. }
  1524. private MemoryStream CreateRequestData(LinkDescriptor binding, bool newline)
  1525. {
  1526. Debug.Assert(
  1527. (binding.State == EntityStates.Added) ||
  1528. (binding.State == EntityStates.Modified && null != binding.Target),
  1529. "This method must be called only when a binding is added or put");
  1530. MemoryStream stream = new MemoryStream();
  1531. XmlWriter writer = XmlUtil.CreateXmlWriterAndWriteProcessingInstruction(stream, HttpProcessUtility.EncodingUtf8NoPreamble);
  1532. EntityDescriptor targetResource = this.entityDescriptors[binding.Target];
  1533. #region <uri xmlns="metadata">
  1534. writer.WriteStartElement(XmlConstants.UriElementName, XmlConstants.DataWebMetadataNamespace);
  1535. string id;
  1536. if (null != targetResource.Identity)
  1537. {
  1538. id = targetResource.GetResourceUri(this.baseUriWithSlash, false ).OriginalString;
  1539. }
  1540. else
  1541. {
  1542. id = "$" + targetResource.ChangeOrder.ToString(CultureInfo.InvariantCulture);
  1543. }
  1544. writer.WriteValue(id);
  1545. writer.WriteEndElement();
  1546. #endregion
  1547. writer.Flush();
  1548. if (newline)
  1549. {
  1550. for (int kk = 0; kk < NewLine.Length; ++kk)
  1551. {
  1552. stream.WriteByte((byte)NewLine[kk]);
  1553. }
  1554. }
  1555. stream.Position = 0;
  1556. return stream;
  1557. }
  1558. private HttpWebRequest CreateRequest(EntityDescriptor box, EntityStates state, bool replaceOnUpdate)
  1559. {
  1560. Debug.Assert(null != box && ((EntityStates.Added == state) || (EntityStates.Modified == state) || (EntityStates.Deleted == state)), "unexpected entity ResourceState");
  1561. string httpMethod = GetEntityHttpMethod(state, replaceOnUpdate);
  1562. Uri requestUri = box.GetResourceUri(this.baseUriWithSlash, false );
  1563. Version requestVersion = ClientType.Create(box.Entity.GetType()).EpmIsV1Compatible ? Util.DataServiceVersion1 : Util.DataServiceVersion2;
  1564. HttpWebRequest request = this.CreateRequest(requestUri, httpMethod, false, XmlConstants.MimeApplicationAtom, requestVersion, false);
  1565. if ((null != box.ETag) && ((EntityStates.Deleted == state) || (EntityStates.Modified == state)))
  1566. {
  1567. request.Headers.Set(HttpRequestHeader.IfMatch, box.ETag);
  1568. }
  1569. return request;
  1570. }
  1571. private void CreateRequestBatch(EntityDescriptor box, StreamWriter text, bool replaceOnUpdate)
  1572. {
  1573. Debug.Assert(null != box, "null box");
  1574. Debug.Assert(null != text, "null text");
  1575. Debug.Assert(box.State == EntityStates.Added || box.State == EntityStates.Deleted || box.State == EntityStates.Modified, "the entity must be in one of the 3 possible states");
  1576. Uri requestUri = box.GetResourceUri(this.baseUriWithSlash, false);
  1577. Debug.Assert(null != requestUri, "request uri is null");
  1578. Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri");
  1579. Version requestVersion = ClientType.Create(box.Entity.GetType()).EpmIsV1Compatible ? Util.DataServiceVersion1 : Util.DataServiceVersion2;
  1580. WriteOperationRequestHeaders(text, GetEntityHttpMethod(box.State, replaceOnUpdate), requestUri.AbsoluteUri, requestVersion);
  1581. text.WriteLine("{0}: {1}", XmlConstants.HttpContentID, box.ChangeOrder);
  1582. if (EntityStates.Deleted != box.State)
  1583. {
  1584. text.WriteLine("{0}: {1}", XmlConstants.HttpContentType, XmlConstants.LinkMimeTypeEntry);
  1585. }
  1586. if ((null != box.ETag) && (EntityStates.Deleted == box.State || EntityStates.Modified == box.State))
  1587. {
  1588. text.WriteLine("{0}: {1}", XmlConstants.HttpRequestIfMatch, box.ETag);
  1589. }
  1590. }
  1591. private Stream CreateRequestData(EntityDescriptor box, bool newline)
  1592. {
  1593. Debug.Assert(null != box, "null box");
  1594. MemoryStream stream = null;
  1595. switch (box.State)
  1596. {
  1597. case EntityStates.Deleted:
  1598. break;
  1599. case EntityStates.Modified:
  1600. case EntityStates.Added:
  1601. stream = new MemoryStream();
  1602. break;
  1603. default:
  1604. Error.ThrowInternalError(InternalError.UnvalidatedEntityState);
  1605. break;
  1606. }
  1607. if (null != stream)
  1608. {
  1609. XmlWriter writer;
  1610. XDocument node = null;
  1611. if (this.WritingEntity != null)
  1612. {
  1613. node = new XDocument();
  1614. writer = node.CreateWriter();
  1615. }
  1616. else
  1617. {
  1618. writer = XmlUtil.CreateXmlWriterAndWriteProcessingInstruction(stream, HttpProcessUtility.EncodingUtf8NoPreamble);
  1619. }
  1620. ClientType type = ClientType.Create(box.Entity.GetType());
  1621. string typeName = this.GetServerTypeName(box);
  1622. #region <entry xmlns="Atom" xmlns:d="DataWeb", xmlns:m="DataWebMetadata">
  1623. writer.WriteStartElement(XmlConstants.AtomEntryElementName, XmlConstants.AtomNamespace);
  1624. writer.WriteAttributeString(XmlConstants.DataWebNamespacePrefix, XmlConstants.XmlNamespacesNamespace, this.DataNamespace);
  1625. writer.WriteAttributeString(XmlConstants.DataWebMetadataNamespacePrefix, XmlConstants.XmlNamespacesNamespace, XmlConstants.DataWebMetadataNamespace);
  1626. if (!String.IsNullOrEmpty(typeName))
  1627. {
  1628. writer.WriteStartElement(XmlConstants.AtomCategoryElementName, XmlConstants.AtomNamespace);
  1629. writer.WriteAttributeString(XmlConstants.AtomCategorySchemeAttributeName, this.typeScheme.OriginalString);
  1630. writer.WriteAttributeString(XmlConstants.AtomCategoryTermAttributeName, typeName);
  1631. writer.WriteEndElement();
  1632. }
  1633. if (type.HasEntityPropertyMappings)
  1634. {
  1635. using (EpmSyndicationContentSerializer s = new EpmSyndicationContentSerializer(type.EpmTargetTree, box.Entity, writer))
  1636. {
  1637. s.Serialize();
  1638. }
  1639. }
  1640. else
  1641. {
  1642. writer.WriteElementString(XmlConstants.AtomTitleElementName, XmlConstants.AtomNamespace, String.Empty);
  1643. writer.WriteStartElement(XmlConstants.AtomAuthorElementName, XmlConstants.AtomNamespace);
  1644. writer.WriteElementString(XmlConstants.AtomNameElementName, XmlConstants.AtomNamespace, String.Empty);
  1645. writer.WriteEndElement();
  1646. writer.WriteElementString(XmlConstants.AtomUpdatedElementName, XmlConstants.AtomNamespace, XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.RoundtripKind));
  1647. }
  1648. if (EntityStates.Modified == box.State)
  1649. {
  1650. writer.WriteElementString(XmlConstants.AtomIdElementName, Util.DereferenceIdentity(box.Identity));
  1651. }
  1652. else
  1653. {
  1654. writer.WriteElementString(XmlConstants.AtomIdElementName, XmlConstants.AtomNamespace, String.Empty);
  1655. }
  1656. #region <link href=�%EditLink%� rel=�%DataWebRelatedNamespace%%AssociationName%� type=�application/atom+xml;feed� />
  1657. if (EntityStates.Added == box.State)
  1658. {
  1659. this.CreateRequestDataLinks(box, writer);
  1660. }
  1661. #endregion
  1662. #region <content type="application/xml"><m:Properites> or <m:Properties>
  1663. if (!type.IsMediaLinkEntry && !box.IsMediaLinkEntry)
  1664. {
  1665. writer.WriteStartElement(XmlConstants.AtomContentElementName, XmlConstants.AtomNamespace);
  1666. writer.WriteAttributeString(XmlConstants.AtomTypeAttributeName, XmlConstants.MimeApplicationXml);
  1667. }
  1668. writer.WriteStartElement(XmlConstants.AtomPropertiesElementName, XmlConstants.DataWebMetadataNamespace);
  1669. bool propertiesWritten;
  1670. this.WriteContentProperties(writer, type, box.Entity, type.HasEntityPropertyMappings ? type.EpmSourceTree.Root : null, out propertiesWritten);
  1671. writer.WriteEndElement();
  1672. if (!type.IsMediaLinkEntry && !box.IsMediaLinkEntry)
  1673. {
  1674. writer.WriteEndElement();
  1675. }
  1676. if (type.HasEntityPropertyMappings)
  1677. {
  1678. using (EpmCustomContentSerializer s = new EpmCustomContentSerializer(type.EpmTargetTree, box.Entity, writer))
  1679. {
  1680. s.Serialize();
  1681. }
  1682. }
  1683. writer.WriteEndElement();
  1684. writer.Flush();
  1685. writer.Close();
  1686. #endregion
  1687. #endregion
  1688. if (this.WritingEntity != null)
  1689. {
  1690. ReadingWritingEntityEventArgs args = new ReadingWritingEntityEventArgs(box.Entity, node.Root);
  1691. this.WritingEntity(this, args);
  1692. XmlWriterSettings settings = XmlUtil.CreateXmlWriterSettings(HttpProcessUtility.EncodingUtf8NoPreamble);
  1693. settings.ConformanceLevel = ConformanceLevel.Auto;
  1694. using (XmlWriter streamWriter = XmlWriter.Create(stream, settings))
  1695. {
  1696. node.Save(streamWriter);
  1697. }
  1698. }
  1699. if (newline)
  1700. {
  1701. for (int kk = 0; kk < NewLine.Length; ++kk)
  1702. {
  1703. stream.WriteByte((byte)NewLine[kk]);
  1704. }
  1705. }
  1706. stream.Position = 0;
  1707. }
  1708. return stream;
  1709. }
  1710. private void CreateRequestDataLinks(EntityDescriptor box, XmlWriter writer)
  1711. {
  1712. Debug.Assert(EntityStates.Added == box.State, "entity not added state");
  1713. ClientType clientType = null;
  1714. foreach (LinkDescriptor end in this.RelatedLinks(box))
  1715. {
  1716. Debug.Assert(!end.ContentGeneratedForSave, "already saved link");
  1717. end.ContentGeneratedForSave = true;
  1718. if (null == clientType)
  1719. {
  1720. clientType = ClientType.Create(box.Entity.GetType());
  1721. }
  1722. string typeAttributeValue;
  1723. if (null != clientType.GetProperty(end.SourceProperty, false).CollectionType)
  1724. {
  1725. typeAttributeValue = XmlConstants.LinkMimeTypeFeed;
  1726. }
  1727. else
  1728. {
  1729. typeAttributeValue = XmlConstants.LinkMimeTypeEntry;
  1730. }
  1731. Debug.Assert(null != end.Target, "null is DELETE");
  1732. String targetEditLink = this.entityDescriptors[end.Target].EditLink.ToString();
  1733. writer.WriteStartElement(XmlConstants.AtomLinkElementName, XmlConstants.AtomNamespace);
  1734. writer.WriteAttributeString(XmlConstants.AtomHRefAttributeName, targetEditLink);
  1735. writer.WriteAttributeString(XmlConstants.AtomLinkRelationAttributeName, XmlConstants.DataWebRelatedNamespace + end.SourceProperty);
  1736. writer.WriteAttributeString(XmlConstants.AtomTypeAttributeName, typeAttributeValue);
  1737. writer.WriteEndElement();
  1738. }
  1739. }
  1740. private void HandleResponseDelete(Descriptor entry)
  1741. {
  1742. if (EntityStates.Deleted != entry.State)
  1743. {
  1744. Error.ThrowBatchUnexpectedContent(InternalError.EntityNotDeleted);
  1745. }
  1746. if (entry.IsResource)
  1747. {
  1748. EntityDescriptor resource = (EntityDescriptor)entry;
  1749. this.DetachResource(resource);
  1750. }
  1751. else
  1752. {
  1753. this.DetachExistingLink((LinkDescriptor)entry, false);
  1754. }
  1755. }
  1756. private void HandleResponsePost(EntityDescriptor entry, MaterializeAtom materializer, Uri editLink, string etag)
  1757. {
  1758. Debug.Assert(editLink != null, "location header must be specified in POST responses.");
  1759. if (EntityStates.Added != entry.State && StreamStates.Added != entry.StreamState)
  1760. {
  1761. Error.ThrowBatchUnexpectedContent(InternalError.EntityNotAddedState);
  1762. }
  1763. if (materializer == null)
  1764. {
  1765. String identity = Util.ReferenceIdentity(editLink.ToString());
  1766. this.AttachIdentity(identity, null , editLink, entry.Entity, etag);
  1767. }
  1768. else
  1769. {
  1770. materializer.SetInsertingObject(entry.Entity);
  1771. foreach (object x in materializer)
  1772. {
  1773. Debug.Assert(null != entry.Identity, "updated inserted should always gain an identity");
  1774. Debug.Assert(x == entry.Entity, "x == box.Entity, should have same object generated by response");
  1775. Debug.Assert(EntityStates.Unchanged == entry.State, "should have moved out of insert");
  1776. Debug.Assert((null != this.identityToDescriptor) && this.identityToDescriptor.ContainsKey(entry.Identity), "should have identity tracked");
  1777. if (entry.EditLink == null)
  1778. {
  1779. entry.EditLink = editLink;
  1780. }
  1781. if (entry.ETag == null)
  1782. {
  1783. entry.ETag = etag;
  1784. }
  1785. }
  1786. }
  1787. foreach (LinkDescriptor end in this.RelatedLinks(entry))
  1788. {
  1789. Debug.Assert(0 != end.SaveResultWasProcessed, "link should have been saved with the enty");
  1790. if (IncludeLinkState(end.SaveResultWasProcessed) || end.SaveResultWasProcessed == EntityStates.Added)
  1791. {
  1792. HandleResponsePost(end);
  1793. }
  1794. }
  1795. }
  1796. private int SaveResultProcessed(Descriptor entry)
  1797. {
  1798. entry.SaveResultWasProcessed = entry.State;
  1799. int count = 0;
  1800. if (entry.IsResource && (EntityStates.Added == entry.State))
  1801. {
  1802. foreach (LinkDescriptor end in this.RelatedLinks((EntityDescriptor)entry))
  1803. {
  1804. Debug.Assert(end.ContentGeneratedForSave, "link should have been saved with the enty");
  1805. if (end.ContentGeneratedForSave)
  1806. {
  1807. Debug.Assert(0 == end.SaveResultWasProcessed, "this link already had a result");
  1808. end.SaveResultWasProcessed = end.State;
  1809. count++;
  1810. }
  1811. }
  1812. }
  1813. return count;
  1814. }
  1815. private IEnumerable<LinkDescriptor> RelatedLinks(EntityDescriptor box)
  1816. {
  1817. foreach (LinkDescriptor end in this.bindings.Values)
  1818. {
  1819. if (end.Source == box.Entity)
  1820. {
  1821. if (null != end.Target)
  1822. {
  1823. EntityDescriptor target = this.entityDescriptors[end.Target];
  1824. if (IncludeLinkState(target.SaveResultWasProcessed) || ((0 == target.SaveResultWasProcessed) && IncludeLinkState(target.State)) ||
  1825. ((null != target.Identity) && (target.ChangeOrder < box.ChangeOrder) &&
  1826. ((0 == target.SaveResultWasProcessed && EntityStates.Added == target.State) ||
  1827. (EntityStates.Added == target.SaveResultWasProcessed))))
  1828. {
  1829. Debug.Assert(box.ChangeOrder < end.ChangeOrder, "saving is out of order");
  1830. yield return end;
  1831. }
  1832. }
  1833. }
  1834. }
  1835. }
  1836. private LoadPropertyResult CreateLoadPropertyRequest(object entity, string propertyName, AsyncCallback callback, object state, Uri requestUri, DataServiceQueryContinuation continuation)
  1837. {
  1838. Debug.Assert(continuation == null || requestUri == null, "continuation == null || requestUri == null -- only one or the either (or neither) may be passed in");
  1839. EntityDescriptor box = this.EnsureContained(entity, "entity");
  1840. Util.CheckArgumentNotEmpty(propertyName, "propertyName");
  1841. ClientType type = ClientType.Create(entity.GetType());
  1842. Debug.Assert(type.IsEntityType, "must be entity type to be contained");
  1843. if (EntityStates.Added == box.State)
  1844. {
  1845. throw Error.InvalidOperation(Strings.Context_NoLoadWithInsertEnd);
  1846. }
  1847. ClientType.ClientProperty property = type.GetProperty(propertyName, false);
  1848. Debug.Assert(null != property, "should have thrown if propertyName didn't exist");
  1849. ProjectionPlan plan;
  1850. if (continuation == null)
  1851. {
  1852. plan = null;
  1853. }
  1854. else
  1855. {
  1856. plan = continuation.Plan;
  1857. requestUri = continuation.NextLinkUri;
  1858. }
  1859. bool mediaLink = (type.MediaDataMember != null && propertyName == type.MediaDataMember.PropertyName);
  1860. Version requestVersion;
  1861. if (requestUri == null)
  1862. {
  1863. Uri relativeUri;
  1864. if (mediaLink)
  1865. {
  1866. relativeUri = Util.CreateUri(XmlConstants.UriValueSegment, UriKind.Relative);
  1867. }
  1868. else
  1869. {
  1870. relativeUri = Util.CreateUri(propertyName + (null != property.CollectionType ? "()" : String.Empty), UriKind.Relative);
  1871. }
  1872. requestUri = Util.CreateUri(box.GetResourceUri(this.baseUriWithSlash, true ), relativeUri);
  1873. requestVersion = Util.DataServiceVersion1;
  1874. }
  1875. else
  1876. {
  1877. requestVersion = Util.DataServiceVersionEmpty;
  1878. }
  1879. HttpWebRequest request = this.CreateRequest(requestUri, XmlConstants.HttpMethodGet, mediaLink, null, requestVersion, false);
  1880. DataServiceRequest dataServiceRequest = DataServiceRequest.GetInstance(property.PropertyType, requestUri);
  1881. return new LoadPropertyResult(entity, propertyName, this, request, callback, state, dataServiceRequest, plan);
  1882. }
  1883. private void WriteContentProperties(XmlWriter writer, ClientType type, object resource, EpmSourcePathSegment currentSegment, out bool propertiesWritten)
  1884. {
  1885. #region <d:property>value</property>
  1886. propertiesWritten = false;
  1887. foreach (ClientType.ClientProperty property in type.Properties)
  1888. {
  1889. if (property == type.MediaDataMember ||
  1890. (type.MediaDataMember != null &&
  1891. type.MediaDataMember.MimeTypeProperty == property))
  1892. {
  1893. continue;
  1894. }
  1895. object propertyValue = property.GetValue(resource);
  1896. EpmSourcePathSegment matchedSegment = currentSegment != null ? currentSegment.SubProperties.SingleOrDefault(s => s.PropertyName == property.PropertyName) : null;
  1897. if (property.IsKnownType)
  1898. {
  1899. if (propertyValue == null || matchedSegment == null || matchedSegment.EpmInfo.Attribute.KeepInContent)
  1900. {
  1901. WriteContentProperty(writer, this.DataNamespace, property, propertyValue);
  1902. propertiesWritten = true;
  1903. }
  1904. }
  1905. #if ASTORIA_OPEN_OBJECT
  1906. else if (property.OpenObjectProperty)
  1907. {
  1908. foreach (KeyValuePair<string, object> pair in (IDictionary<string, object>)propertyValue)
  1909. {
  1910. if ((null == pair.Value) || ClientConvert.IsKnownType(pair.Value.GetType()))
  1911. {
  1912. Type valueType = pair.Value != null ? pair.Value.GetType() : typeof(string);
  1913. ClientType.ClientProperty openProperty = new ClientType.ClientProperty(null, valueType, false, true);
  1914. WriteContentProperty(writer, this.DataNamespace, openProperty, pair.Value);
  1915. propertiesWritten = true;
  1916. }
  1917. }
  1918. }
  1919. #endif
  1920. else if (null == property.CollectionType)
  1921. {
  1922. ClientType nested = ClientType.Create(property.PropertyType);
  1923. if (!nested.IsEntityType)
  1924. {
  1925. #region complex type
  1926. XElement complexProperty = new XElement(((XNamespace)this.DataNamespace) + property.PropertyName);
  1927. bool shouldWriteComplexProperty = false;
  1928. string typeName = this.ResolveNameFromType(nested.ElementType);
  1929. if (!String.IsNullOrEmpty(typeName))
  1930. {
  1931. complexProperty.Add(new XAttribute(((XNamespace)XmlConstants.DataWebMetadataNamespace) + XmlConstants.AtomTypeAttributeName, typeName));
  1932. }
  1933. if (null == propertyValue)
  1934. {
  1935. complexProperty.Add(new XAttribute(((XNamespace)XmlConstants.DataWebMetadataNamespace) + XmlConstants.AtomNullAttributeName, XmlConstants.XmlTrueLiteral));
  1936. shouldWriteComplexProperty = true;
  1937. }
  1938. else
  1939. {
  1940. using (XmlWriter complexPropertyWriter = complexProperty.CreateWriter())
  1941. {
  1942. this.WriteContentProperties(complexPropertyWriter, nested, propertyValue, matchedSegment, out shouldWriteComplexProperty);
  1943. }
  1944. }
  1945. if (shouldWriteComplexProperty)
  1946. {
  1947. complexProperty.WriteTo(writer);
  1948. propertiesWritten = true;
  1949. }
  1950. #endregion
  1951. }
  1952. }
  1953. }
  1954. #endregion
  1955. }
  1956. private void DetachExistingLink(LinkDescriptor existingLink, bool targetDelete)
  1957. {
  1958. if (existingLink.Target != null)
  1959. {
  1960. EntityDescriptor targetResource = this.entityDescriptors[existingLink.Target];
  1961. if (targetResource.IsDeepInsert && !targetDelete)
  1962. {
  1963. EntityDescriptor parentOfTarget = targetResource.ParentForInsert;
  1964. if (Object.ReferenceEquals(targetResource.ParentEntity, existingLink.Source) &&
  1965. (parentOfTarget.State != EntityStates.Deleted ||
  1966. parentOfTarget.State != EntityStates.Detached))
  1967. {
  1968. throw new InvalidOperationException(Strings.Context_ChildResourceExists);
  1969. }
  1970. }
  1971. }
  1972. if (this.bindings.Remove(existingLink))
  1973. {
  1974. existingLink.State = EntityStates.Detached;
  1975. }
  1976. }
  1977. private LinkDescriptor DetachReferenceLink(object source, string sourceProperty, object target, MergeOption linkMerge)
  1978. {
  1979. LinkDescriptor existing = this.GetLinks(source, sourceProperty).FirstOrDefault();
  1980. if (null != existing)
  1981. {
  1982. if ((target == existing.Target) ||
  1983. (MergeOption.AppendOnly == linkMerge) ||
  1984. (MergeOption.PreserveChanges == linkMerge && EntityStates.Modified == existing.State))
  1985. {
  1986. return existing;
  1987. }
  1988. this.DetachExistingLink(existing, false);
  1989. Debug.Assert(!this.bindings.Values.Any(o => (o.Source == source) && (o.SourceProperty == sourceProperty)), "only expecting one");
  1990. }
  1991. return null;
  1992. }
  1993. private EntityDescriptor EnsureContained(object resource, string parameterName)
  1994. {
  1995. Util.CheckArgumentNull(resource, parameterName);
  1996. EntityDescriptor box = null;
  1997. if (!this.entityDescriptors.TryGetValue(resource, out box))
  1998. {
  1999. throw Error.InvalidOperation(Strings.Context_EntityNotContained);
  2000. }
  2001. return box;
  2002. }
  2003. private bool EnsureRelatable(object source, string sourceProperty, object target, EntityStates state)
  2004. {
  2005. EntityDescriptor sourceResource = this.EnsureContained(source, "source");
  2006. EntityDescriptor targetResource = null;
  2007. if ((null != target) || ((EntityStates.Modified != state) && (EntityStates.Unchanged != state)))
  2008. {
  2009. targetResource = this.EnsureContained(target, "target");
  2010. }
  2011. Util.CheckArgumentNotEmpty(sourceProperty, "sourceProperty");
  2012. ClientType type = ClientType.Create(source.GetType());
  2013. Debug.Assert(type.IsEntityType, "should be enforced by just adding an object");
  2014. ClientType.ClientProperty property = type.GetProperty(sourceProperty, false);
  2015. if (property.IsKnownType)
  2016. {
  2017. throw Error.InvalidOperation(Strings.Context_RelationNotRefOrCollection);
  2018. }
  2019. if ((EntityStates.Unchanged == state) && (null == target) && (null != property.CollectionType))
  2020. {
  2021. targetResource = this.EnsureContained(target, "target");
  2022. }
  2023. if (((EntityStates.Added == state) || (EntityStates.Deleted == state)) && (null == property.CollectionType))
  2024. {
  2025. throw Error.InvalidOperation(Strings.Context_AddLinkCollectionOnly);
  2026. }
  2027. else if ((EntityStates.Modified == state) && (null != property.CollectionType))
  2028. {
  2029. throw Error.InvalidOperation(Strings.Context_SetLinkReferenceOnly);
  2030. }
  2031. type = ClientType.Create(property.CollectionType ?? property.PropertyType);
  2032. Debug.Assert(type.IsEntityType, "should be enforced by just adding an object");
  2033. if ((null != target) && !type.ElementType.IsInstanceOfType(target))
  2034. {
  2035. throw Error.Argument(Strings.Context_RelationNotRefOrCollection, "target");
  2036. }
  2037. if ((EntityStates.Added == state) || (EntityStates.Unchanged == state))
  2038. {
  2039. if ((sourceResource.State == EntityStates.Deleted) ||
  2040. ((targetResource != null) && (targetResource.State == EntityStates.Deleted)))
  2041. {
  2042. throw Error.InvalidOperation(Strings.Context_NoRelationWithDeleteEnd);
  2043. }
  2044. }
  2045. if ((EntityStates.Deleted == state) || (EntityStates.Unchanged == state))
  2046. {
  2047. if ((sourceResource.State == EntityStates.Added) ||
  2048. ((targetResource != null) && (targetResource.State == EntityStates.Added)))
  2049. {
  2050. if (EntityStates.Deleted == state)
  2051. {
  2052. return true;
  2053. }
  2054. throw Error.InvalidOperation(Strings.Context_NoRelationWithInsertEnd);
  2055. }
  2056. }
  2057. return false;
  2058. }
  2059. private void ValidateEntitySetName(ref string entitySetName)
  2060. {
  2061. Util.CheckArgumentNotEmpty(entitySetName, "entitySetName");
  2062. entitySetName = entitySetName.Trim(Util.ForwardSlash);
  2063. Util.CheckArgumentNotEmpty(entitySetName, "entitySetName");
  2064. Uri tmp = Util.CreateUri(entitySetName, UriKind.RelativeOrAbsolute);
  2065. if (tmp.IsAbsoluteUri ||
  2066. !String.IsNullOrEmpty(Util.CreateUri(this.baseUriWithSlash, tmp)
  2067. .GetComponents(UriComponents.Query | UriComponents.Fragment, UriFormat.SafeUnescaped)))
  2068. {
  2069. throw Error.Argument(Strings.Context_EntitySetName, "entitySetName");
  2070. }
  2071. }
  2072. private void EnsureIdentityToResource()
  2073. {
  2074. if (null == this.identityToDescriptor)
  2075. {
  2076. System.Threading.Interlocked.CompareExchange(ref this.identityToDescriptor, new Dictionary<String, EntityDescriptor>(EqualityComparer<String>.Default), null);
  2077. }
  2078. }
  2079. private void IncrementChange(Descriptor descriptor)
  2080. {
  2081. descriptor.ChangeOrder = ++this.nextChange;
  2082. }
  2083. private GetReadStreamResult CreateGetReadStreamResult(
  2084. object entity,
  2085. DataServiceRequestArgs args,
  2086. AsyncCallback callback,
  2087. object state)
  2088. {
  2089. EntityDescriptor box = this.EnsureContained(entity, "entity");
  2090. Util.CheckArgumentNull(args, "args");
  2091. Uri requestUri = box.GetMediaResourceUri(this.baseUriWithSlash);
  2092. if (requestUri == null)
  2093. {
  2094. throw new ArgumentException(Strings.Context_EntityNotMediaLinkEntry, "entity");
  2095. }
  2096. #if ASTORIA_LIGHT
  2097. HttpWebRequest request = this.CreateRequest(requestUri, XmlConstants.HttpMethodGet, true, null, null, false, HttpStack.ClientHttp);
  2098. #else
  2099. HttpWebRequest request = this.CreateRequest(requestUri, XmlConstants.HttpMethodGet, true, null, null, false);
  2100. #endif
  2101. WebUtil.ApplyHeadersToRequest(args.Headers, request, false);
  2102. return new GetReadStreamResult(this, "GetReadStream", request, callback, state);
  2103. }
  2104. internal class DataServiceSaveStream
  2105. {
  2106. private readonly Stream stream;
  2107. private readonly bool close;
  2108. private readonly DataServiceRequestArgs args;
  2109. internal DataServiceSaveStream(Stream stream, bool close, DataServiceRequestArgs args)
  2110. {
  2111. Debug.Assert(stream != null, "stream must not be null.");
  2112. this.stream = stream;
  2113. this.close = close;
  2114. this.args = args;
  2115. }
  2116. internal Stream Stream
  2117. {
  2118. get
  2119. {
  2120. return this.stream;
  2121. }
  2122. }
  2123. internal DataServiceRequestArgs Args
  2124. {
  2125. get { return this.args; }
  2126. }
  2127. internal void Close()
  2128. {
  2129. if (this.stream != null && this.close)
  2130. {
  2131. this.stream.Close();
  2132. }
  2133. }
  2134. }
  2135. private class LoadPropertyResult : QueryResult
  2136. {
  2137. #region Private fields.
  2138. private readonly object entity;
  2139. private readonly ProjectionPlan plan;
  2140. private readonly string propertyName;
  2141. #endregion Private fields.
  2142. internal LoadPropertyResult(object entity, string propertyName, DataServiceContext context, HttpWebRequest request, AsyncCallback callback, object state, DataServiceRequest dataServiceRequest, ProjectionPlan plan)
  2143. : base(context, "LoadProperty", dataServiceRequest, request, callback, state)
  2144. {
  2145. this.entity = entity;
  2146. this.propertyName = propertyName;
  2147. this.plan = plan;
  2148. }
  2149. internal QueryOperationResponse LoadProperty()
  2150. {
  2151. MaterializeAtom results = null;
  2152. DataServiceContext context = (DataServiceContext)this.Source;
  2153. ClientType type = ClientType.Create(this.entity.GetType());
  2154. Debug.Assert(type.IsEntityType, "must be entity type to be contained");
  2155. EntityDescriptor box = context.EnsureContained(this.entity, "entity");
  2156. if (EntityStates.Added == box.State)
  2157. {
  2158. throw Error.InvalidOperation(Strings.Context_NoLoadWithInsertEnd);
  2159. }
  2160. ClientType.ClientProperty property = type.GetProperty(this.propertyName, false);
  2161. Type elementType = property.CollectionType ?? property.NullablePropertyType;
  2162. try
  2163. {
  2164. if (type.MediaDataMember == property)
  2165. {
  2166. results = this.ReadPropertyFromRawData(property);
  2167. }
  2168. else
  2169. {
  2170. results = this.ReadPropertyFromAtom(box, property);
  2171. }
  2172. return this.GetResponseWithType(results, elementType);
  2173. }
  2174. catch (InvalidOperationException ex)
  2175. {
  2176. QueryOperationResponse response = this.GetResponseWithType(results, elementType);
  2177. if (response != null)
  2178. {
  2179. response.Error = ex;
  2180. throw new DataServiceQueryException(Strings.DataServiceException_GeneralError, ex, response);
  2181. }
  2182. throw;
  2183. }
  2184. }
  2185. private static byte[] ReadByteArrayWithContentLength(Stream responseStream, int totalLength)
  2186. {
  2187. byte[] buffer = new byte[totalLength];
  2188. int read = 0;
  2189. while (read < totalLength)
  2190. {
  2191. int r = responseStream.Read(buffer, read, totalLength - read);
  2192. if (r <= 0)
  2193. {
  2194. throw Error.InvalidOperation(Strings.Context_UnexpectedZeroRawRead);
  2195. }
  2196. read += r;
  2197. }
  2198. return buffer;
  2199. }
  2200. private static byte[] ReadByteArrayChunked(Stream responseStream)
  2201. {
  2202. byte[] completeBuffer = null;
  2203. using (MemoryStream m = new MemoryStream())
  2204. {
  2205. byte[] buffer = new byte[4096];
  2206. int numRead = 0;
  2207. int totalRead = 0;
  2208. while (true)
  2209. {
  2210. numRead = responseStream.Read(buffer, 0, buffer.Length);
  2211. if (numRead <= 0)
  2212. {
  2213. break;
  2214. }
  2215. m.Write(buffer, 0, numRead);
  2216. totalRead += numRead;
  2217. }
  2218. completeBuffer = new byte[totalRead];
  2219. m.Position = 0;
  2220. numRead = m.Read(completeBuffer, 0, completeBuffer.Length);
  2221. }
  2222. return completeBuffer;
  2223. }
  2224. private MaterializeAtom ReadPropertyFromAtom(EntityDescriptor box, ClientType.ClientProperty property)
  2225. {
  2226. DataServiceContext context = (DataServiceContext)this.Source;
  2227. bool merging = context.ApplyingChanges;
  2228. try
  2229. {
  2230. context.ApplyingChanges = true;
  2231. bool deletedState = (EntityStates.Deleted == box.State);
  2232. Type nestedType;
  2233. #if ASTORIA_OPEN_OBJECT
  2234. if (property.OpenObjectProperty)
  2235. {
  2236. nestedType = typeof(OpenObject);
  2237. }
  2238. else
  2239. #endif
  2240. {
  2241. nestedType = property.CollectionType ?? property.NullablePropertyType;
  2242. }
  2243. ClientType clientType = ClientType.Create(nestedType);
  2244. bool setNestedValue = false;
  2245. object collection = this.entity;
  2246. if (null != property.CollectionType)
  2247. { collection = property.GetValue(this.entity);
  2248. if (null == collection)
  2249. {
  2250. setNestedValue = true;
  2251. if (BindingEntityInfo.IsDataServiceCollection(property.PropertyType))
  2252. {
  2253. Debug.Assert(WebUtil.GetDataServiceCollectionOfT(nestedType) != null, "DataServiceCollection<> must be available here.");
  2254. collection = Activator.CreateInstance(
  2255. WebUtil.GetDataServiceCollectionOfT(nestedType),
  2256. null,
  2257. TrackingMode.None);
  2258. }
  2259. else
  2260. {
  2261. collection = Activator.CreateInstance(typeof(List<>).MakeGenericType(nestedType));
  2262. }
  2263. }
  2264. }
  2265. Type elementType = property.CollectionType ?? property.NullablePropertyType;
  2266. IList results = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
  2267. DataServiceQueryContinuation continuation = null;
  2268. using (MaterializeAtom materializer = this.GetMaterializer(context, this.plan))
  2269. {
  2270. Debug.Assert(materializer != null, "materializer != null -- otherwise GetMaterializer() returned null rather than empty");
  2271. int count = 0;
  2272. #if ASTORIA_OPEN_OBJECT
  2273. object openProperties = null;
  2274. #endif
  2275. foreach (object child in materializer)
  2276. {
  2277. results.Add(child);
  2278. count++;
  2279. #if ASTORIA_OPEN_OBJECT
  2280. property.SetValue(collection, child, this.propertyName, ref openProperties, true);
  2281. #else
  2282. property.SetValue(collection, child, this.propertyName, true);
  2283. #endif
  2284. if ((null != child) && (MergeOption.NoTracking != materializer.MergeOptionValue) && clientType.IsEntityType)
  2285. {
  2286. if (deletedState)
  2287. {
  2288. context.DeleteLink(this.entity, this.propertyName, child);
  2289. }
  2290. else
  2291. { context.AttachLink(this.entity, this.propertyName, child, materializer.MergeOptionValue);
  2292. }
  2293. }
  2294. }
  2295. continuation = materializer.GetContinuation(null);
  2296. Util.SetNextLinkForCollection(collection, continuation);
  2297. }
  2298. if (setNestedValue)
  2299. {
  2300. #if ASTORIA_OPEN_OBJECT
  2301. object openProperties = null;
  2302. property.SetValue(this.entity, collection, this.propertyName, ref openProperties, false);
  2303. #else
  2304. property.SetValue(this.entity, collection, this.propertyName, false);
  2305. #endif
  2306. }
  2307. return MaterializeAtom.CreateWrapper(results, continuation);
  2308. }
  2309. finally
  2310. {
  2311. context.ApplyingChanges = merging;
  2312. }
  2313. }
  2314. private MaterializeAtom ReadPropertyFromRawData(ClientType.ClientProperty property)
  2315. {
  2316. DataServiceContext context = (DataServiceContext)this.Source;
  2317. bool merging = context.ApplyingChanges;
  2318. try
  2319. {
  2320. context.ApplyingChanges = true;
  2321. #if ASTORIA_OPEN_OBJECT
  2322. object openProps = null;
  2323. #endif
  2324. string mimeType = null;
  2325. Encoding encoding = null;
  2326. Type elementType = property.CollectionType ?? property.NullablePropertyType;
  2327. IList results = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
  2328. HttpProcessUtility.ReadContentType(this.ContentType, out mimeType, out encoding);
  2329. using (Stream responseStream = this.GetResponseStream())
  2330. {
  2331. if (property.PropertyType == typeof(byte[]))
  2332. {
  2333. int total = checked((int)this.ContentLength);
  2334. byte[] buffer = null;
  2335. if (total >= 0)
  2336. {
  2337. buffer = LoadPropertyResult.ReadByteArrayWithContentLength(responseStream, total);
  2338. }
  2339. else
  2340. {
  2341. buffer = LoadPropertyResult.ReadByteArrayChunked(responseStream);
  2342. }
  2343. results.Add(buffer);
  2344. #if ASTORIA_OPEN_OBJECT
  2345. property.SetValue(this.entity, buffer, this.propertyName, ref openProps, false);
  2346. #else
  2347. property.SetValue(this.entity, buffer, this.propertyName, false);
  2348. #endif
  2349. }
  2350. else
  2351. {
  2352. StreamReader reader = new StreamReader(responseStream, encoding);
  2353. object convertedValue = property.PropertyType == typeof(string) ?
  2354. reader.ReadToEnd() :
  2355. ClientConvert.ChangeType(reader.ReadToEnd(), property.PropertyType);
  2356. results.Add(convertedValue);
  2357. #if ASTORIA_OPEN_OBJECT
  2358. property.SetValue(this.entity, convertedValue, this.propertyName, ref openProps, false);
  2359. #else
  2360. property.SetValue(this.entity, convertedValue, this.propertyName, false);
  2361. #endif
  2362. }
  2363. }
  2364. #if ASTORIA_OPEN_OBJECT
  2365. Debug.Assert(openProps == null, "These should not be set in this path");
  2366. #endif
  2367. if (property.MimeTypeProperty != null)
  2368. {
  2369. #if ASTORIA_OPEN_OBJECT
  2370. property.MimeTypeProperty.SetValue(this.entity, mimeType, null, ref openProps, false);
  2371. Debug.Assert(openProps == null, "These should not be set in this path");
  2372. #else
  2373. property.MimeTypeProperty.SetValue(this.entity, mimeType, null, false);
  2374. #endif
  2375. }
  2376. return MaterializeAtom.CreateWrapper(results);
  2377. }
  2378. finally
  2379. {
  2380. context.ApplyingChanges = merging;
  2381. }
  2382. }
  2383. }
  2384. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Pending")]
  2385. private class SaveResult : BaseAsyncResult
  2386. {
  2387. private readonly DataServiceContext Context;
  2388. private readonly List<Descriptor> ChangedEntries;
  2389. private readonly DataServiceRequest[] Queries;
  2390. private readonly List<OperationResponse> Responses;
  2391. private readonly string batchBoundary;
  2392. private readonly SaveChangesOptions options;
  2393. private readonly bool executeAsync;
  2394. private int changesCompleted;
  2395. private PerRequest request;
  2396. private HttpWebResponse batchResponse;
  2397. private Stream httpWebResponseStream;
  2398. private DataServiceResponse service;
  2399. private int entryIndex = -1;
  2400. private bool processingMediaLinkEntry;
  2401. private bool processingMediaLinkEntryPut;
  2402. private Stream mediaResourceRequestStream;
  2403. private BatchStream responseBatchStream;
  2404. private byte[] buildBatchBuffer;
  2405. private StreamWriter buildBatchWriter;
  2406. private long copiedContentLength;
  2407. private string changesetBoundary;
  2408. private bool changesetStarted;
  2409. #region constructors
  2410. internal SaveResult(DataServiceContext context, string method, DataServiceRequest[] queries, SaveChangesOptions options, AsyncCallback callback, object state, bool async)
  2411. : base(context, method, callback, state)
  2412. {
  2413. this.executeAsync = async;
  2414. this.Context = context;
  2415. this.Queries = queries;
  2416. this.options = options;
  2417. this.Responses = new List<OperationResponse>();
  2418. if (null == queries)
  2419. {
  2420. #region changed entries
  2421. this.ChangedEntries = context.entityDescriptors.Values.Cast<Descriptor>()
  2422. .Union(context.bindings.Values.Cast<Descriptor>())
  2423. .Where(o => o.IsModified && o.ChangeOrder != UInt32.MaxValue)
  2424. .OrderBy(o => o.ChangeOrder)
  2425. .ToList();
  2426. foreach (Descriptor e in this.ChangedEntries)
  2427. {
  2428. e.ContentGeneratedForSave = false;
  2429. e.SaveResultWasProcessed = 0;
  2430. e.SaveError = null;
  2431. if (!e.IsResource)
  2432. {
  2433. object target = ((LinkDescriptor)e).Target;
  2434. if (null != target)
  2435. {
  2436. Descriptor f = context.entityDescriptors[target];
  2437. if (EntityStates.Unchanged == f.State)
  2438. {
  2439. f.ContentGeneratedForSave = false;
  2440. f.SaveResultWasProcessed = 0;
  2441. f.SaveError = null;
  2442. }
  2443. }
  2444. }
  2445. }
  2446. #endregion
  2447. }
  2448. else
  2449. {
  2450. this.ChangedEntries = new List<Descriptor>();
  2451. }
  2452. if (IsFlagSet(options, SaveChangesOptions.Batch))
  2453. {
  2454. this.batchBoundary = XmlConstants.HttpMultipartBoundaryBatch + "_" + Guid.NewGuid().ToString();
  2455. }
  2456. else
  2457. {
  2458. this.batchBoundary = XmlConstants.HttpMultipartBoundaryBatchResponse + "_" + Guid.NewGuid().ToString();
  2459. this.DataServiceResponse = new DataServiceResponse(null, -1, this.Responses, false );
  2460. }
  2461. }
  2462. #endregion constructor
  2463. #region end
  2464. internal DataServiceResponse DataServiceResponse
  2465. {
  2466. get
  2467. {
  2468. return this.service;
  2469. }
  2470. set
  2471. {
  2472. this.service = value;
  2473. }
  2474. }
  2475. internal DataServiceResponse EndRequest()
  2476. {
  2477. foreach (EntityDescriptor box in this.ChangedEntries.Where(e => e.IsResource).Cast<EntityDescriptor>())
  2478. {
  2479. box.CloseSaveStream();
  2480. }
  2481. if ((null != this.responseBatchStream) || (null != this.httpWebResponseStream))
  2482. {
  2483. this.HandleBatchResponse();
  2484. }
  2485. return this.DataServiceResponse;
  2486. }
  2487. #endregion
  2488. #region start a batch
  2489. internal void BatchBeginRequest(bool replaceOnUpdate)
  2490. {
  2491. PerRequest pereq = null;
  2492. try
  2493. {
  2494. MemoryStream memory = this.GenerateBatchRequest(replaceOnUpdate);
  2495. if (null != memory)
  2496. {
  2497. HttpWebRequest httpWebRequest = this.CreateBatchRequest(memory);
  2498. this.Abortable = httpWebRequest;
  2499. this.request = pereq = new PerRequest();
  2500. pereq.Request = httpWebRequest;
  2501. pereq.RequestContentStream = new PerRequest.ContentStream(memory, true);
  2502. this.httpWebResponseStream = new MemoryStream();
  2503. IAsyncResult asyncResult = BaseAsyncResult.InvokeAsync(httpWebRequest.BeginGetRequestStream, this.AsyncEndGetRequestStream, pereq);
  2504. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  2505. }
  2506. else
  2507. {
  2508. Debug.Assert(this.CompletedSynchronously, "completedSynchronously");
  2509. Debug.Assert(this.IsCompletedInternally, "completed");
  2510. }
  2511. }
  2512. catch (Exception e)
  2513. {
  2514. this.HandleFailure(pereq, e);
  2515. throw;
  2516. }
  2517. finally
  2518. {
  2519. this.HandleCompleted(pereq);
  2520. }
  2521. Debug.Assert((this.CompletedSynchronously && this.IsCompleted) || !this.CompletedSynchronously, "sync without complete");
  2522. }
  2523. #if !ASTORIA_LIGHT
  2524. internal void BatchRequest(bool replaceOnUpdate)
  2525. {
  2526. MemoryStream memory = this.GenerateBatchRequest(replaceOnUpdate);
  2527. if ((null != memory) && (0 < memory.Length))
  2528. {
  2529. HttpWebRequest httpWebRequest = this.CreateBatchRequest(memory);
  2530. using (System.IO.Stream requestStream = httpWebRequest.GetRequestStream())
  2531. {
  2532. byte[] buffer = memory.GetBuffer();
  2533. int bufferOffset = checked((int)memory.Position);
  2534. int bufferLength = checked((int)memory.Length) - bufferOffset;
  2535. requestStream.Write(buffer, bufferOffset, bufferLength);
  2536. }
  2537. HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
  2538. this.batchResponse = httpWebResponse;
  2539. if (null != httpWebResponse)
  2540. {
  2541. this.httpWebResponseStream = httpWebResponse.GetResponseStream();
  2542. }
  2543. }
  2544. }
  2545. #endif
  2546. #endregion
  2547. #region start a non-batch requests
  2548. internal void BeginNextChange(bool replaceOnUpdate)
  2549. {
  2550. Debug.Assert(!this.IsCompletedInternally, "why being called if already completed?");
  2551. PerRequest pereq = null;
  2552. IAsyncResult asyncResult = null;
  2553. do
  2554. {
  2555. HttpWebRequest httpWebRequest = null;
  2556. HttpWebResponse response = null;
  2557. try
  2558. {
  2559. if (null != this.request)
  2560. {
  2561. this.SetCompleted();
  2562. Error.ThrowInternalError(InternalError.InvalidBeginNextChange);
  2563. }
  2564. this.Abortable = httpWebRequest = this.CreateNextRequest(replaceOnUpdate);
  2565. if ((null != httpWebRequest) || (this.entryIndex < this.ChangedEntries.Count))
  2566. {
  2567. if (this.ChangedEntries[this.entryIndex].ContentGeneratedForSave)
  2568. {
  2569. Debug.Assert(this.ChangedEntries[this.entryIndex] is LinkDescriptor, "only expected RelatedEnd to presave");
  2570. Debug.Assert(
  2571. this.ChangedEntries[this.entryIndex].State == EntityStates.Added ||
  2572. this.ChangedEntries[this.entryIndex].State == EntityStates.Modified,
  2573. "only expected added to presave");
  2574. continue;
  2575. }
  2576. PerRequest.ContentStream contentStream = this.CreateChangeData(this.entryIndex, false);
  2577. if (this.executeAsync)
  2578. {
  2579. #region async
  2580. this.request = pereq = new PerRequest();
  2581. pereq.Request = httpWebRequest;
  2582. if (null == contentStream || null == contentStream.Stream)
  2583. {
  2584. asyncResult = BaseAsyncResult.InvokeAsync(httpWebRequest.BeginGetResponse, this.AsyncEndGetResponse, pereq);
  2585. }
  2586. else
  2587. {
  2588. if (contentStream.IsKnownMemoryStream)
  2589. {
  2590. httpWebRequest.ContentLength = contentStream.Stream.Length - contentStream.Stream.Position;
  2591. }
  2592. pereq.RequestContentStream = contentStream;
  2593. asyncResult = BaseAsyncResult.InvokeAsync(httpWebRequest.BeginGetRequestStream, this.AsyncEndGetRequestStream, pereq);
  2594. }
  2595. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  2596. this.CompletedSynchronously &= asyncResult.CompletedSynchronously;
  2597. #endregion
  2598. }
  2599. #if !ASTORIA_LIGHT
  2600. else
  2601. {
  2602. #region sync
  2603. if (null != contentStream && null != contentStream.Stream)
  2604. {
  2605. if (contentStream.IsKnownMemoryStream)
  2606. {
  2607. httpWebRequest.ContentLength = contentStream.Stream.Length - contentStream.Stream.Position;
  2608. }
  2609. using (Stream stream = httpWebRequest.GetRequestStream())
  2610. {
  2611. byte[] buffer = new byte[64 * 1024];
  2612. int read;
  2613. do
  2614. {
  2615. read = contentStream.Stream.Read(buffer, 0, buffer.Length);
  2616. if (read > 0)
  2617. {
  2618. stream.Write(buffer, 0, read);
  2619. }
  2620. }
  2621. while (read > 0);
  2622. }
  2623. }
  2624. response = (HttpWebResponse)httpWebRequest.GetResponse();
  2625. if (!this.processingMediaLinkEntry)
  2626. {
  2627. this.changesCompleted++;
  2628. }
  2629. this.HandleOperationResponse(response);
  2630. this.HandleOperationResponseData(response);
  2631. this.HandleOperationEnd();
  2632. this.request = null;
  2633. #endregion
  2634. }
  2635. #endif
  2636. }
  2637. else
  2638. {
  2639. this.SetCompleted();
  2640. if (this.CompletedSynchronously)
  2641. {
  2642. this.HandleCompleted(pereq);
  2643. }
  2644. }
  2645. }
  2646. catch (InvalidOperationException e)
  2647. {
  2648. WebUtil.GetHttpWebResponse(e, ref response);
  2649. this.HandleOperationException(e, response);
  2650. this.HandleCompleted(pereq);
  2651. }
  2652. finally
  2653. {
  2654. if (null != response)
  2655. {
  2656. response.Close();
  2657. }
  2658. }
  2659. }
  2660. while (((null == pereq) || (pereq.RequestCompleted && asyncResult != null && asyncResult.CompletedSynchronously)) && !this.IsCompletedInternally);
  2661. Debug.Assert(this.executeAsync || this.CompletedSynchronously, "sync !CompletedSynchronously");
  2662. Debug.Assert((this.CompletedSynchronously && this.IsCompleted) || !this.CompletedSynchronously, "sync without complete");
  2663. Debug.Assert(this.entryIndex < this.ChangedEntries.Count || this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generate content for all entities/links");
  2664. }
  2665. protected override void CompletedRequest()
  2666. {
  2667. this.buildBatchBuffer = null;
  2668. if (null != this.buildBatchWriter)
  2669. {
  2670. Debug.Assert(!IsFlagSet(this.options, SaveChangesOptions.Batch), "should be non-batch");
  2671. this.HandleOperationEnd();
  2672. this.buildBatchWriter.WriteLine("--{0}--", this.batchBoundary);
  2673. this.buildBatchWriter.Flush();
  2674. Debug.Assert(Object.ReferenceEquals(this.httpWebResponseStream, this.buildBatchWriter.BaseStream), "expected different stream");
  2675. this.httpWebResponseStream.Position = 0;
  2676. this.buildBatchWriter = null;
  2677. this.responseBatchStream = new BatchStream(this.httpWebResponseStream, this.batchBoundary, HttpProcessUtility.EncodingUtf8NoPreamble, false);
  2678. }
  2679. }
  2680. private static void CompleteCheck(PerRequest value, InternalError errorcode)
  2681. {
  2682. if ((null == value) || value.RequestCompleted)
  2683. {
  2684. Error.ThrowInternalError(errorcode);
  2685. }
  2686. }
  2687. private static void EqualRefCheck(PerRequest actual, PerRequest expected, InternalError errorcode)
  2688. {
  2689. if (!Object.ReferenceEquals(actual, expected))
  2690. {
  2691. Error.ThrowInternalError(errorcode);
  2692. }
  2693. }
  2694. private void HandleCompleted(PerRequest pereq)
  2695. {
  2696. if (null != pereq)
  2697. {
  2698. this.CompletedSynchronously &= pereq.RequestCompletedSynchronously;
  2699. if (pereq.RequestCompleted)
  2700. {
  2701. System.Threading.Interlocked.CompareExchange(ref this.request, null, pereq);
  2702. if (IsFlagSet(this.options, SaveChangesOptions.Batch))
  2703. {
  2704. System.Threading.Interlocked.CompareExchange(ref this.batchResponse, pereq.HttpWebResponse, null);
  2705. pereq.HttpWebResponse = null;
  2706. }
  2707. pereq.Dispose();
  2708. }
  2709. }
  2710. this.HandleCompleted();
  2711. }
  2712. private bool HandleFailure(PerRequest pereq, Exception e)
  2713. {
  2714. if (null != pereq)
  2715. {
  2716. if (IsAborted)
  2717. {
  2718. pereq.SetAborted();
  2719. }
  2720. else
  2721. {
  2722. pereq.SetComplete();
  2723. }
  2724. }
  2725. return this.HandleFailure(e);
  2726. }
  2727. private HttpWebRequest CreateNextRequest(bool replaceOnUpdate)
  2728. {
  2729. if (!this.processingMediaLinkEntry)
  2730. {
  2731. this.entryIndex++;
  2732. }
  2733. else
  2734. {
  2735. Debug.Assert(this.ChangedEntries[this.entryIndex].IsResource, "Only resources can have MR's.");
  2736. EntityDescriptor box = (EntityDescriptor)this.ChangedEntries[this.entryIndex];
  2737. if (this.processingMediaLinkEntryPut && EntityStates.Unchanged == box.State)
  2738. {
  2739. box.ContentGeneratedForSave = true;
  2740. this.entryIndex++;
  2741. }
  2742. this.processingMediaLinkEntry = false;
  2743. this.processingMediaLinkEntryPut = false;
  2744. box.CloseSaveStream();
  2745. }
  2746. if (unchecked((uint)this.entryIndex < (uint)this.ChangedEntries.Count))
  2747. {
  2748. Descriptor entry = this.ChangedEntries[this.entryIndex];
  2749. if (entry.IsResource)
  2750. {
  2751. EntityDescriptor box = (EntityDescriptor)entry;
  2752. HttpWebRequest req;
  2753. if (((EntityStates.Unchanged == entry.State) || (EntityStates.Modified == entry.State)) &&
  2754. (null != (req = this.CheckAndProcessMediaEntryPut(box))))
  2755. {
  2756. this.processingMediaLinkEntry = true;
  2757. this.processingMediaLinkEntryPut = true;
  2758. }
  2759. else if ((EntityStates.Added == entry.State) && (null != (req = this.CheckAndProcessMediaEntryPost(box))))
  2760. {
  2761. this.processingMediaLinkEntry = true;
  2762. this.processingMediaLinkEntryPut = false;
  2763. }
  2764. else
  2765. {
  2766. Debug.Assert(!this.processingMediaLinkEntry || entry.State == EntityStates.Modified, "!this.processingMediaLinkEntry || entry.State == EntityStates.Modified");
  2767. req = this.Context.CreateRequest(box, entry.State, replaceOnUpdate);
  2768. }
  2769. return req;
  2770. }
  2771. return this.Context.CreateRequest((LinkDescriptor)entry);
  2772. }
  2773. return null;
  2774. }
  2775. private HttpWebRequest CheckAndProcessMediaEntryPost(EntityDescriptor entityDescriptor)
  2776. {
  2777. ClientType type = ClientType.Create(entityDescriptor.Entity.GetType());
  2778. if (!type.IsMediaLinkEntry && !entityDescriptor.IsMediaLinkEntry)
  2779. {
  2780. return null;
  2781. }
  2782. if (type.MediaDataMember == null && entityDescriptor.SaveStream == null)
  2783. {
  2784. throw Error.InvalidOperation(Strings.Context_MLEWithoutSaveStream(type.ElementTypeName));
  2785. }
  2786. Debug.Assert(
  2787. (type.MediaDataMember != null && entityDescriptor.SaveStream == null) ||
  2788. (type.MediaDataMember == null && entityDescriptor.SaveStream != null),
  2789. "Only one way of specifying the MR content is allowed.");
  2790. HttpWebRequest mediaRequest = this.CreateMediaResourceRequest(
  2791. entityDescriptor.GetResourceUri(this.Context.baseUriWithSlash, false),
  2792. XmlConstants.HttpMethodPost,
  2793. type.MediaDataMember == null);
  2794. if (type.MediaDataMember != null)
  2795. {
  2796. if (type.MediaDataMember.MimeTypeProperty == null)
  2797. {
  2798. mediaRequest.ContentType = XmlConstants.MimeApplicationOctetStream;
  2799. }
  2800. else
  2801. {
  2802. object mimeTypeValue = type.MediaDataMember.MimeTypeProperty.GetValue(entityDescriptor.Entity);
  2803. String mimeType = mimeTypeValue != null ? mimeTypeValue.ToString() : null;
  2804. if (String.IsNullOrEmpty(mimeType))
  2805. {
  2806. throw Error.InvalidOperation(
  2807. Strings.Context_NoContentTypeForMediaLink(
  2808. type.ElementTypeName,
  2809. type.MediaDataMember.MimeTypeProperty.PropertyName));
  2810. }
  2811. mediaRequest.ContentType = mimeType;
  2812. }
  2813. object value = type.MediaDataMember.GetValue(entityDescriptor.Entity);
  2814. if (value == null)
  2815. {
  2816. mediaRequest.ContentLength = 0;
  2817. this.mediaResourceRequestStream = null;
  2818. }
  2819. else
  2820. {
  2821. byte[] buffer = value as byte[];
  2822. if (buffer == null)
  2823. {
  2824. string mime;
  2825. Encoding encoding;
  2826. HttpProcessUtility.ReadContentType(mediaRequest.ContentType, out mime, out encoding);
  2827. if (encoding == null)
  2828. {
  2829. encoding = Encoding.UTF8;
  2830. mediaRequest.ContentType += XmlConstants.MimeTypeUtf8Encoding;
  2831. }
  2832. buffer = encoding.GetBytes(ClientConvert.ToString(value, false));
  2833. }
  2834. mediaRequest.ContentLength = buffer.Length;
  2835. this.mediaResourceRequestStream = new MemoryStream(buffer, 0, buffer.Length, false, true);
  2836. }
  2837. }
  2838. else
  2839. {
  2840. this.SetupMediaResourceRequest(mediaRequest, entityDescriptor);
  2841. }
  2842. entityDescriptor.State = EntityStates.Modified;
  2843. return mediaRequest;
  2844. }
  2845. private HttpWebRequest CheckAndProcessMediaEntryPut(EntityDescriptor box)
  2846. {
  2847. if (box.SaveStream == null)
  2848. {
  2849. return null;
  2850. }
  2851. Uri requestUri = box.GetEditMediaResourceUri(this.Context.baseUriWithSlash);
  2852. if (requestUri == null)
  2853. {
  2854. throw Error.InvalidOperation(
  2855. Strings.Context_SetSaveStreamWithoutEditMediaLink);
  2856. }
  2857. HttpWebRequest mediaResourceRequest = this.CreateMediaResourceRequest(requestUri, XmlConstants.HttpMethodPut, true);
  2858. this.SetupMediaResourceRequest(mediaResourceRequest, box);
  2859. if (box.StreamETag != null)
  2860. {
  2861. mediaResourceRequest.Headers.Set(HttpRequestHeader.IfMatch, box.StreamETag);
  2862. }
  2863. return mediaResourceRequest;
  2864. }
  2865. private HttpWebRequest CreateMediaResourceRequest(Uri requestUri, string method, bool sendChunked)
  2866. {
  2867. #if ASTORIA_LIGHT
  2868. HttpWebRequest mediaResourceRequest = this.Context.CreateRequest(
  2869. requestUri,
  2870. method,
  2871. false,
  2872. XmlConstants.MimeAny,
  2873. Util.DataServiceVersion1,
  2874. sendChunked,
  2875. HttpStack.ClientHttp);
  2876. #else
  2877. HttpWebRequest mediaResourceRequest = this.Context.CreateRequest(
  2878. requestUri,
  2879. method,
  2880. false,
  2881. XmlConstants.MimeAny,
  2882. Util.DataServiceVersion1,
  2883. sendChunked);
  2884. #endif
  2885. return mediaResourceRequest;
  2886. }
  2887. private void SetupMediaResourceRequest(HttpWebRequest mediaResourceRequest, EntityDescriptor box)
  2888. {
  2889. this.mediaResourceRequestStream = box.SaveStream.Stream;
  2890. WebUtil.ApplyHeadersToRequest(box.SaveStream.Args.Headers, mediaResourceRequest, true);
  2891. }
  2892. private PerRequest.ContentStream CreateChangeData(int index, bool newline)
  2893. {
  2894. Descriptor entry = this.ChangedEntries[index];
  2895. Debug.Assert(!entry.ContentGeneratedForSave, "already saved entity/link");
  2896. if (entry.IsResource)
  2897. {
  2898. EntityDescriptor box = (EntityDescriptor)entry;
  2899. if (this.processingMediaLinkEntry)
  2900. {
  2901. Debug.Assert(
  2902. this.processingMediaLinkEntryPut || entry.State == EntityStates.Modified,
  2903. "We should have modified the MLE state to Modified when we've created the MR POST request.");
  2904. Debug.Assert(
  2905. !this.processingMediaLinkEntryPut || (entry.State == EntityStates.Unchanged || entry.State == EntityStates.Modified),
  2906. "If we're processing MR PUT the entity must be either in Unchanged or Modified state.");
  2907. Debug.Assert(this.mediaResourceRequestStream != null, "We should have precreated the MR stream already.");
  2908. return new PerRequest.ContentStream(this.mediaResourceRequestStream, false);
  2909. }
  2910. else
  2911. {
  2912. entry.ContentGeneratedForSave = true;
  2913. return new PerRequest.ContentStream(this.Context.CreateRequestData(box, newline), true);
  2914. }
  2915. }
  2916. else
  2917. {
  2918. entry.ContentGeneratedForSave = true;
  2919. LinkDescriptor link = (LinkDescriptor)entry;
  2920. if ((EntityStates.Added == link.State) ||
  2921. ((EntityStates.Modified == link.State) && (null != link.Target)))
  2922. {
  2923. return new PerRequest.ContentStream(this.Context.CreateRequestData(link, newline), true);
  2924. }
  2925. }
  2926. return null;
  2927. }
  2928. #endregion
  2929. #region generate batch response from non-batch
  2930. private void HandleOperationStart()
  2931. {
  2932. this.HandleOperationEnd();
  2933. if (null == this.httpWebResponseStream)
  2934. {
  2935. this.httpWebResponseStream = new MemoryStream();
  2936. }
  2937. if (null == this.buildBatchWriter)
  2938. {
  2939. this.buildBatchWriter = new StreamWriter(this.httpWebResponseStream);
  2940. #if TESTUNIXNEWLINE
  2941. this.buildBatchWriter.NewLine = NewLine;
  2942. #endif
  2943. }
  2944. if (null == this.changesetBoundary)
  2945. {
  2946. this.changesetBoundary = XmlConstants.HttpMultipartBoundaryChangesetResponse + "_" + Guid.NewGuid().ToString();
  2947. }
  2948. this.changesetStarted = true;
  2949. this.buildBatchWriter.WriteLine("--{0}", this.batchBoundary);
  2950. this.buildBatchWriter.WriteLine("{0}: {1}; boundary={2}", XmlConstants.HttpContentType, XmlConstants.MimeMultiPartMixed, this.changesetBoundary);
  2951. this.buildBatchWriter.WriteLine();
  2952. this.buildBatchWriter.WriteLine("--{0}", this.changesetBoundary);
  2953. }
  2954. private void HandleOperationEnd()
  2955. {
  2956. if (this.changesetStarted)
  2957. {
  2958. Debug.Assert(null != this.buildBatchWriter, "buildBatchWriter");
  2959. Debug.Assert(null != this.changesetBoundary, "changesetBoundary");
  2960. this.buildBatchWriter.WriteLine();
  2961. this.buildBatchWriter.WriteLine("--{0}--", this.changesetBoundary);
  2962. this.changesetStarted = false;
  2963. }
  2964. }
  2965. private void HandleOperationException(Exception e, HttpWebResponse response)
  2966. {
  2967. if (null != response)
  2968. {
  2969. this.HandleOperationResponse(response);
  2970. this.HandleOperationResponseData(response);
  2971. this.HandleOperationEnd();
  2972. }
  2973. else
  2974. {
  2975. this.HandleOperationStart();
  2976. WriteOperationResponseHeaders(this.buildBatchWriter, 500);
  2977. this.buildBatchWriter.WriteLine("{0}: {1}", XmlConstants.HttpContentType, XmlConstants.MimeTextPlain);
  2978. this.buildBatchWriter.WriteLine("{0}: {1}", XmlConstants.HttpContentID, this.ChangedEntries[this.entryIndex].ChangeOrder);
  2979. this.buildBatchWriter.WriteLine();
  2980. this.buildBatchWriter.WriteLine(e.ToString());
  2981. this.HandleOperationEnd();
  2982. }
  2983. this.request = null;
  2984. if (!IsFlagSet(this.options, SaveChangesOptions.ContinueOnError))
  2985. {
  2986. this.SetCompleted();
  2987. this.processingMediaLinkEntry = false;
  2988. this.ChangedEntries[this.entryIndex].ContentGeneratedForSave = true;
  2989. }
  2990. }
  2991. private void HandleOperationResponse(HttpWebResponse response)
  2992. {
  2993. this.HandleOperationStart();
  2994. Descriptor entry = this.ChangedEntries[this.entryIndex];
  2995. if (entry.IsResource)
  2996. {
  2997. EntityDescriptor entityDescriptor = (EntityDescriptor)entry;
  2998. if (entry.State == EntityStates.Added ||
  2999. (entry.State == EntityStates.Modified &&
  3000. this.processingMediaLinkEntry && !this.processingMediaLinkEntryPut))
  3001. {
  3002. string location = response.Headers[XmlConstants.HttpResponseLocation];
  3003. if (WebUtil.SuccessStatusCode(response.StatusCode))
  3004. {
  3005. if (null != location)
  3006. {
  3007. this.Context.AttachLocation(entityDescriptor.Entity, location);
  3008. }
  3009. else
  3010. {
  3011. throw Error.NotSupported(Strings.Deserialize_NoLocationHeader);
  3012. }
  3013. }
  3014. }
  3015. if (this.processingMediaLinkEntry)
  3016. {
  3017. if (!WebUtil.SuccessStatusCode(response.StatusCode))
  3018. {
  3019. this.processingMediaLinkEntry = false;
  3020. if (!this.processingMediaLinkEntryPut)
  3021. {
  3022. Debug.Assert(entry.State == EntityStates.Modified, "Entity state should be set to Modified once we've sent the POST MR");
  3023. entry.State = EntityStates.Added;
  3024. this.processingMediaLinkEntryPut = false;
  3025. }
  3026. entry.ContentGeneratedForSave = true;
  3027. }
  3028. else if (response.StatusCode == HttpStatusCode.Created)
  3029. {
  3030. entityDescriptor.ETag = response.Headers[XmlConstants.HttpResponseETag];
  3031. }
  3032. }
  3033. }
  3034. WriteOperationResponseHeaders(this.buildBatchWriter, (int)response.StatusCode);
  3035. foreach (string name in response.Headers.AllKeys)
  3036. {
  3037. if (XmlConstants.HttpContentLength != name)
  3038. {
  3039. this.buildBatchWriter.WriteLine("{0}: {1}", name, response.Headers[name]);
  3040. }
  3041. }
  3042. this.buildBatchWriter.WriteLine("{0}: {1}", XmlConstants.HttpContentID, entry.ChangeOrder);
  3043. this.buildBatchWriter.WriteLine();
  3044. }
  3045. private void HandleOperationResponseData(HttpWebResponse response)
  3046. {
  3047. using (Stream stream = response.GetResponseStream())
  3048. {
  3049. if (null != stream)
  3050. {
  3051. this.buildBatchWriter.Flush();
  3052. if (0 == WebUtil.CopyStream(stream, this.buildBatchWriter.BaseStream, ref this.buildBatchBuffer))
  3053. {
  3054. this.HandleOperationResponseNoData();
  3055. }
  3056. }
  3057. }
  3058. }
  3059. private void HandleOperationResponseNoData()
  3060. {
  3061. Debug.Assert(null != this.buildBatchWriter, "null buildBatchWriter");
  3062. this.buildBatchWriter.Flush();
  3063. #if DEBUG
  3064. MemoryStream memory = this.buildBatchWriter.BaseStream as MemoryStream;
  3065. Debug.Assert(null != memory, "expected MemoryStream");
  3066. Debug.Assert(this.buildBatchWriter.NewLine == NewLine, "mismatch NewLine");
  3067. for (int kk = 0; kk < NewLine.Length; ++kk)
  3068. {
  3069. Debug.Assert((char)memory.GetBuffer()[memory.Length - (NewLine.Length - kk)] == NewLine[kk], "didn't end with newline");
  3070. }
  3071. #endif
  3072. this.buildBatchWriter.BaseStream.Position -= NewLine.Length;
  3073. this.buildBatchWriter.WriteLine("{0}: {1}", XmlConstants.HttpContentLength, 0);
  3074. this.buildBatchWriter.WriteLine();
  3075. }
  3076. #endregion
  3077. private HttpWebRequest CreateBatchRequest(MemoryStream memory)
  3078. {
  3079. Uri requestUri = Util.CreateUri(this.Context.baseUriWithSlash, Util.CreateUri("$batch", UriKind.Relative));
  3080. string contentType = XmlConstants.MimeMultiPartMixed + "; " + XmlConstants.HttpMultipartBoundary + "=" + this.batchBoundary;
  3081. HttpWebRequest httpWebRequest = this.Context.CreateRequest(requestUri, XmlConstants.HttpMethodPost, false, contentType, Util.DataServiceVersion1, false);
  3082. httpWebRequest.ContentLength = memory.Length - memory.Position;
  3083. return httpWebRequest;
  3084. }
  3085. private MemoryStream GenerateBatchRequest(bool replaceOnUpdate)
  3086. {
  3087. this.changesetBoundary = null;
  3088. if (null != this.Queries)
  3089. {
  3090. }
  3091. else if (0 == this.ChangedEntries.Count)
  3092. {
  3093. this.DataServiceResponse = new DataServiceResponse(null, (int)WebExceptionStatus.Success, this.Responses, true );
  3094. this.SetCompleted();
  3095. return null;
  3096. }
  3097. else
  3098. {
  3099. this.changesetBoundary = XmlConstants.HttpMultipartBoundaryChangeSet + "_" + Guid.NewGuid().ToString();
  3100. }
  3101. MemoryStream memory = new MemoryStream();
  3102. StreamWriter text = new StreamWriter(memory);
  3103. #if TESTUNIXNEWLINE
  3104. text.NewLine = NewLine;
  3105. #endif
  3106. if (null != this.Queries)
  3107. {
  3108. for (int i = 0; i < this.Queries.Length; ++i)
  3109. {
  3110. Uri requestUri = Util.CreateUri(this.Context.baseUriWithSlash, this.Queries[i].QueryComponents.Uri);
  3111. Debug.Assert(null != requestUri, "request uri is null");
  3112. Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri");
  3113. text.WriteLine("--{0}", this.batchBoundary);
  3114. WriteOperationRequestHeaders(text, XmlConstants.HttpMethodGet, requestUri.AbsoluteUri, this.Queries[i].QueryComponents.Version);
  3115. text.WriteLine();
  3116. }
  3117. }
  3118. else if (0 < this.ChangedEntries.Count)
  3119. {
  3120. text.WriteLine("--{0}", this.batchBoundary);
  3121. text.WriteLine("{0}: {1}; boundary={2}", XmlConstants.HttpContentType, XmlConstants.MimeMultiPartMixed, this.changesetBoundary);
  3122. text.WriteLine();
  3123. for (int i = 0; i < this.ChangedEntries.Count; ++i)
  3124. {
  3125. #region validate changeset boundary starts on newline
  3126. #if DEBUG
  3127. {
  3128. text.Flush();
  3129. for (int kk = 0; kk < NewLine.Length; ++kk)
  3130. {
  3131. Debug.Assert((char)memory.GetBuffer()[memory.Length - (NewLine.Length - kk)] == NewLine[kk], "boundary didn't start with newline");
  3132. }
  3133. }
  3134. #endif
  3135. #endregion
  3136. Descriptor entry = this.ChangedEntries[i];
  3137. if (entry.ContentGeneratedForSave)
  3138. {
  3139. continue;
  3140. }
  3141. text.WriteLine("--{0}", this.changesetBoundary);
  3142. EntityDescriptor entityDescriptor = entry as EntityDescriptor;
  3143. if (entry.IsResource)
  3144. {
  3145. if (entityDescriptor.State == EntityStates.Added)
  3146. {
  3147. ClientType type = ClientType.Create(entityDescriptor.Entity.GetType());
  3148. if (type.IsMediaLinkEntry || entityDescriptor.IsMediaLinkEntry)
  3149. {
  3150. throw Error.NotSupported(Strings.Context_BatchNotSupportedForMediaLink);
  3151. }
  3152. }
  3153. else if (entityDescriptor.State == EntityStates.Unchanged || entityDescriptor.State == EntityStates.Modified)
  3154. {
  3155. if (entityDescriptor.SaveStream != null)
  3156. {
  3157. throw Error.NotSupported(Strings.Context_BatchNotSupportedForMediaLink);
  3158. }
  3159. }
  3160. }
  3161. PerRequest.ContentStream contentStream = this.CreateChangeData(i, true);
  3162. MemoryStream stream = null;
  3163. if (null != contentStream)
  3164. {
  3165. Debug.Assert(contentStream.IsKnownMemoryStream, "Batch requests don't support MRs yet");
  3166. stream = contentStream.Stream as MemoryStream;
  3167. }
  3168. if (entry.IsResource)
  3169. {
  3170. this.Context.CreateRequestBatch(entityDescriptor, text, replaceOnUpdate);
  3171. }
  3172. else
  3173. {
  3174. this.Context.CreateRequestBatch((LinkDescriptor)entry, text);
  3175. }
  3176. byte[] buffer = null;
  3177. int bufferOffset = 0, bufferLength = 0;
  3178. if (null != stream)
  3179. {
  3180. buffer = stream.GetBuffer();
  3181. bufferOffset = checked((int)stream.Position);
  3182. bufferLength = checked((int)stream.Length) - bufferOffset;
  3183. }
  3184. if (0 < bufferLength)
  3185. {
  3186. text.WriteLine("{0}: {1}", XmlConstants.HttpContentLength, bufferLength);
  3187. }
  3188. text.WriteLine();
  3189. if (0 < bufferLength)
  3190. {
  3191. text.Flush();
  3192. text.BaseStream.Write(buffer, bufferOffset, bufferLength);
  3193. }
  3194. }
  3195. #region validate changeset boundary ended with newline
  3196. #if DEBUG
  3197. {
  3198. text.Flush();
  3199. for (int kk = 0; kk < NewLine.Length; ++kk)
  3200. {
  3201. Debug.Assert((char)memory.GetBuffer()[memory.Length - (NewLine.Length - kk)] == NewLine[kk], "post CreateRequest boundary didn't start with newline");
  3202. }
  3203. }
  3204. #endif
  3205. #endregion
  3206. text.WriteLine("--{0}--", this.changesetBoundary);
  3207. }
  3208. text.WriteLine("--{0}--", this.batchBoundary);
  3209. text.Flush();
  3210. Debug.Assert(Object.ReferenceEquals(text.BaseStream, memory), "should be same");
  3211. Debug.Assert(this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generated content for all entities/links");
  3212. #region Validate batch format
  3213. #if DEBUG
  3214. int testGetCount = 0;
  3215. int testOpCount = 0;
  3216. int testBeginSetCount = 0;
  3217. int testEndSetCount = 0;
  3218. memory.Position = 0;
  3219. BatchStream testBatch = new BatchStream(memory, this.batchBoundary, HttpProcessUtility.EncodingUtf8NoPreamble, true);
  3220. while (testBatch.MoveNext())
  3221. {
  3222. switch (testBatch.State)
  3223. {
  3224. case BatchStreamState.StartBatch:
  3225. case BatchStreamState.EndBatch:
  3226. default:
  3227. Debug.Assert(false, "shouldn't happen");
  3228. break;
  3229. case BatchStreamState.Get:
  3230. testGetCount++;
  3231. break;
  3232. case BatchStreamState.BeginChangeSet:
  3233. testBeginSetCount++;
  3234. break;
  3235. case BatchStreamState.EndChangeSet:
  3236. testEndSetCount++;
  3237. break;
  3238. case BatchStreamState.Post:
  3239. case BatchStreamState.Put:
  3240. case BatchStreamState.Delete:
  3241. case BatchStreamState.Merge:
  3242. testOpCount++;
  3243. break;
  3244. }
  3245. }
  3246. Debug.Assert((null == this.Queries && 1 == testBeginSetCount) || (0 == testBeginSetCount), "more than one BeginChangeSet");
  3247. Debug.Assert(testBeginSetCount == testEndSetCount, "more than one EndChangeSet");
  3248. Debug.Assert((null == this.Queries && testGetCount == 0) || this.Queries.Length == testGetCount, "too many get count");
  3249. Debug.Assert(BatchStreamState.EndBatch == testBatch.State, "should have ended propertly");
  3250. #endif
  3251. #endregion
  3252. this.changesetBoundary = null;
  3253. memory.Position = 0;
  3254. return memory;
  3255. }
  3256. #region handle batch response
  3257. private void HandleBatchResponse()
  3258. {
  3259. string boundary = this.batchBoundary;
  3260. Encoding encoding = Encoding.UTF8;
  3261. Dictionary<string, string> headers = null;
  3262. Exception exception = null;
  3263. try
  3264. {
  3265. if (IsFlagSet(this.options, SaveChangesOptions.Batch))
  3266. {
  3267. if ((null == this.batchResponse) || (HttpStatusCode.NoContent == this.batchResponse.StatusCode))
  3268. {
  3269. throw Error.InvalidOperation(Strings.Batch_ExpectedResponse(1));
  3270. }
  3271. headers = WebUtil.WrapResponseHeaders(this.batchResponse);
  3272. HandleResponse(
  3273. this.batchResponse.StatusCode,
  3274. this.batchResponse.Headers[XmlConstants.HttpDataServiceVersion],
  3275. delegate() { return this.httpWebResponseStream; },
  3276. true);
  3277. if (!BatchStream.GetBoundaryAndEncodingFromMultipartMixedContentType(this.batchResponse.ContentType, out boundary, out encoding))
  3278. {
  3279. string mime;
  3280. Exception inner = null;
  3281. HttpProcessUtility.ReadContentType(this.batchResponse.ContentType, out mime, out encoding);
  3282. if (String.Equals(XmlConstants.MimeTextPlain, mime))
  3283. {
  3284. inner = GetResponseText(this.batchResponse.GetResponseStream, this.batchResponse.StatusCode);
  3285. }
  3286. throw Error.InvalidOperation(Strings.Batch_ExpectedContentType(this.batchResponse.ContentType), inner);
  3287. }
  3288. if (null == this.httpWebResponseStream)
  3289. {
  3290. Error.ThrowBatchExpectedResponse(InternalError.NullResponseStream);
  3291. }
  3292. this.DataServiceResponse = new DataServiceResponse(headers, (int)this.batchResponse.StatusCode, this.Responses, true);
  3293. }
  3294. bool close = true;
  3295. BatchStream batchStream = null;
  3296. try
  3297. {
  3298. batchStream = this.responseBatchStream ?? new BatchStream(this.httpWebResponseStream, boundary, encoding, false);
  3299. this.httpWebResponseStream = null;
  3300. this.responseBatchStream = null;
  3301. IEnumerable<OperationResponse> responses = this.HandleBatchResponse(batchStream);
  3302. if (IsFlagSet(this.options, SaveChangesOptions.Batch) && (null != this.Queries))
  3303. {
  3304. close = false;
  3305. this.responseBatchStream = batchStream;
  3306. this.DataServiceResponse = new DataServiceResponse(
  3307. (Dictionary<string, string>)this.DataServiceResponse.BatchHeaders,
  3308. this.DataServiceResponse.BatchStatusCode,
  3309. responses,
  3310. true );
  3311. }
  3312. else
  3313. {
  3314. foreach (ChangeOperationResponse response in responses)
  3315. {
  3316. if (exception == null && response.Error != null)
  3317. {
  3318. exception = response.Error;
  3319. }
  3320. }
  3321. }
  3322. }
  3323. finally
  3324. {
  3325. if (close && (null != batchStream))
  3326. {
  3327. batchStream.Close();
  3328. }
  3329. }
  3330. }
  3331. catch (InvalidOperationException ex)
  3332. {
  3333. exception = ex;
  3334. }
  3335. if (exception != null)
  3336. {
  3337. if (this.DataServiceResponse == null)
  3338. {
  3339. int statusCode = this.batchResponse == null ? (int)HttpStatusCode.InternalServerError : (int)this.batchResponse.StatusCode;
  3340. this.DataServiceResponse = new DataServiceResponse(headers, statusCode, null, IsFlagSet(this.options, SaveChangesOptions.Batch));
  3341. }
  3342. throw new DataServiceRequestException(Strings.DataServiceException_GeneralError, exception, this.DataServiceResponse);
  3343. }
  3344. }
  3345. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506", Justification = "Central method of the API, likely to have many cross-references")]
  3346. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031", Justification = "Cache exception so user can examine it later")]
  3347. private IEnumerable<OperationResponse> HandleBatchResponse(BatchStream batch)
  3348. {
  3349. if (!batch.CanRead)
  3350. {
  3351. yield break;
  3352. }
  3353. string contentType;
  3354. string location;
  3355. string etag;
  3356. Uri editLink = null;
  3357. HttpStatusCode status;
  3358. int changesetIndex = 0;
  3359. int queryCount = 0;
  3360. int operationCount = 0;
  3361. this.entryIndex = 0;
  3362. while (batch.MoveNext())
  3363. {
  3364. var contentHeaders = batch.ContentHeaders;
  3365. Descriptor entry;
  3366. switch (batch.State)
  3367. {
  3368. #region BeginChangeSet
  3369. case BatchStreamState.BeginChangeSet:
  3370. if ((IsFlagSet(this.options, SaveChangesOptions.Batch) && (0 != changesetIndex)) ||
  3371. (0 != operationCount))
  3372. {
  3373. Error.ThrowBatchUnexpectedContent(InternalError.UnexpectedBeginChangeSet);
  3374. }
  3375. break;
  3376. #endregion
  3377. #region EndChangeSet
  3378. case BatchStreamState.EndChangeSet:
  3379. changesetIndex++;
  3380. operationCount = 0;
  3381. break;
  3382. #endregion
  3383. #region GetResponse
  3384. case BatchStreamState.GetResponse:
  3385. Debug.Assert(0 == operationCount, "missing an EndChangeSet 2");
  3386. contentHeaders.TryGetValue(XmlConstants.HttpContentType, out contentType);
  3387. status = (HttpStatusCode)(-1);
  3388. Exception ex = null;
  3389. QueryOperationResponse qresponse = null;
  3390. try
  3391. {
  3392. status = batch.GetStatusCode();
  3393. ex = HandleResponse(status, batch.GetResponseVersion(), batch.GetContentStream, false);
  3394. if (null == ex)
  3395. {
  3396. DataServiceRequest query = this.Queries[queryCount];
  3397. MaterializeAtom materializer = DataServiceRequest.Materialize(this.Context, query.QueryComponents, null, contentType, batch.GetContentStream());
  3398. qresponse = QueryOperationResponse.GetInstance(query.ElementType, contentHeaders, query, materializer);
  3399. }
  3400. }
  3401. catch (ArgumentException e)
  3402. {
  3403. ex = e;
  3404. }
  3405. catch (FormatException e)
  3406. {
  3407. ex = e;
  3408. }
  3409. catch (InvalidOperationException e)
  3410. {
  3411. ex = e;
  3412. }
  3413. if (null == qresponse)
  3414. {
  3415. if (null != this.Queries)
  3416. {
  3417. DataServiceRequest query = this.Queries[queryCount];
  3418. if (this.Context.ignoreResourceNotFoundException && status == HttpStatusCode.NotFound)
  3419. {
  3420. qresponse = QueryOperationResponse.GetInstance(query.ElementType, contentHeaders, query, MaterializeAtom.EmptyResults);
  3421. }
  3422. else
  3423. {
  3424. qresponse = QueryOperationResponse.GetInstance(query.ElementType, contentHeaders, query, MaterializeAtom.EmptyResults);
  3425. qresponse.Error = ex;
  3426. }
  3427. }
  3428. else
  3429. {
  3430. throw ex;
  3431. }
  3432. }
  3433. qresponse.StatusCode = (int)status;
  3434. queryCount++;
  3435. yield return qresponse;
  3436. break;
  3437. #endregion
  3438. #region ChangeResponse
  3439. case BatchStreamState.ChangeResponse:
  3440. HttpStatusCode statusCode = batch.GetStatusCode();
  3441. Exception error = HandleResponse(statusCode, batch.GetResponseVersion(), batch.GetContentStream, false);
  3442. int index = this.ValidateContentID(contentHeaders);
  3443. try
  3444. {
  3445. entry = this.ChangedEntries[index];
  3446. operationCount += this.Context.SaveResultProcessed(entry);
  3447. if (null != error)
  3448. {
  3449. throw error;
  3450. }
  3451. StreamStates streamState = StreamStates.NoStream;
  3452. if (entry.IsResource)
  3453. {
  3454. EntityDescriptor descriptor = (EntityDescriptor)entry;
  3455. streamState = descriptor.StreamState;
  3456. #if DEBUG
  3457. if (descriptor.StreamState == StreamStates.Added)
  3458. {
  3459. Debug.Assert(
  3460. statusCode == HttpStatusCode.Created && entry.State == EntityStates.Modified && descriptor.IsMediaLinkEntry,
  3461. "statusCode == HttpStatusCode.Created && entry.State == EntityStates.Modified && descriptor.IsMediaLinkEntry -- Processing Post MR");
  3462. }
  3463. else if (descriptor.StreamState == StreamStates.Modified)
  3464. {
  3465. Debug.Assert(
  3466. statusCode == HttpStatusCode.NoContent && descriptor.IsMediaLinkEntry,
  3467. "statusCode == HttpStatusCode.NoContent && descriptor.IsMediaLinkEntry -- Processing Put MR");
  3468. }
  3469. #endif
  3470. }
  3471. if (streamState == StreamStates.Added || entry.State == EntityStates.Added)
  3472. {
  3473. #region Post
  3474. if (entry.IsResource)
  3475. {
  3476. string mime = null;
  3477. Encoding postEncoding = null;
  3478. contentHeaders.TryGetValue(XmlConstants.HttpContentType, out contentType);
  3479. contentHeaders.TryGetValue(XmlConstants.HttpResponseLocation, out location);
  3480. contentHeaders.TryGetValue(XmlConstants.HttpResponseETag, out etag);
  3481. EntityDescriptor entityDescriptor = (EntityDescriptor)entry;
  3482. if (location != null)
  3483. {
  3484. editLink = Util.CreateUri(location, UriKind.Absolute);
  3485. }
  3486. else
  3487. {
  3488. throw Error.NotSupported(Strings.Deserialize_NoLocationHeader);
  3489. }
  3490. Stream stream = batch.GetContentStream();
  3491. if (null != stream)
  3492. {
  3493. HttpProcessUtility.ReadContentType(contentType, out mime, out postEncoding);
  3494. if (!String.Equals(XmlConstants.MimeApplicationAtom, mime, StringComparison.OrdinalIgnoreCase))
  3495. {
  3496. throw Error.InvalidOperation(Strings.Deserialize_UnknownMimeTypeSpecified(mime));
  3497. }
  3498. XmlReader reader = XmlUtil.CreateXmlReader(stream, postEncoding);
  3499. QueryComponents qc = new QueryComponents(null, Util.DataServiceVersionEmpty, entityDescriptor.Entity.GetType(), null, null);
  3500. EntityDescriptor descriptor = (EntityDescriptor)entry;
  3501. MergeOption mergeOption = MergeOption.OverwriteChanges;
  3502. if (descriptor.StreamState == StreamStates.Added)
  3503. {
  3504. mergeOption = MergeOption.PreserveChanges;
  3505. Debug.Assert(descriptor.State == EntityStates.Modified, "The MLE state must be Modified.");
  3506. }
  3507. try
  3508. {
  3509. using (MaterializeAtom atom = new MaterializeAtom(this.Context, reader, qc, null, mergeOption))
  3510. {
  3511. this.Context.HandleResponsePost(entityDescriptor, atom, editLink, etag);
  3512. }
  3513. }
  3514. finally
  3515. {
  3516. if (descriptor.StreamState == StreamStates.Added)
  3517. {
  3518. Debug.Assert(descriptor.State == EntityStates.Unchanged, "The materializer should always set the entity state to Unchanged.");
  3519. descriptor.State = EntityStates.Modified;
  3520. descriptor.StreamState = StreamStates.NoStream;
  3521. }
  3522. }
  3523. }
  3524. else
  3525. {
  3526. this.Context.HandleResponsePost(entityDescriptor, null, editLink, etag);
  3527. }
  3528. }
  3529. else
  3530. {
  3531. HandleResponsePost((LinkDescriptor)entry);
  3532. }
  3533. #endregion
  3534. }
  3535. else if (streamState == StreamStates.Modified || entry.State == EntityStates.Modified)
  3536. {
  3537. #region Put, Merge
  3538. contentHeaders.TryGetValue(XmlConstants.HttpResponseETag, out etag);
  3539. HandleResponsePut(entry, etag);
  3540. #endregion
  3541. }
  3542. else if (entry.State == EntityStates.Deleted)
  3543. {
  3544. #region Delete
  3545. this.Context.HandleResponseDelete(entry);
  3546. #endregion
  3547. }
  3548. }
  3549. catch (Exception e)
  3550. {
  3551. this.ChangedEntries[index].SaveError = e;
  3552. error = e;
  3553. }
  3554. ChangeOperationResponse changeOperationResponse =
  3555. new ChangeOperationResponse(contentHeaders, this.ChangedEntries[index]);
  3556. changeOperationResponse.StatusCode = (int)statusCode;
  3557. if (error != null)
  3558. {
  3559. changeOperationResponse.Error = error;
  3560. }
  3561. this.Responses.Add(changeOperationResponse);
  3562. operationCount++;
  3563. this.entryIndex++;
  3564. yield return changeOperationResponse;
  3565. break;
  3566. #endregion
  3567. default:
  3568. Error.ThrowBatchExpectedResponse(InternalError.UnexpectedBatchState);
  3569. break;
  3570. }
  3571. }
  3572. Debug.Assert(batch.State == BatchStreamState.EndBatch, "unexpected batch state");
  3573. if ((null == this.Queries &&
  3574. (0 == changesetIndex ||
  3575. 0 < queryCount ||
  3576. this.ChangedEntries.Any(o => o.ContentGeneratedForSave && 0 == o.SaveResultWasProcessed) &&
  3577. (!IsFlagSet(this.options, SaveChangesOptions.Batch) || null == this.ChangedEntries.FirstOrDefault(o => null != o.SaveError)))) ||
  3578. (null != this.Queries && queryCount != this.Queries.Length))
  3579. {
  3580. throw Error.InvalidOperation(Strings.Batch_IncompleteResponseCount);
  3581. }
  3582. batch.Dispose();
  3583. }
  3584. private int ValidateContentID(Dictionary<string, string> contentHeaders)
  3585. {
  3586. int contentID = 0;
  3587. string contentValueID;
  3588. if (!contentHeaders.TryGetValue(XmlConstants.HttpContentID, out contentValueID) ||
  3589. !Int32.TryParse(contentValueID, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out contentID))
  3590. {
  3591. Error.ThrowBatchUnexpectedContent(InternalError.ChangeResponseMissingContentID);
  3592. }
  3593. for (int i = 0; i < this.ChangedEntries.Count; ++i)
  3594. {
  3595. if (this.ChangedEntries[i].ChangeOrder == contentID)
  3596. {
  3597. return i;
  3598. }
  3599. }
  3600. Error.ThrowBatchUnexpectedContent(InternalError.ChangeResponseUnknownContentID);
  3601. return -1;
  3602. }
  3603. #endregion Batch
  3604. #region callback handlers
  3605. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")]
  3606. private void AsyncEndGetRequestStream(IAsyncResult asyncResult)
  3607. {
  3608. Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted");
  3609. PerRequest pereq = asyncResult == null ? null : asyncResult.AsyncState as PerRequest;
  3610. try
  3611. {
  3612. CompleteCheck(pereq, InternalError.InvalidEndGetRequestCompleted);
  3613. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3614. EqualRefCheck(this.request, pereq, InternalError.InvalidEndGetRequestStream);
  3615. HttpWebRequest httpWebRequest = Util.NullCheck(pereq.Request, InternalError.InvalidEndGetRequestStreamRequest);
  3616. Stream stream = Util.NullCheck(httpWebRequest.EndGetRequestStream(asyncResult), InternalError.InvalidEndGetRequestStreamStream);
  3617. pereq.RequestStream = stream;
  3618. PerRequest.ContentStream contentStream = pereq.RequestContentStream;
  3619. Util.NullCheck(contentStream, InternalError.InvalidEndGetRequestStreamContent);
  3620. Util.NullCheck(contentStream.Stream, InternalError.InvalidEndGetRequestStreamContent);
  3621. if (contentStream.IsKnownMemoryStream)
  3622. {
  3623. MemoryStream memoryStream = contentStream.Stream as MemoryStream;
  3624. byte[] buffer = memoryStream.GetBuffer();
  3625. int bufferOffset = checked((int)memoryStream.Position);
  3626. int bufferLength = checked((int)memoryStream.Length) - bufferOffset;
  3627. if ((null == buffer) || (0 == bufferLength))
  3628. {
  3629. Error.ThrowInternalError(InternalError.InvalidEndGetRequestStreamContentLength);
  3630. }
  3631. }
  3632. pereq.RequestContentBufferValidLength = -1;
  3633. Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetRequestStream_BeforeBeginRead");
  3634. asyncResult = BaseAsyncResult.InvokeAsync(contentStream.Stream.BeginRead, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, pereq);
  3635. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3636. }
  3637. catch (Exception e)
  3638. {
  3639. if (this.HandleFailure(pereq, e))
  3640. {
  3641. throw;
  3642. }
  3643. }
  3644. finally
  3645. {
  3646. this.HandleCompleted(pereq);
  3647. }
  3648. }
  3649. private void AsyncRequestContentEndRead(IAsyncResult asyncResult)
  3650. {
  3651. Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted");
  3652. PerRequest pereq = asyncResult == null ? null : asyncResult.AsyncState as PerRequest;
  3653. try
  3654. {
  3655. CompleteCheck(pereq, InternalError.InvalidEndReadCompleted);
  3656. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3657. EqualRefCheck(this.request, pereq, InternalError.InvalidEndRead);
  3658. PerRequest.ContentStream contentStream = pereq.RequestContentStream;
  3659. Util.NullCheck(contentStream, InternalError.InvalidEndReadStream);
  3660. Util.NullCheck(contentStream.Stream, InternalError.InvalidEndReadStream);
  3661. Stream stream = Util.NullCheck(pereq.RequestStream, InternalError.InvalidEndReadStream);
  3662. Util.DebugInjectFault("SaveAsyncResult::AsyncRequestContentEndRead_BeforeEndRead");
  3663. int count = contentStream.Stream.EndRead(asyncResult);
  3664. if (0 < count)
  3665. {
  3666. bool firstEndRead = (pereq.RequestContentBufferValidLength == -1);
  3667. pereq.RequestContentBufferValidLength = count;
  3668. if (!asyncResult.CompletedSynchronously || firstEndRead)
  3669. {
  3670. do
  3671. {
  3672. Util.DebugInjectFault("SaveAsyncResult::AsyncRequestContentEndRead_BeforeBeginWrite");
  3673. asyncResult = BaseAsyncResult.InvokeAsync(stream.BeginWrite, pereq.RequestContentBuffer, 0, pereq.RequestContentBufferValidLength, this.AsyncEndWrite, pereq);
  3674. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3675. if (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally)
  3676. {
  3677. Util.DebugInjectFault("SaveAsyncResult::AsyncRequestContentEndRead_BeforeBeginRead");
  3678. asyncResult = BaseAsyncResult.InvokeAsync(contentStream.Stream.BeginRead, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, pereq);
  3679. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3680. }
  3681. }
  3682. while (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally &&
  3683. pereq.RequestContentBufferValidLength > 0);
  3684. }
  3685. }
  3686. else
  3687. {
  3688. pereq.RequestContentBufferValidLength = 0;
  3689. pereq.RequestStream = null;
  3690. stream.Close();
  3691. HttpWebRequest httpWebRequest = Util.NullCheck(pereq.Request, InternalError.InvalidEndWriteRequest);
  3692. asyncResult = BaseAsyncResult.InvokeAsync(httpWebRequest.BeginGetResponse, this.AsyncEndGetResponse, pereq);
  3693. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3694. }
  3695. }
  3696. catch (Exception e)
  3697. {
  3698. if (this.HandleFailure(pereq, e))
  3699. {
  3700. throw;
  3701. }
  3702. }
  3703. finally
  3704. {
  3705. this.HandleCompleted(pereq);
  3706. }
  3707. }
  3708. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")]
  3709. private void AsyncEndWrite(IAsyncResult asyncResult)
  3710. {
  3711. Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted");
  3712. PerRequest pereq = asyncResult == null ? null : asyncResult.AsyncState as PerRequest;
  3713. try
  3714. {
  3715. CompleteCheck(pereq, InternalError.InvalidEndWriteCompleted);
  3716. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3717. EqualRefCheck(this.request, pereq, InternalError.InvalidEndWrite);
  3718. PerRequest.ContentStream contentStream = pereq.RequestContentStream;
  3719. Util.NullCheck(contentStream, InternalError.InvalidEndWriteStream);
  3720. Util.NullCheck(contentStream.Stream, InternalError.InvalidEndWriteStream);
  3721. Stream stream = Util.NullCheck(pereq.RequestStream, InternalError.InvalidEndWriteStream);
  3722. Util.DebugInjectFault("SaveAsyncResult::AsyncEndWrite_BeforeEndWrite");
  3723. stream.EndWrite(asyncResult);
  3724. if (!asyncResult.CompletedSynchronously)
  3725. {
  3726. Util.DebugInjectFault("SaveAsyncResult::AsyncEndWrite_BeforeBeginRead");
  3727. asyncResult = BaseAsyncResult.InvokeAsync(contentStream.Stream.BeginRead, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, pereq);
  3728. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3729. }
  3730. }
  3731. catch (Exception e)
  3732. {
  3733. if (this.HandleFailure(pereq, e))
  3734. {
  3735. throw;
  3736. }
  3737. }
  3738. finally
  3739. {
  3740. this.HandleCompleted(pereq);
  3741. }
  3742. }
  3743. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")]
  3744. private void AsyncEndGetResponse(IAsyncResult asyncResult)
  3745. {
  3746. Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted");
  3747. PerRequest pereq = asyncResult == null ? null : asyncResult.AsyncState as PerRequest;
  3748. try
  3749. {
  3750. CompleteCheck(pereq, InternalError.InvalidEndGetResponseCompleted);
  3751. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3752. EqualRefCheck(this.request, pereq, InternalError.InvalidEndGetResponse);
  3753. HttpWebRequest httpWebRequest = Util.NullCheck(pereq.Request, InternalError.InvalidEndGetResponseRequest);
  3754. HttpWebResponse response = null;
  3755. try
  3756. {
  3757. Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetResponse::BeforeEndGetResponse");
  3758. response = (HttpWebResponse)httpWebRequest.EndGetResponse(asyncResult);
  3759. }
  3760. catch (WebException e)
  3761. {
  3762. response = (HttpWebResponse)e.Response;
  3763. if (null == response)
  3764. {
  3765. throw;
  3766. }
  3767. }
  3768. pereq.HttpWebResponse = Util.NullCheck(response, InternalError.InvalidEndGetResponseResponse);
  3769. if (!IsFlagSet(this.options, SaveChangesOptions.Batch))
  3770. {
  3771. this.HandleOperationResponse(response);
  3772. }
  3773. this.copiedContentLength = 0;
  3774. Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetResponse_BeforeGetStream");
  3775. Stream stream = response.GetResponseStream();
  3776. pereq.ResponseStream = stream;
  3777. if ((null != stream) && stream.CanRead)
  3778. {
  3779. if (null != this.buildBatchWriter)
  3780. {
  3781. this.buildBatchWriter.Flush();
  3782. }
  3783. if (null == this.buildBatchBuffer)
  3784. {
  3785. this.buildBatchBuffer = new byte[8000];
  3786. }
  3787. do
  3788. {
  3789. Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetResponse_BeforeBeginRead");
  3790. asyncResult = BaseAsyncResult.InvokeAsync(stream.BeginRead, this.buildBatchBuffer, 0, this.buildBatchBuffer.Length, this.AsyncEndRead, pereq);
  3791. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3792. }
  3793. while (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally && stream.CanRead);
  3794. }
  3795. else
  3796. {
  3797. pereq.SetComplete();
  3798. if (!this.IsCompletedInternally)
  3799. {
  3800. this.SaveNextChange(pereq);
  3801. }
  3802. }
  3803. }
  3804. catch (Exception e)
  3805. {
  3806. if (this.HandleFailure(pereq, e))
  3807. {
  3808. throw;
  3809. }
  3810. }
  3811. finally
  3812. {
  3813. this.HandleCompleted(pereq);
  3814. }
  3815. }
  3816. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")]
  3817. private void AsyncEndRead(IAsyncResult asyncResult)
  3818. {
  3819. Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted");
  3820. PerRequest pereq = asyncResult.AsyncState as PerRequest;
  3821. int count = 0;
  3822. try
  3823. {
  3824. CompleteCheck(pereq, InternalError.InvalidEndReadCompleted);
  3825. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3826. EqualRefCheck(this.request, pereq, InternalError.InvalidEndRead);
  3827. Stream stream = Util.NullCheck(pereq.ResponseStream, InternalError.InvalidEndReadStream);
  3828. Util.DebugInjectFault("SaveAsyncResult::AsyncEndRead_BeforeEndRead");
  3829. count = stream.EndRead(asyncResult);
  3830. if (0 < count)
  3831. {
  3832. Stream outputResponse = Util.NullCheck(this.httpWebResponseStream, InternalError.InvalidEndReadCopy);
  3833. outputResponse.Write(this.buildBatchBuffer, 0, count);
  3834. this.copiedContentLength += count;
  3835. if (!asyncResult.CompletedSynchronously && stream.CanRead)
  3836. {
  3837. do
  3838. {
  3839. asyncResult = BaseAsyncResult.InvokeAsync(stream.BeginRead, this.buildBatchBuffer, 0, this.buildBatchBuffer.Length, this.AsyncEndRead, pereq);
  3840. pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
  3841. }
  3842. while (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally && stream.CanRead);
  3843. }
  3844. }
  3845. else
  3846. {
  3847. pereq.SetComplete();
  3848. if (!this.IsCompletedInternally)
  3849. {
  3850. this.SaveNextChange(pereq);
  3851. }
  3852. }
  3853. }
  3854. catch (Exception e)
  3855. {
  3856. if (this.HandleFailure(pereq, e))
  3857. {
  3858. throw;
  3859. }
  3860. }
  3861. finally
  3862. {
  3863. this.HandleCompleted(pereq);
  3864. }
  3865. }
  3866. private void SaveNextChange(PerRequest pereq)
  3867. {
  3868. Debug.Assert(this.executeAsync, "should be async");
  3869. if (!pereq.RequestCompleted)
  3870. {
  3871. Error.ThrowInternalError(InternalError.SaveNextChangeIncomplete);
  3872. }
  3873. EqualRefCheck(this.request, pereq, InternalError.InvalidSaveNextChange);
  3874. if (IsFlagSet(this.options, SaveChangesOptions.Batch))
  3875. {
  3876. this.httpWebResponseStream.Position = 0;
  3877. this.request = null;
  3878. this.SetCompleted();
  3879. }
  3880. else
  3881. {
  3882. if (0 == this.copiedContentLength)
  3883. {
  3884. this.HandleOperationResponseNoData();
  3885. }
  3886. this.HandleOperationEnd();
  3887. if (!this.processingMediaLinkEntry)
  3888. {
  3889. this.changesCompleted++;
  3890. }
  3891. pereq.Dispose();
  3892. this.request = null;
  3893. if (!pereq.RequestCompletedSynchronously)
  3894. {
  3895. if (!this.IsCompletedInternally)
  3896. {
  3897. this.BeginNextChange(IsFlagSet(this.options, SaveChangesOptions.ReplaceOnUpdate));
  3898. }
  3899. }
  3900. }
  3901. }
  3902. #endregion
  3903. private sealed class PerRequest
  3904. {
  3905. private int requestStatus;
  3906. private byte[] requestContentBuffer;
  3907. internal PerRequest()
  3908. {
  3909. this.RequestCompletedSynchronously = true;
  3910. }
  3911. internal HttpWebRequest Request
  3912. {
  3913. get;
  3914. set;
  3915. }
  3916. internal Stream RequestStream
  3917. {
  3918. get;
  3919. set;
  3920. }
  3921. internal ContentStream RequestContentStream
  3922. {
  3923. get;
  3924. set;
  3925. }
  3926. internal HttpWebResponse HttpWebResponse
  3927. {
  3928. get;
  3929. set;
  3930. }
  3931. internal Stream ResponseStream
  3932. {
  3933. get;
  3934. set;
  3935. }
  3936. internal bool RequestCompletedSynchronously
  3937. {
  3938. get;
  3939. set;
  3940. }
  3941. internal bool RequestCompleted
  3942. {
  3943. get { return this.requestStatus != 0; }
  3944. }
  3945. internal bool RequestAborted
  3946. {
  3947. get { return this.requestStatus == 2; }
  3948. }
  3949. internal byte[] RequestContentBuffer
  3950. {
  3951. get
  3952. {
  3953. if (this.requestContentBuffer == null)
  3954. {
  3955. this.requestContentBuffer = new byte[64 * 1024];
  3956. }
  3957. return this.requestContentBuffer;
  3958. }
  3959. }
  3960. internal int RequestContentBufferValidLength
  3961. {
  3962. get;
  3963. set;
  3964. }
  3965. internal void SetComplete()
  3966. {
  3967. System.Threading.Interlocked.CompareExchange(ref this.requestStatus, 1, 0);
  3968. }
  3969. internal void SetAborted()
  3970. {
  3971. System.Threading.Interlocked.Exchange(ref this.requestStatus, 2);
  3972. }
  3973. internal void Dispose()
  3974. {
  3975. Stream stream = null;
  3976. if (null != (stream = this.ResponseStream))
  3977. {
  3978. this.ResponseStream = null;
  3979. stream.Dispose();
  3980. }
  3981. if (null != this.RequestContentStream)
  3982. {
  3983. if (this.RequestContentStream.Stream != null && this.RequestContentStream.IsKnownMemoryStream)
  3984. {
  3985. this.RequestContentStream.Stream.Dispose();
  3986. }
  3987. this.RequestContentStream = null;
  3988. }
  3989. if (null != (stream = this.RequestStream))
  3990. {
  3991. this.RequestStream = null;
  3992. try
  3993. {
  3994. Util.DebugInjectFault("PerRequest::Dispose_BeforeRequestStreamDisposed");
  3995. stream.Dispose();
  3996. }
  3997. catch (WebException)
  3998. {
  3999. if (!this.RequestAborted)
  4000. {
  4001. throw;
  4002. }
  4003. Util.DebugInjectFault("PerRequest::Dispose_WebExceptionThrown");
  4004. }
  4005. }
  4006. HttpWebResponse response = this.HttpWebResponse;
  4007. if (null != response)
  4008. {
  4009. response.Close();
  4010. }
  4011. this.Request = null;
  4012. this.SetComplete();
  4013. }
  4014. internal class ContentStream
  4015. {
  4016. private readonly Stream stream;
  4017. private readonly bool isKnownMemoryStream;
  4018. public ContentStream(Stream stream, bool isKnownMemoryStream)
  4019. {
  4020. this.stream = stream;
  4021. this.isKnownMemoryStream = isKnownMemoryStream;
  4022. }
  4023. public Stream Stream
  4024. {
  4025. get { return this.stream; }
  4026. }
  4027. public bool IsKnownMemoryStream
  4028. {
  4029. get { return this.isKnownMemoryStream; }
  4030. }
  4031. }
  4032. }
  4033. }
  4034. }
  4035. }