Base solution for your next web application

Activities of "tusksoft"

AN0 v11.4.0 (Metronic 8) Angular .net core

Trying to figure out exactly how styles are generated is an absolute pain. The only relevant AN0 docs I have found involve copying a theme. Documentation is severely lacking in this regard.

Where in the metronic source are theme bundles for AN0 being generated from? metronic_v8.x.x\html\theme? Can the angular app also be used for generating themes?

It seems like there are styles missing when generated from the angular app by extracting output via styles in the angular build. My changes in Metronic 8's angular.json for generating bundles:

"styles": [
  "node_modules/primeicons/primeicons.css",
  "node_modules/primeng/resources/themes/saga-blue/theme.css",
  "node_modules/primeng/resources/primeng.min.css",
  "src/styles.scss",
  {
    "input": "src/assets/sass/style.scss",
    "bundleName": "style"
  },
  {
    "input": "src/assets/sass/plugins.scss",
    "bundleName": "plugins"
  }
],

This generates css files in the build output that can be placed in our copy of the default theme for style.bundle.css and plugins.bundle.css (generated plugins css is much smaller than the default 68KB vs 622KB and styles bundle is also about 200KB less, unsure why)

Reason why I need to generate styles from the angular app is because we have had a large amount of redesign done via the Metronic Angular app and I need to be able to import the generated styles from that. Any insight you can provide on generating styles from Metronic source is greatly appreciated.

  • What is your product version? 10.2.0
  • What is your product type (Angular or MVC)? Angular
  • What is product framework type (.net framework or .net core)? .NET Core
  • What is ABP Framework version? 6.2.0

We have a custom SignalR hub that is meant to handle long-running connections, usually for multiple days. This is a problem with auth tokens because when they expire, the connection dies. The vanilla SignalR NPM package from Microsoft exposes an accessTokenFactory callback in the HubConnectionBuilder options, but the ABP abstraction for SignalR does not provide access to these options. How can we manage refreshing auth tokens on the client side for long-running SignalR connections?

A0 v10.2.0 Angular- single solution .NET5

In short, we cannot get this feature to work. Wildcard DNS and SSL cert are configured and working properly. The A0 instance responds to all subdomains and the primary domain of "mydomain.com" We have set ClientRootAddress and ServerRootAddress to https://{TENANCY_NAME}.mydomain.com, and used the same value for the remoteServiceBaseUrl and appBaseUrl in the Angular appconfig.production.json. Before modifying the Angular production config the tenant switcher still showed up for the mydomain.com and all subdomains, so any tenant could be selected. After modifying the Angular config the switcher no longer displays, but the tenant is still not being pulled from the subdomain. The old tenantId cookie persists from the switcher, and upon clearing browser data or trying to log in via a private window the tenant is lost. As an aside, the site will respond to any subdomain whether it is associated with a tenant or not. What are we missing to get this working properly?

v10.2.0 Angular .net5 abp v 6.2.0

I had a longer version of this typed up but my session was lost and along with it my original post, so here's the boiled down version.

A complete version of IConfiguration is not available to my class libraries without adding services.AddSingleton<IConfiguration>(configuration); to Startup.ConfigurServices. Without that line IConfiguration only has a single provider for environment variables. When I add the line I get a fully populated instance of IConfiguration. Prior to aspnetcore2 explicitly configuring IConfiguration for the container with aforementioned line was required, without it the injected instance would be null (such is my understanding, I never used netcore1.x versions). Starting with aspnetcore2 IConfiguration should be implicitly configured and services.AddSingleton<IConfiguration>(configuration); should not be necessary, but in A0 it seems to be.

I can find no documentation for A0 nor ABP that details this. Is this by design or a bug? If the former, can you elaborate on the reasoning and intention?

Here's what the IConfiguration instance looks like without explicitly adding it to the container:

Here's what it looks like with the explicit configuration:

A0 v10.2.0 Angular .NET 5 current ABP version 6.2.0 desired ABP version 6.3.1

Please see the following ABP issue on github: https://github.com/aspnetboilerplate/aspnetboilerplate/issues/6327

I was advised to upgrade from ABP packages version 6.2.0 to 6.3.1 to fix the issue at hand, however this has broken our app pretty thoroughly. Since A0 upgrades tend to be a brutal process, we're avoiding upgrading until v11 is out and stable, and .NET 6 is supported. Is there a way we can do this minor version upgrade of ABP in the meantime?

Does AspNet Zero uses any particular graphql package in angular, or do you have any recommendation?

We're experimenting with adding GraphQL into our project. We have created types for our complex objects, but now we're getting an error when trying to run the query in the playground. From the message, it appears that GraphQL expects automapper to map Dto to domain objects, however, we are mapping Dto with our own custom code, bypassing automapper.

Is it possbile to tell the GraphQL query to map using our own custom code, instead of automapper?

Here is the full error message/stack trace that comes on the query:

"GraphQL.ExecutionError: Error trying to resolve Entries.\r\n ---> System.InvalidOperationException: Missing map from MyProject.Domain.TimeTracking.TimeEntry to MyProject.Domain.Core.TimeEntries.Dto.TimeEntryDto. Create using CreateMap<TimeEntry, TimeEntryDto>.\r\n at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpressionCore(ExpressionRequest request, Expression instanceParameter, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps, TypeMap& typeMap)\r\n at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpression(ExpressionRequest request, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps)\r\n at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpression(ExpressionRequest request)\r\n at AutoMapper.LockingConcurrentDictionary2.<>c__DisplayClass2_1.<.ctor>b__1()\r\n at System.Lazy1.ViaFactory(LazyThreadSafetyMode mode)\r\n at System.Lazy1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)\r\n at System.Lazy1.CreateValue()\r\n at AutoMapper.LockingConcurrentDictionary2.GetOrAdd(TKey key)\r\n at AutoMapper.QueryableExtensions.ExpressionBuilder.GetMapExpression(Type sourceType, Type destinationType, Object parameters, MemberInfo[] membersToExpand)\r\n at AutoMapper.QueryableExtensions.ProjectionExpression.ToCore[TResult](Object parameters, IEnumerable1 memberPathsToExpand)\r\n at AutoMapper.QueryableExtensions.ProjectionExpression.To[TResult](Object parameters, Expression1[] membersToExpand)\r\n at AutoMapper.QueryableExtensions.Extensions.ProjectTo[TDestination](IQueryable source, IConfigurationProvider configuration, Object parameters, Expression1[] membersToExpand)\r\n at AutoMapper.Mapper.ProjectTo[TDestination](IQueryable source, Object parameters, Expression1[] membersToExpand)\r\n at MyProject.Core.Base.MyProjectQueryBase2.ProjectTo[TDestination](IQueryable source) in C:\Users\shodg\MyProject\src\MyProject.GraphQL\Core\Base\MyProjectQueryBase.cs:line 65\r\n at MyProject.Core.Base.MyProjectQueryBase2.ProjectToListAsync[TDestination](IQueryable source) in C:\\Users\\shodg\\MyProject\\src\\MyProject.GraphQL\\Core\\Base\\MyProjectQueryBase.cs:line 71\r\n at MyProject.Queries.TimeEntryQuery.Resolve(ResolveFieldContext1 context) in C:\Users\shodg\MyProject\src\MyProject.GraphQL\Queries\TimeEntryQuery.cs:line 60\r\n at MyProject.Core.Base.MyProjectQueryBase2.InternalResolve(ResolveFieldContext1 context) in C:\Users\shodg\MyProject\src\MyProject.GraphQL\Core\Base\MyProjectQueryBase.cs:line 47\r\n at Abp.Threading.InternalAsyncHelper.AwaitTaskWithPostActionAndFinallyAndGetResult[T](Task1 actualReturnValue, Func1 postAction, Action`1 finalAction)\r\n at GraphQL.Instrumentation.MiddlewareResolver.Resolve(ResolveFieldContext context)\r\n at GraphQL.Execution.ExecutionStrategy.ExecuteNodeAsync(ExecutionContext context, ExecutionNode node)\r\n --- End of inner exception stack trace ---"

This is a strange one, and I'm not sure if the problem lies with A0, ABP, Windsor, or some combination of the 3. We're injecting a dependency (D1) into another dependency (D2) into an application service. Both D1 and D2 implement ITransientDependency. D1 takes an IRepository via constructor injection. Here's an arbitrary code snippet of D1 to illustrate:

public class StatusProvider : IStatusProvider, ITransientDependency
{
    private readonly StatusForStaffMember _statusForStaffMember;

    public StatusProvider(IRepository<StatusForStaffMember> statusRepo, IRepository<StaffMember> staffMemberRepo, IAbpSession abpSession)
    {
        _statusForStaffMember = statusRepo.GetAllIncluding(s => s.User).FirstOrDefault(s => s.User.Id == abpSession.GetUserId()); //I'm gonna throw right here
        if (_statusForStaffMember == null)
        {
            var staffMember = staffMemberRepo.Get(abpSession.GetUserId());
            _statusForStaffMember = new StatusForStaffMember(staffMember);
        }
    }

    public StaffMember GetCurrentStaffMember()
    {
        return _statusForStaffMember.User;
    }
}

This explodes with a big nasty exception when statusRepo is used, but it boils down to this:

System.ObjectDisposedException: 'Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

This seems odd, as we were just handed the repo. Why has something already been disposed within it when we haven't even made it into our ApplicationService yet? I have no idea, yet if we refactor the above to lazily grab our _statusForStaffMember from the repo like the following, everything works fine:

public class StatusProvider : IStatusProvider, ITransientDependency
{
    private readonly Func<StatusForStaffMember> _statusForStaffMember;

    public StatusProvider(IRepository<StatusForStaffMember> statusRepo, IRepository<StaffMember> staffMemberRepo, IAbpSession abpSession)
    {
            StatusForStaffMember closedOver;
            _statusForStaffMember = () =>
            {
                closedOver = _statusRepo.GetAllIncluding(s => s.User).FirstOrDefault(t => t.User.Id == abpSession.GetUserId()); //everything is fine now
                
                if (closedOver == null)
                {
                    var staffMember = staffMemberRepo.Get(abpSession.GetUserId());
                    closedOver = new StatusForStaffMember(staffMember);
                }

                return closedOver;
            };
    }

    public StaffMember GetCurrentStaffMember()
    {
        return _statusForStaffMember().User;
    }
}

I do NOT understand why I'm getting the ObjectDisposedException if I access the repo early but not if I access it late. If I dig into the repo at runtime via ProxyUtil.GetUnproxiedInstance(_statusRepo) I get ArgumentNullExceptions on Connection, Context, DbQueryTable, Table, and Transaction. The only properties that are properly instantiated appear to be IocResolver and UnitOfWorkManager. If I were going to venture to guess, I'd say that the repo is using Property Injection, and it's being handed to our dependency via Constructor Injection before its fully wired up and that the ObjectDisposedException is misleading.

Please help me understand what is happening, why, and how to resolve it without a using a lazy loading approach.

We have created a domain entity StaffMember that inherits from User. Creating/editing staff members was working, but when adding an owned type, Address, onto the entity, we get a null reference exception when trying to create a new staff member with an address.

Here is the exception and stack trace, from the logs:

ERROR 2019-12-19 16:47:59,889 [33   ] Mvc.ExceptionHandling.AbpExceptionFilter - Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object.
   at Abp.EntityHistory.EntityHistoryHelper.CreateEntityChangeSet(ICollection`1 entityEntries)
   at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext`3.SaveChangesAsync(CancellationToken cancellationToken)
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesInDbContextAsync(DbContext dbContext)
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesAsync()
   at Abp.Authorization.Users.AbpUserStore`2.CreateAsync(TUser user, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Identity.UserManager`1.CreateAsync(TUser user)
   at Abp.Authorization.Users.AbpUserManager`2.CreateAsync(TUser user)
   at MyApp.Authorization.Users.StaffMemberAppService.CreateStaffMemberAsync(CreateOrUpdateStaffMemberInput input) in C:\Users\shodg\myapp\src\MyApp.Application\Domain\Core\StaffMembers\StaffMemberAppService.cs:line 290
   at Abp.Threading.InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func`1 postAction, Action`1 finalAction)
   at MyApp.Authorization.Users.StaffMemberAppService.CreateOrUpdateStaffMember(CreateOrUpdateStaffMemberInput input) in C:\Users\shodg\myapp\src\MyApp.Application\Domain\Core\StaffMembers\StaffMemberAppService.cs:line 203
   at Abp.Threading.InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func`1 postAction, Action`1 finalAction)
   at lambda_method(Closure , Object )
   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeActionMethodAsync&gt;g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

And here is the method in our service that creates staff members:

        [AbpAuthorize(AppPermissions.Pages_Administration_StaffMembers_Create)]
        protected virtual async System.Threading.Tasks.Task CreateStaffMemberAsync(CreateOrUpdateStaffMemberInput input)
        {
            if (AbpSession.TenantId.HasValue)
            {
                await _userPolicy.CheckMaxUserCountAsync(AbpSession.GetTenantId());
            }

            var staffMember = ObjectMapper.Map<StaffMember>(input.User); //Passwords is not mapped (see mapping configuration)
            staffMember.TenantId = AbpSession.TenantId;

            //Set password
            if (input.SetRandomPassword)
            {
                var randomPassword = await _userManager.CreateRandomPassword();
                staffMember.Password = _passwordHasher.HashPassword(staffMember, randomPassword);
                input.User.Password = randomPassword;
            }
            else if (!input.User.Password.IsNullOrEmpty())
            {
                await UserManager.InitializeOptionsAsync(AbpSession.TenantId);
                foreach (var validator in _passwordValidators)
                {
                    CheckErrors(await validator.ValidateAsync(UserManager, staffMember, input.User.Password));
                }

                staffMember.Password = _passwordHasher.HashPassword(staffMember, input.User.Password);
            }

            staffMember.ShouldChangePasswordOnNextLogin = input.User.ShouldChangePasswordOnNextLogin;

            //TODO: replace US's countryID (220) with functionality to load states dynamically based on country selection
            await staffMember.Address.SetCountryAndState(_countryRepository, _stateRepository, 220, input.User.Address.StateId);

            //Assign roles
            staffMember.Roles = new Collection<UserRole>();
            foreach (var roleName in input.AssignedRoleNames)
            {
                var role = await _roleManager.GetRoleByNameAsync(roleName);
                staffMember.Roles.Add(new UserRole(AbpSession.TenantId, staffMember.Id, role.Id));
            }

            CheckErrors(await UserManager.CreateAsync(staffMember));
            await CurrentUnitOfWork.SaveChangesAsync(); //To get new user's Id.

            //Notifications
            await _notificationSubscriptionManager.SubscribeToAllAvailableNotificationsAsync(staffMember.ToUserIdentifier());
            await _appNotifier.WelcomeToTheApplicationAsync(staffMember);

            //Organization Units
            await UserManager.SetOrganizationUnitsAsync(staffMember, input.OrganizationUnits.ToArray());

            //Send activation email
            if (input.SendActivationEmail)
            {
                staffMember.SetNewEmailConfirmationCode();
                await _userEmailer.SendEmailActivationLinkAsync(
                    staffMember,
                    AppUrlService.CreateEmailActivationUrlFormat(AbpSession.TenantId),
                    input.User.Password
                );
            }
        }

The exception is thrown at CheckErrors(await UserManager.CreateAsync(staffMember)).

If helpful, here is the modelBuilder mapping for StaffMember class from our Db context:

            modelBuilder.Entity<StaffMember>(sm =>
            {
                sm.HasIndex(e => new { e.TenantId });
                sm.HasMany(typeof(Project), nameof(StaffMember.Projects));
                sm.OwnsOne(e => e.Address);
            });

Is there a workaround/solution to this behavior? Let us know if you need additional info, and we can provide.

For our app we are segregating users by type as they have data that needs to be segregated. Roles will not suffice as the difference between users involves data tied to the user and IExtendableObject pattern would suffer from the same issues as adding these segregated properties to the User directly.

Consider the following:

public class User : AbpUser<User>
{
    ....
}

public class Foo : User
{
    public object FooProperty { get; set; }
    ....
}

public class Bar : User
{
    public IEnumerable<object> BarProperty { get; set; }
    ....
}

The issue with the above subclassing is that it seems nearly 90% of the code from UserManager and UserAppService would have to be duplicated with mostly just type updates and permissions changes; which makes me feel like I am missing something.

An alternative approach that I considered was creating Foo and Bar with navigation properties to an optional user account. But that introduces more complexity around permissions since it would always have to be checked from the Foo or Bar object as User wouldn't have any knowledge of them. Code for this as follows:

public interface IHaveUser
{
    public User User { get; }
}

public class Foo : IHaveUser
{
    public User User { get; }
    public object FooProperty { get; set; }
    ....
}

public class Bar : IHaveUser
{
    public User User { get; }
    public IEnumerable<object> BarProperty { get; set; }
    ....
}

So to summarize, what is the best approach for creating multiple user types that invloves the least duplication of user management code and is the most compatible with upgrades (minimal modification to AspNetZero demo code)? I prefer the subclassing pattern as it makes the most sense for our needs, just need some direction on making sure we don't duplicate more code than we need to. The intent is to also subclass the existing UserDtos so that we can reuse the Angular components too.

Showing 1 to 10 of 14 entries