Jimmyt1988
Jimmyt1988

Reputation: 21176

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

Upvotes: 1

Views: 1369

Answers (2)

Jimmyt1988
Jimmyt1988

Reputation: 21176

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);
    }
    ...
}

Upvotes: 0

user369142
user369142

Reputation: 2915

I did some work with Umbraco, running it from a console app and then using the Umbraco API to call into Umbraco. I believe I based it on this project: https://github.com/sitereactor/umbraco-console-example

Might be useful.

Upvotes: 1

Related Questions