4
votes

Using Microsoft WebAPI OData version 5.6 (OData v3), if I have the following custom model/DTOs:

public class ParentObject
{
  public int ID { get; set; }
  public string Title {get; set;}
  public ChildObject Child { get; set;}
}  

public class ChildObject
{
  public string Value { get; set;}
}

And the following controller that projects my database into the model:

public class ParentsController : ODataController
{
  public IQueryable<ParentObject> Get()
  {
    var db = new MyDBContext();
    var results = from p in db.parent
                  select new ParentObject
                  {
                    ID = p.id,
                    Title = p.title,
                    Child = p.child == null ? null : new ChildObject 
                    {
                      Value = p.child.value
                    }
                   };

    return results;
   }
  }
} 

How can I force "Child" to be expanded without the consumer needing to specify $expand=Child in the url?

I want to:
1. Check to see if the consumer already specified the $expand=Child
2. If "Child" is not already included in the expand list, include it.
3. Be sure to respect any other query options

I have looked at several online articles that are either unclear to me or reference an older version of the api that no longer matches up.

I have tried modifying the Action to "Get(ODataQueryOptions opts)" which let's me do something like:

if(opts.SelectExpand == null || !opts.SelectExpand.RawExpand.Contains("Child"))
{
  // Add/modify expand clause here.
}

However I am not clear on how to either modify or build up my own ODataQueryOptions object, maintaining all of the other query options and then applying it to the results.

Update 1

I have made some progress. Here's the relevant changes to the controller (the dbcontext query is same as above):

public PageResult<ParentObject> Get(ODataQueryOptions opts)
{
 SelectExpandQueryOption expandOpts = null;
 if(opts.SelectExpand != null && !opts.SelectExpand.RawExpand.Contains("Child"))
 {
  expandOpts = new SelectExpandQueryOption(opts.SelectExpand.RawSelect, string.Join(",", new string[] {"Child", opts.SelectExpand.RawExpand }), opts.Context);
 }
 else if (opts.SelectExpand == null)
 {
  expandOpts = new SelectExpandQueryOption(null, "Child", opts.Context);
 }

 // Query from above goes here

 if(expandOpts != null)
 {
  Request.SetSelectExpandClause(expandOpts.SelectExpandClause);
 }

 var qSettings = new ODataQuerySettings();
 qSettings.PageSize = 100;

 results = opts.ApplyTo(results, qSettings).AsQueryable() as IQueryable<ParentObject>;
 return new PageResult<ParentObject>(results, Request.GetNextPageLink(), Request.GetInlineCount());
}

This seems to work fine when I do not include a "$expand" query option (expandOpts is created). However, when "$expand" is present, I'm getting a ArgumentNullExcpetion on the return statement and can't figure out why.

2
Your technique worked for me too. You should put it as an answer and mark it as accepted. The only difference is that Request.SetSelectExpandClause is obsolete so I used Request.ODataProperties().SelectExpandClause property insteadU.P

2 Answers

1
votes

Take a look near the bottom of this: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options. The MyQueryableAttribute may be what you need. You could create a custom QueryableWithChildAttribute class which injects the queryOptions.Expand you need. Then, just decorate your method with [QueryableWithChild()].

Hope this helps.

1
votes

After many hours on this one myself this is the answer. You need to set the expand string AND do the include to get it working.

[EnableQuery]
public IQueryable<Student> Get_Student(ODataQueryOptions<Student> queryOptions)
{
var db = new EfCustomAdapter();

var  expandOpts = new SelectExpandQueryOption(null, "Parent,Schools", queryOptions.Context);
var q = db.Student.AsQueryable();
q = q.Include(t => t.Parent);
q = q.Include(t => t.Schools);
queryOptions.Request.ODataProperties().SelectExpandClause = expandOpts.SelectExpandClause;
return q;
}

Update: If you need to expand levels below the syntax is.. "parents,schools,bills/payments"

the payments object being a navigation property of the bill.