Using swagger with Blazor WebAssembly in Chrome - google-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();.

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

How to view content of HTML-CSS on an Empty ASP.NET Core Web application

I just started getting aquinted with ASP.NET Core and over all with web development (HTML and CSS).
I created new ASP.NET Core Web Application with the empty template and added two simple HTML files - Index.html and Menu.html. When I run the debugging I can't see anything other than the default "Hello World".
Do I miss anything in the process?
For Html-Css, they are static files, you need to enable StaticFile Middleware by app.UseStaticFiles();.
Try steps below:
Modify Startup.cs to add app.UseStaticFiles();
public class Startup
{
// 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)
{
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
Launch the project and access your static file by entering https://localhost:44387/index.html
In addition, you may get familiar with asp.net core from Introduction to ASP.NET Core

How can I see the code generated for a Razor Page when using Asp.Net Core?

With Asp.Net it was easy to see the code generated by the Razor View Engine: Add a compile error and the error page will give access to the source of the Razor Page.
This changed with Asp.Net Core, which I read somewhere creates the code in memory and does not allow access to that code easily.
Question: Someone knows a trick how to access generated Razor source code with Asp.Net Core?
Add the following class to your ASP.NET Core MVC project:
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
public class CustomCompilationService : DefaultRoslynCompilationService, ICompilationService
{
public CustomCompilationService(ApplicationPartManager partManager,
IOptions<RazorViewEngineOptions> optionsAccessor,
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
ILoggerFactory loggerFactory)
: base(partManager, optionsAccessor, fileProviderAccessor, loggerFactory)
{
}
CompilationResult ICompilationService.Compile(RelativeFileInfo fileInfo,
string compilationContent)
{
return base.Compile(fileInfo, compilationContent);
}
}
Override the ICompilationService added by MVC with the above class;
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<ICompilationService, CustomCompilationService>();
}
Set a break point on the Compile method of CustomCompilationService and view compilationContent.
Notes
View lookups are case sensitive. If your controller routing seeks a view named Index (Index.cshtml) but you've named your view file index (index.cshtml), you'll receive an exception:
InvalidOperationException: The view 'Index' was not found.
Artificial Stupidity provided the correct answer for ASP.NET core 1.x. For version 2.0 of the framework, one can instead use a custom razor template engine:
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
public class CustomMvcRazorTemplateEngine : MvcRazorTemplateEngine
{
public CustomMvcRazorTemplateEngine(RazorEngine engine, RazorProject project) : base(engine, project)
{ }
public override RazorCSharpDocument GenerateCode(RazorCodeDocument codeDocument)
{
RazorCSharpDocument razorCSharpDocument = base.GenerateCode(codeDocument);
// Set breakpoint here for inspecting the generated C# code in razorCSharpDocument.GeneratedCode
// The razor code can be inspected in the Autos or Locals window in codeDocument.Source._innerSourceDocument._content
return razorCSharpDocument;
}
}
Then override the RazorTemplateEngine of the framework:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<RazorTemplateEngine, CustomMvcRazorTemplateEngine>();
}
In version 2.1 of ASP.NET Core, the RazorTemplateEngine seems to be legacy, and the above mechanism does not work anymore. The changes may have to do with the move towards precompilation of razor views, but since I am not involved in the development, I can only guess at the developers' motives.
I would now recommend inspecting the precompiled views in the **.Views.dll generated at build or publish time, depending on your project settings. I personally use Telerik's JustDecompile for this purpose.
If you really need to have a programmatic solution, you can hook into the RazorProjectEngine with a custom phase:
using Microsoft.AspNetCore.Razor.Language;
namespace Econet.PAG.UI
{
internal class DebugRazorEnginePhase : IRazorEnginePhase
{
public RazorEngine Engine { get; set; }
public void Execute(RazorCodeDocument codeDocument)
{
RazorCSharpDocument razorCSharpDocument = codeDocument.GetCSharpDocument();
// Set breakpoint here for inspecting the generated C# code in razorCSharpDocument.GeneratedCode
// The razor code can be inspected in the Autos or Locals window in codeDocument.Source._innerSourceDocument._content
}
}
}
and register it in the creation of the RazorProjectEngine
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton(s =>
{
var fileSystem = s.GetRequiredService<RazorProjectFileSystem>();
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder =>
{
RazorExtensions.Register(builder);
// Roslyn + TagHelpers infrastructure
var metadataReferenceFeature = s.GetRequiredService<LazyMetadataReferenceFeature>();
builder.Features.Add(metadataReferenceFeature);
builder.Features.Add(new CompilationTagHelperFeature());
// TagHelperDescriptorProviders (actually do tag helper discovery)
builder.Features.Add(new DefaultTagHelperDescriptorProvider());
builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
builder.Phases.Add(new DebugRazorEnginePhase());
});
}
Note that except for the line adding the custom phase, the code inside AddSingleton is copied from Microsoft.Extensions.DependencyInjection.MvcRazorMvcCoreBuilderExtensions.AddRazorViewEngineServices(IServiceCollection services) in the Microsoft.AspNetCore.Mvc.Razor sources.
In a simple Console Application:
using Microsoft.AspNetCore.Razor.Language;
class Program
{
static void Main(string[] args)
{
var sourceDocument = RazorSourceDocument.Create("Hello world", "");
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var engine = RazorEngine.Create();
engine.Process(codeDocument);
var csharpDocument = codeDocument.GetCSharpDocument();
var csharp = csharpDocument.GeneratedCode;
Console.WriteLine(csharp);
}
}
Output is:
#pragma checksum "" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "7b502c3a1f48c8609ae212cdfb639dee39673f5e"
// <auto-generated/>
#pragma warning disable 1591
namespace Razor
{
#line hidden
public class Template
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral("Hello world");
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591

Strategy for accessing an application-wide setting on the client in a .NET Core web app

We are in the process of re-writing one of our applications using ASP.NET Core. The architecture we're trying for has a Web API running on a different URL from the presentation. The root URL for this API will change in different environments, of course, so I'm trying to figure out how I can set up configuration and access to the Web API root URL in the JavaScript that requires it for retrieving data. For example, say I have an AJAX call to fetch some data from the API:
$.ajax({
dataType: "json",
url: "http://this.url.will.change/api/whatever", //this will change!
success: function(response) {
//load the items
}
});
I've set up appsettings.json files for various build/deploy scenarios and have them reading and injecting nicely, so I can store the URL there.
{
"Data": {
"DefaultConnection": {
"ConnectionString": "whatever"
}
},
"AppSettings": {
"ApiRootUrl": "http://apiroot/api/"
}
}
I considered writing a UrlHelper extension to provide the Web API root, but I don't think there's a way to inject the IOptions object into a static extension method. So, my question is really this: How can I make a configuration setting globally available in my CSHTML and JavaScript?
Update your Startup.cs like below
public class Startup {
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) {
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton(_ => Configuration);
}
}
Then on your controller you can inject configuration like this
public class ConfigurationController : Controller {
private readonly IConfigurationRoot config;
public ConfigurationController (IConfigurationRoot config) {
this.config = config;
}
public string Test() {
return config.Get<string>("AppSettings:ApiRootUrl");
}
}
We've used to create a special configuration controller which was responsible for creating a dynamic javascript file from selected configurations settings. You can inject IOptions to the controller. Then from the options you can construct a new custom configuration object which will hold only the properties you want to expose (you probably don't want to expose anything like connection string to your db).
Use a json library (like json.net) to serialize this custom configuration object to a JSON string and create file content out of it like
string fileContent = "var globalConf =" + JsonConvert.SerializeObject(configObject);
Convert the string to array of bytes and return it as FileContentResult.
We were also setting some cache headers so the browser didn't hit the controller each time and used cache.
Of course you need to setup routing o the call to specific URL will hit your controller and return the javascript file you have dynamically created. You can reference it on a website using usual script tag.
As for the server side rendering you can always include IOptions in the model (or create a new model which will wrap both options and the original model)

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