I've been looking for a way to detect changes on the attachments of an item using synchronous event receivers in SharePoint 2013 developed in C#.
The ItemAdding event is not relevant as it's not a problem if attachments are uploaded at the same moment the item is created, however, the event ItemUpdating is more relevant. Indeed, I would like to be able to update another field if one (or more) attachment is added or deleted during the synchronous event only. Asynchronous events are not an option.
I tried the solution provided here without any luck :
When an attachment gets added, Request.Files.Count is higher or equal to 1
When an attachment gets removed, it's equal to 1 if other attachments still exist on the item
When no changes are done, it's still equal to 1 if other attachments exist on the item.
Would you have any ideas how I can do it ?
Thank you in advance,
Kevin
EDIT 19 APRIL :
After further investigation, I detected that an empty file was always sent in the Request.File, so I know now the reason why Request.File.Count was always at least equal to 1.
With a small piece of code added, I can detect one or more valid files are being added :
int attachCount = 0;
HttpFileCollection fileCollection = curContext.Request.Files;
for (int j = 0; j < fileCollection.Count; j++)
{
HttpPostedFile file = fileCollection[j];
if (file.ContentLength > 0)
{
attachCount++;
}
}
Only thing left is how to detect that attachments has been removed when being in the ItemUpdating event, any ideas ?
Thank you in advance
After a lot of searching, I used the ItemUpdated event to solve the problem of the attachments deleted with the number of attachments in a dedicated field. All is working as expected. The only down side is the fact that the user might have to wait a few seconds before seeing the impact of the attachment removal.
Related
I have a bot which displays "No answers found" if the question that user has asked isn't present in the knowledge base.
I want to display a different message (e.g Contact support desk on some phone number 123456789) if the bot isn't able to answer on 3 consecutive attempts.
Something like as shown in the below image:
This is hard to answer without knowing exactly how you have QnA Maker set up, but what #JJ_Wailes suggested is the way to go. You need to keep track of the number of wrong answers, and then provide your alternative default message. To give myself more flexibility, I have always created my own default response logic instead of relying on QnA Maker. I'm going to make some assumptions that you are already good making the QnA Maker calls and dealing with user and conversation state. Instead of relying on the QnA Maker default answer, I check for the confidence score returned and create my own message activities for those cases. For simplicity I'm going to ignore prompts. Here is a sample of my typical QnA Maker flow. Unfortunately I have developed in nodejs only, but I think this should be similar enough that you can adapt for dotnet.
var MINIMUM_SCORE = 50;
const conversationData = await this.dialogState.get(context, {});
// Will use conversationData.qnaFailCount to track consecutive wrong answers
// Make the initial call
var qnaResult = await QnAServiceHelper.queryQnAService(activity.text);
var qnaAnswer = qnaResult[0].answer;
// Apply a confidence filter
if (qnaResult[0].score > MINIMUM_SCORE) {
outputActivity = MessageFactory.text(qnaAnswer);
conversationData.qnaFailCount = 0; // Reset counter when answer found
} else {
// If low confidence, increment counter
conversationData.qnaFailCount += 1;
if (conversationData.qnaFailCount < 3) {
outputActivity = MessageFactory.text(defaultAnswer);
} else {
// Send the escalation message for every consecutive "no answer" starting at 3
outputActivity = MessageFactory.text(escalation);
}
}
await this.conversationState.saveChanges(context); // Don't forget to save state!
return outputActivity;
So now you will increment the counter for each answer that is not found and reset it whenever an answer is found. In this way it will continue to give the escalation mesasge for the 4th, 5th, etc. query with no answer. Since you are using conversation state, when the user leaves the site and comes back, they will have a new conversation and the process will start over.
Note that in the event you are using this within Microsoft Teams, that is treated as one long conversation, so the message wouldn't reset even after multiple days, only with a correct answer. It seems like this is not an MS Teams use case but I wanted to mention that.
Looking for more information or at least suggestions on alternatives to EventRecordID as an index when using the Windows Event Collector.
When working with an individual server and individual Eventlog, the EventRecordID element can be used as an index to keep your place when crawling through events in order.
However, when using the Windows Event Collector, the events retain their original EventRecordID in the ForwardedEvents log. That makes it difficult at best to keep track of where you were when crawling through events with a script/program.
The date/timestamp doesn't help either, as events can come in from other systems after you have moved past a given date/time.
Does anyone have any suggestions on a way to track, bookmark, or index events in ForwardedEvents?
You can use the BookmarkID
See how to get it with the Microsoft example in C++ here
or like I did with C#
EventLogQuery eventsQuery = new EventLogQuery("ForwardedEvents", PathType.LogName);
EventLogReader logReader = new EventLogReader(eventsQuery);
EventRecord myevent = logReader.ReadEvent();
string bookmark = ReflectionHelper.GetPropertyValue(myevent.Bookmark, "BookmarkText").ToString();
ReflectionHelper is not from me. Source here
I have an addin that synchronized the contacts folder with an outside source. The Sync happens daily (or manually on demand) and takes a while. Users have asked for the addin to provide information about the sync so that they know it completed successfully, etc.
Since the Outlook API doesn't provide a way to add information to the status bar (i.e. details about the sync as it is happening), I would like to create a log file automatically each sync (and stick it in the Deleted Items folder so that it is out of the way).
When I tried to create a message and .Move() it to the deleted items folder, it appeared there, but without a Received time and so was sorted to the end of the list and hard to find. Also, it looks to the user like an unsent message (a draft).
Is there a way to create a message and have the Received time be set to approx the time the message was created (the property is read-only)?
NameSpace mapi = _outlook.GetNamespace("MAPI");
MAPIFolder deletedItems = mapi.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);
MailItem message = (MailItem)_outlook.CreateItem(OlItemType.olMailItem);
message.Subject = "Contact Sync Errors";
message.BodyFormat = OlBodyFormat.olFormatPlain;
message.Body = "This is my log message";
message.Move(deletedItems);
This is what I ended up doing. I used a Post instead of a Message because that worked better.
PostItem message = (PostItem)this.Application.CreateItem(OlItemType.olPostItem);
message.Subject = "Contact Sync Log";
message.BodyFormat = OlBodyFormat.olFormatPlain;
message.Body = "My Message Here";
message.Post();
message.Delete();
The Post is created, filled out with details, "Posted" so that it has valid timestamps, and then immediately deleted (because I wanted it in the Deleted Items folder). Had I not deleted it, it would have been in the Inbox folder.
Currently developing a connector DLL to HP's Quality Center. I'm using their (insert expelative) COM API to connect to the server. An Interop wrapper gets created automatically by VStudio.
My solution has 2 projects: the DLL and a tester application - essentially a form with buttons that call functions in the DLL. Everything works well - I can create defects, update them and delete them. When I close the main form, the application stops nicely.
But when I call a function that returns a list of all available projects (to fill a combo box), if I close the main form, VStudio still shows the solution as running and I have to stop it.
I've managed to pinpoint a single function in my code that when I call, the solution remains "hung" and if I don't, it closes well. It's a call to a property in the TDC object get_VisibleProjects that returns a List (not the .Net one, but a type in the COM library) - I just iterate over it and return a proper list (that I later use to fill the combo box):
public List<string> GetAvailableProjects()
{
List<string> projects = new List<string>();
foreach (string project in this.tdc.get_VisibleProjects(qcDomain))
{
projects.Add(project);
}
return projects;
}
My assumption is that something gets retained in memory. If I run the EXE outside of VStudio it closes - but who knows what gets left behind in memory?
My question is - how do I get rid of whatever calling this property returns? Shouldn't the GC handle this? Do I need to delve into pointers?
Things I've tried:
getting the list into a variable and setting it to null at the end of the function
Adding a destructor to the class and nulling the tdc object
Stepping through the tester function application all the way out, whne the form closes and the Main function ends - it closes, but VStudio still shows I'm running.
Thanks for your assistance!
Try to add these 2 lines to post-build event:
call "$(DevEnvDir)..\Tools\vsvars32.bat"
editbin.exe /NXCOMPAT:NO "$(TargetPath)"
Have you tried manually releasing the List object using System.Runtime.InteropServices.Marshal.ReleaseComObject when you are finished with it ?
I suspect some dangling threads.
When this happens, pause the process in the debugger and see what threads are still around.
May be try to iterate the list manually using it's count and Item properties instead of using it's iterator, some thing like:
for (int i=1; i <= lst.Count ; ++i)
{
string projectName = lst.Item(i);
}
It might be the Iterator that keeps it alive and not the list object itself, if not using an iterator might not have a problem.
I'm using 20.20.1089, and I'm getting an exception sometime while looping over
like this
foreach (TableRow row in table.TableRows)
{
for (int i = 0; i < row.OwnTableCells.Count ; ++i)
{
// EXCEPTION INDEX OUT OF RANGE
TableCell cell = row.OwnTableCells [i];
}
}
Under the debugger, row.OwnTableCells.Count is 0, but the loop index i has been
increments. I can get Exception:Object reference not set to an instance of an
object.
This happens sometimes only, which is the puzzling part.The webpage is always refreshing automatically after a few seconds. Could this
be the cause of it? Is there a way to disable the cache?
Robin
PS. I'm trying to get this posted on Watin mailing list but it seems the moderator is gone missing, and not approving any new registrations.
Hi your mail was send on the mailing list as well, but for record sake I'll post my answer here as well.
The webpage is always refreshing automatically after a few seconds. Could this
be the cause of it?
This indeed is your problem. Following one of the possible scenarios to show you how things can go wrong:
1 - calling row.OwnTableCells.Count => returns the number of elements currently on the page
2 - refresh timer triggers page refresh => page reloaded
3 - due to refresh the row.OwnTableCells collection will return no elements any more since the row which was referenced no longer exists on the page. You might still see the same row in the browser, but that is a new DOM row object created after the page refresh. In your code you/WatiN is still referencing the old row object.
So you should change your logic to take this refresh of the page into account.
HtH,
Jeroen