-
-
Notifications
You must be signed in to change notification settings - Fork 158
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
Conversation
this is reprensentative of the kind of breaking change this introduces
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"); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); | ||
} |
There was a problem hiding this comment.
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
…once there is really no reason to store the proper cased entity name this also lays the ground work so that later users can specify different cases for entity type names
@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. |
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 I need at least some kind of paging mechanism with pageSize and pageNumber. |
@JanMattner you should be able to implement paging however you like. You can get the 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? |
My bad! Yes, that was what I was looking for. I'll have a closer look at the rest later and give feedback. |
this should be cleaned up by #91
failing with duplicate key value violates unique constraint "pg_database_datname_index"
There was a problem hiding this 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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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(), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: "without an IContextGraphBuilder"
there should be very few places .Dasherize is actually used. most of the time we should stick to using either the Internal or Public attr/relationship names
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! |
400a088
to
517367b
Compare
…d custom route controllers and links
I have found still a buggy usage of the dasherization when creating links. I have added a couple of tests that currently fail. |
@JanMattner thanks for the tests. So, the main issue revolves around this line (which can be improved by removing the 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. |
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: It would dasherize the whole path. I just think that this is not really intuitive if done hidden / implicitly. |
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:
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:
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. |
There is one other issue that we need to address and that's the var template = $"{_namespace}/{controller.ControllerName.Dasherize()}"; I propose a few options:
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.
@JanMattner i'm going to merge this into develop and move any additional routing concerns into a new PR |
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 aLegacyJsonApiController
that would contain the current logic.JsonApiController
would then pass calls into the legacy controller if it were constructed using theIEntityRepository
constructor instead of theIResourceService
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
IEntityRepository
withIResourceService
inJsonApiController
services.AddJsonApi()
) without theDbContext
genericResourceAttribute
to allow custom type names for DbSets