Skip to content

De-Couple DAL from Entity Framework #89

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Apr 25, 2017
Merged

De-Couple DAL from Entity Framework #89

merged 36 commits into from
Apr 25, 2017

Conversation

jaredcnance
Copy link
Contributor

@jaredcnance jaredcnance commented Apr 8, 2017

Closes #84

As written, this will break all controllers inheriting from JsonApiController. You would need to update your controller constructor parameters. One approach (as lightly discussed in #84) would be to provide a deprecated constructor method and possibly a LegacyJsonApiController that would contain the current logic. JsonApiController would then pass calls into the legacy controller if it were constructed using the IEntityRepository constructor instead of the IResourceService param. This would be removed at a later date. I would like to hear any thoughts on this and whether it is necessary, or if we should accept the breaking change. Also, does this constitute a major version change?

Please feel free to tear this apart or raise any concerns/suggestions/complaints

TODO

  • Replace IEntityRepository with IResourceService in JsonApiController
  • Determine how best to maintain backwards compatibility, if at all
  • Acceptance tests to validate extensibility at the service layer
  • Custom ContextGraphBuilder API
  • Service Collection Extension (services.AddJsonApi()) without the DbContext generic
  • Complete documentation
  • Bump project version
  • Add ResourceAttribute to allow custom type names for DbSets

also affects the service collection extensions
Since AddJsonApiInternals<T> was previously exposed, it needs to remain. Also, this is where the DbContext service should be added to the service collection since users could call this instead of UseJsonApi. It would be unreasonable to expect users to inject their dbContexts as the DbContext base type
as far as I can tell, .Net's native DI will check all pathways for resolution even if they shouldn't be resolved. Specifically, the Internal.Generics classes require DbContext to be injected. However, they would never be resolved if the app does not actually use EF and implements their own IResourceService
@@ -34,7 +34,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddJsonApi(options => {
options.Namespace = "api/v1";
options.BuildContextGraph((builder) => {
builder.AddResource<TodoItem>("todo-items");
builder.AddResource<TodoItem>("TodoItems");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, this simulates the DbSet name. I don't really like the way this works, especially since the format provided is not even the public format. But, it will currently break in the de-serialization. Need to do some more work on this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the DbSets, the name is dasherized and there is no opt-out. Why are you not dasherizing here as well?

{
services.AddScoped<DbContext>();
services.AddSingleton<DbContextOptions>(new DbContextOptionsBuilder().Options);
}
Copy link
Contributor Author

@jaredcnance jaredcnance Apr 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really really don't like this, but as stated in the commit message:

as far as I can tell, .Net's native DI will check all pathways for resolution even if they shouldn't be resolved. Specifically, the Internal.Generics classes require DbContext to be injected. However, they would never be resolved if the app does not actually use EF and implements their own IResourceService

Would love to see if anyone has any extra insight into this.

Tracking: aspnet/DependencyInjection#514

@jaredcnance jaredcnance requested a review from jfhs April 14, 2017 02:23
@jaredcnance
Copy link
Contributor Author

@JanMattner @jfhs this is mostly ready. I'll spend some time this weekend adding extra tests around it. Wanted to get your opinions on it so far. I've updated the Readme, and there is a sample project that does not use EF.

@JanMattner
Copy link
Contributor

JanMattner commented Apr 18, 2017

Thanks, great work! However, what first comes to my mind: If I want to use the IResourceService as an interface to some HTTP service, I won't be able to fulfill Task<IEnumerable<T>> GetAsync(); simply because of the huge amount of items.

I need at least some kind of paging mechanism with pageSize and pageNumber.

@jaredcnance
Copy link
Contributor Author

@JanMattner you should be able to implement paging however you like. You can get the IJsonApiContext from your service constructor. From that, you can get the page query like so:

var pageNumber = _jsonApiContext.PageManager.CurrentPage;
var route = $"{EXTERNAL_API_HOST}/items?page={pageNumber}";
var request = new HttpRequestMessage(new HttpMethod("GET"), route);
var response = await client.SendAsync(request);

Is this what you're talking about?

@JanMattner
Copy link
Contributor

My bad! Yes, that was what I was looking for. I'll have a closer look at the rest later and give feedback.

@jaredcnance jaredcnance mentioned this pull request Apr 18, 2017
6 tasks
Copy link
Contributor

@JanMattner JanMattner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you accidentally added the "bin" and "obj" folder of the new "NoEntityFrameworkExample" project.

I think it's great that you clean up the magic dasherization. However, currently I cannot capture the whole impact of that and how the default behavior is changed by removing the dasherization. In any case the easiest way would be to just introduce a new major version and leave it to the users to figure out the impact on their existing applications. The possible breaking change about the controller constructor encourages that solution.

However, we should then at least ensure that all the magic dasherization is addressed.

var entityType = typeof(TResource);
Entities.Add(new ContextEntity
{
EntityName = pluralizedTypeName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is not dasherized. Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below. I'd like to avoid mutating any manual input so we can avoid inconsistencies as described in #93

var entityType = dbSetType.GetGenericArguments()[0];
entities.Add(new ContextEntity
{
EntityName = property.Name.Dasherize(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either there is an attribute for the DbSet to define a custom non-dasherized name or the custom resource name above (when directly adding a resource instead of using the DbContext) must be dasherized as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to agree with allowing for an optional attribute per DbSet that would allow for a custom name.

  • When defining names manually (either through the ContextGraphBuilder or using a ResourceAttribute), whatever input is provided will be accepted as the resource name
  • If no [Resource("my-resource")] is specified on the DbSet, then it will be dasherized by default

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good! And yes you are right, any manual explicit input should not be altered.

public void BuildContextGraph(Action<IContextGraphBuilder> builder)
{
if(builder == null)
throw new ArgumentException("Cannot build non-EF context graph without a IContextGraphBuilder action", nameof(builder));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: "without an IContextGraphBuilder"

@jaredcnance jaredcnance changed the title [WIP] De-Couple DAL from Entity Framework De-Couple DAL from Entity Framework Apr 19, 2017
@jaredcnance
Copy link
Contributor Author

jaredcnance commented Apr 19, 2017

I think this is ready to go (other than the goofy DI stuff mentioned above -- but i dont think I want that to hold up a pre-release).

If there are no other comments, I'll merge this tomorrow afternoon. Thanks @JanMattner for your help!

@JanMattner
Copy link
Contributor

I have found still a buggy usage of the dasherization when creating links. I have added a couple of tests that currently fail.

@jaredcnance
Copy link
Contributor Author

jaredcnance commented Apr 21, 2017

@JanMattner thanks for the tests. So, the main issue revolves around this line (which can be improved by removing the .Dasherize() and assume the routes will be in the same format as the entity name).

I made the assumption that APIs should use semantic endpoints (maybe the wrong name for it?). That is, a request to "/todoitems" will return "todoitems" and a request to "/todo-items" should return "todo-items". This breaks the link builder as it assumes, the base route structure will end with the resource name. I'd be curious to hear if you currently need this support and what kind of constraints create this kind of requirement.

I've added a fix that removes hyphenation from being performed in the link builder.

@JanMattner
Copy link
Contributor

Yes, you are right, in general a server implementation should follow the guidelines and provide semantic endpoints. However, the user is not bound to that and the current implementation is just another hidden dasherization. Furthermore, if I interpret the code correctly, all path segments will be dasherized. But what if I want to host (even my proper semantic) endpoint at:
https://host.com/my/customPath/todo-items

It would dasherize the whole path. I just think that this is not really intuitive if done hidden / implicitly.

@jaredcnance
Copy link
Contributor Author

jaredcnance commented Apr 21, 2017

I agree that there should not be any dasherization in the LinkBuilder. My last commit should have fixed this.

The way the logic is intended to work is to identify the "base route/namespace" from the current route and build links from there:

                 resource-name |--------|
https://host.com/my/customPath/todo-items
|-----base-route-------------|

You're right that a user can define routes however they want and nothing prevents that. If someone defines a controller as:

[Route("custom/route/blueberries")]
public class TodoItemsController : JsonApiController<TodoItem>
{
   // ...
}

There is no great way (that I am aware of) to determine "TodoItems" links should actually point to "Blueberries". In this scenario, the link builder assumes /blueberries is actually part of the base route and would build links like:

https://host.com/custom/route/blueberries/todo-items

I'm unsure how to proceed with this. Perhaps the link builder should throw if the resource cannot be found in the path? But I'm not convinced that we should support routing like this.

@jaredcnance
Copy link
Contributor Author

There is one other issue that we need to address and that's the DasherizedRoutingConvention that assumes all JsonApiControllers should have dasherized names.

var template = $"{_namespace}/{controller.ControllerName.Dasherize()}";

I propose a few options:

  • introduce a flag on JsonApiOptions that would allow users to disable the DasherizedRoutingConvention globally
  • provide attributes that could decorate the controller to disable the routing convention
  • check if the user has supplied a specific route through the RouteAttribute and if so, then disable the convention.

However, I think this is more about routing choices and would like to close this PR and punt routing into another. Although, to be clear, I'd like to see both of these go out in the 2.0.0 release.

these tests are not currently setup correctly. they assume that a resource can be routed based on a different name (todo-items-test -> todo-items). this is not currently supported and is out of scope for this PR. I am open to discussion, but don't believe this is going to be on the roadmap.

Other changes (todoitems -> todo-items) is for the dame reason. TodoItems have been mapped to todo-items in this example so the tests would fail.
@jaredcnance
Copy link
Contributor Author

@JanMattner i'm going to merge this into develop and move any additional routing concerns into a new PR

@jaredcnance jaredcnance merged commit d4cb99d into develop Apr 25, 2017
@jaredcnance jaredcnance deleted the feat/decouple-dal branch April 25, 2017 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

2 participants