/// <summary> /// Called by the CacheController when it wants this CacheRequest to be processed. /// </summary> /// <param name="request">CacheRequest to be processed</param> /// <param name="state">User state object</param> public override object ProcessCacheRequest(CacheRequest request) { StringBuilder requestUri = new StringBuilder(); requestUri.AppendFormat("{0}{1}{2}/{3}", base.BaseUri, (base.BaseUri.ToString().EndsWith("/")) ? string.Empty : "/", Uri.EscapeUriString(base.ScopeName), request.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; webRequest = (HttpWebRequest)WebRequest.Create(requestUri.ToString()); if (this._credentials != null) { webRequest.Credentials = this._credentials; } // Set the method type webRequest.Method = "POST"; webRequest.Accept = "application/atom+xml"; webRequest.ContentType = "application/atom+xml"; switch (request.RequestType) { case CacheRequestType.UploadChanges: return ProcessUploadRequest(webRequest, request); case CacheRequestType.DownloadChanges: default: return ProcessDownloadRequest(webRequest, request); } }
private object ProcessUploadRequest(HttpWebRequest webRequest, CacheRequest request) { using (Stream memoryStream = new MemoryStream()) { // Create a SyncWriter to write the contents this._syncWriter = new ODataAtomWriter(base.BaseUri); this._syncWriter.StartFeed(true, request.KnowledgeBlob ?? new byte[0]); foreach (IOfflineEntity entity in request.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 (TempIdToEntityMapping == null) { TempIdToEntityMapping = new Dictionary<string, IOfflineEntity>(); } tempId = Guid.NewGuid().ToString(); TempIdToEntityMapping.Add(tempId, entity); } this._syncWriter.AddItem(entity, tempId); } this._syncWriter.WriteFeed(XmlWriter.Create(memoryStream)); memoryStream.Flush(); // Set the content length webRequest.ContentLength = memoryStream.Position; using (Stream requestStream = webRequest.GetRequestStream()) { CopyStreamContent(memoryStream, requestStream); // Close the request stream requestStream.Flush(); requestStream.Close(); } } // Fire the Before request handler this.FirePreRequestHandler(webRequest); // Get the response HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); if (webResponse.StatusCode == HttpStatusCode.OK) { ChangeSetResponse changeSetResponse = new ChangeSetResponse(); using (Stream responseStream = webResponse.GetResponseStream()) { // Create the SyncReader this._syncReader = new ODataAtomReader(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(TempIdToEntityMapping, entity, tempId, changeSetResponse); } // 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(TempIdToEntityMapping, conflictEntity, tempId, changeSetResponse); } // Add conflict changeSetResponse.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)) { changeSetResponse.AddUpdatedItem(ackedEntity); } break; case ReaderItemType.SyncBlob: changeSetResponse.ServerBlob = this._syncReader.GetServerBlob(); break; } } } if (TempIdToEntityMapping != null && 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 permanant Id for the following tempId's: "); builder.Append(string.Join(",", TempIdToEntityMapping.Keys.ToArray())); throw new CacheControllerException(builder.ToString()); } this.FirePostResponseHandler(webResponse); webResponse.Close(); return changeSetResponse; } else { throw new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", webResponse.StatusCode, webResponse.StatusDescription)); } }
/// <summary> /// Method that will contain the actual implementation of the cache request processing. /// </summary> /// <param name="request">CacheRequest object</param> /// <returns>ChangeSet for a dowload request or ChangeSetResponse for an upload request</returns> public abstract object ProcessCacheRequest(CacheRequest request);
private object ProcessDownloadRequest(HttpWebRequest webRequest, CacheRequest request) { using (Stream memoryStream = new MemoryStream()) { // Create a SyncWriter to write the contents this._syncWriter = new ODataAtomWriter(base.BaseUri); this._syncWriter.StartFeed(true, request.KnowledgeBlob ?? new byte[0]); this._syncWriter.WriteFeed(XmlWriter.Create(memoryStream)); memoryStream.Flush(); webRequest.ContentLength = memoryStream.Position; Stream requestStream = webRequest.GetRequestStream(); CopyStreamContent(memoryStream, requestStream); requestStream.Flush(); requestStream.Close(); // Fire the Before request handler this.FirePreRequestHandler(webRequest); } // Get the response HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); if (webResponse.StatusCode == HttpStatusCode.OK) { ChangeSet changeSet = new ChangeSet(); using (Stream responseStream = webResponse.GetResponseStream()) { // Create the SyncReader this._syncReader = new ODataAtomReader(responseStream, this._knownTypes); // Read the response while (this._syncReader.Next()) { switch (this._syncReader.ItemType) { case ReaderItemType.Entry: changeSet.AddItem(this._syncReader.GetItem()); break; case ReaderItemType.SyncBlob: changeSet.ServerBlob = this._syncReader.GetServerBlob(); break; case ReaderItemType.HasMoreChanges: changeSet.IsLastBatch = !this._syncReader.GetHasMoreChangesValue(); break; } } this.FirePostResponseHandler(webResponse); } webResponse.Close(); return changeSet; } else { throw new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", webResponse.StatusCode, webResponse.StatusDescription)); } }
/// <summary> /// Method that refreshes the Cache by uploading all modified changes and then downloading the /// server changes. /// </summary> /// <returns>A CacheRefreshStatistics object denoting the statistics from the Refresh call</returns> public CacheRefreshStatistics Refresh() { this._controllerBehavior.Locked = true; try { // First create the CacheRequestHandler this._cacheRequestHandler = CacheRequestHandler.CreateRequestHandler(this._serviceUri, this._controllerBehavior); CacheRefreshStatistics refreshStats = new CacheRefreshStatistics(); refreshStats.StartTime = DateTime.Now; bool uploadComplete = false; bool downloadComplete = false; // Start sync by executin an Upload request while (!uploadComplete || !downloadComplete) { if (!uploadComplete) { Guid changeSetId = Guid.NewGuid(); ChangeSet changeSet = this._localProvider.GetChangeSet(changeSetId); if (changeSet.Data == null || changeSet.Data.Count == 0) { // No data to upload. Skip upload phase. uploadComplete = true; } else { // Create a SyncRequest out of this. CacheRequest request = new CacheRequest() { RequestId = changeSetId, RequestType = CacheRequestType.UploadChanges, Changes = changeSet.Data, KnowledgeBlob = changeSet.ServerBlob, IsLastBatch = changeSet.IsLastBatch }; // Increment the stats refreshStats.TotalChangeSetsUploaded++; ChangeSetResponse response = (ChangeSetResponse)this._cacheRequestHandler.ProcessCacheRequest(request); // Increment the stats refreshStats.TotalUploads += (uint)request.Changes.Count; response.ConflictsInternal.ForEach((e1) => { if (e1 is SyncConflict) { refreshStats.TotalSyncConflicts++; } else { refreshStats.TotalSyncErrors++; } }); // Send the response to the local provider this._localProvider.OnChangeSetUploaded(changeSetId, response); uploadComplete = request.IsLastBatch; } } else if (!downloadComplete) { // Create a SyncRequest for download. CacheRequest request = new CacheRequest() { RequestType = CacheRequestType.DownloadChanges, KnowledgeBlob = this.LocalProvider.GetServerBlob() }; ChangeSet changeSet = (ChangeSet)this._cacheRequestHandler.ProcessCacheRequest(request); // Increment the refresh stats refreshStats.TotalChangeSetsDownloaded++; refreshStats.TotalDownloads += (uint)changeSet.Data.Count; // Call the SaveChangeSet method on local provider. this.LocalProvider.SaveChangeSet(changeSet); downloadComplete = changeSet.IsLastBatch; } } refreshStats.EndTime = DateTime.Now; // Finally return the statistics object return refreshStats; } finally { // Unlock the ControllerBehavior object this._controllerBehavior.Locked = false; } }
/// <summary> /// Method that refreshes the Cache by uploading all modified changes and then downloading the /// server changes. /// </summary> /// <returns>A CacheRefreshStatistics object denoting the statistics from the Refresh call</returns> public CacheRefreshStatistics Refresh() { this._controllerBehavior.Locked = true; try { // First create the CacheRequestHandler this._cacheRequestHandler = CacheRequestHandler.CreateRequestHandler(this._serviceUri, this._controllerBehavior); CacheRefreshStatistics refreshStats = new CacheRefreshStatistics(); refreshStats.StartTime = DateTime.Now; bool uploadComplete = false; bool downloadComplete = false; // Start sync by executin an Upload request while (!uploadComplete || !downloadComplete) { if (!uploadComplete) { Guid changeSetId = Guid.NewGuid(); ChangeSet changeSet = this._localProvider.GetChangeSet(changeSetId); if (changeSet.Data == null || changeSet.Data.Count == 0) { // No data to upload. Skip upload phase. uploadComplete = true; } else { // Create a SyncRequest out of this. CacheRequest request = new CacheRequest() { RequestId = changeSetId, RequestType = CacheRequestType.UploadChanges, Changes = changeSet.Data, KnowledgeBlob = changeSet.ServerBlob, IsLastBatch = changeSet.IsLastBatch }; // Increment the stats refreshStats.TotalChangeSetsUploaded++; ChangeSetResponse response = (ChangeSetResponse)this._cacheRequestHandler.ProcessCacheRequest(request); // Increment the stats refreshStats.TotalUploads += (uint)request.Changes.Count; response.ConflictsInternal.ForEach((e1) => { if (e1 is SyncConflict) { refreshStats.TotalSyncConflicts++; } else { refreshStats.TotalSyncErrors++; } }); // Send the response to the local provider this._localProvider.OnChangeSetUploaded(changeSetId, response); uploadComplete = request.IsLastBatch; } } else if (!downloadComplete) { // Create a SyncRequest for download. CacheRequest request = new CacheRequest() { RequestType = CacheRequestType.DownloadChanges, KnowledgeBlob = this.LocalProvider.GetServerBlob() }; ChangeSet changeSet = (ChangeSet)this._cacheRequestHandler.ProcessCacheRequest(request); // Increment the refresh stats refreshStats.TotalChangeSetsDownloaded++; refreshStats.TotalDownloads += (uint)changeSet.Data.Count; // Call the SaveChangeSet method on local provider. this.LocalProvider.SaveChangeSet(changeSet); downloadComplete = changeSet.IsLastBatch; } } refreshStats.EndTime = DateTime.Now; // Finally return the statistics object return(refreshStats); } finally { // Unlock the ControllerBehavior object this._controllerBehavior.Locked = false; } }
private object ProcessUploadRequest(HttpWebRequest webRequest, CacheRequest request) { using (Stream memoryStream = new MemoryStream()) { // Create a SyncWriter to write the contents this._syncWriter = new ODataAtomWriter(base.BaseUri); this._syncWriter.StartFeed(true, request.KnowledgeBlob ?? new byte[0]); foreach (IOfflineEntity entity in request.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 (TempIdToEntityMapping == null) { TempIdToEntityMapping = new Dictionary <string, IOfflineEntity>(); } tempId = Guid.NewGuid().ToString(); TempIdToEntityMapping.Add(tempId, entity); } this._syncWriter.AddItem(entity, tempId); } this._syncWriter.WriteFeed(XmlWriter.Create(memoryStream)); memoryStream.Flush(); // Set the content length webRequest.ContentLength = memoryStream.Position; using (Stream requestStream = webRequest.GetRequestStream()) { CopyStreamContent(memoryStream, requestStream); // Close the request stream requestStream.Flush(); requestStream.Close(); } } // Fire the Before request handler this.FirePreRequestHandler(webRequest); // Get the response HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); if (webResponse.StatusCode == HttpStatusCode.OK) { ChangeSetResponse changeSetResponse = new ChangeSetResponse(); using (Stream responseStream = webResponse.GetResponseStream()) { // Create the SyncReader this._syncReader = new ODataAtomReader(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(TempIdToEntityMapping, entity, tempId, changeSetResponse); } // 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(TempIdToEntityMapping, conflictEntity, tempId, changeSetResponse); } // Add conflict changeSetResponse.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)) { changeSetResponse.AddUpdatedItem(ackedEntity); } break; case ReaderItemType.SyncBlob: changeSetResponse.ServerBlob = this._syncReader.GetServerBlob(); break; } } } if (TempIdToEntityMapping != null && 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 permanant Id for the following tempId's: "); builder.Append(string.Join(",", TempIdToEntityMapping.Keys.ToArray())); throw new CacheControllerException(builder.ToString()); } this.FirePostResponseHandler(webResponse); webResponse.Close(); return(changeSetResponse); } else { throw new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", webResponse.StatusCode, webResponse.StatusDescription)); } }
private object ProcessDownloadRequest(HttpWebRequest webRequest, CacheRequest request) { using (Stream memoryStream = new MemoryStream()) { // Create a SyncWriter to write the contents this._syncWriter = new ODataAtomWriter(base.BaseUri); this._syncWriter.StartFeed(true, request.KnowledgeBlob ?? new byte[0]); this._syncWriter.WriteFeed(XmlWriter.Create(memoryStream)); memoryStream.Flush(); webRequest.ContentLength = memoryStream.Position; Stream requestStream = webRequest.GetRequestStream(); CopyStreamContent(memoryStream, requestStream); requestStream.Flush(); requestStream.Close(); // Fire the Before request handler this.FirePreRequestHandler(webRequest); } // Get the response HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); if (webResponse.StatusCode == HttpStatusCode.OK) { ChangeSet changeSet = new ChangeSet(); using (Stream responseStream = webResponse.GetResponseStream()) { // Create the SyncReader this._syncReader = new ODataAtomReader(responseStream, this._knownTypes); // Read the response while (this._syncReader.Next()) { switch (this._syncReader.ItemType) { case ReaderItemType.Entry: changeSet.AddItem(this._syncReader.GetItem()); break; case ReaderItemType.SyncBlob: changeSet.ServerBlob = this._syncReader.GetServerBlob(); break; case ReaderItemType.HasMoreChanges: changeSet.IsLastBatch = !this._syncReader.GetHasMoreChangesValue(); break; } } this.FirePostResponseHandler(webResponse); } webResponse.Close(); return(changeSet); } else { throw new CacheControllerException( string.Format("Remote service returned error status. Status: {0}, Description: {1}", webResponse.StatusCode, webResponse.StatusDescription)); } }