ASP.NET 5 beta8 app with virtual directories/applications - asp.net-core

Since ASP.NET 5 beta8 we are experiencing problems using virtual directories and/or sub applications.
We want (for the time beeing) to serve images from a virtual directory or a "sub application". However we only get 404 errors when trying to use a virtual directory and 502.3 errors when using a "sub application".
The server is running IIS 8.0. The Application Pools for the site and the "sub application" is set to "No Managed Code".
Using the same configuration of virtual dirs/apps on another site running the "old" ASP.NET 4 version of our site works like expected.
The problem came after upgrading to beta8, so we assume it has something to do with the HttpPlatformHandler.
Are we missing something or is this a bug?
EDIT:
To clarify, the ASP.NET5 application works just fine. It is only the content from the virtual dirs/apps that cannot be accessed.
The HttpPlatformHandler is installed on the server.
Here is our current Startup.cs
public class Startup
{
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.ConfigureXXXXXXIdentityServices(); // Custom identity implementation
services.AddMvc(options =>
{
options.OutputFormatters
.Add(new JsonOutputFormatter(new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}));
});
services.AddSqlServerCache(options =>
{
options.ConnectionString = "XXXXXX";
options.SchemaName = "dbo";
options.TableName = "AspNet5Sessions";
});
services.AddSession();
var builder = new ContainerBuilder();
builder.RegisterModule(new AutofacModule());
builder.Populate(services);
var container = builder.Build();
return container.Resolve<IServiceProvider>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.MinimumLevel = LogLevel.Debug;
loggerFactory.AddConsole();
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseFileServer(new FileServerOptions
{
RequestPath = new PathString("/gfx"),
FileProvider = new PhysicalFileProvider(#"\\webdata2.XXXXXX.se\webdata\gfx"),
EnableDirectoryBrowsing = false
});
app.UseFileServer(new FileServerOptions
{
RequestPath = new PathString("/files"),
FileProvider = new PhysicalFileProvider(#"\\webdata2.XXXXXX.se\webdata"),
EnableDirectoryBrowsing = false
});
}
else
{
app.UseExceptionHandler("/Error/Index");
}
app.UseIISPlatformHandler();
app.UseStaticFiles();
app.UseIdentity();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
The app.UseFileServer() statements works on our dev machines, but cannot be used on the server, unless there is a way to specify credentials. (haven't found a way to do that... (yet...))

Got it "working".
Dropped all virtual directories and/or applications.
Changed the Application Pool user to a user that had rights to read the file shares on the other machine.
Added app.UseFileServer() to all environments for the required paths.
Feels like there should be an option to pass Network Credentials to the UseFileServer method...

The Hosting model changed in beta 8, meaning that you need to install the new HttpPlatformHandler module as an administrator.
See Change to IIS hosting model

Related

Asp.Net Core 6 Scoped Filter inject UserManager

I'm working on linking Twilio Verify into an Asp.Net Core web site. I'm pretty sure I have to figure out how to access UserManager in the filter (constructor). But, I don't know how to access it.
My VerifyFilter:
public class VerifyFilter : IAsyncResourceFilter
{
private readonly UserManager<ApplicationUser> _manager;
public VerifyFilter(UserManager<ApplicationUser> manager)
{ _manager = manager; }
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{ // cut just to make listing a bit shorter }
}
My program.cs file currently looks like this:
builder.Services.AddScoped<VerifyFilter>();
What I don't know is how I get the UserManager so I can pass it in.
I have another scoped right above it and I had to do this to get Verification to work.
Configuration.Twilio twilio = new Configuration.Twilio();
builder.Services.AddScoped<IVerification>(t => new Verification(twilio));
So I'm sure it's just a matter of figuring out how to get UserManager to pass in as a constructor, but with MinimalAPI and .Net Core 6.0, I don't know where it is at.
My entire program.cs file:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using test4.Data;
using test4.Filters;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services
.AddDefaultIdentity<IdentityUser>(
options =>
{
// These will get updated to production versions later.
options.SignIn.RequireConfirmedAccount = true;
options.Password.RequiredLength = 1;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddScoped<VerifyFilter>();
builder.Services.AddControllers(op =>
{
op.Filters.Add<VerifyFilter>();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
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();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Any suggestions?
Thanks,
Nick
You could try to register the filter via the following code:
//register the Identity service
//register the custom filters.
builder.Services.AddControllers(op =>
{
op.Filters.Add<VerifyFilter>();
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

Deployed WebAssembly Blazor application doesn't route authentication properly, but locally working

I created a 'normal' WebAssembly Blazor client and server application.
I decided later on to add authentication, so I followed the steps at this address:
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-identity-server?view=aspnetcore-3.1&tabs=visual-studio
ending up with this startup code in the server part of the Blazor WebAssembly application:
public class Startup
{
private readonly IWebHostEnvironment _environment;
private readonly IConfiguration _configuration;
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
_environment = environment;
_configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
if (_environment.IsDevelopment())
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
_configuration.GetConnectionString("LocalEnvironment")));
}
else
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
_configuration.GetConnectionString("CloudEnvironment")));
}
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;
options.User.RequireUniqueEmail = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = true;
options.Password.RequireDigit = true;
});
services.AddTransient<IPasswordValidator<ApplicationUser>, CustomPasswordPolicy>();
services.AddTransient<IUserValidator<ApplicationUser>, CustomUsernameEmailPolicy>();
services.AddTransient<IProfileService, ProfileService>();
services.AddHttpContextAccessor();
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(60);
});
}
public void Configure(IApplicationBuilder app, ApplicationDbContext db,
UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager)
{
if (_environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
db.Database.EnsureCreated();
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
IdentityDataInitializer.SeedTestData(userManager, roleManager);
}
}
The resulting application works perfectly when in the Development environment (both in Kestrel and IIS Express, but when I deploy it to an Azure Service App, the authentication part, and only that one, doesn't work properly.
For example: if I click the Login button in the home page, when I'm local there's a jump to the page:
https://localhost:5001/Identity/Account/Login?ReturnUrl=...
That's the correct path, because, moreover, after logging in, I'm redirected correctly to the home page.
But when I click the same button on the deployed application, I see the address becoming first
'.../authentication/login'
and after a few moments, going to
'.../connect/authorize?client_id=Test1.Client&redirect_uri=...'
that's a not existing page.
Personally, I don't even understand, at the moment, if it's a server or client problem, or just the configuration of the service app on Azure...
Please, feel free to ask for other code, or anything that can help.
Thank you in advance.
/connect/authorize is one of the endpoints that IdentityServer listens for and it is the first URL that the application/client should redirect to when the user is about to authenticate.
One way to tell if IdentityServer is up and running is to go to this URL https://yourdomain.com/.well-known/openid-configuration
This URL Should always succeed.
When you deploy to the cloud and Azure Service App, one thing is to make sure you understand where HTTPS is terminated, is it in your application or in Azure Service? If it is not terminated in the application it-self, then it might be that the public URL is HTTPS but what your application sees is HTTP.
Some links to follow:
https://securecloud.blog/2020/07/23/unobvious-stuff-about-azure-services-app-service-tls-termination/
https://www.hanselman.com/blog/SecuringAnAzureAppServiceWebsiteUnderSSLInMinutesWithLetsEncrypt.aspx

Why my View localization failed in .net core?

I followed the Globalization and localization tutorial by Microsoft in https://youtu.be/IAegSBI5lPk?t=2277
And here is my code
startup.cs:
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix,
options => options.ResourcesPath = "Resources");
services.AddLocalization(options => options.ResourcesPath = "Resources");
}
// 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.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
var SupportedCultures = new List<CultureInfo> {
new CultureInfo("en"),
new CultureInfo("zh-Hans"),
new CultureInfo("zh-Hant")
};
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en"),
SupportedCultures = SupportedCultures,
SupportedUICultures = SupportedCultures
};
app.UseRequestLocalization(options);
app.UseStaticFiles();
app.UseMvc();
}
index.cshtml:
#page
#inject IViewLocalizer Localizer
#using Microsoft.AspNetCore.Mvc.Localization
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
#Localizer["TitleString"]
Here is the file list:
And here is the detial of Index.en.resx
Finally when it runs,it turns out to be this:
The #Localizer["TitleString"] do not display 'HelloWorld' correctly but display 'TitleString'.
I think maybe I am missing something,but I don't konw what is the problem.
Would you please like to help me?Thank you.
Resource file naming and placement is crucial for correct locating of corresponding resources. You set ResourcesPath to Resources directory. You should put it not under Pages but in the project root (on the same level where Pages directory resides). Then you should either name resource file Pages.Index.en.resx or put it inside Resources/Pages subdirectory. Here is the correct placement of resource file for Index page:
or
If you want to keep Resources directory under Pages as in question, you should set ResourcesPath to Pages/Resources:
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix,
options => options.ResourcesPath = "Pages/Resources");
services.AddLocalization(options => options.ResourcesPath = "Pages/Resources");
But in this case you should still name resource file properly (Pages/Resources/Pages.Index.en.resx or Pages/Resources/Pages/Index.en.resx):
Check Resource file naming section from Globalization and localization in ASP.NET Core article for more details.

How to configure ASP.NET Core application to use windows authentication?

I want to configure ASP.NET application to use different authentication depends on the environment. So for development environment I want to use Windows authentication and for all other environment I want to use Facebook authentication.
I have already configured Facebook authentication for non development environment. How do I configure windows authentication for development environment? Windows authentication will only be used during development so developers does not have to login every time they run application in VS. We have multiple developers that means the windows identity will be different depend on who is executing it. Once the windows identity is created I will add claims to windows identity.
public class Startup
{
public Startup(IHostingEnvironment env)
{
// some stuff here for building configuration
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization();
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime)
{
if(env.IsDevelopment())
{
// How do i use windows authentication here
}
else
{
// this is my custom extension method
app.UseFacebookAuthentication();
}
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Windows Auth is configured during the web host configuration in program.cs
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
Specifically it's the UseIISIntegration() line.
Now that on it's own does nothing, it also needs configuring in web.config in the aspNetCore node;
<aspNetCore
processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout"
forwardWindowsAuthToken="true" />
The forwardWindowsAuthToken value needs to be set.
So no, you can't do it within an env.IsDevelopment() check.
If you use IIS Express for development environment, there is a quick way of setting up Windows authentication. There is a launchSettings.json in the Properties folder, and you can easily modify it to use Windows authentication for development without modifying the class Startup.
In this file, you can change "windowsAuthentication" to true, and "anonymousAuthentication" to false.
Here is the sample launchSettings.json:
{
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
"applicationUrl": "http://localhost:6366/",
"sslPort": 0
}
},
profiles": {
"IIS Express": {...
}
After this modification, you can run the application by selecting IIS Express as the debug target.

Convention based approach for configuring services based on Operating System in Startup.cs

I recently created an ASP.NET service using 1.0.0-rc1-update1 on coreclr (x64). So, the service is capable of running on all supported Operating Systems; very cool! My service just exposes a simple "TODO" API and uses the Entity Framework 7.0 ORM. For persistence, it employs a Sqlite DB on Linux and SQL Server DB on Windows.
I am wondering if there is a convention based approach to allow Startup.cs to handle differing service configurations for the various Operating Systems? For example, my EF configuration differs because it uses Sqlite on Linux and SQL Server on Windows.
The following article does detail some convention based approaches to configuration, but it seems to only allow for different methods for the higher level abstractions of "Development", "Staging", "Production" environments:
https://docs.asp.net/en/latest/fundamentals/environments.html
Currently, I am just injecting the IRuntimeEnviroment in the constructor of Startup.cs and saving it. Then, when ConfigureServices is invoked, I check the OperatingSystem property of the IRuntimeEnvironment and adjust the EF configuration accordingly (my full Startup.cs provided below)...
Any guidance on other (or recommended) approaches would be greatly appreciated.
public class Startup
{
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
public IConfigurationRoot Configuration { get; set; }
public IRuntimeEnvironment RuntimeEnv { get; set; }
public Startup(IHostingEnvironment env, IRuntimeEnvironment runtimeEnv)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
RuntimeEnv = runtimeEnv;
}
public void ConfigureServices(IServiceCollection services)
{
if (RuntimeEnv.OperatingSystem == "Windows")
{
var connectionString = Configuration["Data:DefaultConnection:ConnectionString"];
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<TodoContext>(options => options.UseSqlServer(connectionString));
}
else if (RuntimeEnv.OperatingSystem == "Linux")
{
var connectionString = Configuration["Data:DefaultConnection:SqlLiteConnection"];
var path = PlatformServices.Default.Application.ApplicationBasePath;
services.AddEntityFramework()
.AddSqlite()
.AddDbContext<TodoContext>(options => options.UseSqlite("Filename=" + Path.Combine(path, "TodoApp.db")));
}
services
.AddMvcCore(options =>
{
options.OutputFormatters.Clear();
options.OutputFormatters.Add(new HttpNotAcceptableOutputFormatter());
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
})
.AddJsonFormatters();
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
app.UseIISPlatformHandler();
app.UseMvc();
loggerFactory.AddConsole(minLevel: LogLevel.Verbose);
loggerFactory.MinimumLevel = LogLevel.Debug;
}
}