Specify file provider for RazorViewEngine in ASP.NET Core 6.0 - razor

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);
});

Related

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

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

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();.

Saving Data locally on a .net Standard app

I want to serialize data on my .net Standard app into a local file and I would like to avoid sqlite if possible.
The standard recommendation for cross plattform app seems to have been PCL Storage, but according to this link, PCL Storage is not maintained anymore, offers no .net Standard support and the alternative PCLExt is not mature.
Can you tell me if it is possible to simply serialize my data e.g. with json?
Tank you very much!
You do not have complete access over the OS's filesystem and platform-specific features like Android's ContentResolver, but for basic file read/write within your app's sandbox (or external filesystem if your app has access to it) .NetStandard 2.0 works fine, and thus works for storing and retrieving text-based files for serializing/deserializing Json.
Example, if you have a Xamarin.Forms based solution and add a .NetStandard 2.0 library project to the solution and also add Newtonsoft.Json to it. You could create these functions in it:
.NetStandard library functions:
public static class Common
{
public static void WriteFile(string fileName, Tuple<string, string> obj)
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
File.WriteAllText(Path.Combine(path, fileName), JsonConvert.SerializeObject(obj));
}
public static Tuple<string, string> ReadFile(string fileName)
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
return JsonConvert.DeserializeObject<Tuple<string, string>>(File.ReadAllText(Path.Combine(path, fileName));
}
}
Now in your Xamarin.Forms project (.NetStandard * Shared project), reference the project/library you created you could do something like this:
ReadWriteCommand = new Command(() =>
{
var someObj = Tuple.Create("Stack", "Overflow");
Common.WriteFile("SushiHangover.txt", someObj);
var readSomeObj = Common.ReadFile("SushiHangover.txt");
if ((someObj.Item1 != readSomeObj.Item1) || (someObj.Item2 != readSomeObj.Item2))
throw new Exception();
});

Create claims identity in Identity 3

Visual Studio 2015 scaffolding uses UserManager<TUser> which cannot be used to create ClaimsIdentity. Does anyone have a working example on how to do this?
The VS2015 scaffolding throws errors:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one
// defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
N.B.: I have added properties to ApplicationUser which do not conflict with IdentyUser.
UserManager has changed in the MVC6 version. You will need to modify your code...
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager) {
var authenticationType = "Put authentication type Here";
var userIdentity = new ClaimsIdentity(await manager.GetClaimsAsync(this), authenticationType);
// Add custom user claims here
return userIdentity;
}
.net core
the answer has changed, per here and here, both authors state the use UserClaimsPrincipalFactory<ApplicationUser> which is the default implementation for core 2.2. The first article says that the method you are looking for has moved. However, as stated you must register your implementation of UserClaimsPrincipalFactory in services like so and a sample class implementation is below. Please take that we have to register MyUserClaimsPrincipalFactory so our service collection knows where to find it. Which means in the constructor of SignInManager<ApplicationUser> it is also referring to IUserClaimsPrincipalFactory<ApplicationUser> but service will resolve it:
services
.AddIdentity<ApplicationUser, ApplicationRole>()
.AddClaimsPrincipalFactory<MyUserClaimsPrincipalFactory>() // <======== HERE
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, MyUserClaimsPrincipalFactory>();
And here is is the class below:
public class MyUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
public MyUserClaimsPrincipalFactory(UserManager<ApplicationUser> userManager,
IOptions<IdentityOptions> optionsAccessor)
: base(userManager, optionsAccessor)
{
}
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
identity.AddClaim(new Claim("ContactName", "John Smith"));
return identity;
}
}

Configuration of asp.net core using settings

I'm evaluating asp.net core and .net core and I'm not yet sure about some things. In the past it was possible to configure many components using the web.config out of the box.
To name some examples:
There was the membership-provider and I could implement many providers but I was able ton configure later which provider should be used. This was dependend of the use-case. Now I should use asp.net identity - but I can only find configurations that are performed in sourcecode.
Same for authentication. I can define "CookieAuthentication" and have to set the name, loginpath or the timeout within sourcecode. In the past I was able to set timeout, etc... via web.config.
Is there any way to configure partially these things out of the box from a config-file? Or is this not supported anymore and I have to implement this configuration on my own? In the past this was a really comfortable way.
In ASP.NET Core, Web.config file is used ONLY for IIS configuration, you cannot use it for application configuration, but there are new, better, more flexible configuration options that you can use.
There are multiple configuration sources that you can use, but in this example I'm using json. These examples are from working code in my SimpleAuth project.
You can configure things in startup from configuration files.
First you add a config file in json format that maps to your class. You can see my example class here, and the json file it maps from here
builder.AddJsonFile("simpleauthsettings.json", optional: true);
Then, in the ConfigureServices method you configure your class to be wired up from the config system as shown
services.Configure<SimpleAuthSettings>(Configuration.GetSection("SimpleAuthSettings"));
Then you add an IOptions accessor of your class to the method signature of the Configure method in the Startup.cs
The Dependency Injection will inject it into that method for you so you can use it there to configure things. Specifically I'm setting the cookie authentication scheme and name from my settings object.
The noteworthy part is that you can add whatever you want to the Configure method signature, and as long as it is something that has been registered in the ConfigureServices method, the DI will be able to inject it for you.
public class Startup
{
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
// this file is the custom configuration file to hydrate my settings from
builder.AddJsonFile("simpleauthsettings.json", optional: true);
....
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
....
services.Configure<SimpleAuthSettings>(Configuration.GetSection("SimpleAuthSettings"));
....
}
// note that the DI can inject whatever you need into this method signature
// I added IOptions<SimpleAuthSettings> authSettingsAccessor to the method signature
// you can add anything you want as long as you register it in ConfigureServices
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IOptions<SimpleAuthSettings> authSettingsAccessor
)
{
...
// Add cookie-based authentication to the request pipeline
SimpleAuthSettings authSettings = authSettingsAccessor.Value;
var ApplicationCookie = new CookieAuthenticationOptions
{
AuthenticationScheme = authSettings.AuthenticationScheme,
CookieName = authSettings.AuthenticationScheme,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
LoginPath = new PathString("/Login/Index"),
Events = new CookieAuthenticationEvents
{
//OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
}
};
app.UseCookieAuthentication(ApplicationCookie);
// authentication MUST be added before MVC
app.UseMvc();
}
}