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.
Related
I have an API developed in .NET Core with EF Core. I have to serve multiple clients with different data(but the same schema). This is a school application, where every school want to keep their data separately due to competition etc. So we have a database for each school. Now my challenge is, based on some parameters, I want to change the connection string of my dbContext object.
for e.g., if I call api/students/1 it should get all the students from school 1 and so on. I am not sure whether there is a better method to do it in the configure services itself. But I should be able to pass SchoolId from my client application
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolDataContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("APIConnectionString")));
services.AddScoped<IUnitOfWorkLearn, UnitOfWorkLearn>();
}
11 May 2021
namespace LearnNew
{
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.
public void ConfigureServices(IServiceCollection services)
{
//Comenting to implement Mr Brownes Solution
//services.AddDbContext<SchoolDataContext>(options =>
// options.UseSqlServer(
// Configuration.GetConnectionString("APIConnectionString")));
services.AddScoped<IUnitOfWorkLearn, UnitOfWorkLearn>();
services.AddControllers();
services.AddHttpContextAccessor();
services.AddDbContext<SchoolDataContext>((sp, options) =>
{
var requestContext = sp.GetRequiredService<HttpContext>();
var constr = GetConnectionStringFromRequestContext(requestContext);
options.UseSqlServer(constr, o => o.UseRelationalNulls());
});
ConfigureSharedKernelServices(services);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "LearnNew", Version = "v1" });
});
}
private string GetConnectionStringFromRequestContext(HttpContext requestContext)
{
//Trying to implement Mr Brownes Solution
var host = requestContext.Request.Host;
// Since I don't know how to get the connection string, I want to
//debug the host variable and see the possible way to get details of
//the host. Below line is temporary until the right method is identified
return Configuration.GetConnectionString("APIConnectionString");
}
private void ConfigureSharedKernelServices(IServiceCollection services)
{
ServiceProvider serviceProvider = services.BuildServiceProvider();
SchoolDataContext appDbContext = serviceProvider.GetService<SchoolDataContext>();
services.RegisterSharedKernel(appDbContext);
}
// 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.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LearnNew v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
You can access the HttpContext when configuring the DbContext like this:
services.AddControllers();
services.AddHttpContextAccessor();
services.AddDbContext<SchoolDataContext>((sp, options) =>
{
var requestContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
var constr = GetConnectionStringFromRequestContext(requestContext);
options.UseSqlServer(constr, o => o.UseRelationalNulls());
});
This code:
var requestContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext; var constr = GetConnectionStringFromRequestContext(requestContext);
options.UseSqlServer(constr, o => o.UseRelationalNulls());
will run for every request, configuring the connection string based on details from the HttpRequestContext.
If you need to use your DbContext on startup, don't resolve it through DI. Just configure a connection like this:
var ob = new DbContextOptionsBuilder<SchoolDataContext>();
var constr = "...";
ob.UseSqlServer(constr);
using (var db = new Db(ob.Options))
{
db.Database.EnsureCreated();
}
But in production you would normally create all your tenant databases ahead-of-time.
I'm looking for something similar like the old behaviour of setting the Culture and everything in the request after that has that Culture.
Similar like ASP.NET 4.5:
Thread.CurrentThread.CurrentCulture = culture_info;
Thread.CurrentThread.CurrentUICulture = culture_info;
When I do this right now, some code seems to be constantly resetting the Culture to English at various points in the MVC pipeline.
Why do I want that? Because I want to set the culture through my extended routing logic in a very large (and customizable) platform-type of web project.
The default RouteDataRequestCultureProvider doesn't seem to work at all - see explained here:
https://irensaltali.com/en/asp-net-core-mvc-localization-by-url-routedatarequestcultureprovider/
and
Localization works when culture is set via QueryString but not when culture is in route
I don't want to use the "hardcoded" url parsing that is being presented as a solution, because my routing logic is complicated - not all routes will have a culture defined in the same spot in the URL.
I tried setting this, but that also didn't work:
httpContext.Features.Set<IRequestCultureFeature>(new RequestCultureFeature(request_culture, new DummyProvider()));
Is there any workaround for just simply setting the Culture for the rest of the request?
A workaround for simply setting the Culture for the rest of the request is to use default QueryStringRequestCultureProvider.
Here is the whole demo:
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddControllersWithViews()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
//...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("de"),
new CultureInfo("fr"),
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
// Formatting numbers, dates, etc.
SupportedCultures = supportedCultures,
// UI strings that we have localized.
SupportedUICultures = supportedCultures
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Create Resource folder and add resource file as below:
Configure resource file like below:
Controllers.HomeController.de.resx
Controllers.HomeController.fr.resx
Reference:Resource file naming
Controller:
public class HomeController : Controller
{
private readonly IStringLocalizer<HomeController> _localizer;
public HomeController(IStringLocalizer<HomeController> localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
ViewData["Title"] = _localizer["Your Title"];
return View();
}
}
Index.cshtml:
#ViewData["Title"]
Result:
While I too can't find a way to set the request culture for the rest of the request you can use the CustomRequestCultureProvider and use your custom routing logic to get set the culture for the request
app.UseRequestLocalization(options =>
{
options.AddSupportedUICultures(... set some cultures here...)
.AddSupportedCultures(... and here...)
.AddInitialRequestCultureProvider(new CustomRequestCultureProvider(async context =>
{
string cultureCode = await RunCustomRoutingAndGetCulture(context.Request);
return new ProviderCultureResult(cultureCode);
}));
});
If your custom logic does not require awaiting remove async and return with Task.FromResult
I am using asp net core 3.0 in my web API project. I have created various API's and all are accessible via Swagger or Postman. But when trying to access the same via any other client like React, Method not allowed (405 error code) is received. On investing further, I find out that at first, OPTION request is received from the React application and the net core web API application is giving the 405 status code. Further, I find out that I need to enable all the methods as well as origins from the net core application to accept all types of requests otherwise it will not accept OPTION request. To achieve this, I enabled CORS policy in startup.cs file but still had no luck. Following is my startup.cs file:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
var elasticUri = Configuration["ElasticConfiguration:Uri"];
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithExceptionDetails()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(elasticUri))
{
MinimumLogEventLevel = LogEventLevel.Verbose,
AutoRegisterTemplate = true,
})
.CreateLogger();
}
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.Configure<IISServerOptions>(options =>
{
options.AutomaticAuthentication = false;
});
services.Configure<ApiBehaviorOptions>(options =>
{
//To handle ModelState Errors manually as ApiController attribute handles those automatically
//and return its own response.
options.SuppressModelStateInvalidFilter = true;
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
services.AddControllers(options =>
{
//To accept browser headers.
options.RespectBrowserAcceptHeader = true;
}).
AddNewtonsoftJson(options =>
{
// Use the default property (Pascal) casing
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
}).
AddJsonOptions(options =>
{
//Not applying any property naming policy
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.IgnoreNullValues = true;
}).
AddXmlSerializerFormatters().
AddXmlDataContractSerializerFormatters();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCors("CorsPolicy");
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//Configuring serilog
loggerFactory.AddSerilog();
}
}
I tried testing the same API with the OPTIONS method from POSTMAN. It is also giving the Http Status Code as 405. But when trying to access the same request using the POST method, I received the response successfully.
Is there anything wrong with the above code or something wrong with the order of middlewares being called in Configure().
Try to add extension method and modifying your startup class:
Extension method:
public static void AddApplicationError(this HttpResponse response, string
message)
{
response.Headers.Add("Application-Error", message);
response.Headers.Add("Access-Control-Expose-Headers", "Application-Error");
response.Headers.Add("Access-Control-Allow-Origin", "*");
}
Startup.cs :
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(builder =>
{
builder.Run(async context =>
{
context.Response.StatusCode = (int)
HttpStatusCode.InternalServerError;
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
context.Response.AddApplicationError(error.Error.Message);
await context.Response.WriteAsync(error.Error.Message);
}
});
});
}
P.S. in my case I had scenario also returning 405 status error, cause was, similar action methods I used and there are conflicted
For ex:
[HttpGet]
public ActionResult GetAllEmployees()
[HttpGet]
public ActionResult GetCustomers()
Hope this will help at least to show exact error message
You need to add Cors in Startup.cs file under your web api project
add this variable in Startup.cs
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
add services.AddCors before services.AddControllers() in the method ConfigureServices in file Startup.cs:
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://localhost:4000",
"http://www.yourdomain.com")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddControllers();
*** You can pass only * to allow all instead of passing http://localhost:4000","http://www.yourdomain.com in the WithOrigins method
add app.UseCors before app.UseAuthentication() in the method Configure in file Startup.cs:
app.UseCors(MyAllowSpecificOrigins);
Check this Microsoft help
Try this:
app.UseCors(policy =>
policy.WithOrigins("https://localhost:PORT", "https://localhost:PORT")
.AllowAnyMethod()
.WithHeaders(HeaderNames.ContentType)
);
I've installed swashbuckle on on a clean asp.net core web api project following these instructions. My startup class is below. You can see I've added AddSwaggerGen(), UseSwagger() and UseSwaggerUI().
When I visit https://localhost:44334/swagger/v1/swagger.json, instead of seeing the swagger UI I expect, I've got a pile of JSON, starting {"swagger":"2.0","info":{"version":"v1","title":"MoqOcr"}...
What am I missing ?
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// sby
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "MoqOcr", Version = "v1" });
});
}
// 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();
}
else
{
app.UseHsts();
}
// sby
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.RoutePrefix = string.Empty;
});
//app.UseHttpsRedirection();
app.UseMvc();
}
}
I have checked your configuration for Swagger in Startup.cs and there seemed no unexpected thing to setup swagger to me. The only thing that pops in my mind is that you are mistaken the SwaggerEndpoint setting which indicates to you (I suppose) that you can access your Swagger UI from that url but it holds a json to build and configure that UI page. Fair enough but you should try https://localhost:44334/swagger
or https://localhost:44334/swagger/index.html to see your Swagger UI page. Hope this solves your problem.
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