jaybird
jaybird

Reputation: 117

ServiceStack "Customizable Fields"

We've been using SS Customizable Fields ("fields=" on the query string) and I think we may be missing a configuration somewhere. For us, it seems that the field names are case-sensitive - if they don't match the DTO, they're not returned. However, this is not true for the example that's linked from the AutoQuery GitHub page (changing the casing still results in the correct fields coming back):

github.servicestack.net/repos.json?fields=Name,Homepage,Language,Updated_At

What are we missing?

Thanks!

Here's a sample that exhibits the behavior we're seeing:

AppHost:

using System.Configuration;
using ServiceStack;
using ServiceStack.Data;
using ServiceStack.OrmLite;
using ServiceStack.Text;

namespace TestSS.Service {
    public class AppHost : AppHostBase {
        public AppHost()
            : base("TestSS Service", typeof(AppHost).Assembly) {
        }

        public override void Configure(Funq.Container container) {
            Plugins.Add(new AutoQueryFeature { EnableUntypedQueries = false });
            Plugins.Add(new CorsFeature());

            PreRequestFilters.Add((httpReq, httpRes) => {
                if (httpReq.Verb == "OPTIONS")
                    httpRes.EndRequest();
            });

            container.Register<IDbConnectionFactory>(
                new OrmLiteConnectionFactory(ConfigurationManager.ConnectionStrings["TestSS"].ConnectionString, SqlServerDialect.Provider));

            JsConfig.DateHandler = DateHandler.ISO8601;
            JsConfig.IncludeNullValues = true;
            JsConfig.EmitCamelCaseNames = true;
        }
    }
}

DTOs:

using ServiceStack.DataAnnotations;

namespace TestSS.DTO {
    public class Employee {
        [PrimaryKey]
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        [References(typeof(Department))]
        public int DepartmentId { get; set; }

        [Ignore]
        public Department Department { get; set; }
    }


    public class Department {
        [PrimaryKey]
        public int Id { get; set; }
        public string Name { get; set; }
    }

}

Service:

using ServiceStack;
using TestSS.DTO;

namespace TestSS.Service.Services {
    [Route("/employees/{Id}", "GET")]
    public class SingleEmployeeRequest : QueryBase<Employee> {
        public string Id { get; set; }
    }

    [Route("/employees", "GET")]
    public class FindEmployeesRequest : QueryBase<Employee>, IJoin<Employee, Department> {
    }
}

Try the following routes:

/employees?fields=FirstName,LastName  <-- works

/employees?fields=firstname,LastName  <-- Only LastName returned

/employees?fields=firstname,lastname  <-- All properties returned ?

Now, remove the IJoin from the FindEmployeesRequest in the Employee service and try the routes again.

/employees?fields=FirstName,LastName  <-- works

/employees?fields=firstname,LastName  <-- works

/employees?fields=firstname,lastname  <-- works

UPDATE:

The casing issue is fixed with 4.0.55 but there seems to be one more odd behavior with this route:

/employees?fields=departmentid

The response contains both the DepartmentId AND Id properties and the ID values are actually the DepartmentId values. The same is true for this route:

/employees?fields=id,departmentid

Upvotes: 1

Views: 413

Answers (1)

mythz
mythz

Reputation: 143319

Support for case-insensitive custom fields was added in this commit with tests for AutoQuery in this commit. This change is available from v4.0.55 that's now available on MyGet.

You can either use case-sensitive custom fields now, but if you choose to upgrade to the latest version there are some breaking naming changes with AutoQuery that will be later documented in the next v4.0.56 release notes.

Due to a new non-RDBMS version of AutoQuery for supporting custom data sources (called AutoQueryData) we've had to rename some existing AutoQuery attributes so they maintain a consistent isolated nomenclature between the 2 implementations.

The naming changes are captured in the obsoleted attributes, essentially it involves adding a Db suffix to any [Query] attributes or QueryBase<T> class you're using, i.e:

[Obsolete("Use [QueryDb]")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class QueryAttribute : AttributeBase { }

[Obsolete("Use [QueryDbField]")]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class QueryFieldAttribute : AttributeBase { }

[Obsolete("Use QueryDb")]
public abstract class QueryBase<T> : QueryBase, IQueryDb<T>, IReturn<QueryResponse<T>> { }

[Obsolete("Use QueryDb")]
public abstract class QueryBase<From, Into> 
    : QueryBase, IQueryDb<From, Into>, IReturn<QueryResponse<Into>> { }

[Obsolete("Use IQueryDb<From>")]
public interface IQuery<From> : IQueryDb { }
[Obsolete("Use IQueryDb<From,Into>")]
public interface IQuery<From, Into> : IQueryDb { }

Whilst using the existing QueryBase class is still supported, it's now obsolete where you should switch to the new QueryDb base class at your own leisure.

So instead of:

[Route("/movies")]
public class FindMovies : QueryBase<Movie>
{
    public string[] Ratings { get; set; }
}

The new base class to use is QueryDb, e.g:

[Route("/movies")]
public class FindMovies : QueryDb<Movie>
{
    public string[] Ratings { get; set; }
}

The Release Notes explains the Custom Fields behavior:

The Fields still need to be defined on the Response DTO as this feature doesn't change the Response DTO Schema, only which fields are populated. This does change the underlying RDBMS SELECT that's executed, also benefiting from reduced bandwidth between your RDBMS and App Server.

A useful JSON customization that you can add when specifying custom fields is ExcludeDefaultValues, e.g:

/query?Fields=Id,Name,Description,JoinTableId&jsconfig=ExcludeDefaultValues

Which will remove any value type fields with a default value from the JSON response, e.g:

Essentially only the fields you Select are queried and populated on the Response DTO, all other fields have their default values which for reference types is null but for ValueTypes, for ints are 0 for DateTime is DateTime.MinValue, etc.

The ?jsconfig=ExcludeDefaultValues modifier instructs ServiceStack's JSON Serializer to omit properties with default values from being serialized, so the link above will instead return:

{
    Total: 30,
    Results: [
    {
        Name: "ServiceStack",
        Homepage: "https://servicestack.net",
        Language: "C#",
        Updated_At: "/Date(1443370980000+0000)/"
    },
    {
        Name: "ServiceStack.Examples",
        Homepage: "https://servicestack.net",
        Language: "C#",
        Updated_At: "/Date(1443326228000+0000)/"
    },
    ...
}

Upvotes: 2

Related Questions