public static string SaveChanges(string rows)
	{
		Dictionary<string, string> result = new Dictionary<string, string>() { { "saved", "" }, { "ids", "" }, { "error", "" } };
		bool exists = false, saved = false;
		string ids = string.Empty, errorMsg = string.Empty, tempMsg = string.Empty;

		try
		{
			DataTable dtjson = (DataTable)JsonConvert.DeserializeObject(rows, (typeof(DataTable)));
			if (dtjson.Rows.Count == 0)
			{
				errorMsg = "Unable to save. An invalid list of changes was provided.";
				saved = false;
			}

			int id = 0, categoryId = 0, groupId = 0, sortOrder = 0, archive = 0;
			int assignedToID = 0, smeID = 0, busResourceID = 0, techResourceID = 0;
			string allocation = string.Empty, description = string.Empty;

			HttpServerUtility server = HttpContext.Current.Server;
			//save
			foreach (DataRow dr in dtjson.Rows)
			{
				id = categoryId = sortOrder = archive = 0;
				assignedToID = smeID = busResourceID = techResourceID = 0;
				allocation = description = string.Empty;
				
				tempMsg = string.Empty;
				int.TryParse(dr["AllocationCategory"].ToString(), out categoryId);
                int.TryParse(dr["AllocationGroup"].ToString(), out groupId);
                int.TryParse(dr["ALLOCATIONID"].ToString(), out id);
				allocation = server.UrlDecode(dr["ALLOCATION"].ToString());
				description = server.UrlDecode(dr["DESCRIPTION"].ToString());
				int.TryParse(dr["DefaultAssignedTo"].ToString(), out assignedToID);
				int.TryParse(dr["DefaultSME"].ToString(), out smeID);
				int.TryParse(dr["DefaultBusinessResource"].ToString(), out busResourceID);
				int.TryParse(dr["DefaultTechnicalResource"].ToString(), out techResourceID);
				int.TryParse(dr["SORT_ORDER"].ToString(), out sortOrder);
				int.TryParse(dr["ARCHIVE"].ToString(), out archive);

				if (string.IsNullOrWhiteSpace(allocation))
				{
					tempMsg = "You must specify a value for Allocation.";
					saved = false;
				}
				else
				{
					if (id == 0)
					{
						exists = false;
						saved = MasterData.Allocation_Add(categoryID: categoryId, groupID: groupId, allocation: allocation, description: description
							, defaultAssignedToID: assignedToID, defaultSMEID: smeID, defaultBusinessResourceID: busResourceID, defaultTechnicalResourceID: techResourceID
							, sortOrder: sortOrder, archive: archive == 1, exists: out exists, newID: out id, errorMsg: out tempMsg);
						if (exists)
						{
							saved = false;
							tempMsg = string.Format("{0}{1}{2}", tempMsg, tempMsg.Length > 0 ? Environment.NewLine : "", "Cannot add duplicate Allocation record [" + allocation + "].");
						}
					}
					else
					{
						saved = MasterData.Allocation_Update(id, categoryID: categoryId, groupID: groupId, allocation: allocation, description: description
							, defaultAssignedToID: assignedToID, defaultSMEID: smeID, defaultBusinessResourceID: busResourceID, defaultTechnicalResourceID: techResourceID
							, sortOrder: sortOrder, archive: archive == 1, errorMsg: out tempMsg);
					}
				}

				if (saved)
				{
					ids += string.Format("{0}{1}", ids.Length > 0 ? "," : "", id.ToString());
				}

				if (tempMsg.Length > 0)
				{
					errorMsg = string.Format("{0}{1}{2}", errorMsg, errorMsg.Length > 0 ? Environment.NewLine : "", tempMsg);
				}
			}
		}
		catch (Exception ex)
		{
			saved = false;
			errorMsg = ex.Message;
			LogUtility.LogException(ex);
		}

		result["ids"] = ids;
		result["saved"] = saved.ToString();
		result["error"] = errorMsg;

		return JsonConvert.SerializeObject(result, Formatting.None);
	}