Force the usage of a bin folder - asp.net-core

I have a larger ASP.NET Core project which means that a lot of DLLs are included in the publish.
Since all of the DLLs are placed in the root folder it's cumbersome to navigate the folder structure (to mange configs etc) due to the sheer amount of files.
Is it possible to tell ASP.NET Core that it should load all assemblies from another folder (bin\)?

I would do it in opposite side. If your problem is just config files then relocate them into config folder and keep them there. As of now dotnet will publish your project + framework (if you use self contained flag).
You can configure aspnetcore to use files
config.AddJsonFile("config/appsettings.json", optional: false, reloadOnChange: false);
So then that folder will be on top and better accessible
Yes in root folder there still be web.config but in my project that file usually is same for all environments. But again it depends where you deploy, because if you deploy to non IIS environment then you don't even need it

Hi How about the Managed Extensibility Framework , It allows you load assemblies dynamically.
Use BuildManager to load assemblies dynamically,
string pluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins");
foreach (string f in Directory.GetDirectories(pluginPath))
{
string binPath = Path.Combine(f, "bin");
if (Directory.Exists(binPath))
{
foreach (String file in Directory.GetFiles(binPath, "*.dll"))
{
Assembly a = Assembly.LoadFrom(file);
BuildManager.AddReferencedAssembly(a);
}
}
Resolve assemblies using below code,
protected virtual void Application_Start(object sender, EventArgs e)
{
//...
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in currentAssemblies)
{
if (assembly.FullName == args.Name || assembly.GetName().Name == args.Name)
{
return assembly;
}
}
return null;
}

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.

Access IIS root directory from Asp.net core

I have shared appsettings in SharedSettings folder.
I am deploying all the asp.net core application in sub directory.
For development, I have created as site with the following directory
c://Website
Here I am keeping my sharedsettings.
But I am developing application from another folder. Here I have configured my application to run as sub application in the web sites.
Folder could be
c://Applications/App1
I want to include the sharedsettings.json in my application. So I have tried the following approach.
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
var sharedFolder = Path.Combine(env.ContentRootPath, "..", "Shared");
config
.AddJsonFile(Path.Combine(sharedFolder, "SharedSettings.json"), optional: true) // When running using dotnet run
.AddJsonFile("SharedSettings.json", optional: true) // When app is published
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
config.AddEnvironmentVariables();
})
But whenever I have tried to get the hosted url path, It always current application hosted directory. (I understand the reason for the result).
But I don't know how to get the root directory path where it is pointing to the different location.
Reference:
https://andrewlock.net/sharing-appsettings-json-configuration-files-between-projects-in-asp-net-core/
According to your description, the only way to achieve your requirement is using Microsoft.Web.Administration library to select the site root path according to the asp.net core application root path.
Notice: If you want to use Microsoft.Web.Administration, you should make sure your application pool contains enough permission to access the applicationhost.config file.
Normally, we will need localsystem permission. About how to modify application pool permission, you could refer to this article.
Codes:
string path = env.ContentRootPath;
ServerManager mgr = new ServerManager();
//the root path is the parent website's root path
string rootpath = "";
foreach (var site in mgr.Sites)
{
foreach (var application in site.Applications)
{
foreach (var virtualDirectory in application.VirtualDirectories)
{
if (virtualDirectory.PhysicalPath == path)
{
rootpath = site.Applications[0].VirtualDirectories.Where(x=>x.Path=="/").Single().PhysicalPath;
}
}
}
}

appsettings.json file not in .net core console project

I understand that .net core has replaced the app.config file with appsetting.json. However this file seems to be added for ASP.net projects only. In fact it is not even available in the add items list.
I found this post that list packages needed to be added:
Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.Configuration.Json
I added all these and it does give me the option of adding a json configuration file but still not the App Settings File which is only available under ASP.Net Core.
I am trying to understand why, doesn't a non web project need configuration and what is the recommended way to configure a .net core console application.
Thanks in advance.
Non-web project may or may not need configuration. But, as you noticed, Visual Studio doesn't scaffold console projects with appsettings.json. Obviously, you can add it to the project as json file. Once you have it, the challenge is to make use of it. I frequently use Configuration object and dependency injection in Entity Framework utilities.
For example,
public static class Program
{
private static IConfigurationRoot Configuration { get; set; }
public static void Main()
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
IServiceCollection services = new ServiceCollection();
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IMyService, MyService>();
IServiceProvider provider = services.BuildServiceProvider();
IMyService myService = provider.GetService<IMyService>();
myService.SomeMethod();
}
public class TemporaryDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
IConfigurationRoot configuration = configBuilder.Build();
DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
builder.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
return new MyDbContext(builder.Options);
}
}
}
This allows me to both run migrations and console-based utilities against DbContext. You don't specify what kind of configuration you are going to need - so this is just one example. But hopefully, you can adjust it to your needs.

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")

Script or utility to change permissions on sub folder of the same name in Windows

I have a parent folder call Projects, under that I have over 400 folders with a separate folder name (eg. Project1, Project2, etc). Under each Project folder there is a standard folder called Management, that should have restricted access but they don't. I would like to change the permissions for this Management subfolder, within all the projects (same permissions). If anyone has an idea on how to do this it would really be appreciated, thanks.
Scott.
Here's a simple Java application that does about what you're asking. If I misinterpreted anything, you can probably fix it but I'm assuming your main task was to figure out how to modify permissions of only specific files.
My solution was to loop through the current directory's fplders (that being the directory that houses all the project files) and then through each project folder to find each management folder. Then, I used the icacls command to remove permission from a specific user.
public static void main(String[] args) {
File file = new File("directory path");
Runtime runtime = Runtime.getRuntime();
for(File pfolder : file.listFiles()) { // Loop through project folders
if(pfolder.isDirectory()) {
for(File mfolder : file.listFiles()) { // Find management folder
if(mfolder.getName().equals("Management")) {
try {
runtime.exec("icacls "+mfolder.getAbsolutePath()+" /deny USERNAME");
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
}
Be sure to modify the initial directory value (make it an absolute path) and the username who's permissions you are removing. Hope this helps!