Ejemplo n.º 1
0
        /// <summary>
        /// Send a Find Record request to the FileMaker API.
        /// </summary>
        public virtual async Task <IEnumerable <T> > SendAsync <T>(
            IFindRequest <T> req,
            Func <T, int, object> fmId,
            Func <T, int, object> modId) where T : class, new()
        {
            var(data, info) = await SendAsync(req, false, fmId, modId).ConfigureAwait(false);

            return(data);
        }
Ejemplo n.º 2
0
 /// <summary>
 /// Adds a pre request script to the request, with a parameter.
 /// </summary>
 /// <param name="request">The request. This is the 'this' parameter.</param>
 /// <param name="scriptName">Name of the script to be called.</param>
 /// <param name="scriptParameter">Script parameter.</param>
 /// <returns>The request instanced that was implicitly passed in which is useful for method chaining.</returns>
 public static IFindRequest <T> SetPreRequestScript <T>(
     this IFindRequest <T> request,
     string scriptName,
     string scriptParameter = null)
 {
     request.PreRequestScript = scriptName;
     if (!string.IsNullOrEmpty(scriptParameter))
     {
         request.PreRequestScriptParameter = scriptParameter;
     }
     return(request);
 }
Ejemplo n.º 3
0
 /// <summary>
 /// Send a Find Record request to the FileMaker API.
 /// </summary>
 public virtual Task <IEnumerable <T> > SendAsync <T>(
     IFindRequest <T> req) where T : class, new() => SendAsync(req, null, null);
Ejemplo n.º 4
0
 /// <summary>
 /// Send a Find Record request to the FileMaker API.
 /// </summary>
 public abstract Task <IFindResponse <Dictionary <string, string> > > SendAsync(IFindRequest <Dictionary <string, string> > req);
Ejemplo n.º 5
0
        /// <summary>
        /// Executes a Find Request and returns the matching objects projected by the type parameter.
        /// </summary>
        /// <typeparam name="T">The type to project the results against.</typeparam>
        /// <param name="req">The Find Request Command.</param>
        /// <param name="fmId">The function to map FileMaker Record Ids to an instance of T.</param>
        /// <param name="modId">The function to map FileMaker Modid to an instance of T</param>
        /// <returns>The projected results matching the find request.</returns>
        public override async Task <IEnumerable <T> > SendAsync <T>(
            IFindRequest <T> req,
            Func <T, int, object> fmId,
            Func <T, int, object> modId)
        {
            HttpResponseMessage response = await ExecuteRequestAsync(req);

            if (response.IsSuccessStatusCode)
            {
                // process response data return OK
                var xdoc = XDocument.Load(await response.Content.ReadAsStreamAsync());

                // act
                var metadata = xdoc
                               .Descendants(_ns + "metadata")
                               .Elements(_ns + "field-definition")
                               .ToDictionary(
                    k => k.Attribute("name").Value,
                    v => v.Attribute("result").Value
                    );

                // load in relatedSet metadatas
                var relatedMeta = xdoc
                                  .Descendants(_ns + "metadata")
                                  .Elements(_ns + "relatedset-definition")
                                  .ToDictionary(
                    k => k.Attribute("table").Value,
                    val => val.Elements(_ns + "field-definition")
                    .ToDictionary(
                        k => k.Attribute("name").Value,
                        v => v.Attribute("result").Value
                        )
                    );

                var dict    = new Dictionary <string, string>();
                var records = xdoc
                              .Descendants(_ns + "resultset")
                              .Elements(_ns + "record")
                              .Select(r => new RecordBase <T, Dictionary <string, IEnumerable <Dictionary <string, object> > > >
                {
                    RecordId   = Convert.ToInt32(r.Attribute("record-id").Value),
                    ModId      = Convert.ToInt32(r.Attribute("mod-id").Value),
                    FieldData  = FieldDataToDictionary(metadata, r.Elements(_ns + "field")).ToObject <T>(),
                    PortalData = r.Elements(_ns + "relatedset")
                                 .ToDictionary(
                        k => k.Attribute("table").Value,
                        v => v.Elements(_ns + "record")
                        .Select(rc =>
                                FieldDataToDictionary(
                                    relatedMeta[v.Attribute("table").Value],
                                    rc.Elements(_ns + "field")
                                    )
                                )
                        )
                })
                              .ToList(); // make sure to ToList here since if we don't subsequent setting of child fields/properties are lost for every time its enumerated again

                // handle record and modid
                foreach (var record in records)
                {
                    fmId?.Invoke(record.FieldData, record.RecordId);
                    modId?.Invoke(record.FieldData, record.ModId);

                    // TODO: update each record's FieldData instance with the contents of its PortalData
                    var portals = typeof(T).GetTypeInfo().DeclaredProperties.Where(p => p.GetCustomAttribute <PortalDataAttribute>() != null);
                    foreach (var portal in portals)
                    {
                        var portalDataAttr     = portal.GetCustomAttribute <PortalDataAttribute>();
                        var namedPortal        = portalDataAttr.NamedPortalInstance;
                        var portalInstanceType = portal.PropertyType.GetTypeInfo().GenericTypeArguments[0];
                        var pt = portal.PropertyType;

                        var dataPortal = record.PortalData[namedPortal].ToList();

                        // .ToList() here so we iterate on a different copy of the collection
                        // which allows for calling add/remove on the list ;) clever
                        // https://stackoverflow.com/a/26864676/86860 - explination
                        // https://stackoverflow.com/a/604843/86860 - solution
                        foreach (var row in dataPortal.ToList())
                        {
                            foreach (var kvp in row.ToList())
                            {
                                if (kvp.Key.Contains(portalDataAttr.TablePrefixFieldNames + "::"))
                                {
                                    row.Add(kvp.Key.Replace(portalDataAttr.TablePrefixFieldNames + "::", ""), kvp.Value);
                                    row.Remove(kvp.Key);
                                }
                            }
                        }

                        var x = dataPortal.Select(portalRow => portalRow.ToObject(portalInstanceType));
                        var y = (IList)Activator.CreateInstance(typeof(List <>).MakeGenericType(portalInstanceType));
                        foreach (var z in x)
                        {
                            y.Add(z);
                        }
                        portal.SetValue(record.FieldData, y);
                    }
                }

                var results = records.Select(r => r.FieldData);

                // make container processing part of the request, IF specified in the original request.
                if (req.LoadContainerData)
                {
                    await ProcessContainers(results);
                }

                return(results);
            }

            return(null);
        }
Ejemplo n.º 6
0
 /// <summary>
 /// Find a record using a dictionary of input parameters.
 /// </summary>
 public override Task <IFindResponse <Dictionary <string, string> > > SendAsync(IFindRequest <Dictionary <string, string> > req)
 {
     throw new NotImplementedException();
 }
Ejemplo n.º 7
0
 /// <summary>
 /// Helper For Getting Raw Responses from Data API.
 /// </summary>
 public Task <HttpResponseMessage> ExecuteRequestAsync <T>(IFindRequest <T> req) => ExecuteRequestAsync(HttpMethod.Post, FindEndpoint(req.Layout), req);
Ejemplo n.º 8
0
        /// <summary>
        /// Strongly typed find request.
        /// </summary>
        /// <typeparam name="T">The type of response objects to return.</typeparam>
        /// <param name="req">The find request parameters.</param>
        /// <param name="fmId">Function to assign the FileMaker RecordId to each instance of {T}.</param>
        /// <param name="modId">Function to assign the FileMaker ModId to each instance of {T}.</param>
        /// <returns>An <see cref="IEnumerable{T}"/> matching the request parameters.</returns>
        public override async Task <IEnumerable <T> > SendAsync <T>(
            IFindRequest <T> req,
            Func <T, int, object> fmId  = null,
            Func <T, int, object> modId = null)
        {
            if (string.IsNullOrEmpty(req.Layout))
            {
                throw new ArgumentException("Layout is required on the find request.");
            }

            var uri    = FindEndpoint(req.Layout);
            var method = HttpMethod.Post;

            if (req.Query == null || req.Query.Count() == 0)
            {
                // if this is an empty query, just punch it in to the Records API instead of the Find API.
                uri    = GetRecordsEndpoint(req.Layout, req.Limit, req.Offset);
                method = HttpMethod.Get;
            }

            var response = await ExecuteRequestAsync(method, uri, req);

            if (response.StatusCode == HttpStatusCode.OK)
            {
                var responseJson = await response.Content.ReadAsStringAsync();

                JObject joResponse = JObject.Parse(responseJson);

                // get JSON result objects into a list
                IList <JToken> results = joResponse["response"]["data"].Children().ToList();

                // serialize JSON results into .NET objects
                IList <T> searchResults = new List <T>();
                foreach (JToken result in results)
                {
                    T searchResult = ConvertJTokenToInstance(fmId, modId, result);

                    // add to response list
                    searchResults.Add(searchResult);
                }

                // make container processing part of the request, IF specified in the original request.
                if (req.LoadContainerData)
                {
                    await ProcessContainers(searchResults);
                }

                return(searchResults);
            }

            if (response.StatusCode == HttpStatusCode.InternalServerError)
            {
                try
                {
                    // attempt to read response content
                    if (response.Content == null)
                    {
                        throw new Exception("Could not read response from Data API.");
                    }

                    var responseJson = await response.Content.ReadAsStringAsync();

                    var responseObject = JsonConvert.DeserializeObject <BaseResponse>(responseJson);
                    if (responseObject.Messages.Any(m => m.Code == "401"))
                    {
                        // filemaker no records match the find request => empty list.
                        return(new List <T>());
                    }

                    throw new Exception(responseObject.Messages.First().Message);
                }
                catch (Exception ex)
                {
                    throw new Exception("Could not read response from Data API.", ex);
                }
            }

            // not found, so return empty list
            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                return(new List <T>());
            }

            // other error TODO: Improve handling
            throw new Exception($"Find Request Error. Request Uri: {response.RequestMessage.RequestUri} responded with {response.StatusCode}");
        }
Ejemplo n.º 9
0
 /// <summary>
 /// Indicate that container data should be processed.
 /// </summary>
 /// <param name="request">The request. This is the 'this' parameter.</param>
 /// <typeparam name="T">The type used for the find request/response.</typeparam>
 /// <returns>The request instanced that was implicitly passed in which is useful for method chaining.</returns>
 public static IFindRequest <T> LoadContainers <T>(this IFindRequest <T> request)
 {
     request.LoadContainerData = true;
     return(request);
 }
Ejemplo n.º 10
0
 /// <summary>
 /// Specify a layout to use for this request.
 /// </summary>
 /// <param name="request">The request. This is the 'this' parameter.</param>
 /// <param name="instance">Object to pull the layout from using its DataContract attribute.</param>
 /// <typeparam name="T">The type used for the find request/response.</typeparam>
 /// <returns>The request instanced that was implicitly passed in which is useful for method chaining.</returns>
 public static IFindRequest <T> UseLayout <T>(this IFindRequest <T> request, T instance)
 {
     request.Layout = FileMakerApiClientBase.GetLayoutName(instance);
     return(request);
 }
Ejemplo n.º 11
0
 /// <summary>
 /// Specify a layout to use for this request.
 /// </summary>
 /// <param name="request">The request. This is the 'this' parameter.</param>
 /// <param name="layout">Name of the layout to use</param>
 /// <typeparam name="T">The type used for the find request/response.</typeparam>
 /// <returns>The request instanced that was implicitly passed in which is useful for method chaining.</returns>
 public static IFindRequest <T> UseLayout <T>(this IFindRequest <T> request, string layout)
 {
     request.Layout = layout;
     return(request);
 }
Ejemplo n.º 12
0
 /// <summary>
 /// Specify the number to offset (skip) before returning records.
 /// </summary>
 /// <param name="request">The request. This is the 'this' parameter.</param>
 /// <param name="offset">The offset to use before returning records.</param>
 /// <typeparam name="T">The type used for the find request/response.</typeparam>
 /// <returns>The request instanced that was implicitly passed in which is useful for method chaining.</returns>
 public static IFindRequest <T> SetOffset <T>(this IFindRequest <T> request, int offset)
 {
     request.Offset = offset;
     return(request);
 }
Ejemplo n.º 13
0
 /// <summary>
 /// Specify the limit of records to be returned.
 /// </summary>
 /// <param name="request">The request. This is the 'this' parameter.</param>
 /// <param name="limit">The maximum number of records to be returned as part of this request.</param>
 /// <typeparam name="T">The type used for the find request/response.</typeparam>
 /// <returns>The request instanced that was implicitly passed in which is useful for method chaining.</returns>
 public static IFindRequest <T> SetLimit <T>(this IFindRequest <T> request, int limit)
 {
     request.Limit = limit;
     return(request);
 }
Ejemplo n.º 14
0
 /// <summary>
 /// Adds a new find criteria to the find request.
 /// </summary>
 /// <param name="request">The request. This is the 'this' parameter.</param>
 /// <param name="criteria">Object containing the data for this find request record.</param>
 /// <param name="omit">Use this criteria for omit(int matching records) or not.</param>
 /// <typeparam name="T">The type used for the find request/response.</typeparam>
 /// <returns>The request instanced that was implicitly passed in which is useful for method chaining.</returns>
 public static IFindRequest <T> AddCriteria <T>(this IFindRequest <T> request, T criteria, bool omit)
 {
     request.AddQuery(criteria, omit);
     return(request);
 }
Ejemplo n.º 15
0
 /// <summary>
 /// Send a Find Record request to the FileMaker API.
 /// </summary>
 public virtual Task <IEnumerable <T> > SendAsync <T>(
     IFindRequest <T> req,
     Func <T, int, object> fmId) where T : class, new() => SendAsync(req, fmId, null);
Ejemplo n.º 16
0
 /// <summary>
 /// Send a Find Record request to the FileMaker API.
 /// </summary>
 public abstract Task <IEnumerable <T> > SendAsync <T>(
     IFindRequest <T> req,
     Func <T, int, object> fmId,
     Func <T, int, object> modId) where T : class, new();
Ejemplo n.º 17
0
        /// <summary>
        /// General purpose Find Request method. Supports additional syntaxes like the { "omit" : "true" } operation.
        /// </summary>
        /// <param name="req">The find request field/value dictionary to pass into FileMaker server.</param>
        /// <returns>A <see cref="Dictionary{String,String}"/> wrapped in a FindResponse containing both record data and portal data.</returns>
        public override async Task <IFindResponse <Dictionary <string, string> > > SendAsync(IFindRequest <Dictionary <string, string> > req)
        {
            if (string.IsNullOrEmpty(req.Layout))
            {
                throw new ArgumentException("Layout is required on the request.");
            }

            var uri      = FindEndpoint(req.Layout);
            var response = await ExecuteRequestAsync(HttpMethod.Post, uri, req);

            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                // on 404 return empty set instead of throwing an exception
                // since this is an expected case
                return(new FindResponse <Dictionary <string, string> >());
            }

            try
            {
                var responseJson = await response.Content.ReadAsStringAsync();

                var responseObject = JsonConvert.DeserializeObject <FindResponse <Dictionary <string, string> > >(responseJson);

                return(responseObject);
            }
            catch (Exception ex)
            {
                // something bad happened. TODO: improve non-OK response handling
                throw new Exception($"Non-OK Response: Status = {response.StatusCode}.", ex);
            }
        }