Как передать сложный параметр объекта в функцию odata webapi 2.2

#c# #odata #asp.net-web-api2

#c# #odata #asp.net-web-api2

Вопрос:

Контроллер:

 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();
    }
}
  

Параметр действия контроллера:

 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    
}
  

Код построения модели:

 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);
  

Из javascript, как бы я вызвал эту функцию odata и передал сложный параметр?

http://example.com/odata/Phase/Default .Пользовательские фазы (@param)

При попытке передать сложный тип в типичном URL-адресе функции odata возникает много проблем. Во-первых, он является частью базового URL, а не частью параметров (после?) итак, если вы взяли полезную нагрузку json, например:

 {
    "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"
        }
    ]
}
  

построил его и urlencoded, даже если я скажу asp.net чтобы игнорировать определенные символы, он по-прежнему не привязывает параметр к фактическому действию контроллера odata, когда я создаю URL-адрес вручную. Это дает 404.

Во-вторых, я понятия не имею, можно ли использовать тип содержимого http GET application / json и параметр данных и поместить его в это местоположение URL, не являющееся параметром, при выполнении вызова jquery ajax.

Семантически это функция odata (а не действие), потому что она ничего не изменяет — просто возвращает данные с пользовательским аргументом, поэтому она использует спецификацию GET (пожалуйста, поправьте меня, если я ошибаюсь).)…

Как это делается?

Ответ №1:

В webapi 2.2 параметры функции и действия могут быть только примитивными типами или типами перечисления.

Если вам нужен сложный тип, вы можете определить тип параметра как string, а затем передать свой параметр как строковый литерал.

Затем в вашем контроллере вы можете получить строковое значение параметра и десериализовать его в объект CustomPhaseRequest.

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

Однако длина URL имеет ограничение. Поэтому, если строка параметра слишком длинная, вы должны определить ее как действие и передать параметр в теле, хотя это ничего не меняет.

Комментарии:

1. Вы уверены в этом? Тогда эта ошибка, которую я получаю, вводит в заблуждение: «Необязательный тип параметра должен быть либо примитивным, сложным, набором примитивов, либо набором сложных».

2. параметр также может быть набором примитивов. Конструктор моделей OData также имеет . Метод CollectionParameter<>() .

Ответ №2:

Вот как я это делаю с помощью связанного действия. Дело в том, что вы должны указать тип odata при использовании сложного объекта в запросе.

 // 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": {
    "fromUserUid@odata.type": "#Guid",
    "fromUserUid": "9A49B9D6-037C-4929-9FB7-0FC627DC9EBD",
    "message": "test message",
    "toUserUid@odata.type": "#Guid",
    "toUserUid": "6BE85BFD-0D3F-487B-9F83-1037D4C35432"
  }
}
  

Ответ №3:

Я разделил свои данные на разные списки (все отсортированные одинаково и одинаковой длины), передал их действию контроллера, а затем воссоздал объект данных, чтобы упростить итерацию. Последний шаг может быть необязательным, для большинства вещей вы также можете просто индексировать непосредственно в различные списки (мне это было нужно для 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 amp;amp; wr.Item2==act.ACTIVITY_ID amp;amp; wr.Item1 == null));

        return acts;
    }
  

И конфигурация веб-api

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