Reputation: 914
I have an application with a custom database routing:
routes.Add("RouteWeb", new RouteWeb());
public override RouteData GetRouteData(HttpContextBase httpContext)
{
if (httpContext.Request.IsAjaxRequest() || httpContext.Request.Url == null) return null;
var page = FindPageFromDataBase(httpContext.Request.Url);
if (page == null) return null;
var pageResult = new RouteData(this, new MvcRouteHandler());
pageResult.Values["culture"] = page.Culture;
pageResult.Values["controller"] = page.controller;
pageResult.Values["action"] = page.action;
return pageResult;
}
As you see I get the pages from database, so the admin of the site could change the route of a page (www.site.com/page -> www.site.com/other-name) and the site works with the new name.
In the database I retrieve the culture of the page, because every page could be in different cultures, for example if you access to www.site.com/page it gets the content in English, while if you access to www.site.com/pagina it shows the content in Spanish.
This works perfect except for one detail, when the user can filter a page using a date.
@using (Ajax.BeginForm(null, null, new AjaxOptions { HttpMethod = FormMethod.Get.ToString(), InsertionMode = InsertionMode.Replace, UpdateTargetId = "content_list", Url = Url.Action("UsersItems", "Users"), OnComplete = "OnComplete" }, new { id = "formSearch" }))
{
...
@Html.DatePickerRangeFor(model => model.DateFrom, new { @class = "form-control", Value = Model.DateFrom == null ? "" : Convert.ToDateTime(Model.DateFrom).ToShortDateString()})
}
I have to use a custom ModelBinder to change the date to the correct format based on the language of the selected page.
public class NullableDateTimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var cultureInfo = new CultureInfo(controllerContext.RouteData.Values["culture"].ToString());
System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo;
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name);
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return value?.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
}
}
My problem is
controllerContext.RouteData.Values["culture"]
is always null. When I first load the page RouteData gets the culture, but when doing the partial ajax request all the values are gone and the ModelBinder gives me an error.
I don't want to store the current culture in a session variable since I read the language from the page of the database and I have the problem only with ajax requests to load partial views.
Is there any way to pass the ModelBinder the culture of the current page?
Thanks in advance.
Upvotes: 1
Views: 138
Reputation: 56849
Rather than setting the current culture in a ModelBinder
, it would be better to use an IAuthorizationFilter
for this purpose. Note that IActionFilter
s run after the ModelBinder
but IAuthorizationFilter
s run before the ModelBinder
.
using System.Globalization;
using System.Threading;
using System.Web.Mvc;
public class CultureFilter : IAuthorizationFilter
{
private readonly string defaultCulture;
public CultureFilter(string defaultCulture)
{
this.defaultCulture = defaultCulture;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
var values = filterContext.RouteData.Values;
string culture = (string)values["culture"] ?? this.defaultCulture;
CultureInfo ci = new CultureInfo(culture);
Thread.CurrentThread.CurrentCulture = ci;
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
}
You can set the filter globally by registering it as a global filter.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CultureFilter(defaultCulture: "en"));
filters.Add(new HandleErrorAttribute());
}
}
Since it runs before the ModelBinder
runs, there is no need to change the culture within the ModelBinder
as it will pick up the ambient culture from the request. So you can eliminate the custom ModelBinder
altogether.
When I first load the page RouteData gets the culture, but when doing the partial ajax request all the values are gone
This is because you are skipping your route whenever an AJAX request comes in:
if (httpContext.Request.IsAjaxRequest() || httpContext.Request.Url == null) return null;
You should eliminate that line.
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var page = FindPageFromDataBase(httpContext.Request.Url);
if (page == null) return null;
var pageResult = new RouteData(this, new MvcRouteHandler());
pageResult.Values["culture"] = page.Culture;
pageResult.Values["controller"] = page.controller;
pageResult.Values["action"] = page.action;
return pageResult;
}
Note also that you may be getting a null value for culture because it is not set in your default route. You should set a default culture for every route where it is not possible to pass the culture through the URL, and omit the default culture for every route where it is possible to pass the culture (which makes the URL segment required in order for the route to match).
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new RouteWeb());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { culture = "en", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Related:
Upvotes: 1