I am working on a app to maintain contacts, but I haven't found a way to successfully retrieve a contact I have created and perform an update or delete on that contact. I have boiled it down to a simple example:
public async Task TestContact()
{
try
{
//Make a list
ContactStore store = await ContactManager.RequestStoreAsync(ContactStoreAccessType.AppContactsReadWrite);
var lists = await store.FindContactListsAsync();
ContactList list = lists.FirstOrDefault((x) => x.DisplayName == "Test List");
if (list == null)
{
list = await store.CreateContactListAsync("Test List");
list.OtherAppReadAccess = ContactListOtherAppReadAccess.Full;
list.OtherAppWriteAccess = ContactListOtherAppWriteAccess.SystemOnly;
await list.SaveAsync();
}
//Make a contact and save it
Contact contact = new Contact();
contact.FirstName = "Test";
contact.LastName = "Contact";
await list.SaveContactAsync(contact);
//Modify the existing one just to show that saving again works
ContactEmail email = new ContactEmail();
email.Address = "test#test.com";
contact.Emails.Add(email);
await list.SaveContactAsync(contact); //This line updates existing contact as desired.
//Now simulate finding that contact, modify it, and save it
store = await ContactManager.RequestStoreAsync(ContactStoreAccessType.AppContactsReadWrite);
var contacts = await store.FindContactsAsync("Test Contact");
contact = contacts[0];
contact.Emails[0].Address = "newemail#test.com"; //Change a value
lists = await store.FindContactListsAsync();
list = lists.FirstOrDefault((x) => x.DisplayName == "Test List");
if (list != null)
{
await list.SaveContactAsync(contact); //This line throws "The operation identifier is not valid"
await list.DeleteContactAsync(contact); //This line throws "Value does not fall within the expected range"
}
}
catch (Exception ex)
{
//exception thrown!
}
}
The code creates a new list as needed, adds a contact to it, and updates that contact in place. It then tries to retrieve that contact by searching for it and calling save (or delete). Both save and delete throw exceptions as noted in the comments.
Has anyone been able to update a contact after finding it through a search? Ultimately I really want to be able to update a contact in place. I'm only trying delete as work around for not being able to save (UPDATE = DELETE + ADD)
Note that I want an IN PLACE update - I am not interested in creating a second contact that is linked to the first when I save changes.
Thanks in advance!
The problem here is that the contacts returned from FindContactsAsync(String) method are "aggregate contacts" (even though they are not linked in People app). As they are not the raw contacts created by your app, you are not able to change or delete them directly and that's why you got errors when calling SaveContactAsync or DeleteContactAsync method.
To solve this issue, we need to get the raw contact according to the "aggregate contact" with using FindRawContactsAsync(Contact) method and then modify, save or delete the the raw contact. For example:
//Now simulate finding that contact, modify it, and save it
var store = await ContactManager.RequestStoreAsync(ContactStoreAccessType.AppContactsReadWrite);
var contacts = await store.FindContactsAsync("Test Contact");
//This contact is a "aggregate contact"
var contact = contacts[0];
//Get the raw contacts according to the "aggregate contact"
var allStore = await ContactManager.RequestStoreAsync(ContactStoreAccessType.AllContactsReadOnly);
var rawContacts = await allStore.AggregateContactManager.FindRawContactsAsync(contact);
foreach (var rawContact in rawContacts)
{
var contactList = await store.GetContactListAsync(rawContact.ContactListId);
if (contactList != null && contactList.DisplayName == "Test List")
{
rawContact.Emails[0].Address = "newemail#test.com"; //Change a value
await contactList.SaveContactAsync(rawContact);
//await contactList.DeleteContactAsync(rawContact);
}
}
Related
Im trying to update a string field in specific document using Firebase Firestore for my android app but every method I see is while knowing the document refernce which I find difficult to find in my program.
Would like for some help for another method or help in finding the document refernce using a specific field value.
Thanks in advance.
(using C# btw)
private async Task<string> GetDocRefAsync(string userId)
{
Object obj = await FirestoreData.GetFirestore().Collection(DBConstants.FS_COLLECTION_USERS).
WhereEqualTo(DBConstants.FS_COLLECTION_USERS_USER_ID, userId).Get();
QuerySnapshot snapshot = (QuerySnapshot)obj;
if (snapshot.IsEmpty)
{
Log.Debug("UpdateGroupAsync", "userId: " + userId + " not found");
return null;
}
string docRef = "";
foreach (DocumentSnapshot item in snapshot.Documents)
{
//docRef = item.;
}
return docRef;
}
Firstly ive tried to find the document ref using this code but dont have a function to get the ref even after getting the correct document.
the fourth line from the bottom is where I couldnt find it.
database pic
this.groupCode = code;
string strUserRef = GetDocRefAsync(userRef).ToString();
DocumentReference docReference = database.Collection(DBConstants.FS_COLLECTION_USERS_GROUPS_CODE).Document(strUserRef);
docReference.Update(DBConstants.FS_COLLECTION_USERS_GROUPS_CODE, groupCode);
If you want to get the documents where a field has a given value, you can use a query. Then once the query returns, you can get documents IDs with the .Id field on each DocumentShapshot in the returned documents.
You will also need to add await for the returned value since it is an async method returning a Task<string> not returning a string.
private async Task<string> GetDocRefAsync(string userId) {
CollectionReference usersRef = FirestoreData.GetFirestore().Collection(DBConstants.FS_COLLECTION_USERS);
Query query = usersRef.WhereEqualTo(DBConstants.FS_COLLECTION_USERS_USER_ID, userId);
// or GetSnapshotAsync depending on the version of firebase
QuerySnapshot querySnapshot = await query.Get();
// Note: if it matches multiple documents this will just return
// the ID of the first match
foreach (DocumentSnapshot item in snapshot.Documents)
{
return item.Id;
}
Log.Debug("UpdateGroupAsync", "userId: " + userId + " not found");
return null;
}
And you can use it like this to update a document (note that you were using a different collection here - probably by mistake).
string userDocId = await GetDocRefAsync(userId);
CollectionReference userCollection = database.Collection(DBConstants.FS_COLLECTION_USERS);
DocumentReference docReference = userCollection.Document(userDocId);
// or UpdateAsync depending on your version of firebase
docReference.Update(DBConstants.FS_COLLECTION_USERS_GROUPS_CODE, groupCode);
I've been investigating how to add (and later remove) a user from an Azure AD group using the Microsoft Graph API (the dotnet/C# library available on nuget).
Nuget MS Graph API
Ignoring all else around getting a connected GraphServiceClient etc. I'm trying code very similar to the sample below, not getting any exception (suggesting things are fine) but when I get the group once again via the API, it's not got any members still!
Interestingly (as an aside), when I ask for memberOf property on the user object and tell it to expand it, it comes back null still.
var user = await client.Users[userPrincipalName]
.Request()
.Select("id,memberOf")
.Expand("memberOf")
.GetAsync();
var group = await client.Groups[groupId]
.Request()
.Select("members")
.Expand("members")
.GetAsync();
group.Members.Add(user);
await client.Groups[groupId].Request().UpdateAsync(group);
// userPrincipalName => "test.user#mytenant.com"
// groupId => the object GUID for the group
Does anyone know what I'm doing wrong here? The docs I used to come up with this code were based on the links to the following documents:
API doc for 'group'
Adding a member to a group
Also, I tried to style the approach on the solution suggested here to setting licenses for users:
Assign user license via Graph API
As usual, thanks for any help.
Peter
Additional
I've also tried poking around in the graph API looking at potentially updating the .Members property/resource rather than the group itself:
await client.Groups[groupId].Members.Request(). // <-- Only has GetAsync()
But it only has the GetAync() method available to it.
Updated based on answer
var usersGroups = await client.Users[userPrincipalName].MemberOf.Request().GetAsync();
if (!usersGroups.Any(g => g is Group && g.Id == groupId))
{
// User is not a member of the group, add them.
var user = await client.Users[userPrincipalName].Request().Select("id").GetAsync();
await client.Groups[groupId].Members.References.Request().AddAsync(user);
}
I've added the code snippet above based on the answer, as I think it succinctly answers the issue regarding adding members.
Thanks to Nan Yu for the answer.
To add user to Group ,you could use :
User userToAdd = await graphClient.Users["objectID"].Request().GetAsync();
await graphClient.Groups["groupObjectID"].Members.References.Request().AddAsync(userToAdd);
To get members of a group :
List<ResultsItem> items = new List<ResultsItem>();
// Get group members.
IGroupMembersCollectionWithReferencesPage members = await graphClient.Groups[id].Members.Request().GetAsync();
if (members?.Count > 0)
{
foreach (User user in members)
{
// Get member properties.
items.Add(new ResultsItem
{
Properties = new Dictionary<string, object>
{
{ Resource.Prop_Upn, user.UserPrincipalName },
{ Resource.Prop_Id, user.Id }
}
});
}
}
Get groups the current user is a direct member of ,you could try :
IUserMemberOfCollectionWithReferencesPage memberOfGroups = await graphClient.Users["testnanyu#testbasic1.onmicrosoft.com"].MemberOf.Request().GetAsync();
if (memberOfGroups?.Count > 0)
{
foreach (var directoryObject in memberOfGroups)
{
// We only want groups, so ignore DirectoryRole objects.
if (directoryObject is Group)
{
Group group = directoryObject as Group;
items.Add(new ResultsItem
{
Display = group.DisplayName,
Id = group.Id
});
}
}
}
I am attempting to make a console application to retrieve results using the Dynamics CRM SDK and C# but I can't seem to get any results from my queries. I am able to see that I am connected to the server, but any QueryExpression I try to make seems to come back with nothing, even if I set it with no filter. Even when using an example from the documentation that we have corresponding values to I end up empty handed. My code is:
CrmServiceClient crmSvc = new CrmServiceClient(ConfigurationManager.ConnectionStrings["MyCRMServer"].ConnectionString);
Console.WriteLine(crmSvc.IsReady);
//Display the CRM version number and org name that you are connected to.
Console.WriteLine("Connected to CRM! (Version: {0}; Org: {1}",
crmSvc.ConnectedOrgVersion, crmSvc.ConnectedOrgUniqueName);
QueryExpression userSettingsQuery = new QueryExpression("contact");
userSettingsQuery.ColumnSet.AllColumns = true;
var retrieveRequest = new RetrieveMultipleRequest()
{
Query = userSettingsQuery
};
EntityCollection EntCol = (crmSvc.ExecuteCrmOrganizationRequest(retrieveRequest) as RetrieveMultipleResponse).EntityCollection;
foreach (var a in EntCol.Entities)
{
Console.WriteLine("Account name: {0} {1}", a.Attributes["firstname"], a.Attributes["lastname"]);
}
Console.Write(crmSvc.LastCrmError);
Console.Write(crmSvc.LastCrmException);
Console.ReadLine();
It returns no errors, and shows True for the connection, and I can't seem to find where to start to troubleshoot from here.
Your code works fine for me so it may be that the user you are connected as doesn't have permission to read contact records or there are no contact records in your CRM organization.
Also, I know you are trying to piece together a sample, but I put together a more straight forward version of your code below.
var crmSvc = new CrmServiceClient(<CONNECTION STRING>);
var contactQuery = new QueryExpression("contact")
{
ColumnSet = new ColumnSet("firstname", "lastname")
};
var contacts = crmSvc.RetrieveMultiple(contactQuery).Entities;
foreach(var contact in contacts)
{
Console.WriteLine("Contact name: {0} {1}", contact.GetAttributeValue<String>("firstname"), contact.GetAttributeValue<String>("lastname"));
}
currently I´m writing on a outlook plugin for syncing goolge contacts with outlook but I have to cover some special case:
When a contact gets deleted on google side, my application detects the missing contact and creates a new contact based on the contact info from the outlook one.
Is there a way to get an event or history from google that tells me a user deleted this contact(s)?
Edit 1:
Here is my code how I´m accessing the contacts (what is working FINE):
public GoogleAccessor()
{
var parameters = new OAuth2Parameters()
{
ClientId = CLIENTID,
ClientSecret = CLIENTSECRET,
RedirectUri = REDIRECTURI,
Scope = SCOPES
};
string url = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
//An own webbrowser for processing the access tokens
IAuthorizationCodeProvider authcodeProvider = new Presentation.BrowserAuthorizationCodeProvider(new Presentation.BrowserAuthentificatorVM());
parameters.AccessCode = authcodeProvider.GetAuthorizationCode(url);
if(parameters.AccessCode == null)
throw new GoogleOAuthException("AccesCode returned 'null' and failed!");
OAuthUtil.GetAccessToken(parameters);
this._contactsRequest = new ContactsRequest(new RequestSettings(APPLICATIONNAME, parameters) {AutoPaging = true});
}
public IList<IContact> GetAllMappedContacts()
{
Feed<Google.Contacts.Contact> f = _contactsRequest.GetContacts();
this._feedUri = new Uri(f.AtomFeed.Feed);
var photoList = new List<PhotoObject>();
foreach (var entry in f.Entries)
{
var photoObject = GetContactPhoto(entry);
if(photoObject != null)
photoList.Add(photoObject);
}
_googleMapper = new GoogleMapper(f.Entries);
return _googleMapper.MapToLocalContacts();;
}
The thing about syncing in general is that syncing is normally meant to work in one direction.
Source Data -> Data Flow -> Received Data.
In this instance, Outlook is your source data and Google is your received data. All information needs to come from your source. Since this is an Outlook add-in you are creating my suggestion would be to add a button to your add-in ribbon. You can call the button whatever ever you like (maybe "dontSyncButton"), but it's purpose is going to be Categorization of your contact.
Make it so that that when a contact is selected and then the button is clicked, the contact is given a special categorization (perhaps "Dont Sync").
Now give some logic to your code that executes the sync, and have that logic decide whether to sync the contact. Also, give some logic to tell the program to delete the contact out of Google for you if the contacts contains the special category. Semi-Pseudo Code below:
if(contact.Categories.ToString() == "Dont Sync")
{
//Don't Sync Contact
If(googleContact.Exists())
{
//Delete contact from Google if it exist
googleContact.Delete();
}
}
else
{
//Sync Contact
}
It would be nice if Outlook had many modifiable properties that weren't visible to users, but since it does not this is really one of the best options I can think of. I do this to sync contacts from a shared Outlook folder to personal ones and it has worked well so far.
Hope this helps!
I'm using the Tridion.OutboundEmail.ContentManagement API to retrieve and manage contact details.
Retrieving Contacts is working fine, as is pulling back the ExtendedDetails dictionary, but the Keywords TcmUriCollection is always empty.
[Test]
public void GetContacts_via_address_book()
{
var uri = new TcmUri(101, 2, TcmItemTypes.StaticAddressBook);
var addressBook = new StaticAddressBook(uri);
var contacts = addressBook.GetContacts();
foreach (var contact in contacts)
{
var firstName = contact.ExtendedDetails["NAME"].StringValue;
Assert.That(contact.EmailAddress, Is.Not.Empty); // PASS
Assert.That(firstName, Is.Not.Empty); // PASS
Assert.That(contact.Keywords.Count, Is.GreaterThan(0)); // FAIL
}
}
I've also tried the following method:
[Test]
public void GetContacts_via_filter()
{
var uri = new TcmUri(101, 2, TcmItemTypes.StaticAddressBook);
var addressBook = new StaticAddressBook(uri);
var filter = new ContactFilter(UserContext.Current);
var contacts = Contact.GetContacts(filter, addressBook);
foreach (var contact in contacts)
{
var firstName = contact.ExtendedDetails["NAME"].StringValue;
Assert.That(contact.EmailAddress, Is.Not.Empty); // PASS
Assert.That(firstName, Is.Not.Empty); // PASS
Assert.That(contact.Keywords.Count, Is.GreaterThan(0)); // FAIL
}
}
I can even add a keyword to a Contact's Keywords collection and save it, and it appears correctly in Tridion, but when I retrieve the same contact again, the collection is once again empty.
Does anyone have any experience with this API, and/or know what the problem is?
This is because Keywords are not loaded when you get a list of Contacts. Only a subset of the data is available, for performance reasons.
To solve this, you will need to re-load each Contact. Since Contacts are streamed from the database, you cannot do this inside of your loop. So you'll want to build the list of Contacts first and then loop over them and load them in full.
For more info and examples, please see my blog post on the subject:
http://pkjaer.wordpress.com/2011/12/01/looping-through-contacts/