Create annotation to a contact entity in Microsoft Dynamics CRM by API - odata

This question is related to Microsoft Dynamics CRM 2015, that I'm calling through API.
I create contact entity:
POST [organization URI]/api/data/contacts
Content-Type: application/json; charset=utf-8
Accept: application/json
{
"emailaddress1": "myemail#example.com",
}
It works, I see new record, after I log into the panel.
And I can call it through the API:
[organization URI]/api/data/contacts(f76e4e7c-ea61-e511-80fd-3863bb342b00)
{
"#odata.context":"[organization URI]/api/data/$metadata#contacts/$entity",
"#odata.etag":"W/\"460199\"",
...
"contactid":"f76e4e7c-ea61-e511-80fd-3863bb342b00",
"emailaddress1":"myemail#example.com",
....
}
Next thing I want to do, is to add annotation record associated with that contact.
Following the guide I call:
POST [organization URI]/api/data/annotations
Content-Type: application/json; charset=utf-8
Accept: application/json
{
"notetext": "TEST",
'contact#odata.bind': 'contacts(f76e4e7c-ea61-e511-80fd-3863bb342b00)'
}
But it returns 400 error:
An undeclared property 'contact' which only has property annotations in the payload but no property value was found in the payload. In OData, only declared navigation properties and declared named streams can be represented as properties without values.
When I call:
POST [organization URI]/api/data/annotations
Content-Type: application/json; charset=utf-8
Accept: application/json
{
"notetext": "TEST",
}
New entity is created, but without a relation to contact.
How to properly compose this POST request? What am I missing here?
I suspect, that contact#odata.bind should be presented somehow different, I've tried contactid#odata.bind, object#odata.bind, objectid#odata.bind - but no effects.
Any ideas?

Instead of using objectid#odata.bind, you have to use objectid_contact#odata.bind. This results are in:
"objectid_contact#odata.bind": "/contacts(f76e4e7c-ea61-e511-80fd-3863bb342b00)"
To get the list of properties, look under the single-valued navigation properties in the documentation.

Part 1:
MS Docs Reference: Deep Insert
You can create entities related to each other by defining them as navigation properties values. This is known as deep insert.
As with a basic create, the response OData-EntityId header contains the Uri of the created entity. The URIs for the related entities created aren’t returned.
Below code is to create Account (1), create + associate Primary contact (2), create & Associate Opportunity (3) and create + associate Task (4)
POST [Organization URI]/api/data/v8.2/accounts HTTP/1.1
Content-Type: application/json; charset=utf-8
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json
{
"name": "Sample Account",
"primarycontactid":
{
"firstname": "John",
"lastname": "Smith"
},
"opportunity_customer_accounts":
[
{
"name": "Opportunity associated to Sample Account",
"Opportunity_Tasks":
[
{ "subject": "Task associated to opportunity" }
]
}
]
}
Part 2:
Associating annotation to contact uses the below syntax.
note["objectid_contact#odata.bind"] = "/contacts(C5DDA727-B375-E611-80C8-00155D00083F)";
Refer SO link & blog
Part 3:
Answer to your comment on another answer about annotation_id_from_first_request:
To get the created record Id in response from last request, you can parse like below:
//get Response from Created Record
entityIdWithLink = XMLHttpRequest.getResponseHeader("OData-EntityId");
//get EntityId from ResponseHeader of Created Record
getEntityId = entityIdWithLink.split(/[()]/);
getEntityId = getEntityId[1];
You can read more
You can compose your POST request so that data from the created record will be returned with a status of 201 (Created).
To get this result, you must use the return=representation preference in the request headers.
To control which properties are returned, append the $select query option to the URL to the entity set.
The $expand query option will be ignored if used.
When an entity is created in this way the OData-EntityId header containing the URI to the created record is not returned
Note: This capability was added with December 2016 update for Dynamics 365
MS Docs Reference: Create with data returned
Update:
If anyone looking for working payload sample to deep insert a record + annotation, the below is from my project:
data = {
"new_attribute1": "test attribute 1",
"new_attribute2": "test attribute 2",
"new_comments": "test comments",
"new_recordurl": recordURL,
"new_feedback_Annotations":
[
{
"notetext": "Screenshot attached",
"subject": "Attachment",
"filename": file.name,
"mimetype": file.type,
"documentbody": base64str,
}
]
};

I've found this working, but in two requests:
POST [organization URI]/api/data/annotations
Content-Type: application/json; charset=utf-8
Accept: application/json
{
"notetext": "TEST"
}
POST [organization URI]/api/data/contacts(f76e4e7c-ea61-e511-80fd-3863bb342b00)/Contact_Annotation/$ref
Content-Type: application/json; charset=utf-8
Accept: application/json
{
"#odata.id": "[organization URI]/annotations(annotation_id_from_first_request)"
}
Edit:
annotation_id_from_first_request value is taken form first request's response.

This answer applies for web api usage:
If the references property has been defined using uppercase letters, you have to use uppercase letters in the property on update and insert. Look at the Schema name in the property list of the primary entity.
Lets say you have an entity called myprefix_entity with a reference to the account entity, and you named it Account, and the schema name became myprefix_AccountId, you would have to refer it as:
"myprefix_AccountId#odata.bind":"/accounts(f76e4e7c-ea61-e511-80fd-000000000000)"
The uppercase A and the uppercase I in myprefix_AccountId matters, if that is how the schema name has been defined.

I'm using this C# Code for creating and linking (the Task.Await stuff is not very clever, so... be careful):
dynamic testAno = new ExpandoObject();
testAno.NoteText = "Hello World!";
testAno.Subject = "Note Subject";
dynamic refAccount = new ExpandoObject();
refAccount.LogicalName = "account";
refAccount.Id = "003CCFC2-4012-DE11-9654-001F2964595C";
testAno.ObjectId = refAccount;
testAno.ObjectTypeCode = refAccount.LogicalName;
var demo = JsonConvert.SerializeObject(testAno);
HttpContent content = new StringContent(demo, Encoding.UTF8, "application/json");
var handler = new HttpClientHandler { UseDefaultCredentials = true };
HttpClient client = new HttpClient(handler);
var test = client.PostAsync(new Uri("http://crm/.../XRMServices/2011/OrganizationData.svc/AnnotationSet"), content).Result;
The JSON is looking like this:
{"NoteText":"Hello World!",
"Subject":"Note Subject",
"ObjectId": {"LogicalName":"account",
"Id":"003CCFC2-4012-DE11-9654-001F2964595C"}
,"ObjectTypeCode":"account"}

You can use following.
'contactid_contact#odata.bind': '/contacts(f76e4e7c-ea61-e511-80fd-3863bb342b00)'
In most of the record you will get _contactid_value as a parameter name. So you have to pass like contactid_entityname#odata.bind as a parameter and in the value you have to pass 'EntitySetName' which would be contacts and GUID. '/EntitysetName(GUID)'
So the value will be '/contacts(f76e4e7c-ea61-e511-80fd-3863bb342b00)'

might be a bit late for this, but the answer in the following link explains how the binding works really well.
basically, you need to use the field schema name with the suffix #odata.bind and the value being "/entityschemaname(recordGUID)" good to remember that the entityschemaname needs to have an 's' and the recordGUID should not have the curly brackets.
for more information follow this link below where i got this information from
'An undeclared property' when trying to create record via Web API

If you use OData, then you can do it this way...
In your Annotation data model:
[DataContract(Name = "annotations")]
public record Annotation
{
[DataMember(Name = "objectid_rd_servicerequestsession")]
public ServiceRequestSession ObjectId { get; set; } = default!;
[DataMember(Name = "objecttypecode")]
public string ObjectTypeCode { get; set; } = default!;
Where the rd_servicerequestsession is your entity name. Then you just need to create a new object Annotation object
var annotation = new Annotation
{
ObjectId = serviceRequestSession,
ObjectTypeCode = "rd_servicerequestsession",
And simply invoke InsertEntry method.

Related

SAP CAP using cds.User attributes

I authenticate on the server by feeding req.user with an instance of cds.User, and I add some attributes:
User {
id: '110226363079182595683',
attr: { name: 'depth1', email: 'depth1#protonmail.com' },
_roles: { 'identified-user': true, 'authenticated-user': true }
}
This allows me to call my CDS services with an authenticated user.
It works well.
Then, I have in my CDS schema an entity :
entity Comments {
key ID : Integer;
project : Association to Projects;
title : String;
text : String;
CreatedBy : String #cds.on.insert : $user;
CreatedByName : String #cds.on.insert : $user.name;
}
My schema on an SQLite database.
I start the server, I launch a UI5 application which allows to insert comments in OData V4 and here is what happens:
HTTP request:
--batch_id-1642708209182-45
Content-Type:application/http
Content-Transfer-Encoding:binary
POST Projects(1)/comments HTTP/1.1
Accept:application/json;odata.metadata=minimal;IEEE754Compatible=true
Accept-Language:fr-FR
Content-Type:application/json;charset=UTF-8;IEEE754Compatible=true
{"ID":0,"text":"test comment"}
--batch_id-1642708209182-45--
Group ID: $auto
Server Log:
[cds] - > CREATE Projects(1)/comments
HTTP Response:
--batch_id-1642708209182-45
content-type: application/http
content-transfer-encoding: binary
HTTP/1.1 201 Created
odata-version: 4.0
content-type: application/json;odata.metadata=minimal;IEEE754Compatible=true
location: Comments(101)
{"#odata.context":"../$metadata#Comments/$entity","ID":101,"project_ID":1,"title":null,"text":"test comment","CreatedBy":"110226363079182595683","CreatedByName":"depth1"}
--batch_id-1642708209182-45--
HTTP Request:
--batch_id-1642708209355-46
Content-Type:application/http
Content-Transfer-Encoding:binary
GET Projects(1)/comments(101)?$select=CreatedByName,ID,text HTTP/1.1
Accept:application/json;odata.metadata=minimal;IEEE754Compatible=true
Accept-Language:fr-FR
Content-Type:application/json;charset=UTF-8;IEEE754Compatible=true
--batch_id-1642708209355-46--
Group ID: $auto
Server log
[cds] - > READ Projects(1)/comments(101) { '$select': 'CreatedByName,ID,text' }
HTTP Response
--batch_id-1642708209355-46
content-type: application/http
content-transfer-encoding: binary
HTTP/1.1 200 OK
odata-version: 4.0
content-type: application/json;odata.metadata=minimal;IEEE754Compatible=true
{"#odata.context":"../$metadata#Comments(CreatedByName,ID,text)/$entity","CreatedByName":null,"ID":101,"text":"aaa"}
--batch_id-1642708209355-46--
In my Database, CreatedBy is filled but not CreatedByName.
Also in the Create request i got the CreatedByName filled by the server and returned it's really strange.
How can i insert some cds.User attributes to the database ?!
Thank you !
Oww
I found a solution ! By adding a behavior in service.js
const cds = require('#sap/cds')
module.exports = cds.service.impl(function() {
this.before('CREATE', 'Comments', fillData)
})
async function fillData(req) {
req.data.CreatedByName = req.user.attr.name;
}

How to set the creationOptions when creating a Team?

Using the Microsoft Teams Portal, I created a Group that came with Assignments and the Notebook tab. I want to create a similar Group programmatically.
I am using the Nuget Microsoft.Graph.Beta 4.0.1-preview to create Team for a School Classroom programmatically on Microsoft Teams.
However I noticed that the team created does not have the Assignments tab nor the Notebook.
Then I compared with a Team I created on the teams portal and found that the Team created manually on the Teams portal have this creationOptions on the class Group
"creationOptions": ["classAssignments", "ExchangeProvisioningFlags:2509"]
But on the group created by API it does not contain that.
The API also does not have the field creationOptions
How to add that information when creating it using the API?
Or
How can I add those options after the Team is created?
I also tried this:
var grupo = await graphClient.Groups.Request().AddAsync(new Group()
{
DisplayName = "Turma Dialética 2021",
Description = "Grupo da Turma Dialética 2021",
MailNickname = "f958e37c-f093-4177-8de4-2f86bfaba624",
MailEnabled = false,
SecurityEnabled = false,
GroupTypes = new string[] { "Unified" },
Visibility = "HiddenMembership",//
AdditionalData = new Dictionary<string, object>()
{
{"creationOptions", new List<string> { "classAssignments", "ExchangeProvisioningFlags:2509" } }
}
}
);
Which sends this JSON payload
{
"description": "Grupo da Turma Dialética 2021",
"displayName": "Turma Dialética 2021",
"groupTypes": [
"Unified"
],
"mailEnabled": false,
"mailNickname": "f958e37c-f093-4177-8de4-2f86bfaba624",
"securityEnabled": false,
"visibility": "HiddenMembership",
"#odata.type": "microsoft.graph.group",
"creationOptions": [
"classAssignments",
"ExchangeProvisioningFlags:2509"
]
}
But when I add this AdditionalData, then this Exception happens:
Microsoft.Graph.ServiceException: 'Code: Request_BadRequest Message: A value without a type name was found and no expected type is available. When the model is specified, each value in the payload must have a type which can be either specified in the payload, explicitly by the caller or implicitly inferred from the parent value. Inner error: AdditionalData: date: 2021-04-08T21:14:22 request-id: d3a0b0d3-5940-46d8-8188-56a1eaf5d350 client-request-id: d3a0b0d3-5940-46d8-8188-56a1eaf5d350`
If I add an # symbol in front of the word creationOptions. e.g. "#creationOptions" then no error when creating the Group, but then when I try to create the Team from this Group:
var team = new Microsoft.Graph.Team()
{
MemberSettings = new TeamMemberSettings()
{
AllowCreateUpdateChannels = false
},
MessagingSettings = new TeamMessagingSettings()
{
AllowUserEditMessages = false,
AllowUserDeleteMessages = false
},
FunSettings = new TeamFunSettings()
{
AllowGiphy = false
}
,
AdditionalData = new Dictionary<string, object>()
{
{"template#odata.bind", "https://graph.microsoft.com/v1.0/teamsTemplates('standard')"},
{"group#odata.bind", "https://graph.microsoft.com/v1.0/groups('" + grupo.Id + "')"}
}
};
graphClient.Teams.Request().AddAsync(team);
This error occurs:
Microsoft.Graph.ServiceException: 'Code: NotFound
Message: Failed to execute Templates backend request CreateTeamFromGroupWithTemplateRequest. Request Url: https://teams.microsoft.com/fabric/amer/templates/api/groups/9ee04411-993b-45f9-8d72-63343f703105/team, Request Method: PUT, Response Status Code: NotFound, Response Headers: Strict-Transport-Security: max-age=2592000
x-operationid: e922312859272d4ea8573cef70e37163
x-telemetryid: 00-e922312859272d4ea8573cef70e37163-0b5da564c6209441-00
X-MSEdge-Ref: Ref A: 34D331F1C0454DA7AE8CD7497D0558A7 Ref B: DM2EDGE1008 Ref C: 2021-04-09T15:16:22Z
Date: Fri, 09 Apr 2021 15:16:23 GMT
, ErrorMessage : {"errors":[{"message":"Failed to execute GetGroupAsync.","errorCode":"Unknown"}],"operationId":"e922312859272d4ea8573cef70e37163"}
Inner error:
AdditionalData:
date: 2021-04-09T15:16:24
request-id: d5ef1a33-d6d9-4d30-b9c5-83a7a477a1b5
client-request-id: d5ef1a33-d6d9-4d30-b9c5-83a7a477a1b5
ClientRequestId: d5ef1a33-d6d9-4d30-b9c5-83a7a477a1b5
'
You need to bind the correct template when POSTing to the /teams endpoint.
The educationClass template should create the right primitives in Teams and also set up the correct options under the hood in the Group.
From the POST /teams doc page:
POST https://graph.microsoft.com/beta/teams
Content-Type: application/json
{
"template#odata.bind": "https://graph.microsoft.com/beta/teamsTemplates('educationClass')",
"displayName": "My Class Team",
"description": "My Class Team’s Description"
}
The property doesn't appear to be defined in the metadata hence why it's not available in the SDK.
However, if you look at the definition for entity which group inherits from (via directory object), you'll find the additionalData property.
Setting the information there with something like the code snippet below should allow you to pass the desired information to the service.
AdditionalData = new Dictionary<string, object>()
{
{"#creationOptions", new List<string> { "classAssignments", "ExchangeProvisioningFlags:2509" } }
}
The # sign in front of the key is because this property is an instance annotation.

How to send header in post request of RestBuilder using grails 3.3.9

Response response = new RestBuilder.post(finalUrl){
header: ["ticket", "getProxyTicket()",
"name", "abc",
"id", "1"
]
contentType: application/json
json data
}
I have the above code. And when I send the post request through API call, I get an exception "Proxy ticket must be sent as parameter in header". I don't know what is wrong with the code. Can anyone help me?
There are different ways to do it. This should work:
new RestBuilder().post(finalUrl){
// this will work if you want the value
// of the ticket header to be the literal "getProxyTicket()".
// if you are trying to invoke a method and you want the return
// value of that method to be the header value, remove the double
// quotes around getProxyTicket()
header "ticket", "getProxyTicket()"
header "name", "abc"
header "id", "1"
// no need to set the content type, the
// json method below will do that
// contentType: application/json
json data
}

Creating multiple entities in single request in Microsoft Dynamics CRM (OData)

I know how to create a single entity in single request. However, one requirement wants me to create multiple entities (in my case it's multiple entries in ContactSet). I tried putting array to
POST /XRMServices/2011/OrganizationData.svc/ContactSet
[{
"MobilePhone": "+0012 555 555 555",
"YomiFullName" : "Demo User 1",
"GenderCode" : {
"Value" : 1
}
.....
<data removed for sanity>
.....
},
{
"MobilePhone": "+0012 555 555 111",
"YomiFullName" : "Demo User 2",
"GenderCode" : {
"Value" : 1
}
.....
<data removed for sanity>
.....
}]
However this does not work and I could not find any documentation explaining me ways to achieve this. Any help would be greatly appreciated.
You need to use an ExecuteMultipleRequest, I don't believe this is available in Rest service however, but is available in the SOAP service.
// Get a reference to the organization service.
using (_serviceProxy = new OrganizationServiceProxy(serverConfig.OrganizationUri, serverConfig.HomeRealmUri,serverConfig.Credentials, serverConfig.DeviceCredentials))
{
// Enable early-bound type support to add/update entity records required for this sample.
_serviceProxy.EnableProxyTypes();
#region Execute Multiple with Results
// Create an ExecuteMultipleRequest object.
requestWithResults = new ExecuteMultipleRequest()
{
// Assign settings that define execution behavior: continue on error, return responses.
Settings = new ExecuteMultipleSettings()
{
ContinueOnError = false,
ReturnResponses = true
},
// Create an empty organization request collection.
Requests = new OrganizationRequestCollection()
};
// Create several (local, in memory) entities in a collection.
EntityCollection input = GetCollectionOfEntitiesToCreate();
// Add a CreateRequest for each entity to the request collection.
foreach (var entity in input.Entities)
{
CreateRequest createRequest = new CreateRequest { Target = entity };
requestWithResults.Requests.Add(createRequest);
}
// Execute all the requests in the request collection using a single web method call.
ExecuteMultipleResponse responseWithResults =
(ExecuteMultipleResponse)_serviceProxy.Execute(requestWithResults);
// Display the results returned in the responses.
foreach (var responseItem in responseWithResults.Responses)
{
// A valid response.
if (responseItem.Response != null)
DisplayResponse(requestWithResults.Requests[responseItem.RequestIndex], responseItem.Response);
// An error has occurred.
else if (responseItem.Fault != null)
DisplayFault(requestWithResults.Requests[responseItem.RequestIndex],
responseItem.RequestIndex, responseItem.Fault);
}
}
ExecuteMultipleRequest is a good but not the only way. If you use CRM 2016 you can use Batch operations that is available in new WebApi. Check article that describes it - https://msdn.microsoft.com/en-us/library/mt607719.aspx
You can use a Web API action (see MSDN) to execute an ExecuteTransactionRequest, as described here. Subject of the example on MSDN is the WinOpportunityRequest, but it should work with any supported request, including custom actions.

Laravel 5 dingo api, add multiple transformed objects to the response

Want to add a transformed object along with other response, I have used following code:
$accessToken = Authorizer::issueAccessToken();
$user = User::where('email', $request->get('username'))->with('profile')->first();
if ($user) {
$accessToken['user'] = $this->response->item($user, new UserTransformer);
}
return $accessToken;
Expected Response:
{
"access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"token_type": "Bearer",
"expires_in": 31536000,
"data": {
"id": 1,
"email": "xxxxx",
"profile": {
"data": {
"id": 1,
"first_name": "Muhammad",
"last_name": "Shakeel",
}
}
}
}
but not getting transformed object, there must be some better way to add multiple transformed objects with response. Am I missing something?
Edit
Current response returns user object without transformation and if I return only user transformed object like following, it returns correct transformed object:
return $this->response->item($user, new UserTransformer);
As discussed on the issue tracker(https://github.com/dingo/api/issues/743#issuecomment-160514245), jason lewis responded to the ticket with following:
The only way you could do this at the moment would be to reverse that. So you'd return the response item, then add in the access token data, probably as meta data.
So, something like this.
return $this->response->item($user, new UserTransformer)->setMeta($accessToken);
The response will then contain a meta data key which will contain your access token data.
I got it to work using Internal Requests. https://github.com/dingo/api/wiki/Internal-Requests
So what you can do is
Suppose you have a route that fetches transformed user object at api/users/{email_id}?access_token=...
While issuing the access_token you can do the following :
$dispatcher = app('Dingo\Api\Dispatcher');
$array = Authorizer::issueAccessToken();
$array['user'] = $dispatcher->get('api/users/'.$request->get("username").'?access_token='.$array['access_token']);
return $array;
This will return transformed data.
NOTE : You will need to have a route that fetches user data.
You will have to handle cases in /api/users/{email-id} where email-id does not exist.

Resources