/// <summary> /// Gets the scope name from the query string. /// </summary> /// <returns>scope name</returns> private string GetScopeName(string[] RelativeUriSegments) { // The item at index 0 of the RelativeUriSegments array contains the scope name. if (/*_serviceHost.*/ RelativeUriSegments.Length < 1 || String.IsNullOrEmpty(/*_serviceHost.*/ RelativeUriSegments[0].Trim())) { throw SyncServiceException.CreateBadRequestError(Strings.MissingScopeNameInRequest); } // Scope names are compared in a case insensitive manner and used as keys in lower case. return /*_serviceHost.*/ (RelativeUriSegments[0].ToLowerInvariant()); }
/// <summary> /// Generate the Id for an entity. The format currently is the OData Id. /// i.e. http://baseUri/tableName(primarykeylist) /// </summary> /// <param name="entity"></param> /// <returns></returns> internal static string GenerateOfflineEntityId(IOfflineEntity entity) { var primaryKeyString = ReflectionUtility.GetPrimaryKeyString(entity); if (String.IsNullOrEmpty(primaryKeyString)) { throw SyncServiceException.CreateInternalServerError( String.Format("GetPrimaryKeyString method returned an empty string for entity type {0}.", entity.GetType())); } return(String.Format(@"{0}/{1}({2})", "http://bitmobile.com/" + AppDomain.CurrentDomain.FriendlyName /*WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri*/, entity.GetType().Name, primaryKeyString)); }
internal static SyncConflictResolution GetSyncConflictResolution(ConflictResolutionPolicy conflictResolutionPolicy) { switch (conflictResolutionPolicy) { case ConflictResolutionPolicy.ClientWins: return(SyncConflictResolution.ClientWins); case ConflictResolutionPolicy.ServerWins: return(SyncConflictResolution.ServerWins); default: throw SyncServiceException.CreateInternalServerError(Strings.UnsupportedConflictResolutionPolicy); } }
internal static string GetContentType(SyncSerializationFormat format) { switch (format) { case SyncSerializationFormat.ODataAtom: return("application/atom+xml"); case SyncSerializationFormat.ODataJson: return("application/json"); default: throw SyncServiceException.CreateBadRequestError("Unsupported serialization format"); } }
/// <summary> /// Verify query parameters for '$' etc. /// </summary> internal void VerifyQueryParameters() { NameValueCollection values = _operationContext.IncomingRequest.UriTemplateMatch.QueryParameters; var queryDictionary = new Dictionary <string, string>(); foreach (string key in values.Keys) { if (!queryDictionary.ContainsKey(key)) { queryDictionary.Add(key, values[key]); } else { throw SyncServiceException.CreateBadRequestError(Strings.DuplicateParametersInRequestUri); } } }
private void ProcessSyncServiceException(SyncServiceException syncServiceException) { string exceptionMessage = WebUtil.GetExceptionMessage(syncServiceException); SyncServiceTracer.TraceWarning(exceptionMessage); _outgoingMessage = _syncConfiguration.UseVerboseErrors ? CreateExceptionMessage((HttpStatusCode)syncServiceException.StatusCode, exceptionMessage) : CreateExceptionMessage((HttpStatusCode)syncServiceException.StatusCode, syncServiceException.Message); // Add the "Allow" HTTP header if present._outgoingMessage.Properties[HttpResponseMessageProperty.Name]. if (!String.IsNullOrEmpty(syncServiceException.ResponseAllowHeader) && null != _outgoingMessage.Properties[HttpResponseMessageProperty.Name]) { ((HttpResponseMessageProperty)_outgoingMessage.Properties[HttpResponseMessageProperty.Name]). Headers.Add("Allow", syncServiceException.ResponseAllowHeader); } }
/// <summary> /// Get a dictionary that contains command parameters. /// </summary> /// <param name="queryStringCollection">Querystring represented as a NameValueCollection</param> /// <returns>Dictionary of command parameter types and values.</returns> private Dictionary <CommandParamType, object> GetCommandParameters(NameValueCollection queryStringCollection) { var commandParams = new Dictionary <CommandParamType, object>(); Dictionary <string, string> filterParams = GetFilterParamsFromIncomingRequest(queryStringCollection); commandParams.Add(CommandParamType.FilterParameters, filterParams); string scopeName = GetScopeName(); // Check the scope requested against the list of enabled scopes. if (!_configuration.ScopeNames.Contains(scopeName.ToLowerInvariant())) { throw SyncServiceException.CreateBadRequestError(Strings.SyncScopeNotSupported); } commandParams.Add(CommandParamType.ScopeName, scopeName); return(commandParams); }
/// <summary> /// Invokes the InitializeService user method. /// </summary> /// <param name="type">service type (used for reflection)</param> private void InvokeStaticInitialization(Type type) { // Search for the InitializeService method going from most-specific to least-specific type. const BindingFlags bindingAttr = BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly; while (type != null) { MethodInfo info = type.GetMethod("InitializeService", bindingAttr, null, new[] { typeof(ISyncServiceConfiguration) }, null); if ((info != null) && (info.ReturnType == typeof(void))) { ParameterInfo[] parameters = info.GetParameters(); if ((parameters.Length == 1) && !parameters[0].IsOut) { var objArray = new object[] { this }; try { info.Invoke(null, objArray); return; } catch (TargetInvocationException exception) { SyncTracer.Warning("Exception invoking the static InitializeService method. Details {0}", WebUtil.GetExceptionMessage(exception)); ErrorHandler.HandleTargetInvocationException(exception); throw; } } } type = type.BaseType; } // We should never exit from here when the InitializeService method is implemented. throw SyncServiceException.CreateInternalServerError(Strings.InitializeServiceMethodNotImplemented); }
/// <summary> /// Get the value for a query string item. /// </summary> /// <param name="item">Item to search for in the incoming request uri.</param> /// <returns>Value of the item.</returns> internal string GetQueryStringItem(string item) { NameValueCollection values = _operationContext.IncomingRequest.UriTemplateMatch.QueryParameters; // Check if we have the item by using a direct function (good performance) // Note: the underlying data structure in a NameValueCollection is a hashtable. string[] strArray = values.GetValues(item); // If no match found, then loop through all keys and do a case insensitive search. if ((strArray == null) || (strArray.Length == 0)) { string str = null; foreach (string str2 in values.Keys) { if ((str2 != null) && StringComparer.OrdinalIgnoreCase.Equals(str2.Trim(), item)) { if (str != null) { // Bad request throw SyncServiceException.CreateBadRequestError(Strings.DuplicateParametersInRequestUri); } str = str2; strArray = values.GetValues(str2); } } if ((strArray == null) || (strArray.Length == 0)) { return(null); } } if (strArray.Length != 1) { // syntax error, cannot have multiple querystring parameters with the same name. throw SyncServiceException.CreateBadRequestError(Strings.DuplicateParametersInRequestUri); } return(strArray[0]); }
/// <summary> /// Validate the HTTP Verb for GET/POST and check if the URL matches the allowed format. /// </summary> internal void ValidateRequestHttpVerbAndSegments() { if (_operationContext == null) { throw SyncServiceException.CreateBadRequestError(Strings.NullWebOperationContext); } // Only allow GET and POST verbs if (0 != String.Compare(RequestHttpMethod, "GET", StringComparison.InvariantCultureIgnoreCase) && 0 != String.Compare(RequestHttpMethod, "POST", StringComparison.InvariantCultureIgnoreCase)) { SyncTracer.Warning("Request HTTP method is not GET or POST. HTTP Method: {0}", RequestHttpMethod ?? String.Empty); throw SyncServiceException.CreateMethodNotAllowed(Strings.UnsupportedHttpMethod, "GET, POST"); } // Check if we have less than 2 relative segments. The maximum segments we can have is 2 // which is for /scope/operation. if (RelativeUriSegments.Length > 2) { throw SyncServiceException.CreateBadRequestError(Strings.InvalidUrlFormat); } }
/// <summary> /// Add a new filter parameter configuration. /// </summary> /// <param name="queryStringParam">Name of the querystring parameter</param> /// <param name="tableName">SQL table name</param> /// <param name="sqlParameterName">SQL parameter name (has to be exact since its used in query formation)</param> /// <param name="typeOfParam">Indicates the Type of the parameter</param> public void AddFilterParameterConfiguration(string queryStringParam, string tableName, string sqlParameterName, Type typeOfParam) { // Check if there is a filter parameter which has the same querystring, tablename and sqlparameter name. if (0 == _filterParameters.Where(p => 0 == String.Compare(p.QueryStringKey, queryStringParam, StringComparison.InvariantCultureIgnoreCase) && 0 == String.Compare(p.TableName, tableName, StringComparison.InvariantCultureIgnoreCase) && 0 == String.Compare(p.SqlParameterName, sqlParameterName, StringComparison.InvariantCultureIgnoreCase) ).Count()) { _filterParameters.Add(new SqlSyncProviderFilterParameterInfo { QueryStringKey = queryStringParam.ToLowerInvariant(), SqlParameterName = sqlParameterName, TableName = tableName, ValueType = typeOfParam }); } else { throw SyncServiceException.CreateInternalServerError(Strings.DuplicateFilterParameter); } }
internal Request(RequestCommand requestCommand, HttpContextServiceHost serviceHost, Dictionary <CommandParamType, object> commandParams, byte[] blob, List <IOfflineEntity> entities, SyncSerializationFormat responseSerializationFormat) { IdToTempIdMapping = new Dictionary <string, string>(); RequestCommand = requestCommand; ServiceHost = serviceHost; CommandParams = commandParams; SyncBlob = blob; ResponseSerializationFormat = responseSerializationFormat; if (null != entities && requestCommand != RequestCommand.UploadChanges) { throw SyncServiceException.CreateBadRequestError(Strings.EntitiesOnlyAllowedForUploadChangesRequest); } EntityList = entities; }
/// <summary>Processes the diagnostics page request.</summary> /// <returns>The response <see cref="Message"/>.</returns> public Message ProcessRequestForDiagnostics() { try { // Intialize the service host. _serviceHost = new HttpContextServiceHost(); // Ensure configuration. CreateConfiguration(); Debug.Assert(_syncConfiguration != null, "_syncConfiguration != null"); // Check the EnableDiagPage property and invoke the callback if necessary. // Return 404-Not Found if not enabled. if (!_syncConfiguration.EnableDiagnosticPage || !OnBeginDiagnosticRequest()) { throw SyncServiceException.CreateResourceNotFound(); } // Perform diagnostic checks and prepare outgoing message. _outgoingMessage = DiagHelper.CreateDiagResponseMessage(_syncConfiguration, _serviceHost); } catch (SyncServiceException syncServiceException) { ProcessSyncServiceException(syncServiceException); } catch (Exception exception) { if (WebUtil.IsFatalException(exception)) { throw; } _outgoingMessage = CreateMessageFromUnhandledException(exception); } return(_outgoingMessage); }
/// <summary> /// Invokes the static InitializeService method that allows one-time service wide policy configuration. /// </summary> /// <param name="syncServiceType">service type (used for reflection).</param> internal void Initialize(Type syncServiceType) { if (!IsInitialized) { lock (_lockObject) { if (!IsInitialized) { // Initialize the filter parameter list, to remove data from previously // failed initialization attempt. _filterParameters = new List <SqlSyncProviderFilterParameterInfo>(); // Invoke the static InitializeService method. InvokeStaticInitialization(syncServiceType); // Build Sync Operation Inspectors list ReadSyncInterceptors(syncServiceType); // Check for empty connection string. if (String.IsNullOrEmpty(ServerConnectionString)) { throw SyncServiceException.CreateInternalServerError(Strings.ConnectionStringNotSet); } // There are no dynamic scope changes allowed anyway, so why should the service start. if (0 == ScopeNames.Count) { throw SyncServiceException.CreateInternalServerError(Strings.NoScopesVisible); } // We are initialized now. IsInitialized = true; } } } }
/// <summary>Creates a new "Method Not Allowed" exception.</summary> /// <param name="errorMessage">Plain text error message for this exception.</param> /// <param name="allow">String value for 'Allow' header in response.</param> /// <returns>A new SyncServiceException to indicate the requested method is not allowed on the response.</returns> public static SyncServiceException CreateMethodNotAllowed(string errorMessage, string allow) { var exception = new SyncServiceException(ResponseHttpStatusCode.MethodNotAllowed, errorMessage) { _responseAllowHeader = allow }; return exception; }
/// <summary> /// Read and parse the incoming request stream for a POST request. /// </summary> private void ReadIncomingRequestStreamForPost() { if (null == _serviceHost.RequestStream || !_serviceHost.RequestStream.CanRead) { SyncTracer.Info("Request stream for HTTP POST is empty, null or cannot be read."); return; } try { var reader = WebUtil.GetSyncReader(_serviceHost.GetRequestContentSerializationFormat(), _serviceHost.RequestStream, _configuration.TypeToTableGlobalNameMapping.Keys.ToArray()); reader.Start(); while (reader.Next()) { switch (reader.ItemType) { case ReaderItemType.Entry: IOfflineEntity entity = reader.GetItem(); if (entity.ServiceMetadata.IsTombstone) { if (String.IsNullOrEmpty(entity.ServiceMetadata.Id)) { throw SyncServiceException.CreateBadRequestError(Strings.TombstoneEntityHasNoId); } WebUtil.ParseIdStringAndPopulateKeyFields(entity, _serviceHost.ServiceBaseUri); } _entityList.Add(entity); bool hasTempId = false; if (reader.HasTempId()) { // Save the entity id to tempId mapping for use later when writing response. _idToTempIdMapping.Add(WebUtil.GenerateOfflineEntityId(entity), reader.GetTempId()); hasTempId = true; } // Make sure, we have atleast one of Id or TempId if (String.IsNullOrEmpty(entity.ServiceMetadata.Id) && !hasTempId) { throw SyncServiceException.CreateBadRequestError(Strings.BothIdAndTempIdAreMissing); } break; case ReaderItemType.SyncBlob: _syncBlob = reader.GetServerBlob(); break; } } } catch (XmlException exception) { SyncTracer.Warning("XmlException: {0}", WebUtil.GetExceptionMessage(exception)); throw SyncServiceException.CreateBadRequestError(Strings.BadRequestPayload); } }
/// <summary> /// Reflect the types from T and cache it in a list for reference. /// </summary> private void DiscoverTypes(Type t) { TableGlobalNameToTypeMapping = new Dictionary <string, Type>(); TypeToTableGlobalNameMapping = new Dictionary <Type, string>(); TypeToTableLocalNameMapping = new Dictionary <Type, string>(); // We want to find the underlying type of every generic type which is a private instance member of the templated class // and check if it has the [SyncEntityType] attribute applied to it. If this check passes, then // extract the type and the attribute information and save it for future reference. FieldInfo[] privateTypes = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); foreach (var privateType in privateTypes) { Type fieldType = privateType.FieldType; if (fieldType.IsGenericType) { Type[] genericArguments = fieldType.GetGenericArguments(); if (null != genericArguments && 1 == genericArguments.Length) { var argument = genericArguments[0]; var attributes = argument.GetCustomAttributes(false); object syncAttribute = null; foreach (var attribute in attributes) { if (attribute.GetType() == typeof(SyncEntityTypeAttribute)) { syncAttribute = attribute; break; } } if (null != syncAttribute) { // Read the TableGlobalName property value PropertyInfo globalTablenamePropertyInfo = syncAttribute.GetType().GetProperty(SyncServiceConstants.SYNC_ENTITY_TYPE_TABLE_GLOBAL_NAME); //Note: We cannot ToLower() this because the datatable name for sqlsyncprovider makes a case sensitive comparison. string globalTableName = Convert.ToString(globalTablenamePropertyInfo.GetValue(syncAttribute, null)); if (TableGlobalNameToTypeMapping.ContainsKey(globalTableName)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, Strings.DuplicateGlobalTableName, globalTableName)); } if (String.IsNullOrEmpty(globalTableName)) { throw new InvalidOperationException(Strings.TableGlobalNameCannotByEmpty); } TableGlobalNameToTypeMapping.Add(globalTableName, argument.UnderlyingSystemType); TypeToTableGlobalNameMapping.Add(argument.UnderlyingSystemType, globalTableName); // Read the TableLocalName property value PropertyInfo localTablenamePropertyInfo = syncAttribute.GetType().GetProperty(SyncServiceConstants.SYNC_ENTITY_TYPE_TABLE_LOCAL_NAME); string localTableName = Convert.ToString(localTablenamePropertyInfo.GetValue(syncAttribute, null)); if (String.IsNullOrEmpty(localTableName)) { throw new InvalidOperationException(Strings.TableLocalNameCannotByEmpty); } TypeToTableLocalNameMapping.Add(argument.UnderlyingSystemType, localTableName); } } } } if (0 == TableGlobalNameToTypeMapping.Count) { throw SyncServiceException.CreateInternalServerError(Strings.NoValidTypeFoundForSync); } }
/// <summary> /// Parse the Id string and populate key fields of the object. This is called when the client sends tombstones /// and the Key fields are not present in the input payload. /// The approach used is to parse each key field individually and set it to a property with the same name. /// For example: For http://host/service.svc/Tag(ID=1), we parse out ID=1 and then populate the ID property of the targetObject with the /// value 1. /// </summary> /// <param name="entity">Entity for which we need to set the key fields.</param> /// <param name="serviceBaseUri"> /// Base Uri of the service. The ServiceMetadata.Id property has the Uri which we want to strip off before attempting to /// parse the keys and values. /// </param> internal static void ParseIdStringAndPopulateKeyFields(IOfflineEntity entity, Uri serviceBaseUri) { Debug.Assert(null != entity); Debug.Assert(!String.IsNullOrEmpty(entity.ServiceMetadata.Id)); string idString = entity.ServiceMetadata.Id; // Remove the ServiceUri and the entity type name from the EntityId // Note: Case sensitive comparisons are made since the client isn't supposed to change the Id for an // entity. string serviceUriWithTableName = serviceBaseUri + "/" + entity.GetType().Name; // If the Id does not have the correct format of serviceUri/TableName, then we should not continue further. if (!idString.StartsWith(serviceUriWithTableName, false, CultureInfo.InvariantCulture)) { throw SyncServiceException.CreateBadRequestError(String.Format(Strings.EntityIdFormatIsIncorrect, idString)); } // Remove the host and the table name from the Id. // Example: http://host/service.svc/table(id=123) will become (id=123) idString = idString.Remove(0, serviceUriWithTableName.Length); // Remove leading '/' if any. After this the id string is of the format (ID=1) or (ID=guid'<guidValue>') // If there are multiple Id values then, they are comma separated.));)) if (idString.StartsWith("/")) { idString = idString.Substring(1); } // Make sure the ( and ) parenthesis exist. if (String.IsNullOrEmpty(idString) || idString[0] != '(' || idString[idString.Length - 1] != ')') { throw SyncServiceException.CreateBadRequestError(String.Format(Strings.EntityIdFormatIsIncorrect, entity.ServiceMetadata.Id)); } // Remove the ( and ) characters. idString = idString.Substring(1, idString.Length - 2); // Get the key properties for the entity. var keyFieldPropertyInfoList = ReflectionUtility.GetPrimaryKeysPropertyInfoMapping(entity.GetType()); // Split the string and get individual keyvalue pair strings. They key and value are still a single string separated by '='. // for types such as Guid, the value will be prefixed with 'guid'. string[] primaryKeyValuePair = idString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); // Throw if there is a mismatch between the key count of the entity and that passed in the URI. if (primaryKeyValuePair.Length != keyFieldPropertyInfoList.Length) { throw SyncServiceException.CreateBadRequestError(String.Format(Strings.BadRequestKeyCountMismatch, entity.ServiceMetadata.Id, entity.GetType())); } // At this point, we have key value pairs of the form "ID=1". foreach (var keyValuePair in primaryKeyValuePair) { // example: ID=1 string[] keyValue = keyValuePair.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); // Every key should have only 2 components. Debug.Assert(2 == keyValue.Length); // Get the property from the key list. string key = keyValue[0].Trim(); var propertyInfo = keyFieldPropertyInfoList.Where(p => p.Name == key).FirstOrDefault(); if (null == propertyInfo) { throw SyncServiceException.CreateBadRequestError( String.Format(Strings.BadRequestKeyNotFoundInResource, key, entity.ServiceMetadata.Id, entity.GetType())); } // Get typed value of the value. object targetValue; // Parse the value based on the target type. if (!ODataIdParser.TryKeyStringToPrimitive(keyValue[1], propertyInfo.PropertyType, out targetValue)) { throw SyncServiceException.CreateBadRequestError( String.Format(Strings.UnableToParseKeyValueForProperty, keyValuePair, entity.ServiceMetadata.Id)); } // Set the property value. propertyInfo.SetValue(entity, targetValue, null); } }
// This method ensures that we have a valid feed through the formatters. The BodyWriter delegate in WCF // seems to not recover from an unhandled exception caused by the formatters and sends out an empty response to the caller. // This is a workaround for this issue until we find a better solution. private SyncWriter GetSyncWriterWithContents() { var conflictEntryKeys = new List <string>(); var errorEntryKeys = new List <string>(); var primaryKeyToIncomingEntitiesMapping = new Dictionary <string, IOfflineEntity>(); // Save the mapping between entity PK string -> entity foreach (var entity in _incomingEntities) { string primaryKey = ReflectionUtility.GetPrimaryKeyString(entity); if (primaryKeyToIncomingEntitiesMapping.ContainsKey(primaryKey)) { throw SyncServiceException.CreateInternalServerError(Strings.MultipleEntriesWithSamePrimaryKeyInIncomingRequest); } primaryKeyToIncomingEntitiesMapping.Add(primaryKey, entity); } if (_rejectedEntities != null) { foreach (var entity in _rejectedEntities.Keys) { string primaryKey = ReflectionUtility.GetPrimaryKeyString(entity); if (primaryKeyToIncomingEntitiesMapping.ContainsKey(primaryKey)) { throw SyncServiceException.CreateInternalServerError(Strings.MultipleEntriesWithSamePrimaryKeyInIncomingRequest); } primaryKeyToIncomingEntitiesMapping.Add(primaryKey, entity); } } // Get the appropriate SyncWriter instance based on the serialization format. var oDataWriter = WebUtil.GetSyncWriter(_responseSerializationFormat, _baseUri); oDataWriter.StartFeed(_applyChangesResponse.IsLastBatch, _applyChangesResponse.ServerBlob); // Write conflict entities. foreach (var entity in _applyChangesResponse.Conflicts) { // Add the primary key string to the conflictEntryKey list. // The primary keys are the same for both Live and Losing entities. conflictEntryKeys.Add(ReflectionUtility.GetPrimaryKeyString(entity.LiveEntity)); string tempId; // If the client change lost, then we need to set the Id property // only if the property was not null/empty in the incoming request. string entityId = WebUtil.GenerateOfflineEntityId(entity.LiveEntity); // Set the Id property of the Live entity (server's copy). entity.LiveEntity.ServiceMetadata.Id = entityId; // Set the Id property of the Losing entity to the incoming entity's Id value entity.LosingEntity.ServiceMetadata.Id = entityId; // get the original tempId. Null value is ok. _idToTempIdMapping.TryGetValue(entityId, out tempId); if (entity.Resolution == SyncConflictResolution.ServerWins) { // The losing entity is the client's copy. // When resolution is ServerWins, we only need to set the losing change tempId. oDataWriter.AddConflictItem(entity.LiveEntity, null /*tempId*/, entity.LosingEntity, tempId, entity.Resolution); } // If the client change won, then just set the Id property since an insert would have succeeded. else { // When resolution is ClientWins, we only need to set the LiveEntity tempId. oDataWriter.AddConflictItem(entity.LiveEntity, tempId, entity.LosingEntity, null /* tempId */, entity.Resolution); } } // Write error entities. foreach (var syncError in _applyChangesResponse.Errors) { Debug.Assert(null != syncError.LiveEntity); Debug.Assert(null != syncError.ErrorEntity); string entityId = WebUtil.GenerateOfflineEntityId(syncError.LiveEntity); // Set the Id for Live and Losing entity. syncError.LiveEntity.ServiceMetadata.Id = entityId; syncError.ErrorEntity.ServiceMetadata.Id = entityId; string primaryKeyString = ReflectionUtility.GetPrimaryKeyString(syncError.ErrorEntity); // Add the string to the error key list. errorEntryKeys.Add(primaryKeyString); string tempId; _idToTempIdMapping.TryGetValue(entityId, out tempId); oDataWriter.AddErrorItem(syncError.LiveEntity, syncError.ErrorEntity, tempId, syncError.Description); } // Write all the inserted records here by iterating over the _incomingNewInsertEntities list foreach (var entity in _incomingNewInsertEntities) { string entityTempId; // Get the tempId of the entity. _idToTempIdMapping.TryGetValue(WebUtil.GenerateOfflineEntityId(entity), out entityTempId); // Write the output to the SyncWriter. oDataWriter.AddItem(entity, entityTempId); } return(oDataWriter); }
/// <summary> /// Get the serialization format for the response based on the value of the HTTP Accept header. /// /// If $format is not specified then the format comes from the accept header. /// /// The order in which a response content-type is chosen is based on the /// incoming "Accept" header and the types that the service supports. /// According to the HTTP/1.1 Header Field Definitions RFC /// (http:///www.w3.org/Protocols/rfc2616/rfc2616-sec14.html), an absence of the /// Accept header means that the client accepts all response types. /// /// Media ranges can be overridden by more specific media ranges, for example: /// both application/json and application/atom+xml would override */*. /// /// Depending on the service configuration application/atom+xml would override application/json /// if application/atom+xml if the default serialization format, and application/json would /// override application/atom+xml if the default serialization format is application/json. /// /// A client can also send a media range of the following type: application/*, which can be /// substituted for application/atom+xml or application/json depending on the service configuration. /// /// A. If the default configured serialization format is "application/atom+xml" /// /// The formats in order of priority are: /// 1. application/atom+xml /// 2. application/json /// 3. application/* or */* substituted with application/atom+xml /// /// Examples (order of accept headers doesn't matter): /// "application/*" -> ATOM+XML /// "application/*,application/JSON" -> JSON /// "application/*,application/ATOM+XML" -> ATOM+XML /// "application/*,application/ATOM+XML,application/JSON" -> ATOM+XML /// "application/JSON" -> JSON /// "application/ATOM+XML" -> ATOM+XML /// "application/JSON,application/ATOM+XML" -> ATOM+XML /// /// B. If the default configured serialization format is "application/json" /// /// The formats in order of priority are: /// 1. application/json /// 2. application/atom+xml /// 3. application/* or */* substituted with application/json /// /// Examples (order of accept headers doesn't matter): /// "application/*" -> JSON /// "application/*,application/JSON" -> JSON /// "application/*,application/ATOM+XML" -> ATOM+XML /// "application/*,application/ATOM+XML,application/JSON" -> JSON /// "application/JSON" -> JSON /// "application/ATOM+XML" -> ATOM+XML /// "application/JSON,application/ATOM+XML" -> JSON /// /// Note: headers from firefox need to be trimmed before we make a comparison.In other words the media range /// parameter as specified in the above RFC are ignored. /// </summary> /// <returns>Response serialization format</returns> internal SyncSerializationFormat GetOutputSerializationFormat(SyncSerializationFormat defaultSerializationFormat) { // Read $format from querystring first string formatQueryString = QueryStringCollection[SYNC_FORMAT_QUERYKEY]; if (!String.IsNullOrEmpty(formatQueryString)) { if (0 == String.Compare(formatQueryString.ToLowerInvariant(), "atom", StringComparison.InvariantCultureIgnoreCase)) { return(SyncSerializationFormat.ODataAtom); } if (0 == String.Compare(formatQueryString.ToLowerInvariant(), "json", StringComparison.InvariantCultureIgnoreCase)) { return(SyncSerializationFormat.ODataJson); } } else if (!String.IsNullOrEmpty(RequestAccept)) { var header = RequestAccept.ToLowerInvariant().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(h => h.Trim()); SyncSerializationFormat?outputSerializationFormat = null; foreach (string headerString in header) { // Media range followed by optional semi-column and accept-params , string[] headerStringParts = headerString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); if (0 == headerStringParts.Length) { continue; } // Is this header application/atom+xml if (headerStringParts[0].Equals(CONTENT_TYPE_APPLICATION_ATOM, StringComparison.OrdinalIgnoreCase)) { outputSerializationFormat = SyncSerializationFormat.ODataAtom; } // Is this header application/json if (headerStringParts[0].Equals(CONTENT_TYPE_APPLICATION_JSON, StringComparison.OrdinalIgnoreCase)) { outputSerializationFormat = SyncSerializationFormat.ODataJson; } // If the default header has been set explicitly then no need to read other headers if (outputSerializationFormat == defaultSerializationFormat) { break; } // Is this header application/* or */* if ((null == outputSerializationFormat) && (headerStringParts[0].Equals(CONTENT_TYPE_APPLICATION_ANY) || headerStringParts[0].Equals(CONTENT_TYPE_ANY))) { // Do not exit the loop as this can be overwritten by an explicit json or atnm+xml header outputSerializationFormat = defaultSerializationFormat; } } if (null == outputSerializationFormat) { throw SyncServiceException.CreateNotAcceptable(Strings.UnsupportedAcceptHeaderValue); } return(outputSerializationFormat.Value); } // return the default serialization format. return(defaultSerializationFormat); }