I am posting this answer as an alternative, customizable way to localize with ASP.NET MVC5 with an asynchronous controller. You may find some problems in my solution, especially when it comes to routing and setting cookies.
This is a kind of short tutorial that I wrote for my heterogeneous / user approach. So I preferred SO over WordPress. :)
Sorry for not giving an accurate and discrete answer to your problem. Hope this helps you in some other way and to other people; who want to do the same setup.
On her blog blog, Nadeem Afana described a strategy for creating a separate Resource project in a solution for implementing internationalization using static resource files. On the blog he described in detail the extension of the same project for processing resources through databases and XML-based approaches. For the first, he used ADO.NET, detachable from the Entity Framework.
We needed to implement both static and dynamic resources in the MVC project, observing the concepts of MVC conventions.
First, add the resources folder to the root of the project with the desired language options: ~/Resources/Resources.resx (the default resource file matches the culture in the USA), ~/Resources/Resources.fi.resx and ~/Resources/Resources.nl.resx . Mark resources as public to make them available in Views.
In ~/Views/Web.config add the resource namespace in the <namespace> element: <add namespace="YourMainNamespace.Reousrces" /> . Under the controllers, create a base controller class:
Here are the cookies
namespace YourNamespace.Controllers {
Then register the global filter in ~/Global.asax.cs to ensure that every action must use the correct culture before executing:
Here are the cookies again!
public class SetCultureActionFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); var response = filterContext.RequestContext.HttpContext.Response; var culture = filterContext.RouteData.Values["culture"].ToString();
And add GlobalFilters.Filters.Add(new SetCultureActionFilterAttribute()); in the MyApplication.Application_Start() method.
In ~/App_Start/RoutesConfig.cs change the default route to:
routes.MapRoute( name: "Default", url: "{culture}/{controller}/{action}/{id}", defaults: new { culture = "en-US", controller = "Home", action = "Index", id = UrlParameter.Optional } );
At this point, we will be able to use the available resources. For example; @Resources.Headline .
Then we will create a custom Translatable attribute for the model properties.
class TranslatableAttribute : Attribute { }
It's enough. But if you want to specify a scope, you can use this class to implement it.
Now add a model called Resource with three properties and a helper method:
public class Resource { [Key, Column(Order = 0)] public string Culture { get; set; } [Key, Column(Order = 1)] public string Name { get; set; } public string Value { get; set; } #region Helpers // Probably using reflection not the best approach. public static string GetPropertyValue<T>(string id, string propertyName) where T : class { return GetPropertyValue<T>(id, propertyName, Thread.CurrentThread.CurrentUICulture.Name); } public static string GetPropertyValue<T>(string id, string propertyName, string culture) where T : class { Type entityType = typeof(T); string[] segments = propertyName.Split('.'); if (segments.Length > 1) { entityType = Type.GetType("YourNameSpace.Models." + segments[0]); propertyName = segments[1]; } if (entityType == null) return "?<invalid type>"; var propertyInfo = entityType.GetProperty(propertyName); var translateableAttribute = propertyInfo.GetCustomAttributes(typeof(TranslatableAttribute), true) .FirstOrDefault(); if (translateableAttribute == null) return "?<this field has no translatable attribute>"; var dbCtx = new YourNamespaceDbContext(); var className = entityType.Name; Resource resource = dbCtx.Resources.Where(r => (r.Culture == culture) && r.Name == className + id + propertyName).FirstOrDefault(); if (resource != null) return resource.Value;
This helper method will help you get translated content. For example, you can say:
var name = Resource.GetPropertyValue<Product>(item.Id.ToString(), "Name");
Note that at any point, the data in the column of the field being translated is unreliable; it will always contain the last updated value. When creating the record, we will reflect all the values โโof the translated properties in the resource model for all supported cultures.
We use asynchronous controllers, so for insertion, modification, and deletion, we will override SaveChangesAsync() in our DbContext class:
public override Task<int> SaveChangesAsync() { ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext; List<ObjectStateEntry> objectDeletedStateEntryList = ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted) .ToList(); List<ObjectStateEntry> objectCreateOrModifiedStateEntryList = ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified) .ToList(); // First handle the delition case, // before making changes to entry state bool changed = UpdateResources(objectDeletedStateEntryList); // Now save the changes int result = base.SaveChangesAsync().Result; // Finally handle the remaining cases changed |= UpdateResources(objectCreateOrModifiedStateEntryList); if (changed) return base.SaveChangesAsync(); return Task.FromResult<int>(result); } private bool UpdateResources(List<ObjectStateEntry> objectStateEntryList) { bool changed = false; foreach (ObjectStateEntry entry in objectStateEntryList) { var typeName = entry.EntitySet.ElementType.Name; if (entry.IsRelationship || typeName == "Resource") return false; var type = Type.GetType("YourNamespace.Models." + typeName); if (type == null) // When seeds run (db created for the first-time), sometimes types might not be create return false; if (entry.State == EntityState.Deleted) { changed |= DeleteResources(type, typeName, entry); continue; } foreach (var propertyInfo in type.GetProperties()) { var attribute = propertyInfo.GetCustomAttributes(typeof(TranslatableAttribute), true).FirstOrDefault(); if (attribute == null) continue; CurrentValueRecord current = entry.CurrentValues; object idField = current.GetValue(current.GetOrdinal("Id")); if (idField == null) continue; var id = idField.ToString(); var propertyName = propertyInfo.Name; string newValue = current.GetValue(current.GetOrdinal(propertyName)).ToString(); var name = typeName + id + propertyName; Resource existingResource = this.Resources.Find(Thread.CurrentThread.CurrentUICulture.Name, name); if (existingResource == null) { foreach (var culture in CultureHelper.Cultures) { this.Resources.Add(new Resource { Culture = culture, Name = name, Value = newValue }); changed |= true; } } else { existingResource.Value = newValue; changed |= true; } } } return changed; } private bool DeleteResources(Type type, string typeName, ObjectStateEntry entry) { bool changed = false; var firstKey = entry.EntityKey.EntityKeyValues.Where(k => k.Key.Equals("Id", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); if (firstKey == null) return false; var id = firstKey.Value.ToString(); foreach (var propertyInfo in type.GetProperties()) { var name = typeName + id + propertyInfo.Name; foreach (var culture in CultureHelper.Cultures) { Resource existingResource = this.Resources.Find(culture, name); if (existingResource == null) continue; this.Resources.Remove(existingResource); changed |= true; } } return changed; }
This will take care of updating and uninstalling.