Reputation: 1159
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
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
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
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