GithubHelp home page GithubHelp logo

fuzzzerd / fmdata Goto Github PK

View Code? Open in Web Editor NEW
27.0 10.0 10.0 1.36 MB

A simple and flexible FileMaker Data API Client for .NET. Use FMData to integrate your FileMaker data into .NET websites and applications.

Home Page: https://fmdata.io

License: MIT License

C# 100.00%
filemaker filemaker-api filemaker-data-api filemaker-integration data-api c-sharp dotnet-core json xml

fmdata's Introduction

FMData

fmdata logo, a C# client for FileMaker

Packages

Package Build Status MyGet Nuget
FMData .NET CI Build Myget NuGet
FMData.Rest .NET CI Build MyGet Pre Release NuGet
FMData.Rest.Auth.FileMakerCloud .NET CI Build FMData.Rest.Auth.FileMakerCloud NuGet
FMData.Xml .NET CI Build FMData.Xml NuGet

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:

  • FMData is the core and it contains the base and abstract classes utilized by the other implementations.
  • FMData.Rest is for the Data API and
    • FMData.Rest.Auth.FileMakerCloud is used for authentication to the Data API hosted by FileMaker Cloud
  • FMData.Xml is 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.

Repository Information

FMData repository/commit activity the past year

FMData issues

CodeFactor

Code size in bytes

Language Count

license

Installation

Install via dotnet add or nuget. Stable releases are on NuGet and CI builds are on MyGet.

dotnet add package FMData.Rest

Example Usage

The 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.

Setting up your model

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")]
public class Model
{
    [DataMember]
    public string Name { get; set; }

    // if your model name does not match use DataMember
    [DataMember(Name="overrideFieldName")] // the internal database field to use
    public string Address { get; set; }

    [DataMember]
    public string SomeContainerField { get; set; }

    // use the ContainerDataFor attribute to map container data to a byte[]
    [ContainerDataFor("SomeContainerField")] // use the name in your C# model
    public byte[] DataForSomeContainerField { get; set; }

    // if your model has properties you don't want mapped use
    [IgnoreDataMember] // to skip mapping of the field
    public string NotNeededField { get; set; }
}

Using IHttpClientFactory

Constructors take an HttpClient and you can setup the DI pipeline in Startup.cs like so for standard use:

services.AddSingleton<FMData.ConnectionInfo>(ci => new FMData.ConnectionInfo
{
    FmsUri = "https://example.com",
    Username = "user",
    Password = "password",
    Database = "FILE_NAME"
});
services.AddHttpClient<IFileMakerApiClient, FileMakerRestClient>();

If you prefer to use a singleton instance of IFileMakerApiClient you have to do a little bit more work in startup. This can improve performance if you're making lots of hits to the Data API over a single request to your application:

services.AddHttpClient(); // setup IHttpClientFactory in the DI container
services.AddSingleton<FMData.ConnectionInfo>(ci => new FMData.ConnectionInfo
{
    FmsUri = "https://example.com",
    Username = "user",
    Password = "password",
    Database = "FILE_NAME"
});
// Keep the FileMaker client as a singleton for speed
services.AddSingleton<IFileMakerApiClient, FileMakerRestClient>(s => {
    var hcf = s.GetRequiredService<IHttpClientFactory>();
    var ci = s.GetRequiredService<ConnectionInfo>();
    return new FileMakerRestClient(hcf.CreateClient(), ci);
});

Behind the scenes, the injected HttpClient is kept alive for the lifetime of the FMData client (rest/xml) and reused throughout. This is useful to manage the lifetime of IFileMakerApiClient as a singleton, since it stores data about FileMaker Data API tokens and reuses them as much as possible. Simply using services.AddHttpClient<IFileMakerApiClient, FileMakerRestClient>(); keeps the lifetime of our similar to that of a 'managed HttpClient' which works for simple scenarios.

Test both approaches in your solution and use what works.

Authentication with FileMaker Cloud

We can use the FileMakerRestClient, when the setup is done. Just create a new ConnectionInfo object and set the required properties:

var conn = new ConnectionInfo();
conn.FmsUri = "https://{NAME}.account.filemaker-cloud.com";
conn.Username = "[email protected]";
conn.Password = "********";
conn.Database = "Reporting";

Then instantiate the FileMakerRestClient with a FileMakerCloudAuthTokenProvider as follows:

var fm = new FileMakerRestClient(new HttpClient(), new FileMakerCloudAuthTokenProvider(conn));

For a full description of using FileMaker Data API with FileMaker Cloud, see this comment.

Performing a Find

var client = new FileMakerRestClient("server", "fileName", "user", "pass"); // without .fmp12
var toFind = new Model { Name = "someName" };
var results = await client.FindAsync(toFind);
// results = IEnumerable<Model> matching with Name field matching "someName" as a FileMaker FindRequest.

Create a new record

var client = new FileMakerRestClient("server", "fileName", "user", "pass"); // without .fmp12
var toCreate = new Model { Name = "someName", Address = "123 Main Street" };
var results  = await client.CreateAsync(toCreate);
//  results is an ICreateResponse which indicates success (0/OK or Failure with FMS code/message)

Updating a record

var client = new FileMakerRestClient("server", "fileName", "user", "pass"); // without .fmp12
var fileMakerRecordId = 1; // this is the value from the calculation: Get(RecordID)
var toUpdate = new Model { Name = "someName", Address = "123 Main Street" };
var results = await client.EditAsync(fileMakerRecordId, toCreate);
//  results is an IEditResponse which indicates success (0/OK or Failure with FMS code/message)

Find with FileMaker ID Mapping

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;
var client = new FileMakerRestClient("server", "fileName", "user", "pass"); // without .fmp12
var toFind = new Model { Name = "someName" };
var results = await client.FindAsync(toFind, FMRecordIdMapper);
// results is IEnumerable<Model> matching with Name field matching "someName" as a FileMaker FindRequest.

Find with Data Info

var toFind = new Model { Name = "someName" };
var req = new FindRequest<Model>() { Layout = layout };
req.AddQuery(toFind, false);
var (data, info) = await fdc.SendAsync(req, true);

Alternatively, if you create a calculated field Get(RecordID) and put it on your layout then map it the normal way.

Find and load Container Data

Make sure you use the [ContainerDataFor("NameOfContainer")] attribute along with a byte[] property for processing of your model.

var client = new FileMakerRestClient("server", "fileName", "user", "pass"); // without .fmp12
var toFind = new Model { Name = "someName" };
var results = await client.FindAsync(toFind);
await client.ProcessContainers(results);
// results = IEnumerable<Model> matching with Name field matching "someName" as a FileMaker FindRequest.

Insert or Update Container Data

// assume recordId = a FileMaker RecordId mapped using FMIdMapper
// assume containerDataByteArray is a byte array with file contents of some sort
var client = new FileMakerRestClient("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.

FileMaker Documentation

Latest Versions

Older Versions

Versioning

We use Semantic Versioning. Using the Major.Minor.Patch syntax, we attempt to follow the basic rules

  1. MAJOR version when you make incompatible API changes,
  2. MINOR version when you add functionality in a backwards-compatible manner, and
  3. PATCH version when you make backwards-compatible bugfixes.

fmdata's People

Contributors

dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar fuzzzerd avatar heebinho avatar inf13 avatar jaksolutionsgithub avatar r-hathcock avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fmdata's Issues

XML Global Field Support

Is your feature request related to a problem? Please describe.
Unable to set global field values for xml requests.

Describe the solution you'd like
Need to manage a small queue on the SetGlobalField implementation; that will add the global field data to the next outgoing request.

Describe alternatives you've considered
Specify the global field as part of the request directly, this is kind of ugly and dirties up the intent of the consuming code.

Additional context
Details on the documentation: https://fmhelp.filemaker.com/docs/16/en/fms16_cwp_guide.pdf#G8.76964

Get Databases

Task list:

  • Update IFileMakerClient with GetDatabasesAsync method
  • Define database response type (IEnumerable or a whole new type?)
  • Write Rest client implementation
  • Write Xml client implementation, reference fmDotNet structure
  • Test data, harness, and coverage

note: this api uses basic authentication, the same as getting a token

Data API empty Find

When performing an empty find request, the client should switch to the underlying get records api to avoid throwing or returning nothing.

Nested array filtering

The find API call returns nested array - as in the example below (NestedActions).

I would like apply the following query: get all records that have fielddata.ES_UTC_Time >= 63397939255000 and apply the same date filter against the NestedActions::ES_UTC_Time.

{"query":[{"ES_UTC_Time":">= 63397939255000"},{"NestedActions::ES_UTC_Time":">= 63397939255000"}],"offset":"1", "limit":"100","sort":[{"fieldName":"ES_UTC_Time","sortOrder":"descend"}]}

I would like to return the parent record even if there are no child NestedActions.

Is this even possible when using the FM API find call ? How would I need to structure the fmdata filter to achieve this?

Appreciate a any help with this.

Thanks.

Data:
{
"response": {
"data": [
{
"fieldData": {
"ES_RECORD_ID": 5.1424603688874037043940639545611105393e+37,
"ES_UTC_Time": 63677801587705
},
"portalData": {
"NestedActions": [
{
"recordId": "26",
"NestedActions::ES_UTC_Time": 63675911309428,
"NestedActions::ES_RECORD_ID": 3.58945963486201731120694061631247244196e+38,
"modId": "0"
},
{
"recordId": "27",
"NestedActions::ES_UTC_Time": 63677800967265,
"NestedActions::ES_RECORD_ID": 4.11367583929358143594030872930602025971e+38,
"modId": "0"
}
],
"FormBeforeEventTimeline": [],
"FormAfterEventTimeline": []
},
"recordId": "46",
"modId": "12"
},
{
"fieldData": {
"ES_RECORD_ID": 2.32825376899054305232124784142823827507e+38,
"ES_UTC_Time": 63675551352139
},
"portalData": {
"NestedActions": [],
"FormBeforeEventTimeline": [],
"FormAfterEventTimeline": []
},
"recordId": "45",
"modId": "15"
}
]
},
"messages": [
{
"code": "0",
"message": "OK"
}
]
}

Create public interface for creating Request objects directly

The existing Request Factories are protected which limits their usefulness to outside consumers that would rather utilize the SendAsync overloads than the Create, Find, Edit, Delete methods on the client.

Need to determine if it makes sense to replace the existing protected methods, make them public, or make the public methods consume the protected overloads, keeping both.

Add ProcessContainer's attribute to find request object.

Is your feature request related to a problem? Please describe.
Should not need two calls to process containers.

Describe the solution you'd like
Add flag to request model to indicate if containers should be processed or not.

Describe alternatives you've considered
Open to suggestions.

Data/Rest API Token Management

Token management should be transparent to the consumers via the main interface.

Token should be automatically refreshed when necessary assuming default timeout of 15min.

Arrange Tests By Overload Method Under Test

Navigating the test projects is challenging due to the organic nature in which they were added. They need to be reorganized to more clearly map to the method under test.

Get Product Information

Couple things...

  • Update IFileMakerClient with GetProductInfoAsync
  • Setup Product Info Response Type
  • Update Rest Client
  • Update Xml Client
  • Test coverage / updates

ContainerData : Object reference not set to an instance of an object

Hey,

I'm trying to use the ContainerData but i always receive an error which is "Object reference not set to an instance of an object.".
My container contains a picture on my layout and in my model i've created a byte[] to receive it.

        [ContainerDataFor("Picture_gr")] // use the name in your C# model
        public byte[] Member_Picture { get; set; }

Maybe i'm doing something wrong but there is only 4 lines of code...

                FileMakerRestClient client = new FileMakerRestClient(serverName, fileName, userName, password);
                var toFind = new Models.Members { Member_Zkp_WEB = ViewBag.sessionv };
                var results = await client.FindAsync(toFind);
                await client.ProcessContainers(results);

Did you know about this error? Maybe you know a workaround or how to fix it?

Thx in advance!

Korp

Perform find request doest no work

Hello,

Maybe I'm wrong in running the code but I have a class "Members" that has more than 20 fields and I run client.FindAsync (toFind) I always have an error.

Indeed, toFind contains my class (with string and int) and sends the complete class.

I feel like this perform find is trying to find a record in Filemaker that has exactly the same values than my class and not just "Member_Zkp_WEB".
The problem is that my class model contains some var who doesnt exist in Filemaker. But it looks like it tries to perform the find on the whole class skeleton.

If comment almost all my vars in my class and i only let Member_Zkp_Web and for example Member_LastName the perform find works en results is giving me the correct record it found.

ViewBag.sessionv contains 26, which exist on my layout.

                FileMakerRestClient client = new FileMakerRestClient("https://fms171.hostmy.solutions", "helloJAK", userName, password);
                var toFind = new Models.Members { Member_Zkp_WEB = ViewBag.sessionv };
                var results = await client.FindAsync(toFind);

Encapsulate connection parameters into a single model for injection.

Is your feature request related to a problem? Please describe.
Unable to take advantage of ASP.NET Core 2.1's HttpClientFactory.

Describe the solution you'd like
Encapsulate the connection parameters server, database, user, password into a class/model which can be registered with dependency injection frameworks in conjunction with the existing HttpClient parameter to more thoroughly take advantage of HttpClientFactory.

https://www.stevejgordon.co.uk/httpclientfactory-named-typed-clients-aspnetcore

Describe alternatives you've considered
Could also accept an instance of HttpClientFactory instead of HttpClient, but that might require giving up Net Standard 1.3.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.