Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Only return selected fields in Web API results

First of all, this is not exactly a duplication of the dozens of other posts and I have tried all of them and none of them work.

I have a model that contains many more values than my web api consumers need.

public class Publication
{
    [Key]
    public int PublicationID { get; set; }
    public string PublicationTitle { get; set; }
    public string Frequency { get; set; }
    public DateTime NextIssueDate { get; set; }
    public DateTime SpaceDeadline { get; set; }
    public DateTime MaterialsDeadline { get; set; }
    public DateTime CreatedDt { get; set; }
    public string CreatedBy { get; set; }
    public DateTime UpdatedDt { get; set; }
    public string UpdatedBy { get; set; }
}

I only want say a few of the fields to be passed in the API. I've tried this code but instead of leaving out say UpdateBy in the Json result, it returns it with a null value. How do I get rid of that? I've tried several dozen variations but they either fail to compile or fail to return results.

    public IQueryable<Publication> GetPublications()
    {
        return db.Publications
            .ToList()
            .Select(p => new Publication {
                PublicationID = p.PublicationID,
                PublicationTitle = p.PublicationTitle,
                Frequency = p.Frequency,
                NextIssueDate = p.NextIssueDate
            })
            .AsQueryable();
    }
like image 975
Connie DeCinko Avatar asked Aug 14 '15 22:08

Connie DeCinko


People also ask

Can we use Actionresult in Web API?

Leverage action results to return data as an HttpResponseMessage object from your Web API controller method. ASP.Net Web API is a lightweight framework used for building stateless and RESTful HTTP services. You can take advantage of Action Results in Web API to return data from the Web API controller methods.

Can Web API return view?

Solution 1 You can return one or the other, not both. Frankly, a WebAPI controller returns nothing but data, never a view page. A MVC controller returns view pages. Yes, your MVC code can be a consumer of a WebAPI, but not the other way around.

Can we have multiple get methods in Web API?

As mentioned, Web API controller can include multiple Get methods with different parameters and types. Let's add following action methods in StudentController to demonstrate how Web API handles multiple HTTP GET requests.


5 Answers

Don't serialize your DAO. Create a complete contract and then serialize it selectively. To creating different contracts for different cases, you could simplify it using Json.Net; you could just create a custom contract resolver and use it as a parameter of SerializeObject() like so

static void Main(string[] args)
{
    var person = new TestContract {FirstName = "John", LastName = "Doe", Age = 36};

    var firstNameContract = new SelectiveSerializer("firstname");
    var allPropertiesContract = new SelectiveSerializer("firstname, lastname, age");

    var allJson = JsonConvert.SerializeObject(
        person, 
        Formatting.Indented,
        new JsonSerializerSettings {ContractResolver = allPropertiesContract});

    var firstNameJson = JsonConvert.SerializeObject(
        person, 
        Formatting.Indented,
        new JsonSerializerSettings {ContractResolver = firstNameContract});

    Console.WriteLine(allJson);
    //  {
    //    "FirstName": "John",
    //    "LastName": "Doe",
    //    "Age": 36
    //  }


    Console.WriteLine(firstNameJson);
    //  {
    //    "FirstName": "John",    
    //  }        
}

public class SelectiveSerializer : DefaultContractResolver
{
    private readonly string[] _fields;

    public SelectiveSerializer(string fields)
    {
        var fieldColl = fields.Split(',');
        _fields = fieldColl
            .Select(f => f.ToLower().Trim())
            .ToArray();
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        property.ShouldSerialize = o => _fields.Contains(member.Name.ToLower());

        return property;
    }
}

public class TestContract
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public int Age { get; set; }
}

Without much effort, you could probably work this into your default mediatype formatter (in the pipeline) to look for a parameter in the request called 'fields' or whatever and then use the custom contract resolver if present, and then it would be seamless default behavior to limit fields if specified or serialize the entire object if not specified.

On the academic side, here is the justification: Any modification to the data is considered a "view concern" which means, in an API, it should controlled by query parameters and accept header. In this case, the "representation" of the data is application/json and you've chose to "filter" the returned fields. All of this can (and should be, imo) be handled during serialization. So your "model" in this case will always be the full model vs. some subset of the model. The full model in this example contains first name, last name, and age. In reality, this could be hundreds of properties. If you want to allow the client to choose a subset of the complete model, this is how you could do it with selective serialization.

You can similar behaviors in graph apis. There, the default for large models is that you get an empty object if you don't specify fields, forcing the client to be very specific about what it asks for, which is great when payload size matters (e.g. mobile applications). And, there's nothing stopping from creating field presets like 'name' which could mean 'firstname, lastname' or 'all' which includes all properties.

I've never been a fan of having hundreds of data objects that all serve some ad hoc requirement for a data set that is used in 20 different contexts where some cases require more data while others require less. IMO if you have to go through the same process to get the data, whether it complete or not, you shouldn't waste your time creating additional objects to frame the data for the sake of the client, and this should help you achieve that.

like image 88
Sinaesthetic Avatar answered Oct 20 '22 22:10

Sinaesthetic


It's because you're returning a collection of Publication objects so you will get every property that is contained in that class, whether you populate it or not. If you want to return a subset of the properties then create a class that has only the properties you want to return and create an instance of that class in your query.

public IQueryable<WhatIReallyWantToReturn> GetPublications()
{
    return db.Publications
        .ToList()
        .Select(p => new WhatIReallyWantToReturn {
            PublicationID = p.PublicationID,
            PublicationTitle = p.PublicationTitle,
            Frequency = p.Frequency,
            NextIssueDate = p.NextIssueDate
        })
        .AsQueryable();
}

private class WhatIReallyWantToReturn
{
    public int PublicationID { get; set; }
    public string PublicationTitle { get; set; }
    public string Frequency { get; set; }
    public DateTime NextIssueDate { get; set; }
}
like image 44
Craig W. Avatar answered Oct 20 '22 22:10

Craig W.


using Newtonsoft.Json;

public class Publication
{
    [Key]
    public int PublicationID { get; set; }
    public string PublicationTitle { get; set; }
    public string Frequency { get; set; }
    public DateTime NextIssueDate { get; set; }
    public DateTime SpaceDeadline { get; set; }
    public DateTime MaterialsDeadline { get; set; }

    [JsonIgnore]    
    public DateTime CreatedDt { get; set; }

    [JsonIgnore]    
    public string CreatedBy { get; set; }

    [JsonIgnore]    
    public DateTime UpdatedDt { get; set; }

    [JsonIgnore]    
    public string UpdatedBy { get; set; }
}
like image 44
birdus Avatar answered Oct 20 '22 22:10

birdus


as Craig W. said you can use viewmodel ,also you can use anonymous type (notice viewmodel is better way because you can use some utilities like automapper for mapping your property automatically)

like image 35
Pooriya Arazesh Avatar answered Oct 20 '22 21:10

Pooriya Arazesh


JsonIgnore annotation has worked for me

[JsonIgnore]  
public int Ranking { get; set; }
like image 44
Santiago Casas Rey Avatar answered Oct 20 '22 22:10

Santiago Casas Rey