| Package | Build Status | MyGet | Nuget |
|---|---|---|---|
| FMData | |||
| FMData.Rest | |||
| FMData.Rest.Auth.FileMakerCloud | |||
| FMData.Xml |
There are plenty of ways to consume RESTful APIs from .NET, but the goal of this project is to provide a blended FileMaker-idiomatic and .NET-idiomatic experience for developers consuming data from FileMaker databases in .NET applications.
The project is organized as three main packages, with a child Auth package for FileMaker Cloud:
FMDatais the core and it contains the base and abstract classes utilized by the other implementations.FMData.Restis for the Data API andFMData.Rest.Auth.FileMakerCloudis used for authentication to the Data API hosted by FileMaker Cloud
FMData.Xmlis for consuming the legacy Xml/CWP API.
Note: Xml support is experimental, if you need full cwp/xml coverage check out fmDotNet.
If you've found a bug, please submit a bug report. If you have a feature idea, open an issue and consider creating a pull request.
Install via dotnet add or nuget. Stable releases are on NuGet and CI builds are on MyGet.
dotnetaddpackageFMData.RestThe recommended way to consume this library is using a strongly typed model as follows.
Please review the /tests/FMData.Rest.Tests/ project folder for expected usage flows.
A model should roughly match a table in your solution. Its accessed via layout.
// use the DataContract attribute to link your model to a layout[DataContract(Name="NameOfYourLayout")]publicclassModel{[DataMember]publicstringName{get;set;}// if your model name does not match use DataMember[DataMember(Name="overrideFieldName")]// the internal database field to usepublicstringAddress{get;set;}[DataMember]publicstringSomeContainerField{get;set;}// use the ContainerDataFor attribute to map container data to a byte[][ContainerDataFor("SomeContainerField")]// use the name in your C# modelpublicbyte[]DataForSomeContainerField{get;set;}// if your model has properties you don't want mapped use[IgnoreDataMember]// to skip mapping of the fieldpublicstringNotNeededField{get;set;}}The simplest way to register FMData with dependency injection is using the built-in extension method. This sets up IHttpClientFactory, registers ConnectionInfo, and configures the client as a singleton (preserving auth token state across requests):
services.AddFMDataRest(conn =>{conn.FmsUri="https://example.com";conn.Database="FILE_NAME";conn.Username="user";conn.Password="password";});This registers both IFileMakerApiClient and IFileMakerRestClient. You can also pass a callback to configure the underlying HttpClient (e.g., to set timeouts):
services.AddFMDataRest(
conn =>{conn.FmsUri="https://example.com";conn.Database="FILE_NAME";conn.Username="user";conn.Password="password";},
httpClient =>{httpClient.Timeout=TimeSpan.FromSeconds(30);});The method returns an IHttpClientBuilder, so you can chain additional configuration like retry policies.
For the XML client, use AddFMDataXml from the FMData.Xml namespace:
services.AddFMDataXml(conn =>{conn.FmsUri="https://example.com";conn.Database="FILE_NAME";conn.Username="user";conn.Password="password";});Note: The
AddFMDataRestandAddFMDataXmlextension methods require netstandard2.0 or later. If you are targeting netstandard1.3 or net45, use the manual approach below.
You can also construct the client with an IHttpClientFactory directly:
varfactory=serviceProvider.GetRequiredService<IHttpClientFactory>();varclient=newFileMakerRestClient(factory,connectionInfo);Or use the traditional HttpClient-based approach:
services.AddSingleton<FMData.ConnectionInfo>(ci =>newFMData.ConnectionInfo{FmsUri="https://example.com",Username="user",Password="password",Database="FILE_NAME"});services.AddHttpClient<IFileMakerApiClient,FileMakerRestClient>();Behind the scenes, the injected HttpClient is kept alive for the lifetime of the FMData client (rest/xml) and reused throughout. The client stores FileMaker Data API tokens and reuses them as much as possible.
We can use the FileMakerRestClient, when the setup is done. Just create a new ConnectionInfo object and set the required properties:
varconn=newConnectionInfo();conn.FmsUri="https://{NAME}.account.filemaker-cloud.com";conn.Username="user@domain.com";conn.Password="********";conn.Database="Reporting";Then instantiate the FileMakerRestClient with a FileMakerCloudAuthTokenProvider as follows:
varfm=newFileMakerRestClient(newHttpClient(),newFileMakerCloudAuthTokenProvider(conn));For a full description of using FileMaker Data API with FileMaker Cloud, see this comment.
varclient=newFileMakerRestClient("server","fileName","user","pass");// without .fmp12vartoFind=newModel{Name="someName"};varresults=awaitclient.FindAsync(toFind);// results = IEnumerable<Model> matching with Name field matching "someName" as a FileMaker FindRequest.varclient=newFileMakerRestClient("server","fileName","user","pass");// without .fmp12vartoCreate=newModel{Name="someName",Address="123 Main Street"};varresults=awaitclient.CreateAsync(toCreate);// results is an ICreateResponse which indicates success (0/OK or Failure with FMS code/message)varclient=newFileMakerRestClient("server","fileName","user","pass");// without .fmp12varfileMakerRecordId=1;// this is the value from the calculation: Get(RecordID)vartoUpdate=newModel{Name="someName",Address="123 Main Street"};varresults=awaitclient.EditAsync(fileMakerRecordId,toCreate);// results is an IEditResponse which indicates success (0/OK or Failure with FMS code/message)Note you need to add an int property to the Model public int FileMakerRecordId { get; set; } and provide the Func to the FindAsync method to tell FMData how to map the FileMaker ID returned from the API to your model.
Func<Model,int,object>FMRecordIdMapper=(o,id)=>o.FileMakerRecordId=id;varclient=newFileMakerRestClient("server","fileName","user","pass");// without .fmp12vartoFind=newModel{Name="someName"};varresults=awaitclient.FindAsync(toFind,FMRecordIdMapper);// results is IEnumerable<Model> matching with Name field matching "someName" as a FileMaker FindRequest.vartoFind=newModel{Name="someName"};varreq=newFindRequest<Model>(){Layout=layout};req.AddQuery(toFind,false);var(data,info,scriptResponse)=awaitfdc.SendAsync(req,true);// scriptResponse.ScriptResult contains the post-request script result// scriptResponse.ScriptErrorPreRequest / ScriptResultPreRequest for pre-request scripts// scriptResponse.ScriptErrorPreSort / ScriptResultPreSort for pre-sort scriptsAll operations (Create, Edit, Delete, Find) return script results when scripts are specified on the request.
// Create with scriptsvarresponse=awaitclient.CreateAsync(input,"MyScript","param","PreRequestScript","preReqParam","PreSortScript","preSortParam");// response.Response.ScriptResult, ScriptResultPreRequest, ScriptResultPreSort// Edit with scriptsvareditResponse=awaitclient.EditAsync(recordId,"MyScript","param",input);// editResponse.Response.ScriptResult, ScriptResultPreRequest, ScriptResultPreSort// Delete with scripts (via IDeleteRequest)vardeleteReq=client.GenerateDeleteRequest();deleteReq.Layout="layout";deleteReq.RecordId=recordId;deleteReq.Script="MyScript";deleteReq.ScriptParameter="param";vardeleteResponse=awaitclient.SendAsync(deleteReq);// deleteResponse.Response.ScriptResult, ScriptResultPreRequest, ScriptResultPreSortAlternatively, if you create a calculated field Get(RecordID) and put it on your layout then map it the normal way.
By default, the FileMaker Data API limits portal records to 50 per request. You can control per-portal limit and offset using the fluent WithPortal builder or ConfigurePortal method on FindRequest<T>.
Use the fluent builder to chain portal configuration:
varreq=newFindRequest<Model>(){Layout="layout"};req.AddQuery(newModel{Name="someName"},false);// configure portals with limit and offsetreq.WithPortal("RelatedInvoices").Limit(100).Offset(1).WithPortal("LineItems").Limit(200);varresults=awaitclient.SendAsync(req);Or use ConfigurePortal directly:
varreq=newFindRequest<Model>(){Layout="layout"};req.AddQuery(newModel{Name="someName"},false);req.ConfigurePortal("RelatedInvoices",limit:100,offset:1);varresults=awaitclient.SendAsync(req);To include specific portals in the response without setting limits:
varreq=newFindRequest<Model>(){Layout="layout"};req.AddQuery(newModel{Name="someName"},false);req.IncludePortals("RelatedInvoices","LineItems");varresults=awaitclient.SendAsync(req);Portal parameters work with both find requests (POST to _find) and empty-query get-records requests (GET).
Make sure you use the [ContainerDataFor("NameOfContainer")] attribute along with a byte[] property for processing of your model.
varclient=newFileMakerRestClient("server","fileName","user","pass");// without .fmp12vartoFind=newModel{Name="someName"};varresults=awaitclient.FindAsync(toFind);awaitclient.ProcessContainers(results);// results = IEnumerable<Model> matching with Name field matching "someName" as a FileMaker FindRequest.// assume recordId = a FileMaker RecordId mapped using FMIdMapper// assume containerDataByteArray is a byte array with file contents of some sortvarclient=newFileMakerRestClient("server","fileName","user","pass");// without .fmp12_client.UpdateContainerAsync("layout",recordId,"containerFieldName","filename.jpg/png/pdf/etc",containerDataByteArray);Note: In order to create a record with container data two calls must be made. One that creates the actual record ( see above) and one that updates the container field contents.
Latest Versions
Older Versions
- FileMaker Data API Documentation (FMS18)
- FileMaker Server 18 Custom Web Publishing Guide
- FileMaker Data API Documentation (FMS17)
- FileMaker REST API Documentation (FMS16)-- Not Supported by this project.
- FileMaker Server 16 Web Publishing Guide
- FileMaker Server 15 Web Publishing Guide
We use Semantic Versioning. Using the Major.Minor.Patch syntax, we attempt to follow the basic rules
- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards-compatible manner, and
- PATCH version when you make backwards-compatible bugfixes.
