ServiceStack: Pass an array to a Service - servicestack

I'm having an issue when I pass an array to my service, it only recognizes the first value in the array:
Here is my request object:
[Route("/dashboard", "GET")]
public class DashboardRequest : IReturn<Dashboard>
{
public int[] EquipmentIds { get; set; }
}
Here is the request which is made:
http://localhost:9090/json/reply/DashboardRequest?EquipmentIds=1&EquipmentIds=2
But when I observe the array in my service, it only contains one value, which is 1.
public object Get(DashboardRequest request)
{
// request.EquipmentIds.Length == 1;
// request.EquipmentIds[0] == 1;
}
One Solution I've done is the following, which seems a bit hacky? I thought the point of specifying it in my Request Object is that I get a strongly typed Request Object?
var equipmentIds = Request
.QueryString["EquipmentIds"]
.Split(',')
.Select(int.Parse)
.ToList();

This works when you use the custom route, e.g:
[Route("/dashboard", "GET")]
public class DashboardRequest : IReturn<Dashboard>
{
public int[] EquipmentIds { get; set; }
}
and call it via the User Defined route, e.g:
http://localhost:9090/dashboard?EquipmentIds=1&EquipmentIds=2
Support for this has also been added on Predefined Routes in this commit which will be available from v4.0.24+ that's now available on MyGet.
So your existing request now works as well, e.g:
http://localhost:9090/json/reply/DashboardRequest?EquipmentIds=1&EquipmentIds=2

Bind the request object to the int array like
[Route("/dashboard/{EquipmentIds}", "GET")]
public class DashboardRequest : IReturn<Dashboard>
{
public int[] EquipmentIds { get; set; }
}
http://localhost:9090/dashboard/1,2

Related

Can AutoQuery return a single item (not list)

When I add a type to AutoQuery, with:
[Route("/templates")]
public class SearchTemplates : QueryDb<Template>
{
public int? Id { get; set; }
public string Name { get; set; }
}
then I can query this object by Id or Name (or whatever other attributes I would add, that the POCO Template has). However it always returns list of items.
It's very useful to be able to GET a single item (not a search result).
This is how I do it:
[Route("/template/{Id}","GET")]
public class SingleTemplate : IReturn<Template>
{
public int Id { get; set; }
}
public Template Get(SingleTemplate request)
{
return Db.LoadSingleById<Template>(request.Id);
}
With all the new AutoQuery and AutoCRUD, it seems to me that the "return a single item by its URL" could also be automatic?
No, All AutoQuery QueryDb<T> services return the same fixed QueryResponse Response DTO as per its Service Contract, i.e:
public abstract class QueryDb<T>
: QueryBase, IQueryDb<T>, IReturn<QueryResponse<T>> { }
public abstract class QueryDb<From, Into>
: QueryBase, IQueryDb<From, Into>, IReturn<QueryResponse<Into>> { }
public class QueryResponse<T> : IQueryResponse
{
public virtual int Offset { get; set; }
public virtual int Total { get; set; }
public virtual List<T> Results { get; set; }
public virtual Dictionary<string, string> Meta { get; set; }
public virtual ResponseStatus ResponseStatus { get; set; }
}
A single result would still populate the Results property, so the JSON wire format would look like:
{ "results":[ {/*Template*/} ] }
You could create your own Custom AutoQuery Implementation that utilizes AutoQuery's IAutoQueryDb API to return your own custom populated DTO but then your Request DTO should NOT inherit from QueryDb<T> as not returning a QueryResponse<T> would break the explicit Service contract of the Request DTO (and all clients expecting it), i.e. you would instead just create a normal ServiceStack Service returning your own custom Response Type.

Error when adding Where or OrderBy clauses to Azure Mobile Apps request

I'm developing an Azure Mobile App service to interface to my Xamarin application.
I've created, connected and successfully populated an SQL Database, but when I try to add some filters to my request, for example an orderby() or where() clauses, it returns me a Bad Request error.
For example, this request: https://myapp.azurewebsites.net/tables/Race?$orderby=iRound%20desc,iYear%20desc&$top=1&ZUMO-API-VERSION=2.0.0 gives me {"message":"The query specified in the URI is not valid. Could not find a property named 'IYear' on type 'MyType'."}.
My configuration method is this:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.AddTablesWithEntityFramework()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
Database.SetInitializer(new CreateDatabaseIfNotExists<MainDataContext>());
app.UseWebApi(config);
and my DbContext is this:
public class MainDataContext : DbContext
{
private const string connectionStringName = "Name=MS_TableConnectionString";
public MainDataContext() : base(connectionStringName)
{
Database.Log = s => WriteLog(s);
}
public void WriteLog(string msg)
{
System.Diagnostics.Debug.WriteLine(msg);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
public DbSet<Race> Race { get; set; }
public DbSet ...ecc...
}
Following this guide, I added a migration after creating my TableControllers. So the TableController for the example type shown above is pretty standard:
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public class RaceController : TableController<Race>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MainDataContext context = new MainDataContext();
DomainManager = new EntityDomainManager<Race>(context, Request);
}
// GET tables/Race
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Race> GetAllRace()
{
return Query();
}
// GET tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<Race> GetRace(string id)
{
return Lookup(id);
}
// PATCH tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Race> PatchRace(string id, Delta<Race> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/Race
public async Task<IHttpActionResult> PostRace(Race item)
{
Race current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteRace(string id)
{
return DeleteAsync(id);
}
}
As you can see, I already tried to add the EnableQuery attribute to my TableController, as seen on Google. I also tried to add these filters to the HttpConfiguration object, without any success:
config.Filters.Add(new EnableQueryAttribute
{
PageSize = 10,
AllowedArithmeticOperators = AllowedArithmeticOperators.All,
AllowedFunctions = AllowedFunctions.All,
AllowedLogicalOperators = AllowedLogicalOperators.All,
AllowedQueryOptions = AllowedQueryOptions.All
});
config.AddODataQueryFilter(new EnableQueryAttribute
{
PageSize = 10,
AllowedArithmeticOperators = AllowedArithmeticOperators.All,
AllowedFunctions = AllowedFunctions.All,
AllowedLogicalOperators = AllowedLogicalOperators.All,
AllowedQueryOptions = AllowedQueryOptions.All
});
I don't know what to investigate more, as things seems to be changing too fast for a newbie like me who's first got into Azure.
EDIT
I forgot to say that asking for the complete table, so for example https://myapp.azurewebsites.net/tables/Race?ZUMO-API-VERSION=2.0.0, returns correctly the entire dataset. The problem occurs only when adding some clauses to the request.
EDIT 2
My model is like this:
public class Race : EntityData
{
public int iRaceId { get; set; }
public int iYear { get; set; }
public int iRound { get; set; }
ecc..
}
and the database table that was automatically created is this, including all the properties inherited from EntityData:
Database table schema
Digging into the source code, Azure Mobile Apps sets up camelCase encoding of all requests and responses. It then puts them back after transmission accordign to rules - so iRaceId becomes IRaceId on the server.
The easiest solution to this is to bypass the auto-naming and use a JsonProperty attribute on each property within your server-side DTO and client-side DTO so that they match and will get encoding/decoded according to your rules.
So:
public class Race : EntityData
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("raceId")]
public int iRaceId { get; set; }
[JsonProperty("year")]
public int iYear { get; set; }
[JsonProperty("round")]
public int iRound { get; set; }
etc..
}

How to get nested element using ServiceStack?

Although I am able to access the SchemaVersion using code below, I cannot access FormatDocID nested element.
Any ideas how can I easily get FormatDocID using ServiceStack and AutoQueryFeature (or similar)?
I put only relevant parts of code here
public override void Configure(Container container)
{
JsConfig.DateHandler = DateHandler.ISO8601;
SetupValidators(container);
SetupIOC(container);
SetupPlugins(container, log);
ContentTypes.Register("application/xml"
, CLXmlSerializer.Serialize, ServiceStack.Text.XmlSerializer.DeserializeFromStream);
SetupMetaDataRedirectionPath();
SetupGlobalResponseFilters();
}
Setup plugins
private void SetupPlugins(Container container)
{
Plugins.Add(new ValidationFeature());
Plugins.Add(new SwaggerFeature());
Plugins.Add(new AutoQueryFeature
{
MaxLimit = 1000,
EnableUntypedQueries = false,
IncludeTotal = true
});
Plugins.Add(new AutoQueryDataFeature {MaxLimit = 100}
.AddDataSource(ctx => ctx.MemorySource(new List<WordDocument>
{
new WordDocument()
{
SchemaVersion = "",
Format = new Word.DocumentFormat()
{
FormatDocID = 254
}
}
}))
);
typeof(RequestLogs).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
typeof(AssignRoles).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
typeof(UnAssignRoles).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
typeof(Authenticate).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
}
Serializable classes
public abstract class Document
{
public DocumentFormat Format;
public class DocumentFormat
{
[XmlAttribute] public int Version;
public int FormatDocID;
public string DocShortName;
}
}
public class WordDocument : Document
{
[XmlAttribute] public string SchemaVersion { get; set; } = "1.0";
}
Thanks in advance for the answers.
It's not clear what you're trying to achieve or why, AutoQuery creates Auto Queryable APIs where the Response is the API Response serialized in the specified Response Content Type.
If you want to intercept the Typed Response DTO before it's returned you can create a Custom AutoQuery Implementation and introspect the response that way, e.g:
public class MyQueryServices : Service
{
public IAutoQueryData AutoQuery { get; set; }
//Override with custom implementation
public object Any(MyQuery query)
{
var q = AutoQuery.CreateQuery(query, base.Request);
var response = AutoQuery.Execute(query, q);
return response;
}
}
But the AutoQuery Memory Data Source you're using lets you provide your own collection of Typed POCOs as the Data source so you already have access to them when you create it, but the source POCOs should be a flat Type with public properties (in contrast to your class with public fields and nested types) - it's not possible to query nested object graph values.
This is an example of a POCO that doesn't use nested classes, or public fields:
public abstract class Document
{
public int Version { get; set; }
public int FormatDocID { get; set; }
public string DocShortName { get; set; }
}
So the solution if you want to use AutoQuery would be to change your Data Source to use Flat POCOs with public properties otherwise you'd need to create the impl of your Service yourself.

NotMapped attribute keeps related property from saving?

I have a few properties that I don't have a direct mapping in the database for, so I'm using the convention of having another variable that is mapped to the database, and a public variable that will be used to do all of my actual work. The common one is [mapping a boolean property to a char column][1], but I also have a StatusID property whose C# enum is different based on the derived type.
My public property has the [NotMapped] attribute on it, and my internal property has the [Column] attribute. I think there's something that because the public property isn't mapped, it's keeping the other property from being mapped as well.
In my project, I start with an abstract base Message class:
[Table("tblMessage")]
public abstract class Message {
[Column("msgIsSample")]
[Required]
internal string dbIsSample { get; set; }
[Column("msgStatusID")]
internal int? dbStatusId { get; set; }
[NotMapped]
public bool IsSample {
get {
return dbIsSample.ToUpper() == "Y";
}
set {
dbIsSample = value ? "Y" : "N";
}
}
public Message() {
this.IsSample = false;
this.dbStatusId = null;
}
}
Right now I only have a single class implementing the base class, Request:
public class Request : Message {
[NotMapped]
public int Status {
get {
return this.dbStatusId.HasValue ? this.dbStatusId.Value : 1;
}
set {
this.dbStatusId = value;
}
}
public Request()
: base() {
this.Status = 1;
}
}
Here is my context:
public class MyContext : DbContext {
public DbSet<Message> Messages { get; set; }
static MyContext() {
Database.SetInitializer<MyContext>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Message>()
.Map<Request>(m => m.Requires("msgTypeID").HasValue(1));
}
}
Is this something that anyone else has run across? I haven't been able to find anything about why this isn't working, even though this looks like the accepted convention until the EF team adds additional custom mapping. Someone else has to have run across this issue.
When I try to execute this code, I get a DbUpdateException saying that it can't insert a NULL into column "msgIsSample" due to my having set that in the table creation script. This doesn't make any sense because the msgIsSample is defaulted to have a "N".
Instead of making it internal, make it protected internal.
At runtime, EF will subclass your entity dynamically. These extended classes are called dynamic proxies.
EF cannot set your property because it does not have access. To give EF access to your property, it must have either public or protected access. You can still have internal properties, but give subclasses access by adding the protected modifier.
[Table("tblMessage")]
public abstract class Message {
[Column("msgIsSample")]
[Required]
public string dbIsSample { get; protected internal set; }
[Column("msgStatusID")]
public int? dbStatusId { get; protected internal set; }

REST Routing in ServiceStack

I just start to learn REST and ServiceStack and there's something about Route that I just can't quite understand. For example if we take the very basic HelloWorld example from GitHub tutorial and re-write it to return collection of User objects. Here is example:
public User
{
public string Name;
public string Address;
public int Age;
}
// Hello - request object without [Route] attribute
public class Hello
{
public string Name { get; set; }
}
public class HelloResponse
{
public IEnumerable<User> Result {get;set;}
}
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { // Collection of User object };
}
}
now everything working right and no problems here. But now I want to add another routing url like: /Hello/{name}/Address
Actually this call (GET) to this url will return a single User selected by Age parameter. How I can do this ? Should I add another Service ? And if the url will be:
/Hello/{name}/{age}/Address
It seems I don't understand something.....
See this earlier answer for details about Routing in ServiceStack. The Smart Routing section in ServiceStack's New API explains further options and different precedence.
There are a few problems with your example. First ServiceStack text serializers only support public properties so you need to change your User Model to use public properties instead of fields, e.g:
public User
{
public string Name { get; set; }
public string Address { get; set; }
public int Age { get; set; }
}
Next, Interfaces on DTOs are a bad idea as there's no good reason for it. They're still supported but you can end up with undesirable results. Use a concrete collection like a List<T> which provides more utility, e.g:
public class HelloResponse
{
public List<User> Results { get; set; }
}
Also the routes should match the property names on your DTO exactly, they are case-insensitive when matching against the Request Path, but they need to map to an exact property name, e.g:
/Hello/{Name}/{Age}/Address

Resources