Google Merchant Product Feed not Accepting my Feed - c#

I am working with the GData api to import products to my merchant feed. The code is as follows:
List<ProductEntry> newEntries = new List<ProductEntry>();
foreach (Product prod in ent.Products.Where(i => !i.IsDeleted && i.IsDisplayed && i.Price > 0))
{
newEntries.Add(prod.GetNewEntry());
}
string GoogleUsername = "nope#gmail.com";
string GooglePassword = "*****";
ContentForShoppingService service = new ContentForShoppingService("MY STORE");
service.setUserCredentials(GoogleUsername, GooglePassword);
service.AccountId = "*******";
service.ShowWarnings = true;
ProductFeed pf = service.InsertProducts(newEntries);
r.Write(pf.Entries.Count.ToString());
This code is returning to me 1 entry, rather than the 400+ it should, and that 1 entry is empty with no error or warning info. Nothing shows on my merchant center dash. Any ideas on what might be occurring here?
OR - how can I get more details on what is going on?

First of all create a service account on Google Developers
Console if you don't have one already.
NuGet package Google.Apis.ShoppingContent.v2
after you do whats mentioned on the link you'll get
a ClientId
a ClientSecret
those we will be using to authenticate.
var credentials = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets() {
ClientId = "yourclientid",
ClientSecret = "yourclientsecret" },
new string[] { ShoppingContentService.Scope.Content },
"user",
CancellationToken.None).Result;
//make this a global variable or just make sure you pass it to InsertProductBatch or any function that needs to use it
service = new ShoppingContentService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = "Your-app-name"
});
Now for the insert part:
private async Task<List<Product>> InsertProductBatch(IEnumerable<Product> products, ulong merchantaccountid)
{
// Create a batch request.
BatchRequest request = new BatchRequest(service);
List<Product> responseproducts = new List<Product>();
foreach (var p in products)
{
ProductsResource.InsertRequest insertRequest =
service.Products.Insert(p, merchantaccountid);
request.Queue<Product>(
insertRequest,
(content, error, index, message) =>
{
responseproducts.Add(content);
//ResponseProducts.Add(content);
if (content != null)
{
//product inserted successfully
}
AppendLine(String.Format("Product inserted with id {0}", ((Product)content).Id));
if (error != null)
{
//there is an error you can access the error message though error.Message
}
});
}
await request.ExecuteAsync(CancellationToken.None);
return responseproducts;
}
And thats all to it.

Related

Dynamics Crm Bulk Update records in a transaction

Requirement
I have the requirement where I want to update few fields on account
and create contact for it reading data from an API.
The number of records to be updated is around 100,000 so I want to
use either ExecuteTransactionRequest or ExecuteMultipleRequest so that I can execute all in batches.
Since I want the contact record to be created for the account updated I used the ExecuteTransactionRequest.
Problem -
The problem is batch size.
I have added the condition if request batch size count equals 500 then execute all the requests. However my batch can include
Update request for account and
Create request for contact
So it may happen that the batch may not be exact 500 and it would skip the execute request. How can I do this and make sure that contact record is created for each Updated Account correctly.
Any help would be appreciated. Thanks in Advance
Below is my code --
var requests = new ExecuteTransactionRequest
{
Requests = new OrganizationRequestCollection(),
ReturnResponses = returnResponses
};
foreach (var customer in customerList)
{
string custNo = customer.GetAttributeValue<string>("customernumber");
// Gets customer details from another api
var custInfo = await CustomerService.Get(custNo);
// Update the values on customer table
Entity cust = new Entity("account");
cust.Id = customer.Id;
cust["companytypecode"] = custInfo.EntityTypeCode;
cust["companytypedescription"] = custInfo .EntityTypeDescription;
var roles = custInfo.Roles.Where(c => c.RoleStatus == "ACTIVE").ToArray();
//create contact for each account
foreach(var role in roles)
{
Entity contact = new Entity("contact");
contact["FirstName"] = role.RolePerson?.FirstName;
contact["MiddleName"] = role.RolePerson?.MiddleNames;
contact["LastName"] = role.RolePerson?.LastName;
contact["AccountId"] = new EntityReference("account", customer.Id);
CreateRequest createRequest = new CreateRequest { Target = contact };
requests.Requests.Add(createRequest);
}
UpdateRequest updateRequest = new UpdateRequest { Target = cust };
requests.Requests.Add(updateRequest);
if (requests.Requests.Count == 500) // Problem is the batch size will not execute per account since it also has create request of contact. How can i make sure that each request is executed correctly
{
service.Execute(requests);
requests.Requests.Clear();
}
}
// For the last remaining accounts
if (requests.Requests.Count > 0)
{
service.Execute(requests);
}
Thank you for helping out. I resolved this with below solution. Happy to be corrected.
EntityCollection requestsCollection = new EntityCollection();
foreach (var customer in customerList)
{
string custNo= customer.GetAttributeValue<string>("customernumber");
var custInfo = await businessService.Get(custNo);
Entity cust = new Entity("account");
cust.Id = customer.Id;
cust["companytypecode"] = custInfo.EntityTypeCode;
cust["companytypedescription"] = custInfo .EntityTypeDescription;
requestsCollection.Entities.Add(cust);
var roles = custInfo.Roles.Where(c => c.RoleStatus == "ACTIVE").ToArray();
foreach(var role in roles)
{
Entity contact = new Entity("contact");
contact["FirstName"] = role.RolePerson?.FirstName;
contact["MiddleName"] = role.RolePerson?.MiddleNames;
contact["LastName"] = role.RolePerson?.LastName;
contact["AccountId"] = new EntityReference("account", customer.Id);
requests.Entities.Add(contact);
}
if (requestsCollection.Entities.Count > 500)
{
ExecuteBulkUpdate(requestsCollection);
requestsCollection = new EntityCollection();
}
}
private void ExecuteBulkUpdate(EntityCollection requestsCollection)
{
var requests = new ExecuteTransactionRequest
{
Requests = new OrganizationRequestCollection(),
ReturnResponses = returnResponses
};
foreach (var request in requestsCollection.Entities)
{
if (request.Id != Guid.Empty)
{
UpdateRequest updateRequest = new UpdateRequest { Target = request };
requests.Requests.Add(updateRequest);
}
else
{
CreateRequest createRequest = new CreateRequest { Target = request };
requests.Requests.Add(createRequest);
}
}
try
{
var responseForCreateRecords = (ExecuteTransactionResponse)service.Execute(requests);
int i = 0;
// Display the results returned in the responses.
foreach (var responseItem in responseForCreateRecords.Responses)
{
if (responseItem != null)
log.LogInformation(responseItem.Results["id"].ToString());
i++;
}
requests.Requests.Clear();
}
catch (FaultException<OrganizationServiceFault> ex)
{
log.LogInformation("Request failed for the {0} and the reason being: {1}",
((ExecuteTransactionFault)(ex.Detail)).FaultedRequestIndex + 1, ex.Detail.Message);
throw;
}
}

gmail api returning null for a specific history id. History id received from Cloud Pub/Sub push subscription

From Cloud pub/sub push service i got a history id. Using that history id i am trying to read the recent mail's but It returns null.
I have configured cloud pub/sub push subscription and add a watch to "Unread" label.
Scenario 1:
I have received a push notification. From that push notification i have taken history id to get the recent messages. it's returning me null value.
Scenario 2:
I have logged into that configured mail id and then the message loaded in inbox. After that if i try to read i am getting the history list.
static string[] Scopes = { GmailService.Scope.MailGoogleCom };
static void Main(string[] args)
{
string UserId = "####.gmail.com";
UserCredential credential;
using (var stream =
new FileStream("client_secret_#####.json", FileMode.Open, FileAccess.Read))
{
string credPath = "token.json";
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
UserId,
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
List<History> result = new List<History>();
UsersResource.HistoryResource.ListRequest request = service.Users.History.List(UserId);
//history id received from cloud pub/sub push subscription.
request.StartHistoryId = Convert.ToUInt64("269871");
do
{
try
{
ListHistoryResponse response = request.Execute();
if (response.History != null)
{
result.AddRange(response.History);
}
request.PageToken = response.NextPageToken;
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
} while (!String.IsNullOrEmpty(request.PageToken));
foreach (var vHistory in result)
{
foreach (var vMsg in vHistory.Messages)
{
string date = string.Empty;
string from = string.Empty;
string subject = string.Empty;
string body = string.Empty;
var emailInfoRequest = service.Users.Messages.Get(UserId, vMsg.Id);
var emailInfoResponse = emailInfoRequest.Execute();
if(emailInfoResponse!= null)
{
foreach (var mParts in emailInfoResponse.Payload.Headers)
{
if (mParts.Name == "Date")
{
date = mParts.Value;
}
else if (mParts.Name == "From")
{
from = mParts.Value;
}
else if (mParts.Name == "Subject")
{
subject = mParts.Value;
}
if (date != "" && from != "")
{
if (emailInfoResponse.Payload.Parts != null)
{
foreach (MessagePart p in emailInfoResponse.Payload.Parts)
{
if (p.MimeType == "text/html")
{
byte[] data = FromBase64ForUrlString(p.Body.Data);
body = Encoding.UTF8.GetString(data);
}
else if(p.Filename!=null && p.Filename.Length>0)
{
string attId = p.Body.AttachmentId;
string outputDir = #"D:\#####\";
MessagePartBody attachPart = service.Users.Messages.Attachments.Get(UserId, vMsg.Id, attId).Execute();
String attachData = attachPart.Data.Replace('-', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
File.WriteAllBytes(Path.Combine(outputDir, p.Filename), data);
}
}
}
else
{
byte[] data = FromBase64ForUrlString(emailInfoResponse.Payload.Body.Data);
body = Encoding.UTF8.GetString(data);
}
}
}
}
}
}
public static byte[] FromBase64ForUrlString(string base64ForUrlInput)
{
int padChars = (base64ForUrlInput.Length % 4) == 0 ? 0 : (4 - (base64ForUrlInput.Length % 4));
StringBuilder result = new StringBuilder(base64ForUrlInput, base64ForUrlInput.Length + padChars);
result.Append(String.Empty.PadRight(padChars, '='));
result.Replace('-', '+');
result.Replace('_', '/');
return Convert.FromBase64String(result.ToString());
}
}
Please let me know how to read the full message using history id. when i receive push notification.
The Gmail Api documentation states that the Users.history:list method requires startHistoryId as a parameter to be executed, rather than giving you this parameter as a response. This is confusing, since it states as an optional parameter, but it is also specifies that it is required. The documentation also specifies:
The supplied startHistoryId should be obtained from the historyId of a
message, thread, or previous list response.
I suggest you to test the methods you use first with "Try this API" and OAuth 2.0 Playground. This makes it easier to understand which parameters you need to supply and which responses you can obtain from each method.
I have dealt with this. The point is that the history_id you are receiving is to be interpreted like the "latest moment when something happened". So, in order to make this work, you MUST use a history_id coming from a previous execution (that, don't forget, in GMail Push API means that you have to implement the initial full sync, or at the very least you should be executing a second run of your partial sync), which will return the events that span from the previous history_id to the one you just received.
I have just published an article on medium, since the detail of the history_id, in my opinion, can be a little sneaky. Article is here.

reading messages from fb

I successfully login to facebook but while trying to read the mailbox I got the following error: {"(OAuthException - #298) (#298) Requires extended permission: read_mailbox"}
If I add that scope in the URL;
var destinationUrl =
String.Format(
"https://www.facebook.com/dialog/oauth?client_id={0}&scope={1}&display=popup&redirect_uri=http://www.facebook.com/connect/login_success.html&response_type=token",
AppID, //client_id
"user_posts" //scope
);
and try to get mails:
private void FaceBookScrapper()
{
var client = new FacebookClient(_fbToken);
var input = new List<FbMesseage>();
if (client != null)
{
dynamic result = client.Get("me/inbox", null);
foreach (var item in result.inbox.data)
{
if (item.unread > 0 || item.unseen > 0)
{
foreach (var message in item.comments.data)
{
input.Add(new FbMesseage
{
Id = message.id,
FromName = message.from.name,
FromId = message.from.id,
Text = message.message,
CreatedDate = message.created_time
});
}
FbMesseageCollectionViewModelIns.LoadData(input);
}
}
}
}
}
}
That permission doesn't exist any more - it has been removed, together with the /user/inbox endpoint.
https://developers.facebook.com/docs/apps/changelog#v2_4_deprecations mentions this, and https://developers.facebook.com/docs/graph-api/reference/v2.5/user/inbox as well.
Under the latter URL, it says it right on the very top of the page:
This document refers to a feature that was removed after Graph API v2.4.
There is no way any more to access a user's inbox via API.

ASP.Net MVC5, Google OAuth 2.0 and Youtube API

I need some help regarding mvc 5 using the google login provider and getting some youtube data. right now i think i get things a little mixed up. i'm not new to mvc but to version 5's owin middleware features. well, and not experienced in implementing oauth 2.0.
What i want:
Login to my MVC5 Application via Google.
Read some Youtube information from the logged in user.
What i have done so far:
Followed this Google OAuth 2.0 tutorial: Web applications (ASP.NET MVC).
Installed Google.Apis.Auth.MVC via NuGet.
Implemented AppFlowMetadata and AuthCallbackController as described.
Configured the redirect uri to "/AuthCallback/IndexAsync" as described.
Implemented a YoutubeController with the following action just to dump out some data:
public async Task<ActionResult> IndexAsync()
{
var result =
await new AuthorizationCodeMvcApp(this, new AppFlowMetadata())
.AuthorizeAsync(cancellationToken);
if (result.Credential == null)
{
return new RedirectResult(result.RedirectUri);
}
else
{
var service = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "MyYoutubeApplication"
});
var playlists = service.Playlists.List("contentDetails, snippet");
playlists.Mine = true;
var list = await playlists.ExecuteAsync();
var json = new JavaScriptSerializer().Serialize(list);
ViewBag.Message = json;
return View();
}
}
So what this does, when trying to access /Youtube/IndexAsync is redirecting me to google, asking for my credentials.
when entered, i'm asked if i'm ok with the permission asked by the application. after confirming, i get redirected to my page, showing my /Youtube/IndexAsync page with the requested data. so far so good, but that's not quite what i want.
what (i think) i have done here is that i completely bypassed the asp.net identity system. the user is not logged in to my application let alone registered.
i want the user to log in with google, register in my application and provide access to his youtube data. then, when on a specific page, retrieve data from the user's youtube account.
What i also have tried:
Following this ASP.Net MVC5 Tutorial
This tutorial does not mention the NuGet package "Google.Apis.Auth.MVC" and talks something about a magic "/signin-google" redirect uri".
This also works, but breaks the solution above, complaining about a wrong redirect uri.
When using this approach, it seems not right to me call AuthorizeAsync in YoutubeController again, since i should already be authorized.
So i'm looking for some light in the dark, telling me what i'm mixing all together :) I hope the question is not as confused as i am right now.
I managed to do this using GooglePlus, haven't tried Google. Here's what I did:
Install the nugets:
> Install-Package Owin.Security.Providers
> Install-Package Google.Apis.Youtube.v3
Add this to Startup.auth.cs:
var g = new GooglePlusAuthenticationOptions();
g.ClientId = Constants.GoogleClientId;
g.ClientSecret = Constants.GoogleClientSecret;
g.RequestOfflineAccess = true; // for refresh token
g.Provider = new GooglePlusAuthenticationProvider
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new Claim(Constants.GoogleAccessToken, context.AccessToken));
if (!String.IsNullOrEmpty(context.RefreshToken))
{
context.Identity.AddClaim(new Claim(Constants.GoogleRefreshToken, context.RefreshToken));
}
return Task.FromResult<object>(null);
}
};
g.Scope.Add(Google.Apis.YouTube.v3.YouTubeService.Scope.YoutubeReadonly);
g.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
app.UseGooglePlusAuthentication(g);
The above code does two things:
Enable authentication via. Google+
Requests for the access token and the refresh token. The tokens are then added as a claim in the GooglePlus middleware.
Create a method that will store the claims containing the token to the database. I have this in the AccountController.cs file
private async Task StoreGooglePlusAuthToken(ApplicationUser user)
{
var claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (claimsIdentity != null)
{
// Retrieve the existing claims for the user and add the google plus access token
var currentClaims = await UserManager.GetClaimsAsync(user.Id);
var ci = claimsIdentity.FindAll(Constants.GoogleAccessToken);
if (ci != null && ci.Count() != 0)
{
var accessToken = ci.First();
if (currentClaims.Count() <= 0)
{
await UserManager.AddClaimAsync(user.Id, accessToken);
}
}
ci = claimsIdentity.FindAll(Constants.GoogleRefreshToken);
if (ci != null && ci.Count() != 0)
{
var refreshToken = ci.First();
if (currentClaims.Count() <= 1)
{
await UserManager.AddClaimAsync(user.Id, refreshToken);
}
}
}
You'll need to call it in 2 places in the AccountController.cs: Once in ExternalLoginCallback:
case SignInStatus.Success:
var currentUser = await UserManager.FindAsync(loginInfo.Login);
if (currentUser != null)
{
await StoreGooglePlusAuthToken(currentUser);
}
return RedirectToLocal(returnUrl);
and once in ExternalLoginConfirmation:
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await StoreGooglePlusAuthToken(user);
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
Now that we've got the users access token and refresh token we can use this to authenticate the user.
I tried a simple search I saw in the examples and it worked:
private async Task<Models.YouTubeViewModel> Search(string searchTerm)
{
var user = (ClaimsPrincipal)Thread.CurrentPrincipal;
var at = user.Claims.FirstOrDefault(x => x.Type == Constants.GoogleAccessToken);
var rt = user.Claims.FirstOrDefault(x => x.Type == Constants.GoogleRefreshToken);
if (at == null || rt == null)
throw new HttpUnhandledException("Access / Refresh Token missing");
TokenResponse token = new TokenResponse
{
AccessToken = at.Value,
RefreshToken = rt.Value
};
var cred = new UserCredential(new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer()
{
ClientSecrets = new ClientSecrets()
{
ClientId = Constants.GoogleClientId,
ClientSecret = Constants.GoogleClientSecret
}
}
),
User.Identity.GetApplicationUser().UserName,
token
);
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
ApplicationName = this.GetType().ToString(),
HttpClientInitializer = cred,
});
var searchListRequest = youtubeService.Search.List("snippet");
searchListRequest.Q = searchTerm;
searchListRequest.MaxResults = 50;
// Call the search.list method to retrieve results matching the specified query term.
var searchListResponse = await searchListRequest.ExecuteAsync();
Models.YouTubeViewModel vm = new Models.YouTubeViewModel(searchTerm);
foreach (var searchResult in searchListResponse.Items)
{
switch (searchResult.Id.Kind)
{
case "youtube#video":
vm.Videos.Add(new Models.Result(searchResult.Snippet.Title, searchResult.Id.VideoId));
break;
case "youtube#channel":
vm.Channels.Add(new Models.Result(searchResult.Snippet.Title, searchResult.Id.ChannelId));
break;
case "youtube#playlist":
vm.Playlists.Add(new Models.Result(searchResult.Snippet.Title, searchResult.Id.PlaylistId));
break;
}
}
return vm;
}
Model Classes
public class Result
{
public string Title { get; set; }
public string Id { get; set; }
public Result() { }
public Result(string title, string id)
{
this.Title = title;
this.Id = id;
}
}
public class YouTubeViewModel
{
public string SearchTerm { get; set; }
public List<Result> Videos { get; set; }
public List<Result> Playlists { get; set; }
public List<Result> Channels { get; set; }
public YouTubeViewModel()
{
Videos = new List<Result>();
Playlists = new List<Result>();
Channels = new List<Result>();
}
public YouTubeViewModel(string searchTerm)
:this()
{
SearchTerm = searchTerm;
}
}
Reference: http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs-2013-project-templates.aspx

Google Cloud Datastore request returns Error 400 in my browser

After many trials and errors I managed to compile a piece of code which should return Entities' values from Google Datastore (it's SQL-like db). Code I used:
static async void Run()
{
UserCredential credential;
using (var stream = new FileStream(#"c:/fakepath/client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { DatastoreService.Scope.Datastore }, "user", CancellationToken.None);
}
// Create the service.
var service = new DatastoreService(new BaseClientService.Initializer
{
ApplicationName = "My Project",
HttpClientInitializer = credential
});
// Run the request.
Console.WriteLine("Executing a list request...");
var request = new LookupRequest();
var key = new Google.Apis.Datastore.v1beta2.Data.Key();
key.Path = new List<KeyPathElement> { new KeyPathElement() { Kind = "book", Name = "title42" } };
request.Keys = new List<Key>() { key };
var lookup = service.Datasets.Lookup(request, "project-name-192"); //yea
var response = lookup.Execute();
// Display the results.
if (response.Found != null)
{
foreach (var x in response.Found)
{
foreach (var y in x.Entity.Properties)
{
Console.WriteLine(y.Key.FirstOrDefault() + " " + y.Value);
}
}
}
}
Error I get:
So, what am I missing? I did just like in example on docs.
You need to go to:
https://console.developers.google.com/project/apps~{your-app-name}/apiui/credential
and use http://localhost:63324/authorize/ as the redirect url.
Remember to change it to your production url when you deploy

Categories

Resources