ahmadalibaloch
ahmadalibaloch

Reputation: 6021

Microsoft Odata api through a ViewModel has problems in PATCH

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.

Upvotes: 3

Views: 3212

Answers (1)

Tan Jinfu
Tan Jinfu

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

Related Questions