I have a code that would take an excel sheet and convert it to google sheet.
It is working but only copying the values.
How can I copy the styling and range (merged cells) also to google Sheets?
Is there also a more simple way to just simulate as I am opening the file as is on google sheets?
foreach (var sheet in wb.Worksheets)
{
if (sheet.Index == 0)
{
// First sheet is created by default, so only set range
range = $"{defaultWorksheetName}!A:Y";
}
else
{
// Add a new sheet
AddSheet(sheetService, spreadhsheet.SpreadsheetId, sheet.Name);
range = $"{sheet.Name}!A:Y";
}
// Get number of rows and columns
int rows = sheet.Cells.MaxDataRow;
int cols = sheet.Cells.MaxDataColumn;
IList<IList<Object>> list = new List<IList<Object>>() { };
// Loop through rows
for (int i = 0; i < rows; i++)
{
List<object> lists = new List<object>();
// Loop through each column in selected row
for (int j = 0; j < cols; j++)
{
lists.Add(sheet.Cells[i, j].Value);
}
list.Add(lists);
}
// Define range
ValueRange VRange = new ValueRange();
VRange.Range = range;
// Set values
VRange.Values = list;
// Create request
SpreadsheetsResource.ValuesResource.UpdateRequest upd = sheetService.Spreadsheets.Values.Update(VRange, spreadhsheet.SpreadsheetId, range);
upd.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED;
// Execute request
UpdateValuesResponse response = upd.Execute();
// Get response
string responseString = JsonConvert.SerializeObject(response);
}
It appears that you are looping through the Excel rows to create an object to build the Google Sheet manually. Looking at the Microsoft documentation, you probably could also read the formatting for these cells and use the Sheets API to apply it, however, the process seems like it could be too time-consuming and error-prone.
My recommendation would be to just have Google do the work for you. The Drive API is capable of uploading Excel files and converting them to Google Sheets by setting the MimeType to application/vnd.google-apps.spreadsheet. There are some limitations, but generally it does a good job of keeping the cell formatting the same.
Google's documentation doesn't include .NET samples and I'm no expert in it, but you can check out their .NET API which does have the Files.create methods, or you could use their REST API instead.
Sources:
Importing files to Google Docs types
Drive API .NET documentation
Drive REST documentation
I found that it is much better and easier to use the Drive API instead of the Sheets API and upload the file as is, then get the link and it will open it in Google sheets.
public static string UploadFile(Stream file, string fileName, string fileMime, string fileDescription)
{
DriveService service = GetService();
var driveFile = new Google.Apis.Drive.v3.Data.File();
driveFile.Name = fileName;
driveFile.Description = fileDescription;
driveFile.MimeType = "application/vnd.google-apps.spreadsheet";
var request = service.Files.Create(driveFile, file, driveFile.MimeType);
request.Fields = "id, webViewLink";
var response = request.Upload();
if (response.Status != Google.Apis.Upload.UploadStatus.Completed)
throw response.Exception;
return request.ResponseBody.WebViewLink;
}
private static DriveService GetService()
{
string[] Scopes = { SheetsService.Scope.Drive };
string ApplicationName = "Excel to Google Sheet";
UserCredential credential = null;
using (var stream =
new FileStream("credentials.json", FileMode.Open, FileAccess.Read))
{
string credPath = "token.json";
var thread = new Thread(() =>
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.FromStream(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result)
{ IsBackground = false };
thread.Start();
if (!thread.Join(20000))
{
throw new Exception("Timeout exception..!!!");
}
else
{
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
return service;
}
}
}
Related
I'm trying to get realtime data from Google Analytics Real Time Reporting API (Google.Apis.Analytics.v3), the following code works fine and gets the active users.
What I can't wrap my head around is how to get data from dimensions?
Fx. this dimension: rt:deviceCategory, it doesn't have a metric according to this: https://developers.google.com/analytics/devguides/reporting/realtime/dimsmets/
If anyone could point me in the right direction on this it would be greatly appreciated.
var credential = GetCredential().Result;
using (var svc = new AnalyticsService(
new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "Google Analytics API Console"
})
)
{
GetRequest request;
RealtimeData response;
request = svc.Data.Realtime.Get("ga:XXXXX", "rt:activeUsers");
response = request.Execute();
foreach (var row in response.Rows)
{
foreach (string col in row)
{
Console.Write(col + " "); // writes the value of the column
}
Console.Write("\r\n");
}
I found the answer here: https://github.com/LindaLawton/Google-Dotnet-Samples/tree/Genreated-samples1.0/Google%20Analytics%20API/v3
The RealtimeSample.cs can add dimensions to the Real Time API, just have to change line no. 127 to:
if (piShared != null && property.GetValue(optional, null) != null) // TODO Test that we do not add values for items that are null
And capitalize the A of analyticsService in a couple of places. Then use as:
RealtimeSample.RealtimeGetOptionalParms param = new RealtimeSample.RealtimeGetOptionalParms();
param.Dimensions = "rt:deviceCategory";
response = RealtimeSample.Get(svc, "ga:XXXXX", "rt:activeUsers", param);
foreach (var row in response.Rows)
{
foreach (string col in row)
{
Console.Write(col + " "); // writes the value of the column
}
Console.Write("\r\n");
}
I started with the basics and learned how to insert a fixed bit of rows to a Google Sheet from my program now I've been trying to figure out how to append output from a SQL. I have a C# program that calls a stored procedure or it can do a select * from table. Simply trying to insert the results/output of that select into a Google Sheet. Every example I find is dealing with hard coded fixed rows and columns. My select produces less than 2000 lines of output. Appreciate the help!
namespace CS_Gsheet1
{
class Program
{
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/sheets.googleapis.com-dotnet-quickstart.json
//static string[] Scopes = { SheetsService.Scope.SpreadsheetsReadonly };
//static string ApplicationName = "Google Sheets API .NET Quickstart";
static string[] Scopes = { SheetsService.Scope.Spreadsheets };
static string ApplicationName = "Test3";
static void Main(string[] args)
{
var service = AuthorizeGoogleApp();
String spreadsheetId = "sheetIDstring";
// Write to specified sheet
String writeRange = "Sheet1!A1:K";
ValueRange valueRange = new ValueRange { MajorDimension = "ROWS" };
IList<object> dataList = new List<object>();
using(SqlConnection myConnection = new SqlConnection("connectionstring"))
{
myConnection.Open();
using(SqlCommand cmd = new SqlCommand("storedproc-selectsmultiplecolumnsandrows", myConnection))
{
cmd.CommandType = CommandType.StoredProcedure;
using(SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
// dataList.Add(reader.GetString(0));
// dataList.Add(reader.GetValue(0));
IList<IList<Object>> values = new List<IList<Object>>();
values.Add(dataList);
}
}
}
}
// Data is accessible through the DataReader object here.
ValueRange valueDataRange = new ValueRange() { MajorDimension = "ROWS" };
valueDataRange.Values = new List<IList<object>> { dataList };
int c = dataList.Count;
Console.WriteLine("dataList Count {0}", c);
for (int i = 0; i < c; i++)
{
// API to append data to sheet
SpreadsheetsResource.ValuesResource.AppendRequest appendRequest = service.Spreadsheets.Values.Append(valueDataRange, spreadsheetId, writeRange);
appendRequest.ValueInputOption = SpreadsheetsResource.ValuesResource.AppendRequest.ValueInputOptionEnum.RAW;
appendRequest.InsertDataOption = SpreadsheetsResource.ValuesResource.AppendRequest.InsertDataOptionEnum.INSERTROWS;
AppendValuesResponse appendValueResponse = appendRequest.Execute();
}
}
private static SheetsService AuthorizeGoogleApp()
{
UserCredential credential;
using (var stream = new FileStream("client_secret.json", FileMode.Open, FileAccess.ReadWrite))
{
string credPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/sheets.googleapis.com-dotnet-quickstart.json");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
// Create Google Sheets API service.
var service = new SheetsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
return service;
}
}
}
}
I was able to tweak this solution to get the process to work without requesting a quota increase from Google. It does not run very fast, but ok for ~1000 records.
https://github.com/jeffcrowder/GoogleSheetsAPIExample/blob/master/GoogleSheetsAPIExample/Program.cs
I'm just try to fetch my test spreadsheet's(API v4) sheets' data by this way:
var service = new SheetsService(
new BaseClientService.Initializer()
{
HttpClientInitializer = new Credentials().Data,
ApplicationName = Config.AppName
});
var request = service.Spreadsheets.Get(sheet_id);
var response = request.Execute();
var sheets = response.Sheets;
foreach (var sheet in sheets)
{
try
{
Console.WriteLine(sheet.Data.GetType());
}
catch (NullReferenceException err)
{
Console.WriteLine(err.Message);
}
}
It throws NullReferenceException, but if I replace Data by another attribute(e.g. Properties, Merges) it works correctly.
In a nutshell it fails only on Data attribute.
How can I solve this?
According to the documentation, you need to add includeGridData=true to the URL parameters if you want to include all data.
The C# wrapper you are using exposes this property on the SpreadsheetsResource.GetRequest:
var request = service.Spreadsheets.Get(sheet_id);
request.IncludeGridData = true; // <-- This will include the grid data
var response = request.Execute();
I'm trying to retrieve data from google analytics with a local console app. Im able to extract some data without having to log in to google account and only using the API.
The problem is i'm not getting the right values and im not sure how to format the code to extract the correct values. I wan't to retrieve all visitors within a certain time frame, in this case "2012-01-01" - "2014-02-20". The real number of visitors is like 10 times larger when looking in the Google Analytics dashboard. I'm getting a number of 15000 when debugging the code. I'm displaying d.TotalResults in the console wich might be wrong, the variable "d" contains lots of different properties.
This is the code i'm running:
public static void Main(string[] args)
{
var serviceAccountEmail = "MY#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"C:\Users\User\Desktop\key.p12", "notasecret", X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { AnalyticsService.Scope.Analytics }
}.FromCertificate(certificate));
// Create the service.
//Twistandtango
var gas = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "TestGoogleAnalytics",
});
var r = gas.Data.Ga.Get("ga:MYProfileID", "2012-01-01", "2014-02-20", "ga:visitors");
//Specify some addition query parameters
r.Dimensions = "ga:pagePath";
r.Sort = "-ga:visitors";
r.MaxResults = 5;
//Execute and fetch the results of our query
Google.Apis.Analytics.v3.Data.GaData d = r.Execute();
Console.WriteLine(d.TotalResults);
Console.ReadLine();
}
I'm trying to query the results I want with this tool https://ga-dev-tools.appspot.com/explorer/. When I implement this in my code it looks like this:
public static void Main(string[] args)
{
var serviceAccountEmail = "MY#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"C:\Users\User\Desktop\key.p12", "notasecret", X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { AnalyticsService.Scope.Analytics }
}.FromCertificate(certificate));
// Create the service.
//Twistandtango
var gas = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "TestGoogleAnalytics",
});
var r = gas.Data.Ga.Get("ga:MYProfileID", "2012-01-01", "2014-02-20", "ga:visits");
//Specify some addition query parameters
r.Dimensions = "ga:visitCount";
r.Metrics = "ga:visits";
r.Segment = "gaid::-1";
//r.Sort = "-ga:visitors";
r.MaxResults = 10000;
//Execute and fetch the results of our query
Google.Apis.Analytics.v3.Data.GaData d = r.Execute();
Console.WriteLine(d.TotalResults);
Console.ReadLine();
}
}
However after adding the metric (r.Metrics = "ga:visits";) to the project I get this error:
Error 7 Property or indexer 'Google.Apis.Analytics.v3.DataResource.GaResource.GetRequest.Metrics' cannot be assigned to -- it is read only
Also here's a index of classes in analytics v3:
https://developers.google.com/resources/api-libraries/documentation/analytics/v3/csharp/latest/annotated.html
Does anyone have knowledge of how this works? How do i retrieve total amount of visitors from the time frame i specified??
Thx
Your problem is that you supply metrics in the data.ga.get method, you dont use r.metrics metrics add more them one separate them with a ,. In this case ga:visits is the metrics you are requesting.
var r = gas.Data.Ga.Get("ga:MYProfileID", "2012-01-01", "2014-02-20", "ga:visits");
Dimensions aren't a required filed so you could and remove r.Dimensions = "ga:visitCount"; see what that returns.
Remember that simply doing Google.Apis.Analytics.v3.Data.GaData d = r.Execute(); isn't going to return all the rows if there are more then what you have max result set to. You need to check your next link to make sure you don't have more rows.
Update in response to question below about next link.
You currently have max-results set to 10000 which is the max number of rows that can be returned in a chunk. If there are more then 10000 rows then you will get a nextlink you need to request the the next chunk of rows. TotalResult will give you the total number of rows that you should be returning. Below I keep requesting more data using .execute until there isn't anymore Next links.
List result = new List();
do {
try
{
GaData DataList = request.Execute(); // Make the request
result.AddRange(DataList.Rows); // store the Data to return later
// hack to get next link stuff
totalResults = (!DataList.TotalResults.HasValue) ? 0 : Int32.Parse(DataList.TotalResults.ToString());
rowcnt = rowcnt + DataList.Rows.Count;
NextLink = DataList.NextLink;
request.StartIndex = rowcnt + 1 ;
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
totalResults = 0;
}
} while (request.StartIndex <= totalResults);
I need to download JPG file from FileCabinet in NetSuite. For that I know the file name, so I searched file and assigned to FileObject. I got the object right, but got NULL content. I am providing here some code. Can anybody point out the error or any missing step here? Thank you.
var result = _service.search(flSearch);
if (result.totalRecords > 0)
{
recordList = result.recordList;
Record[] records = new Record[recordList.Length];
for (int j = 0; j < recordList.Length; j++)
{
if (recordList[j] is File)
{
File itemImage = (File)(recordList[j]);
byte[] data;
data = new Byte[(int)itemImage.fileSize];
data = itemImage.content; //Here getting NULL value
FileStream inFile;
using (inFile = new FileStream("newImage.jpg", FileMode.Create, FileAccess.Write))
{
inFile.Write(data, 0, data.Length);
}
}
}
}
itemImage is just a string - base64.
take that string and do a base64 decode and save that to your local file.
If the search is based on the internal id of the file you want to search, then the following code may help
var service = LoginNetSuite();
Tuple<string, string> fileContent = null;
FileSearch fileSearch = new FileSearch();
FileSearchBasic fileSearchBasic = new FileSearchBasic();
// Specify the folder in which the search is to be done.
SearchMultiSelectField folderFilter = new SearchMultiSelectField();
folderFilter.#operator = SearchMultiSelectFieldOperator.anyOf;
folderFilter.operatorSpecified = true;
RecordRef[] folder = new RecordRef[1];
folder[0] = new RecordRef();
folder[0].internalId = "78990"; // 78990 => Internal id of the folder.
folderFilter.searchValue = folder;
fileSearchBasic.folder = folderFilter;
// Specify the file internal id.
SearchMultiSelectField fileFilter = new SearchMultiSelectField();
fileFilter.#operator = SearchMultiSelectFieldOperator.anyOf;
fileFilter.operatorSpecified = true;
RecordRef[] rec = new RecordRef[1];
rec[0] = new RecordRef();
rec[0].internalId = "345656"; // 345656 => Internal id of the file.
fileFilter.searchValue = rec;
fileSearchBasic.internalId = fileFilter;
fileSearch.basic = fileSearchBasic;
var result = service.search(fileSearch);
var recordList = (Record[])result.recordList;
if (recordList != null && recordList.Length != 0)
{
var file = (File)result.recordList.First();
fileContent = new Tuple<string, string>(file.url, file.name);
}
In this code the folder internal id and the file internal id is given as the search parameters. So the file search will be done in the specified file cabinet with specified file id.
The response from netsuite will consist of the internal id, file name, url, folder name etc. The file can be downloaded from the url location.