Skip to content

Commit d4dd90a

Browse files
brettsammhoeger
authored andcommitted
allowing AddDataProtection()
1 parent 2198edd commit d4dd90a

File tree

4 files changed

+119
-5
lines changed

4 files changed

+119
-5
lines changed

src/WebJobs.Script.WebHost/DependencyInjection/DependencyValidator/DependencyValidator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ private static ExpectedDependencyBuilder CreateExpectedDependencies()
5050
.Optional<FunctionsScaleMonitorService>()
5151
.Optional<FuncAppFileProvisioningService>() // Used by powershell.
5252
.Optional<JobHostService>() // Missing when host is offline.
53-
.Optional<FunctionsSyncService>(); // Conditionally registered.
53+
.Optional<FunctionsSyncService>() // Conditionally registered.
54+
.OptionalExternal("Microsoft.AspNetCore.DataProtection.Internal.DataProtectionHostedService", "Microsoft.AspNetCore.DataProtection", "adb9793829ddae60"); // Popularly-registered by DataProtection.
5455

5556
expected.ExpectSubcollection<ILoggerProvider>()
5657
.Expect<AzureMonitorDiagnosticLoggerProvider>()

src/WebJobs.Script.WebHost/DependencyInjection/DependencyValidator/ExpectedCollectionBuilder.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,11 @@ public ExpectedCollectionBuilder Optional<TImplementation>(ServiceLifetime lifet
4343
_match.AddOptional<TImplementation>(lifetime);
4444
return this;
4545
}
46+
47+
public ExpectedCollectionBuilder OptionalExternal(string externalTypeName, string assemblyName, string assemblyPublicKeyToken)
48+
{
49+
_match.AddOptionalExternal(externalTypeName, assemblyName, assemblyPublicKeyToken);
50+
return this;
51+
}
4652
}
4753
}

src/WebJobs.Script.WebHost/DependencyInjection/DependencyValidator/ServiceMatch.cs

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ internal class ServiceMatch
1414
{
1515
private readonly ICollection<ServiceDescriptor> _requiredDescriptors = new Collection<ServiceDescriptor>();
1616
private readonly ICollection<ServiceDescriptor> _optionalDescriptors = new Collection<ServiceDescriptor>();
17+
18+
// We may not be able to load the type at all -- for example third-party services that we want to allow.
19+
// For this, track the types as strings and we'll match them during IsMatch. These will always be optional.
20+
private readonly ICollection<ExternalService> _optionalExternalServices = new Collection<ExternalService>();
21+
1722
private readonly MatchType _match;
1823

1924
private ServiceMatch(Type serviceType, MatchType match)
@@ -136,6 +141,16 @@ public void AddOptional<TImplementation>(ServiceLifetime lifetime)
136141
_optionalDescriptors.Add(desc);
137142
}
138143

144+
public void AddOptionalExternal(string externalTypeName, string assemblyName, string assemblyPublicKey)
145+
{
146+
if (_match != MatchType.Collection)
147+
{
148+
throw new InvalidOperationException("Optional matches only apply to Collections.");
149+
}
150+
151+
_optionalExternalServices.Add(new ExternalService(externalTypeName, assemblyName, assemblyPublicKey));
152+
}
153+
139154
public IEnumerable<InvalidServiceDescriptor> FindInvalidServices(IServiceCollection services)
140155
{
141156
if (services == null)
@@ -163,7 +178,7 @@ public IEnumerable<InvalidServiceDescriptor> FindInvalidServices(IServiceCollect
163178
return Enumerable.Empty<InvalidServiceDescriptor>();
164179

165180
case MatchType.Collection:
166-
return FindCollectionDifferences(registered, _requiredDescriptors, _optionalDescriptors);
181+
return FindCollectionDifferences(registered, _requiredDescriptors, _optionalDescriptors, _optionalExternalServices);
167182

168183
case MatchType.Subcollection:
169184
return FindMissingServicesInCollection(registered, _requiredDescriptors);
@@ -201,13 +216,19 @@ private static bool IsMatch(ServiceDescriptor expected, ServiceDescriptor regist
201216
expected.ServiceType == registered.ServiceType;
202217
}
203218

204-
private static IEnumerable<InvalidServiceDescriptor> FindCollectionDifferences(IEnumerable<ServiceDescriptor> registeredServices, IEnumerable<ServiceDescriptor> expectedServices, IEnumerable<ServiceDescriptor> optionalServices)
219+
private static IEnumerable<InvalidServiceDescriptor> FindCollectionDifferences(IEnumerable<ServiceDescriptor> registeredServices, IEnumerable<ServiceDescriptor> expectedServices,
220+
IEnumerable<ServiceDescriptor> optionalServices, IEnumerable<ExternalService> optionalExternalServices)
205221
{
206222
// Don't report optional services as missing.
207223
var missingServices = FindMissingServicesInCollection(registeredServices, expectedServices);
208224

209225
var extraServices = registeredServices
210-
.Where(r => !expectedServices.Any(e => IsMatch(e, r)) && !optionalServices.Any(o => IsMatch(o, r)))
226+
.Where(r =>
227+
{
228+
return !expectedServices.Any(e => IsMatch(e, r))
229+
&& !optionalServices.Any(o => IsMatch(o, r))
230+
&& !optionalExternalServices.Any(o => o.IsMatch(r.ImplementationType));
231+
})
211232
.Select(p => new InvalidServiceDescriptor(p, InvalidServiceDescriptorReason.Invalid));
212233

213234
return missingServices.Concat(extraServices);
@@ -219,5 +240,52 @@ private static IEnumerable<InvalidServiceDescriptor> FindMissingServicesInCollec
219240
.Where(e => !registeredServices.Any(r => IsMatch(e, r)))
220241
.Select(p => new InvalidServiceDescriptor(p, InvalidServiceDescriptorReason.Missing));
221242
}
243+
244+
private class ExternalService
245+
{
246+
public ExternalService(string typeName, string assemblyName, string assemblyPublicKey)
247+
{
248+
TypeName = typeName;
249+
AssemblyName = assemblyName;
250+
AssemblyPublicKey = assemblyPublicKey;
251+
}
252+
253+
public string TypeName { get; }
254+
255+
public string AssemblyName { get; }
256+
257+
public string AssemblyPublicKey { get; }
258+
259+
public bool IsMatch(Type serviceType)
260+
{
261+
if (serviceType == null)
262+
{
263+
return false;
264+
}
265+
266+
var serviceAssemblyName = serviceType.Assembly.GetName();
267+
268+
return serviceType.FullName == TypeName
269+
&& serviceAssemblyName.Name == AssemblyName
270+
&& GetPublicKeyTokenString(serviceAssemblyName.GetPublicKeyToken()) == AssemblyPublicKey;
271+
}
272+
273+
private static string GetPublicKeyTokenString(byte[] token)
274+
{
275+
if (token == null || token.Length == 0)
276+
{
277+
return null;
278+
}
279+
280+
string tokenString = string.Empty;
281+
282+
for (int i = 0; i < token.Length; i++)
283+
{
284+
tokenString += string.Format("{0:x2}", token[i]);
285+
}
286+
287+
return tokenString;
288+
}
289+
}
222290
}
223-
}
291+
}

test/WebJobs.Script.Tests/Configuration/ExpectedDependencyBuilderTests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,14 +253,21 @@ public void ExpectCollection_OptionalService_Exists()
253253
services.AddSingleton<ITestSomething, TestSomethingA>();
254254
services.AddSingleton<ITestSomething, TestSomethingB>();
255255
services.AddSingleton<ITestSomething, TestSomethingC>();
256+
services.AddSingleton<ITestSomething, TestSomethingD>();
257+
258+
services.AddSingleton<IJobHost, JobHost>();
256259

257260
ExpectedDependencyBuilder validator = new ExpectedDependencyBuilder();
258261

259262
validator.ExpectCollection<ITestSomething>()
260263
.Expect<TestSomethingA>()
261264
.Optional<TestSomethingB>()
265+
.OptionalExternal(typeof(TestSomethingD).FullName, typeof(TestSomethingD).Assembly.GetName().Name, null)
262266
.Expect<TestSomethingC>();
263267

268+
validator.ExpectCollection<IJobHost>()
269+
.OptionalExternal("Microsoft.Azure.WebJobs.JobHost", "Microsoft.Azure.WebJobs.Host", "31bf3856ad364e35");
270+
264271
var invalidDescriptors = validator.FindInvalidServices(services);
265272
Assert.Empty(invalidDescriptors);
266273
}
@@ -353,6 +360,38 @@ public void ExpectSubcollection_MissingServices()
353360
Assert.Equal(typeof(ITestSomething), missingC.Descriptor.ServiceType);
354361
}
355362

363+
[Fact]
364+
public void ExpectCollection_OptionalExternalService_IncorrectKey()
365+
{
366+
ServiceCollection services = new ServiceCollection();
367+
services.AddSingleton<IJobHost, JobHost>();
368+
369+
ExpectedDependencyBuilder validator = new ExpectedDependencyBuilder();
370+
371+
validator.ExpectCollection<IJobHost>()
372+
.OptionalExternal("Microsoft.Azure.WebJobs.JobHost", "Microsoft.Azure.WebJobs.Host", "abcdef");
373+
374+
var invalidDescriptor = validator.FindInvalidServices(services).Single();
375+
Assert.Equal(InvalidServiceDescriptorReason.Invalid, invalidDescriptor.Reason);
376+
Assert.Equal(typeof(IJobHost), invalidDescriptor.Descriptor.ServiceType);
377+
}
378+
379+
[Fact]
380+
public void ExpectCollection_OptionalExternalService_Factory()
381+
{
382+
ServiceCollection services = new ServiceCollection();
383+
services.AddSingleton<IJobHost>(s => (JobHost)null);
384+
385+
ExpectedDependencyBuilder validator = new ExpectedDependencyBuilder();
386+
387+
validator.ExpectCollection<IJobHost>()
388+
.OptionalExternal("Microsoft.Azure.WebJobs.JobHost", "Microsoft.Azure.WebJobs.Host", "31bf3856ad364e35");
389+
390+
var invalidDescriptor = validator.FindInvalidServices(services).Single();
391+
Assert.Equal(InvalidServiceDescriptorReason.Invalid, invalidDescriptor.Reason);
392+
Assert.Equal(typeof(IJobHost), invalidDescriptor.Descriptor.ServiceType);
393+
}
394+
356395
[Fact]
357396
public void ExpectNone()
358397
{

0 commit comments

Comments
 (0)