CRM - Internal Server Error: when working on multiple entities - dynamics-crm

So I have a custom action (an approval button) that can be triggered either when editing a single entity or for multiple entities when triggered from the entity list view.
The logic essentially removes one entity type and creates a different one with more fields and options available after approval.
The logic works just fine when it is triggered from the Form where there is just a single entity being edited.
However when the same logic is run from the list view and now I am iterating over multiple entities, there is in internal server error when I try to create the record for the new entity type (the one with more options). This makes no sense, I am calling out to a function which already works under a different scenario. And I am creating a new entity, not updating or deleting an existing one so there should be not locks or other concurrency issues.
The the error is gloriously uninformative and I can't see any logs anywhere that would help me debug this. Has anyone run into this before?
Edit
I have enabled CRM Trace Logging for errors (in the registry), however this does not help. This internal server 'Error' does not seem to be errory enough to show up in the logs.
Edit 2
Perhaps some code? The error happens on the SDK.REST.createRecord line, but only when it is run inside the loop of the click handler, when it is run from single entity form, it creates a record without issue.
PCL.EventParticipants = {
EventFormApproveParticipantClick: function (selectedItemIds, entityTypeName) {
debugger;
var anEventRequest,
requestId,
action,
event,
contact,
emailPref,
actualEmail;
console.log('Approval Clicked');
// Do this if we are working on a single event request
if (entityTypeName == null)
{
requestId = Xrm.Page.data.entity.getId();
action = Xrm.Page.data.entity.attributes.get("pcl_action").getValue();
var participant = PCL.EventParticipants.MakeParticipant(
Xrm.Page.data.entity.attributes.get("pcl_contact").getValue()[0].id,
Xrm.Page.data.entity.attributes.get("pcl_event").getValue()[0].id,
Xrm.Page.data.entity.attributes.get("pcl_name").getValue(),
Xrm.Page.data.entity.attributes.get("pcl_emailpreference").getValue(),
Xrm.Page.data.entity.attributes.get("pcl_selectedemail").getValue()
);
if (PCL.EventParticipants.Act(requestId, action, participant)) {
alert('Approval complete.');
}
return;
}
var opSuccess = true;
// When multiple requests are selected do...
for (var x = 0; x < selectedItemIds.length; x++) {
requestId = selectedItemIds[x];
SDK.REST.retrieveRecord(
requestId,
"pcl_eventrequest",
"pcl_eventrequestId,pcl_Action,pcl_Contact,pcl_Event,pcl_name,pcl_EmailPreference,pcl_SelectedEmail", null,
function (anEventRequest) {
requestId = anEventRequest.pcl_eventrequestId;
action = anEventRequest.pcl_Action.Value;
var participant = PCL.EventParticipants.MakeParticipant(
anEventRequest.pcl_Contact.Id,
anEventRequest.pcl_Event.Id,
anEventRequest.pcl_name,
anEventRequest.pcl_EmailPreference,
anEventRequest.pcl_SelectedEmail
);
if (!PCL.EventParticipants.Act(requestId, action, participant)) {
opSuccess = false;
}
},
function(error) {
alert('Could not retrieve selected event request: ' + requestId + ' Check that it has not been removed from the system. --> ' + error.message);
}, false
);
}
if (opSuccess) {
alert('Approvals completed.');
} else {
alert('One or more Approvals failed.');
}
},
Act: function (requestId, actionValue, participant) {
var opSuccess = false;
if (actionValue == '798330000') {
// Add action
opSuccess = PCL.EventParticipants.CreateEventParticipant(participant);
}
if (actionValue == '798330001') {
// Remove action
opSuccess = PCL.EventParticipants.RemoveEventParticipant(participant);
}
if (opSuccess == false) {
return opSuccess;
}
opSuccess = PCL.EventParticipants.RemoveParticipantRequest(requestId);
return opSuccess
},
CreateEventParticipant: function (eventParticipant) {
var existingParticipant = PCL.EventParticipants.RetrieveEventParticipantLike(eventParticipant.pcl_Event.Id, eventParticipant.pcl_Contact.Id);
if (existingParticipant != null) {
alert('Cannot approve this request. This contact is already participating in the selected event.');
return false;
}
var opSuccess = false;
SDK.REST.createRecord(
eventParticipant,
"pcl_eventparticipant",
function (result) {
opSuccess = true;
},
function(error) {
alert('Could not create event request with contactId: ' + eventParticipant.pcl_Contact.Id + ' and eventId: ' + eventParticipant.pcl_Event.Id + '. --> ' + error.message);
}, false
);
return opSuccess;
}, .....
}
Edit 3
I have modified the SDK.REST to have a 5th parameter which toggles whether or not the operation is synchronous or asynchronous. Passing false at the end of any operation makes the operation synchronous.

Related

AJAX call not hitting breakpoints in Async handler

I wrote a quick AJAX script to be called on a button press event which in turn invokes an asysnc handler to pull data from remote APIs. I modified that same script to invoke another handler that was not async and it works fine, I'm not sure why it's not hitting breakpoints in Visual Studio. Here's the AJAX script.
$("#RunNewShodanQuery").click(function (d) {
$.ajax(
{
type: "POST",
async: true,
url: "/Tools/Test?handler=UpdateResultsAsync",
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
error: function (jqXHR, exception) {
var msg = '';
if (jqXHR.status === 0) {
msg = 'Not connect.\n Verify Network.';
} else if (jqXHR.status == 404) {
msg = 'Requested page not found. [404]';
} else if (jqXHR.status == 500) {
msg = 'Internal Server Error [500].';
} else if (exception === 'parsererror') {
msg = 'Requested JSON parse failed.';
} else if (exception === 'timeout') {
msg = 'Time out error.';
} else if (exception === 'abort') {
msg = 'Ajax request aborted.';
} else {
msg = 'Uncaught Error.\n' + jqXHR.responseText;
}
console.log(msg);
},
complete: function (res) {
console.log(res);
}
});
})
Here's the handler in question.
public async Task OnPostUpdateResultsAsync()
{
ModelState.Clear();
foreach (Entry e in _context.Entries)
{
// TBI
}
// Update Date of Last Scan so as not to make needless API calls spamming refreshes
DateOfLastScan = DateTime.Now;
// Dispose of the client once we're done
client.Dispose();
}
I've placed breakpoints in another test handler and modified the above AJAX with a URL to point to the new test handler and VS stops on breakpoints within that handler.
public void OnPostTestHandler()
{
int seven = 5;
}
I'm currently at a loss as to why Visual Studio isn't hitting breakpoints in the async handler. From the browser, I'm seeing entries return with status 200 and it appears that it is executing the handler code just not stopping in it. Any suggestions would be very welcome.
By convention, the name of the handler method is selected based the value of the handler parameter according to the scheme OnPost[handler]Async.
Which means that, for OnPostUpdateResultsAsync, the handler name is UpdateResults instead of UpdateResultsAsync.
For Razor page, PageActionInvoker will call DefaultPageHandlerMethodSelector.SelectHandlers to select the handler.
private List<HandlerMethodDescriptor> SelectHandlers(PageContext context)
{
var handlers = context.ActionDescriptor.HandlerMethods;
var candidates = new List<HandlerMethodDescriptor>();
// Name is optional, may not be provided.
var handlerName = GetHandlerName(context);
// The handler selection process considers handlers according to a few criteria. Handlers
// have a defined HTTP method that they handle, and also optionally a 'name'.
//
// We don't really have a scenario for handler methods without a verb (we don't provide a way
// to create one). If we see one, it will just never match.
//
// The verb must match (with some fuzzy matching) and the handler name must match if
// there is one.
//
// The process is like this:
//
// 1. Match the possible candidates on HTTP method
// 1a. **Added in 2.1** if no candidates matched in 1, then do *fuzzy matching*
// 2. Match the candidates from 1 or 1a on handler name.
// Step 1: match on HTTP method.
var httpMethod = context.HttpContext.Request.Method;
for (var i = 0; i < handlers.Count; i++)
{
var handler = handlers[i];
if (handler.HttpMethod != null &&
string.Equals(handler.HttpMethod, httpMethod, StringComparison.OrdinalIgnoreCase))
{
candidates.Add(handler);
}
}
// Step 1a: do fuzzy HTTP method matching if needed.
if (candidates.Count == 0 && AllowFuzzyHttpMethodMatching)
{
var fuzzyHttpMethod = GetFuzzyMatchHttpMethod(context);
if (fuzzyHttpMethod != null)
{
for (var i = 0; i < handlers.Count; i++)
{
var handler = handlers[i];
if (handler.HttpMethod != null &&
string.Equals(handler.HttpMethod, fuzzyHttpMethod, StringComparison.OrdinalIgnoreCase))
{
candidates.Add(handler);
}
}
}
}
// Step 2: remove candidates with non-matching handlers.
for (var i = candidates.Count - 1; i >= 0; i--)
{
var handler = candidates[i];
if (handler.Name != null &&
!handler.Name.Equals(handlerName, StringComparison.OrdinalIgnoreCase))
{
candidates.RemoveAt(i);
}
}
return candidates;
}

failing to reset language selection after sync

I am facing a problem which I am not aware how to resolve. Let me describe elaborately below:
I have a commonViewModel kendo class where event like save, cancel are written. I am facing problem with the save event of this class.
save: function () {
var that = this;
var routeLanguage = "";
that._showBackConfirmation(false);
that.set("isFormSubmitted", true);
console.log("form is valid, sending the save request!");
if (vm.get("languageTabsVm.selectedLanguage")) {
routeLanguage = "/" + vm.get("languageTabsVm.selectedLanguage");
}
else if (that.get("model.Languages") && that.get("model.Languages").length > 1) {
that.get("model.Languages").forEach(function (lang) {
if (lang.get("IsActive") === true) {
//sätt cv-visning till det språk jag senast redigerade på detta item
routeLanguage = "/" + lang.LanguageId;
}
});
}
//if i call the function _loadDefaultLanguageSelection here, it
// works. because, the datasource is not synced yet.
//Make sure the datasource are syncing changes to the server (includes all crud)
return that.dataSource.sync().fail(function (e) {
//i need to do something here to be in the same language tab. But
//as i am changing directly in to model, it is not possible. But
//saving directly to model is essential because that model is
//shared to other viewmodel for language tab synching purpose.
that.set("isFormSubmitted", false);
console.log("form rejected");
}).done(function () {
if (that.get("isPersonaldetail")) {
var name = that.get("model.Name");
if (name.length > 12)
name = name.substring(0, 11) + "...";
$("#profileName").text(name);
}
that.set("isFormSubmitted", false);
that.set("isSelected", false);
// it is called from here right now. but it is failing because
// model is updated but not synced in that function
that._loadDefaultLanguageSelection();
router.navigate(that.nextRoute + routeLanguage);
});
},
_loadDefaultLanguageSelection: function () {
var that = this;
if (that.get("model.Languages") && that.get("model.Languages").length > 1) {
that.get("model.Languages").forEach(function (lang) {
if (!that.get("isPersonaldetail")) {
lang.set("IsActive", lang.get("LanguageId") === vm.get("languageTabsVm.selectedLanguage"));
}
});
}
},
So, my question is, how can i resolve this problem. one solution is i will have to sync twice. that is not nice. So, I am looking for efficient solution.

Sails.js model method (non-instance)

I've been working on this app for a while. I have several other modules that all work fine. I've been having a ton of trouble with this particular module and it's super frustrating. This problem looks super simple. Maybe I'm over thinking it. Hopefully someone will say that I am. :)
In this module, I decided to use methods from my model. This particular one is non-instanced. Here is my model:
/*
* Account.js
*/
module.exports = {
connection: 'islMongo',
attributes: {
name: {
type: 'string',
required: true,
},
},
numberToName: function(accountNumber) {
Account.findOne(accountNumber).exec(function(err, a){
if (err) {
return 'err';
} else {
return 'ok';
}
});
return 'broke';
},
};
I call it from one of my controllers like this:
var accountName = Account.numberToName(params.id);
At this point accountName's value is "broke". I don't understand why it wouldn't either return "err" or "ok". I simplified my actual function here for testing.
Edit:
I have other calls that work properly. For instance:
updateBalance: function(account, amount, callback) {
/* Accepts account id or account object */
(function _lookupAccount(afterLookup) {
if (typeof account === 'object') return afterLookup(null, account);
Account.findOne(account)
.exec(afterLookup);
})(function (err, a) {
if (err) return callback(err);
if (!a) {
err = new Error();
err.message = "Couldn't find account.";
err.status = 400;
return callback(err);
}
a.balance = parseInt(a.balance) + parseInt(amount);
a.save(callback);
});
},
Is called like this:
Account.updateBalance(params.account, -2000);
The definition has a callback, but I don't actually use one because it isn't needed. The method works fine.
Sails.js documentation provides example methods that don't use callbacks. They simply return the requested data.
// Attribute methods
getFullName: function (){
return this.firstName + ' ' + this.lastName;
},
isMarried: function () {
return !!this.spouse;
},
isEligibleForSocialSecurity: function (){
return this.age >= 65;
},
encryptPassword: function () {
}
And called like this:
if ( rick.isMarried() ) {
// ...
}
Which is what I am trying to do with my method at the top of this post. It seems like the exec() portion of Account.findOne() isn't even being called.
Sails.js & Node.js are asynchronous. So in simple words they don't wait for response from database, but when they got date they call a callback. So you need to read about Queries and callbacks and what is callback hell (you should never do that).
And now get back to your problem.
/*
Account.js
*/
//...
numberToName: function(accountNumber, callback) {
// if you want some additional logic you can create function here and call callback in it
Account.findOne(accountNumber).exec(callback);
}
//...
Tip: callbacks first param is always error.
// AccountController
method: function(req, res){
var id = req.param('id'); // if its int you should parseInt()
var callback = function(error, account){
if(error)
res.send('error');
else
res.send(account.name);
};
Account.numberToName(id, callback);
}

Why my extension sends duplicates on request in geometric progression?

I've created extension that makes some JSON request & send it to some receiver.
My problem is:
Open popup window
After it closing, extensions sends 1 request
Open it on the same page again, and extension will send 2 requests
Open again, 4 requests
Open again, 8 requests
In each uses of popup, extension will be duplicate outgoing data in geometric progression.
Why that happens?
From the panel I'm send addnewurl to the port:
AddDialog.port.on("addnewurl", function (data) {
{
AddDialog is my popup
here It handle port messages aftre popup is closed(hidded)
}
var http = require("sdk/request").Request;
var req = CreateRequest("add_url", {});
req.params = {...};
var sreq = encodeURIComponent(JSON.stringify(req));
count += 1; //Global counter, u will see it in video
console.log('count = '+count);
var cfg = {
url : getRequestURL(),
contentType : "text/html",
content : sreq,
onComplete : function (response) {
var data = {
code : response.status,
body : response.json
};
AddDialog.port.emit("addnewurldone", data);
}
};
http(cfg).post();
});
For more sense I've created a AVI video record of that. See it here:
https://dl.dropboxusercontent.com/u/86175609/Project002.avi
1.6 MB
How to resolve that?
ADDED by request more info
That function emit addnewurl:
function AddNewURL() {
var node = $("#Tree").dynatree("getActiveNode");
if (node == null) {
$("#ServerStatus").text(LocalizedStr.Status_NoGroupSelected);
$("#ServerStatus").css("color", "red");
return;
};
var nkey = node.data.key;
var aImg = null;
var data = {
ownerId : nkey,
name : $("#LinkTitle").val(),
description : $("#LinkDesc").val(),
url : $("#CurrentURL").val(),
scrcapt:$("#ScrCaptureCB :selected").val()
};
$("#load").css("display", "inline");
$("#ServerStatus").text(LocalizedStr.Status_AddURL);
self.port.emit("addnewurl", data);
};
and it calls by button:
self.port.on("showme", function onShow(data) {
....
document.querySelector('#BtnOk').addEventListener('click', function () {
AddNewURL();
});
...
});
"swomme" goes from here(main.js):
AddDialog.on("show", function () {
count = 0;
AddDialog.port.emit("showme", locTbl);
});
function addToolbarButton() {
var enumerator = mediator.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
var document = enumerator.getNext().document;
var navBar = document.getElementById('nav-bar');
if (!navBar) {
return;
}
var btn = document.createElement('toolbarbutton');
btn.setAttribute('id', cBtnId);
btn.setAttribute('type', 'button');
btn.setAttribute('class', 'FLAToolButton');
btn.setAttribute('image', data.url('icons/Add.png'));
btn.setAttribute('orient', 'horizontal');
btn.setAttribute('label', loc("Main_ContextMenu"));
btn.addEventListener('click', function () {
AddDialog.show();
}, false)
navBar.appendChild(btn);
}
}
I think the problem is here
document.querySelector('#BtnOk').addEventListener('click', function () {
AddNewURL();
});
If you are running AddDialog.port.emit("showme", locTbl); when you click your toolbar button then you're adding a click listener to #BtnOk every time as well.
On the first toolbar click it will have one click listener, on the second click two, and so on. You should remove the above code from that function and only run it once.

AngularJs 2 promises inside a watch the second one never works

I have 2 lists in my application and the user is supposed to drag and drop items from one list to another.
When the user drops an element from one of the lists to the other list a request has to be made to the server side code to update a field in the database (SelectedForDiscussion).
This is the code in my controller:
$scope.$watch("questionsDiscuss", function (value) {
var question = $.Enumerable.From($scope.questionsDiscuss).Where(function (item) { return !item.SelectedForDiscussion }).FirstOrDefault()
if (question != undefined) {
questionSelectionService.UpdateQuestionSelectionStatus(question.Id, true)
.then(function (output) {
var question = $.Enumerable.From($scope.questionsDiscuss)
.Where(function (item) { return item.Id == output.data.questionId })
.FirstOrDefault();
var index = $.Enumerable.From($scope.questionsDiscuss).IndexOf(question);
if (question != undefined)
if (output.data.result != "success") {
$scope.questionsDiscuss.splice(index, 1);
$scope.questionsReceived.splice(0, 0, question);
}
else {
question.SelectedForDiscussion = true;
$scope.questionsDiscuss[index] = question;
}
});
}
else {
var question = $.Enumerable.From($scope.questionsReceived).Where(function (item) { return item.SelectedForDiscussion }).FirstOrDefault();
if (question != undefined) {
questionSelectionService.UpdateQuestionSelectionStatus(question.Id, false)
.then(function (output) {
var question = $.Enumerable.From($scope.questionsReceived)
.Where(function (item) { return item.Id == output.data.questionId })
.FirstOrDefault();
var index = $.Enumerable.From($scope.questionsReceived).IndexOf(question);
if (question != undefined)
if (output.data.result != "success") {
$scope.questionsReceived.splice(index, 1);
$scope.questionsDiscuss.splice(0, 0, question);
}
else {
question.SelectedForDiscussion = false;
$scope.questionsReceived[index] = question;
}
});
}
}
}, true);
I have 4 javascript breakpoint placed at the following lines within Firebug:
2 of them at the following lines:
if (question != undefined) {
One at:
var question = $.Enumerable.From($scope.questionsDiscuss)
.Where(function (item) {
return item.Id == output.data.questionId
})
.FirstOrDefault();
And the other at:
var question = $.Enumerable.From($scope.questionsReceived)
.Where(function (item) {
return item.Id == output.data.questionId
})
.FirstOrDefault();
The following happens:
The breakpoints at:
if (question != undefined) {
are always reached.
The breakpoint at
var question = $.Enumerable.From($scope.questionsDiscuss)
.Where(function (item) {
return item.Id == output.data.questionId
})
.FirstOrDefault();
is also reached.
The other is never reached.
Both responses are OK(response code 200).
Everything should work perfectly but the then clause in the second promise is never reached.
Can anyone tell me what I am doing wrong?
The serverside appplication is an ASP.NET MVC application written in C#.
Edit 1:
I figured out why this was happening and I have a work around for it. I am stil interested in an actual solution.
The problem is angularjs throws an error then swallows it when calling $http for the second time. The error is:
digest alredy in progress
I think this is because in my directive I have this code:
dndfunc = function (scope, element, attrs) {
// contains the args for this component
var args = attrs.dndBetweenList.split(',');
// contains the args for the target
var targetArgs = $('#' + args[1]).attr('dnd-between-list').split(',');
// variables used for dnd
var toUpdate;
var target;
var startIndex = -1;
// watch the model, so we always know what element
// is at a specific position
scope.$watch(args[0], function (value) {
toUpdate = value;
}, true);
// also watch for changes in the target list
scope.$watch(targetArgs[0], function (value) {
target = value;
}, true);
// use jquery to make the element sortable (dnd). This is called
// when the element is rendered
$(element[0]).sortable({
items: 'div',
start: function (event, ui) {
// on start we define where the item is dragged from
startIndex = ($(ui.item).index());
},
stop: function (event, ui) {
var newParent = ui.item[0].parentNode.id;
// on stop we determine the new index of the
// item and store it there
var newIndex = ($(ui.item).index());
var toMove = toUpdate[startIndex];
// we need to remove him from the configured model
toUpdate.splice(startIndex, 1);
if (newParent == args[1]) {
// and add it to the linked list
target.splice(newIndex, 0, toMove);
} else {
toUpdate.splice(newIndex, 0, toMove);
}
// we move items in the array, if we want
// to trigger an update in angular use $apply()
// since we're outside angulars lifecycle
scope.$apply(targetArgs[0]);
scope.$apply(args[0]);
},
connectWith: '#' + args[1]
})
}
And there are 2 calls to apply at the end which trigger a new digest cycle I think.
Anyway I fixed it by adding this call before the calls to apply:
if (scope.updateLists != undefined)
scope.updateLists();
And moved all the code from the watch into the updateLists function.
Also because people have mentioned the service as having something to do with it I am pasting the relevant code within it:
GetQuestionsReceived: function (eid, criteria, page, rows) {
var promise = this.GetQuestionsReceivedInternal(eid,criteria, page, rows).then(function (response) {
// The return value gets picked up by the then in the controller.
return response;
});
// Return the promise to the controller
return promise;
},
GetQuestionsReceivedInternal: function (eid, criteria, page, rows) {
return $http({ method: 'GET',
url: '../QuestionManagement/GetQuestions?eventId='+eid+'&page=1&rows=5&'+serialize(criteria)
}).
success(function (data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
results = data;
}).
error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
if (window.console && console.log) {
console.log("Could not obtain questions received. Error:" + data + "Status:" + status + "Headers:" + headers + "Config:" + config);
}
});
},
GetQuestionsDiscuss: function (eid,criteria, page, rows) {
var promise = this.GetQuestionsDiscussInternal(eid,criteria, page, rows).then(function (response) {
// The return value gets picked up by the then in the controller.
return response;
});
// Return the promise to the controller
return promise;
},
GetQuestionsDiscussInternal: function (eid,criteria, page, rows) {
return $http({ method: 'GET',
url: '../QuestionManagement/GetQuestions?eventId=' + eid + '&page=1&rows=5&' + serialize(criteria)
}).
success(function (data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
response = data;
}).
error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
if (window.console && console.log) {
console.log("Could not obtain questions received. Error:" + data + "Status:" + status + "Headers:" + headers + "Config:" + config);
}
});
},
You have two very similar blocks of code, which could be generalized and placed in a function wrapper, leaving behind a very simple calling function.
If you can get everything into that form, then I think you will find it easier to debug.
Here is an attempt to do so :
function updateSelectionStatus(qA, qB, bool) {
var en = $.Enumerable.From(qA);
var question = en.Where(function (item) {
return bool ? !item.SelectedForDiscussion : item.SelectedForDiscussion;
}).FirstOrDefault();
if(question) {
questionSelectionService.UpdateQuestionSelectionStatus(question.Id, bool).then(function (output) {
if (output.data.result == "success") {
question.SelectedForDiscussion = bool;
}
else {
qA.splice(en.IndexOf(question), 1);
qB.unshift(question);
}
});
}
return question;
}
$scope.$watch("questionsDiscuss", function (value) {
if (!updateSelectionStatus($scope.questionsDiscuss, $scope.questionsReceived, true) {
updateSelectionStatus($scope.questionsReceived, $scope.questionsDiscuss, false);
}
}, true);
I may have made some false assumptions and simplified too much (eg. purging the inner $.Enumerable.From, which appears to reselect the same question as the outer), so you may well need to rework my code.
I'm advocating a principle here, rather than offering a solution.

Resources