Reputation: 81
I am attempting to implement the Search Resource Capability as described here: https://cloudblogs.microsoft.com/dynamics365/it/2019/05/21/retrieve-resource-availability-with-universal-resource-scheduling-api/
There is an example here of how to implement it via JavaScript (although the JavaScript libraries are probably deprecated or unsupported), which I have referenced here: https://cloudblogs.microsoft.com/dynamics365/it/2019/07/15/how-to-use-resource-schedulings-search-resource-availability-api/
I have written a .NET Core Class Library that uses the Dynamics 365 OData Service to POST to the msdyn_SearchResourceAvailability Action.
I have seen some examples on the internet, but they all use the Dynamics 365 SDK, not the Dynamics 365 Web API.
I am getting an error and have therefore extracted the JSON that is being posted and tried the same call in Postman, where I am getting the same error:
{
"error": {
"code": "0x0",
"message": "An error occurred while validating input parameters: Microsoft.OData.ODataException: Does not support untyped value in non-open type.\r\n at System.Web.OData.Formatter.Deserialization.DeserializationHelpers.ApplyProperty(ODataProperty property, IEdmStructuredTypeReference resourceType, Object resource, ODataDeserializerProvider deserializerProvider, ODataDeserializerContext readContext)\r\n at System.Web.OData.Formatter.Deserialization.ODataResourceDeserializer.ApplyStructuralProperties(Object resource, ODataResourceWrapper resourceWrapper, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext)\r\n at Microsoft.Crm.Extensibility.CrmODataEntityDeserializer.ApplyStructuralProperties(Object resource, ODataResourceWrapper resourceWrapper, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext)\r\n at System.Web.OData.Formatter.Deserialization.ODataResourceDeserializer.ReadResource(ODataResourceWrapper resourceWrapper, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext)\r\n at Microsoft.Crm.Extensibility.ODataV4.CrmODataActionPayloadDeserializer.ReadEntry(ODataDeserializerContext readContext, ODataParameterReader reader, IEdmOperationParameter parameter)\r\n at Microsoft.Crm.Extensibility.ODataV4.CrmODataActionPayloadDeserializer.Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.ReadFromStream(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)",
"innererror": {
"message": "An error occurred while validating input parameters: Microsoft.OData.ODataException: Does not support untyped value in non-open type.\r\n at System.Web.OData.Formatter.Deserialization.DeserializationHelpers.ApplyProperty(ODataProperty property, IEdmStructuredTypeReference resourceType, Object resource, ODataDeserializerProvider deserializerProvider, ODataDeserializerContext readContext)\r\n at System.Web.OData.Formatter.Deserialization.ODataResourceDeserializer.ApplyStructuralProperties(Object resource, ODataResourceWrapper resourceWrapper, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext)\r\n at Microsoft.Crm.Extensibility.CrmODataEntityDeserializer.ApplyStructuralProperties(Object resource, ODataResourceWrapper resourceWrapper, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext)\r\n at System.Web.OData.Formatter.Deserialization.ODataResourceDeserializer.ReadResource(ODataResourceWrapper resourceWrapper, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext)\r\n at Microsoft.Crm.Extensibility.ODataV4.CrmODataActionPayloadDeserializer.ReadEntry(ODataDeserializerContext readContext, ODataParameterReader reader, IEdmOperationParameter parameter)\r\n at Microsoft.Crm.Extensibility.ODataV4.CrmODataActionPayloadDeserializer.Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.ReadFromStream(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)",
"type": "Microsoft.Crm.CrmHttpException",
"stacktrace": " at Microsoft.Crm.Extensibility.OData.CrmODataUtilities.ValidateInputParameters(ModelStateDictionary controllerModelState)\r\n at Microsoft.Crm.Extensibility.OData.ActionController.<>c__DisplayClass9_0.<PostUnboundAction>b__0()\r\n at Microsoft.PowerApps.CoreFramework.ActivityLoggerExtensions.Execute[TResult](ILogger logger, EventId eventId, ActivityType activityType, Func`1 func, IEnumerable`1 additionalCustomProperties)\r\n at Microsoft.Xrm.Telemetry.XrmTelemetryExtensions.Execute[TResult](ILogger logger, XrmTelemetryActivityType activityType, Func`1 func)\r\n at lambda_method(Closure , Object , Object[] )\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
}
}
}
The JSON that is being posted is as follows:
{
"Version": "1",
"Requirement": {
"msdyn_duration": 180,
"msdyn_effort": 1,
"msdyn_fromdate": "2020-03-10T00:00:00+00:00",
"msdyn_latitude": 55.784129,
"msdyn_longitude": -3.982742,
"msdyn_name": "Super Heroes Resource Requirement",
"msdyn_remainingduration": 180,
"msdyn_todate": "2020-03-12T00:00:00+00:00",
"msdyn_worklocation": 690970002
},
"Settings": {
"ConsiderSlotsWithLessThanRequiredCapacity": false,
"ConsiderSlotsWithLessThanRequiredDuration": false,
"ConsiderTravelTime": false,
"ConsiderSlotsWithOverlappingBooking": false,
"ConsiderSlotsWithProposedBookings": false,
"MovePastStartDateToCurrentDate": false,
"UseRealTimeResourceLocation": false,
"MaxResourceTravelRadius": {
"Value": 10,
"Unit": 192350000
},
"SortOrder": {
"value": [
{
"Name": "bookableresource",
"SortOrder": 0
}
]
}
},
"ResourceSpecification": {
"ResourceTypes": {
"value": [
2,
3,
5
]
},
"PreferredResources": {
"value": [
{
"bookableresourceid": "d7315245-b162-ea11-a811-000d3a0bad7c"
},
{
"bookableresourceid": "b54bc744-b162-ea11-a811-000d3a0ba110"
}
]
},
"RestrictedResources": {
"value": [
{
"bookableresourceid": "ba6d4a4b-b162-ea11-a811-000d3a0bad7c"
},
{
"bookableresourceid": "ca6d4a4b-b162-ea11-a811-000d3a0bad7c"
}
]
},
"Constraints": {
"Characteristics": {
"value": [
{
"characteristicid": "a02db73e-b162-ea11-a811-000d3a0ba110"
}
]
},
"Roles": {
"value": [
{
"bookableresourcecategoryid": "d56d4a4b-b162-ea11-a811-000d3a0bad7c"
}
]
},
"Territories": {
"value": [
{
"territoryid": "7c2db73e-b162-ea11-a811-000d3a0ba110"
}
]
},
"UnspecifiedTerritory": false,
"OrganizationalUnits": {
"value": [
{
"msdyn_organizationalunitid": "822db73e-b162-ea11-a811-000d3a0ba110"
}
]
},
"BusinessUnits": {
"value": [
{
"businessunitid": "fba6cf4b-f24a-ea11-a813-00224801cd21"
}
]
}
}
}
}
Could anyone please advise where I am going wrong?
Upvotes: 3
Views: 3521
Reputation: 308
You need to specify a couple of things:
You should now be able to call msdyn_SearchResourceAvailability via Web API.
Key Tasks:
Sample:
{
"Version": "3",
"IsWebApi": true,
"Requirement": {
"msdyn_fromdate": "2021-08-17T00:00:00Z",
"msdyn_todate": "2021-08-18T23:59:00Z",
"msdyn_remainingduration": 60,
"msdyn_duration": 60,
"@odata.type": "Microsoft.Dynamics.CRM.msdyn_resourcerequirement"
},
"Settings": {
"ConsiderSlotsWithProposedBookings": false,
"MovePastStartDateToCurrentDate": true,
"@odata.type": "Microsoft.Dynamics.CRM.expando"
},
"ResourceSpecification": {
"@odata.type": "Microsoft.Dynamics.CRM.expando",
"[email protected]": "Collection(Microsoft.Dynamics.CRM.expando)",
"ResourceTypes": [
{
"@odata.type": "Microsoft.Dynamics.CRM.expando",
"value": "1"
},
{
"@odata.type": "Microsoft.Dynamics.CRM.expando",
"value": "2"
}
]
}
}
Upvotes: 1
Reputation: 81
Part of the problem is that the C# samples out there use the SDK, which in turn uses the deprecated 2011 WCF service, not the current OData Service. It looks as if the OData Service is more strict in terms of the @odata.type you specify, in that it looks to check that the attributes you are providing are real attributes/fields of that entity. The problem with Settings and ResourceSpecification is that in the Action, the type of entity is not specified.
Upvotes: 0
Reputation: 22836
I was able to upgrade to Field service v8.8.x from v8.7.x and test the CRM action msdyn_SearchResourceAvailability
using web api with the following payload. I don't have all the config & data setup it seems but the web api is resulting good response (different than 400 = Bad request.. lol)
var parameters = {};
parameters.Version = "2";
var requirement = {};
requirement.msdyn_resourcerequirementid = "B9E6F413-0063-EA11-A811-000D3A5A1CAC"; //Delete if creating new record
requirement["@odata.type"] = "Microsoft.Dynamics.CRM.msdyn_resourcerequirement";
parameters.Requirement = requirement;
var settings = {};
settings.systemuserid = "26ADDD07-D9F4-E711-8138-E0071B715B11"; //Delete if creating new record
settings["@odata.type"] = "Microsoft.Dynamics.CRM.systemuser";
parameters.Settings = settings;
var req = new XMLHttpRequest();
req.open("POST", Xrm.Page.context.getClientUrl() + "/api/data/v9.1/msdyn_SearchResourceAvailability", true);
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.onreadystatechange = function() {
if (this.readyState === 4) {
req.onreadystatechange = null;
if (this.status === 200) {
var results = JSON.parse(this.response);
alert(this.response)
} else {
alert(this.status);
}
}
};
req.send(JSON.stringify(parameters));
Response:
{
"@odata.context": "https://crmdev.crm.dynamics.com/api/data/v9.1/$metadata#Microsoft.Dynamics.CRM.msdyn_SearchResourceAvailabilityResponse",
"TimeSlots": [
{
"@odata.type": "#Microsoft.Dynamics.CRM.organization"
},
{
"@odata.type": "#Microsoft.Dynamics.CRM.organization"
},
{
"@odata.type": "#Microsoft.Dynamics.CRM.organization"
},
{
"@odata.type": "#Microsoft.Dynamics.CRM.organization"
},
{
"@odata.type": "#Microsoft.Dynamics.CRM.organization"
}
],
"Resources": [
{
"@odata.type": "#Microsoft.Dynamics.CRM.organization"
}
],
"Related": {
"@odata.type": "#Microsoft.Dynamics.CRM.organization"
},
"Exceptions": {
"@odata.type": "#Microsoft.Dynamics.CRM.organization"
}
}
Update:
Only Version, Requirement, and Settings are required for this call, so start with minimal code input & enhance it
While troubleshooting the error message Microsoft.OData.ODataException: Does not support untyped value in non-open type
, proceed in this direction - typo in some schema name could be the reason
Not sure if this msdyn_SearchResourceAvailability
action message is yet available in web api, but only OrganizationRequest
SDK is tried out Reference
This is the sample request along with required payload:
var parameters = {};
var workorder = {};
workorder.msdyn_workorderid = "ADE6F413-0063-EA11-A811-000D3A5A1CAC"; //Delete if creating new record
workorder["@odata.type"] = "Microsoft.Dynamics.CRM.msdyn_workorder";
parameters.WorkOrder = workorder;
parameters.RealTimeMode = true;
parameters.Duration = 30;
parameters.IgnoreDuration = true;
parameters.IgnoreTravelTime = true;
parameters.AllowOverlapping = true;
parameters.Radius = 0;
parameters.StartTime = new Date("3/10/2020").toISOString();
parameters.EndTime = new Date("3/10/2020").toISOString();
var resources1 = {};
resources1.systemuserid = "3BD2ADED-20B2-E911-A98E-000D3A374B53"; //Delete if creating new record
resources1["@odata.type"] = "Microsoft.Dynamics.CRM.systemuser";
parameters.Resources = [resources1];
var req = new XMLHttpRequest();
req.open("POST", Xrm.Page.context.getClientUrl() + "/api/data/v9.1/msdyn_RetrieveResourceAvailability", true);
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.onreadystatechange = function() {
if (this.readyState === 4) {
req.onreadystatechange = null;
if (this.status === 200) {
var results = JSON.parse(this.response);
alert("response: "+this.response)
} else {
alert(this.status);
}
}
};
req.send(JSON.stringify(parameters));
I just generated this with my sandbox without any data, but you can try the CRM REST builder for building up the request.
Upvotes: 0