Changing ResourceManager (Make it Updatable) - c#

I have a project in MVC 3 (Razor) For localization we are using Strongly typed resources.
We want to have possibility to update translation that already exist "on-line". It means, that it should be possible to edit translation on the website. (e.g. If in the url there is parameter like "translateLanguage=on") Basically, it is not possible to do that with current solution, because if resource has been changed, then it must be recompiled.
Of course we can write our own Resource Manager that will be using a database, but then we would have to rewrite all of our translations to the database and that would be time consuming. It would also mean that we would have to change all of our code to reflect this "new" resource manager.
It would be hard to implement it in all things. Now, we can use it in attributes
e.g.
[Required(ErrorMessageResourceType = typeof(_SomeResource), ErrorMessageResourceName = "SomeResouceElement")
SomeProperty
As well as in code:
string translatedResource = _SomeResource.SomeResourceElement;
Could you provide me with some information how to do this in mvc 3?

Generally resource file consists of two parts xml + autogenerated cs code. If you open resource designer file you will see
/// <summary>
/// Looks up a localized string similar to About project.
/// </summary>
public static string about_project {
get {
return ResourceManager.GetString("about_project", resourceCulture);
}
}
So what you can do you can use ResourceManager.GetString("Key")
Thread.CurrentThread.CurrentCulture = new CultureInfo(cultureName);
var t = Resources.ResourceManager.GetResourceSet(new CultureInfo(cultureName), true, true);
To make it more smart you can rewrite BaseView
public abstract class ViewBase<TModel> : System.Web.Mvc.WebViewPage<TModel>
{
public string GetTranslation(string key)
{
return _rManager.GetString(key);
}
private ResourceManager _rManager;
protected ViewBase()
{
_rManager = Resources.ResourceManager.GetResourceSet(new CultureInfo(cultureName), true, true);
}
}
And then you will be able to use GetTranslation in your razor view (To run this base view you need to modify web.config from Views folder)
And then you will be able after editing xml access to resource data.

Related

Is there a way to read and convert a language config file into an object?

I wanted to add language support into my new project. I thought of creating a config file, similar to json.
So this is an example file:
{
"LabelTextMainMenu": "This is the main Label",
"LabelTextName": "Please enter your name"
}
Now what I want to reach is this (Classname not existing):
LangConfig config = File.ReadAllText(path/to/language/config);
public string LabelName
{
get {config.LabelTextName}
}
Before I'd write this "LangConfig"-Class myself, i'd like to know if there's something that works in the way I want it?
You can deserialize the config file to typed object via Json.Net (or equivalent package).
Below is the sample implementation :
var configData = File.ReadAllText(path/to/language/config.config);
LangConfig config = JsonConvert.DeserializeObject<LangConfig>(configData);
with the typed object, the properties can be accessed as
public string LabelName
{
get {config.LabelTextName}
}

ASP.NET Core: Custom IFileProvider prevents default IFileProvider from working

I am trying to serve some JavaScript from embedded resources in a class library. I managed to find out about the IFileProvider and created my own which is now working well. However, the problem I have now is that physical static files (from wwwroot) are no longer found.
I have the following in my Startup.cs file:
app.UseStaticFiles(
new StaticFileOptions()
{
// Override file provider to allow embedded resources
FileProvider = new CompositeFileProvider(
HostingEnvironment.ContentRootFileProvider,
new EmbeddedScriptFileProvider()),
//etc
});
I would have assumed using the CompositeFileProvider would mean that if the file is not found in one of the file providers, then it will try the other. I am also assuming that the default file provider is the one I specified as HostingEnvironment.ContentRootFileProvider. Is this incorrect?
The only other thing I can think of is that the problem is coming from inside my provider itself in the GetFileInfo() method. The definition of which is as follows:
public IFileInfo GetFileInfo(string subpath)
{
if (string.IsNullOrEmpty(subpath))
{
return new NotFoundFileInfo(subpath);
}
if (subpath.StartsWith("/", StringComparison.Ordinal))
{
subpath = subpath.Substring(1);
}
var metadata = EmbeddedScripts.FindEmbeddedResource(subpath);
if (metadata == null)
{
return new NotFoundFileInfo(subpath);
}
return new EmbeddedResourceFileInfo(metadata);
}
Could it be that returning NotFoundFileInfo(subpath) is causing my problems for physical css, js and other static files? If so, what should I be returning here instead so that the system knows to use the other file provider?
OK after a little digging in the source code (isn't it great that .NET is now open source?!), I managed to find the following links were very helpful indeed:
CompositeFileProvider.cs
- Based on the implementation in GetFileInfo(), I can see that I should pass back null instead of NotFoundFileInfo(subpath) if I want the other providers to try resolve it.
StaticFileMiddleware.cs
- This file shows that if the FileProvider is not specified (null) when setting up static file configuration with app.UseStaticFiles, then it will resolve one with the following line of code:
_fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
And looking at Helpers.cs shows the following code:
internal static IFileProvider ResolveFileProvider(IHostingEnvironment hostingEnv)
{
if (hostingEnv.WebRootFileProvider == null)
{
throw new InvalidOperationException("Missing FileProvider.");
}
return hostingEnv.WebRootFileProvider;
}
Therefore, my assumption of using HostingEnvironment.ContentRootFileProvider was incorrect. I should be using HostingEnvironment.WebRootFileProvider instead.
Everything now works as it should.

How to use enum in AuthorizeAttribute the razor mvc?

I have this enum:
public enum PerfilUsuarioEnum
{
AdministradorSistema = 1,
AdministradorLoja = 2,
Gerente = 3
}
And I want to pass it on my Authorize roles
[Authorize(Roles = PerfilUsuarioEnum.AdministradorLoja + ", " + PerfilUsuarioEnum.Gerente)]
There is some manner to do this?
Roles has to be constant expression such as string. Easiest way is to use cosntant.
public static class PerfilUsuario
{
public const string AdministradorLoja = "AdministradorLoja";
public const string Gerente = "NaviGerentegators";
}
[Authorize(Roles = PerfilUsuario.AdministradorLoja + ", " +
PerfilUsuario.Gerente)]
Great question. Here is what I did...
I decided to make my permissions database driven so I needed a way to convert strings into something "typed" so that I could get compile time warnings and also so I could change the name of the permission in the database and not have to update all of our production code. Since the attributes are string based (so called magic strings), I decided against enumerations and went with a T4 script that read the database and generated a struct for each record. This allowed me to also add things like, a nice display name, details about the permission, and an error message that I could show the user.
Here is a sample permission row after the T4 template runs.
public struct CanViewClaimData
{
// Using const allows the compiler to generate the values in the assembly at compile time and satisfy MVC Authorize Attribute requirements for const strings.
public const System.String Name = "CanViewClaimData";
public const System.String DisplayName = "Can View Claim Data";
public const System.String Description = "The allows users to view claim data";
public const System.String DefaultErrorMessage = "You must have the \"Can View Claim Data\" permission to access this feature.";
}
Then in code I use a sub classed Authorize and mark the Action as such,
[Security.AuthorizedAttribute(Roles = CanViewClaimData.Name, Message = CanViewClaimData.DefaultErrorMessage)]
Then during each automated build and push to our C.I. environment, I run the T4 template as part of the build process to keep the struct strings in sync with the database.
So far this has worked really well and allowed me to give our product owner the ability to edit the permission names, descriptions etc, in the database and without a developer having to be involved.

MVC How to bundle html templates of type "text/html"?

I have a series of template files (*.html) that are used by some JS components. Instead of having those JS components write the templates to the DOM when loaded, I wanted to bundle them together like scripts and have them downloaded separately by the client. This way should be faster (clientside), allow for caching (less round trips), and be more readable (template doesn't have to be stored in a JS string which breaks highlighting/intellisense).
How can this be accomplished?
I.
Use BundleTransformer [http://bundletransformer.codeplex.com/] and Mustache templates [https://mustache.github.io/] or Handlebars [http://handlebarsjs.com/]
II.
[Angular example but you can inspire a lot]
I'm not saying this is the best approach for your case, but i cannot left it like a comment.
Here is an example where the OP stores his bundle in $templateCache. Angular has a templateCache object, which stores all the templates it has loaded so far. It also lets you pre-load templates into the template cache.
Create a BundleTransform class, as he did:
public class PartialsTransform : IBundleTransform
{
private readonly string _moduleName;
public PartialsTransform(string moduleName)
{
_moduleName = moduleName;
}
public void Process(BundleContext context, BundleResponse response)
{
var strBundleResponse = new StringBuilder();
// Javascript module for Angular that uses templateCache
strBundleResponse.AppendFormat(
#"angular.module('{0}').run(['$templateCache',function(t){{",
_moduleName);
foreach (var file in response.Files)
{
// Get the partial page, remove line feeds and escape quotes
var content = File.ReadAllText(file.FullName)
.Replace("\r\n", "").Replace("'", "\\'");
// Create insert statement with template
strBundleResponse.AppendFormat(
#"t.put('partials/{0}','{1}');", file.Name, content);
}
strBundleResponse.Append(#"}]);");
response.Files = new FileInfo[] {};
response.Content = strBundleResponse.ToString();
response.ContentType = "text/javascript";
}
}
But you can store the templates where you want [i don't know where you want to store them].
Then create a Bundle.
public class PartialsBundle : Bundle
{
public PartialsBundle(string moduleName, string virtualPath)
: base(virtualPath, new[] { new PartialsTransform(moduleName) })
{
}
}
And you can use it like a ScriptBundle or StyleBundle.
bundles.Add(new PartialsBundle("testSPA", "~/bundles/partials").Include(
"~/Partials/nav-bar.html",
"~/Partials/home-page.html",
"~/Partials/investment-filter.html",
"~/Partials/investments-component.html",
"~/Partials/sector-component.html",
"~/Partials/transactions-component.html"));
And render like this: #Scripts.Render("~/bundles/partials")
In production transforming in this:
<script src="/bundles/partials?v=dq0i_tF8ogDVZ0X69xyBCdV2O2Qr3nCu0iVsatAzhq41"></script>
This guy is using the $templateCache object forcing Angular not to dynamically download template when are needed.
Further reading here: http://blog.scottlogic.com/2014/08/18/asp-angular-optimisation.html

How to change an EF connection string, based on subdomain of MVC web site?

I have an EF5 ASP.NET MVC 3 (Razor) web site, running under IIS7. Now I want to be able to change the connection string to the MSSQL database depending on the subdomain of the URL, e.g. foo.mydomain.com should connect to my "Foo" database, and bar.mydomain.com should connect to the "Bar" database.
Obviously the DNS records are set up so that they all point to the same web site.
What's the most efficient way of achieving this?
why don't you start passing your own SqlConnection to your YourDbContext?
var partialConString = ConfigurationManager.ConnectionStrings["DBConnectionStringName"].ConnectionString;
var connection = new SqlConnection("Initial Catalog=" + Request.Url.Host + ";" + partialConString);
var context = new MyDbContext(connection, true);
You can also change database in the DBContext:
context.Database.Connection.ChangeDatabase("newDbname");
It's not very easy...
You should change the constructor of object context to dynamically change the connection string.
Take the subdomain name using System.Web.HttpContext.Current.Request.Url.Host. Then use it to compute the proper connection string.
You should do this in the designer generated code. Of course this is not a good place.. to make it work use the T4 templating. Open your model and right click on the blank designer surface, then select "Add code generation item" -> Ado.net entity object generation. This will create a .tt file. Open it and look for the constructor syntax. Add your logic there.
Good luck!
I've come up with what I feel is a better solution than all those proposed to date. I'm using the default EntityModelCodeGenerator, so perhaps there are other, better, solutions for other templates - but this works for me:
Create the other half of the partial class MyEntities.
Override OnContextCreated(), which is called from within the class constructor.
Change the store connection string using a regex.
This comes out as follows:
partial void OnContextCreated()
{
// change connection string, depending on subdomain
if (HttpContext.Current == null) return;
var host = HttpContext.Current.Request.Url.Host;
var subdomain = host.Split('.')[0];
switch (subdomain)
{
case "foo":
ChangeDB("Foo");
break;
case "bar":
ChangeDB("Bar");
break;
}
}
private void ChangeDB(string dbName)
{
var ec = Connection as EntityConnection;
if (ec == null) return;
var match = Regex.Match(ec.StoreConnection.ConnectionString, #"Initial Catalog\s*=.*?;", RegexOptions.IgnoreCase);
if (!match.Success) return;
var newDbString = "initial catalog={0};".Fmt(dbName);
ec.StoreConnection.ConnectionString = ec.StoreConnection.ConnectionString.Replace(match.Value, newDbString);
}
Either use different connection strings in the web.config. Maybe research a bit if you can have conditional XSL transformations, that way, when you publish on a specific configuration the web.Release.config will change your Web.Config to be what you need it to be.
Or, use |DataDirectory| string substitution - http://msdn.microsoft.com/en-us/library/cc716756.aspx
more on DataDirectory string substitution here:
http://forums.asp.net/t/1835930.aspx/1?Problem+With+Database+Connection
I guess, if you want to be by the book, create build configurations for each of your separate releases and put the connection string in the respective web..config and when you publish, that XSL transformation will put the connection string in the resulting web.config and voila.
I've done something like that recently by adding some custom configuration, which uses the host header to determine the connectionStringName, which has to be used.
EF5 has a constructor, which can handle this name
var context = new MyDbContex("name=<DBConnectionStringName>");
I just did for a project
public partial class admpDBcontext : DbContext
{
public static string name
{
get
{
if (System.Web.HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority).ToString() == "http://fcoutl.vogmtl.com")
{
return "name=admpDBcontext";
}
else { return "name=admpNyDBcontext"; }
}
}
public admpDBcontext()
: base(name)
{
}
}
And in the web.config I add the connectionString.

Categories