Base solution for your next web application

Activities of "codemonkey21"

To me, this seems more of a conceptual question than a technical question. I'll explain how in my project, I plan on attempting to tackle this since I'm doing something similar.

My project is sort of local reviews, with a twist. I plan on having regular users (no concept of tenancy) and business owners. Business owners will register, then create a business account (new tenant with subdomain). Since they are the original registrant of that tenancy, they become the account owner / admin for that tenant by default. They can then invite additional employees to join that tenancy and assign various business roles to manage the venues they own.

BusinessUser extends my User class, as it will have various additional properties. Business is really just Tenant. Will have various additional properties. Create a BusinessUserAppService for BusinessUsers. Create a UserAppService for Users. Use two different login forms; one for regular users and one for business users that point to the appropriate Controller / App Service.

So for what you're asking, I don't see this as being completely in one application service. I would use both a TenantAppService and a UserAppService. First I would check for an existing tenant of the same name and if the user exists, if either doesn't exist, create the tenant and add the new user to it.

Just my interpretation of it.

I created my project as a MPA site, then added in Zero following the instructions here: <a class="postlink" href="http://www.aspnetboilerplate.com/Pages/Documents/Zero/Installation">http://www.aspnetboilerplate.com/Pages/ ... stallation</a>

Ran all appropriate migrations and seed methods.

I then created an AngularJS admin app. I created a UserAppService with GetUsers() and tested in the console like demoed here: <a class="postlink" href="http://www.aspnetzero.com/Documents/Developing-Step-By-Step#testing-personappservice-from-browser-console">http://www.aspnetzero.com/Documents/Dev ... er-console</a>

This is where I first got the error for MustHaveTenant. So I assumed it was because I wasn't logging in. I went to the login form and on the LoginAsync method, I get the "Filter name MustHaveTenant not Found" exception when the method _userManager.LoginAsync() runs and trying to login as 'admin' / '123qwe'.

I've read every document 10x now and cannot figure out why this happening.

[HttpPost]
        public async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "")
        {
            if (!ModelState.IsValid)
            {
                throw new UserFriendlyException("Your form is invalid!");
            }

                var loginResult = await _userManager.LoginAsync(
                    loginModel.UsernameOrEmailAddress,
                    loginModel.Password,
                    loginModel.TenancyName
                    );

                switch (loginResult.Result)
                {
                    case AbpLoginResultType.Success:
                        break;
                    case AbpLoginResultType.InvalidUserNameOrEmailAddress:
                    case AbpLoginResultType.InvalidPassword:
                        throw new UserFriendlyException("Invalid user name or password!");
                    case AbpLoginResultType.InvalidTenancyName:
                        throw new UserFriendlyException("No tenant with name: " + loginModel.TenancyName);
                    case AbpLoginResultType.TenantIsNotActive:
                        throw new UserFriendlyException("Tenant is not active: " + loginModel.TenancyName);
                    case AbpLoginResultType.UserIsNotActive:
                        throw new UserFriendlyException("User is not active: " + loginModel.UsernameOrEmailAddress);
                    case AbpLoginResultType.UserEmailIsNotConfirmed:
                        throw new UserFriendlyException("Your email address is not confirmed!");
                    default: //Can not fall to default for now. But other result types can be added in the future and we may forget to handle it
                        throw new UserFriendlyException("Unknown problem with login: " + loginResult.Result);
                }

                AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
                AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = loginModel.RememberMe }, loginResult.Identity);


            if (string.IsNullOrWhiteSpace(returnUrl))
            {
                returnUrl = Request.ApplicationPath;
            }

            return Json(new MvcAjaxResponse { TargetUrl = returnUrl });
        }

I think this is obviously related...

I had to make some changes to my user entity and since I had no data, I basically dropped my database and migrations, created a new initial migration with all my existing entities and ran the script with seed method.

I know get the following error on the CreateUserAndRoles() method when trying to create the second admin for the Default Tenant.

This is all from the instructions for manual install of Zero to a project.

No pending explicit migrations. Running Seed method. System.Data.Entity.Validation.DbEntityValidationException: Can not set TenantId to a different value from the current filter parameter value while MayHaveTenant filter is enabled! at Abp.EntityFramework.AbpDbContext.CheckMayHaveTenant(DbEntityEntry entry) at Abp.EntityFramework.AbpDbContext.CheckAndSetTenantIdProperty(DbEntityEntry entry) at Abp.EntityFramework.AbpDbContext.ApplyAbpConcepts() at Abp.EntityFramework.AbpDbContext.SaveChanges() at Nyxly.Migrations.DefaultTenantRoleAndUserBuilder.CreateUserAndRoles() in c:\MyProjects\Nyxly_ABP_6.3\src\Nyxly.EntityFramework\Migrations\DefaultTenantRoleAndUserBuilder.cs:line 107 at Nyxly.Migrations.DefaultTenantRoleAndUserBuilder.Build() in c:\MyProjects\Nyxly_ABP_6.3\src\Nyxly.EntityFramework\Migrations\DefaultTenantRoleAndUserBuilder.cs:line 27 at Nyxly.Migrations.Configuration.Seed(NyxlyDbContext context) in c:\MyProjects\Nyxly_ABP_6.3\src\Nyxly.EntityFramework\Migrations\Configuration.cs:line 19 at System.Data.Entity.Migrations.DbMigrationsConfiguration1.OnSeed(DbContext context) at System.Data.Entity.Migrations.DbMigrator.SeedDatabase() at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.SeedDatabase() at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable1 pendingMigrations, String targetMigrationId, String lastMigrationId) at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId) at System.Data.Entity.Migrations.DbMigrator.UpdateInternal(String targetMigration) at System.Data.Entity.Migrations.DbMigrator.<>c__DisplayClassc.

Here is my seed method

protected override void Seed(Nyxly.EntityFramework.NyxlyDbContext context)
        {
            // This method will be called every time after migrating to the latest version.
            // You can add any seed data here...
            context.DisableAllFilters();
            new DefaultTenantRoleAndUserBuilder(context).Build();
        }
private void CreateUserAndRoles()
        {
            //Admin role for tenancy owner

            var adminRoleForTenancyOwner = _context.Roles.FirstOrDefault(r => r.TenantId == null && r.Name == "Admin");
            if (adminRoleForTenancyOwner == null)
            {
                adminRoleForTenancyOwner = _context.Roles.Add(new Role { Name = "Admin", DisplayName = "Admin" });
                _context.SaveChanges();
            }

            //Admin user for tenancy owner

            var adminUserForTenancyOwner = _context.Users.FirstOrDefault(u => u.TenantId == null && u.UserName == "admin");
            if (adminUserForTenancyOwner == null)
            {
                adminUserForTenancyOwner = _context.Users.Add(
                    new User
                    {
                        TenantId = null,
                        UserName = "admin",
                        Name = "System",
                        Surname = "Administrator",
                        EmailAddress = "[email protected]",
                        IsEmailConfirmed = true,
                        Password = "AM4OLBpptxBYmM79lGOX9egzZk3vIQU3d/gFCJzaBjAPXzYIK3tQ2N7X4fcrHtElTw==" //123qwe
                    });

                _context.SaveChanges();

                _context.UserRoles.Add(new UserRole(adminUserForTenancyOwner.Id, adminRoleForTenancyOwner.Id));

                _context.SaveChanges();
            }

            //Default tenant

            var defaultTenant = _context.Tenants.FirstOrDefault(t => t.TenancyName == "Default");
            if (defaultTenant == null)
            {
                defaultTenant = _context.Tenants.Add(new Tenant { TenancyName = "Default", Name = "Default" });
                _context.SaveChanges();
            }

            //Admin role for 'Default' tenant

            var adminRoleForDefaultTenant = _context.Roles.FirstOrDefault(r => r.TenantId == defaultTenant.Id && r.Name == "Admin");
            if (adminRoleForDefaultTenant == null)
            {
                adminRoleForDefaultTenant = _context.Roles.Add(new Role { TenantId = defaultTenant.Id, Name = "Admin", DisplayName = "Admin" });
                _context.SaveChanges();
            }

            //Admin for 'Default' tenant

            var adminUserForDefaultTenant = _context.Users.FirstOrDefault(u => u.TenantId == defaultTenant.Id && u.UserName == "admin");
            if (adminUserForDefaultTenant == null)
            {
                adminUserForDefaultTenant = _context.Users.Add(
                    new User
                    {
                        TenantId = defaultTenant.Id,
                        UserName = "admin",
                        Name = "System",
                        Surname = "Administrator",
                        EmailAddress = "[email protected]",
                        IsEmailConfirmed = true,
                        Password = "AM4OLBpptxBYmM79lGOX9egzZk3vIQU3d/gFCJzaBjAPXzYIK3tQ2N7X4fcrHtElTw==" //123qwe
                    });
                _context.SaveChanges();

                _context.UserRoles.Add(new UserRole(adminUserForDefaultTenant.Id, adminRoleForDefaultTenant.Id));
                _context.SaveChanges();
            }
        }

Bah! I knew it was going to be something simple. Yes, I had made modifications and forgot to call the base...

protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.ComplexType<Address>();
            modelBuilder.Configurations.Add(new CategoryTypeMap());
            modelBuilder.Configurations.Add(new CountryCodeTypeMap());
            modelBuilder.Configurations.Add(new PointsLocationTypeMap());
            modelBuilder.Configurations.Add(new UserActivityTypeMap());
            modelBuilder.Configurations.Add(new UserCheckInTypeMap());
            modelBuilder.Configurations.Add(new UserImageTypeMap());
            modelBuilder.Configurations.Add(new UserPointsTypeMap());
        }

All the icons in the navigation provider appear to use Font Awesome. Does this allow the use of a custom icon? If so, how?

<a class="postlink" href="http://www.aspnetboilerplate.com/Pages/Documents/Navigation">http://www.aspnetboilerplate.com/Pages/ ... Navigation</a>

Maybe we'll see an example of feature management in the EventCloud sample application? :)

This question is more of a best practice than a code question. It's related to complex or nested entities and if you need a separate service class for each entity or if you should put related managers and methods in the parent/master entity service class? Let me give you some examples;

User: Entity (Parent object) UserImage: Entity (collection) UserCheckIn:Entity (collection) UserActivity:Entity (collection) UserPoints:Entity (collection) Review:Entity (collection)

A user has a collection of images, checkings, activities, points, etc. I know I should create a UserImageManager at the domain level in the Core project, but would you create a UserImageAppService OR put it in the UserAppService since it relates to the User object owns it.

Venue:Entity (Parent object) Review:Entity (collection) ReviewRating:Entity (collection)

Second example. A venue has reviews, reviews have ratings (helpful, not helpful). Would you create a separate ReviewAppService or add a method to the VenueAppService with a ReviewManager reference like _reviewManager.AddReviewToVenue(CreateReviewInput input) since ultimately a review can only belong to one venue.

Just wanted to put out the question and see what other people are doing or how Halil thinks it should work. Most of the examples / samples are too simple compared to real world to figure out best practice.

Thanks!

I wanted to figure this out as well, I just haven't jumped into that area of my project yet.

Question

I'm trying to figure out where to assign my web users. I want my site to have several types of users. Think of Yelp.

End users: Every day people signing up and using the site to browse, connect, add venues, write reviews, etc. Business users: Register a business (tenant) and then claim their venue/business.

Should be end user be a host user or a default tenant user? These users search the site for venues and write reviews about them. If the venue does not exist, they can add the venue. A venue entity has the IMayHaveTenant interface. This then throws an exception because my end user is current a Default (tenant id 1) user but the venue has a null tenant id. So there is a mis-match.

When a business claims a venue, the venue then gets a tenant id for that business. Is that going to stop host/default users from viewing and writing reviews on a claimed business?

I guess my question is, should a newly created venue be a host tenant of null or the default tenant of 1 since that's what my end users are. Or do I toss out the interface on the venue entity and skip the data filter. I can create an alternate method to claim a business. Or do my end users all need to be host users?

Sorry that was like six questions. :)

Let's put it differently... Like in a real world scenario.

Today I ordered a pizza from a venue called Famous Pizza in my town and it was horrible. I have heard about this website where I can write reviews about restaurants. So I join the web site (host user) because I want to write a review about how bad the pizza was at Famous Pizza. So I search my town and don't find the venue "Famous Pizza". The site suggests to create the venue. I create the venue and write my review. Life goes on, I invite my friends to join the web site, etc.

Some undetermined time later, the owner of Famous Pizza hears about the website and sees all these reviews about their pizza shop. So they join the website (business user / tenant user) and create a business account (tenant). In turn they 'claim' their venue thru some validation method (phone / email / etc). They now can edit the profile for their pizza place and respond to the reviews that customers are leaving on their profile. But maybe Famous Pizza is a chain and they have multiple pizza venues to claim. Using editions, they could upgrade their account to manage several venues. Maybe provide each general manager of a pizza parlor it's own login (tenant user) to respond to reviews in their respective place.

So with this model, most of the users are free to come, browse the site, join, write reviews, add new venues, check in, etc. Business users are tenant users and belong to an account that can claim a venue(s). However, there could be a period of time between the user creating the venue and the venue owner claiming the profile, meaning it wouldn't have a tenant in that time period.

Sorry for the long explaination, but I want to make sure I'm using the interfaces and default tenant as designed or if I have to work a different solution. A perfect example of this model would be <a class="postlink" href="http://www.yelp.com">http://www.yelp.com</a>.

Showing 1 to 10 of 13 entries