What are the best usage of the following resource files.
Properties ā Resources (Phil used this resource for localization in DataAnnotation)
App_GlobalResources folder
App_LocalResources folder
I also would like to know what is the difference between (1) and (2) in asp.net mvc application.
You should avoid App_GlobalResources and App_LocalResources.
Like Craig mentioned, there are problems with App_GlobalResources/App_LocalResources because you can't access them outside of the ASP.NET runtime. A good example of how this would be problematic is when you're unit testing your app.
K. Scott Allen blogged about this a while ago. He does a good job of explaining the problem with App_GlobalResources in ASP.NET MVC here.
If you go with the recommended solution (1) (i.e. as in K. Scott Allen's blog):
For those of you trying to use explicit localization expressions (aka declarative resource binding expressions), e.g. <%$ Resources, MyResource:SomeString %>
public class AppResourceProvider : IResourceProvider
{
private readonly string _ResourceClassName;
ResourceManager _ResourceManager = null;
public AppResourceProvider(string className)
{
_ResourceClassName = className;
}
public object GetObject(string resourceKey, System.Globalization.CultureInfo culture)
{
EnsureResourceManager();
if (culture == null)
{
culture = CultureInfo.CurrentUICulture;
}
return _ResourceManager.GetObject(resourceKey, culture);
}
public System.Resources.IResourceReader ResourceReader
{
get
{
// Not needed for global resources
throw new NotSupportedException();
}
}
private void EnsureResourceManager()
{
var assembly = typeof(Resources.ResourceInAppToGetAssembly).Assembly;
String resourceFullName = String.Format("{0}.Resources.{1}", assembly.GetName().Name, _ResourceClassName);
_ResourceManager = new global::System.Resources.ResourceManager(resourceFullName, assembly);
_ResourceManager.IgnoreCase = true;
}
}
public class AppResourceProviderFactory : ResourceProviderFactory
{
// Thank you, .NET, for providing no way to override global resource providing w/o also overriding local resource providing
private static Type ResXProviderType = typeof(ResourceProviderFactory).Assembly.GetType("System.Web.Compilation.ResXResourceProviderFactory");
ResourceProviderFactory _DefaultFactory;
public AppResourceProviderFactory()
{
_DefaultFactory = (ResourceProviderFactory)Activator.CreateInstance(ResXProviderType);
}
public override IResourceProvider CreateGlobalResourceProvider(string classKey)
{
return new AppResourceProvider(classKey);
}
public override IResourceProvider CreateLocalResourceProvider(string virtualPath)
{
return _DefaultFactory.CreateLocalResourceProvider(virtualPath);
}
}
Then, add this to your web.config:
<globalization requestEncoding="utf-8" responseEncoding="utf-8" fileEncoding="utf-8" culture="en-US" uiCulture="en"
resourceProviderFactoryType="Vendalism.ResourceProvider.AppResourceProviderFactory" />
Properties ā Resources can be seen outside of your views and strong types are generated when you compile your application.
App_* is compiled by ASP.NET, when your views are compiled. They're only available in the view. See this page for global vs. local.
Related
I need to dynamically creates controllers in a ASP.NET Core 6 MVC application.
I found some way to somewhat achieve this but not quite.
I'm able to dynamically add my controller but somehow it reflects only on the second request.
So here is what I do: first I initialize my console app as follows:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc.Infrastructure;
namespace DynamicControllerServer
{
internal class Program
{
static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
ApplicationPartManager partManager = builder.Services.AddMvc().PartManager;
// Store thePartManager in my Middleware to be able to add controlelr after initialization is done
MyMiddleware._partManager = partManager;
// Register controller change event
builder.Services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance);
builder.Services.AddSingleton(MyActionDescriptorChangeProvider.Instance);
var app = builder.Build();
app.UseAuthorization();
app.MapControllers();
// Add Middleware which is responsible to cactn the request and dynamically add the missing controller
app.UseMiddleware<MyMiddleware>();
app.RunAsync();
Console.WriteLine("Server has been started successfully ...");
Console.ReadLine();
}
}
}
Then my middleware looks like this: it basically detects that there is the "dynamic" keyword in the url. If so, it will load the assembly containing the DynamicController:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using System;
using System.Reflection;
namespace DynamicControllerServer
{
public class MyMiddleware
{
public RequestDelegate _next { get; }
private string dllName = "DynamicController1.dll";
static public ApplicationPartManager _partManager = null;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext.Request.Path.HasValue)
{
var queryParams = httpContext.Request.Path.Value;
if(httpContext.Request.Path.Value.Contains("api/dynamic"))
{
// Dynamically load assembly
Assembly assembly = assembly = Assembly.LoadFrom(#"C:\Temp\" + dllName);
// Add controller to the application
AssemblyPart _part = new AssemblyPart(assembly);
_partManager.ApplicationParts.Add(_part);
// Notify change
MyActionDescriptorChangeProvider.Instance.HasChanged = true;
MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
}
}
await _next(httpContext); // calling next middleware
}
}
}
The ActionDescriptorChange provider looks like this:
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Primitives;
namespace DynamicControllerServer
{
public class MyActionDescriptorChangeProvider : IActionDescriptorChangeProvider
{
public static MyActionDescriptorChangeProvider Instance { get; } = new MyActionDescriptorChangeProvider();
public CancellationTokenSource TokenSource { get; private set; }
public bool HasChanged { get; set; }
public IChangeToken GetChangeToken()
{
TokenSource = new CancellationTokenSource();
return new CancellationChangeToken(TokenSource.Token);
}
}
}
Dynamic controller is in separate dll and is very simple:
using Microsoft.AspNetCore.Mvc;
namespace DotNotSelfHostedOwin
{
[Route("api/[controller]")]
[ApiController]
public class DynamicController : ControllerBase
{
public string[] Get()
{
return new string[] { "dynamic1", "dynamic1", DateTime.Now.ToString() };
}
}
}
Here are the packages used in that project:
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
This works "almost" fine ... when first request is made to:
https://localhost:5001/api/dynamic
then it goes in the middleware and load the assembly, but returns a 404 error.
Then second request will actually work as expected:
Second request returns the expected result:
I must doing it wrong and probably my middleware is executed too late in the flow to reflect the dynamic controller right away.
Question is: what should be the proper way to achieve this?
Second question I have is say now the external dll holding our dynamic controller is updated.
How can I reload that controller to get the new definition?
Any help would be appreciated
Thanks in advance
Nick
Here is the answer to my own question in case it can help somebody out there.
It seems building and loading the controller from the middleware will always end up with failure on the first call.
This makes sense since we are already in the http pipeline.
I end up doing same thing from outside the middleware.
Basically my application detect a change in the controller assembly, unload the original assembly and load the new one.
You cannot use the Default context since it will not allow reloading different dll for same assembly:
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); // Produce an exception on updates
To be able to reload new dll for same assembly, Iām loading each controller in its own assembly context. To do that you need to create your own class deriving from AssemblyLoadContext and managing assembly load:
public class MyOwnContext: AssemblyLoadContext
{
// You can find lots of example in the net
}
When you want to unload the assembly, you just unload the context:
MyOwnContextObj.Unload();
Now to add or remove the controller on the fly, you need to keep reference of the PartManager and the ApplicationPart.
To add controller
ApplicationPart part = new AssemblyPart(assembly);
_PartManager.ApplicationParts.Add(part);
To remove:
_PartManager.ApplicationParts.Remove(part);
On course once done, still use following piece of code to acknowledge the change:
MyActionDescriptorChangeProvider.Instance.HasChanged = true;
MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
That allow updating controller on the fly with no interruption of service.
Hope this helps people out there.
I have done a similar solution (used for managing a web app plugins) with some differences that may help you:
List all the external assemblies in a config file or appsettings.json so all the dll names and/or addresses are known at startup
Instead of registering controllers when they are called, register them at program.cs/start up :
//Foreah dllName from settings file
var assembly = Assembly.LoadFrom(#"Base address" + dllNameLoadedFromSettings);
var part = new AssemblyPart(assembly);
services.AddControllersWithViews()
.ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part));
// Any other configuration based on the usage you want
Second: I usually keep plugin dlls in the bin folder so when using IIS as soon as a dll file in bin is changed the upper-level app is automatically reset. So your second question would be solved too.
I have an ASP.NET MVC Razor website which is supposed to be live in multiple countries having different cultures and hence the languages. My Development team is concerned about english only and whole text on UI pages is also written in plain english. I want this english text to be converted into culture specific language. I'm using resource file to manage the strings on my website.
One way is to create multiple resource files based on each language and then using each file based on specific culture. This thing needs to be managed manually. If someone has done this thing please come up with any reference or any sample code for this implementation.
If there is any way where I can automate this thing then it will be best way to go for a multi lingual website. Like as culture can be easily detected by user IP address and based on the culture I should be able to convert all english based text into current culture specific language.
1. Automatically Use User-Culture
Have the user culture be set automatically on the current Thread/HttpContext. In your Web.Config:
<system.web>
...
<globalization enableClientBasedCulture="true" culture="auto" uiCulture="auto" />
...
</system.web>
2. Helper Function
Introduce a global method that will translate input text using the appropriate resource:
public static class Resources
{
public static string GetResource(string key, params object[] data)
{
if (String.IsNullOrEmpty(key))
return key;
// the actual call to your Resources
var res = Texts.ResourceManager.GetString(key.ToUpper(), Thread.CurrentThread.CurrentUICulture);
if (String.IsNullOrEmpty(res))
return data != null && data.Length > 0 ? String.Format(key.ToUpper(), data) : key;
if (data != null && data.Length > 0)
return String.Format(res, data);
return res;
}
}
The method also lets you pass an additional (optional) parameters to be used in a String.Format way. For example:
// in your Resource file (Texts.es.resx)
GREETING_TEXT: "Hola amigo {0}, el tiempo es {1}"
// invocation
Resources.GetResource("GREETING_TEXT", "Chaim", DateTime.Now);
3. Controller Helper:
Introducing a _ methods that will let you translate texts quickly in the Controller:
public class BaseController : Controller
{
public string _(string key, params object[] data)
{
return Resources.GetResource(key, null, data);
}
}
In your controller you have to make sure you're inheriting your BaseController and use it as follows:
public HomeController : BaseController:
{
public ActionResult GreetMe()
{
var msg = _("GREETING_TEXT", this.User, DateTime.Now);
return Content(msg);
}
}
4. Razor Helper
And for your Razor pages:
// non-generic version (for model-less pages)
public abstract class BaseWebPage : WebViewPage
{
public string _(string key, params object[] data)
{
return Resources.GetResource(key, null, data);
}
}
// generic version (for model defined pages)
public abstract class BaseWebPage<T> : WebViewPage<T>
{
public string _(string key, params object[] data)
{
return Resources.GetResource(key, null, data);
}
}
Now we have to set this new base WebPage as the base type for our pages in ~/Views/Web.Config:
<system.web.webPages.razor>
...
<pages pageBaseType="Full.NameSpace.ToYour.BaseWebPage">
...
</system.web.webPages.razor>
(If you're using Areas, you'll have to modify each ~/Areas/AREA_NAME/Views/Web.Config as well)
You can now use it in your Razor pages:
<h1>#_("HELLO")</h1>
WebAPI has a naming convention "FooController" for controller names. This is similar to ASP.NET MVC. Our codebase uses underscores to separate words in identifier named, e.g. "Foo_Bar_Object". In order for the controller names to follow this convention, we need a way to name our controllers "Foo_Controller".
Basically, we don't want URL routes to look like "oursite.com/api/foo_/", and we don't want to have to make exceptions everywhere for the underscore (e.g. route config, names of view folders, etc). To do this in MVC, we do:
Global.asax
protected void Application_Start() {
...
ControllerBuilder.Current.SetControllerFactory(new Custom_Controller_Factory());
}
Custom_Controller_Factory.cs
public class Custom_Controller_Factory : DefaultControllerFactory
{
protected override Type GetControllerType(RequestContext request_context, string controller_name)
{
return base.GetControllerType(request_context, controller_name + "_");
}
}
This seems to completely take care of the problem all in once place in MVC. Is there a way to do the same for WebAPI? I've heard rumor of a DefaultHttpControllerFactory but I can't find it anywhere.
I think DefaultHttpControllerSelector is what you are looking for.
class CustomHttpControllerSelector : DefaultHttpControllerSelector {
public CustomHttpControllerSelector(HttpConfiguration configuration)
: base(configuration) { }
public override string GetControllerName(HttpRequestMessage request) {
IHttpRouteData routeData = request.GetRouteData();
string controller = (string)routeData.Values["controller"]
return controller + "_";
}
Global.asax
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new sh_Custom_Http_Controller_Selector(GlobalConfiguration.Configuration));
When I serve a javascript file to a user from the /Content Directory I want to replace a string token in that file with a value, so that when the user requests a given file, it has all the customizations they expect.
I think that means I need to somehow proxy requests to the /Content directory, perform the dynamic insertion, and give the file to the user.
I'm interested in performing this insertion as a stream or as a in -memory file. I'd prefer to use a stream just because it's probably more efficient memory wise.
How do I get ASP.NET to proxy this directory?
I've attempted
Using routes to point to a controller
WCF to proxy a URL
But they all seem "ugly" to me and I'd like to make this insertion/replacement as transparent as possible int he project.
Is there a cleaner way?
The easiest way is to create an action on a controller.
public class JavascriptController : Controller
{
public ActionResult Load(string file)
{
var content = System.IO.File.ReadAllText(Server.MapPath(string.Format("~/Content/{0}", file)));
//make replacements io content here
return this.Content(content, "application/javascript");
}
}
You can then access the javascript like this (assuming you have the default routing):
http://localhost:53287/Javascript/Load?file=file.js
where file.js is the name of the file you are requesting.
Don't worry about the url, you can customise this by creating another route if necessary
Here is alternative answer to the answer I posted above, taking into account your comment regarding dynamic javascript.
Firstly, I don't know of a way to do this specifically using either mvc or wcf.. the only way I know how to do this is with a lower-level HttpModule
Take a look at the following code:
public class JavascriptReplacementModule : IHttpModule
{
public class ResponseFilter : MemoryStream
{
private Stream outputStream = null;
public ResponseFilter(Stream output)
{
outputStream = output;
}
public override void Flush()
{
base.Flush();
this.Seek(0, SeekOrigin.Begin);
var sr = new StreamReader(this);
string contentInBuffer = sr.ReadToEnd();
//Do replacements here
outputStream.Write(UTF8Encoding.UTF8.GetBytes(contentInBuffer), 0, UTF8Encoding.UTF8.GetByteCount(contentInBuffer));
outputStream.Flush();
}
protected override void Dispose(bool disposing)
{
outputStream.Dispose();
base.Dispose(disposing);
}
}
public void Dispose() { }
public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
}
void context_PostRequestHandlerExecute(object sender, EventArgs e)
{
var context = (HttpApplication)sender;
if (context.Request.Url.AbsolutePath.StartsWith("/Content") && context.Request.Url.AbsolutePath.EndsWith(".js"))
{
HttpContext.Current.Response.Filter = new ResponseFilter(HttpContext.Current.Response.Filter);
}
}
}
And register the module like this (make sure you put the full type in the type attribute):
<system.webServer>
<modules>
<add name="JavascriptReplacementModule" type="JavascriptReplacementModule"/>
</modules>
</system.webServer>
This allows you to modify the output stream before it gets to the client
I'm trying to specify a connection string dynamically based of the url using ninject.
I'm using the ninject.mvc nuget package that uses the webActivator.
My code is as follows:
my injection:
kernel.Bind<IUnitOfWork>().To<UnitOfWork>()
.WithConstructorArgument("connectionString", MvcApplication.GetConnectionStringName());
my global.asax
private static HttpContext _context;
public static string GetConnectionStringName() {
var subDomain = String.Empty;
if (_context != null) {
subDomain = _context.Request.Url.SubDomain();
}
return String.Format("{0}ConnectionString", subDomain);
}
The problem is the _context (which is set in my Application_BeginRequest) is always null because the WebActivator runs before the application_start.
Is it possible in ninject to specify to call MvcApplication.GetConnectionStringName() when a IUnitOfWork is required rather than on application start?
Is there a better approach to what I'm doing?
Thanks
You should use the Ninject binding like this.
kernel.Bind<IUnitOfWork>().To<UnitOfWork>()
.WithConstructorArgument("connectionString", context => MvcApplication.GetConnectionStringName());
Note that context here is of type Ninject's IContext and so has nothing to do with HttpContext.
Anyway I think you approach is suitable for this.
Sometimes (especially when there are multiple related parameters to be injected) I prefer creating an interface and specific implementations for the configurations and let them injected by standard bindings like this.
public interface IUnitOfWorkConfiguration {
string ConnectionString { get; }
}
public class AppConfigUnitOfWorkConfiguration : IUnitOfWorkConfiguration {
public string ConnectionString { get { ... } }
}
public class UnitOfWork {
public UnitOfWork(IUnitOfWorkConfiguration configuration) {
}
}
Bind<IUnitOfWorkConfiguration>().To<AppConfigUnitOfWorkConfiguration>();
Using this approach you can avoid specifying parameter names as string literals.
One more note about using HttpContext. I do not recommend using it that way because of thread safety issues. You should either mark your private static field _context with the [ThreadStatic] atribute or as a better choice simply use HttpContext.Current everywhere.