/// <summary> /// Handler that the user will call when they want the request to resume processing. /// It will check to ensure that the correct WebRequest is passed back to this resumption point. /// Else an error will be thrown. /// </summary> /// <param name="request">HttpWebRequest for which the processing has to resume.</param> void ResumeRequestProcessing(HttpWebRequest request) { AsyncArgsWrapper wrapper = null; this._requestToArgsMapper.TryGetValue(request.GetHashCode(), out wrapper); if (wrapper == null) { // It means they called Resume with another WebRequest. Fail sync. throw new CacheControllerException("Incorrect HttpWebRequest object passed to ResumeRequestProcessing callback."); } try { this._requestToArgsMapper.Remove(request.GetHashCode()); this.GetWebResponse(wrapper); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Check Metadata /// </summary> private void CheckEntityServiceMetadataAndTempIds(AsyncArgsWrapper wrapper, IOfflineEntity entity, string tempId) { // Check service ID if (string.IsNullOrEmpty(entity.GetServiceMetadata().Id)) { throw new CacheControllerException( string.Format("Service did not return a permanent Id for tempId '{0}'", tempId)); } // If an entity has a temp id then it should not be a tombstone if (entity.GetServiceMetadata().IsTombstone) { throw new CacheControllerException(string.Format( "Service returned a tempId '{0}' in tombstoned entity.", tempId)); } // Check that the tempId was sent by client if (!wrapper.TempIdToEntityMapping.ContainsKey(tempId)) { throw new CacheControllerException( "Service returned a response for a tempId which was not uploaded by the client. TempId: " + tempId); } // Once received, remove the tempId from the mapping list. wrapper.TempIdToEntityMapping.Remove(tempId); }
/// <summary> /// Callback invoked when the cache request has been processed. /// </summary> /// <param name="state">AsyncArgsWrapper object</param> void CacheRequestCompleted(object state) { // Fire the ProcessCacheRequestCompleted handler AsyncArgsWrapper wrapper = state as AsyncArgsWrapper; if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { base.OnProcessCacheRequestCompleted( new ProcessCacheRequestCompletedEventArgs( wrapper.CacheRequest.RequestId, wrapper.UploadResponse, wrapper.Error, wrapper.UserPassedState) ); } else { base.OnProcessCacheRequestCompleted( new ProcessCacheRequestCompletedEventArgs( wrapper.CacheRequest.RequestId, wrapper.DownloadResponse, wrapper.Error, wrapper.UserPassedState) ); } }
/// <summary> /// Issues the BeginGetResponse call for the HttpWebRequest /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> private void GetWebResponse(AsyncArgsWrapper wrapper) { var requestData = new TimeoutRequestData() { Request = wrapper.WebRequest }; // Send the request and wait for the response. if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { lock (_timeoutSync) { IAsyncResult resultUpload = wrapper.WebRequest.BeginGetResponse(OnUploadGetResponseCompleted, wrapper); requestData.Handle = ThreadPool.RegisterWaitForSingleObject(resultUpload.AsyncWaitHandle, new WaitOrTimerCallback(TimeOutCallback), requestData, TIMEOUT, true); } } else { lock (_timeoutSync) { IAsyncResult resultDownload = wrapper.WebRequest.BeginGetResponse(OnDownloadGetResponseCompleted, wrapper); requestData.Handle = ThreadPool.RegisterWaitForSingleObject(resultDownload.AsyncWaitHandle, new WaitOrTimerCallback(TimeOutCallback), requestData, TIMEOUT, true); } } }
/// <summary> /// Callback for the Download HttpWebRequest.beginGetRequestStream /// </summary> private void WriteDownloadRequestStream(Stream requestStream, AsyncArgsWrapper wrapper) { try { // Create a SyncWriter to write the contents var syncWriter = (SerializationFormat == SerializationFormat.ODataAtom) ? new ODataAtomWriter(BaseUri) : (SyncWriter) new ODataJsonWriter(BaseUri); //syncWriter = new ODataAtomWriter(BaseUri); syncWriter.StartFeed(wrapper.CacheRequest.IsLastBatch, wrapper.CacheRequest.KnowledgeBlob ?? new byte[0]); if (SerializationFormat == SerializationFormat.ODataAtom) { syncWriter.WriteFeed(XmlWriter.Create(requestStream)); } else { syncWriter.WriteFeed(new XmlJsonWriter(requestStream)); } requestStream.Flush(); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; } }
private IEnumerable <IsolatedStorageOfflineEntity> GetDownloadedValues(AsyncArgsWrapper wrapper) { while (this._syncReader.Next()) { switch (_syncReader.ItemType) { case ReaderItemType.Entry: IsolatedStorageOfflineEntity offlineEntity = _syncReader.GetItem(); var entity = offlineEntity as IEntity; if (entity != null) { LogManager.Logger.SyncDownload(entity.EntityType, offlineEntity.ServiceMetadata.IsTombstone); } yield return(offlineEntity); break; case ReaderItemType.SyncBlob: wrapper.DownloadResponse.ServerBlob = _syncReader.GetServerBlob(); break; case ReaderItemType.HasMoreChanges: wrapper.DownloadResponse.IsLastBatch = !_syncReader.GetHasMoreChangesValue(); break; } } _syncReader.Dispose(); }
/// <summary> /// Invokes the user's AfterReceivingResponse callback. /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> void FirePostResponseHandler(AsyncArgsWrapper wrapper) { if (this._afterResponseHandler != null) { // Invoke the user code. this._afterResponseHandler(wrapper.WebResponse); } }
/// <summary> /// Callback for the Upload HttpWebRequest.beginGetRequestStream /// </summary> private void WriteUploadRequestStream(Stream requestStream, AsyncArgsWrapper wrapper) { try { // Create a SyncWriter to write the contents this.syncWriter = (SerializationFormat == SerializationFormat.ODataAtom) ? new ODataAtomWriter(BaseUri) : (SyncWriter) new ODataJsonWriter(BaseUri); syncWriter.StartFeed(wrapper.CacheRequest.IsLastBatch, wrapper.CacheRequest.KnowledgeBlob ?? new byte[0]); foreach (IOfflineEntity entity in wrapper.CacheRequest.Changes) { // Skip tombstones that dont have a ID element. if (entity.GetServiceMetadata().IsTombstone&& string.IsNullOrEmpty(entity.GetServiceMetadata().Id)) { continue; } string tempId = null; // Check to see if this is an insert. i.e ServiceMetadata.Id is null or empty if (string.IsNullOrEmpty(entity.GetServiceMetadata().Id)) { if (wrapper.TempIdToEntityMapping == null) { wrapper.TempIdToEntityMapping = new Dictionary <string, IOfflineEntity>(); } tempId = Guid.NewGuid().ToString(); wrapper.TempIdToEntityMapping.Add(tempId, entity); } syncWriter.AddItem(entity, tempId); } if (SerializationFormat == SerializationFormat.ODataAtom) { syncWriter.WriteFeed(XmlWriter.Create(requestStream)); } else { this.syncWriter.WriteFeed(new XmlJsonWriter(requestStream)); } requestStream.Flush(); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; } }
/// <summary> /// Callback for the Download HttpWebRequest.beginGetRequestStream. Deserializes the response feed to /// retrieve the list of IOfflineEntity objects and constructs an ChangeSet for that. /// </summary> private async Task ReadDownloadResponse(HttpWebResponse response, AsyncArgsWrapper wrapper) { try { if (response.StatusCode == HttpStatusCode.OK) { using (Stream responseStream = GetWrappedStream(response)) { // Create the SyncReader using (var syncReader = (SerializationFormat == SerializationFormat.ODataAtom) ? new ODataAtomReader(responseStream, this.knownTypes) : (SyncReader) new ODataJsonReader(responseStream, this.knownTypes)) { await Task.Factory.StartNew(() => { // Read the response while (syncReader.Next()) { switch (syncReader.ItemType) { case ReaderItemType.Entry: wrapper.DownloadResponse.AddItem(syncReader.GetItem()); break; case ReaderItemType.SyncBlob: wrapper.DownloadResponse.ServerBlob = syncReader.GetServerBlob(); // Debug.WriteLine(SyncBlob.DeSerialize(wrapper.DownloadResponse.ServerBlob).ToString()); break; case ReaderItemType.HasMoreChanges: wrapper.DownloadResponse.IsLastBatch = !syncReader.GetHasMoreChangesValue(); break; } } }); } } } else { wrapper.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; } }
/// <summary> /// Issues the BeginGetResponse call for the HttpWebRequest /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> private void GetWebResponse(AsyncArgsWrapper wrapper) { // Send the request and wait for the response. if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { wrapper.WebRequest.BeginGetResponse(OnUploadGetResponseCompleted, wrapper); } else { wrapper.WebRequest.BeginGetResponse(OnDownloadGetResponseCompleted, wrapper); } }
/// <summary> /// Callback for the Download HttpWebRequest.beginGetRequestStream /// </summary> /// <param name="asyncResult">IAsyncResult object</param> void OnDownloadGetRequestStreamCompleted(IAsyncResult asyncResult) { AsyncArgsWrapper wrapper = asyncResult.AsyncState as AsyncArgsWrapper; try { Stream requestStream = wrapper.WebRequest.EndGetRequestStream(asyncResult); // CreateInstance a SyncWriter to write the contents if (ApplicationContext.Current.Settings.BitMobileFormatterDisabled) { _syncWriter = new ODataAtomWriter(BaseUri); } else { _syncWriter = new BMWriter(BaseUri); } this._syncWriter.StartFeed(wrapper.CacheRequest.IsLastBatch, wrapper.CacheRequest.KnowledgeBlob ?? new byte[0]); if (base.SerializationFormat == SerializationFormat.ODataAtom) { this._syncWriter.WriteFeed(XmlWriter.Create(requestStream)); } else { this._syncWriter.WriteFeed(JsonReaderWriterFactory.CreateJsonWriter(requestStream)); } requestStream.Flush(); requestStream.Close(); if (this._beforeRequestHandler != null) { // Invoke user code and wait for them to call back us when they are done with the input request this._workerManager.PostProgress(wrapper.WorkerRequest, this.FirePreRequestHandler, wrapper); } else { this.GetWebResponse(wrapper); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Issues the BeginGetResponse call for the HttpWebRequest /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> private async Task GetWebResponse(AsyncArgsWrapper wrapper) { // Send the request and wait for the response. if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { var task = wrapper.WebRequest.GetResponseAsync(); await OnUploadGetResponseCompleted(task, wrapper).ConfigureAwait(false); } else { var task = wrapper.WebRequest.GetResponseAsync(); await OnDownloadGetResponseCompleted(task, wrapper).ConfigureAwait(false); } }
void ProcessCacheRequestWorker(AsyncWorkRequest worker, object[] inputParams) { CacheRequest request = inputParams [0] as CacheRequest; object state = inputParams [1]; _wrapper = new AsyncArgsWrapper() { UserPassedState = state, WorkerRequest = worker, CacheRequest = request }; ProcessRequest(); }
/// <summary> /// Actual worker performing the work /// </summary> /// <param name="worker">AsyncWorkRequest object</param> /// <param name="inputParams">input parameters</param> void ProcessCacheRequestWorker(AsyncWorkRequest worker, object[] inputParams) { Debug.Assert(inputParams.Length == 2); CacheRequest request = inputParams[0] as CacheRequest; object state = inputParams[1]; AsyncArgsWrapper wrapper = new AsyncArgsWrapper() { UserPassedState = state, WorkerRequest = worker, CacheRequest = request }; ProcessRequest(wrapper); }
/// <summary> /// Callback for the Download HttpWebRequest.beginGetRequestStream /// </summary> /// <param name="task">Task representing the future request stream</param> async Task OnDownloadGetRequestStreamCompleted(Task <Stream> task, AsyncArgsWrapper wrapper) { try { Stream requestStream = await task.ConfigureAwait(false); // Create a SyncWriter to write the contents this._syncWriter = (base.SerializationFormat == SerializationFormat.ODataAtom) ? (SyncWriter) new ODataAtomWriter(base.BaseUri) : (SyncWriter) new ODataJsonWriter(base.BaseUri); this._syncWriter.StartFeed(wrapper.CacheRequest.IsLastBatch, wrapper.CacheRequest.KnowledgeBlob ?? new byte[0]); if (base.SerializationFormat == SerializationFormat.ODataAtom) { this._syncWriter.WriteFeed(XmlWriter.Create(requestStream)); } else { this._syncWriter.WriteFeed(JsonReaderWriterFactory.CreateJsonWriter(requestStream)); } requestStream.Flush(); requestStream.Close(); if (this._beforeRequestHandler != null) { // Invoke user code and wait for them to call back us when they are done with the input request this._workerManager.PostProgress(wrapper.WorkerRequest, this.FirePreRequestHandler, wrapper); } else { this.GetWebResponse(wrapper); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Called by the CacheController when it wants this CacheRequest to be processed asynchronously. /// </summary> /// <param name="request">CacheRequest to be processed</param> /// <param name="state">User state object</param> /// <param name="cancellationToken"> </param> public async Task <CacheRequestResult> ProcessCacheRequestAsync(CacheRequest request, object state, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } var wrapper = new AsyncArgsWrapper { UserPassedState = state, CacheRequest = request, Step = HttpState.Start }; wrapper = await ProcessRequest(wrapper, cancellationToken); CacheRequestResult cacheRequestResult; if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { cacheRequestResult = new CacheRequestResult( wrapper.CacheRequest.RequestId, wrapper.UploadResponse, wrapper.CacheRequest.Changes.Count, wrapper.Error, wrapper.Step, wrapper.UserPassedState); } else { cacheRequestResult = new CacheRequestResult( wrapper.CacheRequest.RequestId, wrapper.DownloadResponse, wrapper.Error, wrapper.Step, wrapper.UserPassedState); } return(cacheRequestResult); }
/// <summary> /// Called by the CacheController when it wants this CacheRequest to be processed asynchronously. /// </summary> /// <param name="request">CacheRequest to be processed</param> /// <param name="state">User state object</param> /// <param name="cancellationToken"> </param> public async Task<CacheRequestResult> ProcessCacheRequestAsync(CacheRequest request, object state, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); var wrapper = new AsyncArgsWrapper { UserPassedState = state, CacheRequest = request, Step = HttpState.Start }; wrapper = await ProcessRequest(wrapper, cancellationToken); CacheRequestResult cacheRequestResult; if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { cacheRequestResult = new CacheRequestResult( wrapper.CacheRequest.RequestId, wrapper.UploadResponse, wrapper.CacheRequest.Changes.Count, wrapper.Error, wrapper.Step, wrapper.UserPassedState); } else { cacheRequestResult = new CacheRequestResult( wrapper.CacheRequest.RequestId, wrapper.DownloadResponse, wrapper.Error, wrapper.Step, wrapper.UserPassedState); } return cacheRequestResult; }
/// <summary> /// Invokes the user BeforeSendingRequest callback. It also passes the resumption handler for the user /// to call when they are done with the customizations on the request. /// </summary> /// <param name="state">Async user state object. Ignored.</param> void FirePreRequestHandler(object state) { AsyncArgsWrapper wrapper = state as AsyncArgsWrapper; try { // Add this to the requestToArgs mapper so we can look up the args when the user calls ResumeRequestProcessing this._requestToArgsMapper[wrapper.WebRequest.GetHashCode()] = wrapper; // Invoke the user code. this._beforeRequestHandler(wrapper.WebRequest, ResumeRequestProcessing); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Method that does the actual processing. /// 1. It first creates an HttpWebRequest /// 2. Fills in the required method type and parameters. /// 3. Attaches the user specified ICredentials. /// 4. Serializes the input params (Server blob for downloads and input feed for uploads) /// 5. If user has specified an BeforeSendingRequest callback then invokes it /// 6. Else proceeds to issue the request /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> /// <param name="cancellationToken"> </param> private async Task<AsyncArgsWrapper> ProcessRequest(AsyncArgsWrapper wrapper, CancellationToken cancellationToken) { HttpWebResponse webResponse = null; try { if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); var requestUri = new StringBuilder(); requestUri.AppendFormat("{0}{1}{2}/{3}", BaseUri, (BaseUri.ToString().EndsWith("/")) ? string.Empty : "/", Uri.EscapeUriString(ScopeName), wrapper.CacheRequest.RequestType.ToString()); string prefix = "?"; // Add the scope params if any foreach (var kvp in scopeParameters) { requestUri.AppendFormat("{0}{1}={2}", prefix, Uri.EscapeUriString(kvp.Key), Uri.EscapeUriString(kvp.Value)); if (prefix.Equals("?")) prefix = "&"; } // Create the WebRequest HttpWebRequest webRequest; if (credentials != null) { // Create the Client Http request webRequest = WebRequest.CreateHttp(new Uri(requestUri.ToString())); // Add credentials webRequest.Credentials = credentials; } else { // Use WebRequest.Create the request. This uses any user defined prefix preferences for certain paths webRequest = (HttpWebRequest)WebRequest.Create(requestUri.ToString()); } // set cookies if exists if (CookieContainer != null) webRequest.CookieContainer = CookieContainer; // Set the method type webRequest.Method = "POST"; webRequest.Accept = (SerializationFormat == SerializationFormat.ODataAtom) ? "application/atom+xml" : "application/json"; webRequest.ContentType = (SerializationFormat == SerializationFormat.ODataAtom) ? "application/atom+xml" : "application/json"; #if !WINDOWS_PHONE if (automaticDecompression) { webRequest.Headers["Accept-Encoding"] = "gzip, deflated"; } #endif foreach (var kvp in customHeaders) { webRequest.Headers[kvp.Key] = kvp.Value; } // To be sure where it could be raise an error, just mark the step wrapper.Step = HttpState.WriteRequest; #if !NETFX_CORE using (Stream stream = await Task.Factory.FromAsync<Stream>( webRequest.BeginGetRequestStream, webRequest.EndGetRequestStream, null)) #else using (Stream stream = await webRequest.GetRequestStreamAsync()) #endif { if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) WriteUploadRequestStream(stream, wrapper); else WriteDownloadRequestStream(stream, wrapper); } // If error, return wrapper with error if (wrapper.Error != null) return wrapper; // Get Response if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) wrapper.UploadResponse = new ChangeSetResponse(); else wrapper.DownloadResponse = new ChangeSet(); wrapper.Step = HttpState.ReadResponse; #if !NETFX_CORE webResponse = (HttpWebResponse)(await Task.Factory.FromAsync<WebResponse>( webRequest.BeginGetResponse, webRequest.EndGetResponse, null)); #else webResponse = (HttpWebResponse)(await webRequest.GetResponseAsync()); #endif if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) await ReadUploadResponse(webResponse, wrapper); else await ReadDownloadResponse(webResponse, wrapper); if (webResponse != null) { wrapper.Step = HttpState.End; webResponse.Dispose(); webResponse = null; } } catch (WebException we) { // if (we.Response == null) // { // default to this, there will be cases where the we.Response is != null but the content length = 0 // e.g 413 and other server errors. e.g the else branch of reader.ReadToDescendent. By defaulting to this // we always capture the error rather then returning an error of null in some cases wrapper.Error = we; //} if(we.Response != null) { using (var stream = GetWrappedStream(we.Response)) { using (var reader = SerializationFormat == SerializationFormat.ODataAtom ? XmlReader.Create(stream) : new XmlJsonReader(stream, XmlDictionaryReaderQuotas.Max)) { if (reader.ReadToDescendant(FormatterConstants.ErrorDescriptionElementNamePascalCasing)) { wrapper.Error = new Exception(reader.ReadElementContentAsString()); } } } } } catch (OperationCanceledException) { // Re throw the operation cancelled throw; } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) throw; wrapper.Error = e; return wrapper; } return wrapper; }
/// <summary> /// Callback for the Upload HttpWebRequest.beginGetRequestStream /// </summary> private void WriteUploadRequestStream(Stream requestStream, AsyncArgsWrapper wrapper) { try { // Create a SyncWriter to write the contents var syncWriter = (SerializationFormat == SerializationFormat.ODataAtom) ? new ODataAtomWriter(BaseUri) : (SyncWriter)new ODataJsonWriter(BaseUri); syncWriter.StartFeed(wrapper.CacheRequest.IsLastBatch, wrapper.CacheRequest.KnowledgeBlob ?? new byte[0]); foreach (IOfflineEntity entity in wrapper.CacheRequest.Changes) { // Skip tombstones that dont have a ID element. if (entity.GetServiceMetadata().IsTombstone && string.IsNullOrEmpty(entity.GetServiceMetadata().Id)) continue; string tempId = null; // Check to see if this is an insert. i.e ServiceMetadata.Id is null or empty if (string.IsNullOrEmpty(entity.GetServiceMetadata().Id)) { if (wrapper.TempIdToEntityMapping == null) wrapper.TempIdToEntityMapping = new Dictionary<string, IOfflineEntity>(); tempId = Guid.NewGuid().ToString(); wrapper.TempIdToEntityMapping.Add(tempId, entity); } syncWriter.AddItem(entity, tempId); } if (SerializationFormat == SerializationFormat.ODataAtom) syncWriter.WriteFeed(XmlWriter.Create(requestStream)); else syncWriter.WriteFeed(new XmlJsonWriter(requestStream)); requestStream.Flush(); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; } }
/// <summary> /// Callback for the Upload HttpWebRequest.BeginGetResponse call /// </summary> private async Task ReadUploadResponse(HttpWebResponse response, AsyncArgsWrapper wrapper) { try { if (response.StatusCode == HttpStatusCode.OK) { using (Stream responseStream = GetWrappedStream(response)) { using (var syncReader = (SerializationFormat == SerializationFormat.ODataAtom) ? new ODataAtomReader(responseStream, this.knownTypes) : (SyncReader) new ODataJsonReader(responseStream, this.knownTypes)) { // Read the response await Task.Factory.StartNew(() => { while (syncReader.Next()) { switch (syncReader.ItemType) { case ReaderItemType.Entry: IOfflineEntity entity = syncReader.GetItem(); IOfflineEntity ackedEntity = entity; string tempId = null; // If conflict only one temp ID should be set if (syncReader.HasTempId() && syncReader.HasConflictTempId()) { throw new CacheControllerException( string.Format( "Service returned a TempId '{0}' in both live and conflicting entities.", syncReader.GetTempId())); } // Validate the live temp ID if any, before adding anything to the offline context if (syncReader.HasTempId()) { tempId = syncReader.GetTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, entity, tempId); } // If conflict if (syncReader.HasConflict()) { Conflict conflict = syncReader.GetConflict(); IOfflineEntity conflictEntity = (conflict is SyncConflict) ? ((SyncConflict)conflict).LosingEntity : ((SyncError)conflict).ErrorEntity; // Validate conflict temp ID if any if (syncReader.HasConflictTempId()) { tempId = syncReader.GetConflictTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, conflictEntity, tempId); } // Add conflict wrapper.UploadResponse.AddConflict(conflict); // // If there is a conflict and the tempId is set in the conflict entity then the client version lost the // conflict and the live entity is the server version (ServerWins) // if (syncReader.HasConflictTempId() && entity.GetServiceMetadata().IsTombstone) { // // This is a ServerWins conflict, or conflict error. The winning version is a tombstone without temp Id // so there is no way to map the winning entity with a temp Id. The temp Id is in the conflict so we are // using the conflict entity, which has the PK, to build a tombstone entity used to update the offline context // // In theory, we should copy the service metadata but it is the same end result as the service fills in // all the properties in the conflict entity // // Add the conflict entity conflictEntity.GetServiceMetadata().IsTombstone = true; ackedEntity = conflictEntity; } } // Add ackedEntity to storage. If ackedEntity is still equal to entity then add non-conflict entity. if (!String.IsNullOrEmpty(tempId)) { wrapper.UploadResponse.AddUpdatedItem(ackedEntity); } break; case ReaderItemType.SyncBlob: wrapper.UploadResponse.ServerBlob = syncReader.GetServerBlob(); break; } } }); if (wrapper.TempIdToEntityMapping != null && wrapper.TempIdToEntityMapping.Count != 0) { // The client sent some inserts which werent ack'd by the service. Throw. var builder = new StringBuilder( "Server did not acknowledge with a permanent Id for the following tempId's: "); builder.Append(string.Join(",", wrapper.TempIdToEntityMapping.Keys.ToArray())); throw new CacheControllerException(builder.ToString()); } } } } else { wrapper.UploadResponse.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; } }
/// <summary> /// Callback for the Download HttpWebRequest.beginGetRequestStream. Deserializes the response feed to /// retrieve the list of IOfflineEntity objects and constructs an ChangeSet for that. /// </summary> private async Task ReadDownloadResponse(HttpWebResponse response, AsyncArgsWrapper wrapper) { try { if (response.StatusCode == HttpStatusCode.OK) { using (Stream responseStream = GetWrappedStream(response)) { // Create the SyncReader using (var syncReader = (SerializationFormat == SerializationFormat.ODataAtom) ? new ODataAtomReader(responseStream, this.knownTypes) : (SyncReader)new ODataJsonReader(responseStream, this.knownTypes)) { await Task.Factory.StartNew(() => { // Read the response while (syncReader.Next()) { switch (syncReader.ItemType) { case ReaderItemType.Entry: wrapper.DownloadResponse.AddItem(syncReader.GetItem()); break; case ReaderItemType.SyncBlob: wrapper.DownloadResponse.ServerBlob = syncReader.GetServerBlob(); // Debug.WriteLine(SyncBlob.DeSerialize(wrapper.DownloadResponse.ServerBlob).ToString()); break; case ReaderItemType.HasMoreChanges: wrapper.DownloadResponse.IsLastBatch = !syncReader.GetHasMoreChangesValue(); break; } } }); } } } else { wrapper.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; } }
/// <summary> /// Callback for the Download HttpWebRequest.beginGetRequestStream. Deserializes the response feed to /// retrieve the list of IOfflineEntity objects and constructs an ChangeSet for that. /// </summary> /// <param name="asyncResult">IAsyncResult object</param> void OnDownloadGetResponseCompleted(IAsyncResult asyncResult) { AsyncArgsWrapper wrapper = asyncResult.AsyncState as AsyncArgsWrapper; wrapper.DownloadResponse = new ChangeSet(); HttpWebResponse response = null; try { try { response = wrapper.WebRequest.EndGetResponse(asyncResult) as HttpWebResponse; if (String.IsNullOrEmpty(behaviors.UserId)) { behaviors.UserId = response.Headers["userid"]; } if (string.IsNullOrWhiteSpace(behaviors.UserEmail)) { behaviors.UserEmail = response.Headers["email"]; } behaviors.ResourceVersion = response.Headers["resourceversion"]; } catch (WebException we) { wrapper.Error = we; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } catch (SecurityException se) { wrapper.Error = se; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } if (response.StatusCode == HttpStatusCode.OK) { behaviors.SaveUserSession(); int contentLength = (int)response.ContentLength; if (response.Headers.AllKeys.Contains("unzippedcontentlength")) { string value = response.Headers["unzippedcontentlength"]; if (!int.TryParse(value, out contentLength)) { throw new WebException("Invalid value of header unzippedcontentlength: " + value); } } Stream responseStream = new ProgressStream(response.GetResponseStream() , contentLength , behaviors.ReadProgressCallback); // CreateInstance the SyncReader if (ApplicationContext.Current.Settings.BitMobileFormatterDisabled) { _syncReader = new ODataAtomReader(responseStream, _knownTypes); } else { _syncReader = new BMReader(responseStream, _knownTypes); } // Read the response wrapper.DownloadResponse.Data = GetDownloadedValues(wrapper); wrapper.WebResponse = response; // Invoke user code on the correct synchronization context. this.FirePostResponseHandler(wrapper); } else { wrapper.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Method that does the actual processing. /// 1. It first creates an HttpWebRequest /// 2. Fills in the required method type and parameters. /// 3. Attaches the user specified ICredentials. /// 4. Serializes the input params (Server blob for downloads and input feed for uploads) /// 5. If user has specified an BeforeSendingRequest callback then invokes it /// 6. Else proceeds to issue the request /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> void ProcessRequest(AsyncArgsWrapper wrapper) { try { StringBuilder requestUri = new StringBuilder(); requestUri.AppendFormat("{0}{1}{2}/{3}", base.BaseUri, (base.BaseUri.ToString().EndsWith("/")) ? string.Empty : "/", Uri.EscapeUriString(base.ScopeName), wrapper.CacheRequest.RequestType.ToString()); string prefix = "?"; // Add the scope params if any foreach (KeyValuePair<string, string> kvp in this._scopeParameters) { requestUri.AppendFormat("{0}{1}={2}", prefix, Uri.EscapeUriString(kvp.Key), Uri.EscapeUriString(kvp.Value)); if (prefix.Equals("?")) { prefix = "&"; } } //requestUri.AppendFormat("{0}{1}={2}", prefix, Uri.EscapeUriString("user"), Uri.EscapeUriString("test")); //requestUri.AppendFormat("{0}{1}={2}", "&", Uri.EscapeUriString("pwd"), Uri.EscapeUriString("test")); // CreateInstance the WebRequest HttpWebRequest webRequest = null; if (this._credentials != null) { // CreateInstance the Client Http request //webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.CreateInstance(new Uri(requestUri.ToString())); webRequest = (HttpWebRequest)HttpWebRequest.Create(new Uri(requestUri.ToString().ToCurrentScheme(ApplicationContext.Current.Settings.HttpsDisabled))); NetworkCredential credential = this._credentials.GetCredential(BaseUri, "Basic"); // Add credentials webRequest.Credentials = this._credentials; string svcCredentials = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(credential.UserName + ":" + credential.Password)); webRequest.Headers.Add("Authorization", "Basic " + svcCredentials); webRequest.Headers.Add("configname:" + behaviors.ConfigName); webRequest.Headers.Add("configversion:" + behaviors.ConfigVersion); webRequest.Headers.Add("coreversion", behaviors.CoreVersion.ToString()); } else { // Use WebRequest.CreateInstance the request. This uses any user defined prefix preferences for certain paths webRequest = (HttpWebRequest)WebRequest.Create(requestUri.ToString().ToCurrentScheme(ApplicationContext.Current.Settings.HttpsDisabled)); } foreach (var item in behaviors.DeviceInfo) webRequest.Headers.Add(item.Key, item.Value); webRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; webRequest.Timeout = 5000; // Set the method type //webRequest.Timeout = 10; //webRequest.ReadWriteTimeout = 10; webRequest.Method = "POST"; webRequest.Accept = ApplicationContext.Current.Settings.BitMobileFormatterDisabled ? "application/atom+xml" : "application/bitmobile"; webRequest.ContentType = ApplicationContext.Current.Settings.BitMobileFormatterDisabled ? "application/atom+xml" : "application/bitmobile"; wrapper.WebRequest = webRequest; var requestData = new TimeoutRequestData() { Request = wrapper.WebRequest }; // Get the request stream if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { lock (_timeoutSync) { IAsyncResult resultUpload = webRequest.BeginGetRequestStream(OnUploadGetRequestStreamCompleted, wrapper); requestData.Handle = ThreadPool.RegisterWaitForSingleObject(resultUpload.AsyncWaitHandle, new WaitOrTimerCallback(TimeOutCallback), requestData, TIMEOUT, true); } } else { lock (_timeoutSync) { IAsyncResult resultDownload = webRequest.BeginGetRequestStream(OnDownloadGetRequestStreamCompleted, wrapper); requestData.Handle = ThreadPool.RegisterWaitForSingleObject(resultDownload.AsyncWaitHandle, new WaitOrTimerCallback(TimeOutCallback), requestData, TIMEOUT, true); } } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Issues the BeginGetResponse call for the HttpWebRequest /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> private async Task GetWebResponse(AsyncArgsWrapper wrapper) { // Send the request and wait for the response. if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { var task = wrapper.WebRequest.GetResponseAsync(); await OnUploadGetResponseCompleted(task, wrapper).ConfigureAwait(false); } else { var task = wrapper.WebRequest.GetResponseAsync(); await OnDownloadGetResponseCompleted(task, wrapper).ConfigureAwait(false); } }
/// <summary> /// Method that does the actual processing. /// 1. It first creates an HttpWebRequest /// 2. Fills in the required method type and parameters. /// 3. Attaches the user specified ICredentials. /// 4. Serializes the input params (Server blob for downloads and input feed for uploads) /// 5. If user has specified an BeforeSendingRequest callback then invokes it /// 6. Else proceeds to issue the request /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> void ProcessRequest(AsyncArgsWrapper wrapper) { try { StringBuilder requestUri = new StringBuilder(); requestUri.AppendFormat("{0}{1}{2}/{3}", base.BaseUri, (base.BaseUri.ToString().EndsWith("/")) ? string.Empty : "/", Uri.EscapeUriString(base.ScopeName), wrapper.CacheRequest.RequestType.ToString()); string prefix = "?"; // Add the scope params if any foreach (KeyValuePair <string, string> kvp in this._scopeParameters) { requestUri.AppendFormat("{0}{1}={2}", prefix, Uri.EscapeUriString(kvp.Key), Uri.EscapeUriString(kvp.Value)); if (prefix.Equals("?")) { prefix = "&"; } } //requestUri.AppendFormat("{0}{1}={2}", prefix, Uri.EscapeUriString("user"), Uri.EscapeUriString("test")); //requestUri.AppendFormat("{0}{1}={2}", "&", Uri.EscapeUriString("pwd"), Uri.EscapeUriString("test")); // CreateInstance the WebRequest HttpWebRequest webRequest = null; if (this._credentials != null) { // CreateInstance the Client Http request //webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.CreateInstance(new Uri(requestUri.ToString())); webRequest = (HttpWebRequest)HttpWebRequest.Create(new Uri(requestUri.ToString().ToCurrentScheme(ApplicationContext.Current.Settings.HttpsDisabled))); NetworkCredential credential = this._credentials.GetCredential(BaseUri, "Basic"); // Add credentials webRequest.Credentials = this._credentials; string svcCredentials = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(credential.UserName + ":" + credential.Password)); webRequest.Headers.Add("Authorization", "Basic " + svcCredentials); webRequest.Headers.Add("configname:" + behaviors.ConfigName); webRequest.Headers.Add("configversion:" + behaviors.ConfigVersion); webRequest.Headers.Add("coreversion", behaviors.CoreVersion.ToString()); } else { // Use WebRequest.CreateInstance the request. This uses any user defined prefix preferences for certain paths webRequest = (HttpWebRequest)WebRequest.Create(requestUri.ToString().ToCurrentScheme(ApplicationContext.Current.Settings.HttpsDisabled)); } foreach (var item in behaviors.DeviceInfo) { webRequest.Headers.Add(item.Key, item.Value); } webRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; webRequest.Timeout = 5000; // Set the method type //webRequest.Timeout = 10; //webRequest.ReadWriteTimeout = 10; webRequest.Method = "POST"; webRequest.Accept = ApplicationContext.Current.Settings.BitMobileFormatterDisabled ? "application/atom+xml" : "application/bitmobile"; webRequest.ContentType = ApplicationContext.Current.Settings.BitMobileFormatterDisabled ? "application/atom+xml" : "application/bitmobile"; wrapper.WebRequest = webRequest; var requestData = new TimeoutRequestData() { Request = wrapper.WebRequest }; // Get the request stream if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { lock (_timeoutSync) { IAsyncResult resultUpload = webRequest.BeginGetRequestStream(OnUploadGetRequestStreamCompleted, wrapper); requestData.Handle = ThreadPool.RegisterWaitForSingleObject(resultUpload.AsyncWaitHandle, new WaitOrTimerCallback(TimeOutCallback), requestData, TIMEOUT, true); } } else { lock (_timeoutSync) { IAsyncResult resultDownload = webRequest.BeginGetRequestStream(OnDownloadGetRequestStreamCompleted, wrapper); requestData.Handle = ThreadPool.RegisterWaitForSingleObject(resultDownload.AsyncWaitHandle, new WaitOrTimerCallback(TimeOutCallback), requestData, TIMEOUT, true); } } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Callback for the Upload HttpWebRequest.beginGetRequestStream /// </summary> /// <param name="task">Task representing the future request stream</param> async Task OnUploadGetRequestStreamCompleted(Task<Stream> task, AsyncArgsWrapper wrapper) { try { Stream requestStream = await task.ConfigureAwait(false); // Create a SyncWriter to write the contents this._syncWriter = (base.SerializationFormat == SerializationFormat.ODataAtom) ? (SyncWriter)new ODataAtomWriter(base.BaseUri) : (SyncWriter)new ODataJsonWriter(base.BaseUri); this._syncWriter.StartFeed(wrapper.CacheRequest.IsLastBatch, wrapper.CacheRequest.KnowledgeBlob ?? new byte[0]); foreach (IOfflineEntity entity in wrapper.CacheRequest.Changes) { // Skip tombstones that dont have a ID element. if (entity.ServiceMetadata.IsTombstone && string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { continue; } string tempId = null; // Check to see if this is an insert. i.e ServiceMetadata.Id is null or empty if (string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { if (wrapper.TempIdToEntityMapping == null) { wrapper.TempIdToEntityMapping = new Dictionary<string, IOfflineEntity>(); } tempId = Guid.NewGuid().ToString(); wrapper.TempIdToEntityMapping.Add(tempId, entity); } this._syncWriter.AddItem(entity, tempId); } if (base.SerializationFormat == SerializationFormat.ODataAtom) { this._syncWriter.WriteFeed(XmlWriter.Create(requestStream)); } else { this._syncWriter.WriteFeed(JsonReaderWriterFactory.CreateJsonWriter(requestStream)); } requestStream.Flush(); requestStream.Close(); if (this._beforeRequestHandler != null) { // Invoke user code and wait for them to call back us when they are done with the input request this._workerManager.PostProgress(wrapper.WorkerRequest, this.FirePreRequestHandler, wrapper); } else { this.GetWebResponse(wrapper); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Callback for the Upload HttpWebRequest.beginGetRequestStream /// </summary> /// <param name="task">Task representing the future request stream</param> async Task OnUploadGetRequestStreamCompleted(Task <Stream> task, AsyncArgsWrapper wrapper) { try { Stream requestStream = await task.ConfigureAwait(false); // Create a SyncWriter to write the contents this._syncWriter = (base.SerializationFormat == SerializationFormat.ODataAtom) ? (SyncWriter) new ODataAtomWriter(base.BaseUri) : (SyncWriter) new ODataJsonWriter(base.BaseUri); this._syncWriter.StartFeed(wrapper.CacheRequest.IsLastBatch, wrapper.CacheRequest.KnowledgeBlob ?? new byte[0]); foreach (IOfflineEntity entity in wrapper.CacheRequest.Changes) { // Skip tombstones that dont have a ID element. if (entity.ServiceMetadata.IsTombstone && string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { continue; } string tempId = null; // Check to see if this is an insert. i.e ServiceMetadata.Id is null or empty if (string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { if (wrapper.TempIdToEntityMapping == null) { wrapper.TempIdToEntityMapping = new Dictionary <string, IOfflineEntity>(); } tempId = Guid.NewGuid().ToString(); wrapper.TempIdToEntityMapping.Add(tempId, entity); } this._syncWriter.AddItem(entity, tempId); } if (base.SerializationFormat == SerializationFormat.ODataAtom) { this._syncWriter.WriteFeed(XmlWriter.Create(requestStream)); } else { this._syncWriter.WriteFeed(JsonReaderWriterFactory.CreateJsonWriter(requestStream)); } requestStream.Flush(); requestStream.Close(); if (this._beforeRequestHandler != null) { // Invoke user code and wait for them to call back us when they are done with the input request this._workerManager.PostProgress(wrapper.WorkerRequest, this.FirePreRequestHandler, wrapper); } else { this.GetWebResponse(wrapper); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Callback for the Download HttpWebRequest.beginGetRequestStream. Deserializes the response feed to /// retrieve the list of IOfflineEntity objects and constructs an ChangeSet for that. /// </summary> /// <param name="asyncResult">IAsyncResult object</param> async Task OnDownloadGetResponseCompleted(Task<WebResponse> task, AsyncArgsWrapper wrapper) { wrapper.DownloadResponse = new ChangeSet(); HttpWebResponse response = null; try { try { response = await task.ConfigureAwait(false) as HttpWebResponse; } catch (WebException we) { wrapper.Error = we; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } catch (SecurityException se) { wrapper.Error = se; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } if (response.StatusCode == HttpStatusCode.OK) { Stream responseStream = response.GetResponseStream(); // Create the SyncReader this._syncReader = (base.SerializationFormat == ClientServices.SerializationFormat.ODataAtom) ? (SyncReader)new ODataAtomReader(responseStream, this._knownTypes) : (SyncReader)new ODataJsonReader(responseStream, this._knownTypes); // Read the response while (this._syncReader.Next()) { switch (this._syncReader.ItemType) { case ReaderItemType.Entry: wrapper.DownloadResponse.AddItem(this._syncReader.GetItem()); break; case ReaderItemType.SyncBlob: wrapper.DownloadResponse.ServerBlob = this._syncReader.GetServerBlob(); break; case ReaderItemType.HasMoreChanges: wrapper.DownloadResponse.IsLastBatch = !this._syncReader.GetHasMoreChangesValue(); break; } } wrapper.WebResponse = response; // Invoke user code on the correct synchronization context. this.FirePostResponseHandler(wrapper); } else { wrapper.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Callback for the Upload HttpWebRequest.BeginGetResponse call /// </summary> /// <param name="task">IAsyncResult object</param> async Task OnUploadGetResponseCompleted(Task<WebResponse> task, AsyncArgsWrapper wrapper) { wrapper.UploadResponse = new ChangeSetResponse(); HttpWebResponse response = null; try { try { response = await task.ConfigureAwait(false) as HttpWebResponse; } catch (WebException we) { wrapper.UploadResponse.Error = we; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } catch (SecurityException se) { wrapper.UploadResponse.Error = se; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } if (response.StatusCode == HttpStatusCode.OK) { Stream responseStream = response.GetResponseStream(); // Create the SyncReader this._syncReader = (base.SerializationFormat == ClientServices.SerializationFormat.ODataAtom) ? (SyncReader)new ODataAtomReader(responseStream, this._knownTypes) : (SyncReader)new ODataJsonReader(responseStream, this._knownTypes); // Read the response while (this._syncReader.Next()) { switch (this._syncReader.ItemType) { case ReaderItemType.Entry: IOfflineEntity entity = this._syncReader.GetItem(); IOfflineEntity ackedEntity = entity; string tempId = null; // If conflict only one temp ID should be set if (this._syncReader.HasTempId() && this._syncReader.HasConflictTempId()) { throw new CacheControllerException(string.Format("Service returned a TempId '{0}' in both live and conflicting entities.", this._syncReader.GetTempId())); } // Validate the live temp ID if any, before adding anything to the offline context if (this._syncReader.HasTempId()) { tempId = this._syncReader.GetTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, entity, tempId); } // If conflict if (this._syncReader.HasConflict()) { Conflict conflict = this._syncReader.GetConflict(); IOfflineEntity conflictEntity = (conflict is SyncConflict) ? ((SyncConflict)conflict).LosingEntity : ((SyncError)conflict).ErrorEntity; // Validate conflict temp ID if any if (this._syncReader.HasConflictTempId()) { tempId = this._syncReader.GetConflictTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, conflictEntity, tempId); } // Add conflict wrapper.UploadResponse.AddConflict(conflict); // // If there is a conflict and the tempId is set in the conflict entity then the client version lost the // conflict and the live entity is the server version (ServerWins) // if (this._syncReader.HasConflictTempId() && entity.ServiceMetadata.IsTombstone) { // // This is a ServerWins conflict, or conflict error. The winning version is a tombstone without temp Id // so there is no way to map the winning entity with a temp Id. The temp Id is in the conflict so we are // using the conflict entity, which has the PK, to build a tombstone entity used to update the offline context // // In theory, we should copy the service metadata but it is the same end result as the service fills in // all the properties in the conflict entity // // Add the conflict entity conflictEntity.ServiceMetadata.IsTombstone = true; ackedEntity = conflictEntity; } } // Add ackedEntity to storage. If ackedEntity is still equal to entity then add non-conflict entity. if (!String.IsNullOrEmpty(tempId)) { wrapper.UploadResponse.AddUpdatedItem(ackedEntity); } break; case ReaderItemType.SyncBlob: wrapper.UploadResponse.ServerBlob = this._syncReader.GetServerBlob(); break; } } if (wrapper.TempIdToEntityMapping != null && wrapper.TempIdToEntityMapping.Count != 0) { // The client sent some inserts which werent ack'd by the service. Throw. StringBuilder builder = new StringBuilder("Server did not acknowledge with a permanent Id for the following tempId's: "); builder.Append(string.Join(",", wrapper.TempIdToEntityMapping.Keys.ToArray())); throw new CacheControllerException(builder.ToString()); } wrapper.WebResponse = response; // Invoke user code on the correct synchronization context. this.FirePostResponseHandler(wrapper); } else { wrapper.UploadResponse.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
private IEnumerable<IsolatedStorageOfflineEntity> GetDownloadedValues(AsyncArgsWrapper wrapper) { while (this._syncReader.Next()) { switch (_syncReader.ItemType) { case ReaderItemType.Entry: IsolatedStorageOfflineEntity offlineEntity = _syncReader.GetItem(); var entity = offlineEntity as IEntity; if (entity != null) LogManager.Logger.SyncDownload(entity.EntityType, offlineEntity.ServiceMetadata.IsTombstone); yield return offlineEntity; break; case ReaderItemType.SyncBlob: wrapper.DownloadResponse.ServerBlob = _syncReader.GetServerBlob(); break; case ReaderItemType.HasMoreChanges: wrapper.DownloadResponse.IsLastBatch = !_syncReader.GetHasMoreChangesValue(); break; } } _syncReader.Dispose(); }
/// <summary> /// Callback for the Download HttpWebRequest.beginGetRequestStream /// </summary> /// <param name="task">Task representing the future request stream</param> async Task OnDownloadGetRequestStreamCompleted(Task<Stream> task, AsyncArgsWrapper wrapper) { try { Stream requestStream = await task.ConfigureAwait(false); // Create a SyncWriter to write the contents this._syncWriter = (base.SerializationFormat == SerializationFormat.ODataAtom) ? (SyncWriter)new ODataAtomWriter(base.BaseUri) : (SyncWriter)new ODataJsonWriter(base.BaseUri); this._syncWriter.StartFeed(wrapper.CacheRequest.IsLastBatch, wrapper.CacheRequest.KnowledgeBlob ?? new byte[0]); if (base.SerializationFormat == SerializationFormat.ODataAtom) { this._syncWriter.WriteFeed(XmlWriter.Create(requestStream)); } else { this._syncWriter.WriteFeed(JsonReaderWriterFactory.CreateJsonWriter(requestStream)); } requestStream.Flush(); requestStream.Close(); if (this._beforeRequestHandler != null) { // Invoke user code and wait for them to call back us when they are done with the input request this._workerManager.PostProgress(wrapper.WorkerRequest, this.FirePreRequestHandler, wrapper); } else { this.GetWebResponse(wrapper); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Callback for the Upload HttpWebRequest.beginGetRequestStream /// </summary> /// <param name="asyncResult">IAsyncResult object</param> void OnUploadGetRequestStreamCompleted(IAsyncResult asyncResult) { AsyncArgsWrapper wrapper = asyncResult.AsyncState as AsyncArgsWrapper; try { Stream requestStream = wrapper.WebRequest.EndGetRequestStream(asyncResult); // CreateInstance a SyncWriter to write the contents if (ApplicationContext.Current.Settings.BitMobileFormatterDisabled) { _syncWriter = new ODataAtomWriter(BaseUri); } else { _syncWriter = new BMWriter(BaseUri); } this._syncWriter.StartFeed(wrapper.CacheRequest.IsLastBatch, wrapper.CacheRequest.KnowledgeBlob ?? new byte[0]); foreach (IOfflineEntity entity in wrapper.CacheRequest.Changes) { var ientity = entity as IEntity; // Skip tombstones that dont have a ID element. if (entity.ServiceMetadata.IsTombstone && string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { if (ientity != null) { LogManager.Logger.SyncUpload(ientity.EntityType, true); } continue; } string tempId = null; // Check to see if this is an insert. i.e ServiceMetadata.Id is null or empty if (string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { if (wrapper.TempIdToEntityMapping == null) { wrapper.TempIdToEntityMapping = new Dictionary <string, IOfflineEntity>(); } tempId = Guid.NewGuid().ToString(); wrapper.TempIdToEntityMapping.Add(tempId, entity); } this._syncWriter.AddItem(entity, tempId); if (ientity != null) { LogManager.Logger.SyncUpload(ientity.EntityType); } } if (base.SerializationFormat == SerializationFormat.ODataAtom) { this._syncWriter.WriteFeed(XmlWriter.Create(requestStream)); } else { this._syncWriter.WriteFeed(JsonReaderWriterFactory.CreateJsonWriter(requestStream)); } requestStream.Flush(); requestStream.Close(); if (this._beforeRequestHandler != null) { // Invoke user code and wait for them to call back us when they are done with the input request this._workerManager.PostProgress(wrapper.WorkerRequest, this.FirePreRequestHandler, wrapper); } else { this.GetWebResponse(wrapper); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Invokes the user's AfterReceivingResponse callback. /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> void FirePostResponseHandler(AsyncArgsWrapper wrapper) { if (this._afterResponseHandler != null) { // Invoke the user code. this._afterResponseHandler(wrapper.WebResponse); } }
/// <summary> /// Actual worker performing the work /// </summary> /// <param name="worker">AsyncWorkRequest object</param> /// <param name="inputParams">input parameters</param> void ProcessCacheRequestWorker(AsyncWorkRequest worker, object[] inputParams) { Debug.Assert(inputParams.Length == 2); CacheRequest request = inputParams[0] as CacheRequest; object state = inputParams[1]; AsyncArgsWrapper wrapper = new AsyncArgsWrapper() { UserPassedState = state, WorkerRequest = worker, CacheRequest = request }; ProcessRequest(wrapper); }
/// <summary> /// Method that does the actual processing. /// 1. It first creates an HttpWebRequest /// 2. Fills in the required method type and parameters. /// 3. Attaches the user specified ICredentials. /// 4. Serializes the input params (Server blob for downloads and input feed for uploads) /// 5. If user has specified an BeforeSendingRequest callback then invokes it /// 6. Else proceeds to issue the request /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> /// <param name="cancellationToken"> </param> private async Task <AsyncArgsWrapper> ProcessRequest(AsyncArgsWrapper wrapper, CancellationToken cancellationToken) { HttpWebResponse webResponse = null; try { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } var requestUri = new StringBuilder(); requestUri.AppendFormat("{0}{1}{2}/{3}", BaseUri, (BaseUri.ToString().EndsWith("/")) ? string.Empty : "/", Uri.EscapeUriString(ScopeName), wrapper.CacheRequest.RequestType.ToString()); string prefix = "?"; // Add the scope params if any foreach (var kvp in scopeParameters) { requestUri.AppendFormat("{0}{1}={2}", prefix, Uri.EscapeUriString(kvp.Key), Uri.EscapeUriString(kvp.Value)); if (prefix.Equals("?")) { prefix = "&"; } } // Create the WebRequest HttpWebRequest webRequest; if (credentials != null) { // Create the Client Http request webRequest = WebRequest.CreateHttp(new Uri(requestUri.ToString())); // Add credentials webRequest.Credentials = credentials; } else { // Use WebRequest.Create the request. This uses any user defined prefix preferences for certain paths webRequest = (HttpWebRequest)WebRequest.Create(requestUri.ToString()); } // Set the method type webRequest.Method = "POST"; webRequest.Accept = (SerializationFormat == SerializationFormat.ODataAtom) ? "application/atom+xml" : "application/json"; webRequest.ContentType = (SerializationFormat == SerializationFormat.ODataAtom) ? "application/atom+xml" : "application/json"; // Write on the stream #if !NETFX_CORE using (Stream stream = await Task.Factory.FromAsync <Stream>( webRequest.BeginGetRequestStream, webRequest.EndGetRequestStream, null)) #else using (Stream stream = await webRequest.GetRequestStreamAsync()) #endif { if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { WriteUploadRequestStream(stream, wrapper); } else { WriteDownloadRequestStream(stream, wrapper); } } // If error, return wrapper with error if (wrapper.Error != null) { return(wrapper); } // Get Response if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { wrapper.UploadResponse = new ChangeSetResponse(); } else { wrapper.DownloadResponse = new ChangeSet(); } #if !NETFX_CORE webResponse = (HttpWebResponse)(await Task.Factory.FromAsync <WebResponse>( webRequest.BeginGetResponse, webRequest.EndGetResponse, null)); #else webResponse = (HttpWebResponse)(await webRequest.GetResponseAsync()); #endif if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { await ReadUploadResponse(webResponse, wrapper); } else { await ReadDownloadResponse(webResponse, wrapper); } } catch (WebException we) { if (we.Response == null) { wrapper.Error = we; } else { var stream = we.Response.GetResponseStream(); var reader = SerializationFormat == SerializationFormat.ODataAtom ? XmlReader.Create(stream) : new XmlJsonReader(stream, XmlDictionaryReaderQuotas.Max); if (reader.ReadToDescendant(FormatterConstants.ErrorDescriptionElementNamePascalCasing)) { wrapper.Error = new Exception(reader.ReadElementContentAsString()); } } } catch (OperationCanceledException) { // Re throw the operation cancelled throw; } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; return(wrapper); } return(wrapper); }
/// <summary> /// Issues the BeginGetResponse call for the HttpWebRequest /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> private void GetWebResponse(AsyncArgsWrapper wrapper) { var requestData = new TimeoutRequestData() { Request = wrapper.WebRequest }; // Send the request and wait for the response. if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { lock (_timeoutSync) { IAsyncResult resultUpload = wrapper.WebRequest.BeginGetResponse(OnUploadGetResponseCompleted, wrapper); requestData.Handle = ThreadPool.RegisterWaitForSingleObject(resultUpload.AsyncWaitHandle, new WaitOrTimerCallback(TimeOutCallback), requestData, TIMEOUT, true); } } else { lock (_timeoutSync) { IAsyncResult resultDownload = wrapper.WebRequest.BeginGetResponse(OnDownloadGetResponseCompleted, wrapper); requestData.Handle = ThreadPool.RegisterWaitForSingleObject(resultDownload.AsyncWaitHandle, new WaitOrTimerCallback(TimeOutCallback), requestData, TIMEOUT, true); } } }
/// <summary> /// Method that does the actual processing. /// 1. It first creates an HttpWebRequest /// 2. Fills in the required method type and parameters. /// 3. Attaches the user specified ICredentials. /// 4. Serializes the input params (Server blob for downloads and input feed for uploads) /// 5. If user has specified an BeforeSendingRequest callback then invokes it /// 6. Else proceeds to issue the request /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> /// <param name="cancellationToken"> </param> private async Task <AsyncArgsWrapper> ProcessRequest(AsyncArgsWrapper wrapper, CancellationToken cancellationToken) { HttpWebResponse webResponse = null; try { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } var requestUri = new StringBuilder(); requestUri.AppendFormat("{0}{1}{2}/{3}", BaseUri, (BaseUri.ToString().EndsWith("/")) ? string.Empty : "/", Uri.EscapeUriString(ScopeName), wrapper.CacheRequest.RequestType.ToString()); string prefix = "?"; // Add the scope params if any foreach (var kvp in scopeParameters) { requestUri.AppendFormat("{0}{1}={2}", prefix, Uri.EscapeUriString(kvp.Key), Uri.EscapeUriString(kvp.Value)); if (prefix.Equals("?")) { prefix = "&"; } } // Create the WebRequest HttpWebRequest webRequest; if (credentials != null) { // Create the Client Http request webRequest = WebRequest.CreateHttp(new Uri(requestUri.ToString())); // Add credentials webRequest.Credentials = credentials; } else { // Use WebRequest.Create the request. This uses any user defined prefix preferences for certain paths webRequest = (HttpWebRequest)WebRequest.Create(requestUri.ToString()); } // set cookies if exists if (CookieContainer != null) { webRequest.CookieContainer = CookieContainer; } // Set the method type webRequest.Method = "POST"; webRequest.Accept = (SerializationFormat == SerializationFormat.ODataAtom) ? "application/atom+xml" : "application/json"; webRequest.ContentType = (SerializationFormat == SerializationFormat.ODataAtom) ? "application/atom+xml" : "application/json"; #if !WINDOWS_PHONE if (automaticDecompression) { webRequest.Headers["Accept-Encoding"] = "gzip, deflated"; } #endif foreach (var kvp in customHeaders) { webRequest.Headers[kvp.Key] = kvp.Value; } // To be sure where it could be raise an error, just mark the step wrapper.Step = HttpState.WriteRequest; #if !NETFX_CORE using (Stream stream = await Task.Factory.FromAsync <Stream>( webRequest.BeginGetRequestStream, webRequest.EndGetRequestStream, null)) #else using (Stream stream = await webRequest.GetRequestStreamAsync()) #endif { if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { WriteUploadRequestStream(stream, wrapper); } else { WriteDownloadRequestStream(stream, wrapper); } } // If error, return wrapper with error if (wrapper.Error != null) { return(wrapper); } // Get Response if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { wrapper.UploadResponse = new ChangeSetResponse(); } else { wrapper.DownloadResponse = new ChangeSet(); } wrapper.Step = HttpState.ReadResponse; #if !NETFX_CORE webResponse = (HttpWebResponse)(await Task.Factory.FromAsync <WebResponse>( webRequest.BeginGetResponse, webRequest.EndGetResponse, null)); #else webResponse = (HttpWebResponse)(await webRequest.GetResponseAsync()); #endif if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { await ReadUploadResponse(webResponse, wrapper); } else { await ReadDownloadResponse(webResponse, wrapper); } if (webResponse != null) { wrapper.Step = HttpState.End; webResponse.Dispose(); webResponse = null; } } catch (WebException we) { // if (we.Response == null) // { // default to this, there will be cases where the we.Response is != null but the content length = 0 // e.g 413 and other server errors. e.g the else branch of reader.ReadToDescendent. By defaulting to this // we always capture the error rather then returning an error of null in some cases wrapper.Error = we; //} if (we.Response != null) { using (var stream = GetWrappedStream(we.Response)) { using (var reader = SerializationFormat == SerializationFormat.ODataAtom ? XmlReader.Create(stream) : new XmlJsonReader(stream, XmlDictionaryReaderQuotas.Max)) { if (reader.ReadToDescendant(FormatterConstants.ErrorDescriptionElementNamePascalCasing)) { wrapper.Error = new Exception(reader.ReadElementContentAsString()); } } } } } catch (OperationCanceledException) { // Re throw the operation cancelled throw; } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; return(wrapper); } return(wrapper); }
/// <summary> /// Callback for the Upload HttpWebRequest.BeginGetResponse call /// </summary> /// <param name="asyncResult">IAsyncResult object</param> void OnUploadGetResponseCompleted(IAsyncResult asyncResult) { AsyncArgsWrapper wrapper = asyncResult.AsyncState as AsyncArgsWrapper; wrapper.UploadResponse = new ChangeSetResponse(); HttpWebResponse response = null; try { try { response = wrapper.WebRequest.EndGetResponse(asyncResult) as HttpWebResponse; } catch (WebException we) { wrapper.UploadResponse.Error = we; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } catch (SecurityException se) { wrapper.UploadResponse.Error = se; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } if (response.StatusCode == HttpStatusCode.OK) { Stream responseStream = response.GetResponseStream(); // CreateInstance the SyncReader if (ApplicationContext.Current.Settings.BitMobileFormatterDisabled) { _syncReader = new ODataAtomReader(responseStream, _knownTypes); } else { _syncReader = new BMReader(responseStream, _knownTypes); } // Read the response while (this._syncReader.Next()) { switch (this._syncReader.ItemType) { case ReaderItemType.Entry: IOfflineEntity entity = this._syncReader.GetItem(); IOfflineEntity ackedEntity = entity; string tempId = null; // If conflict only one temp ID should be set if (this._syncReader.HasTempId() && this._syncReader.HasConflictTempId()) { throw new CacheControllerException(string.Format("Service returned a TempId '{0}' in both live and conflicting entities.", this._syncReader.GetTempId())); } // Validate the live temp ID if any, before adding anything to the offline context if (this._syncReader.HasTempId()) { tempId = this._syncReader.GetTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, entity, tempId); } // If conflict if (this._syncReader.HasConflict()) { Conflict conflict = this._syncReader.GetConflict(); IOfflineEntity conflictEntity = (conflict is SyncConflict) ? ((SyncConflict)conflict).LosingEntity : ((SyncError)conflict).ErrorEntity; // Validate conflict temp ID if any if (this._syncReader.HasConflictTempId()) { tempId = this._syncReader.GetConflictTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, conflictEntity, tempId); } // Add conflict wrapper.UploadResponse.AddConflict(conflict); // // If there is a conflict and the tempId is set in the conflict entity then the client version lost the // conflict and the live entity is the server version (ServerWins) // if (this._syncReader.HasConflictTempId() && entity.ServiceMetadata.IsTombstone) { // // This is a ServerWins conflict, or conflict error. The winning version is a tombstone without temp Id // so there is no way to map the winning entity with a temp Id. The temp Id is in the conflict so we are // using the conflict entity, which has the PK, to build a tombstone entity used to update the offline context // // In theory, we should copy the service metadata but it is the same end result as the service fills in // all the properties in the conflict entity // // Add the conflict entity conflictEntity.ServiceMetadata.IsTombstone = true; ackedEntity = conflictEntity; } } // Add ackedEntity to storage. If ackedEntity is still equal to entity then add non-conflict entity. if (!String.IsNullOrEmpty(tempId)) { wrapper.UploadResponse.AddUpdatedItem(ackedEntity); } break; case ReaderItemType.SyncBlob: wrapper.UploadResponse.ServerBlob = this._syncReader.GetServerBlob(); break; } } wrapper.WebResponse = response; // Invoke user code on the correct synchronization context. this.FirePostResponseHandler(wrapper); } else { wrapper.UploadResponse.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Callback for the Download HttpWebRequest.beginGetRequestStream /// </summary> private void WriteDownloadRequestStream(Stream requestStream, AsyncArgsWrapper wrapper) { try { // Create a SyncWriter to write the contents var syncWriter = (SerializationFormat == SerializationFormat.ODataAtom) ? new ODataAtomWriter(BaseUri) : (SyncWriter)new ODataJsonWriter(BaseUri); //syncWriter = new ODataAtomWriter(BaseUri); syncWriter.StartFeed(wrapper.CacheRequest.IsLastBatch, wrapper.CacheRequest.KnowledgeBlob ?? new byte[0]); if (SerializationFormat == SerializationFormat.ODataAtom) syncWriter.WriteFeed(XmlWriter.Create(requestStream)); else syncWriter.WriteFeed(new XmlJsonWriter(requestStream)); requestStream.Flush(); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) throw; wrapper.Error = e; } }
/// <summary> /// Callback for the Upload HttpWebRequest.BeginGetResponse call /// </summary> /// <param name="task">IAsyncResult object</param> async Task OnUploadGetResponseCompleted(Task <WebResponse> task, AsyncArgsWrapper wrapper) { wrapper.UploadResponse = new ChangeSetResponse(); HttpWebResponse response = null; try { try { response = await task.ConfigureAwait(false) as HttpWebResponse; } catch (WebException we) { wrapper.UploadResponse.Error = we; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } catch (SecurityException se) { wrapper.UploadResponse.Error = se; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } if (response.StatusCode == HttpStatusCode.OK) { Stream responseStream = response.GetResponseStream(); // Create the SyncReader this._syncReader = (base.SerializationFormat == ClientServices.SerializationFormat.ODataAtom) ? (SyncReader) new ODataAtomReader(responseStream, this._knownTypes) : (SyncReader) new ODataJsonReader(responseStream, this._knownTypes); // Read the response while (this._syncReader.Next()) { switch (this._syncReader.ItemType) { case ReaderItemType.Entry: IOfflineEntity entity = this._syncReader.GetItem(); IOfflineEntity ackedEntity = entity; string tempId = null; // If conflict only one temp ID should be set if (this._syncReader.HasTempId() && this._syncReader.HasConflictTempId()) { throw new CacheControllerException(string.Format("Service returned a TempId '{0}' in both live and conflicting entities.", this._syncReader.GetTempId())); } // Validate the live temp ID if any, before adding anything to the offline context if (this._syncReader.HasTempId()) { tempId = this._syncReader.GetTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, entity, tempId); } // If conflict if (this._syncReader.HasConflict()) { Conflict conflict = this._syncReader.GetConflict(); IOfflineEntity conflictEntity = (conflict is SyncConflict) ? ((SyncConflict)conflict).LosingEntity : ((SyncError)conflict).ErrorEntity; // Validate conflict temp ID if any if (this._syncReader.HasConflictTempId()) { tempId = this._syncReader.GetConflictTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, conflictEntity, tempId); } // Add conflict wrapper.UploadResponse.AddConflict(conflict); // // If there is a conflict and the tempId is set in the conflict entity then the client version lost the // conflict and the live entity is the server version (ServerWins) // if (this._syncReader.HasConflictTempId() && entity.ServiceMetadata.IsTombstone) { // // This is a ServerWins conflict, or conflict error. The winning version is a tombstone without temp Id // so there is no way to map the winning entity with a temp Id. The temp Id is in the conflict so we are // using the conflict entity, which has the PK, to build a tombstone entity used to update the offline context // // In theory, we should copy the service metadata but it is the same end result as the service fills in // all the properties in the conflict entity // // Add the conflict entity conflictEntity.ServiceMetadata.IsTombstone = true; ackedEntity = conflictEntity; } } // Add ackedEntity to storage. If ackedEntity is still equal to entity then add non-conflict entity. if (!String.IsNullOrEmpty(tempId)) { wrapper.UploadResponse.AddUpdatedItem(ackedEntity); } break; case ReaderItemType.SyncBlob: wrapper.UploadResponse.ServerBlob = this._syncReader.GetServerBlob(); break; } } if (wrapper.TempIdToEntityMapping != null && wrapper.TempIdToEntityMapping.Count != 0) { // The client sent some inserts which werent ack'd by the service. Throw. StringBuilder builder = new StringBuilder("Server did not acknowledge with a permanent Id for the following tempId's: "); builder.Append(string.Join(",", wrapper.TempIdToEntityMapping.Keys.ToArray())); throw new CacheControllerException(builder.ToString()); } wrapper.WebResponse = response; // Invoke user code on the correct synchronization context. this.FirePostResponseHandler(wrapper); } else { wrapper.UploadResponse.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Callback for the Upload HttpWebRequest.BeginGetResponse call /// </summary> private async Task ReadUploadResponse(HttpWebResponse response, AsyncArgsWrapper wrapper) { try { if (response.StatusCode == HttpStatusCode.OK) { using (Stream responseStream = GetWrappedStream(response)) { using (var syncReader = (SerializationFormat == SerializationFormat.ODataAtom) ? new ODataAtomReader(responseStream, this.knownTypes) : (SyncReader)new ODataJsonReader(responseStream, this.knownTypes)) { // Read the response await Task.Factory.StartNew(() => { while (syncReader.Next()) { switch (syncReader.ItemType) { case ReaderItemType.Entry: IOfflineEntity entity = syncReader.GetItem(); IOfflineEntity ackedEntity = entity; string tempId = null; // If conflict only one temp ID should be set if (syncReader.HasTempId() && syncReader.HasConflictTempId()) { throw new CacheControllerException( string.Format( "Service returned a TempId '{0}' in both live and conflicting entities.", syncReader.GetTempId())); } // Validate the live temp ID if any, before adding anything to the offline context if (syncReader.HasTempId()) { tempId = syncReader.GetTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, entity, tempId); } // If conflict if (syncReader.HasConflict()) { Conflict conflict = syncReader.GetConflict(); IOfflineEntity conflictEntity = (conflict is SyncConflict) ? ((SyncConflict)conflict).LosingEntity : ((SyncError)conflict).ErrorEntity; // Validate conflict temp ID if any if (syncReader.HasConflictTempId()) { tempId = syncReader.GetConflictTempId(); CheckEntityServiceMetadataAndTempIds(wrapper, conflictEntity, tempId); } // Add conflict wrapper.UploadResponse.AddConflict(conflict); // // If there is a conflict and the tempId is set in the conflict entity then the client version lost the // conflict and the live entity is the server version (ServerWins) // if (syncReader.HasConflictTempId() && entity.GetServiceMetadata().IsTombstone) { // // This is a ServerWins conflict, or conflict error. The winning version is a tombstone without temp Id // so there is no way to map the winning entity with a temp Id. The temp Id is in the conflict so we are // using the conflict entity, which has the PK, to build a tombstone entity used to update the offline context // // In theory, we should copy the service metadata but it is the same end result as the service fills in // all the properties in the conflict entity // // Add the conflict entity conflictEntity.GetServiceMetadata().IsTombstone = true; ackedEntity = conflictEntity; } } // Add ackedEntity to storage. If ackedEntity is still equal to entity then add non-conflict entity. if (!String.IsNullOrEmpty(tempId)) { wrapper.UploadResponse.AddUpdatedItem(ackedEntity); } break; case ReaderItemType.SyncBlob: wrapper.UploadResponse.ServerBlob = syncReader.GetServerBlob(); break; } } }); if (wrapper.TempIdToEntityMapping != null && wrapper.TempIdToEntityMapping.Count != 0) { // The client sent some inserts which werent ack'd by the service. Throw. var builder = new StringBuilder( "Server did not acknowledge with a permanent Id for the following tempId's: "); builder.Append(string.Join(",", wrapper.TempIdToEntityMapping.Keys.ToArray())); throw new CacheControllerException(builder.ToString()); } } } } else { wrapper.UploadResponse.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) throw; wrapper.Error = e; } }
private void CheckEntityServiceMetadataAndTempIds(AsyncArgsWrapper wrapper, IOfflineEntity entity, string tempId) { // Check service ID if (string.IsNullOrEmpty(entity.ServiceMetadata.Id)) { throw new CacheControllerException(string.Format("Service did not return a permanent Id for tempId '{0}'", tempId)); } // If an entity has a temp id then it should not be a tombstone if (entity.ServiceMetadata.IsTombstone) { throw new CacheControllerException(string.Format("Service returned a tempId '{0}' in tombstoned entity.", tempId)); } // Check that the tempId was sent by client if (!wrapper.TempIdToEntityMapping.ContainsKey(tempId)) { throw new CacheControllerException("Service returned a response for a tempId which was not uploaded by the client. TempId: " + tempId); } // Once received, remove the tempId from the mapping list. wrapper.TempIdToEntityMapping.Remove(tempId); }
/// <summary> /// Method that does the actual processing. /// 1. It first creates an HttpWebRequest /// 2. Fills in the required method type and parameters. /// 3. Attaches the user specified ICredentials. /// 4. Serializes the input params (Server blob for downloads and input feed for uploads) /// 5. If user has specified an BeforeSendingRequest callback then invokes it /// 6. Else proceeds to issue the request /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> void ProcessRequest(AsyncArgsWrapper wrapper) { try { StringBuilder requestUri = new StringBuilder(); requestUri.AppendFormat("{0}{1}{2}/{3}", base.BaseUri, (base.BaseUri.ToString().EndsWith("/")) ? string.Empty : "/", Uri.EscapeUriString(base.ScopeName), wrapper.CacheRequest.RequestType.ToString()); string prefix = "?"; // Add the scope params if any foreach (KeyValuePair <string, string> kvp in this._scopeParameters) { requestUri.AppendFormat("{0}{1}={2}", prefix, Uri.EscapeUriString(kvp.Key), Uri.EscapeUriString(kvp.Value)); if (prefix.Equals("?")) { prefix = "&"; } } // Create the WebRequest HttpWebRequest webRequest = null; if (this._credentials != null) { // Create the Client Http request webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(new Uri(requestUri.ToString())); // Add credentials webRequest.Credentials = this._credentials; } else { // Use WebRequest.Create the request. This uses any user defined prefix preferences for certain paths webRequest = (HttpWebRequest)WebRequest.Create(requestUri.ToString()); } // Set the method type webRequest.Method = "POST"; webRequest.Accept = (base.SerializationFormat == SerializationFormat.ODataAtom) ? "application/atom+xml" : "application/json"; webRequest.ContentType = (base.SerializationFormat == SerializationFormat.ODataAtom) ? "application/atom+xml" : "application/json"; wrapper.WebRequest = webRequest; // Get the request stream if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { webRequest.BeginGetRequestStream(OnUploadGetRequestStreamCompleted, wrapper); } else { webRequest.BeginGetRequestStream(OnDownloadGetRequestStreamCompleted, wrapper); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Issues the BeginGetResponse call for the HttpWebRequest /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> private void GetWebResponse(AsyncArgsWrapper wrapper) { // Send the request and wait for the response. if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { wrapper.WebRequest.BeginGetResponse(OnUploadGetResponseCompleted, wrapper); } else { wrapper.WebRequest.BeginGetResponse(OnDownloadGetResponseCompleted, wrapper); } }
/// <summary> /// Callback for the Download HttpWebRequest.beginGetRequestStream. Deserializes the response feed to /// retrieve the list of IOfflineEntity objects and constructs an ChangeSet for that. /// </summary> /// <param name="asyncResult">IAsyncResult object</param> void OnDownloadGetResponseCompleted(IAsyncResult asyncResult) { AsyncArgsWrapper wrapper = asyncResult.AsyncState as AsyncArgsWrapper; wrapper.DownloadResponse = new ChangeSet(); HttpWebResponse response = null; try { try { response = wrapper.WebRequest.EndGetResponse(asyncResult) as HttpWebResponse; } catch (WebException we) { wrapper.Error = we; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } catch (SecurityException se) { wrapper.Error = se; // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); return; } if (response.StatusCode == HttpStatusCode.OK) { Stream responseStream = response.GetResponseStream(); // Create the SyncReader this._syncReader = (base.SerializationFormat == ClientServices.SerializationFormat.ODataAtom) ? (SyncReader) new ODataAtomReader(responseStream, this._knownTypes) : (SyncReader) new ODataJsonReader(responseStream, this._knownTypes); // Read the response while (this._syncReader.Next()) { switch (this._syncReader.ItemType) { case ReaderItemType.Entry: wrapper.DownloadResponse.AddItem(this._syncReader.GetItem()); break; case ReaderItemType.SyncBlob: wrapper.DownloadResponse.ServerBlob = this._syncReader.GetServerBlob(); break; case ReaderItemType.HasMoreChanges: wrapper.DownloadResponse.IsLastBatch = !this._syncReader.GetHasMoreChangesValue(); break; } } wrapper.WebResponse = response; // Invoke user code on the correct synchronization context. this.FirePostResponseHandler(wrapper); } else { wrapper.Error = new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", response.StatusCode, response.StatusDescription)); } // If we get here then it means we completed the request. Return to the original caller this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
/// <summary> /// Method that does the actual processing. /// 1. It first creates an HttpWebRequest /// 2. Fills in the required method type and parameters. /// 3. Attaches the user specified ICredentials. /// 4. Serializes the input params (Server blob for downloads and input feed for uploads) /// 5. If user has specified an BeforeSendingRequest callback then invokes it /// 6. Else proceeds to issue the request /// </summary> /// <param name="wrapper">AsyncArgsWrapper object</param> void ProcessRequest(AsyncArgsWrapper wrapper) { try { StringBuilder requestUri = new StringBuilder(); requestUri.AppendFormat("{0}{1}{2}/{3}", base.BaseUri, (base.BaseUri.ToString().EndsWith("/")) ? string.Empty : "/", Uri.EscapeUriString(base.ScopeName), wrapper.CacheRequest.RequestType.ToString()); string prefix = "?"; // Add the scope params if any foreach (KeyValuePair<string, string> kvp in this._scopeParameters) { requestUri.AppendFormat("{0}{1}={2}", prefix, Uri.EscapeUriString(kvp.Key), Uri.EscapeUriString(kvp.Value)); if (prefix.Equals("?")) { prefix = "&"; } } // Create the WebRequest HttpWebRequest webRequest = null; if (this._credentials != null) { // Create the Client Http request webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(new Uri(requestUri.ToString())); // Add credentials webRequest.Credentials = this._credentials; } else { // Use WebRequest.Create the request. This uses any user defined prefix preferences for certain paths webRequest = (HttpWebRequest)WebRequest.Create(requestUri.ToString()); } // Set the method type webRequest.Method = "POST"; webRequest.Accept = (base.SerializationFormat == SerializationFormat.ODataAtom) ? "application/atom+xml" : "application/json"; webRequest.ContentType = (base.SerializationFormat == SerializationFormat.ODataAtom) ? "application/atom+xml" : "application/json"; wrapper.WebRequest = webRequest; // Get the request stream if (wrapper.CacheRequest.RequestType == CacheRequestType.UploadChanges) { webRequest.BeginGetRequestStream(OnUploadGetRequestStreamCompleted, wrapper); } else { webRequest.BeginGetRequestStream(OnDownloadGetRequestStreamCompleted, wrapper); } } catch (Exception e) { if (ExceptionUtility.IsFatal(e)) { throw; } wrapper.Error = e; this._workerManager.CompleteWorkRequest(wrapper.WorkerRequest, wrapper); } }
void ProcessCacheRequestWorker(AsyncWorkRequest worker, object[] inputParams) { var request = inputParams[0] as CacheRequest; object state = inputParams[1]; _wrapper = new AsyncArgsWrapper { UserPassedState = state, WorkerRequest = worker, CacheRequest = request }; ProcessRequest(); }