UWP Appointments: Appointment ID returning empty - c#

I am trying to add Appointments to my UWP App.
I have successfully set up the Appointment but the result for created Appointment Id is empty which means that the appointment is not created.
Following is my Code:
public static Rect GetElementRect(FrameworkElement element)
{
GeneralTransform transform = element.TransformToVisual(null);
Point point = transform.TransformPoint(new Point());
return new Rect(point, new Size(element.ActualWidth, element.ActualHeight));
}
private async Task<ManagerResponseModel> AddAppointmentToOutlookCalendar(ViewingSummaryModel model)
{
ManagerResponseModel result = new ManagerResponseModel();
//Add appointment if assigned to the same user
if(model.HousingOfficerId == AppSession.LoggedinUserId)
{
// Create an Appointment that should be added the user's appointments provider app.
var appointment = new Appointment();
//Populate Viewing Data in appointment
appointment.Subject = string.Format("Viewing at {0}", model.PropertyAddress);
appointment.Location = (string.IsNullOrWhiteSpace(model.PropertyAddress)) ? "NA" : model.PropertyAddress;
appointment.BusyStatus = AppointmentBusyStatus.Tentative;
appointment.Sensitivity = AppointmentSensitivity.Public;
appointment.AllDay = false;
//var timeZoneOffset = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now);
var date = GeneralHelper.GetCombinedDateTimeStringForViewing(model.ViewingDate, model.FormattedTime);
//var startTime = new DateTimeOffset(date.Year, date.Month, date.Day, date.Hour, date.Minute, 0, TimeZoneInfo.Local.BaseUtcOffset);
appointment.StartTime = date;
appointment.Details = string.Format("Customer: {0}", model.CustomerName) + "\r"
+ string.Format("Housing Officer: {0}", (string.IsNullOrWhiteSpace(model.AssignedTo)) ? "NA" : model.AssignedTo) + "\r"
+ string.Format("Address: {0}", model.PropertyAddress) + "\r"
+ string.Format("Created by: {0}", (string.IsNullOrWhiteSpace(model.CreatorName)) ? "You" : model.CreatorName);
// Get the selection rect of the button pressed to add this appointment
var rect = GetElementRect(this.Frame as FrameworkElement);
string appointmentId = string.Empty;
// ShowAddAppointmentAsync returns an appointment id if the appointment given was added to the user's calendar.
// This value should be stored in app data and roamed so that the appointment can be replaced or removed in the future.
// An empty string return value indicates that the user canceled the operation before the appointment was added.
if (!string.IsNullOrWhiteSpace(model.OutlookIdentifier))
{
appointmentId = await AppointmentManager.ShowReplaceAppointmentAsync(model.OutlookIdentifier, appointment, rect, Placement.Default, date);
/*Appointment doesn't exist on this system, try to add a new one*/
if (string.IsNullOrWhiteSpace(appointmentId))
{
appointmentId = await AppointmentManager.ShowAddAppointmentAsync(appointment, rect, Windows.UI.Popups.Placement.Default);
}
}
else
{
appointmentId = await AppointmentManager.ShowAddAppointmentAsync(appointment, rect, Windows.UI.Popups.Placement.Default);
}
model.OutlookIdentifier = appointmentId;
result.isSuccessful = string.IsNullOrWhiteSpace(appointmentId);
result.responseObject = model;
}
else
{
result.isSuccessful = true;
result.responseObject = model;
}
return result;
}
The following line:
appointmentId = await AppointmentManager.ShowAddAppointmentAsync(appointment, rect, Windows.UI.Popups.Placement.Default);
returns empty only if user cancels the operation or there is some other issue resulting in failure in creation of appointment. I am not cancelling the operation, nor is there any raised exception so I have no idea what am I doing wrong here.

I figured it out after reading a related thread on MSDN. It was a really stupid mistake. I had forgotten to add the Appointments capability in my Package.appxmanifest file.
So the problem was, my App was not authorized to add appointments to User's calendar which is why the appointment Id was returning empty (Would have been nice if the relevant error was returned too, Microsoft).
To fix this, add the following line to your package.appxmanifest file in Capabilities:
<Capabilities>
<uap:Capability Name="appointments" />
</Capabilities>
Or alternatively, you can just click on the file, go to Capabilities tab and check the "Appointments" capability like in the screenshot below:

Related

EWS API trying get appointment starting from a modified date

I am currently working on an app that checks all appointments on an individual email, it can update appointments, delete them, create new etc. This part is working already.
I am now trying to put all appointment starting from a specified last modified time into a list, and here is my issue. Code:
public void getByModifiedDate(CalendarFolder calendar)
{
bool found = false;
string date;
Console.WriteLine("Modified date: ");
date = Convert.ToString(Console.ReadLine());
List<TAppointments> tempList = new List<TAppointments>();
var appointments = getAppointments(calendar);
//loop through every appts on calendar
foreach (Appointment a in appointments)
{
if (a.LastModifiedTime.ToString() == date)
{
TAppointments app = new TAppointments(a.ICalUid.ToString(), a.Subject.ToString(), a.Start.ToString(), a.End.ToString(), a.LastModifiedTime.ToString());
currentApp = app;
tempList.Add(app);
found = true;
}
}
if (!found)
{
Console.WriteLine("No appointment found.");
}
printAppointments(tempList);
}
Here I can only find a single item, because well that's what I coded. Is there a way to specified a starting date? Something like lastmodifiedtime >= date? I've made some researched and did not find anything.
I fixed my case. I simply converted my specified date to DateTime, then I could compare it properly, as seen below. I hope it helps some people who has the same issue.
foreach (Appointment a in appointments)
{
if (a.LastModifiedTime >= Convert.ToDateTime(date))
{
i++;
TAppointments app = new TAppointments(a.ICalUid.ToString(), a.Subject.ToString(), a.Start.ToString(), a.End.ToString(), a.LastModifiedTime.ToString());
currentApp = app;
tempList.Add(app);
found = true;
}
}

The account does not have permission to impersonate the requested user while saving appointment on other user's outlook calendar

I want to create appointment on outlook calendar by using EWS. I followed https://msdn.microsoft.com/en-us/library/office/dn722379(v=exchg.150).aspx link. but I am seeing this error:
"The account does not have permission to impersonate the requested user".
Please go through my code
private static ExchangeService Service
{
get
{
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
//service.Credentials = new WebCredentials("example#abc.com", password);
service.AutodiscoverUrl("example#abc.com");
return service;
}
}
private void SaveAppointment()
{
IAppointmentFactory iappointment = new AppointmentFactory();
List<string> lstEmail = new List<string>() {"other#abc.com"};
CreateAppointments(Service, lstEmail, iappointment);
}
private static void CreateAppointments(ExchangeService service, List<string> emailAddresses, IAppointmentFactory factory)
{
// Loop through the list of email addresses to add the appointment.
foreach (var emailAddress in emailAddresses)
{
Console.WriteLine(string.Format(" Placing appointment in calendar for {0}.", emailAddress));
// Set the email address of the account to get the appointment.
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailAddress);
// Get the appointment to add.
Microsoft.Exchange.WebServices.Data.Appointment appointment = factory.GetAppointment(service);
// Save the appointment.
try
{
if (appointment.RequiredAttendees.Count > 0)
{
// The appointment has attendees so send them the meeting request.
appointment.Save(SendInvitationsMode.SendToAllAndSaveCopy);
}
else
{
// The appointment does not have attendees, so just save to calendar.
appointment.Save(SendInvitationsMode.SendToNone);
}
}
catch (ServiceResponseException ex)
{
Console.WriteLine(string.Format("Could not create appointment for {0}", emailAddress));
Console.WriteLine(ex.Message);
}
}
}
It seems as you only have delegate access to the calendar, you want to save appointments to.
In order to get your code working, please remove the line
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailAddress);
and change the Save methods like this:
appointment.Save(new FolderId(WellKnownFolderName.Calendar, new Mailbox(emailAddress)), SendInvitationsMode.SendOnlyToAllAndSaveCopy);

Editing an appointment programmatically

I'm using Microsoft.Office.Interop.Outlook to load appointments into the interface and to edit appointments . I can load appointments completely fine, editing does work but not 100% of the time. Sometimes I get the error The operation cannot be performed because the message has been changed
May I ask is there a better approach for editing appointments to avoid The operation cannot be performed because the message has been changed error. Error occurs when trying to Save()
public Outlook.AppointmentItem EditOutlookAppointment(CustomAppointment appointment, int retries = 0)
{
Outlook.AppointmentItem appointReturned = null;
try
{
Outlook.Application outlookApp = new Outlook.Application();
MAPIFolder calendarFolder = outlookApp.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar);
for (int i = calendarFolder.Items.Count; i > 0; i--)
{
var appointmentItem = ((Outlook.AppointmentItem)calendarFolder.Items[i]);
if (appointmentItem.GlobalAppointmentID == appointment.UniqueId)
{
// set some properties
appointmentItem.Subject = appointment.Subject;
appointmentItem.Body = appointment.Body;
appointmentItem.Location = appointment.Location; //Set the location
appointmentItem.Start = appointment.Start;
appointmentItem.End = appointment.End;
appointmentItem.Save();
return appointmentItem;
}
}
}
catch (Exception ex)
{
//Error message implementation here
}
return appointReturned;
}
Firstly, errors like that are unavoidable - it means the appointment was modified between the time Outlook opened it and the time you called Save. And since Outlook really likes to cache the appointments (it always caches the appointment being edited in Outlook or the previously edited appointment), that period of time can be quite large. This can happen if the appointment was modified by the Exchange server itself or by an incoming meeting update processing.
Secondly, looping through all items in the Calendar folder can be a huge performance problem. Unfortunately Outlook won't let you search (Items.Find/FindNext and Items.Restrict) on binary (PT_BINARY) properties such as GlobalAppointmentID. You'd need to use Extended MAPI (C++ or Delphi) or Redemption (I am its author - any language) for this: (RDOtems.Find/FindNext/Restrict in Redemption allow to search on binary properties).
UPDATE. The following should work using Redemption (off the top of my head):
publicRedemption.RDOAppointmentItem EditOutlookAppointment(CustomAppointment appointment, int retries = 0)
{
try
{
Outlook.Application outlookApp = new Outlook.Application();
Redemption.RDOSession session = new Redemption.RDOSession();
session.MAPIOBJECT = outlookApp.Session.MAPIOBJECT; //share the Outlook session
RDOFolder calendarFolder = session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar);
Redemption.RDOAppointmentItem appointmentItem = calendarFolder.Items.Find("GlobalAppointmentID = '"+appointment.UniqueId + "'");
if (appointmentItem != null)
{
// set some properties
appointmentItem.Subject = appointment.Subject;
appointmentItem.Body = appointment.Body;
appointmentItem.Location = appointment.Location; //Set the location
appointmentItem.Start = appointment.Start;
appointmentItem.End = appointment.End;
appointmentItem.Save();
return appointmentItem;
}
}
catch (Exception ex)
{
//Error message implementation here
}
return null;
}

Assigning Outlook Appointment recurrence type from code

I am trying to Add an Appointment in Outlook by code. There is a attribute called "GetRecurrencePattern().RecurrenceType" for the "Outlook.AppointmentItem" object. When I am trying to set this attribute to "OlRecurrenceType.olRecursDaily" it automatically gets converted to "OlRecurrenceType.olRecursWeekly".
Here is my code:
Outlook.AppointmentItem oMeet;
Meeting Meet;
Hashtable htrecc = GetReccuranceTable(strRec);
if (Meet.recctype.Substring(0, 3) == "day")
{
oMeet.GetRecurrencePattern().RecurrenceType = OlRecurrenceType.olRecursDaily;
OlRecurrenceType pattern2 = oMeet.GetRecurrencePattern().RecurrenceType;
string rectype = pattern2.ToString();
oMeet.GetRecurrencePattern().Interval = Convert.ToInt32(htrecc["Interval"]);
if (string.Equals("no", htrecc["Occurence"]))
oMeet.GetRecurrencePattern().NoEndDate = true;
else
if (!string.IsNullOrEmpty(Convert.ToString(htrecc["Occurence"])))
oMeet.GetRecurrencePattern().Occurrences = Convert.ToInt32(htrecc["Occurence"]);
else
oMeet.GetRecurrencePattern().PatternEndDate = Meet.EndTime;
}
The string "rectype" returns a value "olRecursWeekly"
Please Suggest if I am going wrong somewhere.
Every time you call GetRecurrencePattern(), you get back a brand new RecurrentPattern COM object. Do not call GetRecurrencePattern() every time - call it once, cache the value, and use it everywhere in your code
Outlook.RecurrencePattern pattern = oMeet.GetRecurrencePattern();
pattern.RecurrenceType = OlRecurrenceType.olRecursDaily;
OlRecurrenceType pattern2 = pattern.RecurrenceType;
string rectype = pattern2.ToString();
pattern.Interval = Convert.ToInt32(htrecc["Interval"]);
if (string.Equals("no", htrecc["Occurence"]))
pattern.NoEndDate = true;
else
if (!string.IsNullOrEmpty(Convert.ToString(htrecc["Occurence"])))
pattern.Occurrences = Convert.ToInt32(htrecc["Occurence"]);
else
pattern.PatternEndDate = Meet.EndTime;

EWS Managed API SyncFolderItems How to Update and Delete/Keeping track of items

I am building an app that is required to live in a trusted domain, monitor a collection of mailbox-calendars on an exchange server in that domain, and sync appointments to different mailboxes on one or many other servers. The mailbox it is synced with is defined in an internal mapping table (sqlce) that is maintained by the user of this application.
The problem I have is I can not work out a way to keep track of the remote appointment so that I can update or delete it if necessary. After I create the appointments on the remote server they have a new itemid which does not correspond to the one returned by the sync folder items call on the local exchange server. I can't find the item by start time/subject as these may have been changed or deleted.
My sync method is below - am I going about this entirely the wrong way or is there a better way to use the SyncFolderItems method?
The best approach I have come up with so far to get around my problem is to save ItemID of the remote appointment into a property of the local appointment but even this I am not sure will work because I don't know what properties are maintained after a delete? Please Help!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Exchange.WebServices.Data;
using System.Net;
namespace ProExchangeSync2012
{
class ExchangeWebServiceMethods
{
public string ProExchangeSyncCalendars(string LocalMailbox
,string RemoteMailbox
,string SyncState
,ExchangeService RemoteService
,ExchangeService LocalService
)
{
//if SyncState is empty string set to null
if (SyncState.ToString().Length == 0)
{ SyncState = null; }
ExchangeService LocalExchangeService = LocalService;
ExchangeService RemoteExchangeService = RemoteService;
RemoteExchangeService.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress,RemoteMailbox);
//Folders and mailboxes to pass to the webservice in SyncItems call.
Mailbox DonorMailBox = new Mailbox(LocalMailbox);
Mailbox DestinationMailBox = new Mailbox(RemoteMailbox);
FolderId DonorFolder = new FolderId(WellKnownFolderName.Calendar, DonorMailBox);
FolderId DestinationFolder = new FolderId(WellKnownFolderName.Calendar, DestinationMailBox);
//Create a ChangeCollection object and call syncfolderitems on local exchange service.
ChangeCollection<ItemChange> ItemChanges
= LocalExchangeService.SyncFolderItems(new FolderId(WellKnownFolderName.Calendar, DonorMailBox) //PASS IN THE MAILBOX HERE>
, PropertySet.FirstClassProperties
, null
, 512
, SyncFolderItemsScope.NormalItems
, SyncState
);
//Store the SyncState
SyncState = ItemChanges.SyncState;
//Fetch all the required properties of the items
//LocalService.LoadPropertiesForItems(ItemChanges, PropertySet.FirstClassProperties);
if (ItemChanges.Count == 0)
{
Console.WriteLine("There are no items to synchronize.");
}
else
{
foreach (ItemChange ic in ItemChanges)
{
if (ic.ChangeType == ChangeType.Create)
{
Appointment lappointment = Appointment.Bind(LocalExchangeService, ic.ItemId);
Appointment rappointment = new Appointment(RemoteExchangeService);
rappointment.Subject = lappointment.Subject;
rappointment.Start = lappointment.Start;
rappointment.Body = lappointment.Body;
rappointment.End = lappointment.End;
rappointment.Location = lappointment.Location;
rappointment.Save();
}
else if (ic.ChangeType == ChangeType.Update)
{
//Bind to the local appointment and get the start date
Appointment lappointment = Appointment.Bind(LocalExchangeService, ic.ItemId);
DateTime StartDate = lappointment.Start;
ItemId ItemToUpdate = ItemIDSearch(RemoteExchangeService,StartDate,lappointment.Subject);
//Bind to the remote appointment using ItemToUpdate & update all the details
//this is is less intensive than comparing the appointments for changes.
Appointment rappointment = Appointment.Bind(RemoteExchangeService, ItemToUpdate);
rappointment.Subject = lappointment.Subject;
rappointment.Start = lappointment.Start;
rappointment.Body = lappointment.Body;
rappointment.End = lappointment.End;
rappointment.Location = lappointment.Location;
rappointment.Save();
}
else if (ic.ChangeType == ChangeType.Delete)
{
Appointment lappointment = Appointment.Bind(LocalExchangeService, ic.ItemId.UniqueId);
DateTime StartDate = lappointment.Start;
ItemId ItemToUpdate = ItemIDSearch(RemoteExchangeService, StartDate, lappointment.Subject);
Appointment rappointment = Appointment.Bind(RemoteExchangeService, ic.ItemId.UniqueId);
rappointment.Delete(DeleteMode.MoveToDeletedItems);
}
}
}
return SyncState;
}
//End of Sync Method
//Below method returns a single itemid from exchange service based on start datetime of an appointment in a mailbox.
public ItemId ItemIDSearch(ExchangeService ExchangeService, DateTime AppointmentStart, string subject)
{
ItemId FoundItem;
ItemView iv = new ItemView(1000);
iv.Traversal = ItemTraversal.Associated;
SearchFilter.SearchFilterCollection searchFilterCollection = new SearchFilter.SearchFilterCollection(LogicalOperator.And);
searchFilterCollection.Add(new SearchFilter.IsEqualTo(AppointmentSchema.Subject,subject));
searchFilterCollection.Add(new SearchFilter.IsEqualTo(AppointmentSchema.Start, AppointmentStart));
FindItemsResults<Item> fiitems = ExchangeService.FindItems(WellKnownFolderName.Calendar, searchFilterCollection, iv);
if (fiitems.Items.Count == 1)//if we only get one result do the work else return null
{
FoundItem = fiitems.Items[0].Id;
}
FoundItem = null;
return FoundItem;
}
}
}
So the final solution to all this, which came from the messiah of Exchange Web Services, Glen Scales, was that I stored the "CleanGlobalObjectId" of the appointments I was synching with in my internal database along with the EWS UniqueId that is returned when calling the EWS SyncFolderItems method.
Using the CleanGlobalObjectId which is an extended property of the appointment, I was always able to find a specific appointment on the server even if it had been hard deleted, because the value of this property never changes.

Categories