TableController and Odata Query - azure

In a small Azure Mobile App, I have the following GET method in a tablecontroller:
public IQueryable<User> GetAllUser()
{
return Query();
}
using the following REST call, I can query users with the lastname='Tiger'
GET: ~/userinfo?$filter=lastName%20eq%20'Tiger'
Now I'd like to add an organisation fields to my user, so I've changed the get method to:
public IQueryable<UserDto> GetAllUser()
{
return Query().Select(u => new UserDto{FirstName=u.FirstName, LastName=u.LastName, Organisation="Acme"});
}
but now, when I try to query my users, using the same filter:
GET: ~/userinfo?$filter=lastName%20eq%20'Tiger'
I get an 'Bad Request' error....
How can I make this work...
Any help would be greatly appreciated....

Try:
public IQueryable<UserDto> GetAll()
{
return Query().AsEnumerable().Select(u =>
new UserDto
{
FirstName = u.FirstName,
LastName = u.LastName,
Organisation = "Acme"
}).AsQueryable();
}
Your ODATA query will be used on the DTO and not the entity.
Your Controller should still use TableController<User>.
You could also use AutoMapper and simply do:
return Query().ProjectTo<UserDTO>();
This will do LINQ to Entities.

Your post helped me solve my own issue!
From what I can tell, it doesn't work because when you're using DTO objects and MappedEntityDomainManager, this.Query() call crashes. In my case, both my Model and DTO object inherit from Azure.Mobile.Server.EntityData.
Basically... avoid this.Query() with MappedEntityDomainManager
Try this:
public IQueryable<UserDto> GetAllUser()
{
return _context.User.Select(u => new UserDto{FirstName=u.FirstName, LastName=u.LastName, Organisation="Acme"});
}
where _context is your DBContext and .User is your User DBSet

Related

AutoPopulate attribute not working on AutoQuery DTO

I am trying to get the new AutoPopulate attribute to work but I am having some difficulty understanding the new AutoQuery functionality.
To test it out I am aiming to replace this service that is a standard AutoQuery endpoint but it also filters by the logged in users ID. I want to replace it so it works completely with just the model definition.
public class DevExtremeService : ServiceBase
{
public IAutoQueryDb AutoQuery { get; set; }
public QueryResponse<DeWatchedUrlResponse> Any(WatchedUrlDevExRequest request)
{
var q = AutoQuery.CreateDevXQuery(request, Request.GetRequestParams(), Request);
q.Where(x => x.UserAuthCustomId == GetUserId());
var response = AutoQuery.Execute(request, q, base.Request);
return response;
}
}
[Route("/de/watched-urls")]
public class WatchedUrlDevExRequest : QueryDb<WatchedUrlRecord, DeWatchedUrlResponse>
{
}
So I deleted the service and updated model to:
[ValidateIsAuthenticated]
[AutoPopulate(nameof(WatchedUrlDevExRequest.UserAuthCustomId), Eval = "userAuthId")]
[Route("/de/watched-urls")]
public class WatchedUrlDevExRequest : QueryDb<WatchedUrlRecord, DeWatchedUrlResponse>
{
public long UserAuthCustomId { get; set; }
}
My understanding from reading the release notes is that userAuthId is a variable declared in the AutoQuery #script context that is added by default.
I have tried a few different variations and I cannot get the property to populate. The docs seem focused on audit history and multitenancy but really I am just looking for a quick way to make endpoints.
I have 2 main questions:
Why is the auto populate not working on this property?
Where can I see the default #script definition so I can see how things like userAuthId are defined and better get an understanding how to add my own?
edit
I re-read docs and I gues this only works when writing data to db. I really like the concept of being able to apply #script to a request model via attribute. Is that possible?
AutoQuery CRUD's [AutoPopulate] attribute initially only populated AutoQuery CRUD's Data Model when performing CRUD operations, e.g. Inserting, Updating or Deleting entities.
For ensuring a query only returns a users records, it's recommended to use an AutoFilter instead, which behaves as expected ensuring the query is always applied to the Data Model, e.g:
[ValidateIsAuthenticated]
[Route("/de/watched-urls")]
[AutoFilter(QueryTerm.Ensure, nameof(WatchedUrlRecord.UserAuthCustomId),
Eval = "userAuthId")]
public class WatchedUrlDevExRequest : QueryDb<WatchedUrlRecord, DeWatchedUrlResponse>
{
}
However as I can see it's a useful feature I've also just added support for [AutoPopulate] & [AutoMap] attributes on Query DTOs in this commit where your AutoQuery DTO would work as expected where it populates the Request DTO property:
[ValidateIsAuthenticated]
[AutoPopulate(nameof(WatchedUrlDevExRequest.UserAuthCustomId), Eval = "userAuthId")]
[Route("/de/watched-urls")]
public class WatchedUrlDevExRequest : QueryDb<WatchedUrlRecord, DeWatchedUrlResponse>
{
public long UserAuthCustomId { get; set; }
}
This change is available from v5.10.3 that's now available on MyGet.
An alternative approach to populate AutoQuery's Request DTO you could have a custom AutoQuery implementation like you have, an Extensible Query Filter or custom base class or I'd personally go with a Global Request Filter that updates all Request DTOs with a shared interface, e.g:
GlobalRequestFilters.Add((req, res, dto) => {
if (dto is IHasUserAuthCustomId authDto)
{
var session = req.GetSession();
if (session.IsAuthenticated)
authDto.UserAuthCustomId = session.UserAuthId;
}
});
Or you could wrap this logic in a Request Filter Attribute and apply the behavior to Request DTOs that way.
Note: userAuthId is a ServiceStack #Script method that returns the currently authenticated User Id.

Acumatica GetList error: Optimization cannot be performed.The following fields cause the error: Attributes.AttributeID

Developer's version of Acumatica 2020R1 is installed locally. Data for sample tenant MyTenant from training for I-300 were loaded, and WSDL connection established.
DefaultSoapClient is created fine.
However, attempts to export any data by using Getlist cause errors:
using (Default.DefaultSoapClient soapClient =
new Default.DefaultSoapClient())
{
//Sign in to Acumatica ERP
soapClient.Login
(
"Admin",
"*",
"MyTenant",
"Yogifon",
null
);
try
{
//Retrieving the list of customers with contacts
//InitialDataRetrieval.RetrieveListOfCustomers(soapClient);
//Retrieving the list of stock items modified within the past day
// RetrievalOfDelta.ExportStockItems(soapClient);
RetrievalOfDelta.ExportItemClass(soapClient);
}
public static void ExportItemClass(DefaultSoapClient soapClient)
{
Console.WriteLine("Retrieving the list of item classes...");
ItemClass ItemClassToBeFound = new ItemClass
{
ReturnBehavior = ReturnBehavior.All,
};
Entity[] ItemClasses = soapClient.GetList(ItemClassToBeFound);
string lcItemType = "", lcValuationMethod = "";
int lnCustomFieldsCount;
using (StreamWriter file = new StreamWriter("ItemClass.csv"))
{
//Write the values for each item
foreach (ItemClass loItemClass in ItemClasses)
{
file.WriteLine(loItemClass.Note);
}
}
The Acumatica instance was modified by adding a custom field to Stock Items using DAC, and by adding several Attributes to Customer and Stock Items.
Interesting enough, this code used to work until something broke it.
What is wrong here?
Thank you.
Alexander
In the request you have the following line: ReturnBehavior = ReturnBehavior.All
That means that you try to retrieve all linked/detail entities of the object. Unfortunately, some object are not optimized enough to not affect query performance in GetList scenarios.
So, you have to options:
Replace ReturnBehavior=All by explicitly specifying linked/detail entities that you want to retrieve and not include Attributes into the list.
Retrieve StockItem with attributes one by one using Get operation instead of GetList.
P.S. The problem with attributes will most likely be fixed in the next version of API endpoint.
Edit:
Code sample for Get:
public static void ExportItemClass(DefaultSoapClient soapClient)
{
Console.WriteLine("Retrieving the list of item classes...");
ItemClass ItemClassToBeFound = new ItemClass
{
ReturnBehavior = ReturnBehavior.Default //retrieve only default fields (without attributes and other linked/detailed entities)
};
Entity[] ItemClasses = soapClient.GetList(ItemClassToBeFound);
foreach(var entity in ItemClasses)
{
ItemClass itemClass= entity as ItemClass;
ItemClass.ReturnBehavior=ReturnBehavior.All;
// retrieve each ItemClass with all the details/linked entities individually
ItemClass retrievedItemCLass = soapClient.Get(itemClass);
}

Broadleaf APIs not working

I'm trying to consume Broadleaf APIs to Create Cart, Add Item and Checkout on a consumer application.
Cloned Demo application and modified the configuration as per the link:
https://www.broadleafcommerce.com/docs/core/current/broadleaf-concepts/rest/rest-tutorials
Problems:
1. Create new Cart->
POST: http://localhost:8080/api/v1/cart
Exception: HttpRequestMethodNotSupportedException: Request method 'POST' not supported
With GET request: worked
Add Product ID:
POST: http://localhost:8080/api/v1/cart/1?categoryId=1&customerId=100
Exception:HttpRequestMethodNotSupportedException: Request method 'POST' not supported
GET Request worked but the product is not added.
3.Add a Payment to the Order
POST: http://localhost:8080/api/v1/cart/checkout/payment?customerId=100
Added the OrderPaymentWrapper in the body as mentioned in the above URL
Exception:
messageKey": "queryParameterNotPresent",
"message": "com.broadleafcommerce.rest.api.exception.BroadleafWebServicesException.queryParameterNotPresent"
Alternatively, referred https://demo.broadleafcommerce.org/api/v2/swagger-ui.html#/ to invoke the API as per the swagger documentation.
Same issue, unable to create a order flow.
I've tried to debug by running on localhost https://github.com/BroadleafCommerce/DemoSite
Same issue.
Please advise.
This looks like an outstanding issue with our #FrameworkController annotation. I opened an issue in Broadleaf at https://github.com/BroadleafCommerce/Issues/issues/3 with more information as to why it is currently failing.
The workaround is to modify CustomCartEndpoint in the API project that you have to add in the createNewCartForCustomer() method. The final implementation of CustomCartEndpoint should look like this:
#RestController
#RequestMapping(value = "/cart",
produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public class CustomCartEndpoint extends CartEndpoint {
#Override
#RequestMapping(value = "", method = RequestMethod.GET)
public OrderWrapper findCartForCustomer(HttpServletRequest request) {
try {
return super.findCartForCustomer(request);
} catch (Exception e) {
// if we failed to find the cart, create a new one
return createNewCartForCustomer(request);
}
}
#Override
#RequestMapping(value = "", method = RequestMethod.POST)
public OrderWrapper createNewCartForCustomer(HttpServletRequest request) {
return super.createNewCartForCustomer(request);
}
}

Laravel 5 pagination Call to a member function render() on a non-object

I am trying to paginate some categories and this error popped up from nowhere and I don't know how to fix it :
Call to a member function render() on a non-object
This is my controller:
public function getFilmeByCateg()
{
$categorii = Categorii::all();
//$Movies = Movies::all();
$categorie = Request::segment(2);
$cat = Categorii::where('denumire', '=',$categorie)->first();
$cat2 = Categorii_filme::where('categorie_id', '=' ,$cat->categorie_id)->get();
$filme = array();
foreach($cat2 as $filmulet)
{
$film = Movies::where('movie_id','=',$filmulet->film_id)->paginate(12)->first();
$filme[] = $film;
}
return view('filme')->with('Movies',$filme)->with('categorii',$categorii);
}
And this is how I render the paginator in my layout:
{!!$Movies->render() !!}
This is in my routes:
Route::get('categorie/{categorie}','WelcomeController#getFilmeByCateg');
This is in my Movies.php:
class Movies extends Model {
protected $table = "movies";
}
Can someone tell me how can I manage to make this work ?
Since $filme is an array, you can access render() method whose object you wanted is $Movies which is array.
foreach($cat2 as $filmulet)
{
$film = Movies::where('movie_id','=',$filmulet->film_id)->paginate(12)->first();
$filme[] = $film;
}
Here $filme is an array.
return view('filme')->with('Movies',$filme)->with('categorii',$categorii);
Here you passed $filme as $Movie variable.
{!!$Movies->render() !!}
And here you wanted to access render() method of $filme which is not an object.
Change your controller to this:
public function getFilmeByCateg()
{
$categorii = Categorii::all();
//$Movies = Movies::all();
$categorie = Request::segment(2);
$cat = Categorii::where('denumire', '=',$categorie)->first();
$movie_ids = Categorii_filme::where('categorie_id', '=' ,$cat->categorie_id)->get()->lists('movie_id');
$filme = Movies::whereIn('movie_id','=',$movie_ids)->paginate(12);
return view('filme')->with('Movies',$filme)->with('categorii',$categorii);
}
OK First of all, you made it the wrong way.
Using an intermediate class for representing the pivot table is completely bad.
I'll make it work for you.
Your Category class
class Category extends \Model {
protected $table = 'categories';
public function movies()
{
return $this->belongsToMany('Your\Namespace\To\Movie');
}
}
Your Movie class
class Movie extends \Model {
protected $table = 'movies';
public function categories()
{
return $this->belongsToMany('Your\Namespace\To\Category');
}
}
Your route is still :
Route::get('categorie/{category_id}','WelcomeController#getFilmeByCateg');
So in your controller method you do the following :
public function getFilmeByCateg($category_id)
{
$category = Category::findOrFail($category_id);
return view('filme')->with('Movies',$category->movies()->paginate(12)->ge())->with('categorii',Category::all());
}
Now in your view $Movies->render() will work
If you wont paginate your data how would you render it ? Documents on the official laravel website states :
There are several ways to paginate items. The simplest is by using the paginate method on the query builder or an Eloquent model.
Paging database results
$users = DB::table('users')->paginate(15);
Note: Currently, pagination operations that use a groupBy statement cannot be executed efficiently by Laravel. If you need to use a groupBy with a paginated result set, it is recommended that you query the database and create a paginator manually.
Creating A Paginator Manually
Sometimes you may wish to create a pagination instance manually,
passing it an array of items. You may do so by creating either an
Illuminate\Pagination\Paginator or
Illuminate\Pagination\LengthAwarePaginator instance, depending on your
needs.
Now in your case you should first
use Illuminate\Pagination\LengthAwarePaginator as Paginator;
and then
$paginator = new Paginator($items, $count, $limit, $page, [
'path' => $this->request->url(),
'query' => $this->request->query(),
]);
Please supply parameters accordingly in the Paginator function above.
I know it is very late but it will help someone who get in to same issue. Instead of ->get() use ->paginate(10) in query.

ServiceStack Json Serializer ignore properties

I have a business requirement to only send permissioned properties in our response payload. For instance, our response DTO may have several properties, and one of them is SSN. If the user doesn't have permissions to view the SSN then I would never want it to be in the Json response. The second requirement is that we send null values if the client has permissions to view or change the property. Because of the second requirement setting the properties that the user cannot view to null will not work. I have to still return null values.
I have a solution that will work. I create an expandoObject by reflecting through my DTO and add only the properties that I need. This is working in my tests.
I have looked at implementing ITextSerializer. I could use that and wrap my response DTO in another object that would have a list of properties to skip. Then I could roll my own SerializeToString() and SerializeToStream(). I don't really see any other ways at this point. I can't use the JsConfig and make a SerializeFn because the properties to skip would change with each request.
So I think that implementing ITextSerializer is a good option. Are there any good examples of this getting implemented? I would really like to use all the hard work that was already done in the serializer and take advantage of the great performance. I think that in an ideal world I would just need to add a check in the WriteType.WriteProperties() to look and the property is one to write, but that is internal and really, most of them are so I can't really take advantage of them.
If someone has some insight please let me know! Maybe I am making the implementation of ITextSerialzer a lot harder that it really is?
Thanks!
Pull request #359 added the property "ExcludePropertyReference" to the JsConfig and the JsConfigScope. You can now exclude references in scope like I needed to.
I would be hesitant to write my own Serializer. I would try to find solutions that you can plug in into the existing ServiceStack code. That way you will have to worry less about updating dlls and breaking changes.
One potential solution would be decorating your properties with a Custom Attributes that you could reflect upon and obscure the property values. This could be done in the Service before Serialization even happens. This would still include values that they user does not have permission to see but I would argue that if you null those properties out they won't even be serialized by JSON anyways. If you keep all the properties the same they you will keep the benefits of strong typed DTOs.
Here is some hacky code I quickly came up with to demonstrate this. I would move this into a plugin and make the reflection faster with some sort of property caching but I think you will get the idea.
Hit the url twice using the following routes to see it in action.
/test?role
/test?role=Admin (hack to pretend to be an authenticated request)
[System.AttributeUsage(System.AttributeTargets.Property)]
public class SecureProperty : System.Attribute
{
public string Role {get;set;}
public SecureProperty(string role)
{
Role = role;
}
}
[Route("/test")]
public class Test : IReturn
{
public string Name { get; set; }
[SecureProperty("Admin")]
public string SSN { get; set; }
public string SSN2 { get; set; }
public string Role {get;set;}
}
public class TestService : Service
{
public object Get(Test request)
{
// hack to demo roles.
var usersCurrentRole = request.Role;
var props = typeof(Test).GetProperties()
.Where(
prop => ((SecureProperty[])prop
.GetCustomAttributes(typeof(SecureProperty), false))
.Any(att => att.Role != usersCurrentRole)
);
var t = new Test() {
Name = "Joe",
SSN = "123-45-6789",
SSN2 = "123-45-6789" };
foreach(var p in props) {
p.SetValue(t, "xxx-xx-xxxx", null);
}
return t;
}
}
Require().StartHost("http://localhost:8080/",
configurationBuilder: host => { });
I create this demo in ScriptCS. Check it out.

Resources