I've recently being trying to listen to events from a user's calendars so that I can track when appointments are added, removed and modified, and ran into a couple of problems.
Problem 1: Events intermittently stop firing
I was listening to events on the MAPIFolder object and the Calendar's Items collection and they would work for a while and at some point would stop firing.
I eventually traced this down to what I believe is a COM reference counting issue. I wasn't storing a reference to these objects, so I guess there was no implicit AddRef by the interop library. As such, there was no need for the runtime to keep the object around.
Hanging on to these references appears to be resolving this issue, so I stand by my assumptions.
Problem 2: How do we know what has been deleted?
The events I was hooking up to were on the ItemsEvents_Event interface off the MAPIFolder.Items property. There are thee events on this interface:
- ItemAdd
- ItemChange
- ItemRemove
The first two have a single parameter which will be the AppointmentItem that is either being added or changed. Great! The last one doesn't have any parameters - all it tells you is that an item has been removed, but doesn't give you any indication what has been deleted. Besides that, its actually too late anyway, the item has already been deleted (you cannot cancel it).
So, I am now hooking up to the BeforeItemMove event on the MAPIFolderEvents_12_Event interface off the MAPIFolder object. This gives me a reference to the AppointmentItem being moved, the folder in which it is being moved to, and also allows me to cancel it.
To detect that it is being deleted, just check the folder it is being moved to is null (hard-delete such as Shift-Del) or the deleted items folder (soft-delete).
Solution
I have created a class that will monitor a user's default calendar and fires three events:
- AppointmentAdded - gives you the AppointmentItem instance added.
- AppointmentModified - gives you the AppointmentItem instance changed.
- AppointmentDeleting - gives you the AppointmentItem that is about to be deleted and allows you to cancel the operation if needed.
Sample usage:
The following code is for use within the ThisAddIn class. It outputs to console when an item is added, modified, or about to be deleted. It also displays a dialog box confirming an item should be deleted.
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
CalendarMonitor monitor = new CalendarMonitor(this.Application.ActiveExplorer());
monitor.AppointmentAdded +=
new EventHandler<EventArgs<AppointmentItem>>(monitor_AppointmentAdded);
monitor.AppointmentModified +=
new EventHandler<EventArgs<AppointmentItem>>(monitor_AppointmentModified);
monitor.AppointmentDeleting +=
new EventHandler<CancelEventArgs<AppointmentItem>>(monitor_AppointmentDeleting);
}
private void monitor_AppointmentAdded(object sender, EventArgs<AppointmentItem> e)
{
Debug.Print("Appointment Added: {0}", e.Value.GlobalAppointmentID);
}
private void monitor_AppointmentModified(object sender, EventArgs<AppointmentItem> e)
{
Debug.Print("Appointment Modified: {0}", e.Value.GlobalAppointmentID);
}
private void monitor_AppointmentDeleting(object sender, CancelEventArgs<AppointmentItem> e)
{
Debug.Print("Appointment Deleting: {0}", e.Value.GlobalAppointmentID);
DialogResult dr = MessageBox.Show("Delete appointment?", "Confirm",
MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (dr == DialogResult.No)
{
e.Cancel = true;
}
}
There are two other classes used: EventArgs
EventArgs.cs:
using System;
using System.Collections.Generic;
namespace OutlookAddIn2
{
public class EventArgs<T> : EventArgs
{
private T _value;
public EventArgs(T aValue)
{
_value = aValue;
}
public T Value
{
get { return _value; }
set { _value = value; }
}
}
}
CancelEventArgs.cs:
using System;
using System.Collections.Generic;
namespace OutlookAddIn2
{
public class CancelEventArgs<T> : EventArgs<T>
{
private bool _cancel;
public CancelEventArgs(T aValue)
: base(aValue)
{
}
public bool Cancel
{
get { return _cancel; }
set { _cancel = value; }
}
}
}
CalendarMonitor.cs:
using System;
using System.Collections.Generic;
using Microsoft.Office.Interop.Outlook;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace OutlookAddIn2
{
public class CalendarMonitor
{
private Explorer _explorer;
private List<string> _folderPaths;
private List<MAPIFolder> _calendarFolders;
private List<Items> _calendarItems;
private MAPIFolder _deletedItemsFolder;
public event EventHandler<EventArgs<AppointmentItem>> AppointmentAdded;
public event EventHandler<EventArgs<AppointmentItem>> AppointmentModified;
public event EventHandler<CancelEventArgs<AppointmentItem>> AppointmentDeleting;
public CalendarMonitor(Explorer anExplorer)
{
_folderPaths = new List<string>();
_calendarFolders = new List<MAPIFolder>();
_calendarItems = new List<Items>();
_explorer = anExplorer;
_explorer.BeforeFolderSwitch +=
new ExplorerEvents_10_BeforeFolderSwitchEventHandler(Explorer_BeforeFolderSwitch);
NameSpace session = _explorer.Session;
try
{
_deletedItemsFolder = session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);
HookupDefaultCalendarEvents(session);
}
finally
{
Marshal.ReleaseComObject(session);
session = null;
}
}
private void HookupDefaultCalendarEvents(NameSpace aSession)
{
MAPIFolder folder = aSession.GetDefaultFolder(OlDefaultFolders.olFolderCalendar);
if (folder != null)
{
try
{
HookupCalendarEvents(folder);
}
finally
{
Marshal.ReleaseComObject(folder);
folder = null;
}
}
}
private void Explorer_BeforeFolderSwitch(object aNewFolder, ref bool Cancel)
{
MAPIFolder folder = (aNewFolder as MAPIFolder);
//
// Hookup events to any other Calendar folder opened.
//
if (folder != null)
{
try
{
if (folder.DefaultItemType == OlItemType.olAppointmentItem)
{
HookupCalendarEvents(folder);
}
}
finally
{
Marshal.ReleaseComObject(folder);
folder = null;
}
}
}
private void HookupCalendarEvents(MAPIFolder aCalendarFolder)
{
if (aCalendarFolder.DefaultItemType != OlItemType.olAppointmentItem)
{
throw new ArgumentException("The MAPIFolder must use " +
"AppointmentItems as the default type.");
}
//
// Ignore other user's calendars.
//
if ((_folderPaths.Contains(aCalendarFolder.FolderPath) == false)
&& (IsUsersCalendar(aCalendarFolder)))
{
Items items = aCalendarFolder.Items;
//
// Store folder path to prevent double ups on our listeners.
//
_folderPaths.Add(aCalendarFolder.FolderPath);
//
// Store a reference to the folder and to the items collection so that it remains alive for
// as long as we want. This keeps the ref count up on the underlying COM object and prevents
// it from being intermittently released (then the events don't get fired).
//
_calendarFolders.Add(aCalendarFolder);
_calendarItems.Add(items);
//
// Add listeners for the events we need.
//
((MAPIFolderEvents_12_Event)aCalendarFolder).BeforeItemMove +=
new MAPIFolderEvents_12_BeforeItemMoveEventHandler(Calendar_BeforeItemMove);
items.ItemChange += new ItemsEvents_ItemChangeEventHandler(CalendarItems_ItemChange);
items.ItemAdd += new ItemsEvents_ItemAddEventHandler(CalendarItems_ItemAdd);
}
}
private void CalendarItems_ItemAdd(object anItem)
{
AppointmentItem appointment = (anItem as AppointmentItem);
if (appointment != null)
{
try
{
if (this.AppointmentAdded != null)
{
this.AppointmentAdded(this, new EventArgs<AppointmentItem>(appointment));
}
}
finally
{
Marshal.ReleaseComObject(appointment);
appointment = null;
}
}
}
private void CalendarItems_ItemChange(object anItem)
{
AppointmentItem appointment = (anItem as AppointmentItem);
if (appointment != null)
{
try
{
if (this.AppointmentModified != null)
{
this.AppointmentModified(this, new EventArgs<AppointmentItem>(appointment));
}
}
finally
{
Marshal.ReleaseComObject(appointment);
appointment = null;
}
}
}
private void Calendar_BeforeItemMove(object anItem, MAPIFolder aMoveToFolder, ref bool Cancel)
{
if ((aMoveToFolder == null) || (IsDeletedItemsFolder(aMoveToFolder)))
{
AppointmentItem appointment = (anItem as AppointmentItem);
if (appointment != null)
{
try
{
if (this.AppointmentDeleting != null)
{
//
// Listeners to the AppointmentDeleting event can cancel the move operation if moving
// to the deleted items folder.
//
CancelEventArgs<AppointmentItem> args = new CancelEventArgs<AppointmentItem>(appointment);
this.AppointmentDeleting(this, args);
Cancel = args.Cancel;
}
}
finally
{
Marshal.ReleaseComObject(appointment);
appointment = null;
}
}
}
}
private bool IsUsersCalendar(MAPIFolder aFolder)
{
//
// This is based purely on my observations so far - a better way?
//
return (aFolder.Store != null);
}
private bool IsDeletedItemsFolder(MAPIFolder aFolder)
{
return (aFolder.EntryID == _deletedItemsFolder.EntryID);
}
}
}
77 comments:
Thanks so much for this! I would have never figured it out.
I've been looking up other people's posts for help for years. I'm glad I've helped at least one person!
Hi Adrian, thank you so much for posting this code. I have finally managed to get Stephen Toub code "Custom Calendar Providers for Outlook 2003" to work but I have one doubt, which I need your help for. I need to be able delete an event from Outlook calendar when an event from my webservice is deleted. Would I be able to use your code to accomplish this???
Hi Adrian, I was reading through your code "CalendarMonitor" class, you have a variable called "MAPIFolderEvents_12_Event", can you tell me what this is as it's not been declared anywhere in your code????
The code here only listens to events of a Calendar in Outlook (it won't delete, add, move anything itself), specifically 2007. I believe there are some small differences between 2007 and 2003, but you should still be able to use this code as a basis.
As for the MAPIFolderEvents_12_Event reference, this is an interface implemented by the MAPIFolder object. From memory, I think you have to explicitly cast the MAPIFolder object to MAPIFolderEvents_12_Event to get access to the events. You should find this defined in the Outlook COM interop library that is referenced as part of the Visual Studio Outlook AddIn Project.
Hi Adrian, I'm glad to see I'm not the only one that had this issue. I was able to accomplish removing an appointment from the db when the user deleted an appointment from the calendar by returning all the items in the Deleted Items folder and then looping through those items to return the AppointmentItem for each item in the folder by using the following code.
Outlook.MAPIFolder deletedItemsFolder =
Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);
Outlook.Items deletedAppointments = deletedItemsFolder.Items;
foreach (AppointmentItem outlookAppointment in deletedAppointments)
{
if (outlookAppointment != null)
{
object objAppointmentID = GetUserProperty(outlookAppointment, APPOINTMENT_ID_KEY);
if (objAppointmentID == null) { return; }
int appointmentID = int.Parse(objAppointmentID.ToString());
Appointment appointment = Appointment.GetAppointment(appointmentID);
appointment.Delete();
If you see any issues with doing it this way please respond so I can fix them.
Thanks
The only issue I can see with this method is you are assuming that all deleted items go to the deleted items folder.
Appointments can be deleted directly (Shift-Del is the shortcut I think) and this won't get picked up by that code.
Cheers,
Adrian.
Hi Adrian,
this post is really helped me a lot. This is great code i was looking for. i have small issue in my outlook add-in, when user changes the time of the appointment item through the calender dragging, it fires item change event. I want to cancel Item Change event in certain conditions. Is it possible to cancel Item Change change event.
Thanks & Regards,
Sandeep
Sandeep,
Good question, but I don't know. From what I have seen, it looks unlikely.
Cheers,
Adrian.
hi Adrian,
Your code works great if i make any change in appointment or click on delete Appointment. If i send meeting invitation and and then cancel meeting it doesn't capture this event. I think BeforeItemMove should capture this event, but it doesn't. Can you please tell me how to capture cancel meeting event?
Thanks & Regards,
Sandeep
Hi Adrian,
A very useful post.
Just a question for you see if you can help. How we can trap the item before delete event in outlook 2000 or if there is any other work around we can use by implementing some other events?
Thanks in advance
I'm sorry WAQ, I don't know with Outlook 2000.
Hi Adrian, loving the code you've posted although for some reason the MAPIFolderEvents_12_Event.BeforeItemMove event doesn't seem to fire on a custom calendar. Any ideas why this might be?
Thanks in advance,
Alex
Great Post Adrian! Thanks to you I was able to actually get my application running the way I wanted.
Just one thing is bugging me: If I create or edit an appointment using Outlook 2007 everthing works as expected and I'm able to retrieve the "e.Value.Organizer" value. However, if a user does the same using Outlook web access 2003 - Premium or Basic, that value comes back with the vale "Error."
Any guidedance or suggestions woould be greatly appreciated. Thanks again.
Hi Alex,
The code posted won't work on a custom calendar because it is only listening to events on the default calendar.
You'll have to loop through all of the MAPIFolders looking for anything that is a calendar.
Cheers,
Adrian.
Hi eaglesigma,
Yeah, I haven't really played around much with OWA. It wasn't a requirement for me, so I deliberately chose to ignore it. :o)
Sorry, and good luck!
Adrian.
Hi Adrian, I've actually modified the code you posted to take in an array of MAPIFolders when constructing the CalendarMonitor object so it it is listening to events on the my calendar. The item events fire but not the folder event as mentioned before...?
Alex,
Hmmm... can't see why the folder event does not fire. I don't think I've tried it with a custom calendar myself and haven't really looked at this Outlook stuff for quite a while now.
I'm really sorry but I don't have the time to look into this right away.
If you have any luck getting it to work or find out why it doesn't, please let me know. Otherwise I'll try and get to it sometime over the next week or so (which is probably way too late for you).
Cheers,
Adrian.
Hello Adrian,
The modify event seems to be firing too early.
Here is the issue:
Sometimes when the calendar has multiple updates the entry shows up
on the calendar view but when I
access the properties, such as Start or Subject, it does not have the new values- it has the previous values that were present before the modify event fired.
The tricky part is that on the calendar view, the tooltip balloon shows the correct and latest data-but if you double click the item to open it, it still has the the old data. Somehow, the tooltip control is able to access the latest data before the item actually receives the changes.
The problem is that my code checks for the Start time and Subject to decide how to process the item; so when I get the Modify event and my code launches, it doesn't do anything because the two fields are still the same.
With 5 to 10 minutes eventually all the items in the calendar receive the latest data. However, at that point no event is triggered to let me process the modified entries.
What can I use in my Modify event that will force the public folder to give me the latest properties for the item?
Thanks you for your help.
William
Hi William,
I have feeling that I found some issues with my code after I posted this. It had to do with holding on to a reference of something, causing Outlook to display old data.
I'll dig it out and see if I can find out for you.
Cheers,
Adrian.
Many, ,any thanks for checking Adrian!
William
William,
I've update the code here to reflect some changes I made with regards to the way I handle references.
Also see this this link for some additional resources:
http://adriandev.blogspot.com/2008/10/vsto-memory-leaks-reference-management.html
Hello Adrian,
Thanks for updating your code.
I'm getting the following error messages when I try to run the code:
1. The best overloaded method match for 'OutlookAddIn2.CalendarMonitor.CalendarMonitor(Microsoft.Office.Interop.Outlook.Explorer)' has some invalid arguments
And
2. Argument '1': cannot convert from 'Microsoft.Office.Interop.Outlook.NameSpace' to 'Microsoft.Office.Interop.Outlook.Explorer
Sorry for my lack of knowlege but this error I have no clue about.
William
Sorry William, when I didn't update ALL the code! Try passing in this.Application.ActiveExplorer() to the CalendarMonitor constructor.
Cheers,
Adrian.
Thank you Adrian!
The updated code works beautifully Adrian. The old data issue is history. Thank you so much for your time and effort.
William
The code for the delete event handler does not work for me, but the other two yes, any clkue?
Adrian,
Thanks to your insight and willingness to share I was able to get my project working properly. I was going crazy looking for an explanation of why my code was behaving so erratically, until you pointed out the leaking problems with COM objects and the need for carefull house cleaning. That turned out to be dead-on for my particular situation. I just wanted to say thanks man! We're incredibly lucky to have someone like you show us around.
William
Well said.
Hi Adrian,
Thanks a lot for sharing your knowledge...I just copied your code as such and executed...
Appointment added and modified works fine. But when I delete the appoitment from the default calendar folder, nothing happens?
Is there any specific reason why the Appointment_Deleting is not firing..
Once again, thank you so much for your sample code which saves my time a lot...
Hi Adrian,
Thanks a lot for sharing your knowledge...I just copied your code as such and executed...
Appointment added and modified works fine. But when I delete the appoitment from the default calendar folder, nothing happens?
Is there any specific reason why the Appointment_Deleting is not firing..
Once again, thank you so much for your sample code which saves my time a lot...
Hi,
Sorry I have taken so long to reply - very busy on my current project (which has nothing to do with Outlook).
I'm not sure why the Appointment_Deleting doesn't fire. I haven't looked at this code for a while but it did work at the time. :o)
I'll have a look into it if I have a chance.
Cheers,
Adrian.
Thanks for the tip on adding the MAPI folder and items collection to lists. This properly prevented them from going out of scope. I added references to them in my private member variables in my Outlook add-in, but this didn't seem to do the trick.
Hi Adrian,
I am here again :),Appointment_Deleting is not working on default calendar,other 2 are working,any idea.
best regards
Hi Adrian,
When i run ur programme and modify an appointment,the event fires twice.Can u please check it out on your machine.
Best regards
Hello Adrian!
I am having the same problems as zainu concerning the Appointment Delete.. Any clue/hint on why this is ? Thanks for posting the code !!
Hi Adrian,
An outlook appointment can be modify through drag n drop or by simple modification. now i want to Differentiate between these two. is it possible to do tha same?
In reply to the people who are experiencing problems with the delete event. I had the same problem and discovered that the solution was not to call: Marshal.ReleaseComObject(folder);
folder = null;
in the HookupDefaultCalendarEvents method and in the Explorer_BeforeFolderSwitch event.
If anyone can explain this feel free.
Regards
Hi Buddy,
Its a very good article it works fine for outlook 2007 but not working for outlook 2003. Can you please tell me what are the procedure for outlook 2003 i m in urgent need. Thanks...
Sorry Bijaya, I am not familiar with VSTO for Office 2003.
In past my uncle had big problems with mails.And he solved their with help-outlook express repairing tool,tool fixed all data free of charge and helped to him and me too how analyze the file of dbx format and prepare temporary files for further extraction with Outlook Express repair tools.Utility helped me too very easy and effectively.
Hi My Outlook Delete event is not firing as well as for modified item it running twice any suggestions Please.
Thanks !!!!
Hi all,
Anybody found a solution regarding Outlook 2003 and the missing BeforeItemMove-event?
I searched the namespaces and on google with no luck so far. Bad luck my customer runs on office 2003 :(
Hope to hear from some one.
Kind regards,
Morten, Denmark
Hi Adrian,
Nice to learn from the particular topic. I have implemented exactly the same code in my outlook 2007 add in. I am facing some issues like:
1. In the ThisAddin monitor_AppointmentAdded and monitor_AppointmentModified events are fired multiple times. For Example If I am adding a meeting Appointment then the monitor_AppointmentAdded event fires once and after that monitor_AppointmentModified event fires multiple times. But in this case I need only monitor_AppointmentAdded should fire.
2. And the 2nd problem that i am facing is that the monitor_AppointmentDeleting event is not firing whenever I am deleting a meeting request.
Can you please give me a solution regarding the same problems so that I can implement this in my project.
Thanks a lot.
Hi Bijaya,
I'm sorry that you are having some issues with this code. Some others have pointed out issues with the deleted event as well.
However, I do not have the time nor inclination to fix this code so that it suits everyone's purposes.
I'm just trying to share some of my code and experiences with the community. This stuff was a learning experience for me too, and is the main reason I posted it. As such, it is by no means perfect.
Please take this code as it is, and if required, make changes, fixes, whatever - take it is a starting point, not a complete solution.
Cool!
*upload beer to adrian* ;-)
I have a basic question: how to view the debug output?
Thanks very much!
You can view the debug output by running your add-in through Visual Studio.
If you have an external debug viewer (I think SysInternals has one) you might be able to monitor it without Visual Studio.
Genial post and this post helped me alot in my college assignement. Gratefulness you as your information.
I have heard about a lot of tools which work with address books. But the day before yesterday I observed in the Internet a such-and-such application - wab repair tool. Which marveled me,reason of it resolved all my old problems for seconds and absolutely free as far as I kept in mind. I have been glad...
thank you bro you saved my life!
Hi
I hope this forum is still active.
Thanks for the code - but I am having an issue.
I can't get the BeforItemMove event to fire - any Idea?
I am using Office 2010
Has anyone solved this?
Im having trouble with change event firing up multiple times
any help would by great
Hi Adrian,
I have to create an add in like
Suppose i am the owner of an appointment and send the appointment/meeting request to three people.In that three people one user id compulsory to attend(i.,e our customer).Once he accepts or Declines the appointment a mail will come to me only..whereas the other two users in the appointment would not know about the status.Is there any way we can automate through any add in that other tow users also came to know that our customer has accepted/declined the meeting.
Thanks & Regards,
Suresh K
Hi Adrian,
I have to create an add in like
Suppose i am the owner of an appointment and send the appointment/meeting request to three people.In that three people one user id compulsory to attend(i.,e our customer).Once he accepts or Declines the appointment a mail will come to me only..whereas the other two users in the appointment would not know about the status.Is there any way we can automate through any add in that other tow users also came to know that our customer has accepted/declined the meeting.
Thanks & Regards,
Suresh K
Excellent code (even 5 years later!)
Many thanks !
From what I've learnt ReleaseComObject is only meant as a safe guard against the possibility that the application exits without the GC having done it's job.
Besides, when Outlook exits it does it's own releasing of all COM objects.
It is possible that the problems discussed for which ReleaseComObject was the intended solution was in fact cause by the GC collecting references to eagerly. If the GC collects the object on which the handlers are attached they won't fire.
It's possible that adding a call to ReleaseComObject at the end of a scope was enough to avoid the GC and thus mitigating the problems observed.
I'm seeing that the "Modified" event is fired twice more times, after I have effectively modified an event. I can't see why, it just get fired like 8 seconds after I modify and save, then a couple seconds after, again.
Anyone else seeing this behavior?
Adrian, I am using your code verbatim, but for some reason the Calendar_BeforeItemMove method is never getting called. I'm using it with Outlook 2013 - could this be the problem?
I am obliged to you for sharing this piece of information here and updating us with your resourceful guidance. Hope this might benefit many learners. Keep sharing this gainful articles and continue updating for us.
lg mobile service center in velachery
lg mobile service center in porur
lg mobile service center in vadapalani
I want to know more about American eagle credit card login
wonderful thanks for sharing an amazing idea. keep it...
Softgen Infotech is the Best Oracle Training institute located in BTM Layout, Bangalore providing quality training with Realtime Trainers and 100% Job Assistance.
Nice Code . Any body having issues with exchange users After getting appointment response received even if the meeting attendee is sending accepted response we are getting response none . It works fine with non exchange user .
Hi, This is a great article. Loved your efforts on it buddy. Thanks for sharing this with us.
Get cissp
it training courses.
CISSP training ,cissp exam cost, CISSP certification. .Get VMware, vmware training.,vmware course., vmware online training., vmware interview questions and answers.,vmware Certification. .AWS, aws training,aws course,aws certification training,aws online training
Get PMP pmp certification, pmp training,pmp certification in gurgaon,pmp certification cost,pmp training certification
great information!
React js training in Bangalore
Node js training in Bangalore
best angular js training in bangalore
Dot Net Training Institutes in Bangalore
I have read all the comments and suggestions posted by the visitors for this article, very nice and waiting for your next article. Thanks!
angular js training in chennai
angular js training in tambaram
full stack training in chennai
full stack training in tambaram
php training in chennai
php training in tambaram
photoshop training in chennai
photoshop training in tambaram
I have read all the comments and suggestions posted by the visitors for this article, very nice and waiting for your next article.
web designing training in chennai
web designing training in omr
digital marketing training in chennai
digital marketing training in omr
rpa training in chennai
rpa training in omr
tally training in chennai
tally training in omr
Hi it's really nice blog with new information,
Thanks to share with us,
c and c plus plus course in porur
machine learning training in chennai
machine learning training in porur
Thank you so much for sharing this pretty post, it was so good to read and useful to improve my knowledge as updated one, keep blogging.
java training in chennai
java training in velachery
aws training in chennai
aws training in velachery
python training in chennai
python training in velachery
selenium training in chennai
selenium training in velachery
Great blog and it is very useful.
web designing training in chennai
web designing training in annanagar
digital marketing training in chennai
digital marketing training in annanagar
rpa training in chennai
rpa training in annanagar
tally training in chennai
tally training in annanagar
Thanks for this great blog keep sharing.
acte chennai
acte complaints
acte reviews
acte trainer complaints
acte trainer reviews
acte velachery reviews complaints
acte tambaram reviews complaints
acte anna nagar reviews complaints
acte porur reviews complaints
acte omr reviews complaints
Informative blog. Thanks for sharing.
Python Online Training
Thanks for sharing the information.
Mobile Application Development Company in Gurgaon
Digital transformation companies in india
Very interesting article with detailed explanation. Thanks for sharing. keep up the good work Angular training in Chennai
Post a Comment