Add new FileServer locations after startup (edit middleware after startup) - c#

My web application needs to let an admin user add and remove served folders from a .net core 2 app. I have found a way to provide a list of served folders, but I can't find a way to dynamically add or remove them once the app has been configured.
How do I re-run the configure function from within the application? Alternatively, how do I add or remove UseFileServer() configurations within an already-running service?
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseDeveloperExceptionPage();
app.UseMvc();
//get these dynamically from the database
var locations = new Dictionary<string, string>{
{#"C:\folder1", "/folder1"},
{#"D:\folder2", "/folder2"}
};
foreach (var kvp in locations)
{
app.UseFileServer(new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(
kvp.Key
),
RequestPath = new PathString(kvp.Value),
EnableDirectoryBrowsing = true
});
}
}
}
I'm using .net core 2.0.0-preview2-final.

You may want to dynamically inject the FileServer middleware based on your settings.
There is an example project on Microsoft's Chris Ross' Github: https://github.com/Tratcher/MiddlewareInjector/tree/master/MiddlewareInjector
You'll have to add the MiddlewareInjectorOptions, MiddlewareInjectorMiddleware and MiddlewareInjectorExtensions classes from the aforementioned repo to your project.
Then, in your Startup class, register the MiddlewareInjectorOptions as a singleton (so it's available throughout your application) and use the MiddlewareInjector:
public class Startup
{
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<MiddlewareInjectorOptions>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
var injectorOptions = app.ApplicationServices.GetService<MiddlewareInjectorOptions>();
app.UseMiddlewareInjector(injectorOptions);
app.UseWelcomePage();
}
}
Then, inject the MiddlewareInjectorOptions wherever you want and configure the middleware dynamically, like this:
public class FileServerConfigurator
{
private readonly MiddlewareInjectorOptions middlewareInjectorOptions;
public FileServerConfigurator(MiddlewareInjectorOptions middlewareInjectorOptions)
{
this.middlewareInjectorOptions = middlewareInjectorOptions;
}
public void SetPath(string requestPath, string physicalPath)
{
var fileProvider = new PhysicalFileProvider(physicalPath);
middlewareInjectorOptions.InjectMiddleware(app =>
{
app.UseFileServer(new FileServerOptions
{
RequestPath = requestPath,
FileProvider = fileProvider,
EnableDirectoryBrowsing = true
});
});
}
}
Note that this MiddlewareInjector can inject just a single middleware, so your code should call UseFileServer() for each path you want to serve.
I've created a Gist with the required code: https://gist.github.com/michaldudak/4eb6b0b26405543cff4c4f01a51ea869

Related

How to access Singleton directly from ConfigureServices without BuildServiceProvider?

How to access singletons from ConfigureServices? There's a reason that I can't use appsettings for few configs.
For example, let's say that I want to set swagger title and version from database, not appsettings. My actual problem is I want to set consul address from my database. The problem should be the same, that I need to access my database in ConfigureServices. I have a custom extension like this:
public static IServiceCollection AddConsulConfig(this IServiceCollection services, string address)
{
services.AddSingleton<IConsulClient, ConsulClient>(p => new ConsulClient(consulConfig =>
{
consulConfig.Address = new Uri(address);
}));
return services;
}
I call it from startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IGlobalParameter, GlobalParameterManager>();
//I want to use IGlobalParameter here directly but without BuildServiceProvider
//This part is the problem
var service = ??
var varTitle = service.GetById("Title").Result.Value;
var varConsulAddress = service.GetById("ConsulAddress").Result.Value;
services.AddConsulConfig(varConsulAddress);
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = varTitle, Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// I can use it here or in the controller no problem
var service = app.ApplicationServices.GetRequiredService<IGlobalParameter>();
var varTitle = service.GetById("Title").Result.Value;
var varConsulAddress = service.GetById("ConsulAddress").Result.Value;
}
I DO NOT want to use BuildServiceProvider as it will make multiple instances, even visual studio gives warning about it. referenced in How to Resolve Instance Inside ConfigureServices in ASP.NET Core
I knew the existence of IConfigureOptions from the following link
https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/#the-new-improved-answer
But, I can't seem to find how exactly do I use that in ConfigureService:
public class ConsulOptions : IConfigureOptions<IServiceCollection>
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public ConsulOptions(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Configure(IServiceCollection services)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var provider = scope.ServiceProvider;
IGlobalParameter globalParameter = provider.GetRequiredService<IGlobalParameter>();
var ConsulAddress = globalParameter.GetById("ConsulAddress").Result.Value;
services.AddConsulConfig(ConsulAddress);
}
}
}
Set it in startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IGlobalParameter, GlobalParameterManager>();
services.AddSingleton<IConfigureOptions<IServiceCollection>, ConsulOptions>(); // So what? it's not called
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// IConsulClient is still null here
}
Any solution to how do I achieve this?
Thank you Jeremy, it's as simple as that. I don't know why I spend way too much time figuring out how to set this
The solution is to add singleton :
services.AddSingleton<IConsulClient, ConsulClient>(
p => new ConsulClient(consulConfig =>
{
var ConsulAddress = p.GetRequiredService<IGlobalParameter>().GetById("ConsulAddress").Result.Value;
consulConfig.Address = new Uri(ConsulAddress);
}
));

Use ASP.NET Core Web API with an app that's not using MVC

Is it possible to use Web API within a .NET Core application that is not using MVC, but making use of app.Run?
I want to provide a simple API and would rather use Web API than roll my own. I've added in MvcCore in ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddMvcCore(x =>
{
x.RespectBrowserAcceptHeader = true;
}).AddFormatterMappings()
.AddJsonFormatters();
// ...
}
I added a very simple Controller:
[Route("/api/test")]
public class TestController : Controller
{
[HttpGet]
[Route("")]
public async Task<IActionResult> Get()
{
return Ok("Hello World");
}
}
However, when I attempt to hit /api/test my normal processing code within App.Run processes.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var scopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
// ...
app.Run(async (context) =>
{
using (var scope = scopeFactory.CreateScope())
{
var request = new Request(context);
var response = new Response(context);
await scope.ServiceProvider.GetRequiredService<IRequestFactory>().ProcessAsync(request, response);
}
});
}
How do I defer to the Web API if I don't want to handle the request?
In order to configure MVC,you have to add it both in ConfigureServices and Configure like below
ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
Configure
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
So in your code,you have only done the first part.
Also I see that you have used app.Run ,do not use Run if you have
other middlewares to run.app.Run delegate terminates the pipeline and MVC middleware will not get chance to execute
What you have to do is to change the code to app.Map ,refer this article for the user of Run,use and map
So the order of middleware matters. you have to change the code like this
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var scopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
// ...
app.Use(async (context,next) =>
{
using (var scope = scopeFactory.CreateScope())
{
var request = new Request(context);
var response = new Response(context);
await scope.ServiceProvider.GetRequiredService<IRequestFactory>().ProcessAsync(request, response);
}
await next.Invoke()
});
app.UseMvc();
}

ASP.NET Core MVC main project can't reach Controllers in separate assembly

I want to use a separate project to store the Controllers for my test app. Due to how ASP.NET Core is working, you need to call services.AddMvc().AddApplicationPart with the assembly you want to add.
I use Visual Studio 2017 (so no more project.json there)
The problem is I can't reach the assembly I want to include!
I added reference there in my project:
Also, I decided to use polyfill for AppDomian like so (to reach assembly I need):
public class AppDomain
{
public static AppDomain CurrentDomain { get; private set; }
static AppDomain()
{
CurrentDomain = new AppDomain();
}
public Assembly[] GetAssemblies()
{
var assemblies = new List<Assembly>();
var dependencies = DependencyContext.Default.RuntimeLibraries;
foreach (var library in dependencies)
{
if (IsCandidateCompilationLibrary(library))
{
var assembly = Assembly.Load(new AssemblyName(library.Name));
assemblies.Add(assembly);
}
}
return assemblies.ToArray();
}
private static bool IsCandidateCompilationLibrary(RuntimeLibrary compilationLibrary)
{
return compilationLibrary.Name == ("TrainDiary")
|| compilationLibrary.Dependencies.Any(d => d.Name.StartsWith("TrainDiary"));
}
}
But when I used it there wasonly one assembly in the list (only linked to this project itself, not one with Controllers).
Here is my Startup.cs:
public class Startup
{
public IConfigurationRoot Configuration { get; }
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();
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
var assemblies = GetAssemblies(); // I tought I will find there Assmbly I need but no
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddReact();
services.AddMvc();
services.AddLogging();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseReact(config =>
{
});
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
public List<Assembly> GetAssemblies()
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
List<Assembly> currentAssemblies = new List<Assembly>();
foreach (Assembly assembly in assemblies)
{
if (assembly.GetName().Name.Contains("TrainDiary"))
{
currentAssemblies.Add(assembly);
}
}
return currentAssemblies;
}
}
How can I get my project to also lookup controllers in the other project? Why couldn't it see it?
UPD
Tried to make directly like in this exapmle here.
So my code started looks like that:
var controllersAssembly = Assembly.Load(new AssemblyName("TrainDiary.Controllers"));
services.AddMvc()
.AddApplicationPart(controllersAssembly)
.AddControllersAsServices();
And it succeed to get the assembly:
But when I try to call Controller function - it fails!
Controller code:
[Route("Login")]
public class LoginController: Controller
{
[Route("Login/Test")]
public void Test(string name, string password)
{
string username = name;
string pass = password;
}
}
Requests:
UPD2:
For some reason remove Routes helped:
public class LoginController: Controller
{
public void Test(string name, string password)
{
string username = name;
string pass = password;
}
}
So, if sum this up:
1) You need to Add Reference to your separate project with
controllers.
2) Configure services (part):
public void ConfigureServices(IServiceCollection services)
{
var controllersAssembly = Assembly.Load(newAssemblyName("SomeProject.MeetTheControllers"));
services.AddMvc().AddApplicationPart(controllersAssembly).AddControllersAsServices();
}
3) My configure looks like that (pay attention to UseMvcWithDefaultRoute):
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseReact(config =>
{
});
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
So in my case of controller like this you don't need to put Routes attribute here, just call it by Some/Test url
namespace SomeProject.MeetTheControllers.Test
{
using Microsoft.AspNetCore.Mvc;
public class SomeController: Controller
{
public void Test(string name, string notname)
{
string username = name;
string nan = notname;
}
}
}

public void method in Startup.cs not running on build

This is the code in my Startup.cs file and 2 of my three methods are running on build. However I added the bottom method public void PackageRequestDataAccess and for some reason its not running.
namespace Company.Shipping.Service
{
public class Startup
{
private IHostingEnvironment _environment;
private IConfigurationRoot _configurationRoot;
public Startup(IHostingEnvironment env)
{
_environment = env;
}
public void ConfigureServices(IServiceCollection services)
{
//Code Ran successfully here
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime)
{
//Code running successfully here
}
//Method below not running
public void PackageRequestDataAccess(Common.ServiceHost.WebHost.ServiceConfiguration configuration, IServiceCollection services)
{
IMongoCollection<PackageDataEntity> _reqrespcollection;
MongoDBRepository<PackageDataEntity> _repo = new MongoDBRepository<PackageDataEntity>(configuration.ConnectionStrings["MongoDB"]);
_reqrespcollection = _repo.Collection;
int _expiry = Convert.ToInt32(configuration.Settings["ShippingReqRespDataTTL"]);
TimeSpan _ttl = new TimeSpan(0, 0, _expiry);
CreateIndexOptions index = new CreateIndexOptions();
index.ExpireAfter = _ttl;
var _key = Builders<PackageDataEntity>.IndexKeys.Ascending("RequestSentOn");
_reqrespcollection.Indexes.CreateOneAsync(_key);
}
}
}
I need to run all these three methods whenever the application starts.
As per the MSDN docs available here only the Configure and ConfigureServices are called during startup.
The Startup class must include a Configure method and can optionally
include a ConfigureServices method, both of which are called when the
application starts.
In you case, may be you can add your logic to any one of this method or just call the method from the above method(s).

ASP.NET 5 - Access to Dependency Container in Startup.Configure

I want to access my Options instance which is added as singleton in ConfigureServices. Here is my code:
public class Startup
{
private IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton((serviceProvider) => ConfigurationBinder.Bind<Options>(Configuration));
}
public void Configure(IApplicationBuilder app)
{
var root = ""; // I want to access my Options instance to get root from it
var fileServerOptions = new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(root)
};
app.UseFileServer(fileServerOptions);
}
}
My question is how to access instance of Options in Configure method to set root variable.
As suggested in How to use ConfigurationBinder in Configure method of startup.cs, the runtime can inject the options directly into the Configure method:
public void Configure(IApplicationBuilder app, Options options)
{
// do something with options
}
According to Joe Audette's comment this is the solution:
var options = app.ApplicationServices.GetService<Options>();

Categories