How to declare a blob data type param on a function in business central - dynamics-business-central

I'm new to business central, I want to remove an attached image file because my client requested that functionality. I created a function that will check that, and it is all working fine. I noticed that I'm doing a repetition for all the image fields, which got me writing the function for every blob field on the page.
I want to write a single function then pass a blob params then receive that and do the check. But I noticed that I can't declare a blob file as a params.
procedure CheckImageFieldValue(imagefile: Blob; removeImageFile: Boolean)
begin
removeImageFile := false;
if imagefile.HasValue() then begin
removeImageFile:= true;
end
else begin
removeImageFile := false;
end;
end;
Error: 'Blob' is not a valid variable type

The Blob data type is a bit tricky to work with.
If you wanted to do something like this, you would have to stream the blob to a TempBlob (it's an object either Record or Codeunit depending on which version of Business Central you are running).
But streaming the blob to a TempBlob requires you to load the content, which would include at check to see if the Blob has a value.

Related

Is there a way to pass dependent fields to a Julia struct?

##define the struct
struct DataLoader
getter::String
DataLoader(getter="remote") = new(getter)
end
##testing the struct
ld = DataLoader("local")
ld.getter
##local
ld = DataLoader()
ld.getter
##remote
I'm trying to write a dataloader in Julia with some additional methods later defined on it.
The data can be loaded in two ways by the user - "remote" and "local".
If the user choses local, I need another field base_dir - the directory where the data is stored. If the user choses remote, I don't need base_dir as I know which URL to make the request to.
After that, when the loader has been defined, I'll call some functions on the loader to do other stuff (example function below):
function bar(x::DataLoader)
if x.getter == "local"
#do_somethings
println("local found")
elseif x.getter == "remote"
println("remote found")
else
println("wrong mode passed")
end
end
My question is how do I define this co-dependency in the struct? That is, how do I tell the user and implement in my code the logic that if the getter is local I need base_dir and if it's remote, I can dynamically remove the field base_dir from my struct.
I'm new to Julia so any other tips on improving the code would also be more than welcome.
I think there are several ways to do this; in my view, the simplest one is to rely on the dispatcher. This revolves around using two structs, one for "local", one for "remote". If really needed, you can create an "AbstractLoader" they both belong to, more on that at the end.
struct LocalLoader
basedir::String
end
struct RemoteLoader
end
ll = LocalLoader("test_directory")
rl = RemoteLoader()
function bar(ll::LocalLoader)
println("Base directory is $(ll.basedir)")
end
function bar(rl::RemoteLoader)
println("This is the remote loader")
end
bar(ll)
bar(rl)
I see several advantages to this logic:
for the developer: the two methods can be developed independently
for the developer: you don't have to place some 'if' logic in your code which will remain simpler. (and it is not a possibility to pass an invalid mode). Said another way: you don't have to implement the dispatcher as you rely on Julia's dispatcher.
It clarifies the API to the user: when the user wants a local object a string must be passed. When the user wants a remote he/she can not pass the extra parameter.
The main drawback is code duplication. If there is some code duplication it can be countered, to do that you would need to make the two structs belong to an abstract type.
That would change the code in the following way:
abstract type AbstractLoader end
struct LocalLoader <: AbstractLoader
basedir::String
end
struct RemoteLoader <: AbstractLoader
end
ll = LocalLoader("test_directory")
rl = RemoteLoader()
function bar(ll::LocalLoader)
println("Base directory is $(ll.basedir)")
end
function bar(rl::RemoteLoader)
println("This is the remote loader")
end
bar(ll)
bar(rl)
function foo(al::AbstractLoader)
println("Do common tasks")
end
foo(ll)
foo(rl)

Fetch error on Office Script (Excel on web)

I am trying to call an external API from an Excel on web. However, I am stuck on trying to get the result from the fetch call. I am even using the Office doc example to make sure
From an Excel, click on Automate to create a new script
async function main(workbook: ExcelScript.Workbook): Promise<void> {
let fetchResult = await fetch('https://jsonplaceholder.typicode.com/todos/1');
let json = await fetchResult.json();
}
I keep on getting the following message (at the fetchResult.json() call)
"Office Scripts cannot infer the data type of this variable or inferring it might result in unexpected errors. Please annotate the type of the variable to avoid this error. You can also use the Quick fix option provided in the editor to auto fill the type based on the usage. Quick Fix can be accessed by right clicking on the variable name and selecting Quick Fix link."
When running the Chrome inspector, the API request seems to be on hold "CAUTION: request is not finished yet"
PS: I am not the Office administrator and is not reachable right now, but hoping this is not a problem with my user or the Office account configuration
Any idea what the problem might be?
Thanks!
"any" types not being allowed in OfficeScript is by design. We think any types in general can lead to developer errors. I understand it can be hard to declare types – but these days most popular APIs provide you the interface (or d.ts) that you can use.
Secondly, there are tools such as https://quicktype.io/typescript where you can type in your sample JSON and it’ll give you the full interface which you can then declare in your code using interface keyword.
See this code for example: https://github.com/sumurthy/officescripts-projects/blob/main/API%20Calls/APICall.ts
You don’t need to declare all properties – only the ones you’ll use.
It’s more up-front work – but in the end the quality is better.
Adding an interface definition for the expected JSON type fixed the problem for me.
interface Todo {
userId: number;
id: number;
title: string;
completed: boolean
}
async function main(workbook: ExcelScript.Workbook): Promise<void> {
let fetchResult = await fetch('https://jsonplaceholder.typicode.com/todos/1');
let json: Todo = await fetchResult.json();
console.log(json);
}
You may need to define a different interface if the Web API you're calling returns different data structure.

Firestore Timestamp gets converted to map when processed through cloud function

I am having an issue with the firebase.firestore.Timestamp class.
I am working on an Angular 6 app with firestore. I have been using Timestamps with no issues for a while, but I am in the process of moving some of my client-side services to cloud functions.
The issue is this:
When I perform a write operation directly from the client side, such as this:
const doc = { startTimestamp: firebase.firestore.Timestamp.fromDate(new Date()) };
firebase.firestore().doc('some_collection/uid).set(doc);
The document gets written correctly to firestore as a Timestamp.
However, when I send doc to a cloud function, then perform the write from the function, it gets written as a map, not a timestamp. Similarly, if I use a JS Date() object instead of a firestore.Timestamp, it gets written correctly from the client side but written as a string from the cloud function.
This makes sense given that the documents is just JSON in the request.body, I guess I was just hoping that firestore would be smart enough to implicitly handle the conversion.
For now, I have a workaround that just manually converts the objects into firestore.Timestamps again in the cloud function, but I am hoping that there is a more effective solution, possibly something buried in the SDK that I have not been able to find.
Has anyone else come across and, ideally, found a solution for this?
I can provide more code samples if needed.
The behavior you're observing is expected. The Firestore client libraries have a special interpretation of Timestamp type objects, and they get converted to a Timestamp type field in the database when written. However, if you try to serialize a Timestamp objects as JSON, you will just get an object with the timestamp's milliseconds and nanoseconds components. If you want to send these timestamp components to Cloud Functions or some other piece of software, that's fine, but that other piece of software is going to have to reconstitute a real Timestamp object from those parts before writing to Firestore with the Admin SDK or whatever SDK you're using to deal with Firestore.
in your class model, use the
#ServerTimestamp var timestamp: Date? = null
or
#ServerTimestamp Date timestamp = null
You can leave out initializing timestamp from your code, say new Date()
Example:
#IgnoreExtraProperties
data class ProductItem(
var userId: String? = "",
var avgRating: Double = 0.toDouble(),
#ServerTimestamp var timestamp: Date? = null
)
or
public class ProductItem() {
String userId;
Double avgRating;
#ServerTimestamp Date timestamp;
}

Add a Custom Header on a TISAPIRequest (Delphi 10.1 Datasnap Server)

Do you know how to manually add a custom header on a TISAPIRequest ?.
This class (or the most generic TWebRequest) doesn't expose a RawHeaders property to allow adding new customized Headers when needed.
PS: I have a dirty solution for when my WebRequest is a TIdHTTPAppRequest (Datasnap standalone server), because then I can create a Helper Class to access to its private FRequestInfo property, and from there gain access to a RawHeaders, which I can use to add a new header. But I only use standalone servers for development and testing, the production environment must run on IIS servers.
TIdHTTPAppRequestHelper = class helper for TIdHTTPAppRequest
public
function GetRequestInfo: TIdEntityHeaderInfo;
end;
implementation
function TIdHTTPAppRequestHelper.GetRequestInfo: TIdEntityHeaderInfo;
begin
Result := FRequestInfo;
end;
procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var Token: string;
begin
Response.SetCustomHeader('Access-Control-Allow-Origin','*');
Token := Request.Query;
if Copy(Token, 1, 10) = 'dssession=' then begin
if Request is TIdHTTPAppRequest then begin
TIdHTTPAppRequest(Request).GetRequestInfo.RawHeaders.AddValue('Pragma', Token);
end;
end;
if FServerFunctionInvokerAction <> nil then
FServerFunctionInvokerAction.Enabled := AllowServerFunctionInvoker;
end;
It is possible to write a similar code to get the same result (to add a custom header to your WebRequest) when the WebRequest is a TISAPIRequest instead of THTTPAppRequest ?.
Thank you.
TISAPIRequest has a public ECB property, which returns a pointer to the ISAPI EXTENSION_CONTROL_BLOCK structure that represents the underlying request data. However, the ECB does not allow you to alter the request headers in any way, only read values from them. You can set custom response headers via the ECB, though.
The only way I can find to add/modify custom request header values in ISAPI is to write an ISAPI Filter DLL, which is outside the scope of TISAPIRequest handling. Inside the DLL's exported HttpFilterProc() function, the SF_NOTIFY_PREPROC_HEADERS notification provides an HTTP_FILTER_PREPROC_HEADERS structure that contains pointers to AddHeader() and SetHeader() functions for adding/modifying request header values.

How to initialize a struct value fields using reflection?

I got a .ini configuration file that I want to use to initialize a Configuration struct.
I'd like to use the Configuration fields names and loop over them to populate my new instance with the corresponding value in the .ini file.
I thought the best way to achieve this might be reflection API (maybe I'm totally wrong, tell me...)
My problem here is that I cannot figure out how to access field's name (if it is at least possible)
Here is my code:
package test
import(
"reflect"
"gopkg.in/ini.v1"
)
type Config struct {
certPath string
keyPath string
caPath string
}
func InitConfig(iniConf *ini.File) *Config{
config:=new(Config)
var valuePtr reflect.Value = reflect.ValueOf(config)
var value reflect.Value = valuePtr.Elem()
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
if field.Type() == reflect.TypeOf("") {
//here is my problem, I can't get the field name, this method does not exist... :'(
value:=cfg.GetSection("section").GetKey(field.GetName())
field.SetString(value)
}
}
return config
}
Any help appreciated...
Use the type to get a StructField. The StructField has the name:
name := value.Type().Field(i).Name
Note that the ini package's File.MapTo and Section.MapTo methods implement this functionality.
While #MuffinTop solved your immediate issue, I'd say you may be solving a wrong problem. I personally know of at least two packages, github.com/Thomasdezeeuw/ini and gopkg.in/gcfg.v1, which are able to parse INI-style files (of the various level of "INI-ness", FWIW) and automatically populate your struct-typed values using reflection, so for you it merely amounts to properly setting tags on the fields of your struct (if needed at all).
I used both of these packages in production so am able to immediately recommend them. You might find more packages dedicated to parsing INI files on godoc.org.

Resources