Skip to content

Commit 630d8e2

Browse files
committed
restricting DI services
1 parent 04f8d66 commit 630d8e2

15 files changed

+1049
-3
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Text;
6+
using Microsoft.Azure.WebJobs.Host.Loggers;
7+
using Microsoft.Azure.WebJobs.Hosting;
8+
using Microsoft.Azure.WebJobs.Script.Diagnostics;
9+
using Microsoft.Azure.WebJobs.Script.Eventing;
10+
using Microsoft.Azure.WebJobs.Script.Rpc;
11+
using Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics;
12+
using Microsoft.Extensions.DependencyInjection;
13+
using Microsoft.Extensions.Hosting;
14+
using Microsoft.Extensions.Logging;
15+
16+
namespace Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection
17+
{
18+
internal class DependencyValidator : IDependencyValidator
19+
{
20+
private static readonly ExpectedDependencyBuilder _expectedDependencies = CreateExpectedDependencies();
21+
22+
private static ExpectedDependencyBuilder CreateExpectedDependencies()
23+
{
24+
var expected = new ExpectedDependencyBuilder();
25+
26+
expected.ExpectNone<IScriptEventManager>();
27+
expected.ExpectNone<IEventGenerator>();
28+
29+
expected.Expect<ILoggerFactory, ScriptLoggerFactory>();
30+
31+
expected.Expect<IMetricsLogger, WebHostMetricsLogger>();
32+
33+
expected.Expect<IEventCollectorFactory>("Microsoft.Azure.WebJobs.Logging.EventCollectorFactory");
34+
35+
expected.ExpectCollection<IEventCollectorProvider>()
36+
.Expect<FunctionInstanceLogCollectorProvider>()
37+
.Expect("Microsoft.Azure.WebJobs.Logging.FunctionResultAggregatorProvider");
38+
39+
expected.ExpectCollection<IHostedService>()
40+
.Expect<JobHostService>()
41+
.Expect<JobHostService>("Microsoft.Azure.WebJobs.Hosting.OptionsLoggingService")
42+
.Expect<PrimaryHostCoordinator>()
43+
.Expect<HttpInitializationService>()
44+
.Expect<FileMonitoringService>()
45+
.Expect<LanguageWorkerConsoleLogService>()
46+
.Optional<FunctionsSyncService>(); // This service is conditionally registered.
47+
48+
expected.ExpectSubcollection<ILoggerProvider>()
49+
.Expect<AzureMonitorDiagnosticLoggerProvider>()
50+
.Expect<FunctionFileLoggerProvider>()
51+
.Expect<HostFileLoggerProvider>()
52+
.Expect<SystemLoggerProvider>()
53+
.Expect<UserLogMetricsLoggerProvider>();
54+
55+
return expected;
56+
}
57+
58+
public void Validate(IServiceCollection services)
59+
{
60+
StringBuilder sb = new StringBuilder();
61+
62+
foreach (InvalidServiceDescriptor invalidDescriptor in _expectedDependencies.FindInvalidServices(services))
63+
{
64+
sb.AppendLine($" [{invalidDescriptor.Reason}] {FormatServiceDescriptor(invalidDescriptor.Descriptor)}");
65+
}
66+
67+
if (sb.Length > 0)
68+
{
69+
string msg = $"The following service registrations did not match the expected services:{Environment.NewLine}{sb.ToString()}";
70+
throw new InvalidHostServicesException(msg);
71+
}
72+
}
73+
74+
private static string FormatServiceDescriptor(ServiceDescriptor descriptor)
75+
{
76+
string format = $"{nameof(descriptor.ServiceType)}: {descriptor.ServiceType}, {nameof(descriptor.Lifetime)}: {descriptor.Lifetime}";
77+
78+
if (descriptor.ImplementationFactory != null)
79+
{
80+
format += $", {nameof(descriptor.ImplementationFactory)}: {descriptor.ImplementationFactory}";
81+
}
82+
83+
if (descriptor.ImplementationInstance != null)
84+
{
85+
format += $", {nameof(descriptor.ImplementationInstance)}: {descriptor.ImplementationInstance}";
86+
}
87+
88+
if (descriptor.ImplementationType != null)
89+
{
90+
format += $", {nameof(descriptor.ImplementationType)}: {descriptor.ImplementationType}";
91+
}
92+
93+
return format;
94+
}
95+
}
96+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using Microsoft.Extensions.DependencyInjection;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection
7+
{
8+
internal class ExpectedCollectionBuilder
9+
{
10+
private readonly ServiceMatch _match;
11+
12+
public ExpectedCollectionBuilder(ServiceMatch match)
13+
{
14+
_match = match;
15+
}
16+
17+
public ExpectedCollectionBuilder Expect<TImplementation>(ServiceLifetime lifetime = ServiceLifetime.Singleton)
18+
{
19+
_match.Add<TImplementation>(lifetime);
20+
return this;
21+
}
22+
23+
public ExpectedCollectionBuilder Expect(string typeName, ServiceLifetime lifetime = ServiceLifetime.Singleton)
24+
{
25+
_match.Add(typeName, lifetime);
26+
return this;
27+
}
28+
29+
public ExpectedCollectionBuilder Expect<TPeerType>(string typeName, ServiceLifetime lifetime = ServiceLifetime.Singleton)
30+
{
31+
_match.Add<TPeerType>(typeName, lifetime);
32+
return this;
33+
}
34+
35+
public ExpectedCollectionBuilder ExpectFactory<TFactoryType>(ServiceLifetime lifetime = ServiceLifetime.Singleton)
36+
{
37+
_match.AddFactory<TFactoryType>(lifetime);
38+
return this;
39+
}
40+
41+
public ExpectedCollectionBuilder Optional<TImplementation>(ServiceLifetime lifetime = ServiceLifetime.Singleton)
42+
{
43+
_match.AddOptional<TImplementation>(lifetime);
44+
return this;
45+
}
46+
}
47+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Microsoft.Extensions.DependencyInjection;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection
9+
{
10+
internal class ExpectedDependencyBuilder
11+
{
12+
private readonly IDictionary<Type, ServiceMatch> _serviceMatches = new Dictionary<Type, ServiceMatch>();
13+
14+
private void AddNewMatch<T>(ServiceMatch match)
15+
{
16+
if (!_serviceMatches.TryAdd(typeof(T), match))
17+
{
18+
throw new InvalidOperationException($"Type {typeof(T)} has already been registered as expected.");
19+
}
20+
}
21+
22+
public void Expect<TService, TImplementation>(ServiceLifetime lifetime = ServiceLifetime.Singleton)
23+
{
24+
ServiceMatch match = ServiceMatch.CreateMatch<TService>();
25+
AddNewMatch<TService>(match);
26+
match.Add<TImplementation>(lifetime);
27+
}
28+
29+
public void Expect<TService>(string typeName, ServiceLifetime lifetime = ServiceLifetime.Singleton)
30+
{
31+
if (typeName == null)
32+
{
33+
throw new ArgumentNullException(nameof(typeName));
34+
}
35+
36+
ServiceMatch match = ServiceMatch.CreateMatch<TService>();
37+
AddNewMatch<TService>(match);
38+
match.Add<TService>(typeName, lifetime);
39+
}
40+
41+
public void ExpectFactory<TService>(ServiceLifetime lifetime = ServiceLifetime.Singleton)
42+
{
43+
ExpectFactory<TService, TService>(lifetime);
44+
}
45+
46+
public void ExpectFactory<TService, TPeerType>(ServiceLifetime lifetime = ServiceLifetime.Singleton)
47+
{
48+
ServiceMatch match = ServiceMatch.CreateMatch<TService>();
49+
AddNewMatch<TService>(match);
50+
match.AddFactory<TPeerType>(lifetime);
51+
}
52+
53+
public void Expect<TService, TPeerType>(string typeName, ServiceLifetime lifetime = ServiceLifetime.Singleton)
54+
{
55+
if (typeName == null)
56+
{
57+
throw new ArgumentNullException(nameof(typeName));
58+
}
59+
60+
ServiceMatch match = ServiceMatch.CreateMatch<TService>();
61+
AddNewMatch<TService>(match);
62+
match.Add<TPeerType>(typeName, lifetime);
63+
}
64+
65+
public void ExpectNone<TService>()
66+
{
67+
ServiceMatch match = ServiceMatch.CreateNoneMatch<TService>();
68+
AddNewMatch<TService>(match);
69+
}
70+
71+
public ExpectedCollectionBuilder ExpectCollection<TService>()
72+
{
73+
ServiceMatch match = ServiceMatch.CreateCollectionMatch<TService>();
74+
AddNewMatch<TService>(match);
75+
return new ExpectedCollectionBuilder(match);
76+
}
77+
78+
public ExpectedCollectionBuilder ExpectSubcollection<TService>()
79+
{
80+
ServiceMatch match = ServiceMatch.CreateSubcollectionMatch<TService>();
81+
AddNewMatch<TService>(match);
82+
return new ExpectedCollectionBuilder(match);
83+
}
84+
85+
public IEnumerable<InvalidServiceDescriptor> FindInvalidServices(IServiceCollection services)
86+
{
87+
List<InvalidServiceDescriptor> invalidDescriptors = new List<InvalidServiceDescriptor>();
88+
89+
foreach (ServiceMatch match in _serviceMatches.Values)
90+
{
91+
invalidDescriptors.AddRange(match.FindInvalidServices(services));
92+
}
93+
94+
return invalidDescriptors;
95+
}
96+
}
97+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using Microsoft.Extensions.DependencyInjection;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection
7+
{
8+
public interface IDependencyValidator
9+
{
10+
void Validate(IServiceCollection services);
11+
}
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using Microsoft.Extensions.DependencyInjection;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection
7+
{
8+
internal class InvalidServiceDescriptor
9+
{
10+
public InvalidServiceDescriptor(ServiceDescriptor descriptor, InvalidServiceDescriptorReason reason)
11+
{
12+
Descriptor = descriptor;
13+
Reason = reason;
14+
}
15+
16+
public InvalidServiceDescriptorReason Reason { get; private set; }
17+
18+
public ServiceDescriptor Descriptor { get; private set; }
19+
}
20+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection
5+
{
6+
internal enum InvalidServiceDescriptorReason
7+
{
8+
Invalid,
9+
Missing
10+
}
11+
}

0 commit comments

Comments
 (0)