Base solution for your next web application

Activities of "JapNolt"

There is also no way to delete a webhook subscription. Is this by design or an oversight?

I find it odd that when calling the AddSubscription API in the WebhookSubscriptionAppService, I can pass in the IsActive property and it is honored. But when I try to "toggle" the IsActive property with UpdateSubscription API, the IsActive is not honored. Feels like a bug to me but maybe I'm not understanding something??

Github issue: https://github.com/aspnetzero/aspnet-zero-core/issues/5009

The question centers around the Range attribute on the property MaxResultCount. I want to be able to "customize" this MaxResultCount property per edition/tenant. I plan to do this using a feature.

I used the keyword new to hide the base class property and cover it with the new property declared here. I used the ICustomValidate interface to implement my own custom feature lookup and validation logic. See below.

public class GetWrapupCallsInputBase : PagedAndSortedInputDto, IShouldNormalize, ICustomValidate
{
    // used the keyword new to hide the base class property and cover it with the new property declared here.
    new public int MaxResultCount { get; set; }

    public void AddValidationErrors(CustomValidationContext context)
    {
        new ExportRowCountValidator(context, MaxResultCount, AppConsts.WrapupDetailsRowCountFeatureKey).Validate();
    }
// ...
}

Here's the validate logic.

public void Validate()
{
    var featureChecker = _context.IocResolver.Resolve<IFeatureChecker>();
    int limit = 1000;

    if (featureChecker.IsEnabled(AppConsts.EnableExportRowCountFeatureKey))
    {
        limit = featureChecker.GetValue(_field).To<int>();
    }

    if (_maxResultCount < 1 || _maxResultCount > limit)
    {
        var message = _context.Localize(O365ContactCenterConsts.LocalizationSourceName, "ExportRowCountError");
        message = string.Format(message, _maxResultCount, 1, limit);
        _context.Results.Add(new ValidationResult(message));
    }
}

My problem is that the behavior is different when calling the API from Swagger/UI versus from unit tests. When I took this code, launched the program and tried it out in the UI, everything worked perfectly. I was able to log in as the Host Admin user and customize the Feature values for the WrapupDetailsRowCountFeatureKey. Then, when I tested requesting values from the API, I was able to exceed the AspNetZero default value of 1000 that is hard-coded into the base class's MaxResultCount Range attribute value, but my Validate code perfectly threw the error that I wanted it to when I exceeded my own custom maximum value.

On the other hand, when I tried this in unit tests, my tests failed. When I used the debugger to step through it, I realized that it was correctly running the Validate logic that my covering property was supposed to have, and it passed through the method with no errors, the requested row count value was within the range specified by the custom feature value. But as soon as control left my code, the AspNetZero attribute validation (which I'm unable to step through in the debugger, since it's not actually in my project) threw the error that I was trying to override -- "You may not have a maximum value outside the range 1 to 1000."

Any update?

@ismcagdas I disagree. The work around that I presented above actually works BECAUSE we've inserted a Setting into the database. BUT if there is NO user level setting, NO tenant level setting, and NO application level setting saved in the database, then ANZ uses the default setting value defined in AppSettingProvider. And, as I described above, the default setting is different on each node because you are generating a unique page id for the default setting.

The error we're receiving is throw new UserFriendlyException(L("UnknownPage"));. From my understanding, this error has nothing to do with DashboardDefinition. This error only happens if the page id that is being requested by the user in GetAllAvailableWidgetDefinitionsForPage is not available in the response from GetUserDashboard. And this can happen in the scenario I described in the paragraph above.

I forgot to mention this earlier, but we are currently on ANZ 11.3. But I think the problem we're facing still exists in the latest version of ANZ although we haven't tested it.

To work around the issue, we added a database migration to insert a row into the AbpSettings table so that a default setting is supplied by the database instead of from code. Here's our migration code that adds an Application level setting (not tenant or user). migrationBuilder.Sql(@"INSERT INTO AbpSettings(CreationTime, Name, Value) SELECT GETUTCDATE(), 'App.DashboardCustomization.Configuration.Angular.TenantDashboard', '<dashboard_definition_as_JSON_string' ");

I think this problem could still exist even if in your PR fix, but I didn't test. I will try to explain what I think the problem is:

  • in AppSettingsProvider, settings are defined for the Dashboard in GetDashboardSettings
  • in GetDefaultAngularTenantDashboardView, for example, a new Dashboard object is created, with a new Page.
  • In the constructor of Page, a new GUID is generated..
  • This default setting is generated on each node in the cluster, but each node has a different value for the default setting.
  • This creates a problem in the DashboardCustomizationAppService in GetDashboardWithAuthorizedWidgets because await SettingManager.GetSettingValueAsync(GetSettingName(application, dashboardName)); will return a different value, depending on which node the request gets routed to.

Let me know if you have any more questions.

Alright, thanks. Is there any workaround we can use for now for this?

Here is the related error logs:

Audit Logs

Input Prams: { "input": { "dashboardName": "TenantDashboard", "application": "Angular", "pageId": "Page3e4759efcafa4ed5a09096a59fa2a175" } }

Error state: Abp.UI.UserFriendlyException: [Unknown page] at DashboardCustomization.DashboardCustomizationAppService.GetAllAvailableWidgetDefinitionsForPage(GetAvailableWidgetDefinitionsForPageInput input) in C:**\2**\src***.Application\DashboardCustomization\DashboardCustomizationAppService.cs:line 205 at lambda_method10582(Closure , Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Logged|12_1(ControllerActionInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

UserFriendlyException.Code:0 UserFriendlyException.Details:

App Service(Kudu)

WARN 2023-08-14 13:44:42,518 [62 ] Mvc.ExceptionHandling.AbpExceptionFilter - [Unknown page] Abp.UI.UserFriendlyException: [Unknown page] at ***.DashboardCustomization.DashboardCustomizationAppService.GetAllAvailableWidgetDefinitionsForPage(GetAvailableWidgetDefinitionsForPageInput input) in C:\**\**\2\s\src\***.Application\DashboardCustomization\DashboardCustomizationAppService.cs:line 205 at lambda_method10582(Closure , Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeActionMethodAsync&gt;g__Logged|12_1(ControllerActionInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.&lt;InvokeNextActionFilterAsync&gt;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.&lt;InvokeInnerFilterAsync&gt;g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&lt;InvokeNextExceptionFilterAsync&gt;g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

Any update on this?

Showing 1 to 10 of 57 entries