Base solution for your next web application

Activities of "BigCDogCrew"

Yes, same database. It's all the same project. The user's should not experience any differences or disruption in workflow.

It will hopefully be presented to the users as the same website (one project, same appsettings.json). I'm thinking the easiest solution might be to install the new Angular project and then retrofit it to accomodate our domain-specific legacy ANZ-Mvc/jQuery pages.

Thanks for the link. I have read that document a few times and it's starting to make sense, but there is still a missing piece of the puzzle in my mind.  How does the user decide which notification method will apply?  Consider the image attached below.

If I am understanding correctly, all implementations of IRealTimeNotifier will be invoked by each notification. So, if we implement EmailRealtimeNotifier and SMSRealtimeNotifier and (signalR) WebRealTimeNotifier and PushRealTimeNotifier, then all of those classes will receive SendNotificationAysnc invocations for each and every notification created.   Correct?

So, we would then have to include logic in each one of those notifiers to look up the user's notification subscriptions and check to see if that method of notification is appropriate or not.  Correct?  This would result in querying the database four times for each notification to retrieve the user's subscription choices.

Would it not be best to have the publisher/distributor perform this configuration query once and then only invoke the specific IRealTimeNotifier(s) that are desired by the user for that specific type of notification? Or, alternatively...pass the subscription information to the IRealTimeNotifier as an input parameter so the notifier can use it without looking it up.

At the root of this problem is the fact that not all notifications are created equal.  Some are informational and some are critical.  Some occur seldom and others occur frequently.   Users have to decide which method of notification is best for each specific type of notification.

In this example below, my user is not that much concerned about new user registration.  They want to see those notifications when they login to the website, but they don't want to be bothered by email or sms for new users.   However, when a new product is created, they want to be informed immediately by SMS and also receive an email with the details that they can easily archive for future reference.   They are not too concerned about new product settings, but want to receive an email so they can archive those settings for future reference.

BTW... I am NOT advocating radio buttons.  This image below is just a visual aide to help explain the problem.  The actual UI for selecting methods would need to allow for a substantially large number of options.

Has there been any change in this area over the last two years?

This would be a very nice feature to have. We need certain notifications to be delivered by email and other to be delivered by SMS; but ultimately, the user should decide for themselves which or both methods they want to use for any given subscription.

Issue Added To GitHub https://github.com/aspnetboilerplate/aspnetboilerplate/issues/6186

I am going to make a guess at what is happening and why.

The primary difference between the ANZ query and the LinqPad query is that ANZ applies the tenancy filters. That's the "why". Because ANZ needs to apply the tenancy filters, it has opted to use a nested subquery structure which provides the necessary additional WHERE clause.

However, the nested subquery also requires a SELECT clause...which by default would be SELECT *. And that's the "what". ANZ doesn't know what fields to select in the subquery, so it's easier (and safer) just to select them all. That way, no matter which fields are requested by the outer query, those fields will be available.

The ideal solution would be for ANZ to scan the outer query and identify the fields projected from the foreign key, and then include only those fields in the subquery SELECT clause. In the event the outer query does not reference any foregin key field, then the default behavior should be to include only the primary key field; as the primary key field will always be required by the JOIN clause.

So, the original query which did not include any foreign key field (e.g. no Parent fields referenced in the projection) ....

var parentId = 1;
var orgUnits = _organizationUnitRepository
    .GetAll()
    .Where(e => e.Parent.Id == parentId)
    .Select(e => e.DisplayName)
    .ToList();

Should have produced the following code (note the Id field is the only subquery output)...

SELECT [a].[DisplayName]
FROM [AbpOrganizationUnits] AS [a]
LEFT JOIN (
    SELECT [a0].[Id]
    FROM [AbpOrganizationUnits] AS [a0]
    WHERE (tenancyFilter2)
) AS [t1] ON [a].[ParentId] = [t1].[Id]
WHERE (tenancyFilter1) AND ([t1].[Id] = @__p_0)

And when the query does specify a foreign key field, such as this....

            var parentId = 1;
            var orgUnits = _organizationUnitRepository
                .GetAll()
                .Where(e => e.Parent.Id == parentId)
                .Select(e => new { e.DisplayName, ParentDisplayName = e.Parent.DisplayName })
                .ToList();

Then ANZ should see that "Parent.DisplayName" while scanning the outer query and return only that field in addition to the primary key; like the following....

SELECT [a].[DisplayName], [t1].[DisplayName] AS [ParentDisplayName]
FROM [AbpOrganizationUnits] AS [a]
LEFT JOIN (
    SELECT [a0].[Id], [a0].[DisplayName]
    FROM [AbpOrganizationUnits] AS [a0]
    WHERE (tenancyFilter2)
) AS [t1] ON [a].[ParentId] = [t1].[Id]
WHERE (tenancyFilter1) AND ([t1].[Id] = @__p_0)


Hi @ismcagdas,

Thanks for your help.

Adding a projection to the ANZ query to return only the DisplayName field...

var parentId = 1;
var orgUnits = _organizationUnitRepository
    .GetAll()
    .Where(e => e.Parent.Id == parentId)
    .Select(e => e.DisplayName)
    .ToList();

results in the following generated SQL...

SELECT [a].[DisplayName]
FROM [AbpOrganizationUnits] AS [a]
LEFT JOIN (
    SELECT [a0].[Id], [a0].[Code], [a0].[CreationTime], [a0].[CreatorUserId], [a0].[DeleterUserId], [a0].[DeletionTime], [a0].[DisplayName], [a0].[IsDeleted], [a0].[LastModificationTime], [a0].[LastModifierUserId], [a0].[ParentId], [a0].[TenantId]
    FROM [AbpOrganizationUnits] AS [a0]
    WHERE (tenancyFilter2)
) AS [t1] ON [a].[ParentId] = [t1].[Id]
WHERE (tenancyFilter1) AND ([t1].[Id] = @__p_0)

Omitting the projection from the LinqPad query, we use this statement....

 AbpOrganizationUnits.Where(e => e.Parent.Id == 1)

And LinqPad generates the following SQL statement...

SELECT [t0].[Id], [t0].[Code], [t0].[CreationTime], [t0].[CreatorUserId], [t0].[DeleterUserId], [t0].[DeletionTime], [t0].[DisplayName], [t0].[IsDeleted], [t0].[LastModificationTime], [t0].[LastModifierUserId], [t0].[ParentId], [t0].[TenantId]
FROM [AbpOrganizationUnits] AS [t0]
LEFT OUTER JOIN [AbpOrganizationUnits] AS [t1] ON [t1].[Id] = [t0].[ParentId]
WHERE [t1].[Id] = @p0

@ismcagdas,

We have used DateTime.UtcNow to populate all of our custom datetime fields. But the ABP fields (CreationTime, DeletionTime, LastModificationTime) all use local time. Our current work-around is to run our Azure servers in UTC, which assures that all DateTime fields are stored in UTC. But it's time to make a proper fix.

We are using AspNetZero v8.4, Abp 5.5, Mvc/jQuery.

Will you please provide an example of how to change the clock provider in ConfigureServices?

Based upon earlier posts in this thread, it looks like we should go back and replace all "DateTime.UtcNow" references with "ClockProviders.Utc.Now". Do you advise that change?

Thanks, BigC

The code sample above looks like it may have a logical error. If person is not null, then you are trying to insert that person into the repository again? And if person is null, then you are trying to insert a null into the repository? I don't understand.

Maybe this line below is supposed to insert the new record rather than old person?

record.Id = _personRepository.InsertAndGetId(person);

should be

record.Id = _personRepository.InsertAndGetId(record);

If that's the case, I think you only need to capture the output if you intend to use the new Id immediately for some cross-referenced object. You don't have to reset record.Id = record.Id. For example...

var newId = _personRepository.InsertAndGetId(record);
callLog.PersonId = newId;
_callLogRepository.Update(callLog);

I figured, just maybe the AuthorizationProvider is not being loaded. So, I added this line to thePreInitialize() of [MyProject]TestBaseModule. <br>

Configuration.Authorization.Providers.Add<AppAuthorizationProvider>();

This gave me an error: <br>

Message: 
    Abp.AbpInitializationException : Duplicate permission name detected for Pages.Accounts
  Stack Trace: 
    PermissionDictionary.AddPermissionRecursively(Permission permission)
    PermissionDictionary.AddPermissionRecursively(Permission permission)
    PermissionDictionary.AddAllPermissions()
    PermissionManager.Initialize()
    AbpKernelModule.PostInitialize()
    <>c.<StartModules>b__15_2(AbpModuleInfo module)
    List`1.ForEach(Action`1 action)
    AbpModuleManager.StartModules()
    AbpBootstrapper.Initialize()
    AbpIntegratedTestBase`1.InitializeAbp()
    AbpIntegratedTestBase`1.ctor(Boolean initializeAbp, IIocManager localIocManager)
    AppTestBase`1.ctor() line 40
    AppTestBase.ctor()
    TenantAppService_Tests.ctor() line 30

So, we know for certain the AuthorizationProvider is being loaded.

Here is some additional information that may help.

When I run the GetTenants_Test, the LoginAsHost works fine but PermissionChecker throws an exception...even though the AbpSession shows that I am logged in as the Host Admin. See images below.

Showing 1 to 10 of 28 entries