How to pass IOptions to an ASP.NET 5 middle-ware service? - asp.net

I have started playing around with ASP.NET 5 vNext and I am struggling passing the options from config.json into a middle-ware service that is used by my WebApi controller.
Here is a snippet with my middle-ware service:
public class MyService : IMyService
{
public MyService(IOptions<MyOptions> settings)
{
var o = settings.Options;
}
}
Here is my WebApi controller that is using the middle-ware service:
public class MyController : Controller
{
private IMyService _myService;
public TestController(IMyService service)
{
_myService = service;
}
}
In Startup.cs I am reading the options:
services.AddOptions();
services.Configure<MyOptions>(Configuration);
What I am struggling with is how to register an instance to IMyService so that it would be passed to the constructor of the controller (how can I get a hold of the IOptions)?
services.AddInstance<IMyService>(new MyService(XXXXX));
As suggested below I did try to use both
services.AddTransient<MyService>();
and
services.AddSingleton<MyService>();
But in both cases I am seeing the following error:
An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type
'MyApp.Services.IMyService' while attempting to activate
'MyApp.Controllers.TestController'.
Microsoft.Framework.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider
sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
Thanks for your help!

Don't register it as an Instance. Instead just add it as Scoped/Transient/Singleton depending on your requirements and let Dependency Injection do its magic;
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddScoped<IMyService, MyService>();

For example, you can to add to Startup.cs that code:
public void ConfigureServices(IServiceCollection services)
{
var builder = new ConfigurationBuilder("[path to file with configuration]");
builder.AddJsonFile("config.json");
var config = builder.Build();
services.AddOptions();
services.Configure<MyOptions>(config);
//services.AddSingleton<IMyService, MyService>();
services.AddTransient<IMyService, MyService>();
}
I can assure you that you can use Singleton or Transient.
If you're interested, you can find more info here https://github.com/aspnet/Docs/issues/24.
And additionally, currently Autofac creates DI for ASP.NET 5 on
http://alexmg.com/autofac-4-0-alpha-1-for-asp-net-5-0-beta-3/

Related

How can I get the baseurl of my site in ASP.NET Core?

Say my website is hosted in the mywebsite folder of www.example.com and I visit https://www.example.com/mywebsite/home/about.
How do I get the base url part in an MVC controller? The part that I am looking for is https://www.example.com/mywebsite
The example listed here doesn't work as we don't have access to Request.Url in ASP.NET Core
You should still be able to piece together what you need. You have access to the request object if your controller inherits from Controller.
If you are using VS2017, fire up a new ASPNet Core MVC app and replace the homecontroller with:
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
ViewData["Message"] = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}";
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
public IActionResult Error()
{
return View();
}
}
I just put in some of the stuff that might interest you in the "About" method, but you should explore the rest of the request class so you know what else is available.
As #Tseng pointed out, you might have a problem when running Kestrel behind IIS or Azure App Service, but if you use the IISIntegration package or AzureAppServices package (by installing the Nuget package and adding it in Program.cs to your WebHostBuilder), it should forward those headers to you. It works great for me in Azure, because I sometimes have to make decisions based on which hostname they hit. The IIS/Azure packages also forward the original remote IP address, which I log.
If you need this anywhere in your app than you should create a class and add it as a service.
Define your static class and your extension method for adding it to the service pipeline like this.
public class MyHttpContext
{
private static IHttpContextAccessor m_httpContextAccessor;
public static HttpContext Current => m_httpContextAccessor.HttpContext;
public static string AppBaseUrl => $"{Current.Request.Scheme}://{Current.Request.Host}{Current.Request.PathBase}";
internal static void Configure(IHttpContextAccessor contextAccessor)
{
m_httpContextAccessor = contextAccessor;
}
}
public static class HttpContextExtensions
{
public static void AddHttpContextAccessor(this IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
public static IApplicationBuilder UseHttpContext(this IApplicationBuilder app)
{
MyHttpContext.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
return app;
}
}
It might be a little redundant to expose the HttpContext in this case but I find it very helpful.
You would than add it to the pipeline in your Configfure method which is located in Startup.cs
app.UseHttpContext()
From there it is simple to use it anywhere in your code.
var appBaseUrl = MyHttpContext.AppBaseUrl;
All of these existing answers depend on an HttpContext object, which is only available during an incoming request. However, I needed to get the URLs in a background service where HttpContext was not available.
This information is also available in the Microsoft.AspNetCore.Hosting.Server.IServer service, as long as the actual host service provides this information. If you're using the default Kestrel server, I've found that it is indeed provided. I have not tested this when hosting IIS in-process or with other hosting models.
You need to get an instance of IServer and then look for the .Features entry of type IServerAddressesFeature.
Here's an extension method to get the URL(s) directly from an IServiceProvider:
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
public static ICollection<string> GetApplicationUrls(this IServiceProvider services)
{
var server = services.GetService<IServer>();
var addresses = server?.Features.Get<IServerAddressesFeature>();
return addresses?.Addresses ?? Array.Empty<string>();
}
You could however accomplish the same thing by injecting IServer if DI services are available.
using Microsoft.AspNetCore.Http;
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
public AccountController(IHttpContextAccessor httpContextAccessor)
{
var request = httpContextAccessor.HttpContext.Request;
var domain = $"{request.Scheme}://{request.Host}";
//domain => https://varunsoft.in
}
NPNelson answer works if with .Value.ToString()
var baseUrl = $"{this.Request.Scheme}://{this.Request.Host.Value.ToString()}{this.Request.PathBase.Value.ToString()}";
var baseUrl = Request.GetTypedHeaders().Referer.ToString();
This way you can capture the base url information.
This is how I could get it in Asp .Net Core 3.1 version.
You can access the resource from the link below.
Reference
string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Content("~"));
you can check for more information here:
How can I get my webapp's base URL in ASP.NET MVC?

How to get SignalR Hub Context in a ASP.NET Core?

I'm trying to get the context for a hub using the following:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<SomeHub>();
The problem is that GlobalHost is not defined. I see it is part of the SignalR.Core dll. At the moment, I have the following in my project .json file, under dependencies:
"Microsoft.AspNet.SignalR.Server": "3.0.0-*"
If I add the latest available version of Core:
"Microsoft.AspNet.SignalR.Server": "3.0.0-*",
"Microsoft.AspNet.SignalR.Core" : "2.1.2"
I get a whole bunch of errors because server and core are conflicting. If I change them to both use version "3.0.0-*", all the conflicts go away, but GlobalHost cannot be found. If I remove Server, and just user Core version 2.1.2 then GlobalHost works, but all the other things needing Server, obviously do not.
Any ideas?
IConnectionManager does not exist any more in SignalR for ASP.Net Core.
I've been using HubContext for getting access to a hub.
public class HomeController : Controller
{
private readonly IHubContext<LiveHub> _hubContext;
public HomeController(IHubContext<LiveHub> hubContext)
{
_hubContext = hubContext;
}
public void SendToAll(string message)
{
_hubContext.Clients.All.InvokeAsync("Send", message);
}
}
I'm using .net core 2.0.0 and SignalR 1.0.0-alpha1-final
Microsoft.AspNet.SignalR.Infrastructure.IConnectionManager is a DI injected service through which you can get the hub context...For example:
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Infrastructure;
using Microsoft.AspNet.Mvc;
public class TestController : Controller
{
private IHubContext testHub;
public TestController(IConnectionManager connectionManager)
{
testHub = connectionManager.GetHubContext<TestHub>();
}
.....
To use the hub in a backgroud service, in addition to controllers, you must use the IHostedService interface and get the hub by DI.
public class MyBackgroundService : IHostedService, IDisposable
{
public static IHubContext<NotifierHub> HubContext;
public MyBackgroundService(IHubContext<NotifierHub> hubContext)
{
HubContext = hubContext;
}
public Task StartAsync(CancellationToken cancellationToken)
{
//TODO: your start logic, some timers, singletons, etc
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
//TODO: your stop logic
return Task.CompletedTask;
}
public void Dispose()
{
}
}
Then you can call your hub from anywhere in your code from HubContext static field:
MyBackgroundService.HubContext.Clients.All.SendAsync("UpdateData", myData).Wait();
Learn more about IHostedService:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1
You can create and start a timer in MyBackgroundService and call the hub in ElapsedEvent.
I needed to be able to access the Hub Context from outside the app request thread - because I was subscribing to NServicebus messages, and needed to be able to trigger a client function when I received a message.
Here's how I got it sorted:
public static IServiceProvider __serviceProvider;
then during startup configuration
app.UseServices(services =>
{
__serviceProvider = new ServiceCollection()
.BuildServiceProvider(CallContextServiceLocator.Locator.ServiceProvider);
});
Then anywhere else in the vNext asp.net application (any other thread)
var manager = Startup.__serviceProvider.GetRequiredService<IConnectionManager>();
var hub = manager.GetHubContext<ChatHub>();
Hope this helps!
I added some code to my Startup.cs to grab reference to the ConnectionManager which you can then use to do a GetHubContext at anytime from anywhere in your code. Similar to Nimo's answer but a little different, maybe simpler.
services.AddSignalR(options =>
{
options.Hubs.EnableDetailedErrors = true;
});
var provider = services.BuildServiceProvider();
//Hold on to the reference to the connectionManager
var connManager = provider.GetService(typeof(IConnectionManager)) as IConnectionManager;
//Use it somewhere else
var hub = connManager.GetHubContext<SignalHub>();
I'm looking at SignalR source code and it seems that IHubContext is registered as a singleton.
Which means you get the same instance whenever you access it.
Which means you can simply save it in a static var and use it from whatever.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHubContext<MyHub> hubContext)
{
_staticVar = hubContext;
}
But be warned - it's an anti-pattern.

MVC Custom Membership and Role Provider context lifetime issue

I'm having problems with custom membership within MVC 4 I keep getting a context lifetime related error when I do a ajax call to get a partial result from the server(controller), the error is always {"The provider has been closed"} or {"There is already an open DataReader associated with this Command which must be closed first."} the error always lands within the custom RoleProvider.
I will try to explain the current setup im using.
I have inherited from the Membership and RoleProvier and overridden all the methods like so
public class CustomRoleProvider : RoleProvider
{
private IAccountService _accountService;
public CustomRoleProvider()
{
_accountService = new AccountService();
}
public override string[] GetRolesForUser(string username)
{
return _accountService.GetRolesForUser(username);
}
}
The Membership provider is implemented in the same way the IAccountService above is the service layer that deals with all user accounts & roles all the service layer classes implement a base service class called ServiceBase that creates the DB context
public class ServiceBase
{
protected Context Context;
protected ServiceBase() : this("Context") {}
protected ServiceBase(string dbName)
{
IDatabaseInitializer<Context> initializer = new DbInitialiser();
Database.SetInitializer(initializer);
Context = new Context(dbName);
}
}
The Controller that has the ajax to made to it
[Authorize(Roles = "Administrator,Supplier")]
public class AuctionController : Controller
{
private IAuctionService _service;
public AuctionController()
{
_service = new AuctionService();
}
public AuctionController(IAuctionService service)
{
_service = service;
}
[CacheControl(HttpCacheability.NoCache), HttpGet]
public ActionResult RefreshAuctionTimes(int auctionId)
{
return PartialView("_AuctionTimer", BusinessLogic.Map.ConvertAuction(_service.GetAuction (auctionId)));
}
}
The problem only started when I added the [Authorize(Roles = "Administrator,Supplier")] attribute to the controller that handled the ajax call, I know this is the lifetime of the DbContext being for the life of the app and the controllers service layer being destroyed and recreated on every post but I'm not sure of the best way to handle this, I have used this setup before but with DI and Windsor and never had this problem as the IOC was controlling the context.
Would it be best to create the providers its own DB context or is the conflict between the 2 providers and really they need to share the same db context?
Any help would be great thanks
The problem is exactly what you're suspecting. Is due to the fact that you're creating a single instance of the DbContext and therefore you're having connection issues. If you use it with an IOC/DI schema, you're going to fix it. The other option is to manually handle the connections.
An example of how to do this using Ninject as IOC container is here
They need to share the same context in order for the problem to stop.
I would suggest you create your service layer class on each call to GetRolesForUser:
public override string[] GetRolesForUser(string username)
{
return new AccountService().GetRolesForUser(username);
}

Autofac with MVC4: controller does not have a default constructor

I've been working with Autofac in MVC3 and love it. Now I am trying to implement it with MVC4.
I installed the pre-release versions of Autofac MVC4 and Autofac WebApi through the Package Manager Console (Install-Package Autofac.Mvc4 -Pre and Install-Package Autofac.WebApi -Pre)
I adjusted my IoC container as following:
private static void SetAutofacContainer()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerHttpRequest().InstancePerApiRequest();
builder.RegisterType<DatabaseFactory>().As<IDatabaseFactory>().InstancePerHttpRequest().InstancePerApiRequest();
builder.RegisterType<RepositoryWrapper>().As<RepositoryWrapper>().InstancePerHttpRequest().InstancePerApiRequest();
builder.RegisterType<ServiceWrapper>().As<ServiceWrapper>().InstancePerHttpRequest().InstancePerApiRequest();
// Repositories
builder.RegisterAssemblyTypes(typeof(UserRepository).Assembly).Where(t => t.Name.EndsWith("Repository")).AsImplementedInterfaces().InstancePerHttpRequest().InstancePerApiRequest();
// Services
builder.RegisterAssemblyTypes(typeof(UserService).Assembly).Where(t => t.Name.EndsWith("Service")).AsImplementedInterfaces().InstancePerHttpRequest().InstancePerApiRequest();
IContainer container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
}
When I run the application (by accessing the API controller) I get the exception:
"Controllers.UserController' does not have a default constructor"
The controller looks like this:
namespace Controllers
{
[Authorize]
public class UserController : ApiController
{
private ServiceWrapper _services;
public UserController(ServiceWrapper services)
{
_services = services;
}
// GET api/user/{userrequest}
public IQueryable<User> Get(UserRequest request)
{
if (ModelState.IsValid)
{
'...
}
}
}
Am I missing something? Did I not set it up right? Any help would be greatly appreciated!
Update
My API controller are within a separate project in the same solution. If I place the API controller in my main MVC project, it works. Could someone please enlighten me on how to get Autofac to register the API controllers in my API project?
With the RegisterApiControllers method you tell Autofac where (in which assembly) it should look for your ApiControllers
So the following call:
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
Registers the ApiControllers from the current assembly (project).
If you have ApiControllers also in a different project you need to use it like this:
builder.RegisterApiControllers(typeof(UserController).Assembly);
Which means: register all the ApiController form the assembly (project) where the UserController lives. So you only need one RegisterApiControllers per assembly even if you have multiple ApiController in an assembly (project).

Using Autofac for DI into WCF service hosted in ASP.NET application

I'm having trouble injecting services dependencies into my WCF service using Autofac 1.4.5. I've read and followed the Autofac wiki page on WcfIntegration but my debugging shows me that my WCF service is created by the System.ServiceModel.Dispatcher.InstanceBehavior.GetInstance() method and not by the AutofacWebServiceHostFactory. What am I doing wrong?
I've set up my ajax.svc file to look like the one in the example for use with WebHttpBinding:
<%# ServiceHost Language="C#" Debug="true"
Service="Generic.Frontend.Web.Ajax, Generic.Frontend.Web"
Factory="Autofac.Integration.Wcf.AutofacWebServiceHostFactory,
Autofac.Integration.Wcf" %>
My WCF service class Ajax is defined like this:
namespace Generic.Frontend.Web
{
[ServiceContract]
[AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Ajax
{
public MapWebService MapWebService { get; set;}
public Ajax() {
// this constructor is being called
}
public Ajax(MapWebService mapWebService)
{
// this constructor should be called
MapWebService = mapWebService;
}
[WebGet(ResponseFormat = WebMessageFormat.Json)]
[OperationContract(Name = "mapchange")]
public MapChangeResult ProcessMapChange(string args)
{
// use the injected service here
var result = MapWebService.ProcessMapChange(args);
return result;
}
}
}
Now I've used the wiring up in the Global.asax.cs as shown in the wiki mentioned above:
var builder = new ContainerBuilder();
builder.RegisterModule(new AutofacModuleWebservice());
var container = builder.Build();
AutofacServiceHostFactory.Container = container;
with
class AutofacModuleWebservice : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register<Ajax>();
builder.Register<MapWebService>().ContainerScoped();
}
}
In my web.config I have
<services>
<service name="Generic.Frontend.Web.Ajax">
<endpoint address="http://mysite.com/ajax.svc/" binding="webHttpBinding"
contract="Generic.Frontend.Web.Ajax" />
</service>
</services>
.
The service already works fine but I can't get the Autofac bits (read: creation/injection) to work. Any ideas?
Edit:
Removing the default constructor unfortunately leads to the following exception:
System.InvalidOperationException:
The service type provided could not be loaded as a service because it does not
have a default (parameter-less) constructor. To fix the problem, add a default
constructor to the type, or pass an instance of the type to the host.
Cheers, Oliver
Is your service setup with InstanceContextMode.Single? If it is then wcf will create your service using the default constructor. To get around this change your instance context mode and let autofac manage the lifetime of your service.
Try deleting the default Ajax constructor and modifying your constructor to this. If it gets run with mapWebService == null that would indicate a resolution problem.
public Ajax(MapWebService mapWebService = null)
{
// this constructor should be called
MapWebService = mapWebService;
}
I just got the same System.InvalidOperationException and solved it by changing the ServiceBehavior InstanceContextMode of the implementation from InstanceContextMode.PerCall to InstanceContextMode.PerSession, perhaps your AutoFac lifetime scope is out of sync with your web service implementation?
For testing AutoFac service creation I recommend creating a unit test and directly resolving them as this will highlight any issues and give more meaningful exception messages. For services with a request lifetime scope create a test aspx page and again resolve them directly.
I had the same problem and came across this question while searching for an answer.
In my case, using property injection worked, and the code in the question already has a property that can be used:
namespace Generic.Frontend.Web
{
[ServiceContract]
[AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Ajax
{
// inject the dependency here
public MapWebService MapWebService { get; set;}
[WebGet(ResponseFormat = WebMessageFormat.Json)]
[OperationContract(Name = "mapchange")]
public MapChangeResult ProcessMapChange(string args)
{
// use the injected service here
var result = MapWebService.ProcessMapChange(args);
return result;
}
}
}
and register to use property injection (sample code from the wiki and syntax has changed as this is now using version 2.5.2.830):
builder.RegisterType<Ajax>().PropertiesAutowired();
Following the instructions solved it for me:
code.google.com/p/autofac/wiki/… I simply do : builder.RegisterType();
and I've followed their instuructions for changing the .svc file.
When you look at your .svc file you do not get any hints about something being wrong there btw?
You host it throu the iis and do not utilize WAS, I do not see your code for overriding global.asax.cs
Add the global file to your solution and there you implement:
protected void Application_Start(object sender, EventArgs e)
{
// build and set container in application start
IContainer container = AutofacContainerBuilder.BuildContainer();
AutofacHostFactory.Container = container;
}
AutofacContainerBuilder is my container builder.

Resources