TronComputers
TronComputers

Reputation: 324

NET 5 WebAPI + OData - custom result model

Am I able to make this prittier with custom model/class?

    "@odata.context": "https://localhost:5001/api/$metadata#Customers",
    "@odata.count": 4830,
    "value": [
        {
            "Id": 1,
            "Code": "",
            "Name1": "",
            "Name2": "",
            "Name3": ""
        },
             ...

I don't need @data.context and I want to be able to add response message for zero results.

For example:

    "responseMessage": "Example message from API with OData",
    "responseCode": 10,
    "itemsLength": 4830,
    "items": [
        {
            "Id": 1,
            "Code": "",
            "Name1": "",
            "Name2": "",
            "Name3": ""
        },
             ...

Also I want to handle exeptions with same model

For example:

    "responseMessage": "[Exception.Message] - Exception message example text.",
    "responseCode": -1,
    "itemsLength": 0,
    "items": []

Upvotes: 1

Views: 914

Answers (1)

TronComputers
TronComputers

Reputation: 324

I've found the solution.

OData works best with EF as it "forces" to use IQueryable instead of IEnumerable. In my case, I can't use EF because it's not my database, so I'm using Dapper. We all know that Dapper lives by different laws and IQueryable does not provide any benefits but thanks to this it is possible to return a custom response using OData:

MODEL

    public class CustomersResponse
    {
        public string ReponseMessage { get; set; }
        public int ResponseCode { get; set; }
        public int ItemsLength { get; set; }
        public IQueryable Items { get; set; }
    }

KONTROLER

        [HttpGet]
        public async Task<IActionResult> Get(ODataQueryOptions<Customer> options)
        {
            var response = new CustomersResponse();

            try
            {
                var customers = await _dataReader.GetCustomers("database_name");
                var customersResult = options.ApplyTo(customers.AsQueryable()).Cast<Customer>();
                if(customersResult.Count() == 0)
                {
                    response.ItemsLength = customers.Count();
                    response.ReponseMessage = "NotFound";
                    response.ResponseCode = 404;
                    response.Items = customersResult;
                    return NotFound(response);
                }
                response.ItemsLength = customers.Count();
                response.ReponseMessage = "OK";
                response.ResponseCode = 200;
                response.Items = customersResult;
                return Ok(response);
            }
            catch (Exception ex)
            {
                response.ResponseCode = 500;
                response.Items = null;
                response.ReponseMessage = ex.Message;
                response.ItemsLength = 0;
                return StatusCode(500, response);
            }
        }

We remove the [EnableQuery] attribute from the endpoint, add the ODataQueryOptions<Customer> options parameter and use options.ApplyTo(), which will prepare the data accordingly. I would like to remind you that this is on average efficient, because we collect all customers in the first line, and then we filter using OData.

WORKS

.Filter()
.OrderBy()
.SetMaxTop(50)

DOESN'T WORK

.Count() // we don't need that anymore
.Expand()
.Select()

In the case of Select() there is most likely a problem with the returned data as OData tries to convert it to its type somehow.

{
    "reponseMessage": "Unable to cast object of type 'Microsoft.AspNetCore.OData.Query.Wrapper.SelectSome`1[OptimoLogic.Models.Customer]' to type 'OptimoLogic.Models.Customer'.",
    "responseCode": 500,
    "itemsLength": 0,
    "items": null
}

Upvotes: 1

Related Questions