Referencia API
La superficie real de ApiController, req y res: lo que existe, con su firma. Si un miembro no está aquí, no está.
ApiController
Clase base de la que heredan todos los controladores. Da las utilidades de JSON, los resultados con su código de estado, el registro y los dos ganchos del ciclo de vida. El JWT NO se genera aquí: lo emite el middleware cuando el login contesta con X-Auth-Status.
Métodos de respuesta
| Propiedad / Método | Tipo | Descripción |
Ok(obj) | 200 | El resultado normal. Serializa el objeto y responde 200. Es la forma preferible, y la que respeta el responseformat del servidor. |
OkRaw(json) | 200 | Igual, pero con un JSON que ya tienes hecho. Es el que va detrás de TableToJson(). |
Created(obj, location) | 201 | Para lo que acabas de crear. location es opcional y pone la cabecera Location. |
NoContent() | 204 | Fue bien y no hay nada que devolver. Típico de un DELETE. |
BadRequest(msg, code) | 400 | Lo que mandó el cliente no vale. code es tuyo y viaja en el Problem Details. |
Unauthorized(msg) · Forbidden(msg) | 401 · 403 | Sin identificar, o identificado y sin permiso. |
NotFound(msg) | 404 | No existe lo que pidió. |
Fail(status, msg, code) | el que digas | Cualquier otro error, con el código de estado que le pongas. |
newObject(fields) | Object | Crea un objeto VFP con esas propiedades a NULL. fields es una lista separada por comas. |
ToJson(obj) · ParseJson(txt) | String · Object | Serializar y deserializar, vía JSONFox. El inverso de ToJson se llama ParseJson: no hay ningún FromJson. |
LogInfo · LogWarning · LogError · LogFromException | Void | Escriben en el log del servidor. El último vuelca la excepción entera, con su línea. |
Qué sale cuando devuelves un error
Desde 0.9.15, los resultados de error se describen con Problem Details (RFC 9457), el mismo formato que usa ASP.NET, y viajan con Content-Type: application/problem+json; charset=utf-8.
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "No existe el artículo ZZZZ",
"instance": "/articulos/ZZZZ",
"code": "not_found"
}
detail es tu mensaje, el que le pasaste a this.NotFound(...); code es el tuyo y viaja como miembro de extensión, que es lo que la RFC permite para lo que no es suyo; title es la frase de razón del código y no se traduce, porque es legible por máquina; instance la pone el motor. Los 200 NO cambian: salen sin sobre. Y si tu servidor declara responseformat: foxserver, ese formato tampoco cambia.
Hooks del ciclo de vida
| Propiedad / Método | Tipo | Descripción |
BeforeEndpoint(req, res) | Boolean | Corre antes de cada endpoint DE ESE CONTROLADOR. Devolver .F. corta la petición. Es donde se abren las tablas. |
AfterEndpoint(req, res) | Void | Corre después de cada endpoint de ese controlador. Es donde se cierran. |
FoxServerRequest
Objeto que encapsula toda la información de la petición HTTP entrante. Disponible como parámetro req en todos los endpoints.
Propiedades
| Propiedad / Método | Tipo | Descripción |
Method | String | GET, POST, PUT, PATCH, DELETE |
uri | Object | Un OBJETO, no una cadena: Scheme, Host, Port, AbsolutePath, PathAndQuery y AbsoluteUri. La ruta sin la query está en uri.AbsolutePath. |
Params | Object | Parámetros de la ruta. req.Params.id desde /{id}, o req.GetParam("id", "") con valor por defecto. |
query | Object | Query string. req.query.page desde ?page=2, o req.GetQuery("page", 1). |
Headers | Object | Las cabeceras. Se leen con req.GetHeader("Authorization", "") y se comprueban con req.HasHeader(). |
RawBody | String | El cuerpo sin parsear. También req.GetRawBody(). Es lo que hay que leer cuando el cuerpo puede no venir, o cuando una fecha no debe pasar por JSONFox. |
json | Object | El cuerpo JSON ya parseado. req.HasJsonBody() dice si lo hay, y req.HasJsonError() / req.JsonError si no se pudo parsear. |
ContentType | String | El Content-Type de la petición. |
AuthenticatedUser | Object | Con el token ya validado: .id, .name y .role. |
Claims | Object | Los claims extra del token. req.GetClaim(nombre, defecto), req.HasClaim(nombre) y req.ClaimNames(). |
MultipartData · FormFields | Object | Subidas y formularios: req.file(campo), req.files(campo), req.HasFiles(), req.GetFormField(campo, defecto). |
req.path, req.body, req.remoteIP y req.scheme no existen Y leer una propiedad que no existe devuelve vacío sin dar error, que es la peor forma de equivocarse. La ruta está en req.uri.AbsolutePath, el cuerpo en req.RawBody y el método en req.Method. La IP del cliente NO se expone al controlador.
Métodos
ABCLeer parámetros, cabeceras y el cuerpo JSON
* Un parametro de ruta, con valor por defecto
lcId = req.GetParam("id", "")
* Una cabecera: GetHeader, no req.headers[...]
IF req.HasHeader("X-Api-Key")
lcKey = req.GetHeader("X-Api-Key", "")
ENDIF
* El JSON del cuerpo, con cuidado: leer una propiedad que no vino
* da error 1734, asi que se comprueba antes o se envuelve en TRY.
IF req.HasJsonBody() AND !ISNULL(req.json)
TRY
lcCiudad = NVL(req.json.direccion.ciudad, "")
CATCH
lcCiudad = ""
ENDTRY
ENDIF
FoxServerResponse
Objeto para construir y enviar la respuesta HTTP. Disponible como parámetro res en todos los endpoints. Todos los métodos son encadenables (retornan res).
| Propiedad / Método | Descripción |
status(code) | Establece el código de estado HTTP (200, 201, 400, 401, 404, 500...) |
json(jsonStr) | Envia body JSON. Establece Content-Type: application/json automáticamente. |
send(text) | Envia texto plano o HTML como body. |
header(key, value) | Agrega o sobreescribe un header de respuesta. Encadenable. |
location(url) | Establece el header Location. Usar con status(301) o status(302) para redirección. |
sendFile(path, disp) | Envía un fichero. disp puede ser "inline" (mostrar en el navegador) o "attachment" (forzar la descarga). |
events(tipo) | Declara un tipo de evento SSE que este endpoint soporta. Sin declararlo, nadie se puede suscribir a él. |
emit(tipo, json) | Emite un evento SSE desde cualquier endpoint. El framing, el retry y los pings los pone el middleware. |
Ejemplo
ABCResponder encadenando los métodos de res
* Standard success response
res.status(200).json(THIS.ToJson(loResult))
* Created with location header
res.status(201)
.header("Location", "/api/v1/users/" + lcNewId)
.json(THIS.ToJson(loResult))
* Error response
res.status(400).json('{"error":"Name is required"}')
* File download
res.status(200)
.header("Content-Disposition", 'attachment; filename="report.pdf"')
.sendFile("C:\reports\report.pdf", "attachment")
Métodos detallados de ApiController
Gestión de objetos y JSON
| Propiedad / Método | Descripción |
NewObject(fields) | Crea un objeto VFP con propiedades inicializadas a NULL. fields es una lista separada por comas: "status,data,message". |
NewObjectFromCursor(cursor, session) | Crea un objeto VFP con la estructura de un cursor. Útil para plantillas basadas en estructuras de tabla. |
ToJson(obj) | Serializa un objeto VFP a cadena JSON via JSONFox. |
ParseJson(jsonStr) | Deserializa una cadena JSON a objeto VFP via JSONFox. |
TableToJson(cursor, currentRow) | Convierte un cursor a JSON. currentRow=.T. devuelve el registro actual como objeto; .F. devuelve todos los registros como array. |
TableToJsonObject(cursor, currentRow, session) | Igual que TableToJson pero retorna una colección de objetos VFP en lugar de cadena JSON. Útil para manipular datos antes de la serialización final. |
MasterToJSON(master, detail, expr, attr) | Genera JSON jerárquico maestro-detalle. expr es la condición de join (ej: "master.id = detail.master_id"), attr es el nombre del array de detalle en el JSON. |
ABCMasterToJSON: pedidos con sus líneas
* Ejemplo: maestro-detalle con MasterToJSON
SELECT id, fecha, cliente FROM pedidos INTO CURSOR curPedidos
SELECT id, pedido_id, producto, cantidad FROM lineas INTO CURSOR curLineas
lcJson = THIS.MasterToJSON("curPedidos", "curLineas", "curPedidos.id = curLineas.pedido_id", "lineas")
res.status(200).json(lcJson)
Utilidades de texto y formato
| Propiedad / Método | Descripción |
Format(template, p0..p10) | Sustituye marcadores {0}, {1}, ... en la cadena template. Similar a String.Format de C#. |
EscapeCharsToJSON(text) | Escapa caracteres especiales para uso seguro en JSON (acentos, barras, comillas). |
EscapeCharsToHTML(text) | Escapa caracteres especiales para uso en HTML (&, <, >, acentos). |
Content(req, source) | Serializa source (cursor, objeto o string) al formato que el cliente solicita via Accept header o ?format= (json, xml, csv). |
TableToXml(cursor, currentRow) | Convierte un cursor a XML. |
TableToCsv(cursor, currentRow) | Convierte un cursor a CSV. |
Utilidades de fecha, codificación e identificadores
| Propiedad / Método | Tipo | Descripción |
NewGuid() | String | Genera un GUID sin llaves ni guiones. Usar para todas las PKs. |
DTOUNX(dt, useMsec) | Number | Convierte un DateTime VFP a timestamp Unix. useMsec=.T. devuelve milisegundos. |
UNXTOD(timestamp) | DateTime | Convierte un timestamp Unix (segundos o milisegundos) a DateTime VFP. |
Base64URLEncode(input) | String | Codifica en Base64URL (usado en JWT y URLs seguras). |
Base64URLDecode(input) | String | Decodifica desde Base64URL. |
ABCFechas Unix, GUID y Format
* Conversiones de fecha
lnTs = THIS.DTOUNX(DATETIME(), .F.) && segundos
ldFecha = THIS.UNXTOD(1716825600)
* Identificadores
lcId = THIS.NewGuid() && "a1b2c3d4e5f647g8h9i0j1k2l3m4n5o6"
* Formato de mensajes
lcMsg = THIS.Format("Usuario {0} accedio a {1} desde {2}", lcUser, lcUrl, lcIp)
Ciclo de vida y recursos
| Propiedad / Método | Descripción |
Initialize() | Se ejecuta automáticamente al crear una instancia. Configura cPath, carga JSONFox y ApiService. |
LoadJsonFox() | Carga JSONFox.app si aún no esta disponible. Llamado internamente por Initialize(). |
GetGlobalResource(name) | Obtiene un recurso global compartido (conexiones, configuraciones) a traves del ciclo de vida de la aplicación. |
SetWorkingDirectory(path) | Establece cPath y llama a Initialize(). Útil para testing fuera del ciclo de FoxServer. |
CleanUp() / Release() / Dispose() | Libera recursos al finalizar el ciclo de vida del controlador. Invocados automáticamente por FoxServer. |
FoxServerRequest: Métodos detallados
| Propiedad / Método | Descripción |
GetHeader(key, default) | Retorna el valor del header HTTP indicado. Si no existe retorna default. |
GetParam(key, default) | Retorna un parámetro de ruta URL (ej: {id}). Retorna default si no existe. |
GetQuery(key, default) | Retorna un parámetro de query string (?key=value). Retorna default si no existe. |
GetQueryParams() | Retorna todos los parámetros de query como FoxServerTupleDictionary. |
HasJsonBody() | .T. si la petición tiene body JSON valido y parseado. Verificar antes de acceder a req.json.*. |
GetRawBody() | Retorna el body de la petición sin parsear. Necesario para verificar firmas de webhooks. |
GetFormField(key, default) | Retorna el valor de un campo de formulario multipart o urlencoded. |
HasFormField(key) | .T. si el campo de formulario existe en la petición. |
File(field) | Retorna el objeto de un archivo subido via multipart. Expone FileName y SaveToFile(path). |
Files(field) | Retorna una colección de archivos subidos bajo el mismo nombre de campo. |
ABCCabeceras, cuerpo, ficheros subidos y firma de webhook
* Leer headers y params
lcToken = req.GetHeader("Authorization", "")
lcId = req.GetParam("id", "")
lcPage = req.GetQuery("page", "1")
* Verificar body antes de acceder
IF req.HasJsonBody()
lcEmail = req.json.email
ELSE
res.status(400).json('{"error":"JSON body required"}')
RETURN
ENDIF
* Subir archivo
loFile = req.File("avatar")
IF !ISNULL(loFile)
loFile.SaveToFile(THIS.cPath + "uploads\" + loFile.FileName)
ENDIF
* Webhook: raw body para HMAC
lcRaw = req.GetRawBody()
lcSig = req.GetHeader("X-Signature", "")
Colecciones
FoxServerTupleDictionary
Diccionario clave-valor usado para Headers, Params y Query. Acceso case-insensitive.
| Propiedad / Método | Descripción |
Count | Número de entradas. |
GetKey(n) | Clave en la posición n (base 1). |
GetValue(n) | Valor en la posición n. |
Contains(key) | .T. si la clave existe. |
GetValueByKey(key) | Valor por nombre de clave. |
ABCRecorrer todos los parámetros de la query
* Iterar todos los query params
loParams = req.GetQueryParams()
FOR lnI = 1 TO loParams.Count
lcKey = loParams.GetKey(lnI)
lcVal = loParams.GetValue(lnI)
THIS.LogInfo(THIS.Format("Param {0}={1}", lcKey, lcVal))
ENDFOR
FoxServerTuple
Par clave-valor simple. Componente interno de FoxServerTupleDictionary.
| Propiedad / Método | Descripción |
Key | Nombre de la clave. |
Value | Valor asociado. |
Siguiente: Preguntas frecuentes →