Passing requesting service object as null in .NET Core - c#

My ConfigureService method in startup.cs class
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddNewtonsoftJson();
services.AddSingleton<IConfiguration>(this.Configuration);
// Load settings
var settings = new BotSettings();
Configuration.Bind(settings);
services.AddDbContext<BotDbContext>(options => options.UseSqlServer(settings.ConnectionString));
// Create the credential provider to be used with the Bot Framework Adapter.
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
services.AddSingleton<BotAdapter>(sp => (BotFrameworkHttpAdapter)sp.GetService<IBotFrameworkHttpAdapter>());
// Register AuthConfiguration to enable custom claim validation for skills.
services.AddSingleton(sp => new AuthenticationConfiguration { ClaimsValidator = new AllowedCallersClaimsValidator(settings.SkillConfiguration) });
// register components.
ComponentRegistration.Add(new DialogsComponentRegistration());
ComponentRegistration.Add(new DeclarativeComponentRegistration());
ComponentRegistration.Add(new AdaptiveComponentRegistration());
ComponentRegistration.Add(new LanguageGenerationComponentRegistration());
ComponentRegistration.Add(new QnAMakerComponentRegistration());
ComponentRegistration.Add(new LuisComponentRegistration());
// register Handoff
ConfigureHandOff(services, settings);
// This is for custom action component registration.
ComponentRegistration.Add(new CustomActionComponentRegistration());
// Register the skills client and skills request handler.
services.AddSingleton<SkillConversationIdFactoryBase, SkillConversationIdFactory>();
services.AddHttpClient<BotFrameworkClient, SkillHttpClient>();
services.AddSingleton<ChannelServiceHandler, SkillHandler>();
services.AddApplicationInsightsTelemetry(settings?.ApplicationInsights?.InstrumentationKey ?? string.Empty);
services.AddSingleton<ITelemetryInitializer, OperationCorrelationTelemetryInitializer>();
services.AddSingleton<ITelemetryInitializer, TelemetryBotIdInitializer>();
services.AddSingleton<IBotTelemetryClient, BotTelemetryClient>();
services.AddSingleton<TelemetryLoggerMiddleware>(sp =>
{
var telemetryClient = sp.GetService<IBotTelemetryClient>();
return new TelemetryLoggerMiddleware(telemetryClient, logPersonalInformation: settings?.Telemetry?.LogPersonalInformation ?? false);
});
services.AddSingleton<TelemetryInitializerMiddleware>(sp =>
{
var httpContextAccessor = sp.GetService<IHttpContextAccessor>();
var telemetryLoggerMiddleware = sp.GetService<TelemetryLoggerMiddleware>();
return new TelemetryInitializerMiddleware(httpContextAccessor, telemetryLoggerMiddleware, settings?.Telemetry?.LogActivities ?? false);
});
var storage = ConfigureStorage(settings);
services.AddSingleton(storage);
var userState = new UserState(storage);
var conversationState = new ConversationState(storage);
services.AddSingleton(userState);
services.AddSingleton(conversationState);
//Configure bot loading path
var botDir = settings.Bot;
var resourceExplorer = new ResourceExplorer().AddFolder(botDir);
var defaultLocale = Configuration.GetValue<string>("defaultLanguage") ?? "en-us";
var rootDialog = GetRootDialog(botDir);
services.AddSingleton(resourceExplorer);
resourceExplorer.RegisterType<OnQnAMatch>("Microsoft.OnQnAMatch");
services.AddSingleton<IBotFrameworkHttpAdapter, BotFrameworkHttpAdapter>(s =>
GetBotAdapter(storage, settings, userState, conversationState, s));
var removeRecipientMention = settings?.Feature?.RemoveRecipientMention ?? false;
//Adding Required Services
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
services.AddTransient<IUserService, UserService>();
services.AddTransient<ICommunicationService, CommunicationService>();
services.AddTransient<IMessageService, MessageService>();
services.AddSingleton<IBot>(s =>
new ComposerBot(
s.GetService<IUserService>(),
s.GetService<ConversationState>(),
s.GetService<UserState>(),
s.GetService<MessageRouter>(),
s.GetService<MessageRouterResultHandler>()));
}
However when I am trying to access UserService Object it passing null object in ComposerBot.cs class? What could be the reason?
public ComposerBot(
IUserService userService,
ConversationState conversationState,
UserState userState,
MessageRouter messageRouter,
MessageRouterResultHandler messageRouterResultHandler)
{
this.userService = userService; **showing NULL**
this.conversationState = conversationState;
this.userState = userState;
this.dialogState = conversationState.CreateProperty<DialogState>("DialogState");
this.messageRouter = messageRouter;
this.messageRouterResultHandler = messageRouterResultHandler;
}

I think you are running into this issue:
https://github.com/dotnet/aspnetcore/issues/28684.
this is related:
https://github.com/dotnet/aspnetcore/issues/17442
A tempory solution at least for me was to inject the service in a razor page to get the user there and pass the user to the service.
note: this should be a comment but i dont have enough repuation to comment.

Related

ServiceBusConnectionException - Receive Transport faulted

I have the following error when I start the masstransit with azure bus.Start(); on my function StartService() I have configuring my azure with masstransit and autofact. The error:
MassTransit.Azure.ServiceBus.Core.ServiceBusConnectionException
HResult=0x80131500
Message=ReceiveTransport faulted: sb://softbaire-amilkar.servicebus.windows.net/;SharedAccessKeyName=**REMOVED**;SharedAccessKey=**REMOVED**/TeamTimeManager
Source=mscorlib
configuration with masstransit:
public static IContainer ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.AddMassTransit(cfg =>
{
cfg.SetKebabCaseEndpointNameFormatter();
cfg.AddConsumer<TeamTimeManager>();
cfg.UsingAzureServiceBus((context, conf) =>
{
var settings = new HostSettings
{
ServiceUri = new Uri("sb://softbaire-amilkar.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=UeIC0z5RPCt25SjnWdss2ssP5a6msUKNJxmLnBpm26g="),
TokenProvider = TokenProvider.CreateManagedIdentityTokenProvider()
};
conf.Host(settings);
conf.ConfigureEndpoints(context);
});
});
return builder.Build();
}
this is where I start the service and I get the error:
public void StartService()
{
var container = CreatorContainer.ConfigureContainer();
var bus = container.Resolve<IBusControl>();
if (host != null)
{
host.Close();
}
host = new ServiceHost(typeof(TeamTimeManager));
utilHost = new ServiceHost(typeof(TeamTimeUtilityManager));
bus.Start();
source.TraceInformation("Starting TeamTimeManager Azure Bus...");
host.Open();
source.TraceInformation("TeamTimeManager Started!");
utilHost.Open();
utilSource.TraceInformation("Starting TeamTimeUtilityManager...");
}
UPDATE
this problem is solved when I comment on the line:
cfg.AddConsumer<TeamTimeManager>();
if I add a queue or a subscription the problem appears again
BUG
https://github.com/Azure/azure-sdk-for-net/issues/8627
It's likely permissions. MassTransit requires Manage, and you're configuring the managed identity token provider.
Remove the shared access credentials from the connection string, since they would conflict with the managed identity provider.
Make sure the service identity has Manage permissions on the namespace.
Well, the problem was in the token provider.
The problem was that the token generated was connected to azure but without any queue or topic register, the problem started when I wanted to register queues and topics, when I wanted to generate the connection with the queue or a topic I got an error because the token was invalid for my user(weird), so... I changed the method to generate the token and everything started to work correctly.
Before:
var settings = new HostSettings
{
ServiceUri = new Uri("sb://xxxx-busazure.servicebus.windows.net"),
TokenProvider = TokenProvider.CreateManagedIdentityTokenProvider()
};
After: now I'm using CreateSharedAccessSignatureTokenProvider() you need to send as parameters the "SharedAccessKeyName" and the "SharedAccessKey"
var settings = new HostSettings
{
ServiceUri = new Uri("sb://xxxxx-busazure.servicebus.windows.net"),
TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider("RootManageSharedAccessKey", "xxxxxxxxxxxxxxx=")
};
queues and everything is running smoothly, Final configuration method:
public static IContainer ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.AddMassTransit(cfg =>
{
cfg.SetKebabCaseEndpointNameFormatter();
cfg.AddServiceBusMessageScheduler();
cfg.AddConsumer<TeamTimeManager>();
cfg.UsingAzureServiceBus((context, conf) =>
{
conf.UseServiceBusMessageScheduler();
var settings = new HostSettings
{
ServiceUri = new Uri("sb://amilkar-busazure.servicebus.windows.net"),
TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider("RootManageSharedAccessKey", "xxxxxxxxxxxxxxxxxxxxxx")
};
conf.Host(settings);
conf.ReceiveEndpoint("team-time-manager", e =>
{
e.ConfigureConsumer<TeamTimeManager>(context);
});
conf.ConfigureEndpoints(context);
});
});
return builder.Build();
}

How do you get IOptions<T> values from services?

I'm trying to do TDD while developing but I am struggling. My first test I can successfully get the options set in services.AddAuthentication(...), but I am not able to get the options from .AddCookie(..) that is added onto the previous call. During debug I do see that there is an IPostConfigureOptions added for CookieAuthenticationOptions, and I suspect that it somehow alters the default option values, but I don't know how to get it.
Code to be tested:
public static void AddOpenIdConnect(this IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options => { options.MinimumSameSitePolicy = SameSiteMode.None; });
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(
CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(2);
options.SlidingExpiration = true;
options.AccessDeniedPath = new PathString("/Error/403");
}
);
}
My first test ensured the AuthenticationOptions were set, and it passes:
[TestMethod]
public void AddOpenIdConnect_Should_AddAuthenticationOptions()
{
// arrange
var services = new ServiceCollection();
// act
services.AddOpenIdConnect();
// assert
var provider = services.BuildServiceProvider();
var authOptions = provider.GetRequiredService<IOptions<AuthenticationOptions>>();
authOptions.Value.DefaultScheme.Should().Be(CookieAuthenticationDefaults.AuthenticationScheme);
authOptions.Value.DefaultChallengeScheme.Should().Be(OpenIdConnectDefaults.AuthenticationScheme);
}
This method fails, as AccessDeniedPath returns the default setting and not the setting that the code being tested sets (you can see that I commented out the check on ExpireTimeSpan as it fails as well.)
[TestMethod]
public void AddOpenIdConnect_Should_AddCookieAuthenticationOptions()
{
// arrange
var services = new ServiceCollection();
// act
services.AddOpenIdConnect();
// assert
var provider = services.BuildServiceProvider();
var authCookieOptions = provider.GetRequiredService<IOptions<CookieAuthenticationOptions>>();
//authCookieOptions.Value.ExpireTimeSpan.Should().Be(TimeSpan.FromHours(2));
authCookieOptions.Value.SlidingExpiration.Should().BeTrue();
authCookieOptions.Value.AccessDeniedPath.Should().Be("/Error/403");
}
Any help would be much appreciated, as I still have to tack on a ".AddOpenIdConnect(options =>" after this.
Thank you!
The source code for the AddCookie extension shows that is uses the authentication scheme to configure a named option when adding CookieAuthenticationOptions
public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder,
string authenticationScheme, string displayName, Action<CookieAuthenticationOptions> configureOptions)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
builder.Services.AddOptions<CookieAuthenticationOptions>(authenticationScheme).Validate(o => o.Cookie.Expiration == null, "Cookie.Expiration is ignored, use ExpireTimeSpan instead.");
return builder.AddScheme<CookieAuthenticationOptions, CookieAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
}
Source Code
Note the
builder.Services.AddOptions<CookieAuthenticationOptions>(authenticationScheme) //<-- Named option
Thus the same scheme used when the option was registered will need to be used in order to access the name option
This can be done using IOptionsSnapshot<TOptions>.Get(String) Method
//...omitted for brevity
// Assert
var provider = services.BuildServiceProvider();
IOptionsSnapshot<CookieAuthenticationOptions> namedOptionsAccessor =
provider.GetRequiredService<IOptionsSnapshot<CookieAuthenticationOptions>>();
CookieAuthenticationOptions options =
namedOptionsAccessor.Get(CookieAuthenticationDefaults.AuthenticationScheme);
options.ExpireTimeSpan.Should().Be(TimeSpan.FromHours(2));
options.SlidingExpiration.Should().BeTrue();
options.AccessDeniedPath.Should().Be("/Error/403");

Unit Testing IServiceCollection Registration

I'm trying to figure out the easiest way to test my Service Registrations method for my framework. I'm creating dynamic services my registration looks like so:
var messageHubConfig = new DynamicHubServiceConfiguration<Message, MessageDTO>();
messageHubConfig.SetDynamicHubOptions<AstootContext>(async (context, dto) =>
{
return await context.ConversationSubscriptions
.Where(x => x.ConversationId == dto.ConversationId
&& x.IsSubscribed)
.Distinct()
.Select(x => x.User.UniqueIdentifier)
.ToListAsync();
});
messageHubConfig.RequiresDynamicValidator = false;
messageHubConfig.EventMapping.AddCreateEvent(async (sp, obj, dto) =>
{
var conversationService = sp.GetService<IRestEzService<Conversation, ConversationDTO>>();
var conversationDTO = await conversationService.Get(new object[] { dto.ConversationId });
var hubTaskQueue = sp.GetService<IHubServiceTaskQueue>();
hubTaskQueue.QueueDynamicCreate(conversationDTO);
}).When(async (sp, dto) => {
var context = sp.GetService<AstootContext>();
return await context.Conversations.Where(x => x.Id == dto.ConversationId).Where(x => x.Messages.Count == 1).AnyAsync();
});
//Registers service with a hub
restConfiguration.RegisterRestService(typeof(IMessageDTOService),
typeof(MessageDTOService),
messageHubConfig);
Inside of my Register Rest Service Method I have a lot of different services Getting registered e.g:
services.AddTransient(restServiceType, (IServiceProvider serviceProvider) =>
{
var restService = (IRestEzService<TEntity, TDTO>)
ActivatorUtilities.CreateInstance(serviceProvider, restServiceImplementationType);
serviceOption.EventMapping?.Register(serviceProvider, restService);
return restService;
});
How can I be assure that my factory configuration is being registered properly, How can I create a Service Collection for testing?
Create a ServiceCollection,
var services = new ServiceCollection();
call your registration function and then assert that your restServiceType was added.
Next build a provider from the service collection, resolve the restServiceType
var provider = services.BuildServiceProvider();
var restService = provider.GetRequiredService(restServiceType);
and assert that it is created as desired.
The GetRequiredService extension method will throw an exception if the service is unable to resolve the target type.
Now that is based solely on what is currently being shown in your example as I am unaware of any other dependencies.
Based on #Nkosi's answer, a quick test that all Servives are wired up and in a particular order:
// Arrange
var services = new ServiceCollection();
// Act
var provider = services.AddBaseServices(); // Whatever service you have...
// Assert
Assert.AreEqual(27, provider.Count);
// Run this code once and copy the output into this test...
for(int i = 0; i < provider.Count; i++)
{
System.Diagnostics.Debug.WriteLine($"Assert.AreEqual(\"{provider[i].ServiceType.Name}\", provider[{i}].ServiceType.Name);");
}

Data protection operation unsuccessful when using Autofac on Azure

I am getting this error when hosting my application on azure:
The data protection operation was unsuccessful. This may have been caused by not having the user profile loaded for the current thread's user context, which may be the case when the thread is impersonating.
When trying creating a user or resending a confirmation email.
This article:
http://tech.trailmax.info/2014/06/asp-net-identity-and-cryptographicexception-when-running-your-site-on-microsoft-azure-web-sites/
says that you should create a single instance on the startup class, but I am using autofac, so I have done this:
builder.Register(c => new IdentityFactoryOptions<UserProvider>() { DataProtectionProvider = new DpapiDataProtectionProvider(c.Resolve<PiiiCKConfig>().Issuer) }).SingleInstance();
and then in my UserManager constructor, I do this:
// Get our data protection provider
var dataProtectionProvider = options.DataProtectionProvider;
// If we have on
if (dataProtectionProvider != null)
{
// Set our token provider
UserTokenProvider = new DataProtectorTokenProvider<User>(dataProtectionProvider.Create("PiiiK Identity"))
{
// Set our long the email confirmation token will last
TokenLifespan = TimeSpan.FromHours(6)
};
}
But I still get the error.
Does anyone know how I can solve this issue?
According to your description, I checked this issue on my side and I could reproduce this issue. Per my test, you could follow the approaches below to solve this issue:
builder.Register(c => new IdentityFactoryOptions<UserProvider>() { DataProtectionProvider = new DpapiDataProtectionProvider(c.Resolve<PiiiCKConfig>().Issuer) }).SingleInstance();
You could using IAppBuilder.GetDataProtectionProvider() instead of declaring a new DpapiDataProtectionProvider, based on the above code, you need to modify it as follows:
builder.Register(c => new IdentityFactoryOptions<UserProvider>()
{
DataProtectionProvider = app.GetDataProtectionProvider()
}).SingleInstance();
Or
builder.Register<IDataProtectionProvider>(c => app.GetDataProtectionProvider()).SingleInstance();
Additionally, here is a similar issue, you could refer to here.
UPDATE:
Here is the code snippet of Startup.Auth.cs as follows:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.Register(c => new IdentityFactoryOptions<ApplicationUserManager>()
{
DataProtectionProvider = app.GetDataProtectionProvider()
}).SingleInstance();
//Or
//builder.Register<IDataProtectionProvider>(c =>app.GetDataProtectionProvider()).SingleInstance();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}

Setup View engine in ASP.NET MVC6 to work with AspNet.TestHost.TestServer in Unit Tests

How to setup view engine in ASP.NET MVC 6 to work with test host created by TestServer. I've tried to implement the trick from MVC 6 repo:
[Fact]
public async Task CallMvc()
{
var client = GetTestHttpClient();
//call to HomeController.Index to get Home/Index.cshtml content
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "/");
var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
PAssert.IsTrue(() => content != null);
}
private HttpClient GetTestHttpClient(Action<IServiceCollection> configureServices = null)
{
var applicationServices = CallContextServiceLocator.Locator.ServiceProvider;
var applicationEnvironment = applicationServices.GetRequiredService<IApplicationEnvironment>();
var libraryManager = applicationServices.GetRequiredService<ILibraryManager>();
var startupAssembly = typeof(Startup).Assembly;
var applicationName = startupAssembly.GetName().Name;
var library = libraryManager.GetLibraryInformation(applicationName);
var applicationRoot = Path.GetDirectoryName(library.Path);
var hostingEnvironment = new HostingEnvironment()
{
WebRootPath = applicationRoot
};
var loggerFactory = new LoggerFactory();
var startup = new Startup();
Action<IServiceCollection> configureServicesAction = services =>
{
services.AddInstance(applicationEnvironment);
services.AddInstance<IHostingEnvironment>(hostingEnvironment);
// Inject a custom assembly provider. Overrides AddMvc() because that uses TryAdd().
var assemblyProvider = new FixedSetAssemblyProvider();
assemblyProvider.CandidateAssemblies.Add(startupAssembly);
services.AddInstance<IAssemblyProvider>(assemblyProvider);
startup.ConfigureServices(services);
};
Action<IApplicationBuilder> configureApp = _ => startup.Configure(_, hostingEnvironment, loggerFactory);
var server = TestServer.Create(configureApp, configureServicesAction);
var httpClient = server.CreateClient();
return httpClient;
}
Startup class is just the simplest setup for MVC:
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add MVC services to the services container.
services.AddMvc();
}
// Configure is called after ConfigureServices is called.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add MVC to the request pipeline.
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
}
}
I'm getting Response status code does not indicate success: 500 (Internal Server Error) and internally it's not able to locate Index.cshtml view. All paths
below are following Unit Tests library path or dnx path:
var applicationBasePath = _appEnvironment.ApplicationBasePath;
var webRootPath = _env.WebRootPath;
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
What is the way to setup view engine and environment to work from UnitTests using TestServer?
Your idea of defining a different environment is going to the right direction.
I have seen a quite elegant solution for this by using extension methods:
https://github.com/bartmax/TestServerMvcHelper
It is even on NuGet but I can't get it working from there. Propably you can incorporate the two classes MvcTestApplicationEnvironment.cs and WebHostBuilderExtensions.cs into your solution.
Then you can setup the TestServer by using this:
var builder = TestServer.CreateBuilder();
builder.UseStartup<Startup>()
.UseEnvironment("Testing")
.UseApplicationPath("YourMvcApplication");
var server = new TestServer(builder);

Categories

Resources