Search code examples
c#odataasp.net-web-api2

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?


Solution

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