t316
t316

Reputation: 1159

How to pass complex object param to odata webapi 2.2 function

Controller:

public class PhaseController: ODataController
{
    [EnableQuery]
    [System.Web.Http.HttpGet]
    public IQueryable < Phase > CustomPhases(CustomPhaseRequest request = null)
    {

        if (request == null) //default values
            request = new CustomPhaseRequest()
        {
            Duration = 3,
                PhaseFilters = new List < CustomPhaseFilter > ()
                {
                    new CustomPhaseFilter()
                        {
                            Name = "sp500", DisplayName = "SP500", GreaterThanValue = 10, Method = "returns", FieldName = "BLAH"
                        },
                        //new CustomPhaseFilter() { Name= "yr10",      DisplayName= "10YR",    GreaterThanValue= 0, Method= "delta", FieldName= "BAR", Unit= "bp" },                
                        //new CustomPhaseFilter() { Name= "em",        DisplayName= "EM",      GreaterThanValue= 0, Method= "returns", FieldName= "FOO" }
                }
        };
        var provider = DrawDownController.YadiYadahDataProvider;
        var data = provider.GetCustomPhases(provider.GetCachedData(), request);
        return data.AsQueryable();
    }
}

Controller action param:

public class CustomPhaseRequest
{
    public int Duration { get; set; }
    public List<CustomPhaseFilter> PhaseFilters { get; set; }
}

public class CustomPhaseFilter
{
    public string Name { get; set; }              // maps to str
    public string DisplayName { get; set; }       // maps to display
    public double? SmallerThanValue { get; set; } // maps to value
    public double? GreaterThanValue { get; set; } // maps to value
    public string Method { get; set; }            // maps to method
    public string FieldName { get; set; }         // maps to dataStr   
    public string Unit { get; set; }              // maps to unit    
}

Model Builder Code:

ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();

EntitySetConfiguration < Phase > phaseConfig = modelBuilder.EntitySet < Phase > ("Phase");
phaseConfig.EntityType.HasKey(p => p.Key);

var phaseType = modelBuilder.EntityType < Phase > ();
var customPhasesFunction = phaseType.Collection.Function("CustomPhases");

customPhasesFunction.ReturnsCollection < Phase > ().Parameter < CustomPhaseRequest > ("request");

IEdmModel model = modelBuilder.GetEdmModel();
config.MapODataServiceRoute("odata", "odata", model);

From javascript how would I call this odata function and pass in the complex param?

http://example.com/odata/Phase/Default.CustomPhases(@param)

There are many issues with trying to pass a complex type in the typical odata function url. First is that it is part of the base url and not part of the params (after ?) so if you took a json payload like:

{
    "Duration": 3, 
    "PhaseFilters": [
        {
            "Name": "sp500", 
            "DisplayName": "SP500", 
            "GreaterThanValue": 10, 
            "Method": "returns", 
            "FieldName": "BLAH"
        }, 
        {
            "Name": "yr10", 
            "DisplayName": "10YR", 
            "GreaterThanValue": 0, 
            "Method": "delta", 
            "FieldName": "BAR", 
            "Unit": "bp"
        }, 
        {
            "Name": "em", 
            "DisplayName": "EM", 
            "GreaterThanValue": 0, 
            "Method": "returns", 
            "FieldName": "FOO"
        }
    ]
}

stringified it and urlencoded it, even if I tell asp.net to ignore certain characters it still doesn't parameter bind in to the actual odata controller action when I construct the url manually. It gives a 404.

Second is that I have no idea if one can use an http GET application/json content type and a data parameter and have it placed in that non parameter location of the url while doing a jquery ajax call.

Semantically this is an odata function (not a action) because it doesn't modify anything - just retreives data with a custom argument, so it uses a GET spec-wise (please correct me if I am wrong)...

How is this done?

Upvotes: 2

Views: 6486

Answers (3)

James L.
James L.

Reputation: 14545

I separated my data into different lists (all sorted in the same way and all the same length), passed that to the controller action, and then recreated a data object to make it simpler to iterate through. The last step may not be necessary, for most things you could also just index directly into the various lists (I needed it for LINQ)

    [HttpGet]
    public IQueryable<ACTUAL> GetRelatedActuals([FromODataUri] Guid key, ODataActionParameters parameters)
    {
        var wrIds = parameters["wrIds"] as IEnumerable<Guid?>;
        var activities = parameters["activities"] as IEnumerable<string>;
        var projs = parameters["projs"] as IEnumerable<string>;

        var matchingWrs = new List<Tuple<Guid?, string, string>>();
        for (int i = 0; i < wrIds.Count(); i++)
        {
            matchingWrs.Add(new Tuple<Guid?, string, string> ( wrIds.ElementAt(i), activities.ElementAt(i), projs.ElementAt(i)));
        }

        var acts =  _repo.Query<ACTUAL>().Where(act => wrIds.Any(id=>id==act.WorkRequestId) || matchingWrs.Any(wr=>wr.Item3==act.PROJECT_ID && wr.Item2==act.ACTIVITY_ID && wr.Item1 == null));

        return acts;
    }

And the web api config

  var getrelated = builder.EntityType<ACTUAL>()
     .Function("GetRelatedActuals");
        getrelated.CollectionParameter<Guid>("wrIds");
        getrelated.CollectionParameter<string>("activities");
        getrelated.CollectionParameter<string>("projs");
        getrelated.ReturnsCollection<ACTUAL>();

Upvotes: 0

Scope Creep
Scope Creep

Reputation: 881

Here's how I do it with a bound action. The point here is that you must supply the odata type when using a complex object in the request.

// setup model builder (wherever you setup your OData EDM)
var messageAction = builder.EntityType<MessageDto>().Collection.Action("SendMessage");
messageAction.Namespace = "YourNamespace";
messageAction.Parameter<SendMessageRequest>("request");
messageAction.Returns<SendMessageResponse>();

// setup controller action (in controller, duh)
[HttpPost]
[ODataRoute("Messages/YourNamespace.SendMessage")]
public IHttpActionResult SendMessage(ODataActionParameters parameters)
{

    // parameter will be in the parameters argument
    //...your code

}

// SendMessageRequest
public class SendMessageRequest
{

    public Guid FromUserUid { get; set; }
    public Guid ToUserUid { get; set; }
    public string Message { get; set; }
}

// json request
{
  "request": {
    "[email protected]": "#Guid",
    "fromUserUid": "9A49B9D6-037C-4929-9FB7-0FC627DC9EBD",
    "message": "test message",
    "[email protected]": "#Guid",
    "toUserUid": "6BE85BFD-0D3F-487B-9F83-1037D4C35432"
  }
}

Upvotes: 2

Feng Zhao
Feng Zhao

Reputation: 2995

The parameter of function and action can only be primitive types or enumeration types in webapi 2.2.

If you want the complex type, you can define the parameter type as string and then pass your parameter as string literal.

Then in your controller, you can get the string value of the parameter and deserialize it to CustomPhaseRequest object.

public IQueryable<Phase> CustomPhases(string requestString)
{
    // Deserialize the requestString to requestObject
}

However the length of url has a limit. So if the parameter string is too long, you have to define it as action and pass the parameter in the body although it doesn't modify anything.

Upvotes: 4

Related Questions