Search code examples
umbracohttpcontext

System.ArgumentNullException: Value cannot be null - Umbraco HTTPContext on save and publish


source: https://gist.github.com/sniffdk/7600822

The following code is run by an activity outside of an http request, so i need to mock the http context.

I have mocked the http context like so:

public class GetUmbracoServiceMockedHttpContext : IGetUmbracoService
{
    private UmbracoHelper umbracoHelper;

    public T GetService<T>()
        where T : IService
    {
        UmbracoContext context = UmbracoContext.Current;

        if (context == null)
        {
            var dummyHttpContext = new HttpContextWrapper(new HttpContext(new SimpleWorkerRequest("blah.aspx", "", new StringWriter())));
            context = UmbracoContext.EnsureContext(
                dummyHttpContext,
                ApplicationContext.Current,
                new WebSecurity(dummyHttpContext, ApplicationContext.Current),
                UmbracoConfig.For.UmbracoSettings(),
                UrlProviderResolver.Current.Providers,
                false);
        }

        var serviceTypeProperty = context.Application.Services
            .GetType()
            .GetProperties()
            .SingleOrDefault(x => x.PropertyType == typeof(T));

        if (serviceTypeProperty == null)
        {
            return default(T);
        }

        return (T)serviceTypeProperty
            .GetValue(context.Application.Services);
    }
}

I inject this IGetUmbracoService service into a controller and call:

service.GetService<IContentService>().SaveAndPublishWithStatus(item);

... The following error occurs.

System.ArgumentNullException: Value cannot be null. Parameter name: httpContext at System.Web.HttpContextWrapper..ctor(HttpContext httpContext) at Umbraco.Web.SingletonHttpContextAccessor.get_Value() at Umbraco.Web.RequestLifespanMessagesFactory.Get() at Umbraco.Core.Services.ContentService.SaveAndPublishDo(IContent content, Int32 userId, Boolean raiseEvents) at Umbraco.Core.Services.ContentService.Umbraco.Core.Services.IContentServiceOperations.SaveAndPublish(IContent content, Int32 userId, Boolean raiseEvents) at Umbraco.Core.Services.ContentService.SaveAndPublishWithStatus(IContent content, Int32 userId, Boolean raiseEvents)

How do i mock the http context without using the frowned upon HttpContext.Current = ...?


I assume the relevant issue comes from:

RequestLifespanMessagesFactory.cs

which in turn is calling an implementation of this:

SingletonHttpContextAccessor.cs


Solution

  • Thanks user369142. This is what ended up working:

    I also had to make sure that i was not raising any events on the SaveandPublish calls... as the HttpContext expects there to be messages registered in the context but we do not mock any... If you make sure raise events is false, it skips over the code that cares about that.

    public class CustomSingletonHttpContextAccessor : IHttpContextAccessor
    {
        public HttpContextBase Value
        {
            get
            {
                HttpContext context = HttpContext.Current;
                if (context == null)
                {
                    context = new HttpContext(new HttpRequest(null, "http://mockurl.com", null), new HttpResponse(null));
                }
    
                return new HttpContextWrapper(context);
            }
        }
    }
    
    public class CustomRequestLifespanMessagesFactory : IEventMessagesFactory
    {
        private readonly IHttpContextAccessor _httpAccessor;
    
        public CustomRequestLifespanMessagesFactory(IHttpContextAccessor httpAccessor)
        {
            if (httpAccessor == null)
            {
                throw new ArgumentNullException("httpAccessor");
            }
    
            _httpAccessor = httpAccessor;
        }
    
        public EventMessages Get()
        {
            if (_httpAccessor.Value.Items[typeof(CustomRequestLifespanMessagesFactory).Name] == null)
            {
                _httpAccessor.Value.Items[typeof(CustomRequestLifespanMessagesFactory).Name] = new EventMessages();
            }
    
            return (EventMessages)_httpAccessor.Value.Items[typeof(CustomRequestLifespanMessagesFactory).Name];
        }
    }
    
    public class CustomBootManager : WebBootManager
    {
        public CustomBootManager(UmbracoApplicationBase umbracoApplication)
            : base(umbracoApplication)
        {
        }
    
        protected override ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory)
        {
            //use a request based messaging factory
            var evtMsgs = new CustomRequestLifespanMessagesFactory(new CustomSingletonHttpContextAccessor());
    
            return new ServiceContext(
                new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()),
                new PetaPocoUnitOfWorkProvider(dbFactory),
                new FileUnitOfWorkProvider(),
                new PublishingStrategy(evtMsgs, ProfilingLogger.Logger),
                ApplicationCache,
                ProfilingLogger.Logger,
                evtMsgs);
        }
    }
    
    public class CustomUmbracoApplication : Umbraco.Web.UmbracoApplication
    {
        ...
        protected override IBootManager GetBootManager()
        {
            return new CustomBootManager(this);
        }
        ...
    }