Multi-tenancy and WorkContext

Topics: Customizing Orchard, Writing modules
Jul 1, 2013 at 2:00 PM
Edited Jul 1, 2013 at 2:09 PM
After enabling multi tenancy on our Orchard instance, I got problems with a IOrchardShellEvents in a module. Here we register a custom DateTimeModelBinder. In that DateTimeModelBinder I need access to the current culture of the orchard site.
public class CustomBinderShellEvent : IOrchardShellEvents
    {
        private readonly IOrchardServices _orchardServices;

        public CustomBinderShellEvent(IOrchardServices orchardServices)
        {
            _orchardServices = orchardServices;
        }

        #region IOrchardShellEvents Members

        public void Activated()
        {
            if (ModelBinders.Binders.ContainsKey(typeof (DateTime))) return;

            // Register custom DateTime Binder to fix culture issues with CulturePicker Widget
            var binder = new DateTimeModelBinder(_orchardServices);
            ModelBinders.Binders.Add(typeof(DateTime), binder);
            ModelBinders.Binders.Add(typeof(DateTime?), binder);
        }

        public void Terminating()
        {
        }

        #endregion
    }
public class DateTimeModelBinder : DefaultModelBinder
    {
        private readonly IOrchardServices _orchardServices;

        public DateTimeModelBinder(IOrchardServices orchardServices)
        {
            _orchardServices = orchardServices;
        }

        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var currentCulture = _orchardServices.WorkContext.CurrentCulture;

            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            var theDate = value.AttemptedValue;
            var dt = new DateTime();
            var success = DateTime.TryParse(
                theDate,
                CultureInfo.GetCultureInfo(currentCulture),
                DateTimeStyles.None,
                out dt);

            if (success)
            {
                return dt;
            }

            // Return an appropriate default
            return DateTime.MinValue;
        }
    }
Is this normal behavior for multi tenancy sites? Is there a workaround?
We are using Orchard 1.6.1.
Developer
Jul 1, 2013 at 3:00 PM
What is the behavior you're seeing?
In any case, I think you should inject a Work<IOrchardServices> in DateTimeModelBinder and thus in CustomBinderShellEvent as well, because, if I recall correctly, the ModelBinders collection will hold on to the instances you add throughout the lifetime of the application (not just for the current request).
Jul 1, 2013 at 4:28 PM
Edited Jul 1, 2013 at 4:37 PM
I just replaced the constructor injection with the Work<IOrchardServices>(), passed it through to datetimemodelbinder --> when the model binder is triggered in code, the value of the field Work<IOrchardServices>() is null.
public class CustomBinderShellEvent : IOrchardShellEvents
    {
        private readonly Work<IOrchardServices> _orchardServices;

        public CustomBinderShellEvent(Work<IOrchardServices> orchardServices)
        {
            _orchardServices = orchardServices;
        }

        #region IOrchardShellEvents Members

        public void Activated()
        {
            if (ModelBinders.Binders.ContainsKey(typeof (DateTime))) return;

            // Register custom DateTime Binder to fix culture issues with CulturePicker Widget
            var binder = new DateTimeModelBinder(_orchardServices);
            ModelBinders.Binders.Add(typeof(DateTime), binder);
            ModelBinders.Binders.Add(typeof(DateTime?), binder);
        }

        public void Terminating()
        {
        }

        #endregion
    }
 public class DateTimeModelBinder : DefaultModelBinder
    {
        private readonly Work<IOrchardServices> _orchardServices;

        public DateTimeModelBinder(Work<IOrchardServices> orchardServices)
        {
            _orchardServices = orchardServices;
        }

        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            // https://orchard.codeplex.com/discussions/448702
            var currentCulture = _orchardServices.Value.WorkContext.CurrentCulture;

            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            var theDate = value.AttemptedValue;
            var dt = new DateTime();
            var success = DateTime.TryParse(
                theDate,
                CultureInfo.GetCultureInfo(currentCulture),
                DateTimeStyles.None,
                out dt);

            if (success)
            {
                return dt;
            }

            // Return an appropriate default
            return DateTime.MinValue;
        }
    }
in here _orchardServices.Value => null
Developer
Jul 1, 2013 at 4:53 PM
Interesting, I don't why that is without looking into it. But perhaps access HttpContext.Current directly (or inject HttpContextBase or RequestContext and see if that works), then use the GetWorkContext extension method to get a WorkContext.
Developer
Jul 1, 2013 at 5:31 PM
Edited Jul 1, 2013 at 5:39 PM
Like sfmskywalker said - please use the extension method on RequestContext to acquire current WorkContext instance and the current culture: var culture = controllerContext.RequestContext.GetWorkContext().CurrentCulture. No injection needed.

The problem here is that IOrchardShellEvents implementations reside outside of the shell lifetime scope, so you can only inject global dependencies there (not the shell and not the work context ones).
Fortunately, the work context accessor object is always included within the current request context (in the RequestContext.RouteData.DataTokens collection, to be clear), so you can access the current work context wherever you have access to the current HttpContext, ControllerContext etc. The extension method above does all the heavy lifting for you, extracting the information from the context.
Jul 2, 2013 at 1:51 PM
Ok this worked, but it is strange that this issue came up when starting to use the Multi-tenancy.
Thanks for the help