File: Controllers\DefaultControllerActivatorTest.cs
Web Access
Project: src\src\Mvc\Mvc.Core\test\Microsoft.AspNetCore.Mvc.Core.Test.csproj (Microsoft.AspNetCore.Mvc.Core.Test)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing;
using Moq;
 
namespace Microsoft.AspNetCore.Mvc.Controllers;
 
public class DefaultControllerActivatorTest
{
    [Theory]
    [InlineData(typeof(TypeDerivingFromController))]
    [InlineData(typeof(PocoType))]
    public void Create_CreatesInstancesOfTypes(Type type)
    {
        // Arrange
        var activator = new DefaultControllerActivator(new TypeActivatorCache());
        var serviceProvider = new Mock<IServiceProvider>(MockBehavior.Strict);
 
        var httpContext = new DefaultHttpContext
        {
            RequestServices = serviceProvider.Object
        };
 
        var context = new ControllerContext(
            new ActionContext(
                httpContext,
                new RouteData(),
                new ControllerActionDescriptor
                {
                    ControllerTypeInfo = type.GetTypeInfo()
                }));
 
        // Act
        var instance = activator.Create(context);
 
        // Assert
        Assert.IsType(type, instance);
    }
 
    [Fact]
    public void Release_DisposesController_IfDisposable()
    {
        // Arrange
        var controller = new MyController();
        var activator = new DefaultControllerActivator(Mock.Of<ITypeActivatorCache>());
 
        // Act
        activator.Release(new ControllerContext(), controller);
 
        // Assert
        Assert.True(controller.Disposed);
    }
 
    [Fact]
    public async Task ReleaseAsync_AsynchronouslyDisposesController_IfAsyncDisposableAsync()
    {
        // Arrange
        var controller = new MyAsyncDisposableController();
        var activator = new DefaultControllerActivator(Mock.Of<ITypeActivatorCache>());
 
        // Act
        await activator.ReleaseAsync(new ControllerContext(), controller);
 
        // Assert
        Assert.True(controller.Disposed);
    }
 
    [Fact]
    public async Task ReleaseAsync_SynchronouslyDisposesController_IfDisposableAsync()
    {
        // Arrange
        var controller = new MyController();
        var activator = new DefaultControllerActivator(Mock.Of<ITypeActivatorCache>());
 
        // Act
        await activator.ReleaseAsync(new ControllerContext(), controller);
 
        // Assert
        Assert.True(controller.Disposed);
    }
 
    [Fact]
    public async Task ReleaseAsync_SynchronouslyDisposesController_PrefersDisposeAsyncOverDispose()
    {
        // Arrange
        var controller = new MyDisposableAndAsyncDisposableController();
        var activator = new DefaultControllerActivator(Mock.Of<ITypeActivatorCache>());
 
        // Act
        await activator.ReleaseAsync(new ControllerContext(), controller);
 
        // Assert
        Assert.False(controller.SyncDisposed);
        Assert.True(controller.AsyncDisposed);
    }
 
    [Fact]
    public void DefaultControllerActivator_ReleasesNonIDisposableController()
    {
        // Arrange
        var activator = new DefaultControllerActivator(Mock.Of<ITypeActivatorCache>());
        var controller = new object();
 
        // Act + Assert (does not throw)
        activator.Release(Mock.Of<ControllerContext>(), controller);
    }
 
    [Fact]
    public void Create_TypeActivatesTypesWithServices()
    {
        // Arrange
        var activator = new DefaultControllerActivator(new TypeActivatorCache());
        var serviceProvider = new Mock<IServiceProvider>(MockBehavior.Strict);
        var testService = new TestService();
        serviceProvider.Setup(s => s.GetService(typeof(TestService)))
                       .Returns(testService)
                       .Verifiable();
 
        var httpContext = new DefaultHttpContext
        {
            RequestServices = serviceProvider.Object
        };
 
        var context = new ControllerContext(
            new ActionContext(
                httpContext,
                new RouteData(),
                new ControllerActionDescriptor
                {
                    ControllerTypeInfo = typeof(TypeDerivingFromControllerWithServices).GetTypeInfo()
                }));
 
        // Act
        var instance = activator.Create(context);
 
        // Assert
        var controller = Assert.IsType<TypeDerivingFromControllerWithServices>(instance);
        Assert.Same(testService, controller.TestService);
        serviceProvider.Verify();
    }
 
    public class Controller
    {
    }
 
    private class TypeDerivingFromController : Controller
    {
    }
 
    private class TypeDerivingFromControllerWithServices : Controller
    {
        public TypeDerivingFromControllerWithServices(TestService service)
        {
            TestService = service;
        }
 
        public TestService TestService { get; }
    }
 
    private IServiceProvider GetServices()
    {
        var metadataProvider = new EmptyModelMetadataProvider();
        var services = new Mock<IServiceProvider>();
        services
            .Setup(s => s.GetService(typeof(IUrlHelper)))
            .Returns(Mock.Of<IUrlHelper>());
        services
            .Setup(s => s.GetService(typeof(IModelMetadataProvider)))
            .Returns(metadataProvider);
        services
            .Setup(s => s.GetService(typeof(IObjectModelValidator)))
            .Returns(new DefaultObjectValidator(metadataProvider, new List<IModelValidatorProvider>(), new MvcOptions()));
        return services.Object;
    }
 
    private class PocoType
    {
    }
 
    private class TestService
    {
    }
 
    private class MyController : IDisposable
    {
        public bool Disposed { get; set; }
 
        public void Dispose()
        {
            Disposed = true;
        }
    }
 
    private class MyAsyncDisposableController : IAsyncDisposable
    {
        public bool Disposed { get; set; }
 
        public void Dispose()
        {
        }
 
        public ValueTask DisposeAsync()
        {
            Disposed = true;
            return default;
        }
    }
 
    private class MyDisposableAndAsyncDisposableController : IDisposable, IAsyncDisposable
    {
        public bool AsyncDisposed { get; set; }
        public bool SyncDisposed { get; set; }
 
        public void Dispose()
        {
            SyncDisposed = true;
        }
 
        public ValueTask DisposeAsync()
        {
            AsyncDisposed = true;
            return default;
        }
    }
}