Need help moving MSBuild to Nuke - msbuild

I'm trying to replace MSBuild with nuke, but I'm having trouble doing so. The first thing that I tried to do is setting the intermediate output paths and whatnot to another folder. I have previously done this in MSBuild by using a props file that defines BaseOutputPath, OutputPath, BaseIntermediateOutputPath, IntermediateOutputPath to some sane values in the build folder. So what I did is I defined functions for getting these values in my nuke build script:
const string DefaultBuildOutputFolderName = "build_folder";
[Parameter($"Absolute path where to output kari built things. Default is \"{DefaultBuildOutputFolderName}\"")]
readonly AbsolutePath KariBuildPath = null;
AbsolutePath SourceDirectory => RootDirectory / "source";
AbsolutePath BuildOutputDirectory => KariBuildPath ?? (RootDirectory / DefaultBuildOutputFolderName);
AbsolutePath BinOutputDirectory => BuildOutputDirectory / "bin";
AbsolutePath ObjOutputDirectory => BuildOutputDirectory / "obj";
AbsolutePath PackageOutputDirectory => BuildOutputDirectory / ".nupkg";
AbsolutePath GetProjectBaseOutputPath(string projectName) => BinOutputDirectory / projectName;
AbsolutePath GetProjectOutputPath(string projectName, string configuration) => GetProjectBaseOutputPath(projectName) / configuration;
AbsolutePath GetBaseIntermediateOutputPath(string projectName) => ObjOutputDirectory / projectName;
AbsolutePath GetIntermediateOutputPath(string projectName, string configuration) => GetBaseIntermediateOutputPath(projectName) / configuration;
AbsolutePath GetPackageOutputPath(string projectName, string configuration) => PackageOutputDirectory / projectName / configuration;
Then I'm just setting individual properties, which also have to have a trailing slash
DotNetPublishSettings SetSaneDefaults(DotNetPublishSettings settings, string projectName)
{
return settings
.SetConfiguration(Configuration)
.SetFramework("net6.0")
.SetProperty("BaseOutputPath", GetProjectBaseOutputPath(projectName) + System.IO.Path.DirectorySeparatorChar)
.SetProperty("OutputPath", GetProjectOutputPath(projectName, Configuration) + System.IO.Path.DirectorySeparatorChar)
.SetProperty("BaseIntermediateOutputPath", GetBaseIntermediateOutputPath(projectName) + System.IO.Path.DirectorySeparatorChar)
.SetProperty("IntermediateOutputPath", GetIntermediateOutputPath(projectName, Configuration) + System.IO.Path.DirectorySeparatorChar)
.SetProperty("PackageOutputPath", GetPackageOutputPath(projectName, Configuration) + System.IO.Path.DirectorySeparatorChar)
.SetProperty("AssemblyName", projectName);
}
What's the right way to do it? Also, how would it resolve the dependencies if I'm setting these properties per project?
Is it possible for nuke to handle the whole build process and not delegate to msbuild?
MSBuild sucks and will involve to some extent configuration duplication
Say, I want to build a project. Do I have to respecify its dependencies and manually invoke a build for each? Like I'm completely lost how to do this
I've read through the docs but practically it's not clear at all how to do it
Is it possible to specify all configuration outside project files and have the project files and the solution file get autogenerated?
The project is by this link

Related

IIS ASP.NET 6 startup throws System.IO.DirectoryNotFoundException: D:\agent\_work\38\s\IdentityServer\wwwroot\

We are updating one of our applications, in this case IdentityServer, from .NET 5 to .NET 6. It is being hosted by IIS and deployed by Azure Devops Services. The issue we are seeing is that on our development environment the website fails to load but on our staging environment it runs just fine. The error we are seeing on development is
12:45:37.519|Fatal|1||Host terminated unexpectedly.||
System.IO.DirectoryNotFoundException: D:\agent\_work\38\s\IdentityServer\wwwroot\
at Microsoft.Extensions.FileProviders.PhysicalFileProvider..ctor(String root, ExclusionFilters filters)
at Microsoft.Extensions.FileProviders.PhysicalFileProvider..ctor(String root)
at Microsoft.AspNetCore.Hosting.StaticWebAssets.StaticWebAssetsLoader.<>c.<UseStaticWebAssetsCore>b__1_0(String contentRoot)
at Microsoft.AspNetCore.StaticWebAssets.ManifestStaticWebAssetFileProvider..ctor(StaticWebAssetManifest manifest, Func`2 fileProviderFactory)
at Microsoft.AspNetCore.Hosting.StaticWebAssets.StaticWebAssetsLoader.UseStaticWebAssetsCore(IWebHostEnvironment environment, Stream manifest)
at Microsoft.AspNetCore.Hosting.StaticWebAssets.StaticWebAssetsLoader.UseStaticWebAssets(IWebHostEnvironment environment, IConfiguration configuration)
at Microsoft.AspNetCore.WebHost.<>c.<ConfigureWebDefaults>b__9_0(WebHostBuilderContext ctx, IConfigurationBuilder cb)
at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass9_0.<ConfigureAppConfiguration>b__0(HostBuilderContext context, IConfigurationBuilder builder)
at Microsoft.Extensions.Hosting.HostBuilder.BuildAppConfiguration()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
at IdentityServer.Program.Main(String[] args) in D:\agent\_work\38\s\IdentityServer\Program.cs:line 23
The path it reports, D:\agent\_work\38\s\IdentityServer\wwwroot\ is interesting because that path is the same as the path from the DevOps build machine. We don't see this error if we revert back to .NET 5 and we don't see the problem on our staging machine.
The Program.cs class is defined as
using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using NewRelic.LogEnrichers.Serilog;
using Serilog;
using Serilog.Events;
namespace IdentityServer
{
public class Program
{
public static int Main(string[] args)
{
try
{
CreateLogger();
Log.Information("Starting host...");
CreateHostBuilder(args).Build().Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly.");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
public static void CreateLogger()
{
var configuration = GetConfiguration();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.Enrich.FromLogContext() // allows logging middleware to inject output values
.Enrich.WithThreadId()
.Enrich.WithNewRelicLogsInContext()
.CreateLogger();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
var configuration = GetConfiguration();
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(
webBuilder =>
{
webBuilder.UseConfiguration(configuration);
webBuilder.UseSerilog();
webBuilder.UseIIS();
webBuilder.CaptureStartupErrors(true);
webBuilder.UseStartup<Startup>();
});
}
private static IConfiguration GetConfiguration()
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{environment}.json", true, true);
var configuration = builder.Build();
return configuration;
}
}
}
We do have other .NET 6 web applications running just fine on this instance of IIS. I was thinking that the problem might be in our release pipelines but they are identical in their task configurations between the environments. Tried looking for the directory path in the code or configuration but don't see it anywhere. Have tried manually setting the WebRoot and ContentRoot paths via .UseWebRoot("path to folder") and .UseContentRoot("path to folder") in the Program.cs but didn't see any change in the logs or the app starting.
Even updated the web.config file to have the exact path for executing the project dll in the aspNetCore element but still no change.
Update 10 Feb 2022
Added debug output to the startup to verify file and folder paths. Everything in the environment variables and execution file path look correct.
ASPNETCORE_IIS_PHYSICAL_PATH - C:\inetpub\webapps\IdentityServer\
Executable Path: C:\inetpub\webapps\IdentityServer\IdentityServer.dll
The problem ended up being how we were pushing our updates out to the servers from DevOps. Our pipelines were built to copy over files out of the Release directory of the build folder. One of the problems with this approach is that files not needed for a site to run but generated during a build are also copied to the release server. In this case, a new file which is generated in .NET 6, .staticwebassets.runtime.json, was getting copied to our servers.
The way .NET 6 seems to behave is that if the environment is set to Development then it will look for this file to figure out where the static web assets are located. If the file doesn't exist then it will assume the files are in a wwwroot sub-directory of the site. This makes sense for instances where you are running the project from your local Visual Studio. More details about this file are available in another SO post with links to the source code in GitHub. To fix our problem we changed our release pipeline to use the publish.zip file that is generated when you run the publish command on a solution. The archive only contains the files needed to run the site, so none of the extraneous files like .staticwebassets.runtime.json are included. We should have been doing this the whole time... lesson learned.
We now unzip the publish.zip file, apply any file transformations, then copy the unzipped files to the web server.

aspnet core dependency injection with IOptions in startup

What is going on gang?
I am looking for a proper and clean way of injecting configuration into my classes using the IOptions package. Currently, I am registering all the configuration object in one place and everything is just fine. The problem is with some classes that I need to register in the startup method.
The initial method implementation looks like this: (extension method)
public static void RegisterHttpClients(this IServiceCollection services, AppSettings appSettings)
{
services.AddHttpClient<IPdfService, PdfService>(
httpClient => httpClient.DefaultRequestHeaders.Add("PdfApiKey", appSettings.PdfApiKey));
}
This AppSettings object I am also injecting into other classes using IOptions so would like to somehow reuse the object from there instead of having to get same object from json again like that:
var appSettings = Configuration.GetSection("AppSettings").Get<AppSettings>();
services.RegisterHttpClients(appSettings);
In other words - I already have the thing registered with services.Configure<AppSettings>(configuration.GetSection("AppSettings")); so would kind of like to piggy-back on this if possible.
Any thoughts/suggestions for possible directions on how to configure this?
EDIT: (added JSON and AppSettings files)
public class AppSettings
{
public string PdfApiKey {get; set;}
public string AnotherProperty {get; set;}
}
{
"AppSettings": {
"PdfApiKey": "SomeKey",
"AnotherProperty": "HollyTheCow"
}
}
Thanks in advance!
You can accomplish this by registering your options with Configure (which you've done), then using the second overload to AddHttpClient() to get the registered options from the DI container (Service Locator pattern). Note I've used IServiceProvider.GetRequiredService, but you can use IServiceProvider.GetService()?.Value if it's an optional setting.
services.Configure<AppSettings>(_configuration.GetSection("AppSettings"));
services.AddHttpClient<YourService>((serviceProvider, config) => {
var settings = serviceProvider.GetRequiredService<IOptions<AppSettings>>().Value;
config.BaseAddress = new Uri(settings.BaseUri);
});
Your code has no any error and will run successfully
There may be many appSetting.json files on your project.
Check the ASPNETCORE_ENVIRONMENTvariable on your project.
Right click on your project > Properties > Debug > Environment variables
if ASPNETCORE_ENVIRONMENT is set on Development IConfiguration read data from
appSettings.development.json and you should add AppSettigs json in appSettings.development.json
if ASPNETCORE_ENVIRONMENT is set on Production IConfiguration read data from
appSettings.production.json and you should add AppSettigs json in appSettings.production.json
if you want IConfiguration read data from appSetting.json delete both appSettings.development.json and appSettings.production.json file.
in this case IConfiguration by default read and bind data from appSetting.json
Another options
You can config your IConfiguration to load data dynamically from
appSettings.json like this
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
this.configuration = builder.Build();
}

How can you use multiple directories for static files in an aspnet core app?

By default, the wwwroot directory can host static files. However, in some scenarios, it might be ideal to have two directories for static files. (For example, having webpack dump a build into one gitignored directory, and keeping some image files, favicon and such in a not-gitignored directory). Technically, this could be achieved by having two folders within the wwwroot, but it might organizationally preferable to have these folders at the root level. Is there a way to configure an aspnet core app to use two separate directories for static files?
Just register UseStaticFiles twice:
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "static"))
});
Now files will be found from wwwroot and static folders.
Registering UseStaticFiles twice won't solve it for MapFallbackToFile
An alternative approach.
// Build the different providers you need
var webRootProvider =
new PhysicalFileProvider(builder.Environment.WebRootPath);
var newPathProvider =
new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, #"Other"));
// Create the Composite Provider
var compositeProvider =
new CompositeFileProvider(webRootProvider, newPathProvider);
// Replace the default provider with the new one
app.Environment.WebRootFileProvider = compositeProvider;
https://wildermuth.com/2022/04/25/multiple-directories-for-static-files-in-aspnetcore/

in asp.net 5 is it possible to store and read custom files in approot instead of wwwroot?

when you deploy an asp.net5/mvc6 app there is the wwwroot folder where web assets like css, js, images belong, and there is approot folder where packages and source code belong.
It seems that classes in the Microsoft.Framework.Configuration namespace for example must be able to read files from below approot since that is where config.json files would live.
What I want to know is, is it possible to store and read custom files of my own in approot? and if so how?
For example I'm not using Entity Framework so I need a place to put sql install and upgrade scripts and would prefer not to put them beneath wwwroot. I also have custom configuration files for things like navigation sitemap that I would rather not put below wwwroot if it is possible to put them elsewhere such as approot.
I know I can access files below wwwroot using IHostingEnvironment env.MapPath("~/somefileinwwwrootfoilder.json")
Is there a similar way to access files under approot?
The accepted answer is correct, but since a ASP.NET Core 1.0 release a few things have changed so I thought I'd write a new clear things up a bit.
What I did was create a folder in my project called AppData. You can call it anything you like.
Note: it's not in wwwroot because we want to keep this data private.
Next, you can use IHostingEnvironment to get access to the folder path. This interface can be injected as a dependency into some kind of helper service and what you end up with is something like this:
public class AppDataHelper
{
private readonly IHostingEnvironment _hostingEnvironment;
private const string _appDataFolder = "AppData";
public AppDataHelper(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
public async Task<string> ReadAllTextAsync(string relativePath)
{
var path = Path.Combine(_hostingEnvironment.ContentRootPath, _appDataFolder, relativePath);
using (var reader = File.OpenText(path))
{
return await reader.ReadToEndAsync();
}
}
}
Additionally, to get things to deploy correctly I had to add the AppData folder to the publishOptions include list in project.json.
As mentioned in the comments, to deploy AppData folder correctly in ASP.NET MVC Core 2 (using *.csproj file, instead of project.json), syntax is as follows:
<ItemGroup>
<None Include="AppData\*" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
Yes, it is possible. Just get the path to your app folder and the pass it to configuration or whoever else needs it:
public class Startup
{
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var wwwrootRoot = env.WebRootPath;
var appRoot = appEnv.ApplicationBasePath;

How can we enable caching for Bundles in MVC5

I have created 2 bundles in my mvc project as given below:
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/Scripts/BaseScripts").Include(
"~/Content/js/jquery-{version}.js",
"~/Content/js/jquery-ui-{version}.js",
"~/Scripts/jquery.unobtrusive-ajax.min.js",
"~/Content/js/bootstrap.js",
"~/Content/js/bootstrap-datepicker.js",
"~/Scripts/jquery.validate.min.js",
"~/Scripts/jquery.validate.unobtrusive.js",
"~/Scripts/customvalidation.js"
)
);
bundles.Add(new ScriptBundle("~/Scripts/CustomScripts").Include(
"~/Content/js/customscripts/commonscript.js",
"~/Content/js/customscripts/registration.js"
));
bundles.Add(new StyleBundle("~/Styles/Css").Include(
"~/Content/css/bootstrap.min.css",
"~/Content/css/ymobiz.css",
"~/Content/css/Style.css",
"~/Content/css/datepicker3.css",
"~/Content/font-awesome-4.1.0/css/font-awesome.min.css",
"~/Content/css/css.css"
));
//To enable bundling and minification
BundleTable.EnableOptimizations = true;
}
Now i need to enable caching for these files as well. Is there any way by which we can control caching duration for these bundles files
MVC bundles are returned as a single static file to browsers whose cache time is set to 1 year by default. ASP.NET MVC takes care of change tracking of your bundle files and changes bundle url if content of any file is changed or a file is being added / removed from bundle.
As bundles are already cached and change tracking is maintained by asp.net mvc framework, what else cache control do you want on those bundles?
EDIT (in response to comment)
Unfortunately you can not alter that limit. Cache limit is handles by ProcessRequest method of BundleHandler class and this is internal sealed so there is no chance that you can inherit \ override these requests.
For further details you can refer this question.
Add a key in webconfig
<appSettings>
<add key="Version" value="sa291988" />
</appSettings>
Create a class, where we define the format for both JavaScript and styles.
using System.Configuration;
namespace BundlingSample
{
public class SiteKeys {
public static string StyleVersion {
get {
return "<link href=\"{0}?v=" +
ConfigurationManager.AppSettings["version"] + "\" rel=\"stylesheet\"/>";
}
}
public static string ScriptVersion {
get {
return "<script src=\"{0}?v=" +
ConfigurationManager.AppSettings["version"] + "\"></script>";
}
}
}
}
#Styles.RenderFormat(SiteKeys.StyleVersion,"~/Content/css")
#Scripts.RenderFormat(SiteKeys.ScriptVersion,"~/bundles/jquery")