Base solution for your next web application

Activities of "compassinformatics17"

  • What is your product version?: 4.8.1
  • What is your product type: MVC
  • What is product framework type: .NET Framework (Non Core)

Issue

  • Dates are stored in the SqlServer in ISO format YYYY-MM-DD HH:MM:SS
  • But query results return the dates pre-localised and not in ISO format as one would expect. This is not at all ideal since I would like to use the ISO date as a starting point for date transformations on the client side.
  • I have tried many approaches to get the framework to return the date in raw ISO form but none work, the results always seem to be pre-localised in the debugger and also in the JSON response.
  • I have also put this question on StackOverfllow last year: https://stackoverflow.com/questions/62016070/asp-net-abp-return-an-iso-datetimenot-local-from-the-db
  • Is there a way to make the framework default to ISO date time formatting rather than localising all results.

Things I have tried

  • Setting the DateTimeKind
  • Disabling DateTime Normalisation
  • Setting JSON parameters in Global.asax Application_Start() such as:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
  • Commenting out "Clock.Provider = ClockProviders.Utc" in Global.asax.
  • Forcibly converting to ISO format using date.ToString('yyyy-MM-dd').
    • Cannot do this because the LINQ query is not aware of ASP date functions since comparable functions do not exist in SQL and hence the query will fail and throw and error.

Example of the issue

var query = _someRepository.GetAll();

var rows = query.Select(a => new RowViewModel { Id = a.Id, Name = a.Name, CreatedBy = a.CreatorUser.Name + " " + a.CreatorUser.Surname, CreatedOn = a.CreationTime //Returns 22/05/2020 10:05:05, I want 2020-05-22 10:05:05 });

Hi, I checked the web.config possibility also based on reports on StackOverflow. The web.config file generated by Core during deployment is there but it contains no reference to any environment variable. Thanks.

Hi,

Thanks for getting back to me. I understand the log4net config but I'm not sure that is related to my issue. My issue is not so much with configuring logging. The logging works fine. But on our live environment, I want the app to run in production mode. But it is ignoring the ASPNETCORE_ENVIRONMENT variable which is set to Production.

Even though the environment is production, AspNetZero still prints out full stack traces to the screen on error because it is always running in development mode. Any ideas on how to make the framework obey the ASPNETCORE_ENVIRONMENT variable and run in production mode.

Is there perhaps some known issue with the ASPNETCORE_ENVIRONMENT variable and the framework in Azure?

Thanks.

Hi All,

We are deploying our product which is AspNetZero Core & Jquery 8.9.2 to Azure. We have encountered an issue with environment configuration. ASPNETCORE_ENVIRONMENT has been set to Production in Azure and I have verified that it is in effect via Kudu. But AspNetZero is still operating in Development mode and returning full stack traces for any errors. I wonder if there is something that I am missing. We have not made any customisations which should affect this functionality.

The only changes we have made to the dev, prod process is that we only use a single appsettings.json file and our deployment process auto replaces the values in this file dependant on the environment into which we are deploying via DevOps. But to my knowlege this code does not alter or reference ASPNETCORE_ENVIRONMENT in any way. Everything works fine via this setup, but I cannot see why the ASPNETCORE_ENVIRONMENT is being ignored by the project.

AppConfigurations.cs

        private static IConfigurationRoot BuildConfiguration(string path, string environmentName = null, bool addUserSecrets = false)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(path)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

            // Disable this, we want to always use appsettings.json for clarity and ease of configuration.
            // appsettings.Production.json and appsettings.Staging.json also removed.
            //if (!environmentName.IsNullOrWhiteSpace())
            //{
            //    builder = builder.AddJsonFile($"appsettings.{environmentName}.json", optional: true);
            //}

            builder = builder.AddEnvironmentVariables();

            if (addUserSecrets)
            {
                builder.AddUserSecrets(typeof(AppConfigurations).GetAssembly());
            }

            var builtConfig = builder.Build();
            new AppAzureKeyVaultConfigurer().Configure(builder, builtConfig);

            return builder.Build();
        }
    }

Hi Sorry for the late reply.

I'm using Yarn to handle dependencies as specified in this AspNetZero Tutorial. All works fine but the files are not changed on the azure version.

Below are a few images detailing the config and build task output in DevOps. It runs fine but the JS files are not minified in the artefact once the build completes on DevOps. I cannot see what's amiss and why the files are not being minified since all appears correct to my eyes.

Thanks, Barry.

Hi Guys, I managed to get this sorted. This allows you to access appsettings.json from the context.

// Enable access appsettings.json in the context.
private readonly IConfigurationRoot _appConfiguration = new ConfigurationBuilder()
        .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .Build();

Hi All, I am trying to access some Azure specific settings in my DbContext. It works in local dev but fails once deployed to Azure or IIS becuase WebContentDirectoryFinder.CalculateContentRootFolder() is apparently for Dev.

I am trying to configure Azure Managed Identity access and I need to edit my context as follows.

public MyDbContext(DbContextOptions<MyDbContext> options)
            : base(options)
        {
            // Check to see if we are attempting to connect to an Azure DB. If we are use Azure Managed Identity to authenticate.
            var dbConnection = Database.GetDbConnection();
            if (dbConnection is SqlConnection connection)
            {
                // If not an Azure db connection, just return, no need to get managed identity token.
                if (!connection.ConnectionString.Contains(_appConfiguration["Azure:azureDatabaseEndpoint"], StringComparison.OrdinalIgnoreCase))
                    return;

                // If an Azure connection but using password, just return, no need to get managed identity token.
                if (connection.ConnectionString.Contains("password=", StringComparison.OrdinalIgnoreCase))
                    return;

                // Get Azure managed identity token for the given AzureServiceTokenProviderConnectionString connection string.
                // https://docs.microsoft.com/en-us/azure/key-vault/general/service-to-service-authentication#connection-string-support
                connection.AccessToken = new AzureServiceTokenProvider(_appConfiguration["Azure:AzureServiceTokenProviderConnectionString"])
                    .GetAccessTokenAsync($"https://{_appConfiguration["Azure:AzureDatabaseEndpoint"]}/")
                    .Result;
            }
        }

I could use DI to pull in IAppConfigurationAccessor as one would normally do but this then causes issues because the framework is designed to use the normal constructor

public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)

I also tried to get them locally in the context as follows, which works in Dev but not on IIS or Azure because of WebContentDirectoryFinder.CalculateContentRootFolder() being Dev only functionality.

// Enable access appsettings.json in the context.
private readonly IConfigurationRoot _appConfiguration = AppConfigurations.Get(
                                                                    WebContentDirectoryFinder.CalculateContentRootFolder(),
                                                                    addUserSecrets: true
                                                                    );

Is there some way in which I can access the appsettings.json from within the context other than this or is there a way for me to get the correct path for the root directory of MVC?

Thanks, Barry.

Hi,

OK I did some more investigation on this. It works correctly if I run it locally on my dev machine. But when run in devops, the build process completes without errors but the files are not changed. I am still trying to ascertain the cause, so if anyone else has experienced the same issue, I would be interested in hearing how it was resolved.

No problem, thanks for getting back to me. Note the small function at the end which makes the files editable. This gets around the TFS locking issue for bundle generation.

var gulp = require("gulp");
var path = require('path');
var merge = require("merge-stream");
var globby = require('globby');
var concat = require('gulp-concat');
var less = require('gulp-less');
var uglify = require('gulp-uglify-es').default;
var cleanCss = require('gulp-clean-css');
var postcss = require("gulp-postcss");
var url = require("postcss-url");

var bundleConfig = require(path.resolve(__dirname, 'bundles.json'));
var production = false;

const { watch } = require('gulp');

var styleEntries = {};
var scriptEntries = {};

var viewScripts = globby.sync([
    './wwwroot/view-resources/**/*.js',
    '!./wwwroot/view-resources/**/*.min.js'
]);

var viewStyles = globby.sync([
    './wwwroot/view-resources/**/*.css',
    './wwwroot/view-resources/**/*.less',
    '!./wwwroot/view-resources/**/*.min.css'
]);

var metronicScripts = globby.sync([
    './wwwroot/metronic/**/*.js',
    '!./wwwroot/metronic/**/*.min.js',
    '!./wwwroot/metronic/core/**/*.js'
]);

var metronicStyles = globby.sync([
    './wwwroot/metronic/**/*.css',
    './wwwroot/metronic/**/*.less',
    '!./wwwroot/metronic/**/*.min.css'
]);

function processInputDefinition(input) {
    var result = [];
    for (var i = 0; i < input.length; i++) {
        var url = input[i];
        if (url.startsWith('!')) {
            result.push('!' + path.resolve(__dirname, url.substring(1)));
        } else {
            result.push(path.resolve(__dirname, url));
        }
    }

    return result;
}

function fillScriptBundles() {
    // User defined bundles
    for (var k = 0; k < bundleConfig.scripts.length; k++) {
        var scriptBundle = bundleConfig.scripts[k];
        scriptEntries[scriptBundle.output] = globby.sync(processInputDefinition(scriptBundle.input), { noext: true });
    }

    // View scripts
    for (var i = 0; i < viewScripts.length; i++) {
        var viewScriptName = viewScripts[i].replace('./wwwroot/', '');
        scriptEntries[viewScriptName.replace('.js', '.min.js')] = [path.resolve(__dirname, viewScripts[i])];
    }

    // Metronic scripts
    for (var j = 0; j < metronicScripts.length; j++) {
        var metronicScriptName = metronicScripts[j].replace('./wwwroot/', '');
        scriptEntries[metronicScriptName.replace('.js', '.min.js')] = [path.resolve(__dirname, metronicScripts[j])];
    }
}

function fillStyleBundles() {
    // User defined styles
    for (var k = 0; k < bundleConfig.styles.length; k++) {
        var styleBundle = bundleConfig.styles[k];
        styleEntries[styleBundle.output] = globby.sync(processInputDefinition(styleBundle.input), { noext: true });
    }

    // View styles
    for (var j = 0; j < viewStyles.length; j++) {
        var viewStyleName = viewStyles[j].replace('./wwwroot/', '');

        if (viewStyleName.indexOf('.css') >= 0) {
            styleEntries[viewStyleName.replace('.css', '.min.css')] = [path.resolve(__dirname, 'wwwroot/' + viewStyleName)];
        }

        if (viewStyleName.indexOf('.less') >= 0) {
            styleEntries[viewStyleName.replace('.less', '.min.css')] = [path.resolve(__dirname, 'wwwroot/' + viewStyleName)];
        }
    }

    // Metronic styles
    for (var i = 0; i < metronicStyles.length; i++) {
        var metronicStyleName = metronicStyles[i].replace('./wwwroot/', '');

        if (metronicStyleName.indexOf('.css') >= 0) {
            styleEntries[metronicStyleName.replace('.css', '.min.css')] =
                [path.resolve(__dirname, 'wwwroot/' + metronicStyleName)];
        }

        if (metronicStyleName.indexOf('.less') >= 0) {
            styleEntries[metronicStyleName.replace('.less', '.min.css')] = [path.resolve(__dirname, 'wwwroot/' + metronicStyleName)];
        }
    }
}

function getFileNameFromPath(fullPath) {
    return path.basename(fullPath);
}

function getPathWithoutFileNameFromPath(fullPath) {
    return path.dirname(fullPath);
}

function fillScriptMappings() {
    for (var k = 0; k < bundleConfig.scriptMappings.length; k++) {
        var scriptBundle = bundleConfig.scriptMappings[k];
        var inputFilesToBeCopied = globby.sync(processInputDefinition(scriptBundle.input), { noext: true });
        for (var j = 0; j < inputFilesToBeCopied.length; j++) {
            var outputFileName = path.join(scriptBundle.outputFolder, getFileNameFromPath(inputFilesToBeCopied[j]));
            scriptEntries[outputFileName] = [inputFilesToBeCopied[j]];
        }
    }
}

function createScriptBundles() {
    var tasks = [];
    for (var script in scriptEntries) {
        tasks.push(
            createScriptBundle(script)
        );
    }

    return tasks;
}

function createScriptBundle(script) {
    var bundleName = getFileNameFromPath(script);
    var bundlePath = getPathWithoutFileNameFromPath(script);

    var stream = gulp.src(scriptEntries[script]);

    if (production) {
        stream = stream
            .pipe(uglify());
    }

    return stream.pipe(concat(bundleName))
        .pipe(gulp.dest('wwwroot/' + bundlePath));
}

function createStyleBundles() {
    var tasks = [];
    for (var style in styleEntries) {
        tasks.push(
            createStyleBundle(style)
        );
    }

    return tasks;
}

function createStyleBundle(style) {

    var bundleName = getFileNameFromPath(style);
    var bundlePath = getPathWithoutFileNameFromPath(style);

    var options = {
        url: function (asset) {
            // Ignore absolute URLs
            if (asset.url.substring(0, 1) === '/') {
                return asset.url;
            }

            var outputFolder = '';

            if (asset.url.match(/\.(png|svg|jpg|gif)$/)) {
                outputFolder = 'dist/img';
            } else if (asset.url.match(/\.(woff|woff2|eot|ttf|otf)[?]{0,1}.*$/)) {
                outputFolder = 'dist/fonts';
            } else {
                // Ignore not recognized assets like data:image etc...
                return asset.url;
            }

            var fileName = path.basename(asset.absolutePath);
            var outputPath = path.join(__dirname, '/wwwroot/' + outputFolder + '/');

            gulp.src(asset.absolutePath).pipe(gulp.dest(outputPath));

            return '/' + outputFolder + '/' + fileName;
        }
    };

    var stream = gulp.src(styleEntries[style])
        .pipe(postcss([url(options)]))
        .pipe(less({ math: 'parens-division' }));

    if (production) {
        stream = stream.pipe(cleanCss());
    }

    return stream
        .pipe(concat(bundleName))
        .pipe(gulp.dest('wwwroot/' + bundlePath));
}

function findMatchingElements(path, array) {
    var result = [];
    for (var item in array) {
        if (array[item].indexOf(path) >= 0) {
            result[item] = array[item];
        }
    }

    return result;
}

function watchScriptEntries() {
    for (var script in scriptEntries) {
        var watcher = watch(scriptEntries[script]);

        watcher.on('change', function (path, stats) {
            console.log(`${path} updated`);

            var changedBundles = findMatchingElements(path, scriptEntries);

            for (var changedBundle in changedBundles) {
                createScriptBundle(changedBundle);
            }

        });
    }
}

function watchStyleEntries() {
    for (var style in styleEntries) {
        var watcher = watch(styleEntries[style]);

        watcher.on('change', function (path, stats) {
            console.log(`${path} updated`);

            var changedBundles = findMatchingElements(path, styleEntries);

            for (var changedBundle in changedBundles) {
                createStyleBundle(changedBundle);
            }

        });
    }
}

function build() {

    production = true;

    fillScriptBundles();
    fillStyleBundles();
    fillScriptMappings();

    var scriptTasks = createScriptBundles();
    var styleTasks = createStyleBundles();

    return merge(scriptTasks.concat(styleTasks));
}

function buildDev() {

    fillScriptBundles();
    fillStyleBundles();
    fillScriptMappings();

    var scriptTasks = createScriptBundles();
    var styleTasks = createStyleBundles();

    watchScriptEntries();
    watchStyleEntries();

    console.log('Bundles are being created, please wait...');

    return merge(scriptTasks.concat(styleTasks));
}

function clearReadOnlyAttribute(path) {
    console.log('Ensuring files are editable, please wait...');
    require('child_process').exec('attrib -r "' + path + '\*.*" /s /d');
}

// boneill: Make all files and folders in wwwroot editable. Fixes TFS locking issue for bundle generation.
clearReadOnlyAttribute('./wwwroot/');

exports.build = build;
exports.buildDev = buildDev;

Hi Guys,

I am coming to the end of the dev stage of an application and want to deploy to QA/UAT. As part of this process I want to run "npm run build". I followed the process outlined here https://docs.aspnetzero.com/en/aspnet-core-mvc/latest/Setting-Up-an-Azure-Pipeline-Mvc-Core for yarn and "npm run build" in DevOps. As you can see below, "npm run build" executes correctly with no errors, but the created bundles are identical to the dev versions and are not minified. I notice that this was a bug in the past #6987. Is it possible this bug has reappeared?

I'm using version 8.9.2 of the framework.

Thanks, Barry.

2020-08-04T13:43:54.4659893Z [14:43:54] Using gulpfile H:\Agents\1\_work\67\s\src\MyProject.Web.Mvc\gulpfile.js
2020-08-04T13:43:54.4721122Z [14:43:54] Starting 'build'...
2020-08-04T13:47:48.3216910Z [14:47:48] Finished 'build' after 3.88 min
2020-08-04T13:47:48.4513226Z ##[section]Finishing: NPM Run Build task
Showing 1 to 10 of 23 entries