[Fixes #3907] Improve MVC error when MVC services aren't registered

This commit is contained in:
jacalvar 2016-01-13 16:10:00 -08:00
parent c7f6ed4445
commit 82381d97c2
8 changed files with 51 additions and 86 deletions

View File

@ -2,10 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Infrastructure;
using Microsoft.AspNet.Mvc.Internal;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNet.Builder
{
@ -77,7 +79,13 @@ namespace Microsoft.AspNet.Builder
// Verify if AddMvc was done before calling UseMvc
// We use the MvcMarkerService to make sure if all the services were added.
MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices);
if (app.ApplicationServices.GetService(typeof(MvcMarkerService)) == null)
{
throw new InvalidOperationException(Resources.FormatUnableToFindServices(
nameof(IServiceCollection),
"AddMvc",
"ConfigureServices(...)"));
}
var routes = new RouteBuilder(app)
{

View File

@ -119,10 +119,6 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
var services = context.RequestServices;
// Verify if AddMvc was done before calling UseMvc
// We use the MvcMarkerService to make sure if all the services were added.
MvcServicesHelper.ThrowIfMvcNotRegistered(services);
// The IActionContextAccessor is optional. We want to avoid the overhead of using CallContext
// if possible.
_actionContextAccessor = services.GetService<IActionContextAccessor>();

View File

@ -1,11 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNet.Mvc.Internal
{
/// <summary>
/// This is a Marker class which is used to determine if all the services were added
/// to when Mvc is loaded.
/// A marker class used to determine if all the MVC services were added
/// to the <see cref="IServiceCollection"/> before MVC is configured.
/// </summary>
public class MvcMarkerService
{

View File

@ -1,30 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Mvc.Core;
namespace Microsoft.AspNet.Mvc.Internal
{
/// <summary>
/// Helper class which contains MvcServices related helpers.
/// </summary>
public static class MvcServicesHelper
{
/// <summary>
/// Throws InvalidOperationException when MvcMarkerService is not present
/// in the list of services.
/// </summary>
/// <param name="services">The list of services.</param>
public static void ThrowIfMvcNotRegistered(IServiceProvider services)
{
if (services.GetService(typeof(MvcMarkerService)) == null)
{
throw new InvalidOperationException(Resources.FormatUnableToFindServices(
"IServiceCollection.AddMvc()",
"IApplicationBuilder.ConfigureServices(...)",
"IApplicationBuilder.UseMvc(...)"));
}
}
}
}

View File

@ -475,7 +475,7 @@ namespace Microsoft.AspNet.Mvc.Core
}
/// <summary>
/// Unable to find the required services. Please add all the required services by calling '{0}' inside the call to '{1}' or '{2}' in the application startup code.
/// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{4}' in the application startup code.
/// </summary>
internal static string UnableToFindServices
{
@ -483,11 +483,11 @@ namespace Microsoft.AspNet.Mvc.Core
}
/// <summary>
/// Unable to find the required services. Please add all the required services by calling '{0}' inside the call to '{1}' or '{2}' in the application startup code.
/// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{4}' in the application startup code.
/// </summary>
internal static string FormatUnableToFindServices(object p0, object p1, object p2)
internal static string FormatUnableToFindServices(object p0, object p1, object p4)
{
return string.Format(CultureInfo.CurrentCulture, GetString("UnableToFindServices"), p0, p1, p2);
return string.Format(CultureInfo.CurrentCulture, GetString("UnableToFindServices"), p0, p1, p4);
}
/// <summary>

View File

@ -208,7 +208,7 @@
<value>An unescaped '[' token is not allowed inside of a replacement token. Use '[[' to escape.</value>
</data>
<data name="UnableToFindServices" xml:space="preserve">
<value>Unable to find the required services. Please add all the required services by calling '{0}' inside the call to '{1}' or '{2}' in the application startup code.</value>
<value>Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code.</value>
</data>
<data name="AttributeRoute_DuplicateNames_Item" xml:space="preserve">
<value>Action: '{0}' - Template: '{1}'</value>

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Core.Builder
{
public class MvcApplicationBuilderExtensionsTest
{
[Fact]
public void UseMvc_ThrowsInvalidOperationException_IfMvcMarkerServiceIsNotRegistered()
{
// Arrange
var applicationBuilderMock = new Mock<IApplicationBuilder>();
applicationBuilderMock
.Setup(s => s.ApplicationServices)
.Returns(Mock.Of<IServiceProvider>());
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => applicationBuilderMock.Object.UseMvc(rb => { }));
Assert.Equal(
"Unable to find the required services. Please add all the required services by calling " +
"'IServiceCollection.AddMvc' inside the call to 'ConfigureServices(...)' " +
"in the application startup code.",
exception.Message);
}
}
}

View File

@ -1,44 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Internal;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class MvcServicesHelperTests
{
[Fact]
public void MvcServicesHelperThrowsIfServiceIsAbsent()
{
// Arrange
var services = new Mock<IServiceProvider>();
services.Setup(o => o.GetService(typeof(IEnumerable<MvcMarkerService>)))
.Returns(new List<MvcMarkerService>());
var expectedMessage = "Unable to find the required services. Please add all the required " +
"services by calling 'IServiceCollection.AddMvc()' inside the call to 'IApplicationBuilder.ConfigureServices(...)' " +
"or 'IApplicationBuilder.UseMvc(...)' in the application startup code.";
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(
() => MvcServicesHelper.ThrowIfMvcNotRegistered(services.Object));
Assert.Equal(expectedMessage, ex.Message);
}
[Fact]
public void MvcServicesHelperDoesNotThrowIfServiceExists()
{
// Arrange
var services = new Mock<IServiceProvider>();
var expectedOutput = new MvcMarkerService();
services.Setup(o => o.GetService(typeof(MvcMarkerService)))
.Returns(expectedOutput);
// Act & Assert (does not throw)
MvcServicesHelper.ThrowIfMvcNotRegistered(services.Object);
}
}
}