I have defined an API page in an extension using .AL language. I can then read an object and retrieve the mediaEditLink property. I then use that to make a PATCH call and pass the text I want to store in the Note field directly in the body of the request. I pass auth headers and I am able to update the Notes field. But it appears to be invalid. I cannot view the note in the normal UI. It just shows the error “The data cannot be shown because it is not valid.”
I have tried using GET POST PATCH and tried various Content-Type headers as well.
Can anyone help explain what I need to do to convert the text to something will store and reload properly.
Here is my page .al
page 83703 "SF_API - RecordLinks"
{
PageType = API;
APIPublisher = 'XXXXX';
APIGroup = 'XXXXX';
APIVersion = 'v2.0';
EntityName = 'sf_recordlink';
EntitySetName = 'sf_recordlinks';
EntityCaption = 'SF RecordLink';
EntitySetCaption = 'SF RecordLinks';
SourceTable = "Record Link";
ODataKeyFields = "Link ID";
ChangeTrackingAllowed = true;
DelayedInsert = true;
Extensible = false;
layout
{
area(content)
{
repeater(Group)
{
field(LinkId; Rec."Link ID")
{
Caption = 'Link ID';
Editable = false;
}
field(RecordId; Rec."Record ID")
{
Caption = 'Record ID';
}
field(Type; Rec."Type")
{
Caption = 'Type';
}
field(Description; Rec."Description")
{
Caption = 'Description';
}
field(Note; Rec."Note")
{
Caption = 'Note';
}
}
}
}
actions
{
}
}
Hi @Dustin_Yoder,
To send data to a blob field you must use PUT.
As Content-Type I’ve always used “application/octed-stream”.
You can take a look at Dynamics 365 Business Central: handling BLOB fields on APIs – Stefano Demiliani for more detailed info.
I am discovering that the ‘Note’ field in particular is using some proprietary format and so I am able to update and retrieve my values, but the UI says they are invalid and they won’t display. I think I need to add some trigger to my custom API to process this with some RecordLinkMgt function or something. Any ideas on where I put that to preprocess my sent values before they are stored? Also, what function would I need to use?
I do have to say that needing to write a custom API in an extension and then go through all this mess as well, just to simply use the API to update a note field, is almost laughable at the level of complexity for the simplest of tasks. I can see why Microsoft development is expensive and slow.
Sorry to rant but it just seems like there is no way it needs to be done this way but it definitely does seem to be the case. (just for any other newbies that come across this post)
Yes, I think you are right. If you look at how the system stores notes in the Record Link table, you will see that the “WriteNote” function from “Record Link Management” codeunit is used… this function calls the implementation codeunit that uses a dotnet binary variable to store data in the blob.
I think the best thing to do is to create a text variable in your page and change the “Note” field to use this variable instead of the blob field. In the insert/modify trigger you can use the “WriteNote” function to store the text you send in the “note” json key.
Otherwise, remove the “Note” field from the page and use a bound action with a text parameter to do this.
This is what I have so far. I am able to update the note text on existing notes at this point. Haven’t tested new inserts, but I am wondering if I can also read the note so i can return as well. I did see a way to do this with ReadNote() but wasn’t sure where to put that call now that I’m using a variable for the Note field instead of the function call. Is there a trigger for reading that field? I was doing something like
field(Note; getNote())
and then defined a getNote that uses readNote to load the value. But that approach doesn’t work now that I have Note field as
field(Note; NoteText)
Can I use ReadNote in a trigger for reading like I am using WriteNote for modifying?
page 83703 "SF_API - RecordLinks"
{
PageType = API;
APIPublisher = 'XXXXXXXX';
APIGroup = 'XXXXXXXX';
APIVersion = 'v2.0';
EntityName = 'sf_recordlink';
EntitySetName = 'sf_recordlinks';
EntityCaption = 'SF RecordLink';
EntitySetCaption = 'SF RecordLinks';
SourceTable = "Record Link";
ODataKeyFields = "Link ID";
ChangeTrackingAllowed = true;
DelayedInsert = true;
Extensible = false;
layout
{
area(content)
{
repeater(Group)
{
field(LinkId; Rec."Link ID")
{
Caption = 'Link ID';
Editable = false;
}
field(RecordId; Rec."Record ID")
{
Caption = 'Record ID';
}
field(Type; Rec."Type")
{
Caption = 'Type';
}
field(Note; "NoteText")
{
Caption = 'Note Text';
}
}
}
}
actions
{
}
var
NoteText: Text[1024];
trigger OnInsertRecord(BelowxRec: Boolean): Boolean
var
RecordLinkMgt: Codeunit "Record Link Management";
begin
if Rec.Type = Rec.Type::Note then begin
RecordLinkMgt.WriteNote(Rec, NoteText);
end;
exit(true);
end;
trigger OnModifyRecord(): Boolean
var
RecordLinkMgt: Codeunit "Record Link Management";
begin
if Rec.Type = Rec.Type::Note then begin
RecordLinkMgt.WriteNote(Rec, NoteText);
end;
exit(true);
end;
}
Yes, you can use the OnAfterGetRecord trigger to set NoteText value with “ReadNote” function.
Just to be complete, here is my final .al file defining this custom api that does seem to allow pretty good access to read and write Notes from the RecordLinks table. Would be up for improvements on this if I am not really doing something correctly or if it could be made more robust somehow, but it is working for my purposes.
Thank you pjllaneras for the help.
page 83703 "SF_API - RecordLinks"
{
PageType = API;
APIPublisher = 'XXXXXXXXX';
APIGroup = 'XXXXXXXX';
APIVersion = 'v2.0';
EntityName = 'sf_recordlink';
EntitySetName = 'sf_recordlinks';
EntityCaption = 'SF RecordLink';
EntitySetCaption = 'SF RecordLinks';
SourceTable = "Record Link";
ODataKeyFields = "Link ID";
ChangeTrackingAllowed = true;
DelayedInsert = true;
Extensible = false;
layout
{
area(content)
{
repeater(Group)
{
field(LinkId; Rec."Link ID")
{
Caption = 'Link ID';
Editable = false;
}
field(RecordId; Rec."Record ID")
{
Caption = 'Record ID';
}
field(Type; Rec."Type")
{
Caption = 'Type';
}
field(Description; Rec."Description")
{
Caption = 'Description';
}
field(Note; "NoteText")
{
Caption = 'Note Text';
}
// field(Note; Rec."Note")
// {
// Caption = 'Note';
// }
}
}
}
actions
{
}
var
NoteText: Text[1024];
trigger OnInsertRecord(BelowxRec: Boolean): Boolean
var
RecordLinkMgt: Codeunit "Record Link Management";
begin
if Rec.Type = Rec.Type::Note then begin
RecordLinkMgt.WriteNote(Rec, NoteText);
end;
exit(true);
end;
trigger OnModifyRecord(): Boolean
var
RecordLinkMgt: Codeunit "Record Link Management";
begin
if Rec.Type = Rec.Type::Note then begin
RecordLinkMgt.WriteNote(Rec, NoteText);
end;
exit(true);
end;
trigger OnAfterGetRecord()
var
RecordLinkMgt: Codeunit "Record Link Management";
begin
Rec.CalcFields(Note); // Load the Note BLOB field
NoteText := RecordLinkMgt.ReadNote(Rec)
end;
}
1 Like
Looks like I need a UserId and Company field or at least one of those fields for a newly inserted note to appear on a sales order. I’m guessing it is for permissions. I added those 2 fields to my .al page but is there a way to have those fields auto populate onInsertRecord or do I actually have to pass those along when I create the new record?
Do I set the Rec.“User Id” or how does this work?
Also, where do I get the current user id from?
trigger OnInsertRecord(BelowxRec: Boolean): Boolean
var
RecordLinkMgt: Codeunit "Record Link Management";
begin
if Rec.Type = Rec.Type::Note then begin
RecordLinkMgt.WriteNote(Rec, NoteText);
end;
// maybe some IF statement here to check if UserId was null
Rec."User Id" := CurrentUserId;
// or I have seen USERID used in places but thought that is probably old?
exit(true);
end;
This is my final onInsertRecord function for anyone who might use this. This one does set the userid, company, and created date which does allow a newly inserted note to show up on a sales order. Probably needed for getting a note to show up in the UI anywhere really. I would still be up for comments on how I could improve this custom API page and make it more proper. I’m super new to Business Central and am having to learn everything as I go.
trigger OnInsertRecord(BelowxRec: Boolean): Boolean
var RecordLinkMgt: Codeunit "Record Link Management";
begin
if Rec.Type = Rec.Type::Note then begin
RecordLinkMgt.WriteNote(Rec, NoteText);
end;
// maybe if statements here to only default if no incoming value is set?
// currently this is probably more secure.
Rec."User Id" := UserId;
Rec."Company" := CompanyName;
Rec."Created" := CurrentDateTime;
exit(true);
end;