Google Apps Application APIs: Is there a better way to find correspondance between a Documents List API Document and a Spreadsheets API Spreadsheet? - visual-c++

There is a task, using the .NET library for the Google Data API, to traverse across Google Drive folders, find required spreadsheets and change data of the selected spreadsheets.
Folders traversing is performed using the Google.GData.Documents.FolderQuery and other classes of the Google.GData.Documents namespace. After a correct document is found is necessary to manage it using the Google.GData.Spreadsheets.Spreadsheet class. Now I find a correspondence between the Google.GData.Documents.DocumentEntry class and the Google.GData.Spreadsheets.Spreadsheet class instances by extracting the document key from the document URL, iterating all spreadsheets, extracting a spreadsheet URL and comparing the two keys. The code looks like
private string GetKey(string url) {
string res = null;
Match match = Regex.Match(url, #"\?key=([A-Za-z0-9]+)");
if (match.Success) {
res = match.Groups[1].Value;
}
return res;
}
private SpreadsheetEntry GetSpreadSheetForDocument(SpreadsheetsService serviceSS, DocumentEntry entrySS) {
SpreadsheetEntry res = null;
string strSSKey = GetKey(entrySS.AlternateUri.Content);
Google.GData.Spreadsheets.SpreadsheetQuery query = new Google.GData.Spreadsheets.SpreadsheetQuery();
SpreadsheetFeed feed = serviceSS.Query(query);
foreach (SpreadsheetEntry entry in feed.Entries) {
if (GetKey(entry.AlternateUri.Content) == strSSKey) {
res = entry;
break;
}
}
return res;
}
Is there another, more elegant and correct, way to do this?

As best I can tell, not only is there no better way to do this, but even this technique will fail. As of recent(?) changes to Google Drive API, the keys for the SAME DOCUMENT retrieved by Document List versus Spreadsheets APIs are incompatible. Though using a spreadsheet URL constructed from a key returned by Document List API WILL get you a SpreadsheetEntry, spreadsheet operations on that entry are likely to produce "Invalid Token" Authentication Exceptions.
Your mileage may vary, depending on the authentication style you use. I am using the least recommended User Credentials method.

Related

CustomPropertyMatchInformation does not match existing terms SharePoint Online Term Store CSOM

I am new to SharePoint CSOM. I have a Azure function app that updates a term set in SharePoint Online term store. The function app uses a local custom property defined on the terms to find them. I use the below code to search for the terms
protected async Task<TermCollection> GetTermsByCustomProperty(TermSet termSet, string propertyName, string propertyValue)
{
var matchedTerms = termSet.GetTermsWithCustomProperty(new CustomPropertyMatchInformation(termSet.Context)
{
CustomPropertyName = propertyName,
CustomPropertyValue = propertyValue,
TrimUnavailable = false
});
termSet.Context.Load(matchedTerms);
await termSet.Context.ExecuteQueryRetryAsync();
return matchedTerms;
}
Sometimes the above code does not return any terms when I know that one or more terms exist with the propertyValue defined on them. This happens at random. Other times it will find the matching terms. I am not sure what I can try to find the cause of the issue. Any help will be highly appreciated.
Thanks in advance.

Bringing Active Directory Users using JNDI in multiple threads

I have designed an application which brings the users from the active directory to an MySQL database, and shows them on GUI. It also brings the groups of which a user is a member of.
So, my program works this way:
for(String domain : allConfiguredADomains) {
LdapContext domainCtx = getDomainCtx(domain);
// Bring all users from this domain and store them in DB
getAllUsersForDomain(domain, domainCtx);
// Bring all the groups for every user
getAllGroupsForUsersInTheDomain(domain, domainCtx)
}
void getAllUsersForDomain(String domain, LdapContext domainCtx) {
String filter = "(objectClass=User)"
NamingEnumeration<SearchResult> result = domainCtx.search(domain, filter, ..);
while(result.hasMoreElements()) {
SearchResult searchResult = (SearchResult) result.nextElement();
// Process and store in database
storeUserInDatabase(searchResult);
}
}
void getAllGroupsForUsersInTheDomain(String domain, LdapContext domainCtx) {
List<String> userDistinguishedNames = getAllUsersFromDatabase("distinguishedName");
for(String userDn : userDistinguishedNames) {
String filter = "(&(objectClass=Group)(distinguishedName=" + userDn + "))";
NamingEnumeration<SearchResult> result = domainCtx.search(domain, filter, ..);
List<String> allGroupsOfUser = new List<String>();
while(result.hasMoreElements()) {
SearchResult searchResult = (SearchResult) result.nextElement();
String groupDistinguishedName = searchResult.getAttributes().get("distinguishedName").get();
allGroupsOfUser.add(groupDistinguishedName);
}
// Store them in database
storeAllGroupsOfUserInDatabase(userDn, allGroupsOfUser);
}
}
This application, however, takes lot of time, when there are too many users in the active directory. So, I decided to implement parallelism (using Threading). I divided this using search filter on distinguishedName of a user.
String filter = "(&(objectClass=User)(distinguishedName=a*"))";
and so on.. in each thread while fetching users.
I got better performance, but still not so good. Can someone suggest
a better way ?
Also, I don't have an idea how can I introduce
parallelism while fetching groups ?
If someone has any suggestions to do this better with powershell or C#, please suggest, I am open to technology.
Please note: reading user attribute memberOf does not provide all groups, hence I am fetching groups separately.
I'm not an Active Directory expert - just wanted to share some thoughts.
Threading by alphabet letter allows a maximum of 26 threads. Have you considered creating search threads by some other attributes, group membership etc? This might let you create more threads.
Review the Active Directory docs to see whether there is a way to improve search performance (for example, with a database we could create an index).

Paging in MS Graph API

Graph API Paging explains that the response would contain a field #odata.nextLink which would contain a skiptoken pointing to the next page of contents.
When I test the API, I'm getting a fully-qualified MS Graph URL which contains the skiptoken as a query param. E.g. Below is the value I got for the field #odata.nextLink in the response JSON.
https://graph.microsoft.com/v1.0/users?$top=25&$skiptoken=X%27445370740200001E3A757365723134406F33363561702E6F6E6D6963726F736F66742E636F6D29557365725F31363064343831382D343162382D343961372D383063642D653136636561303437343437001E3A7573657235407368616C696E692D746573742E31626F74322E696E666F29557365725F62666639356437612D333764632D343266652D386335632D373639616534303233396166B900000000000000000000%27
Is it safe to assume we'll always get the full URL and not just the skiptoken? Because if it's true, it helps avoid parsing the skiptoken and then concatenating it to the existing URL to form the full URL ourselves.
EDIT - Compared to MS Graph API, response obtained from Azure AD Graph API differs in that the JSON field #odata.nextLink contains only the skipToken and not the fully-qualified URL.
if you would like to have all users in single list, you can achieve that using the code that follows:
public static async Task<IEnumerable<User>> GetUsersAsync()
{
var graphClient = GetAuthenticatedClient();
List<User> allUsers = new List<User>();
var users = await graphClient.Users.Request().Top(998)
.Select("displayName,mail,givenName,surname,id")
.GetAsync();
while (users.Count > 0)
{
allUsers.AddRange(users);
if (users.NextPageRequest != null)
{
users = await users.NextPageRequest
.GetAsync();
}
else
{
break;
}
}
return allUsers;
}
I am using graph client library
Yes. In Microsoft Graph you can assume that you'll always get the fully qualified URL for the #odata.nextLink. You can simply use the next link to get the next page of results, and clients should treat the nextLink as opaque (which is described in both OData v4 and in the Microsoft REST API guidelines here: https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#98-pagination.
This is different from AAD Graph API (which is not OData v4), which doesn't return the fully qualified next link, and means you need to do some more complicated manipulations to get the next page of results.
Hence Microsoft Graph should make this simpler for you.
Hope this helps,
The above code did not work for me without adding a call to 'CurrentPage' on the last line.
Sample taken from here.
var driveItems = new List<DriveItem>();
var driveItemsPage = await graphClient.Me.Drive.Root.Children.Request().GetAsync();
driveItems.AddRange(driveItemsPage.CurrentPage);
while (driveItemsPage.NextPageRequest != null)
{
driveItemsPage = await driveItemsPage.NextPageRequest.GetAsync();
driveItems.AddRange(driveItemsPage.CurrentPage);
}
I followed Tracy's answer and I was able to fetch all the messages at one go.
public List<Message> GetMessages()
{
var messages = new List<Message>();
var pages = Client.Users[_email]
.Messages
.Request(QueryOptions)
// Fetch the emails with attachments directly instead of downloading them later.
.Expand("attachments")
.GetAsync()
.Result;
messages.AddRange(pages.CurrentPage);
while (pages.NextPageRequest != null)
{
pages = pages.NextPageRequest.GetAsync().Result;
messages.AddRange(pages.CurrentPage);
}
return messages;
}

Defalut XmlSiteMapProvider implementation cannot use SiteMap.FindSiteMapNode?

I just upgrade MvcSiteMapProvider from v3 to v4.6.3.
I see the upgrade note indicate:
In general, any reference to System.Web.SiteMap.Provider will need to be updated to MvcSiteMapProvider.SiteMaps.Current
I am trying to get the sitemap node by using:
SiteMaps.Current.FindSiteMapNode(rawUrl)
But it always return null
I looked into the code. In the sitemap it's actually calling the function:
protected virtual ISiteMapNode FindSiteMapNodeFromUrlMatch(IUrlKey urlToMatch)
{
if (this.urlTable.ContainsKey(urlToMatch))
{
return this.urlTable[urlToMatch];
}
return null;
}
It's trying to find a match in the urlTable.
I am using Default implementation of XmlSiteMapProvider .
It define var url = node.GetAttributeValue("url");
siteMapNode.Url = url;
siteMapNode.UrlResolver = node.GetAttributeValue("urlResolver");
So if I did not define url or urlResolver attribute in the .sitemap file. These variables a set to empty string, when generate the node.
And when this nodes are passed to AddNode function in SiteMap.
When adding the node
bool isMvcUrl = string.IsNullOrEmpty(node.UnresolvedUrl) && this.UsesDefaultUrlResolver(node);
this code will check if there is url or urlResolver
// Only store URLs if they are clickable and are configured using the Url
// property or provided by a custom URL resolver.
if (!isMvcUrl && node.Clickable)
{
url = this.siteMapChildStateFactory.CreateUrlKey(node);
// Check for duplicates (including matching or empty host names).
if (this.urlTable
.Where(k => string.Equals(k.Key.RootRelativeUrl, url.RootRelativeUrl, StringComparison.OrdinalIgnoreCase))
.Where(k => string.IsNullOrEmpty(k.Key.HostName) || string.IsNullOrEmpty(url.HostName) || string.Equals(k.Key.HostName, url.HostName, StringComparison.OrdinalIgnoreCase))
.Count() > 0)
{
var absoluteUrl = this.urlPath.ResolveUrl(node.UnresolvedUrl, string.IsNullOrEmpty(node.Protocol) ? Uri.UriSchemeHttp : node.Protocol, node.HostName);
throw new InvalidOperationException(string.Format(Resources.Messages.MultipleNodesWithIdenticalUrl, absoluteUrl));
}
}
// Add the URL
if (url != null)
{
this.urlTable[url] = node;
}
Finally no url is add to the urlTable, which result in FindSiteMapNode cannot find anything.
I am not sure if there needs to be specific configuration. Or should I implement custom XmlSiteMapProvider just add the url.
ISiteMapNodeProvider instances cannot use the FindSiteMapNode function for 2 reasons. The first you have already discovered is that finding by URL can only be done if you set the url attribute explicitly in the node configuration. The second reason is that the SiteMapBuilder doesn't add any of the nodes to the SiteMap until all of the ISiteMapNodeProvider instances have completed running, so it would be moot to add the URL to the URL table anyway.
It might help if you explain what you are trying to accomplish.
The ISiteMapNodeProvider classes have complete control over the data that is added to the SiteMapNode instances and they also have access to their parent SiteMapNode instance. This is generally all that is needed in order to populate the data. Looking up another SiteMapNode from the SiteMap object while populating the data is not supported. But as long as the node you are interested in is populated in the same ISiteMapNodeProvider instance, you can just get a reference to it later by storing it in a variable.
Update
Okay, I reread your question and your comment and it now just seems like you are looking in the wrong place. MvcSiteMapProvider v4 is no longer based on Microsoft's SiteMap provider model, so using XmlSiteMapProvider doesn't make sense, as it would sidestep the entire implementation. The only case where this might make sense is if you have a hybrid ASP.NET and ASP.NET MVC application that you want to keep a consitant menu structure between. See Upgrading from v3 to v4.
There are 2 stages to working with the data. The first stage (the ISiteMapBuilder and ISiteMapNodeProvider) loads the data from various sources (XML, .NET attributes, DynamicNodeProviders, and custom implementations of ISiteMapNodeProvider) and adds it to an object graph that starts at the SiteMap object. Much like Microsoft's model, this data is stored in a shared cache and only loaded when the cache expires. This is the stage you have been focusing on and it definitely doesn't make sense to lookup nodes here.
The second stage is when an individual request is made to access the data. This is where looking up data based on a URL might make sense, but there is already a built-in CurrentNode property that finds the node matching the current URL (or more likely the current route since we are dealing with MVC) which in most cases is the best approach to finding a node. Each node has a ParentNode and ChildNodes properties that can be used to walk up or down the tree from there.
In this second stage, you can access the SiteMap data at any point after the Application_Start event such as within a controller action, in one of the built in HTML helpers, an HTML helper template in the /Views/Shared/DisplayTemplates/ directory, or a custom HTML helper. This is the point in the application life cycle which you might call the lines SiteMaps.Current.FindSiteMapNode(rawUrl) or (more likely) SiteMaps.Current.CurrentNode to get an instance of the node so you can inspect its Attributes property (the custom attributes).
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
var currentNode = MvcSiteMapProvider.SiteMaps.Current.CurrentNode;
string permission = currentNode.Attributes.ContainsKey("permission") ? currentNode.Attributes["permission"].ToString() : string.Empty;
string programs = currentNode.Attributes.ContainsKey("programs") ? currentNode.Attributes["programs"].ToString() : string.Empty;
string agencies = currentNode.Attributes.ContainsKey("agencies") ? currentNode.Attributes["agencies"].ToString() : string.Empty;
// Do something with the custom attributes of the About page here
return View();
}
The most common usage of custom attributes is to use them from within a custom HTML helper template. Here is a custom version of the /Views/Shared/DisplayTemplates/SiteMapNodeModel.cshtml template that displays the custom attributes. Note that this template is called recursively by the Menu, SiteMapPath, and SiteMap HTML helpers. Have a look at this answer for more help if HTML helper customization is what you intend to do.
#model MvcSiteMapProvider.Web.Html.Models.SiteMapNodeModel
#using System.Web.Mvc.Html
#using MvcSiteMapProvider.Web.Html.Models
#if (Model.IsCurrentNode && Model.SourceMetadata["HtmlHelper"].ToString() != "MvcSiteMapProvider.Web.Html.MenuHelper") {
<text>#Model.Title</text>
} else if (Model.IsClickable) {
if (string.IsNullOrEmpty(Model.Description))
{
#Model.Title
}
else
{
#Model.Title
}
} else {
<text>#Model.Title</text>
}
#string permission = Model.Attributes.ContainsKey("permission") ? Model.Attributes["permission"].ToString() : string.Empty
#string programs = Model.Attributes.ContainsKey("programs") ? Model.Attributes["programs"].ToString() : string.Empty
#string agencies = Model.Attributes.ContainsKey("agencies") ? Model.Attributes["agencies"].ToString() : string.Empty
<div>#permission</div>
<div>#programs</div>
<div>#agencies</div>

How to discover all Entity Types? One of each?

I need to write a service that connects to CRM, and returns with a list of all of the entity available on the server (custom or otherwise).
How can I do this? To be clear, I am not looking to return all data for all entities. Just a list of every type, regardless of whether any actually exist.
You need to use RetrieveAllEntitiesRequest
RetrieveAllEntitiesRequest request = new RetrieveAllEntitiesRequest()
{
EntityFilters = EntityFilters.Entity,
RetrieveAsIfPublished = true
};
// service is the IOrganizationService
RetrieveAllEntitiesResponse response = (RetrieveAllEntitiesResponse)service.Execute(request);
foreach (EntityMetadata currentEntity in response.EntityMetadata)
{
string logicalName = currentEntity.LogicalName;
// your logic here
}
note that you will get also system or hidden entities, like wizardpage or recordcountsnapshot
You will probably find these sections of the MSDN useful:
Customize Entity Metadata (lookout for the samples linked on that page).
Retrieve and Detect Changes to Metadata.

Resources