3

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

  1. 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?

  2. 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.

ahmadalibaloch
  • 5,851
  • 2
  • 50
  • 59

1 Answers1

2

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;
    }
}
Tan Jinfu
  • 3,327
  • 1
  • 19
  • 20
  • So your idea is to apply PATCH on the existing Product Controller and give permission aware list via new OdataProduct Controller. Right? – ahmadalibaloch Mar 13 '14 at 13:24
  • No, I mean do GET/PATCH/PUT/POST in ODataProductsController. – Tan Jinfu Mar 14 '14 at 02:14
  • How can I PATCH an ODataProduct viewmodel with Product. This is the main problem I asked in the question!!!!!!!!! – ahmadalibaloch Mar 14 '14 at 12:22
  • Thank you for your answer, but I have solved this problem by adding a property to my original Product entity which is not mapped in database (added a [NotMapped] attribute). Now I did not neet to make a OdataProduct entity for this purpose. Remember you will need to set the not mapped property in modelBuilder like this: `builder.EntitySet("Product").EntityType.Property(c => c.IsAccessible);` also you will need to initialize it to make it work with LINQ to Queryable like this `var result = db.Products.ToList().Where(p => p.IsAccessible == p.IsAccessible);` in your controller->GET. – ahmadalibaloch Mar 18 '14 at 11:23