Adding conditions on complex properties with ServiceStack AutoQuery - c#

I need to add filtering to my API requests that support AutoQuery, so based on this SO answer, used q.And to add conditions. The issue is that one of the POCO properties is a List<string> and it seems doing a simple Contains() won't work. Here's a simple example of what I have:
public class PocoObject
{
public int Id { get; set; }
public List<string> Names { get; set; }
}
My service looks like this:
public object Get(PocoObjects request)
{
var q = AutoQuery.CreateQuery(request, Request.GetRequestParams());
if (someCondition)
{
q.And(x => x.Names.Contains(request.TargetName));
}
return AutoQuery.Execute(request, q);
}
Problem is, I get an error like this:
variable 'x' of type 'TestProject.ServiceModel.Types.PocoObject' referenced from scope '', but it is not defined
If I change the Contains to a simpler equality comparison on another property, the AutoQuery works. Any ideas how to accomplish this?

You can't do a server side SQL query on a blobbed complex type property like List<string>. Any queries need to be applied on the client after the results are returned from the db and its deserialised back into a typed POCO.

Related

Is there a way to derive a type argument from a string for passing into a generic method?

I typed this out in Notepad++ real quick so please forgive any typos/mistakes. If it's possible, I'd be getting rid of some repetitive work (i.e. a long case statement). Not a huge deal but I'm curious if it's possible and if so, how bad would it be to actually implement the code.
jsonFromWebpage = {
StatusUpdate: {
QueryType: "SomeClassName",
LocalCount: 5,
RemoteCount: 5
},
StatusUpdate: {
QueryType: "AnotherClass",
LocalCount: 29,
RemoteCount: 30
}
}
// Model
public class StatusUpdate
{
public string QueryType { get; set; }
public int LocalCount { get; set; }
public int RemoteCount { get; set; }
}
// Controller
public IActionResult GetStatusUpdate([FromBody] List<StatusUpdate> status)
{
_service.GetStatusUpdate(status);
return status
}
// Service
public List<Status> GetStatusUpdate(List<StatusUpdate> status)
{
foreach(var s in status)
{
var typeArgument = s.QueryType; // <--- Is there a way for this...
status.CurrentCount = GetTotalCount<typeArgument>(); // <--- to work here?
status.RemoteCount = thisworksfineforotherreasons(s.QueryType);
}
}
// Repo
public int GetTotalCount<T>() where T: class
{
var result = _db.GetCount<T>();
return result;
}
EDIT
First, thank you to everyone that has responded. Having read everything so far, I wanted to give a little more context. Here's a different take on the example:
// View
<div class="col-12">
<div class="api-types">History</div>
<div class="progress-bar">50 out of 50 copied</div>
</div>
<div class="col-12">
<div class="api-types">Users</div>
<div class="progress-bar">25 out of 32 copied</div>
</div>
// -- View javascript
var types = [];
$(".api-types").each(function (c, i) {
types.push({ ApiAndClassName: $(i).text() });
});
pushToController(JSON.stringify(types));
// Controller
public IActionResult GetSyncStatus(List<SyncStatusVM> status)
{
_service.GetSyncStatus(status);
return Json(status);
}
// Service
public List<SyncStatusVM> GetSyncStatus(List<SyncStatusVM> status)
{
foreach(var s in status)
{
// LocalCount
var magicTypeFigurator = s.ApiAndClassName
s.LocalCount = _repo.GetCount<magicTypeFigurator>(); <-- "this is a variable but should be a type..."
// Remote
var url = $"https://api.domain.com/{s.ApiAndClassName.ToLower()}"
s.RemoteCount = FetchCountFromApi(url);
}
return status;
}
// Repository
public long GetCount<T>()
{
var result = _orm.Count<T>();
return result;
}
// Models
public class SyncStatusVM
{
public string ApiAndClassName { get; set; }
public int LocalCount { get; set; }
public int RemoteCount { get; set; }
}
public class History
{
public long Id {get;set;}
public DateTime CreatedDate {get;set;}
public string Message {get;set;}
}
public class Users
{
public long Id {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
}
Using this code, I can just create a section in the view and a class for each type. The class is reused by the ORM and desearializing from the API. The most cumbersome point is having a case statement in the controller that calls the generic method with the correct type, based on the "ApiAndClassName". I could edit the ORM so it's string based instead of generic but I don't like that method for various reasons. I could turn the case statement into a collection in the controller or just move it to the service layer but what I have in place already works. I could also just refactor so the view builds from a collection but there are other data points where that wouldn't be the best option. Unless there's something I'm missing, the generic argument from string thing kinda makes sense. It's a fringe case... and kinda just curious if it can be done well enough.
Generally strong typsisation is your friend. Compile time type checks are a feature, not a enemy to be fought. Without them or with too agressive casting, we get the JavaScript and PHP examples from this comic.
For work with weakly typed langauges or WebServices, .NET has the ExpandoObject. The data can be stored in it, then later transfered into the proper type of instance. Also it looks like your case would fall into JSON deserialisation, wich is a well established code.
Generic is the wrong term. Generics are usually about the type still being known at compile time, so the compile time type checks still work. You are explicitly about the type not being known at compile time, only at runtime. This is very distinct from a generic. Dynamic Types are the proper term afaik. But to not mix it up with the type Dynamic (yes, naming here becomes really confusing).
Reflection is the droid you are looking for. For most purposes, the name of a class or field does not exist at runtime. It is primarily there for you and the compiler to communicate. Now Reflection is the exception. It is all about getting stuff (like instances or property/fields) based on a string representation of their name. The nessesary metadata is baked into the .NET Assemblies, as much as the COM support. But as I support strong typisation, I am not a friend of it.
switch/case statements can usually be replaced with a collection of some sort. Cases are really just a hardcoded way to check a collection of constants. You use the case identifier as the key and whatever else you need for the Value. You can totally use Functions as the value (thanks to delegates). Or the Type type, you then use for the instance creation.
But for your case it sounds like all of this is wrong. Bog standart Inheritance - Inheritance might be the real droid you are looking for. A JSON service would not usually give you different instance in a single collection, unless those instances are related in some way. "SomeClassName" and "AnotherClass" should have another ancestor. Or in fact, they should even be just one class - QueryType is simply a string field of said class.
Assuming that you have a way to map strings to Type objects, yes: you can use MethodInfo.MakeGenericMethod():
var totalCount = (int) (
GetType()
.GetMethod("GetTotalCount")
.MakeGenericMethod(MapStringToType(s.QueryType))
.Invoke(this, null)
);
This assumes the presence of a method Type MapStringToType(string) in the local scope.
One way to map types would be to use a Dictionary<string, Type> and fill it with the allowed types and their respective names that will be used in the JSON data to refer to them.

C# OData4 Apply OData query without controller returning Queryable

I currently have an C# WebAPI that uses a version of OData that we wrote. We want to start using Microsoft's OData4 which can do more then our custom implementation.
Creating a controller that extends the ODataController I can create a controller that automatically queries based on the query string. (Shown below)
The problem is that it returns the results of the query when I want it to return the Result object which includes additional data. When I set the return type to Result though it will no longer apply the query string.
How can I use the automatic queryable implementation and still return my own object? I've tried making a public method that returns the correct object and calls a private method returning the queryable but it doesn't filter the queryable correctly.
Am I on the right track, or are there other options?
public class CallController : ODataController
{
[EnableQuery]
public IQueryable<Call> GetCall()
{
var list = new List<Call>
{
new Call
{
Id = 4
},
new Call
{
Id = 9
},
new Call
{
Id = 1
}
};
return list.AsQueryable();
}
}
public class Call
{
public int Id { get; set; }
}
public class Result
{
public Call[] Calls { get; set; }
public string NewToken { get; set; }
public string Warning { get; set; }
}
Use ODataQuertOptions instead of [EnableQuery] attribute. Check https://learn.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#invoking-query-options-directly
You would need to intercept the response with an action filter attribute in the onactionexecuted and convert the value to whatever you want. It wouldn't be pretty since it wouldn't be clear what the method was truly returning. But I don't see any other option with odata as the result must be iquerable.
Under the hood, the EnableQuery attribute is executing the action it decorates to get an IQueryable<T>, converting the odata query into something that can be applied to the IQueryable<T>, applying it and returning the results.
In order to work, it needs an IQueryable<T>.
The ODataQueryOptions and example in Ihar's answer may give you what you want, but for various reasons it wasn't as useful to me as EnableQuery and so I ended up with an output formatter.
You can inspect the first output formatters in services.AddMvc(options => { options.OutputFormatters } in Startup.ConfigureServices and see that the first one has a bunch of different payload kinds.
I have been able to insert a custom error handler to handle ODataPayloadKind.Error payloads - re-writing the content returned from the server to remove stack traces etc if not in dev mode. I haven't looked into non-error cases, but you may be able to use what I have here as a starting point.

How to build query string for Web API?

Assume that I have Web API method:
[HttpGet]
public string SomeAction([FromUri] ObjectA a, [FromUri] ObjectB b)
{
return Ok("test");
}
How to call it using HttpClient? What is the best way to prepare appropriate query string for objects A and B?
UPDATE
ObjectA and ObjectB are complex objects... e.g.
public class ObjectA
{
public string Prop1 { get; set; }
public int Prop2 { get; set; }
public double Prop3 { get; set; }
}
I can prepare query string for every specific case, but I'm interested to get any universal method that would allow to work with any objects...
For now I see only one possible solution - using reflection go through the list of properties and build query string. I assume that there should be some already implemented mechanism... am I wrong?
If you're including complex objects on the query string, I will assume they are being encoded in some manner, from what you're trying to do, it would have to be a default encoding or you won't be able to put them on the query string.
Normal routing would be:
/{controller}/{action}/{a}/{b}
or
/{controller}/{action}?a={content}&b={content}
Dependent upon how you have your routes configured and your preference on using query string parameters as opposed to "friendly" urls.
Alternately, the MVC framework will attempt to populate your object from values present in any available data location (i.e. query string, cookies, et. al.) where variable names = property names. In this case, you could use something like this:
/SomeController/SomeAction?objectaProp1=abc&objectaProp2=def...
Hope this helps.

ResponstDTO with complex Property in ServiceStack

Havin a Response with a complex property, i want to to map to my responseDTO properly. For all basic types it works out flawlessly.
The ResponseDTO looks like this:
public class ResponseDto
{
public string Id {
get;
set;
}
public struct Refs
{
public Genre GenreDto {
get;
set;
}
public Location LocationDto {
get;
set;
}
}
public Refs References {
get;
set;
}
}
Genre and Location are both for now simple classes with simple properties (int/string)
public class GenreDto {
public string Id {
get;
set;
}
public string Name {
get;
set;
}
}
Question:
Is there any way, without changing/replacing the generic unserializer ( and more specific example) (in this example JSON ) to map such complex properties?
One specific difference to the GithubResponse example is, that i cant use a dictionry of one type, since i have different types under references. Thats why i use a struct, but this seems not to work. Maybe only IEnumerable are allowed?
Update
There is a way using lamda expressins to parse the json manually github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/CentroidTests.cs#L136 but i would really like to avoid this, since the ResponseDTO becomes kinda useless this way - since when writing this kind of manual mapping i would no longer us Automapper to map from ResponseDto to DomainModel - i though like this abstraction and "seperation".
Thanks
I used lambda expressions to solve this issue, a more complex example would be
static public Func<JsonObject,Cart> fromJson = cart => new Cart(new CartDto {
Id = cart.Get<string>("id"),
SelectedDeliveryId = cart.Get<string>("selectedDeliveryId"),
SelectedPaymentId = cart.Get<string>("selectedPaymentId"),
Amount = cart.Get<float>("selectedPaymentId"),
AddressBilling = cart.Object("references").ArrayObjects("address_billing").FirstOrDefault().ConvertTo(AddressDto.fromJson),
AddressDelivery = cart.Object("references").ArrayObjects("address_delivery").FirstOrDefault().ConvertTo(AddressDto.fromJson),
AvailableShippingTypes = cart.Object("references").ArrayObjects("delivery").ConvertAll(ShippingTypeDto.fromJson),
AvailablePaypmentTypes = cart.Object("references").ArrayObjects("payment").ConvertAll(PaymentOptionDto.fromJson),
Tickets = cart.Object("references").ArrayObjects("ticket").ConvertAll(TicketDto.fromJson)
});
So this lamda exprpession is used to parse the JsonObject response of the request and map everything inside, even nested ressources. This works out very well and flexible
Some time ago i stumbled upon a similar problem. Actually ServiceStack works well with complex properties. The problem in my scenario was that i was fetching data from a database and was passing the objects returned from the DB provider directly to ServiceStack. The solution was to either create DTOs out of the models returned by the DB provider or invoke .ToList() on those same models.
I'm just sharing some experience with SS but may be you can specify what's not working for you. Is there an exception thrown or something else.

Orderby complex type using OData

I am starting to use OData in my MVC4 application and the problem that I am having, is that I cannot perform any sort or filter operations on my IQueryable because I am using complex objects. Below is a simple example of something I am trying to accomplish:
My API Controller is attempting to return a collection of MyObjects as IQueryable.
public IQueryable Get()
{
List<MyObject> myObjects = GetMyObjects();
return myObjects.AsQueryable() ;
}
Each MyObject contains an InnerObject that has the properties I want to sort and/or filter on.
public class MyObject
{
[Key]
public MyInnerObject innerObject{ get; set; }
public MyObject(Dictionary<string, object> value)
{
innerObject= new MyInnerObject(){
item = value["item"].ToString(),
itmdesc = value["itmdesc"].ToString()
};
}
}
public class MyInnerObject
{
public string item { get; set; }
public string itmdesc { get; set; }
}
I can successfully execute the top commands through the url
localhost:5050/Test/Get?$top=10
But I really want to be able to sort my results using
localhost:5050/Test/Get?$top=10&$orderby=innerObject.item
I have tried
localhost:5050/Test/Get?$top=10&$orderby=innerObject.item
localhost:5050/Test/Get?$top=10&$orderby=innerObject/item
localhost:5050/Test/Get?$top=10&$orderby=item
Any suggestions?
EDIT:
I should mention that it works if I put the item and itmdesc properties within MyObject, but for my purposes (this is just a minified version of my complex entities), they will need to be wrapped in a complex type.
In my Api controller, I have also tried IQueryable< MyObject> but that doesnt make a difference
In general, OData as a protocol allows the second thing you tried (localhost:5050/Test/Get?$top=10&$orderby=innerObject/item). It is likely that this is a temporary limitation of the Web API implementation of OData (assuming that's what you're using based on the rest of your environment and the returning of IQueryable).
Aspnet Web API OData doesn't support ordering by nested properties or expressions. There is an issue open on codeplex for supporting ordering by nested properties.
However if you are slightly adventurous you can use ODataQueryOptions to model bind the individual odata query options and then translate $orderby AST to a linq expression and apply it manually to your IQueryable.

Categories

Resources