Base solution for your next web application
Starts in:
01 DAYS
01 HRS
01 MIN
01 SEC

Activities of "mikatmlvertti"

@maharatha were you able to make it enviromental variable? I also was thinking this since I don't wan't passphrase to be visible in project files.

I was wondering where I should overwrite SimpleStringCipher DefaultPassPhrase so the startup value is not being used and how to handle Seed HostRoleAndUserCreator where Host admin user is created and it is given encrypted Password?

Answer

I think we have latest codes. It is just that now finnish users need to select FLE Standard Time. I didn't know that without googling, so I doubt that normal user is not able to figure it out either. I think that there was better options before updating.. We propably need to put a link to [https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones]) so users can check it out if they are unsure as I was.

This forum would be nicer, if it notified when you are quoted or your topic is being updated.. I only get few days after last reply/other update and then notifications end.

So I added new property for Tenant class:

[StringLength(1024)]
public virtual string AzureStorageConnectionString { get; set; }

and created new tenant cache class so the string is cached as normal db connection string:

public class TenantaCacheExtended : TenantCache<Tenant, User>
    {
        public TenantaCacheExtended(
           ICacheManager cacheManager,
           IRepository<Tenant> tenantRepository,
           IUnitOfWorkManager unitOfWorkManager) : base(cacheManager, tenantRepository, unitOfWorkManager)
        {
        }

        protected override TenantCacheItem CreateTenantCacheItem(Tenant tenant)
        {
            return new TenantCacheItem
            {
                Id = tenant.Id,
                Name = tenant.Name,
                TenancyName = tenant.TenancyName,
                EditionId = tenant.EditionId,
                ConnectionString = SimpleStringCipher.Instance.Decrypt(tenant.ConnectionString),
                IsActive = tenant.IsActive,
                CustomData = SimpleStringCipher.Instance.Decrypt(tenant.AzureStorageConnectionString)
            };
        }
    }

which is being registered for DI at Modules PreInitialize() method

Configuration.ReplaceService<ITenantCache, TenantaCacheExtended>(DependencyLifeStyle.Transient);

To get the connection string at runtime, I use this: Interface:

public interface IAzureStoragePerTenantConnectionStringResolver
    {
        /// <summary>
        /// Gets the connection string for given args.
        /// </summary>
        string AzureStorageConnectionString(AzureStorageConnectionStringResolveArgs args);
    }

Parameters:

public class AzureStorageConnectionStringResolveArgs
    {
        public int? TenantId { get; set; }
    }

Runtime class:

public class AzureStoragePerTenantConnectionStringResolver : IAzureStoragePerTenantConnectionStringResolver, ITransientDependency
    {
        /// <summary>
        /// Reference to the session.
        /// </summary>
        private readonly IDefaultAzureStorageConnectionStringResolver _defaultResolver;
        private readonly ITenantCache _tenantCache;

        public AzureStoragePerTenantConnectionStringResolver(
            IDefaultAzureStorageConnectionStringResolver defaultResolver,
            ITenantCache tenantCache)

        {
            _tenantCache = tenantCache;
            _defaultResolver = defaultResolver;
        }

        public string AzureStorageConnectionString(AzureStorageConnectionStringResolveArgs args)
        {
            if (args.TenantId == null)
            {
                //Requested for host
                return _defaultResolver.GetDefaultAzureStorageConnectionString();
            }

            var tenantCacheItem = _tenantCache.Get(args.TenantId.Value);
            var azureString = tenantCacheItem.CustomData as string;
            if (azureString.IsNullOrEmpty())
            {
                //Tenant has not dedicated storage
                return _defaultResolver.GetDefaultAzureStorageConnectionString();
            }

            return azureString;
        }
    }

If there is no Tenant specific Azure string, I use host: Interface:

public interface IDefaultAzureStorageConnectionStringResolver
    {
        string GetDefaultAzureStorageConnectionString();
    }

Runtime class:

public class DefaultAzureStorageConnectionStringResolver : IDefaultAzureStorageConnectionStringResolver, ISingletonDependency
    {
        private readonly IConfigurationRoot _appConfiguration;

        private string DefaultConnectionString;

        public DefaultAzureStorageConnectionStringResolver(IHostingEnvironment env)
        {
            _appConfiguration = env.GetAppConfiguration();
            DefaultConnectionString = null;
        }

        private void ResolveConnectionString()
        {
            DefaultConnectionString = _appConfiguration["AzureStorage:ConnectionString"];

            if (string.IsNullOrWhiteSpace(DefaultConnectionString))
            {
                throw new Exception("Unable to resolve Default Storage Connection string!");
            }
        }

        public string GetDefaultAzureStorageConnectionString()
        {
            if (DefaultConnectionString == null)
                ResolveConnectionString();

            return DefaultConnectionString;
        }
    }

So host azure con-string is being saved same way as the normal db connection string. In appsettings when developing and as enviromental variable at server.

That AzureStoragePerTenantConnectionStringResolver class is being used at my Azure handler.

private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
        private readonly IAzureStoragePerTenantConnectionStringResolver _connectionStringResolver; //constructor DI

private CloudBlobClient GetStorageClient()
        {
            var tenant = GetCurrentTenantId();
            string connectionString = _connectionStringResolver.AzureStorageConnectionString
                (new AzureStorageConnectionStringResolveArgs() { TenantId= tenant});
            var account = CloudStorageAccount.Parse(connectionString);
            return account.CreateCloudBlobClient();
        }

protected virtual int? GetCurrentTenantId()
        {
            return _currentUnitOfWorkProvider.Current != null
                ? _currentUnitOfWorkProvider.Current.GetTenantId()
                : AbpSession.TenantId;
        }

Also I copied StorageString functions and views at Create and Edit Tenant UI modals (Angular) Create:

<div class="form-group" *ngIf="!useHostDb">
                        <label>{{l("AzureStorageConnectionString")}} *</label>
                        <input #azureStorageConnectionStringInput="ngModel" type="text" name="AzureStorageConnectionString" class="form-control" [(ngModel)]="tenant.azureStorageConnectionString" [ngClass]="{'edited':tenant.azureStorageConnectionString}" required maxlength="1024">
                        <validation-messages [formCtrl]="azureStorageConnectionStringInput"></validation-messages>
                    </div>

Edit html:

<div class="form-group" *ngIf="currentAzureStorageConnectionString">
                        <label>{{l("AzureStorageConnectionString")}} *</label>
                        <input #azureStorageConnectionStringInput="ngModel" type="text" name="AzureStorageConnectionString" class="form-control" [(ngModel)]="tenant.azureStorageConnectionString" required maxlength="1024">
                        <validation-messages [formCtrl]="azureStorageConnectionStringInput"></validation-messages>
                    </div>

Edit ts:

currentConnectionString: string;
currentAzureStorageConnectionString: string;
--
this._tenantService.getTenantForEdit(tenantId).subscribe((tenantResult) => {
                this.tenant = tenantResult;
                this.currentConnectionString = tenantResult.connectionString;
                this.currentAzureStorageConnectionString = tenantResult.azureStorageConnectionString;
--

and added AzureStorageConnectionString property to the necessary dto:s.

And few changes to TenantAppService for saving and editing:

[AbpAuthorize(AppPermissions.Pages_Tenants_Create)]
        [UnitOfWork(IsDisabled = true)]
        public async Task CreateTenant(CreateTenantInput input)
        {
            await TenantManager.CreateWithAdminUserAsync(input.TenancyName,
                input.Name,
                input.AdminPassword,
                input.AdminEmailAddress,
                input.ConnectionString,
                input.IsActive,
                input.EditionId,
                input.ShouldChangePasswordOnNextLogin,
                input.SendActivationEmail,
                input.SubscriptionEndDateUtc?.ToUniversalTime(),
                input.IsInTrialPeriod,
                AppUrlService.CreateEmailActivationUrlFormat(input.TenancyName),
                input.AzureStorageConnectionString
            );
        }

        [AbpAuthorize(AppPermissions.Pages_Tenants_Edit)]
        public async Task<TenantEditDto> GetTenantForEdit(EntityDto input)
        {
            var tenantEditDto = ObjectMapper.Map<TenantEditDto>(await TenantManager.GetByIdAsync(input.Id));
            tenantEditDto.ConnectionString = SimpleStringCipher.Instance.Decrypt(tenantEditDto.ConnectionString);
            tenantEditDto.AzureStorageConnectionString = SimpleStringCipher.Instance.Decrypt(tenantEditDto.AzureStorageConnectionString);
            return tenantEditDto;
        }

        [AbpAuthorize(AppPermissions.Pages_Tenants_Edit)]
        public async Task UpdateTenant(TenantEditDto input)
        {
            await TenantManager.CheckEditionAsync(input.EditionId, input.IsInTrialPeriod);

            input.ConnectionString = SimpleStringCipher.Instance.Encrypt(input.ConnectionString);
            input.AzureStorageConnectionString = SimpleStringCipher.Instance.Encrypt(input.AzureStorageConnectionString);
            var tenant = await TenantManager.GetByIdAsync(input.Id);
            ObjectMapper.Map(input, tenant);
            tenant.SubscriptionEndDateUtc = tenant.SubscriptionEndDateUtc?.ToUniversalTime();

            await TenantManager.UpdateAsync(tenant);
        }

And TenantManager CreateWithAdminUserAsync:

public async Task<int> CreateWithAdminUserAsync(
            string tenancyName,
            string name,
            string adminPassword,
            string adminEmailAddress,
            string connectionString,
            bool isActive,
            int? editionId,
            bool shouldChangePasswordOnNextLogin,
            bool sendActivationEmail,
            DateTime? subscriptionEndDate,
            bool isInTrialPeriod,
            string emailActivationLink,
            string azureConnectionString)
        {
---
//Create tenant
                var tenant = new Tenant(tenancyName, name)
                {
                    IsActive = isActive,
                    EditionId = editionId,
                    SubscriptionEndDateUtc = subscriptionEndDate?.ToUniversalTime(),
                    IsInTrialPeriod = isInTrialPeriod,
                    ConnectionString = connectionString.IsNullOrWhiteSpace() ? null : SimpleStringCipher.Instance.Encrypt(connectionString),
                    AzureStorageConnectionString = azureConnectionString.IsNullOrWhiteSpace() ? null : SimpleStringCipher.Instance.Encrypt(azureConnectionString)
                };

There you have.

Question

Hi

I updated our project with Angular + Core 5.5 template and now timezone selection is not as good as it used to be. There are fewer options and user can't see the hour offset. For example if you want to select Helsinki timezone, you would have to select "Egypt".

I tried to change TimeZoneService to get KnownIanaTimeZoneNames but, when I select Europe/Helsinki, I get exception: System.Exception: Unable to map Europe/Helsinki to iana timezone. at Abp.Timing.Timezone.TimezoneHelper.WindowsToIana(String windowsTimezoneId) in D:\Github\aspnetboilerplate\src\Abp\Timing\Timezone\TimezoneHelper.cs:line 45

So can you provide any workaround for this issue?

Hi

I updated our project with template 5.5 and after publishing to our test server in azure, app is not working. I have changed in Azures wwwroot/assets/appconfig.json correct urls, but when I reload app, I see with Developer Tools (Mozilla) that there is GET for http://ourdomain.azurewebsites.net/assets/appconfig.production.json and this production json has localhost settings. I can't see this production file in azures filebrowser, so I don't understand where it is coming from and why it is now trying to get that "production" config.

//Edit Visual Studio Server Explorer is not showing this appconfig.production.json, but Azure portals console is able to show that there is this file...

I was able to copy non production appconfig with console and overwrite production file so I was able to get app working.

Is this appconfig.production.json new thing with Template 5.5 or can anyone tell me why there is now production config file. Before I could change appconfig file with visual studio azure browser but now after that, I need to use azure console :D

Started playing with settings and now I inserted those two lines to PreInitialize() and got exeption that there is allready class defined as ITenantCache. Removed Register and left only Replace -> my TenantCacheExtended CreateTenantCacheItem is being used.

So Not Initialize() but PreInitialize().

Hi,

Using "using(_session.Use)" worked. I was able to get localized texts with "L('str')".

Thank you!

First without ReplaceService, then both Register and Replace and lastly only Replace.

public override void Initialize()
{
    IocManager.RegisterAssemblyByConvention(typeof('my'ApplicationModule).GetAssembly());
    
    IocManager.Register<ITenantCache, TenantaCacheExtended>(DependencyLifeStyle.Transient);
    Configuration.ReplaceService<ITenantCache, TenantaCacheExtended>(DependencyLifeStyle.Transient);
}

And this is in public class 'my'ApplicationModule : AbpModule.

<cite>alper: </cite> I think you need to replace the service with the code like below.

I tried again in Core module Initialize and PostInitialize and Application module Initialize but it didn't work. Where do you think I need to add that Replace method?

How can I set localization culture when executing background job? I want culture to be set by the users culture settings. I loaded user entity and settings but there setting "Abp.Localization.DefaultLanguageName" value is null. I have changed language so it should be different than default.

Showing 21 to 30 of 52 entries