Martijn
Martijn

Reputation: 24799

Is it possible to return different models in an ODataController?

IN a OData v4 Controller, is it possible to return different models for the Get() and the Get([FromIDataUri] key)?

I like to use ViewModels, and when using the Get() method I'd like to return an xxxOverviewViewModel. When using the Get([FromIDataUri] key) method, I'd like to return a xxxViewModel.

Is this possible, and if so, how?

I've tried to return different models, but I always get a 406 Acceptable.

Webapi.config:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.EnableCors();

        config.MapODataServiceRoute("ODataRoute", "odata", GetEdmModel());

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Filter().Expand().Select().OrderBy().MaxTop(null).Count();
    }

    private static IEdmModel GetEdmModel()
    {
        var builder = new ODataConventionModelBuilder();
        builder.EntitySet<ComplaintViewModel>("ComplaintOData");
        return builder.GetEdmModel();

    }
}

ComplaintODataController

public class ComplaintODataController : ODataController
{
    private readonly QueryProcessor _queryProcessor;

    public ComplaintODataController(QueryProcessor queryProcessor)
    {
        _queryProcessor = queryProcessor;
    }

    [EnableQuery]
    public IQueryable<ComplaintOverviewViewModel> Get()
    {
        var result = _queryProcessor.Handle(new GetAllComplaintsQuery());
        return result;
    }

    // WHEN CALLING THIS METHOD I GET A 406: 
    [EnableQuery]
    public ComplaintViewModel Get([FromODataUri] int key)
    {
        var result = _queryProcessor.Handle(new GetComplaintByIdQuery { Id = key });
        return result;
    }
}

EDIT:

My GetAllComplaintsQuery.Handle method looks like this:

public IQueryable<ComplaintOverviewViewModel> Handle(GetAllComplaintsQuery query)
{
    // .All is an IQueryable<Complaint>
    var result = _unitOfWork.Complaints.All.Select(c => new ComplaintOverviewViewModel
    {
        ComplaintType = c.ComplaintType.Description,
        CreationDate = c.CreationDate,
        Customer = c.Customer,
        Description = c.Description,
        Id = c.Id,
        SequenceNumber = c.SequenceNumber,
        Creator = c.Creator.Name
    });

    return result;
}

And this is my ComplaintConfiguration

public class ComplaintConfiguration : EntityTypeConfiguration<Complaint>
{
    public ComplaintConfiguration()
    {
        Property(p => p.SequenceNumber).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
        Property(p => p.Description).IsRequired();
    }
}

Upvotes: 2

Views: 1879

Answers (3)

Michael Domashchenko
Michael Domashchenko

Reputation: 1480

You did not include definitions for your ViewModel classes, so let me first make sure we're on the same page regarding what you're actually trying to achieve.

It looks like you want to return some restricted set of fields when client requests a list of Complaint records from the parameters-less Get() but when the client requests a specific complaint from Get([FromODataUri] int key) method you want to include additional fields.

I modeled this assumption using the following hierarchy:

public class ComplaintTypeViewModel
{
    public int Id { get; set; }
}

public class ComplaintOverviewViewModel : ComplaintTypeViewModel
{
    public string Name { get; set; }
}

public class ComplaintViewModel : ComplaintOverviewViewModel
{
    public string Details { get; set; }
}

Ran tests, GET /odata/ComplaintOData returned a list with just Id and Name as expected, GET /odata/ComplaintOData(1) returned a single record containing Details in addition to the other two fields, also as expected.

I never had to change your code for the controller or the WebApiConfig.cs except the string parameter in builder.EntitySet<ComplaintTypeViewModel>("ComplaintOData"); which had to match the ccontroller (you have "ComplaintTypeOData" in your code).

Since it worked, I tried to figure out how I can reproduce your 406. I've changed ComplaintViewModel to extend ComplaintTypeViewModel directly instead of extending ComplaintOverviewViewModel (note that I've just duplicated the Name property):

public class ComplaintTypeViewModel
{
    public int Id { get; set; }
}

public class ComplaintOverviewViewModel : ComplaintTypeViewModel
{
    public string Name { get; set; }
}

public class ComplaintViewModel : ComplaintTypeViewModel
{
    public string Name { get; set; }
    public string Details { get; set; }
}

This worked just fine as well.

The only way I could reproduce your 406 is when I changed the ComplaintViewModel to not have ComplaintTypeViewModel in its inheritance hierarchy at all:

public class ComplaintTypeViewModel
{
    public int Id { get; set; }
}

public class ComplaintOverviewViewModel : ComplaintTypeViewModel
{
    public string Name { get; set; }
}

public class ComplaintViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Details { get; set; }
}

This finally gave me the 406 code from GET /odata/ComplaintOData(1) while GET /odata/ComplaintOData was still returning the list with Id and Name just fine.

So, it looks like as long as all of your ViewModel classes extend the same T from builder.EntitySet<T>("name") you can return any of them from Get() overloads in your controller. Please check how you define your ViewModels

Upvotes: 1

Roman Marusyk
Roman Marusyk

Reputation: 24599

Try to just remove [EnableQuery] from action

public ComplaintViewModel Get([FromODataUri] int key)

EnableQueryAttribute enable querying using the OData query syntax. It is acceptable for collections IQueryable<>. When you want to return single entity, don't use that attribute.

UPDATES

Also try to rename parameter int key to id

Upvotes: 0

apomene
apomene

Reputation: 14389

Create an umbrella class for both models and have your get methods return respectively what you want.

public class myMainModel
{
  public xxxOverviewViewModel x {get;set;}
  public xxxOverviewViewModel y {get;set;}
}

myMainModel Get()
{ 
  ....
  return myMainModel.x;
}

myMainModel Get( int key)
{ 
  ....
  return myMainModel.y;
}

Upvotes: 0

Related Questions