Base solution for your next web application

Activities of "marble68"

Update - I was finally able to resolve this. The solution was to ensure to use the core classes from ABP, and ensure to substitue in framework core.

V11 MVC Core JQuery

I have a host entity (HostEntity) with specific values for the tenants.

For the tenants, I have another entity (TenantEntity) when a FK to the host entity

When the app service, GetAll(), when the query includes HostEntityFK. The purpose is to get the name of the HostEntity for the view model.

However, if I disable the MayHaveTenant filter, I get all TenantEntities.

How can I disable the filter for the hostentityrepository in the IQueryable? Is this possible?

Hi @Astech - Yeah, sort of.

Until ismcagdas get some of these libraries updates - you have to use the version 4 Serilog stuff.

So basically - I added Serilog: Settings.Configuration, sinks.file, expressions, and extensions hosting -

For Castle, it wants a factory. Serilog factory wants a serilog configuration - so, what you need in startup is:

                var _loggerConfig = new LoggerConfiguration()
                    .ReadFrom.Configuration(_appConfiguration)
                    .CreateLogger();

                options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                     f => f.LogUsing<SerilogFactory>(new SerilogFactory(_loggerConfig))
                );

In your appsettings - put your Serilog configuration in there. Something like this very basic configuration:

  "Serilog": {
    "Using": [
      "Serilog.Sinks.File",
      "Serilog.Expressions"

    ],
    "MinimumLevel": "Debug",
    "WriteTo": [
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "./App_Data/Logs/SeriLog-.txt",
                  "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} -- [{SourceContext}]{NewLine}{Exception}",
                  "rollingInterval": "Day",
                  "retainedFileCountLimit": 20,
                  "rollOnFileSizeLimit": true,
                  "fileSizeLimitBytes": 500000000
                }
              }
            ]
          }
        }
      },
    ],
    "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
  }

My goal, which is probably like yours, is to get the sourcecontext flowing into serilog so we can break out the ANZ logs by domain etc. more easily.

While Log4Net is adequate for logging, Serilog is on a whole different level IMHO.

I think I'm not getting all the power of Serilog's context routing using this older version, but regardless, I have it almost there.

Now, we wait for Castle Windsor to make it to Castle Core 5.

For those who aren't familiar with Serilog and want to break their logs into logical domains, here's a configuration to get you started.

I've not broken out healthchecks and web requests, but this basically puts anything logged from an app service in one log, anything from entityframework in another, and anything from backgroundworkers in yet another.

Lastly - anything not logged into its own file lands in the general 'logs' file.

Obviously, there's a gigantic advantage to having the log files setup this way.

Of note, I have my serilog configuration in my appsettings (see above).

 "Serilog": {
    "Using": [
      "Serilog.Sinks.File",
      "Serilog.Expressions"

    ],
    "MinimumLevel": "Debug",
    "WriteTo": [
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "EndsWith(SourceContext, 'BackgroundWorker')"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "./App_Data/Logs/BackgroundWorkersLog-.txt",
                  "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} -- [{SourceContext}]{NewLine}{Exception}",
                  "rollingInterval": "Day",
                  "retainedFileCountLimit": 20,
                  "rollOnFileSizeLimit": true,
                  "fileSizeLimitBytes": 500000000
                }
              }
            ]
          }
        }
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "StartsWith(SourceContext, 'Microsoft.EntityFrameworkCore') or StartsWith(SourceContext, 'Microsoft.AspNetCore')"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "./App_Data/Logs/EntityFrameworkLog-.txt",
                  "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} -- [{SourceContext}]{NewLine}{Exception}",
                  "rollingInterval": "Day",
                  "retainedFileCountLimit": 20,
                  "rollOnFileSizeLimit": true,
                  "fileSizeLimitBytes": 500000000
                }
              }
            ]
          }
        }
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "EndsWith(SourceContext, 'AppService')"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "./App_Data/Logs/AppServicesLog-.txt",
                  "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} -- [{SourceContext}]{NewLine}{Exception}",
                  "rollingInterval": "Day",
                  "retainedFileCountLimit": 20,
                  "rollOnFileSizeLimit": true,
                  "fileSizeLimitBytes": 500000000
                }
              }
            ]
          }
        }
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByExcluding",
                "Args": {
                  "expression": "EndsWith(SourceContext, 'AppService') or EndsWith(SourceContext, 'BackgroundWorker') or StartsWith(SourceContext, 'Microsoft.EntityFrameworkCore') or StartsWith(SourceContext, 'Microsoft.AspNetCore')"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "./App_Data/Logs/Log-.txt",
                  "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} -- [{SourceContext}]{NewLine}{Exception}",
                  "rollingInterval": "Day",
                  "retainedFileCountLimit": 20,
                  "rollOnFileSizeLimit": true,
                  "fileSizeLimitBytes": 500000000
                }
              }
            ]
          }
        }
      }
    ],
    "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
  }

Has anyone re-used pages and found a way to dynamically generate the breadcrumbs so they determine the previous page correctly?

For example, I have a page that is referenced in multiple locations throughout my solution.

The breadcrumbs are a list of breadcrumbitem and the helper renders it.

However, these are basically just hard coded.

Would it be better to do this client side and examine the history of the browser, or look at referrer (which could possibly be blocked)?

Not sure how to address this. I want to keep my code DRY.

For anyone coming across this - since anyone who has permissions to set the visual settings for a tenant site, or as a host, site wide, does not have the ability to set their own personal theme - here's a dead simple way to do it. My project is jQuery based.

In the UiCustomizationViewModel.cs file, add a boolean for personal settings, as I've added PersonalSettings below.

    public class UiCustomizationViewModel
    {
        public string Theme { get; set; }

        public List<ThemeSettingsDto> Settings { get; set; }

        public bool HasUiCustomizationPagePermission { get; set; }
        public bool PersonalSettings { get; set; }

        public ThemeSettingsDto GetThemeSettings(string themeName)
        {
            return Settings.First(s => s.Theme == themeName);
        }
    }

Next, in your UiCustomizationController, you'll want a flag for if the user is setting their personal settings. This could likely be done in reverse, but this is how I did it. Note the new personal parameter with the default value and how it's set on the view model.

        public async Task<ActionResult> Index(bool personal = false)
        {
            var model = new UiCustomizationViewModel
            {
                Theme = await SettingManager.GetSettingValueAsync(AppSettings.UiManagement.Theme),
                Settings = await _uiCustomizationAppService.GetUiManagementSettings(),
                HasUiCustomizationPagePermission = await PermissionChecker.IsGrantedAsync(AppPermissions.Pages_Administration_UiCustomization),
                PersonalSettings = personal
            };

            return View(model);
        }

Then, in the index.cshtml under UiCustomization, you'll need to modify which of the buttons are shown based on the personal settings, like so:

Change the line that reads:

@if (Model.HasUiCustomizationPagePermission)

to read

@if (Model.PersonalSettings==false && Model.HasUiCustomizationPagePermission)

Lastly, you'll want to modify the dropdown menu html in the file Default.cshtml under AppUserMenu.

In that file, you remove or comment out the if statement that hides visual settings if the user has permission to visual settings. Comment out this if statement:

@if (!Model.HasUiCustomizationPagePermission)

Then, modify the action to pass the parameter personal=true so the controller / page knows you're wanting to edit your personal settings.

It should look like this:

@Url.Action("Index", "UiCustomization",new {personal=true})

That's it!

Now, an admin can set a system default, but use another theme for themselves. Users without a custom theme will get the default.

I've tested at host and tenant level and haven't seen any issues with it.

This allows for a better admin user experience.

Answer

You can make a console application that hits your API, then move it to a class.

Do that first. There's a plethora of examples out there.

Consider testing your calls with postman.

Then, create a windows service project, add your class to it.

Have your windows service perform the processes.

This may of value to you: https://learn.microsoft.com/en-us/dotnet/core/extensions/windows-service

There are a multitude of windows service projects out there you can check to create your own.

If you have swagger enabled -> this may be of distinct value to you: https://stackoverflow.com/questions/54094688/generate-net-client-from-swagger

You can put your credentials in the service's configuration file - but if you don't have direct control of where you install it, you may want to consider another strategy.

Vanilla 13.1.1 project, on postgress - Adding a basic entity with GUID ID column has scaffolding issues.

No rendering get entity for view and tests with min max length are reversed.

Just an FYI.

Showing 231 to 238 of 238 entries