How to load app configuration from appsettings.json in MAUI startup? - configuration

I would need to retrieve my app settings from an appsettings.json file in a MAUI application.
I tagged it a MauiAsset generation action, and I can see it in the assets directory of of the generated apk.
It doesn't seem to be available in ConfigureAppConfiguration and no AddJsonFile extension exists to add it in the application builder.
Should I use another generation action?
What is the good way to retrieve it?
public void Configure(IAppHostBuilder appBuilder)
{
appBuilder
.ConfigureAppConfiguration((app, config) =>
{
// I should be able to work with appsettings here
})
.ConfigureServices(svcs =>
{
})
.UseMauiApp<App>();
}

It looks like the ConfigureAppConfiguration was just added with this PR a couple of days ago. It will probably show up for the next release.
If you really need it now you could add it as an embedded resource and do the deserialization from JSON yourself. If you could wait a bit longer you probably want to do that.

It's possible to get the usual host builder by using the MauiAppBuilder.Host getter.
var builder = MauiApp.CreateBuilder();
builder
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
})
.UseMauiApp<App>()
.Host
.ConfigureAppConfiguration((app, config) =>
{
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
.ConfigureServices((ctx, svcs) =>
{
})
.ConfigureLogging(logging =>
{
logging.AddSerilog();
});
To get app location and appsetings path, use the following:
Assembly CallingAssembly = Assembly.GetEntryAssembly();
Version VersionRuntime = CallingAssembly.GetName().Version;
string AssemblyLocation = Path.GetDirectoryName(CallingAssembly.Location);
string ConfigFile = Path.Combine(AssemblyLocation, "appsettings.json");

Related

Specify file provider for RazorViewEngine in ASP.NET Core 6.0

I'm having a solution with an ASP.NET Core 6 MVC application project and a WebJob (console application)
Both applications are using a common library project where I have IRazorViewToStringRenderer service with views. I want to reuse this service in both applications, WebApp and WebJob. My solution is based on this sample https://github.com/aspnet/Entropy/blob/master/samples/Mvc.RenderViewToString/Program.cs
Here is how I use it:
var viewToStringEngine = ServiceProvider.GetService<IRazorViewToStringRenderer>();
string htmlContent = await viewToStringEngine.RenderToStringAsync<MyView>("~/Views/MyView.cshtml", new MyView());
The problem is RazorViewEngineOptions doesn't have anymore the option to specify the file provider ( in ASP.NET Core 6 )
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProviders.Clear();
options.FileProviders.Add(fileProvider);
});
IRazorViewToStringRenderer service is working fine when is called from the Web App, but is not working from the WebJob. It is only working if the WebJob services contains an IWebHostEnvironment with the ApplicationName as the name of the project where IRazorViewToStringRenderer is implemented, otherwise the views cannot be found.
How to specify file provider for the RazorViewEngine ? ( github sample )
WebJob service configuration:
private static ServiceCollection ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(Configuration);
var applicationEnvironment = PlatformServices.Default.Application;
services.AddSingleton(applicationEnvironment);
services.AddSingleton<Microsoft.AspNetCore.Hosting.IWebHostEnvironment>(new WebJobHostEnvironment
{
ApplicationName = Assembly.GetEntryAssembly().GetName().Name,
//ApplicationName = typeof(IRazorViewToStringRenderer).Assembly.GetName().Name,
});
var listener = new DiagnosticListener("Microsoft.AspNetCore");
services.AddSingleton<DiagnosticListener>(listener);
services.AddSingleton<DiagnosticSource>(listener);
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
services.AddSingleton<ILoggerFactory, LoggerFactory>(sp => new LoggerFactory());
services.AddMvcCore().AddRazorViewEngine();
services.AddCommonRazorEngine(Configuration);
return services;
}
RazorServiceCollectionExtension.cs
public static class RazorServiceCollectionExtension
{
public static void AddCommonRazorEngine(this IServiceCollection services, IConfiguration configuration)
{
//var fileProvider = new EmbeddedFileProvider(typeof(RazorViewToStringRenderer).Assembly);
// FileProviders property is not available anymore
services.Configure<RazorViewEngineOptions>(options =>
{
//options.FileProviders.Add(fileProvider);
});
services.AddScoped<IRazorViewToStringRenderer, RazorViewToStringRenderer>();
}
}
Edit
For others searching a similar solution, I updated my github sample
I encountered this problem myself, and it appears that the functionality has been moved to an external package. I was able to work around this by following the instructions located here and then amending them for my purposes:
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-compilation?view=aspnetcore-6.0&tabs=visual-studio
I.e. install the package: Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
Then you can change the appropriate file provider by using the code:
services.Configure<MvcRazorRuntimeCompilationOptions>(options =>
{
options.FileProviders.Clear();
options.FileProviders.Add(fileProvider);
});

Using swagger with Blazor WebAssembly in Chrome

I have been playing with blazor wasm and hit a problem with using swagger on the server project that is created by the default project. This problem only happens in Chrome, and not Edge.
The problem is very odd, I have set up swagger and when I go to https://localhost:44323/swagger/index.html I get a working swagger page, but when I try to use any of my controllers or even the default weather one it just runs and sits there saying loading forever. If I put break point in the controller it does get hit.
If I open the browser debug tools, and stop it it will say "Paused in the debuger" the browser will flicker and then it will show results.
If i go to https://localhost:44323/WeatherForecast it runs and give the correct response.
I added the project in Visual studio, going new project => selecting blazor app => Blazor WebAssembly App, and selecting AspNetCore hosted, and Progressive Web Application.
I have installed via nuget
Swashbuckle.AspNetCore.SwaggerGen v5.5.0
Swashbuckle.AspNetCore.SwaggerUI v5.5.0
My entire start up class is
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// 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)
{
services.AddControllersWithViews();
services.AddRazorPages();
//Added Swagger
services.AddSwaggerGen(setUpAction =>
{
setUpAction.SwaggerDoc("PetStoreAPI", new OpenApiInfo { Title = "PetStore API", Version = "1" });
//Add comments, to get this to work you need to go into project properties, build tab, then select "XML Documentation file"
var xmlCommentFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlCommentFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentFile);
setUpAction.IncludeXmlComments(xmlCommentFullPath);
});
}
public void ConfigureContainer(ContainerBuilder builder)
{
// Add any Autofac modules or registrations.
// This is called AFTER ConfigureServices so things you
// register here OVERRIDE things registered in ConfigureServices.
//
// You must have the call to `UseServiceProviderFactory(new AutofacServiceProviderFactory())`
// when building the host or this won't be called.
builder.RegisterModule(new Autofac.AutofacConfiguration());
builder.AddAutoMapper(typeof(Startup).Assembly);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
//Added Swagger
app.UseSwagger();
app.UseSwaggerUI(setupAction =>
{
setupAction.SwaggerEndpoint("/swagger/PetStoreAPI/swagger.json", "PetStore API");
});
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
}
I encounter similar issue, however I was able to bring it up by adding services.AddControllers(); right after services.AddControllersWithViews(); and services.AddRazorPages();.

Manipulating the controller map for an API application

I have a set of extensions in my application (yii2-advanced) that have to be maintained in their own repos. To make them as detached as possible, they register as modules on their own and adds to the menu and migrations like this:
vendor\ext\Ext.php
namespace vendor\ext;
use yii\base\BootstrapInterface;
class ext implements BootstrapInterface
{
public function bootstrap($app)
{
$this->addToMenu($app);
$this->addToModules($app);
$app->params['migrations'] = array_merge($app->params['migrations'], ['#vendor/ext/migrations']);
}
private function addToMenu($app)
{
...
}
private function addToModules($app)
{
$app->setModule('ext', ['class' => 'vendor\ext\Module']);
}
}
vendor\ext\composer.json
"extra": {
"bootstrap": "vendor\\ext\\Ext"
},
This works very well, with the controllers in vendor\ext\controllers\. I have an extra application created as a REST API, and I need that application to access vendor\ext\api\ instead of vendor\ext\controllers\.
So if you'd access example.com/ext/controller you'd get vendor\ext\controllers\controller::index(), but if you'd access api.example.com/ext/controller you'd get vendor\ext\api\controller::index().
I've read trough the docs a lot to solve the bootstrapping functionalities that I have, but I can't seem to figure out this one.
Perhaps you can map the controller:
https://www.yiiframework.com/doc/guide/2.0/en/rest-routing#routing
As per example in that documentation:
[
'class' => 'yii\rest\UrlRule',
'controller' => ['u' => 'user'],
]
You could use bootstrapping to create the rules in the UrlManager.
Usually I use strict parsing in Rest API's, so we have to define our rules explicitly anyway.
I solved it by adding each API version as another module and register it with the corresponding version module of my API application.
Now I can develop each module with its supported api versions in a separate repository and everything registers automatically.
Posting the bootstrap code below if anyone needs do do something similar.
public $supported_api_versions = [];
public function bootstrap($app)
{
$this->addToMenu($app);
if($app->id == 'app-frontend')
{
$this->addToModules($app);
}
elseif($app->id == 'app-backend')
{
// Add code here if the module should be available in backend application
}
elseif($app->id == 'app-api')
{
foreach ( $this->supported_api_versions as $api ) {
$module = $app->getModule($api);
$module->setModule($this->name, ['class' => 'vendor\\'.$this->name.'\api\\'.$api.'\Module']);
}
}
elseif($app->id == 'app-console')
{
$app->params['migrations'] = array_merge($app->params['migrations'], ['#vendor/'.$this->name.'/migrations']);
}
}

How do I use Castle Windsor to create a RavenDB session with client version > 3.0.3660?

I am using Castle Windsor v3.4.0 to create a RavenDB document session instance but when I use a RavenDB client version later than 3.0.3660 I get this error when calling the Store method:
Castle.MicroKernel.ComponentNotFoundException: 'No component for supporting the service System.Net.Http.HttpMessageHandler was found'
Here is the smallest piece code I can come up with that reproduces the error:
using Castle.Facilities.TypedFactory;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Raven.Client;
using Raven.Client.Document;
public class Program
{
public static void Main()
{
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component
.For<IDocumentStore>()
.ImplementedBy<DocumentStore>()
.DependsOn(new { Url = "http://localhost:8081", DefaultDatabase = "Test" })
.OnCreate(x => x.Initialize())
.LifeStyle.Singleton,
Component
.For<IDocumentSession>()
.UsingFactoryMethod(x => x.Resolve<IDocumentStore>().OpenSession())
.LifeStyle.Transient);
using (var documentSession = container.Resolve<IDocumentSession>())
{
documentSession.Store(new object());
documentSession.SaveChanges();
}
}
}
Here's what I believe is happening. A change was made to the RavenDB client after v3.0.3660 that changed how the HttpMessageHandler is created in the HttpJsonRequest class:
https://github.com/ravendb/ravendb/commit/740ad10d42d50b1eff0fc89d1a6894fd57578984
I believe that this change in combination with my use of the TypedFactoryFacility in my Windsor container is causing RavenDB to request an instance of HttpJsonRequestFactory and it's dependencies from Windsor rather than using it's own internal one.
How I can change my code to avoid this problem so that I can use a more recent version of the RavenDB client?
Given your MVCE, Windsor is set up to inject object's properties. So, when creating the DocumentStore, Castle is trying to find a value for the HttpMessageHandlerFactory property and is failing since nothing is configured for that particular type.
I was able to get your example to work (at least, it got to inserting the data into my non-existing server) by just filtering out that property:
container.Register(
Component.For<IDocumentStore>()
.ImplementedBy<DocumentStore>()
.DependsOn(new { Url = "http://localhost:8081", DefaultDatabase = "Test" })
.OnCreate(x => x.Initialize())
.PropertiesIgnore(p => p.Name == nameof(DocumentStore.HttpMessageHandlerFactory))
.LifeStyle.Singleton);
Alternatively, if you have a value for it, you could add it to the object passed to DependsOn().

Razor page can't see referenced class library at run time in ASP.NET Core RC2

I started a new MVC Web Application project for the RC2 release and I'm trying to add a class library as a project reference.
I added a simple class library to my project and referenced it and got the following in the project.json file:
"frameworks": {
"net452": {
"dependencies": {
"MyClassLibrary": {
"target": "project"
}
}
}
},
I can use this library in any of the Controllers and the Startup.cs files without any trouble but I get the following error at run time when I try and use the library from a Razor page:
The name 'MyClassLibrary' does not exist in the current context
Output.WriteLine(MyClassLibrary.MyStaticClass.SomeStaticString);
It's weird because I'm getting intellisense for the class library when I'm editing the Razor page, and I can't find anything that says you can't use project references from here.
I thought it was hard enough getting this running under RC1 with the "wrap folder" in the class library project but this has me stumped.
A workaround has been posted on the issue page (cred to pranavkm and patrikwlund)
https://github.com/aspnet/Razor/issues/755
Apparently you need to explicitly add references to Razor compilation using RazorViewEngineOptions.CompilationCallback.
Add the following to your ConfigureServices method in your Startup class:
var myAssemblies = AppDomain.CurrentDomain.GetAssemblies().Select(x => MetadataReference.CreateFromFile(x.Location)).ToList();
services.Configure((RazorViewEngineOptions options) =>
{
var previous = options.CompilationCallback;
options.CompilationCallback = (context) =>
{
previous?.Invoke(context);
context.Compilation = context.Compilation.AddReferences(myAssemblies);
};
});
I had to filter out dynamic assemblies to avoid this runtime exception:
The invoked member is not supported in a dynamic assembly.
This worked for me:
var myAssemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => !x.IsDynamic)
.Select(x => Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(x.Location))
.ToList();
services.Configure((Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions options) => {
var previous = options.CompilationCallback;
options.CompilationCallback = (context) => {
previous?.Invoke(context);
context.Compilation = context.Compilation.AddReferences(myAssemblies);
};
});