GithubHelp home page GithubHelp logo

Comments (14)

ardalis avatar ardalis commented on May 15, 2024 3

It's that way (in the app) mainly for simplicity. In the excerpt above, not that it says "Your application can avoid" -> can, not must or should. There are benefits to avoiding the project reference, mainly in that it becomes difficult/impossible to have early-bound compile-time references to types in the Infrastructure project (you could still do it with reflection, but that requires a bit more effort). However, it's not trivial to configure this in ASP.NET Core with its default mechanism for DI and registering services like EF and Identity. The main reason is that these static helper methods mainly use generics like AddDbContext and for these to work you need to be able to reference T at compile time.

I have an article demonstrating how to do exactly what the guidance suggests in full .NET here:
https://ardalis.com/avoid-referencing-infrastructure-in-visual-studio-solutions

I intend to write a similar article for ASP.NET Core but haven't made the time yet. I will be revising the sample and eBook for 2.1 in the very near future and I'll keep this feedback in mind and will try to at least show how to achieve this even if the sample keeps things simple and keeps the project reference.

Thanks for the feedback!

from eshoponweb.

DannyAllegrezza avatar DannyAllegrezza commented on May 15, 2024 2

@J0nKn1ght,

Any updates here? Are you continuing to keep the ApplicationUser in Core?

I'm curious because I was about to write a post asking the same question as you. I was curious if @ardalis could expand on how we might keep a clean architecture yet still have identity.

I'm wondering if the IdentityGuid in buyer class is a hint at how one might accomplish this?

    public class Buyer : BaseEntity, IAggregateRoot
    {
        public string IdentityGuid { get; private set; }

        private List<PaymentMethod> _paymentMethods = new List<PaymentMethod>();

        public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();

        protected Buyer()
        {
        }

        public Buyer(string identity) : this()
        {
            IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity));
        }
    }

Unless I am missing something, it appears to be unused.

from eshoponweb.

trevor-hackett avatar trevor-hackett commented on May 15, 2024 1

Unless you really needed to have the navigation properties, you could keep the ApplicationUser separate and don't have navigation properties. You'd store the userId with the Orders (and anywhere else that you need) and if you had the userId already, you could just query the Orders where the userIds are equal.

from eshoponweb.

ardalis avatar ardalis commented on May 15, 2024 1

@DannyAllegrezza The Buyer entity's IdentityGuid is how the domain interacts with the identity system in this sample. It's intentionally separating identity concerns from domain concerns. You can see where this is used in the BasketWithItemsSpecification class, which will list the items in the Buyer's basket (or an anonymous user's basket). It's also used in the CustomerOrdersWithItemsSpecification.

from eshoponweb.

ardalis avatar ardalis commented on May 15, 2024 1

My recommendation if you're going to use the built-in identity features that rely on IdentityUser is to keep your definition of ApplicationUser in the Infrastructure class. In your domain, refer to this class's ID anywhere you need it, just as Buyer does. If you need to include additional details on the 'user' you can do so on a domain entity in ApplicationCore. For example, I could easily add FirstName and LastName to Buyer. It would then be pretty easy to create a specification that would return all orders from any buyer with a last name of 'smith', for example. As for specifying the default display names for Email and PhoneNumber, etc., these annotations should generally be applied to ViewModels, since you don't want to directly expose your domain model to your MVC Views (or APIs). Instead, create a BuyerViewModel class (for instance) which has only the properties the view will use, and add any additional metadata to this class.

from eshoponweb.

J0nKn1ght avatar J0nKn1ght commented on May 15, 2024

Ok, maybe my example was a bit simplistic. In real life, you would want to be able to do a query such as 'select all Orders where Order.OrderDate < x and Order.User.LastName.Contains(y)'. Not having navigation properties precludes this being a database query.
I did try to take this approach, and keep ApplicationUser in the Infrastructure, and have a UserProfile model in Core, which shared the same ID and had the navigation properties to my business entities. However, the first thing that I wanted to do was create a user admin feature, where you could filter users by various properties, including the roles they were in. Not having navigation properties makes it impossible to run this query at the database level, and when the system can potentially grow to 10,000s of users, retrieving the data and querying in memory isn't really viable. This is still an issue with the latest version of Asp.Net Core Identity.
I've ended up moving ApplicationUser back into Core, and adding the required references to Asp.Net Identity, etc., which obviously goes against the Clean Architecture principles.

from eshoponweb.

J0nKn1ght avatar J0nKn1ght commented on May 15, 2024

@DannyAllegrezza No, I'm afraid that I haven't seen anything yet which suggests a solution to this problem. In fact it's got worse, in that I've also ended up adding a reference to Microsoft.AspNetCore.Mvc.Core in my Core project, so that I can use the ModelMetadataType attribute in order to override the default display names for the 'Email' and 'PhoneNumber' properties in the IdentityUser class.
Incidentally, I've also changed all of the 'Identity' related definitions so that they are typed as Guid, rather than the default type of string.
If anyone has any further suggestions of how to properly implement a clean architecture with the current .Net Core tool set (v2), I'd be interested to hear how to achieve it.

from eshoponweb.

J0nKn1ght avatar J0nKn1ght commented on May 15, 2024

@ardalis Thanks. I did go down that route, but as I mentioned in my previous post, the first thing that I needed to do on the site I was developing was to add a User Admin function, which needed to be able to query users by Role, and that approach made it impossible to do that without the query running in memory. I guess the only option really is to write my own Identity provider, which I don't have time to do with this project.
On the use of ViewModels, I know that this is the accepted approach, but I've never quite understood how to square this with the DRY principle. You are literally repeating every class that you need to use, and therefore increasing the maintenance burden. Also, this means that to some extent you have to duplicate the business logic in the ViewModel, by repeating the validation attributes that are required for the view to take advantage of client-side javascript validation.
I can understand having a defined interface for the models when writing a public API, but for internal use within an MVC site, it just seems a bit redundant to me.

from eshoponweb.

ardalis avatar ardalis commented on May 15, 2024

Right, sorry I didn't address that. If you're following a DDD approach, you could probably consider your user/role administration as a separate bounded context from the rest of your application (I'm guessing - I don't know the details of your app's primary focus). It also seems likely to me that the user admin portion of the app, whether it's its own BC or not, is mainly CRUD and doesn't have a ton of complex business rules. In this case, there's not necessarily a lot to be gained by following a pure DDD approach and using many of its patterns. Just create something simple that exposes the data to the UI and be done with it, but ideally do this in such a way that it's separate (different BC) from your main app for which you are trying to follow more of a DDD approach.

Regarding ViewModels, the advice is similar. If it's a simple CRUD app to be used internally, you can probably avoid the extra effort. However, if it's going to be public-facing and/or it's going to have a complex domain model, ViewModels offer advantages. When you do use ViewModels, you should have different models based on individual views or requests to the server. That means they shouldn't simply map 1:1 to your domain entities, because at a minimum you probably don't need an ID property on a Create action/form, for instance. You may have other properties, like DateCreated, that shouldn't be exposed on your create/edit forms, either. And there are potential attack vectors exposed by using your data/domain model as your API/web model, as well. For example, imagine you expose your ApplicationUser class, which let's say has a List Roles property in addition to a FullName property, on a profile edit page. On the POST action for this page you accept an ApplicationUser as a parameter and let model binding do its work, and in this action use a DbContext to attach the model-bound entity and then save it. This works great. Except that if a malicious user doesn't just post their FullName, but also an array of Roles that includes "Admin" in it (and you have such a role), then they've just made themselves an admin. If instead you had a ProfileViewModel that only had a FullName property, this scenario would be impossible.

HTH.

from eshoponweb.

J0nKn1ght avatar J0nKn1ght commented on May 15, 2024

Thanks for your detailed reply, @ardalis. The app we're developing is a bespoke CMS, so the vast majority of it is focused on the admin site, with very little on the site itself.
TBH, I'm wishing that we hadn't switched to .Net Core, as once you start to do any heavy development in it, the cracks begin to show (in terms of bugs and missing features). This isn't helped by the fact that VS2017 also seems to have numerous bugs in it. Although there have been a number of updates to VS2017, it now looks like there won't be an update to EF Core until late Q1 2018. As we have an initial release planned for Jan 2018, it means that we're having to drop out to SQL queries to work around some of the deficiencies in EF Core v2.
I think that these factors, combined with trying to use Clean Architecture for the first time, was a recipe for frustration!
Thanks for all of your advice, though.

from eshoponweb.

trevor-hackett avatar trevor-hackett commented on May 15, 2024

The way identity and the entities are separated in this project is very good practise. In most systems in enterprise applications users and application data are completely separate in different databases if not completely different systems altogether. Normally you'd do your specific filtering logic by querying the user system/database and then take the results and join them against your application database. In most cases this is out of your control and something you have to live with. The way this project is structured, it forces you to think similarly.

Having said all that, do what makes sense for your specific application. Follow as many good practices and design patterns as possible. The key is to not over architect your application and make it more difficult than it needs to be. Don't be afraid to break rules where it makes sense for your application.

from eshoponweb.

ardalis avatar ardalis commented on May 15, 2024

You're welcome and sorry you're hitting frustrations. Let me know (via my ardalis.com site) if you think it would be valuable at all for me to have a look at your app and its setup. I may be able to help. I agree with you that 1.x of .NET Core and EF and the early VS2017 builds were a bit rough, but right now with 2.x of .NET Core and the latest VS2017 I think things are in solid shape and there are no blockers for my scenarios or those of my clients. I'd be curious to hear more about what blocking issues you're seeing. Cheers.

from eshoponweb.

bgl3317 avatar bgl3317 commented on May 15, 2024

This is an excerpt from the guide -

The user interface layer in an ASP.NET Core MVC application will be the entry point for the application
and will be an ASP.NET Core MVC (or Razor Pages) project. This project should reference the
Application Core project, and its types should interact with infrastructure strictly through interfaces
defined in Application Core. No direct instantiation of (or static calls to) Infrastructure layer types
should be permitted in the UI layer. Your application can avoid referencing the Infrastructure project,
while still using its types at runtime.

Yet, in the sample app code, the web layer project references the infrastructure project and uses four separate infrastructure namespaces in the Startup file alone. This is really disappointing and seems to undermine the credibility of the architecture guidelines.

from eshoponweb.

TungDao1102 avatar TungDao1102 commented on May 15, 2024

I found a solution on reddit that we can use fluent API to configure foreign keys without specifying navigation properties inside the class. For example:

public class Card
{
public int Id { get; set; set; }
public string Name { get; set; set; }
public string UserId { get; set; set; }
// Other properties
}

protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<Card>()
            .HasOne<User>()
            .WithMany()
            .HasForeignKey(c => c.UserId);
    } 

What do you guys think about this approach?

from eshoponweb.

Related Issues (20)

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.