I need the ability to monitor for and read e-mail from a particular mailbox on a MS Exchange Server (internal to my company). I also need to be able to read the sender's e-mail address, subject, message body and download an attachment, if any.
What is the best way to do this using C# (or VB.NET)?
It's a mess. MAPI or CDO via a .NET interop DLL is officially unsupported by Microsoft--it will appear to work fine, but there are problems with memory leaks due to their differing memory models. You could use CDOEX, but that only works on the Exchange server itself, not remotely; useless. You could interop with Outlook, but now you've just made a dependency on Outlook; overkill. Finally, you could use Exchange 2003's WebDAV support, but WebDAV is complicated, .NET has poor built-in support for it, and (to add insult to injury) Exchange 2007 nearly completely drops WebDAV support.
What's a guy to do? I ended up using AfterLogic's IMAP component to communicate with my Exchange 2003 server via IMAP, and this ended up working very well. (I normally seek out free or open-source libraries, but I found all of the .NET ones wanting--especially when it comes to some of the quirks of 2003's IMAP implementation--and this one was cheap enough and worked on the first try. I know there are others out there.)
If your organization is on Exchange 2007, however, you're in luck. Exchange 2007 comes with a SOAP-based Web service interface that finally provides a unified, language-independent way of interacting with the Exchange server. If you can make 2007+ a requirement, this is definitely the way to go. (Sadly for me, my company has a "but 2003 isn't broken" policy.)
If you need to bridge both Exchange 2003 and 2007, IMAP or POP3 is definitely the way to go.
Um,
I might be a bit too late here but isn't this kinda the point to EWS ?
https://msdn.microsoft.com/en-us/library/dd633710(EXCHG.80).aspx
Takes about 6 lines of code to get the mail from a mailbox:
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
//service.Credentials = new NetworkCredential( "{Active Directory ID}", "{Password}", "{Domain Name}" );
service.AutodiscoverUrl( "First.Last#MyCompany.com" );
FindItemsResults<Item> findResults = service.FindItems(
WellKnownFolderName.Inbox,
new ItemView( 10 )
);
foreach ( Item item in findResults.Items )
{
Console.WriteLine( item.Subject );
}
Graph - currently, the preferred unified API (pure HTTP based) to access emails, contacts, appointments, tasks etc., residing on Exchange Server along with other data hosted by Microsoft (Teams, Sharepoint, etc.).
Use Graph Explorer or OutlookSpy (I am its author) to play with the API.
Microsoft provides Graph SDK for use in several languages
EWS. Fully supported. It is purely HTTP based and can be accessed from any language, but there are .Net and Java specific libraries.
MS has indicated that no new features will be added, and that Graph is preferable, even though not all EWS features are yet available in Graph (such as high fidelity Fast Transfer Stream export/import exposed through ExportItems/ImportItems).
You can use EWSEditor or OutlookSpy (I am its author) to play with the API.
Extended MAPI. This is the native API used by Outlook. It ends up using the MSEMS Exchange MAPI provider, which can talk to Exchange using RPC (Exchange 2013 no longer supports it) or RPC-over-HTTP (Exchange 2007 or newer) or MAPI-over-HTTP (Exchange 2013 and newer).
The API itself can only be accessed from unmanaged C++ or Delphi. You can also use Redemption (any language, I am its author) - its RDO family of objects is an Extended MAPI wrapper. To use Extended MAPI, you need to install either Outlook or the standalone (Exchange) version of MAPI (on extended support, and it does not support Unicode PST and MSG files and cannot access Exchange 2016). Extended MAPI can be used in a service.
You can play with the API using OutlookSpy (I am its author) or MFCMAPI.
Outlook Object Model - not Exchange specific, but it allows access to all data available in Outlook on the machine where the code runs. Cannot be used in a service.
Exchange Active Sync. Microsoft no longer invests any significant resources into this protocol.
Outlook used to install CDO 1.21 library (it wraps Extended MAPI), but it had been deprecated by Microsoft and no longer receives any updates.
There used to be a third-party .Net MAPI wrapper called MAPI33, but it is no longer being developed or supported.
WebDAV - deprecated.
Collaborative Data Objects for Exchange (CDOEX) - deprecated.
Exchange OLE DB Provider (EXOLEDB) - deprecated.
Here is some old code I had laying around to do WebDAV. I think it was written against Exchange 2003, but I don't remember any more. Feel free to borrow it if its helpful...
class MailUtil
{
private CredentialCache creds = new CredentialCache();
public MailUtil()
{
// set up webdav connection to exchange
this.creds = new CredentialCache();
this.creds.Add(new Uri("http://mail.domain.com/Exchange/me#domain.com/Inbox/"), "Basic", new NetworkCredential("myUserName", "myPassword", "WINDOWSDOMAIN"));
}
/// <summary>
/// Gets all unread emails in a user's Inbox
/// </summary>
/// <returns>A list of unread mail messages</returns>
public List<model.Mail> GetUnreadMail()
{
List<model.Mail> unreadMail = new List<model.Mail>();
string reqStr =
#"<?xml version=""1.0""?>
<g:searchrequest xmlns:g=""DAV:"">
<g:sql>
SELECT
""urn:schemas:mailheader:from"", ""urn:schemas:httpmail:textdescription""
FROM
""http://mail.domain.com/Exchange/me#domain.com/Inbox/""
WHERE
""urn:schemas:httpmail:read"" = FALSE
AND ""urn:schemas:httpmail:subject"" = 'tbintg'
AND ""DAV:contentclass"" = 'urn:content-classes:message'
</g:sql>
</g:searchrequest>";
byte[] reqBytes = Encoding.UTF8.GetBytes(reqStr);
// set up web request
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://mail.domain.com/Exchange/me#domain.com/Inbox/");
request.Credentials = this.creds;
request.Method = "SEARCH";
request.ContentLength = reqBytes.Length;
request.ContentType = "text/xml";
request.Timeout = 300000;
using (Stream requestStream = request.GetRequestStream())
{
try
{
requestStream.Write(reqBytes, 0, reqBytes.Length);
}
catch
{
}
finally
{
requestStream.Close();
}
}
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
try
{
XmlDocument document = new XmlDocument();
document.Load(responseStream);
// set up namespaces
XmlNamespaceManager nsmgr = new XmlNamespaceManager(document.NameTable);
nsmgr.AddNamespace("a", "DAV:");
nsmgr.AddNamespace("b", "urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/");
nsmgr.AddNamespace("c", "xml:");
nsmgr.AddNamespace("d", "urn:schemas:mailheader:");
nsmgr.AddNamespace("e", "urn:schemas:httpmail:");
// Load each response (each mail item) into an object
XmlNodeList responseNodes = document.GetElementsByTagName("a:response");
foreach (XmlNode responseNode in responseNodes)
{
// get the <propstat> node that contains valid HTTP responses
XmlNode uriNode = responseNode.SelectSingleNode("child::a:href", nsmgr);
XmlNode propstatNode = responseNode.SelectSingleNode("descendant::a:propstat[a:status='HTTP/1.1 200 OK']", nsmgr);
if (propstatNode != null)
{
// read properties of this response, and load into a data object
XmlNode fromNode = propstatNode.SelectSingleNode("descendant::d:from", nsmgr);
XmlNode descNode = propstatNode.SelectSingleNode("descendant::e:textdescription", nsmgr);
// make new data object
model.Mail mail = new model.Mail();
if (uriNode != null)
mail.Uri = uriNode.InnerText;
if (fromNode != null)
mail.From = fromNode.InnerText;
if (descNode != null)
mail.Body = descNode.InnerText;
unreadMail.Add(mail);
}
}
}
catch (Exception e)
{
string msg = e.Message;
}
finally
{
responseStream.Close();
}
}
return unreadMail;
}
}
And model.Mail:
class Mail
{
private string uri;
private string from;
private string body;
public string Uri
{
get { return this.uri; }
set { this.uri = value; }
}
public string From
{
get { return this.from; }
set { this.from = value; }
}
public string Body
{
get { return this.body; }
set { this.body = value; }
}
}
I used code that was published on CodeProject.com. If you want to use POP3, it is one of the better solutions that I have found.
If your Exchange server is configured to support POP or IMAP, that's an easy way out.
Another option is WebDAV access. there is a library available for that. This might be your best option.
I think there are options using COM objects to access Exchange, but I'm not sure how easy it is.
It all depends on what exactly your administrator is willing to give you access to I guess.
You should be able to use MAPI to access the mailbox and get the information you need. Unfortunately the only .NET MAPI library (MAPI33) I know of seems to be unmaintained. This used to be a great way to access MAPI through .NET, but I can't speak to its effectiveness now. There's more information about where you can get it here: Download location for MAPI33.dll?
I got a solution working in the end using Redemption, have a look at these questions...
Using Redemption...
Using Redemption on a 64 bit machine
Related
I can't show any code here yet, because i'm still analyzing if it is possible to realize this.
In our company we have a virtual system to make and manage leave applications. I should now check if it would be possible to enter an approved vacation in the Outlook calendar of the applicant.
I would need a central solution which remotely accesses the calendar and enters the appointments. We currently use the on premise solution of Mircosoft Exchange 2019 and Office 365.
During my research I came across EWS but it seems that Exchange 2019 does not support it anymore. Is there possibly another solution which I could use? Basically I would like to realize a solution with C# but I would also be able to realize a Powershell or Java solution. But most of the time I did not find a real solution.
Most of the time the examples are always local on the machines or using an older Exchange Server like 2013. I haven't found reliable information for 2019 yet. I hope someone here can help me or give me a hint. Or it would also be helpful to say if it is not possible.
Best regards!
I am currently working on a solution. I will post the code when I am successfull!**
Exchange 2019 fully supports EWS.
EWS is still the preferred API to access Exchange, even if Microsoft is not adding any new features to it.
ON the client side, you can use Outlook Object Model and its Namespace.GetSharedDefaultFolder methods. Once you have an instance of the MAPIFolder object, you can use MAPIFolder.Items.Add to create a new appointment.
I did choose a little bit different approach but I was able to make it fully work. I now can create events and delete them if needed.
public void UpdateCalender()
{
ExchangeService Service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
Uri Url = new Uri("https://localmaildomain.sys/EWS/Exchange.asmx");
Service.Url = Url;
Service.Credentials = new NetworkCredential("service_user","service_password");
Folder inboxFolder = Folder.Bind(Service, new FolderId(WellKnownFolderName.Calendar, Temp.UserMail));
foreach (var entry in Temp.Positions)
{
if (!entry.Storno)
{
try
{
Appointment appointment = new Appointment(Service);
appointment.Subject = $"Urlaub/Vacation ({entry.Type})";
appointment.Body = $"{entry.Comment}";
appointment.IsAllDayEvent = true;
appointment.Start = entry.Date.AddSeconds(1);
appointment.End = entry.Date.AddSeconds(1);
appointment.LegacyFreeBusyStatus = LegacyFreeBusyStatus.OOF;
appointment.Save(inboxFolder.Id, SendInvitationsMode.SendToNone);
}
catch (Exception Ex)
{
Console.WriteLine($"Calender item could not be created! Exception: {Ex.ToString()}");
}
}
else
{
CalendarView view = new CalendarView(entry.Date, entry.Date);
FindItemsResults<Appointment> results = Service.FindAppointments(inboxFolder.Id,view);
foreach (var appointment in results)
{
if (appointment.Subject == $"Urlaub/Vacation ({entry.Type})" && appointment.Start == entry.Date)
{
try
{
appointment.Delete(DeleteMode.MoveToDeletedItems);
}
catch(Exception Ex)
{
Console.WriteLine($"Calender item could not be deleted! Exception: {Ex.ToString()}");
}
break;
}
}
}
}
}
I have been contemplating on a dilemma for hours. I have a Visual Studio Solution that contains a WCF, WebForms, UWP, Xamarin and a SharedLibrary Projects.
I intend to use the WCF project as the backend which talks to the database and process Email and SMS integration and feed the other apps.
OPTION A
Currently, The WCF is hosted on an Azure App Service which makes it accessible via POST, GET, etc from the url which is: https://mywcfprojectlink.azurewebsites.net/service1.svc/GetUsers
With such arrangements, I can perform a POST request to get data from the apps:
string response = string.Empty;
string url = "https://mywcfprojectlink.azurewebsites.net/service1.svc/GetUsers";
try
{
var values = new Dictionary<string, string>
{
{ "data", Encryption.EncryptString(dat.ToString()) } //dat is incoming method param
};
string jsonString = JsonConvert.SerializeObject(values);
var cli = new WebClient();
cli.Headers[HttpRequestHeader.ContentType] = "application/json";
response = cli.UploadString($"{url}", jsonString);
var result = JsonConvert.DeserializeObject<string>(response);
topic.InnerText = Encryption.DecryptString(result.ToString());
}
catch (Exception)
{
return string.Empty;
}
The method above is a simple one as I have other ones where I Deserialize with Models/Classes.
OPTION B
I equally have access to the methods defined in service1 by adding the project reference to my WebForms which surprisingly is also compatible with xamarin but not with UWP. Nevertheless, I am interested in the WebForms scenario. Below is an example method:
using BackEnd;
//Service1 service1 = new Service1();
//var send = service1.GetUsers(dat.ToString()); //dat is incoming method param
//topic.InnerText = send;
Obviously, using the Option B would eliminate the need to encrypt, decrypt, serialize or deserialize the data being sent. However, I have serious performance concerns.
I need to know the better option and if there is yet another alternative (probably an Azure Resource), you can share with me.
If you decide to use https endpoint of the Azure website, option A is secure because of SSL encryption. So you don't have to encrypt/decrypt it by yourself. The only tip is to create a proper authorization mechanism. For example use TransportWithMessageCredential. An example is provided in below article https://www.codeproject.com/Articles/1092557/WCF-Security-and-Authentication-in-Azure-WsHttpBin
We like to enable some hidden features of our software only if it is run inside of the company network. The key requirements are:
no need for a third party library outside of DotNet 4.5.1
easy to implement (should not be more than some dozens of lines .. I don't want to reimplement a crypto library)
It should be reasonable safe:
at least: hard to reverse engineer
at best: "impossible" to break even with read-access to the source code
low maintenance overhead
Win2012-Server is available for installation of additional software (open source or own implementation prefered - server can be assumed to be safe)
What I have thought about:
Check if a specific PC is available with a known MAC or IP (current implementation, not really secure and some other flaws)
Test, if a service is available on a specific response (i.e. I send 'Hello' to MyServer:12345 - server responses with 'World')
Similar to 2nd but a more complex challenge (i.e. send a seed for a RNG to the server, verify the response)
Set up an apache with HTTPS and verify the certificate
If you use ActiveDirectory, you could add a reference to the System.DirectoryServices namespace and check
ActiveDirectorySite currentSite = ActiveDirectorySite.GetComputerSite();
then you can get a bit of information from the currentSite object and check against that. That's how I enable/disable features of an application I'm developing currently.
I also grab:
var client = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in client.AddressList)
{
if(ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
ipAddress = ip;
}
}
Which you can check to make sure the client is connected with the proper protocol.
I've choosen the last option: Set up a webserver in the intranet and verify the certificate.
It was easier than expected. There are enough tutorials for setting up an apache with https for every supported OS. The self-signed certificate have a lifetime of 9999 days - should be okay until 2042. The C#-part is also reasonable small:
private static bool m_isHomeLocation = false;
public static bool IsHomeLocation
{
get
{
if (m_isHomeLocation)
return true;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://yourLicenseServer:yourConfiguredPort");
request.ServerCertificateValidationCallback += ((s, certificate, chain, sslPolicyErrors) => true);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
response.Close();
var thumbprint = new X509Certificate2(request.ServicePoint.Certificate).Thumbprint;
m_isHomeLocation = (thumbprint == "WhateverThumbprintYourCertificateHave");
}
catch
{
// pass - maybe next time
}
return m_isHomeLocation;
}
}
The short question is whether is this possible and if so, how?
Outline
I have a .NET application which currently uses a service account to access information across a Google Apps domain using the Google Drive API. This works fine using the google-api-dotnet-client library and code along the same lines as shown in the samples here - which are currently a very good basic example of what I'm doing.
What I want to do now is extend it so as well as using those APIs provided by the "new" google-api-dotnet-client library, it uses the older "GData" libraries, as provided for via the
older google-gdata library, specifically the Spreadsheets API (and perhaps more to come).
The Problem
This is where the difficulty arises. The former library does exactly what I want, as evidenced by the second link in the first paragraph above - and the fact I have it doing it myself. HOWEVER... although the second library has been updated to support OAuth 2.0 in addition to OAuth 1.0 and the other older auth techniques, it does not - as far as I can tell from extensive Googling and trail-and-error - allow the "service account on behalf of all my users" operation which I need.
My question is whether I'm missing something (possibly a hard to find or undocumented something) which would allow me to do what I want. Failing that, is there any way I could force this behaviour and make these two libraries operate side by side?
The ideal solution
Ideally I would love some way of having the Google.GData.Spreadsheets.SpreadsheetsService instance be able to take advantage of the Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient> instance I'm already using... somehow. Is such witchcraft possible? I'm I missing the obvious?
Failing that, I'm happy to do the whole OAuth2 "assertion flow client" dance again if I have to, in some way that the older library can handle.
Help?
Other Thoughts
I have considered - and rejected for the time being - the option of starting from scratch and writing my own library to make this happen. This is for two reasons:
The gdata library already exists, and has been developed by many people likely cleverer than myself. I'm not so arrogant that I believe I can do better.
I'm not certain the OAuth2 with service account approach is even supported/allowed on these older APIs.
An alternate approach which I've been hoping to avoid but may have to fall back to depending on the answers here will be to use 2-legged OAuth 1.0 for portions of this. I'd prefer not to, as having parts of the app rely on one old auth method whilst other parts do it the nice new way just feels wrong to me. And there's that much more to go wrong...
Updates
I have considered the possibility of subclassing GDataRequestFactory and GDataRequest so I can make my own request factory and have that take the instance of Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient> (well, an instance of Google.Apis.Authentication.IAuthenticator anyway) which could step in to authenticate the request just before it's called. However... the constructor for GDataRequest is internal, which has stopped me.
It's really looking like this isn't meant to be.
For the sake of other folks coming across this question (now that the solution linked to in the accepted answer uses deprecated code), here's how I solved it:
First, start in "new API" land (use the Google.Apis.Auth nuget package) by setting up a ServiceAccountCredential following Google's Service Account example:
//In the old api, this accessed the main api accounts' sheets, not anymore
//** Important ** share spreadsheets with the Service Account by inviting the "serviceAccountEmail" address to the sheet
string serviceAccountEmail = "12345697-abcdefghijklmnop#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" }
}.FromCertificate(certificate));
Tell the credential to request an Access Token:
credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait();
Now it's time to switch back to "old API" land (use the Google.GData.Spreadsheets nuget package). Start by constructing the SpreadsheetsService (similar to Google's example):
SpreadsheetsService service = new SpreadsheetsService("MySpreadsheetIntegration-v1");
To use Service Account authentication, we'll create an instance of the GDataRequestFactory and set a custom Authorization header:
var requestFactory = new GDataRequestFactory("My App User Agent");
requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
Finally, set the SpreadsheetsService's RequestFactory property to this new factory:
service.RequestFactory = requestFactory;
And go ahead and use the SpreadsheetsService as you would had you authenticated using any other technique. (Tip: share spreadsheets with the Service Account by inviting the serviceAccountEmail address to the sheet)
I managed to solve this by subclassing GDataRequestFactory and creating my own implementation of the interfaces implemented by GDataRequest. This implementation wraps an instance of GDataRequest instantiated via reflection, and adds in the necessary code to perform authentication using an instance of IAuthenticator (in my case, Auth2Authenticator).
I wrote a blog post on it and added an example as a Gist:
Blog: Using Google's Spreadsheet API using .NET, OAuth 2.0 and a Service Account
Gist 4244834
Feel free to use this if it helps you (BSD licence).
Hey just stumbled accross the same problem and produced a different solution:
Has anybody ever concidered of writing the parameters from the credentials-object directly to an OAuth2Parameters-Object?
I did this and it worked nicely:
public class OAuthTest
{
OAuth2Parameters param = new OAuth2Parameters();
public OAuthTest()
{
Debug.WriteLine("Calling: AuthGoogleDataInterface()");
bool init = AuthGoogleDataInterface();
if (init)
{
GOAuth2RequestFactory requestFactory = new GOAuth2RequestFactory(null, "My App User Agent", this.param);
//requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
var service = new SpreadsheetsService("MyService");
service.RequestFactory = requestFactory;
SpreadsheetQuery query = new SpreadsheetQuery();
// Make a request to the API and get all spreadsheets.
SpreadsheetFeed feed = service.Query(query);
// Iterate through all of the spreadsheets returned
foreach (SpreadsheetEntry entry in feed.Entries)
{
// Print the title of this spreadsheet to the screen
Debug.WriteLine(entry.Title.Text);
}
}
Debug.WriteLine(m_Init);
}
private bool AuthGoogleDataInterface()
{
bool b_success;
try
{
Console.WriteLine("New User Credential");
// New User Credential
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
GoogleClientSecrets GCSecrets = GoogleClientSecrets.Load(stream);
string[] ArrScope = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" };
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GCSecrets.Secrets,
ArrScope,
"user", CancellationToken.None,
new FileDataStore("My.cal")).Result;
// put the Information generated for the credentials object into the OAuth2Parameters-Object to access the Spreadsheets
this.param.ClientId = GCSecrets.Secrets.ClientId; //CLIENT_ID;
this.param.ClientSecret = GCSecrets.Secrets.ClientSecret; //CLIENT_SECRET;
this.param.RedirectUri = "urn:ietf:wg:oauth:2.0:oob"; //REDIRECT_URI;
this.param.Scope = ArrScope.ToString();
this.param.AccessToken = credential.Token.AccessToken;
this.param.RefreshToken = credential.Token.RefreshToken;
}
Debug.WriteLine("AuthGoogleDataInterface: Success");
b_success = true;
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
b_success = false;
}
return b_success;
}
}
I'm wanting to copy an already existing Google Docs Spreadsheet to a new Google Docs spreadsheet. I dont think the v2.0 .NET API can handle it natively (or if so I can't find the class/method), however It looks like the v3.0 protocol can but I'm not sure how to implement this in the current framework or even if it is possible with the current .net api. eg. ~DocumentsFeed.copy() (pseudo code).
Exporting to a temp excel file then uploading with a new name is not possible either as some of the complex formulas get messed up in the conversion process.
I am a bit of a .NET noob so any info would be greatly appreciated eg. How would I go about doing this in .NET if I could only use the v3 protocol (ajax etc) and not the .NET API.
Thanks
EDIT: (final class thanks to #langsamu for his help!)
using System;
using Google.GData.Documents;
using Google.GData.Client;
using Google.GData.Extensions;
public class GoogleDocument
{
private DocumentsService ds;
private String username;
private String password;
public GoogleDocument(String username, String password)
{
this.ds = new DocumentsService("doc service name");
this.username = username;
this.password = password;
this.ds.setUserCredentials(username, password);
this.ds.QueryClientLoginToken();
}
public void copyDocument(String oldFileName, String newFileName)
{
SpreadsheetQuery query = new Google.GData.Documents.SpreadsheetQuery();
query.Title = oldFileName;
query.TitleExact = true;
DocumentsFeed feed = this.ds.Query(query);
AtomEntry entry = feed.Entries[0];
entry.Title.Text = newFileName;
var feedUri = new Uri(DocumentsListQuery.documentsBaseUri);
this.ds.Insert(feedUri, entry);
}
}
Google.GData.Documents.DocumentsService service = new Google.GData.Documents.DocumentsService("YOUR_APPLICATIONS_NAME");
service.setUserCredentials("YOUR_USERNAME", "YOUR_PASSWORD");
Google.GData.Documents.SpreadsheetQuery query = new Google.GData.Documents.SpreadsheetQuery();
query.Title = "YOUR_SPREADSHEETS_TITLE";
query.TitleExact = true;
Google.GData.Documents.DocumentsFeed feed = service.Query(query);
Google.GData.Client.AtomEntry entry = feed.Entries[0];
var feedUri = new Uri(Google.GData.Documents.DocumentsListQuery.documentsBaseUri);
service.Insert(feedUri, entry);
This solution is basically about retrieving an existing spreadsheet (service.Query) using the Document List API and re-inserting it (service.Insert).
Make sure you replace the ALL CAPS application name, username, password and spreadsheet title.
Add a reference to Google.GData.Documents.
This is using .NET 4 (should work with lower versions as well) and Google Documents List Data API v2.0 (DLL says version is 1.6.0.0: google-gdata), which seems to use version 3.0 of the protocol.
It is a bit unclear if you are developing a web application or a desktop application, so I'll try and cover both (essentially they are very much alike - because...).
If you are developing a web application you won't be able to make a 100% AJAX solution. You will only be able to request URL's on the same domain. To do this you will need to either do the communication server side only, or do it server side and proxy it to your web app through AJAX.
If you are developing a desktop application you'll have to do this stuff aswell. Except the AJAX part.
An example app would be fairly easy - 2-3 hours work to whip up considering the documentation given. With just a little knowledge of HTTP and POST request forming you should be able to make it work.