Reputation: 6021
Objectives My objective was to send some extra (non-defined) properties with an entity Product. For example in an an AngularJs listing view, I need to show some products as links(accessible) and others not accessible based on the permissions calculated from current user (which I get from session)' data and the productId.
What forces me to have this problem
Now, Odata doesn't allow me to add extra properties while sending a IQueryable result like this.
public IQueryable<Product> GET()
{
return db.Products.AsQueryable<Product>();
}
simply because the returning type is Product and adding extra properties will make it something else, if I try it like this
var result = db.Products.Select(p => new Product
{
ID = p.ID,
Accessible = (code for permission checks),
[other properties...]
}
Solution approached
I approached it with a solution, making OdataController of a new type OdataProduct that has defined properties which I need to send to make a list permission aware. (Do you have any idea how to make lists permissions aware, other than my this solution?)
public class OdataProduct
{
public Product product { get; set; }
public OdataProduct(Product product)
{
this.product = product;
}
//
public void setPermissions(User user, string controller)
{
if (user == null) return;
isAllowed = [permission check code];
}
public bool isAllowed { get; set; }
}
I tried to inherit this OdataProduct
form Product
but faced the problem in downcasting the OdataProduct
to Product
when receiving the POST
requests to save into database. Db.Products.Add();
Now with this ViewModel
and a controller of this type I have successfully sent the permission aware results dependent on current user in session for all Products in the list like this.
public class OdataProductController : ODataController
{
private OfferAssistantDbContext db = new OfferAssistantDbContext();
// GET api/OdataProduct
public IQueryable<OdataProduct> GET()
{
var result = db.Products.AsQueryable<Product>();
var OResult = new List<OdataProduct>();
var currentUser = (User)HttpContext.Current.Session["LoggedUser"];
OdataProduct OProduct;
foreach (var item in result)
{
OProduct = new OdataProduct(item);
OProduct.setPermissions(currentUser, "Product");
OResult.Add(OProduct);
}
return OResult.AsQueryable<OdataProduct>();
}
//other methods of the controller below...
}
Problem I want solution for
When I send a PATCH
request to OdataProduct
controller, I get a Delta
object that is not Product, if I send a Product in the Payload
and also modify accordingly the Odata parameters of PATCH
method to receive a Product
instead of OdataProduct
it is received as null, while in the default case I am not able to run this command to PATCH
the real entity of Product
not the ViewModel
. below.
var dbProduct = db.Products.Find(key); oDataProduct.Patch((dbProduct); //OdataProduct is not of dbProduct type What is the solution?
Another problem I face is while setting the Permissions of the OdataProduct above
OProduct.setPermissions(currentUser, "Product"); //stepping into thie method the exception line is this.Childs = product.DependantProducts.Where(dp => dp.ParentID == product.ID).Count();
it says the DataReader is already open. Though it is not the main problem but please give some info here too.
Upvotes: 3
Views: 3212
Reputation: 3345
The first problem can be solved this way, firstly, define a View Moodel ODataProduct which contains the needed properties of the Model plus IsAllowed:
public class ODataProduct
{
public int ID { get; set; }
public string Title { get; set; }
public bool IsAllowed { get; set; }
}
Then create a new entityset whose element type is ODataProduct.
private static IEdmModel GetModel()
{
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
var odataProductsEntitySet = modelBuilder.EntitySet<ODataProduct>("ODataProducts");
return modelBuilder.GetEdmModel();
}
The last step is to update the ODataProductController to execute GET, POST, PATCH ect.
public class ODataProductsController : ODataController
{
private ProductsContext db = new ProductsContext();
public IHttpActionResult Get()
{
var Products = db.Products.Select(m => new ODataProduct()
{
ID = m.ID,
Title=m.Title,
// Other properties
IsAllowed = true,
});
return Ok( Products);
}
public ODataProduct Patch(int key, Delta<ODataProduct> odataProduct)
{
var dbProduct = db.Products.Single(m => m.ID == key);
foreach (string propertyName in odataProduct.GetChangedPropertyNames())
{
if ("IsAllowed" == propertyName)
{
continue;
}
object propertyValue;
if (odataProduct.TryGetPropertyValue(propertyName, out propertyValue))
{
var propertyInfo = typeof(Product).GetProperty(propertyName);
propertyInfo.SetValue(dbProduct, propertyValue);
}
}
db.SaveChanges();
ODataProduct odataProductReturn = new ODataProduct()
{
ID = dbProduct.ID,
Title=dbProduct.Title,
// other properties
};
odataProductReturn.IsAllowed = true;// or false according to your business logic
return odataProductReturn;
}
}
Upvotes: 2