MVC TryUpdateModel executing outside of WebRequest.

Apr 3, 2015 at 2:48 PM
Edited Apr 3, 2015 at 2:54 PM
I use FluentValidation IValidator registered as WebRequestLifestyle.

When TryUpdateModel is executed on the controller I get :

The registered delegate for type IValidator threw an exception. The Validator is registered as 'Web Request' lifestyle, but the instance is requested outside the context of a Web Request.

But the controller HttpContext is not null.

The validator wich gets the repositories also injected by WebRequest :
public partial class AddressModelValidator : AbstractValidator<AddressModel>
{
        
     public AddressModelValidator(ITMDBRepo repoTMDB, ITMDBOldRepo repoTMDBOld)
     {
         RuleFor(x => x.Property).Test(Value).WithMessage(ValidationsErrorMessage);
      }
}
The registration :
//Repositories
container.RegisterPerWebRequest<ITMDBRepo, TMDBRepo>();
container.RegisterPerWebRequest<ITMDBOldRepo, TMDBOldRepo>();

//Validations
IEnumerable<Assembly> assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>();
container.RegisterManyForOpenGeneric(typeof(IValidator<>), new WebRequestLifestyle(), assemblies);
The FluentValidationModelValidatorProvider :
 FluentValidationModelValidatorProvider.Configure(
                provider =>
                {
                    provider.ValidatorFactory = new FluentValidatorFactory(container);
                    provider.AddImplicitRequiredValidator = false;   
                }
            );
The FluentValidatorFactory :
public class FluentValidatorFactory : IValidatorFactory
    {
        private IServiceProvider Injector { get; set; }

        public FluentValidatorFactory(IServiceProvider injector)
        {
            Injector = injector;
        }

        public virtual IValidator CreateInstance(Type validatorType)
        {
            return Injector.GetService(validatorType) as IValidator;
        }

        public IValidator<T> GetValidator<T>()
        {
            return Injector.GetService(typeof(IValidator<T>)) as IValidator<T>;
        }
        public IValidator GetValidator(Type type)
        {           
            Type generic = typeof(IValidator<>);    
            Type specific = generic.MakeGenericType(type);    

            return Injector.GetService(specific) as IValidator;
        }
    }
Everything works fine except for TryUpdateModel.

Thanks,
Maly
Coordinator
Apr 3, 2015 at 2:52 PM
Can you post the complete stack trace?
Apr 3, 2015 at 3:09 PM
Sorry about the french words in the stack trace :

The registered delegate for type IValidator threw an exception. The AddressModelValidator is registered as 'Web Request' lifestyle, but the instance is requested outside the context of a Web Request.
à SimpleInjector.InstanceProducer.GetInstance()
à SimpleInjector.Container.System.IServiceProvider.GetService(Type serviceType)
à MMBS.Core.FluentValidatorFactory.GetValidator(Type type) dans c:\Users\mlemire\Desktop\Tourmed\MMBS\MMBS.Core\Injection\FluentValidatorFactory.cs:ligne 34
à FluentValidation.Mvc.FluentValidationModelValidatorProvider.CreateValidator(ModelMetadata metadata, ControllerContext context) dans c:\Projects\FluentValidation\src\FluentValidation.Mvc4\FluentValidationModelValidatorProvider.cs:ligne 87
à FluentValidation.Mvc.FluentValidationModelValidatorProvider.GetValidators(ModelMetadata metadata, ControllerContext context) dans c:\Projects\FluentValidation\src\FluentValidation.Mvc4\FluentValidationModelValidatorProvider.cs:ligne 77
à System.Web.Mvc.ModelValidatorProviderCollection.d__0.MoveNext()
à System.Linq.Enumerable.WhereEnumerableIterator1.MoveNext()
à System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable
1 source)
à System.Web.Mvc.DefaultModelBinder.SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, Object value)
à System.Web.Mvc.DefaultModelBinder.BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
à System.Web.Mvc.DefaultModelBinder.BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
à System.Web.Mvc.DefaultModelBinder.BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Object model)
à System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
à System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
à System.Web.Mvc.DefaultModelBinder.GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
à System.Web.Mvc.DefaultModelBinder.BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
à System.Web.Mvc.DefaultModelBinder.BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
à System.Web.Mvc.DefaultModelBinder.BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Object model)
à System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
à System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
à System.Web.Mvc.DefaultModelBinder.GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
à System.Web.Mvc.DefaultModelBinder.BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
à System.Web.Mvc.DefaultModelBinder.BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
à System.Web.Mvc.DefaultModelBinder.BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Object model)
à System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
à System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
à System.Web.Mvc.Controller.TryUpdateModel[TModel](TModel model, String prefix, String[] includeProperties, String[] excludeProperties, IValueProvider valueProvider)
à System.Web.Mvc.Controller.TryUpdateModel[TModel](TModel model)
à TMA.Areas.Events.Controllers.ClientsDestinationAddressesController.d__14.MoveNext() dans c:\Users\mlemire\Desktop\Tourmed\TMA\Areas\Events\Controllers\ClientsDestinationAddressesController.cs:ligne 156
--- Fin de la trace de la pile à partir de l'emplacement précédent au niveau duquel l'exception a été levée ---
à System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
à System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
à System.Runtime.CompilerServices.TaskAwaiter.GetResult()
à System.Threading.Tasks.TaskHelpersExtensions.ThrowIfFaulted(Task task)
à System.Web.Mvc.Async.TaskAsyncActionDescriptor.EndExecute(IAsyncResult asyncResult)
à System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.b__36(IAsyncResult asyncResult)
à System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult1.CallEndDelegate(IAsyncResult asyncResult)
à System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase
1.End()
à System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
à System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
à System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.b__3d()
à System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.b__3f()
à System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass33.b__32(IAsyncResult asyncResult)
à System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult1.CallEndDelegate(IAsyncResult asyncResult)
à System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase
1.End()
à System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
à System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult)
à System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.<>c__DisplayClass2b.b__1c()
à System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.b__1e(IAsyncResult asyncResult)
Coordinator
Apr 3, 2015 at 3:22 PM
Can you show your ClientsDestinationAddressesController? I'm especially interested on the code (around line 156) that calls TryUpdateModel.
Apr 3, 2015 at 3:27 PM
The post is sent by JQuery post.
        /// <summary>
        /// Post ClientsDestinationAddress/createmodel
        /// </summary>
        [HttpPost]
        [Authorize(Roles = UsersRoles.TMAUser)]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> CreateModel([Bind(Include = @"
                                AddressEdit,
                                AddressEdit.Address,
                                AddressEdit.CountryEdit,
                                AddressEdit.StateEdit,
                                AddressEdit.ZipEdit,
                                AddressEdit.PhoneEdit, 
                                AddressEdit.FaxEdit,
                                ClientsDestinationAddress, 
                                ClientsDestinationAddressesList,
                                AddressPhoneList")] ClientsDestinationAddressEditViewModel model)
        {
            model.AddressEdit.Address.AddressPhones = (await JsonJS.DeserializeFromMVCAsync<AddressPhoneListViewModel>(model.AddressPhoneList)).AddressPhones.ToList();

            TryUpdateModel(model);

            if (ModelState.IsValid)
            {
                ClientsDestinationAddressListViewModel viewModel = await JsonJS.DeserializeFromMVCAsync<ClientsDestinationAddressListViewModel>(model.ClientsDestinationAddressesList);

                if (viewModel != null && viewModel.ClientsDestinationAddresses != null && viewModel.ClientsDestinationAddresses.Any())
                {
                    model.ClientsDestinationAddress.Id = Math.Min(0, viewModel.ClientsDestinationAddresses.Min(x => x.Id)) - 1;
                }
                else
                {
                    model.ClientsDestinationAddress.Id = -1;
                }

                model.ClientsDestinationAddress.Address = await RepoTMDB.Addresses.ViewModelToModel(model.AddressEdit);
                ((IList<ClientsDestinationAddressModel>)viewModel.ClientsDestinationAddresses).Add(model.ClientsDestinationAddress);

                SetFocusedRowKey(model.ClientsDestinationAddress.Id.ToString());
                return View("List", viewModel);
            }
            model.AddressEdit.CountryEdit.Countries = await RepoTMDB.Countries.GetVMList();
            model.AddressEdit.StateEdit.States = await RepoTMDB.States.GetVMList();

            return View("Create", model);
        }
JsonJavaScript is also injected.
container.RegisterPerWebRequest<IJsonJavaScript, JsonJavaScript>();
public class JsonJavaScript : IJsonJavaScript
    {
        private JsonSerializerSettings microsoftDateFormatSettings = new JsonSerializerSettings
        {
            DateFormatHandling = DateFormatHandling.MicrosoftDateFormat
        };

        /// <summary>
        /// Serialize an object to a javascript formated dates json string.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public async Task<string> SerializeToJSAsync(object value)
        {
            return await Task.Run(() => JsonConvert.SerializeObject(value, new JavaScriptDateTimeConverter()));
        }

        /// <summary>
        /// Deserialize a javascript formated dates json string to an object.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="json"></param>
        /// <returns></returns>
        public async Task<T> DeserializeFromJSAsync<T>(string json)
        {
            return await Task.Run(() => JsonConvert.DeserializeObject<T>(json, new JavaScriptDateTimeConverter()));
        }

        /// <summary>
        /// Serialize an object to microsft mvc formated dates json string.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public async Task<string> SerializeToMVCAsync(object value)
        {
            return await Task.Run(() => JsonConvert.SerializeObject(value, microsoftDateFormatSettings));
        }

        /// <summary>
        /// Deserialize a microsft mvc formated dates json string to an object.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="json"></param>
        /// <returns></returns>
        public async Task<T> DeserializeFromMVCAsync<T>(string json)
        {
            return await Task.Run(() => JsonConvert.DeserializeObject<T>(json, microsoftDateFormatSettings));
        }

        /// <summary>
        /// Serialize an object to ISO 8601 formated dates json string.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public async Task<string> SerializeToISOAsync(object value)
        {
            return await Task.Run(() => JsonConvert.SerializeObject(value));
        }

        /// <summary>
        /// Deserialize a ISO 8601 formated dates json string to an object.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="json"></param>
        /// <returns></returns>
        public async Task<T> DeserializeFromISOAsync<T>(string json)
        {
            return await Task.Run(() => JsonConvert.DeserializeObject<T>(json));
        }
    }
Apr 3, 2015 at 3:39 PM
Edited Apr 3, 2015 at 3:42 PM
I think I've found the bug.
If I create a non async JsonJS.DeserializeFromMVC I don't have the error anymore.
Seems like the Task.Run cannot synchronize with the current thread context and gets a null HttpContext.

Sorry for the bother,
Maly
Coordinator
Apr 3, 2015 at 4:04 PM
Are you sure HttpContext.Current is not null at the time that TryUpdateModel is called? Can you check this by setting a breakpoint on line 156 and inspect the value of HttpContext.Current when the break point hits? My intuition tells me that at that point HttpContext.Current is actually null, because the only way to get this exception is when HttpContext.Current is null at the time that that instance is being resolved.

This would typically be caused by calling ConfigureAwait(false) on a task as explained here, but this is not something you seem to be doing. Are you accidentally running .NET 4.0 (instead of 4.5), because async/await doesn't really work well in .NET 4.0. Or did you misconfigure your application and are you accidentally using the LegacyAspNetSynchronizationContext? You can read more about this here.

Let me know if this works out for you.
Coordinator
Apr 3, 2015 at 4:40 PM
Edited Apr 4, 2015 at 9:56 AM
Seems like my last post crossed yours. I'm sorry about that.

My experience is that although asynchronous programming has become much easier with C# 5 and .NET 4.5, it is most of the time still not worth the trouble. You can read a more elaborate rant about this here.
Apr 3, 2015 at 6:44 PM
Actually the HttpContext was not null right before TryUpdateModel.
Seems it was not accessible in TryUpdateModel because of the old Asp.Net synchronization context.

I was able to use the async method by using the new task synchronization context.

It's activated by setting this key in web.config.
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />

Thanks for your help,
Maly