Get Hangfire working with ASP.NET MVC and LightInject - c#

I have an ASP.NET MVC App and I recently upgraded to use LightInject DI.
However I cant seem to get Hangfire running correctly, even using the LightInject Extension!
My Hangfire setup in Startup.cs:
public void Configuration(IAppBuilder app)
{
var container = new ServiceContainer();
container.RegisterControllers(typeof(Web.Controllers.DashboardController).Assembly);
ConfigureServices(container);
ConfigureHangfire(container,app);
container.EnableMvc();
}
private void ConfigureHangfire(ServiceContainer container, IAppBuilder app)
{
var hangfireConnString = ConfigurationManager.ConnectionStrings["HfConnString"].ConnectionString;
GlobalConfiguration.Configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseLightInjectActivator(container)
.UseSqlServerStorage(hangfireConnString, new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.FromSeconds(10),
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
});
var options = new DashboardOptions()
{
Authorization = new[] {new SystemAuthorizationFilter()}
};
app.UseHangfireDashboard("/hangfire",options);
app.UseHangfireServer();
}
However I get the following error when running a hangfire job:
System.NullReferenceException: Object reference not set to an instance of an object.
at LightInject.Web.PerWebRequestScopeManager.GetOrAddScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 148
at LightInject.Web.PerWebRequestScopeManager.get_CurrentScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 129
at LightInject.ScopeManager.BeginScope() in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6091
I would love any help to get this going!
Thanks so much in advance.

I actually fixed this by giving hangfire its own container. So the start of my ConfigureHangfire method became:
private void ConfigureHangfire(ServiceContainer container, IAppBuilder app)
{
var hangfireConnString = ConfigurationManager.ConnectionStrings["HfConnString"].ConnectionString;
var container = new ServiceContainer();
ConfigureServices(container);
GlobalConfiguration.Configuration etc....
Im not sure that this is entirely correct, and if its not i would really like to be corrected! But in any case I hope this helps someone!

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 I set custom redirect value for login page in IdentityService v4?

I've made the following config.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddIdentityServer(SetupIdentityServer)
.AddDeveloperSigningCredential()
.AddInMemoryClients(GetClients)
.AddInMemoryIdentityResources(GetIdentities)
.AddInMemoryApiResources(GetApis)
.AddTestUsers(GetUsers);
services.AddAuthentication("TheCookie")
.AddCookie("TheCookie", _ =>
{
_.ExpireTimeSpan = TimeSpan.FromMinutes(1);
_.SlidingExpiration = true;
});
...
}
private static void SetupIdentityServer(IdentityServerOptions options)
{
options.UserInteraction.LoginUrl = "/Security/Credentials";
options.UserInteraction.LoginReturnUrlParameter = "bbb";
options.UserInteraction.CustomRedirectReturnUrlParameter = "aaa";
}
However, when I access a page that requires authorization, I get 404 because I'm forwarded to the default /Account/Login" controller/action. I can confirm that the SetupIdentityServer is invoked because it hit a breaky there. Still, the URL in the browser stays default and the return page is the one I'm trying to access instead of the phony setup above.
The docs for IdS4 don't explain very much and I can't figure out what I'm missing. I've tried to follow the set up like suggested here.
I suspect that it's got to do with my client, which is set up as follows.
private static IEnumerable<Client> GetClients => new[]
{
new Client
{
ClientId = "Website",
ClientName = "The Client",
ClientUri = "http://identityserver.io",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = {"http://localhost:5000/security/credentials"},
PostLogoutRedirectUris = {"http://localhost:5000/index.html"},
AllowedCorsOrigins = {"http://localhost:5000", "https://localhost:44300"},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
}
}
};
It's my understanding that the grant type implicit is supposed to be used for SPAs while MVC-style login is being governed by the setup is managed by default simply specifying it as I did in SetupIdentityServer. I sense that the issue might be somewhere there but googling for stuff about clients' types gave me more headache than clarity.

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));
}
}

Autofac Web API 2 and Owin resolve in StartupConfig

I have a project that is both OWIN and Web API 2.
I have tried to follow the instructions as best as I can.
Inside my Configration method in the StartupConfig class I have this:
// Get our configuration
var config = new HttpConfiguration();
var container = ConfigureInversionOfControl(app, config);
var scope = config.DependencyResolver.GetRequestLifetimeScope();
var serverOptions = ConfigureOAuthTokenGeneration(app, scope);
//** removed for brevity **//
// Cors must be first, or it will not work
app.UseCors(CorsOptions.AllowAll);
// Register the Autofac middleware FIRST. This also adds
// Autofac-injected middleware registered with the container.
app.UseAutofacMiddleware(container);
//app.UseAutofacWebApi(config);
app.UseOAuthAuthorizationServer(serverOptions);
app.UseWebApi(config);
My ConfigurInversionOfControl method looks like this:
private static IContainer ConfigureInversionOfControl(IAppBuilder app, HttpConfiguration config)
{
// Create our container
var builder = new ContainerBuilder();
// You can register controllers all at once using assembly scanning...
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// Add our dependencies (Can't use life because this is available to all controllers http://stackoverflow.com/questions/36173210/get-same-instance-of-a-component-registered-with-autofac-as-instanceperlifetimes)
builder.RegisterType<UnitOfWork<DatabaseContext>>().As<IUnitOfWork>().InstancePerRequest();
// Register our services
builder.Register(c => new AdvancedEncryptionStandardProvider(ConfigurationManager.AppSettings["rm:key"], ConfigurationManager.AppSettings["rm:secret"])).As<IAdvancedEncryptionStandardProvider>();
builder.RegisterType<LogService>().As<ILogService>();
builder.RegisterType<EmailService>().As<IEmailService>();
builder.RegisterType<RefreshTokenService>().As<IRefreshTokenService>();
// Register our providers
builder.Register(c => new SendGridProvider(c.Resolve<IUnitOfWork>(), c.Resolve<IEmailService>(), ConfigurationManager.AppSettings["SendGridApiKey"])).As<ISendGridProvider>();
builder.RegisterType<LogProvider>().As<ILogProvider>();
builder.RegisterType<RefreshTokenProvider>().As<IAuthenticationTokenProvider>();
builder.RegisterType<OAuthProvider>().As<OAuthProvider>();
// Build
var container = builder.Build();
// Lets Web API know it should locate services using the AutofacWebApiDependencyResolver
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
// Return our container
return container;
}
Now I have set this up (I assume correctly). How do I resolve one of the services in my StartupConfig class?
I need to be able to create an instance of the OAuthProvider and RefreshTokenProvider. I tried something similar to this:
// Get our providers
var authProvider = scope.Resolve<OAuthProvider>();
var refreshTokenProvider = scope.Resolve<IAuthenticationTokenProvider>();
// Create our OAuth options
return new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true, // TODO: Remove this line
TokenEndpointPath = new PathString("/oauth/access_token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
AccessTokenFormat = new Business.Authentication.JwtFormat("http://localhost:62668"),
Provider = authProvider,
RefreshTokenProvider = refreshTokenProvider
};
But when it reaches the line var authProvider = scope.Resolve<OAuthProvider>();, I get an error stating:
Parameter cannot be null: Parameter name: context
Now I would assume that the context has not been created yet, so I assume I am doing something wrong with my resolve.
Can anyone help?
This was actually really simple.
I needed the change the scope from:
var scope = config.DependencyResolver.GetRequestLifetimeScope();
to
var scope = config.DependencyResolver.GetRootLifetimeScope();
I assume because the "request" is not available in the StartupConfig class.
Anyway, changing this fixed my issue.

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