GithubHelp home page GithubHelp logo

skywardapps / popcorn Goto Github PK

View Code? Open in Web Editor NEW
59.0 14.0 19.0 7.15 MB

Popcorn is a .Net Middleware for your RESTful API that allows your consumers to request exactly as much or as little as they need, with no effort from you.

Home Page: https://skywardapps.github.io/popcorn/

License: MIT License

C# 85.85% HTML 12.69% SCSS 1.45%
rest-api rest restful-api restful api-wrapper api-rest api-server rest-server restful-server dotnet dotnetcore dotnet-core asp-net-core asp-net aspnetcore aspnet-core asp-net-core-mvc webapi web-api csharp

popcorn's Introduction

# Popcorn

Join the chat at https://gitter.im/popcorn-api/Lobby Build status NuGet

Table Of Contents

Jump straight in

  • .NET Core - We have a .net core middleware that you can drop in to enable Popcorn on Web Apis. Feel free to grab it on nuget.
  • Other implementations - See our Roadmap or issues on GitHub for coming work

What is Popcorn?

Popcorn is a communication protocol on top of a RESTful API that allows requesting clients to identify individual fields of resources to include when retrieving the resource or resource collection.

It allows for a recursive selection of fields, allowing multiple calls to be condensed into one.

Features

Ok, so.... what is it in action?

Okay, maybe some examples will help!

Lets say you have a REST API with an endpoint like so:

https://myserver.com/api/1/contacts

Which returns a list of contacts in the form:

[
 {
   "Id":1,
   "Name":"Liz Lemon"
 },
 {
   "Id":2,
   "Name":"Pete Hornberger"
 },
 {
   "Id":3,
   "Name":"Jack Donaghy"
 },
 ...
}

Now, if you want to get a list of phone numbers for each of those, you now need to make a series of calls to further endpoints, one for each contact you want to look up the information for:

https://myserver.com/api/1/contacts/1/phonenumbers

[
  {"Type":"cell","Number":"867-5309"}
]

https://myserver.com/api/1/contacts/2/phonenumbers

[
  {"Type":"landline","Number":"555-5555"}
]

https://myserver.com/api/1/contacts/3/phonenumbers

[
  {"Type":"cell","Number":"123-4567"}
]

That's quite a lot of overhead and work! Popcorn aims to simplify this at the client's request. Let's say that while we want the numbers for each contact, we don't really need the type of the number (cell or landline) and would prefer to save the bandwidth by not transfering it. Now, instead of making many calls, all the above can be reduced down to:

https://myserver.com/api/1/contacts?include=[Id,Name,PhoneNumbers[Number]]

Which provides:

[
 {
    
   "Id":1,
   "Name":"Liz Lemon",
   "PhoneNumbers":
   [
    {
     "Number":"867-5309"
    }
   ]
 },
 {
   "Id":2,
   "Name":"Pete Hornberger",
   "PhoneNumbers":
   [
    {
     "Number":"555-5555"
    }
   ]
 },
 {
   "Id":3,
   "Name": "Jack Donaghy",
   "PhoneNumbers":
   [
    {
     "Number":"123-4567"
    }
   ]
 },
 ...
}

Presto! All the information we wanted at our fingertips, and none of the data we didn't!

Why would I use it?

By implementing the Popcorn protocol, you get a consistent, well defined API abstraction that your API consumers will be able to easily utilize. You will be able to take advantage of the various libraries and tools Popcorn offers; right now this includes a C# automatic implementation for Asp.Net Core and EntityFramework Core, but many more platforms are on the roadmap.

Pros

  • Faster calls
  • Saves data
  • Potential for server-side optimization
  • Less boilerplate code to write

Cons

  • You don't get to write as much code
  • Your consumers don't get to write as much code

How can I use it in my project?

First you need to figure out if you're working with a platform that has a provider implemented in the popcorn project. (Probably check out Documentation).

If there's a provider already in the project, great! The platform-specific documentation will walk you though incorporating the provider into your project.

If there isn't a provider yet, you'll have to roll your own. You'll still get the benefit of working with a standard, so your consumers will have a well documented spec to work with. Feel free to contribute any platform-specific providers you come up with!

Further Reading

popcorn's People

Contributors

96andrei avatar aerotog avatar alexbarbato avatar chrsi avatar dcalianno avatar dependabot[bot] avatar ematuschek avatar geoffmartinskyward avatar gitter-badger avatar jboyer2012 avatar jeffmathewsyngenta avatar jmathew avatar jmathewskyward avatar jtone123 avatar maxstralin avatar nfury avatar nicholasmtelliott avatar pokewithstick avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

popcorn's Issues

Compare / Contrast JSON API and GraphQL

One very obvious question that potential users will have is:

Why should I use this instead of JSON API or GraphQL?

We should address this head on in the documentation.

Some points to consider:

  • Simplicity (+ for Popcorn)
  • Performance in regards to consuming output (Potential + for Popcorn, maybe + for GraphQL too)
  • Implementation provided (+ for Popcorn for .net at least!)
  • Popcorn can fairly seamlessly integrate with an existing API without changing the schema

We need to provided researched pros and cons.

There are no issues yet.

Steps to Reproduce

  1. Open issues tab in github

Expected Behavior

There should be issues.

Actual Behavior

There are no issues

Enhancement: Add a table of contents to skywardapps.github.io/popcorn/

Possible Solution

Modify the MD source to provide the top level table of contents.

Steps to Reproduce

  1. Open the site https://skywardapps.github.io/
  2. Find the DotNet tutorial on Sorting.

Expected Behavior

The ability to find relevant topics quickly and without navigating through several levels of documentation.

Actual Behavior

It takes several clicks even if you know where to drill down. It can take many more clicks if you don't know where it is.

Update to .NET Core 2.0

Possible Solution

Update dotnet sdk to 2.0

Detailed Description

We are currently running on 1.1 and could probably do to update to the most current implementation

Missing lazy loading tutorial.

Lazy loading is implemented for the entity framework core layer, but there is currently no tutorial or documentation around using it. We need to create one and link to it from DotNetDocumentation.md

Support 'Expanding' into an existing instance

We want to create a variation of the expand method that, instead of trying to instantiate a new object to project into, takes an existing one.

This helps the scenario in which we have an existing item and we want to update it with items from the source object. So perhaps you have a Car and a projection that hides the license plate number:

class Car {
  public string Color {get; set;}
  public string NickName {get; set;}
  public string License {get; set;}
}

class EditableCar {
  public string Color {get; set;}
  public string NickName {get; set;}
}

I may want to get my existing car object, then overlay an EditableCar onto it to overwrite the values as set in EditableCar. So taking:

{
  Color:"black",
  NickName: "nighthawk",
  License: "2AK 8GJ"
}

and expanding the following projection into it

{
  Color:"white",
  NickName":"dayhawk"
}

would result in

{
  Color:"white",
  NickName":"dayhawk",
  License: "2AK 8GJ"   
}

Add a [InternalOnly] attribute

We want to add an [InternalOnly(bool throwException = true)] attribute. We should be able to apply it to classes, methods, or properties.

The goal of this attribute is to ensure that the object/value it is attached to is NEVER passed out of the Expander. So you may mark Password as internal only to assert that even if it is somehow requested, it'll never get sent over the wire.

This means that the attribute should be checked:

  • When expanding an object, similar to an Authorization check. If the object type is InternalOnly, return null
  • When reading a property or method. If the source property or method is marked InternalOnly, is should return null instead

In the two above cases, if the throwException property is set (which should be the default) it would throw an Exception of a new type: InternalOnlyViolationException. If it is not set (ie [InternalOnly(false)]) then simply return the null and do not throw.

Remove dependency on Serilog

I'm not sure we even actually log to serilog; it's a dependency we don't really need to take on.

Possible Solution

If we are actually logging somewhere, we'd want to migrate to something like LibLog that provides an abstract way for the implementor to designate the logging library.

Expected Behavior

Should not have to install & use serilog to use popcorn.

Actual Behavior

Serilog is brought in as an automatic dependency.

Support 'Expanding' a Dictionary<string, ?> into a strong type

We want to support the scenario where the destination type is a strong type (eg 'Car') but the source is a dictionary. The simple implementation is to take each key in the dictionary and try to write it to a property in the target type. This is the same as Expand currently does, but instead of iterating over the source's properties we'd need to iterate over its keys.

Missing Authorization tutorial

Possible Solution

We will want to add a tutorial explaining how to use the newly added authorization ability. See #9 for a full explanation of what was added as well as referencing the commit to see the code.

Possible Implementation

  • Add an example usage to the dotnet example project
  • Add a tutorial to the dotnet example project as well
  • Double check that all in-code documentation is as you feel needed to use this feature

Add open source CI tool?

Possible Solution

Ideally we would have a GitHub facing CI tool that did things like:

  • Build (required)
  • Test (required)
  • Code Coverage (optional)
  • Deployment (not desired)

You'll find that there is already a git-lab.yml in the project so we may just want to mirror some of those steps to a free for open source site + potentially add relevant things.

Possible Implementation

  1. Investigate Travis CI/Appveyor and/or any other possible options. Appveyor may be a decent place to start as they seem to build on windows machines.
  2. Implement the CI stages shown above
  3. Include badges for the following on the readme. Nuget version, Build status, test coverage (if applicable)

Add Sort ability to blind expansion

Currently the sort function will not work at all for blindly expanded objects. The big question here is do we want to support sort (and thus likely other extensions + features we add to popcorn minus the simple include expansion) for Blindly expanded objects?

Possible Solution

Should we choose to extend the sort functionality to Blindly expanded objects, we will probably just try to sort objects normally (based on properties) and should that fail, attempt to sort blind based on dictionary values.

I could see this being a bit of a bear to keep up and possibly lead to some weird error scenarios if not implemented with some intention (just because doing things blind by nature makes them more risky)

(Side note: Reminder to update the tutorial when we update the sorting functionality as it currently says sorting isn't supported for blind expansion objects should we implement this)

Missing Factories tutorial

Detailed Description

In PopcornConfiguration.cs we define the ability to assign a factory, both with and without a context provided. We want to document how to use these functions so as to make them accessible to users.

Possible Implementation

  • Add a test for the Assign Factory function that takes in a context to ExpanderTests.cs (you'll find a test for the generic AssignFactory function already exists and can be used as reference)
  • Add an example of how to use the factory assignment in the PopcornExample project if possible.
  • Add a tutorial to the PopcornExample project on how to use factories.

Enhancement: Add documentation on using the Example project(s)

Issue tracker is ONLY used for reporting bugs. NO NEW FEATURE ACCEPTED! Use stackoverflow for supporting issues.

Currently there is no documentation for the example project.

It is not obvious that the project will run and the endpoints accessed though curl/postman/etc type http request tools.

Possible Solution

Write up a how to for using the example project.

Expected Behavior

Users can jump into the example and try out popcorn easily.

Possible Implementation

Add an md file for the user to explore.

[InternalOnly] Documentation

Possible Solution

In PR #35 / Issue #34 we added the InternalOnly attribute. We also want to include a tutorial on how this feature can be used, as well as maybe adding a bit of inline documentation.

Possible Implementation

  1. Add a tutorial on how to use the InternalOnly attribute
  2. Add an example to the example project
  3. We may also want to update the usage of InternalOnlyViolationException to include the name of the method/class/property that was marked InternalOnly in the message returned with the exception

Missing "context" tutorial

Possible Solution

We currently have the ability to "SetContext" on the intial Popcorn configuration (seen in PopcornConfiguration.cs), but we don't have any documentation that really dives into the power of a context and how it can be used within Popcorn.

Possible Implementation

  • Add a tutorial and link it to the PopcornCoreExample
  • Bonus points for tests to prove the context usages
  • More bonus points for adding an example implementation of those use cases in PopcornCoreExample

Add Authorizers to restrict access to objects

We need to be able to assign functions to restrict access to certain object, potentially based on the Context.

Imagine for example I'm interacting with an auction website API. I may request the status of a particular auction, and as part of that object there is a list of 'bids' on the auction. I should only see my own, not bids belonging to anyone else.

I should be able to attach a function to the 'Bid' object type to restrict it from being projected (either as a property or within a collection) if my criteria isn't met.

Blind Expansion only applies to primary objects, not properties on objects

Currently it would seem that blind expansions aren't universally applied to objects. In the example below you will see that if an API response is the object itself it is expanded blindly, but that same object, used as a property, on a different object in a response is not expanded or allowed to be manipulated by includes.

Steps to Reproduce

In the example project you'll see we have a Business class and an API endpoint to GET businesses.

    public class Business
    {
        public string Name { get; set; }
        public string StreetAddress { get; set; }
        public int ZipCode { get; set; }
        
        public List<Employee> Employees { get; set; }

        public enum Purposes
        {
            Shoes,
            Vehicles,
            Clothes,
            Tools
        }
        public Purposes Purpose { get; set; }
    }

If the GET businesses endpoint is queried as below you will see that that it returns an expanded response as you'd expect:

http://localhost:49699/api/example/businesses?include=[Name,ZipCode,StreetAddress,Employees]

{
    "Success": true,
    "Data": [
        {
            "Name": "Shoe Biz",
            "ZipCode": 55555,
            "StreetAddress": "1234 Street St",
            "Employees": [
                {
                    "FirstName": "Jack",
                    "LastName": "Donaghy",
                    "Employment": "Employed"
                },
                {
                    "FirstName": "Liz",
                    "LastName": "Lemon",
                    "Employment": "Employed"
                }
            ]
        },
        {
            "Name": "Car Biz",
            "ZipCode": 44444,
            "StreetAddress": "4321 Avenue Ave",
            "Employees": []
        }
    ]
}

however
You'll see in code that I'm pushing soon (or that you can recreate yourself) if we add a "Manufacturer" property to our Cars class as shown below.

    public class Car
    {
      ......

        public Business Manufacturer { get; set; }
    }

    public class CarProjection
    {
       .....

        public Business Manufacturer { get; set; }
    }

And a request is made to the GET cars endpoint:

http://localhost:49699/api/example/cars?include=[Make,Model,Manufacturer]

{
    "Success": true,
    "Data": [
        {
            "Model": "Firebird",
            "Make": "Pontiac",
            "Insured": false
        },
        {
            "Model": "250 GTO",
            "Make": "Ferrari N.V.",
            "Insured": false,
            "Manufacturer": {
                "Name": "Car Biz",
                "StreetAddress": "4321 Avenue Ave",
                "ZipCode": 44444,
                "Employees": [],
                "Purpose": 1
            }
        },
        {
            "Model": "Cayman",
            "Make": "Porsche",
            "Insured": false,
            "Manufacturer": {
                "Name": "Car Biz",
                "StreetAddress": "4321 Avenue Ave",
                "ZipCode": 44444,
                "Employees": [],
                "Purpose": 1
            }
        }
    ]
}

Should we attempt to do any specific includes on the manufacturer property we don't see any change in results:

http://localhost:49699/api/example/cars?include=[Make,Model,Manufacturer[Name]]

{
    "Success": true,
    "Data": [
        {
            "Model": "Firebird",
            "Make": "Pontiac",
            "Insured": false
        },
        {
            "Model": "250 GTO",
            "Make": "Ferrari N.V.",
            "Insured": false,
            "Manufacturer": {
                "Name": "Car Biz",
                "StreetAddress": "4321 Avenue Ave",
                "ZipCode": 44444,
                "Employees": [],
                "Purpose": 1
            }
        },
        {
            "Model": "Cayman",
            "Make": "Porsche",
            "Insured": false,
            "Manufacturer": {
                "Name": "Car Biz",
                "StreetAddress": "4321 Avenue Ave",
                "ZipCode": 44444,
                "Employees": [],
                "Purpose": 1
            }
        }
    ]
}

Expected Behavior

I would potentially expect the object to be blindly expanded universally, whether as a response object itself or when referenced as a property.

Possible Implementation

We could probably refactor the logic a bit to broaden the scope of when objects are checked to blind expand and how. Currently we see the manufacturer is set in the TrySetValue method because there is no mapping to push this logic down the lane and have the object attempt to blindly mapped.

Note: Depending on our implementation here we may also want to consider the impact on the InternalOnly property as it is set on the non-projected object and how that plays in with blind expansion

Debug mode - "complete" query string parameter

Possible Solution

As I'm debugging/testing an implementation that uses Popcorn, I find I'm spending quite a bit more time configuring my tests to have the exact desired includes to have sufficient data to prove what I want to prove.

Maybe we consider adding a query string parameter, like say "complete", that returns all available properties in the response object and its subordinates in an API call?
If abuse (or really potentially defeating the purpose of having Popcorn at all) is a concern here, maybe we make the attribute for debug mode only?

Steps to Reproduce

  1. Create a complex object that has 10+ properties, with complex sub-properties
  2. Attempt to query an endpoint that returns said object
  3. Format an includes string that basically returns everything

Expected Behavior

Ideally, testers and devs alike could have an option to brute popcorn a little, minimally in debug mode, to more easily test their endpoints so they don't have extremely long include statements in their tests.
i.e. Less time testing => More time making new products

Actual Behavior

A non-trivial amount of time spent structuring API tests to have the perfect include statement to prove the logic implemented on the endpoint.

Need a .Net Framework implementation for Entity Framework

Within the dotnet implementation, we have:

  • A base implementation in .Net Standard (usable by all)
  • An injection layer in .Net Core for EntityFrameworkCore and AspNetCore

Many projects use the original Entity Framework and Asp.Net under .Net Framework. We would like to add an implementation for this scenario.

Setup:

  • A new .Net Framework Class Library
  • Named PopcornFramework
  • Base namespace Skyward.Popcorn.Framework
  • Nuget references to asp.net and entity framework packages as needed.

Requirements:

  • Must implement an attribute that can be applied to individual endpoints that will expand and project. Reference Skyward.Popcorn.Core.ExpandResultAttribute for an example.
  • Add an extension method to PopcornConfiguration that allows a user to enable lazy-loaded property expansion. Reference Skyward.Popcorn.Core.EntityFrameworkCore for an example. NOTE: It is possible that since lazy loading is actually a feature in EF, there's no real additional work needed here.

Deploy release 2.0.0

Possible Solution

Let's deploy!

Possible Implementation

This one is farily straightforward

  1. Make sure all desired code is committed to master and accurate to the release 2.0 milestone (close all issues that have been finished)
  2. Update both csproj files with release information
  3. Write release notes in MD
  4. Push to nuget
  5. Paste release notes into GitHub as a release and publish

We need an attribute that designates that one type can map from another

We get a lot of mapping boilerplate lines when using simple maps that don't require much customization.

eg

config.Map<Car,CarProjection>();
config.Map<Employee,EmployeeProjection>();
...

Ideally, we want an attribute that you can apply that designates something as the target of a mapping. So for example:

[ExpandFrom(typeof(Car), "[My,Default,Includes]"]
class CarProjections {
...
}

This means that we need a new method on PopcornConfiguration called something like 'ScanAssemblyForMapping' that will look for all class types within an assembly that have the [ExpandFrom] attribute applied, and automatically register them.

Add a default response wrapper for .net implementation layer.

We offer an 'inspector' option that allows users to intercept a response from a controller and 'wrap' it in some way. Internally, we use this pretty consistently to provide a 'Response' object containing a boolean 'Success' status, object 'Data' property (on success) and strings for ErrorCode, Message, and Details (on error).

It would be convenient for users who are setting up a brand new API to offer a default implementation that does this, as it has many benefits.

  • Create a class 'ApiResponse' with the properties mentioned above
  • The Inspector signature used throughout would need to be upgraded to include any thrown exception as well as the result object and the context
  • ExpandResultAttribute.OnActionExecuted would need to pull any thrown exception from the ActionExecutedContext and pass it to any inspector lambda.
  • Create a method on PopcornConfiguration called 'UseApiResponse'
    • This would attach an inspector that captures the result and wraps it in a new ApiResponse with Success = true if no exception was thrown, or Success = false, ErrorCode = (type name of the exception), Message = (Exception message), Details = (Exception.ToString())

Compiler picks the wrong overload of `Translate`

Related to https://stackoverflow.com/q/46819033/730326

Steps to Reproduce

Given a model like so:

public class DataModel {
    public List<Person> Persons { get; set; }
}

public class Person {
    public Guid Id { get; set; }
    public List<Pet> Pets {get; set;}

    public List<Pet> GetLivingPets() {
       // Do some computation to get the pets that are alive
    }
}

public class Pet {
    public Guid Id { get; set; }
    public string Name { get; set; }
    public bool Alive { get; set; }
}

public PetProjection {
    public Guid? Id { get; set; }
    public string Name { get; set; } 
}


public PersonProjection {
    public Guid? Id { get; set; }
    public List<PetProjection> LivingPets {get; set;}
}

And a mapping like so:

popcornConfig
.Map<Person, PersonProjection>(config: (personConfig) =>
{

     personConfig.Translate(fp => fp.LivingPets, f => f.GetLivingPets());

})
.MapEntityFramework<Pet, PetProjection, DataModel>(dbContextOptionsBuilder);

Expected Behavior

No error. Compiles and provides expansion for the Pets. (ie persons?include=[Name, LivingPets[Name]] returns only the pets names not the whole object)

Actual Behavior

On the .Translate line.

'Dictionary<string, object>' does not contain a definition for 'GetLivingPets' and no extension method 'GetLivingPets' accepting a first argument of type 'Dictionary<string, object>' could be found (are you missing a using directive or an assembly reference?)   

Work around

If I change the lambda signature to personConfig.Translate(fp => fp.LivingPets, (f, dict) => f.GetLivingPets()); it works just fine.

Alternatively, if I rename GetLivingPets() to LivingPets() I can remove the translation and it all works.

ExpandEntityFramework does not offer a way to load db context when expanding plain functions

Issue

Below is an example Model-Projection setup that shows the issue. See comments for details.

Model.cs
public Model {
    public List<SubEntity> SubEntities {get; set;}

    public List<SubEntity> FilteredSubEntities() {
           // The context will need the 'SubEntities' property loaded when
           // this function is evaluated. However, if only 'FilteredSubEntities' is
           // included in the includes, SubEntities will be 'null' and this will fail.
           return SubEntities.Where(...).ToList();
    } 
}
public ModelProjection {
     public List<SubEntity> FilteredSubEntities { get; set; }
}

public SubEntity { // Doesnt matter }
public SubEntityProjection { //Doesnt matter }

This is the mapping. Notice there's nothing custom about it.

.MapEntityFramework<Model , ModelProjection, Context>(dbContextOptionsBuilder)
.MapEntityFramework<SubEntity , SubEntityProjection, Context>(dbContextOptionsBuilder)

The include that does not work:

/api/models?include=[FilteredSubEntities]

However this will work but only if in this order:

/api/models?include=[SubEntities, FilteredSubEntities]

Expected enhancement

Not sure. But something like the context being passed to functions being expanded inside the model should do the trick. Ideally, only if the context is specified in the function signature. Similar to the way dependency injection is done in Aspnet Core.

Work around

This is a gross workaround because I'm directly using the dbcontext inside the context. The hardcoded key is the most brittle portion. I could also loop the dictionary but that would happen for every api request.

.Map<Model, ModelProjection>(config: (config) =>
{
    config
        .PrepareProperty("FilteredSubEntities", (destinationObject, destinationProperty, sourceObject, context) =>
        {
            if (context.ContainsKey("Db"))
            {
                var expandDb = context["Db"] as MyContext;
                expandDb.Entry(sourceObject).Collection("SubEntities").Load();
            }
        });
})

ExpandResultAttribute should optionally allow a parameter to disable expansion when applied

Right now you can:

  • Expand ALL endpoints automatically
  • Expand only the endpoints you want by setting calling SetOptIn() and then applying [ExpandResult]

There's no way to expand all endpoints automatically, but opt out of specific endpoints.

Desired Behavior

There should be a property on the ExpandResultAttribute class called bool ShouldExpand. This must be settable via a constructor, so that all of:

  • [ExpandResult] // Should expand
  • [ExpandResult(true)] // Should expand
  • [ExpandResult(false)] // Should not expand

work. This means that the property will need to be checked in OnActionExecuted. If ShouldExpand is false, then the inspect should not run either.

MapEntityFramework method does not allow mapping configuration

While the Map method on PopcornConfiguration allows you to pass in a configuration action, this ability is not present on the MapEntityFramework extension method. This means that you cannot (easily) configure translations or other advanced properties.

Steps to Reproduce

Try to map an entity framework object with a translation.
config.MapEntityFramework<Project, ProjectProjection, TestModelContext>(TestModelContext.ConfigureOptions(), (definition) => { definition.Translate(o => o.Id, () => Guid.NewGuid()); });

Expected Behavior

You should be able to configure this in the MapEntityFramework call

Actual Behavior

You cannot. You have to execute a Map<> call immediately following the MapEntityFramework<> call to attach any additional configuration.

Possible Solution

Accept a new parameter in the MapEntityFramework method (optional) that will configure the object after MapEntityFramework does its configuration. (Basically act exactly like Map after all the custom work).

Polymorphism in DefaultIncludes

Possible Solution

  1. We can make it so the [IncludeByDefault]'s are respected for a base class and all of it's derived classes
  2. We can also optionally (or additionally) add a attribute that can be specified on a base class that indicates whether its sub class [IncludeByDefault]'s should be included.

Steps to Reproduce

Given the following projections

    public class BaseProjection
    {
        [IncludeByDefault]
        public Guid? Id { get; set; }
        public string Name { get; set; }
    }


    public class SubProjection:BaseProjection
    {
        [IncludeByDefault]
        public Guid? SubId { get; set; }
        [IncludeByDefault]
        public string SubName { get; set; }
    }

If then we make a hypothetical call as shown below (that for sake of example is just a call for an object that contains a property that correlates to BaseProjection) , we get the following response:

http://localhost:5000/api/1/object?include=[Base]
{
    "Success": true,
    "Data":
        {
            "Base": {
                    "SubId": "709cf9c6-dc56-44b4-ba7b-f204c342e8ed",
                    "SubName": "asdf",
              }
       }
}

Expected Behavior

We would minimally expect the "Id" for the BaseProjection to be included as well

Actual Behavior

Note that the "Id" property isn't included.

Possible Implementation

It's a bit up for debate how we could choose to implement this, but here is a note from a chat we had on whether this is a bug or enhancement:
Well, the question is what the expectation was; should it have included the base properties marked include by default? There's arguments either way. If they are included by default, it is hard (or potentially impossible) for me to then exclude them in a subclass. If they are not, then it is hard (or potentially impossible) for me to then additionally include them in a subclass. (Either case is probably solved by an explicit include on the request)

Allow 'mapping' to be defined for more than one target type.

Right now Popcorn only allows configuration of a 'default' mapping; one source type to one destination type.

This makes sense at the point of 'blind' expansion (the return result from the Api) but we need to be able to map any given source into multiple different projections, especially given that we know the destination type when we inspect properties. So for example:

// data object
class User{
 public Guid Id {get; set;}
 public string Name {get; set;}
 public string Email {get; set; }
}

// a projection
class UserNameOnly { 
 public Guid Id {get; set;}
 public string Name {get; set;}
}

// a different projection
class UserEmailOnly {
 public Guid Id {get; set;}
 public string Email {get; set;}
}

// Aggregation object
public class UserRelationship{
 public User Owner {get; set;}
 public User  Child {get; set;}
} 

// A projection of the aggregation, 
// projecting the User into different forms
public class UserRelationshipProjection {
 public UserNameOnly Owner {get; set;}
 public UserEmailOnly Child {get; set;}
}

We should be able to return a UserRelationship from an API and have the contained users correctly projected into UserNameOnly and UserEmailOnly types as expected.

We do still need the concept of a 'default' projection type to meet the requirements of the current implementation.

Add integration tests

Right now we have no full-pipeline tests for the AspNetCore implementation.

Please add tests that instatiate an in-memory HttpServer using PopcornCoreExample and test that:

  • Objects that are mapped are 'expanded' correctly
  • Objects that are not mapped are returned as it
  • In either case, the response should be in our Response object wrapper
  • Exceptions are caught and placed in the error code section of the Response object
  • Nulls are not serialized
  • Dictionaries are serialized properly
  • Lists are serialized properly
  • DefaultInclude attributes are respected
  • Sort is respected
  • The [ExpandResult] attribute is respected

These should go in a dedicated test project.

Missing Blind Expansion tutorial

We implemented 'blind expansion' for the dotnet version (which basically means being able to 'expand' a type without having to explicitly create a projection for it) but there's no tutorial or documentation around using it. We need to create one and link to it from DotNetDocumentation.md

We probably also need further testing to make sure this is a valuable use-case. We previously had issues with blind expansion being too aggressive.

Omitting properties that are in the include list but are 'null'?

If I include an entity and that entity is null, it will be omitted from the results. This is confusing because it looks the same as if I had not included it at all.

This appears to be true of any object that is null. I'm not sure what happens to empty lists.

Is there a configuration change I can make to have null values show up in the responses. Perhaps using the .SetInspector()?

If not possible currently, a configuration function like .ExposeNulls() could be added.

ExpandFrom Documentation

In #33 we created an ExpandFrom attribute. We now need to go back and add some documentation and usage examples.

You'll definitely want to read #33 and become familiar with the implementation.

Possible Implementation

  1. Adding a tutorial for ExpandFrom to docs
    1a. Add a reference to the Documentation list and the table of contents
  2. Add a relevant example usage to the NetCoreExample project. It'll probably be easiest to make a new relevant class to the scenario in the example.

Less restrictive self referencing loop detection

Popcorn has self referencing loop detection which throws an exception whenever an include contains an object with a reference to a parent object. This is important in preventing infinite loops.

I think self referencing loops are only a problem if there is a self referencing loop in the default includes. If there are no loops in the default includes, the depth of nesting is defined by the include statement provided by the client. Which is to say it goes as deep as the requester wants it to but should never be infinite.

I would like to keep the existing protection against self referencing loops with default includes but also allow for includes that include their parent object.

For example, given a root object called Root with a child Child, I would like to have the following include on 'Root' be valid: [Name, Child[ Name, Root[ Name ] ] ].

This should resolve to:

{
    "Name": "Bob",
    "Child": {
          "Name": "Jane",
          "Root": {
                "Name": "Bob"
           }
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.