How to get base url without accessing a request - asp.net-core

How to get the base URL in AspNet core application without having a request?
I know from the Request you can get the scheme and host (ie $"{Request.Scheme}://{Request.Host}" would give something like https://localhost:5000), but is it possible to get this information from anywhere else?
In other words, if I have a service class that needs to build absolute URLs, how can I get the current URL when there is not an http request available?
UPDATE: Maybe that scenario does not even make sense since the hosting URL is totally external to the application and that's why it only makes sense to extract it from the Request host..

i needed for some reason to get the base URL in Start.cs Configure, so i come up with this
var URLS = app.ServerFeatures.Get<IServerAddressesFeature>().Addresses;

You are right, hosting URL is an external information, and you can simply pass it as configuration parameter to your application.
Maybe this will help you somehow: without request, you can get a configured listening address (like http://+:5000) using the IWebHostBuilder interface. It provides access to host settings via the GetSetting method:
/// <summary>
/// Get the setting value from the configuration.
/// </summary>
/// <param name="key">The key of the setting to look up.</param>
/// <returns>The value the setting currently contains.</returns>
string GetSetting(string key);
There is a WebHostDefaults.ServerUrlsKey setting name, that allows to configure listening address. We override it when add .UseUrls extension method:
public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls);
or define urls configuration parameter as described in the documentation (you know, by default listening is configured to localhost:5000).
So, having instance of IWebHostBuilder, you can call .GetSetting(WebHostDefaults.ServerUrlsKey) and get the current value.

,The ASP.NET Core Module generates a dynamic port to assign to the backend process. CreateDefaultBuilder calls the UseIISIntegration method. UseIISIntegration configures Kestrel to listen on the dynamic port at the localhost IP address (127.0.0.1). If the dynamic port is 1234, Kestrel listens at 127.0.0.1:1234. This configuration replaces other URL configurations provided by.
For IIS Integration, it works if you get the address after the WebHostBuilder.Build() have run.
var builder = CreateWebHostBuilder(args);
var webHost = builder.Build();
var addresses = webHost.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
var address = addresses.FirstOrDefault();
AppDomain.CurrentDomain.SetData("BaseUrl", address ?? "");
webHost.Run();
and got the local Kestrel address in the HostedService like this:
string baseUrl = AppDomain.CurrentDomain.GetData("BaseUrl").ToString();
But there's a catch - this address is useless, because you can not make a request directly on this address. The IIS Integration middleware checks that only the IIS handler can make a request on this address. It produces a similar error:
<category>Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware</category>
<state>'MS-ASPNETCORE-TOKEN' does not match the expected pairing token 'ed5bc610-b7b9-4c1c-9941-954d0579edfc', request rejected.</state>
And in general case (no IIS Integration) this method of getting the address does not work if you use Kestrel configured to run with a custom port (not 5000), or a dynamic port 0. In this case the address needs to be obtained in a delayed manner, only after the application started.
For this case i tried this way: In Configure method in the StartUp class, i saved in ServerAddressFeature in the private member.
private IServerAddressesFeature _serverAddressesFeature;
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
_serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
... not related code here ...
And in the ConfigureServices method i added a dependency
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServerAddressesFeature>((sp) => _serverAddressesFeature);
... not related code here ...
Then in a hosted service i obtain this saved feature using dependency injection, and use it to get the address.
It works, only get the address in the StartAsync method, not in the service constructor!
public class WarmUpService : IHostedService
{
private readonly ILogger _logger;
private readonly IServerAddressesFeature _saf;
public WarmUpService(ILogger<WarmUpService> logger, IServerAddressesFeature serverAddressesFeature)
{
_logger = logger;
_saf = serverAddressesFeature;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
// the URL can be Got here
string baseUrl = _saf?.Addresses?.FirstOrDefault();
// await _WarmUp(baseUrl);
}
catch(Exception ex)
{
_logger.LogCritical(ex, "WarmUp Failed");
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}

Related

.NET Core DI passing info of AddHttpContextAccessor to a service

In my solution I have projects for my API, my Web App and also have another project which includes services, that are getting some information from a database and formatting them, these are currently only used by this API, but these could be used by other API projects in the future.
My API have a couple controllers that are returning JSON data from the result returned by the services.
In some cases the services needs to call the API to process some information before calling the request to the database. Since I have dev/staging/prod environment with their own URL I don't want to hardcode the URLs in the services I want to use DI to get these dynamicaly depending on the context.
In the Startup.cs of my API I have added services.AddHttpContextAccessor(); in the ConfigureServices(IServiceCollection services) section to gain access to the current http context :
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
...
}
With that I know I can now access the information directly into my controller which I tried and it worked :
public class DataController : ControllerBase
{
...
private readonly string _baseUrl;
public FeaturesController(...
,IHttpContextAccessor httpContextAccessor)
{
...
_baseUrl = UrlHelpers.ShowBaseURL(httpContextAccessor) ?? throw new ArgumentNullException(nameof(_baseUrl));
}
}
public static class UrlHelpers
{
public static string ShowBaseURL(IHttpContextAccessor httpcontextaccessor)
{
var request = httpcontextaccessor.HttpContext.Request;
var absoluteUri = string.Concat(
request.Scheme,
"://",
request.Host.ToUriComponent(),
request.PathBase.ToUriComponent());
return absoluteUri;
}
}
I could do just about the same thing in the services but to me they should not act directly on the httpcontext, since this is not the job they are meant to do. I am sure I could do better by adding a class injected of some sort that would have then make the specific value available to my services.
I know I could also pass the _baseUrl directly as an argument when calling the services from my controller but since I am trying to better understand DI and use it I would rather find another way if it is viable.
I can't give credit but I went with Steven solution which make the most sens

Getting Hub Context for ASPNet.Core Signal-R (.NET Core 2.1) RC

I'm using ASP.NET Core 2.1 RC1.
I'm also using Signal-R for it (found here):
https://learn.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-2.1
I'm creating a .NET Core console application that's hosting Kestrel and using Signal-R. I've pretty much set it up exactly as the getting started documentation states for setting up the Startup.
This all works great. I'm able to connect to the it, get my HTML with signal-R script in it, receive messages I crafted with Clients.All.SendAsync. Works great.
BUT
I want to be able to send a message to clients, from outside the Hub. Where some event happens in my application, and a message is sent to clients. In full .NET, I'd use the GlobalHost and get the context. In ALL my searches on Stack Overflow, they reference something that no longer works, or used within an REST API controller that's passed in the IHubContext.
I have an event listener in my program.cs, and when the event is triggered, I'd love to be able to send a message to my UserInterfaceHub.
So -- how do I get the hub context in Program.CS - so I can send messages to it (call the SwitchUI method) from within an event delegate I have in Program.CS?
StartUp.cs
public void ConfigureServices(IServiceCollection services) {
services.Configure<CookiePolicyOptions>(options => {
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc();
services.AddCors(options => options.AddPolicy("CorsPolicy",
builder => {builder.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin().AllowCredentials();}));
services.AddSignalR();
var provider = services.BuildServiceProvider();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseCors("CorsPolicy");
app.UseSignalR(routes => {routes.MapHub<UserInterfaceHub>("/uihub");});
app.UseMvc();
//app.Run(async (context) =>{await context.Response.WriteAsync("Active");});
}
Program.CS
CreateWebHostBuilder(args)
.UseKestrel()
.UseUrls("http://0.0.0.0:" + appProperties.HostPort.ToString().Trim())
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Build()
.Start();
UserInterfaceHub.cs
namespace InterfaceModule.Hubs {
public class UserInterfaceHub : Hub {
public async Task SwitchUI(string message) {
await Clients.All.SendAsync("ReceiveEvent", message);
}
public override async Task OnConnectedAsync() {
//await SwitchUI("HOWDY NEW PERSON!");
await base.OnConnectedAsync();
}
}
}
edit adding clarity.
In Program.CS, I have this event delegate:
//now that we're ready, start listening.
await
deviceClient.SetInputMessageHandlerAsync(ModuleProperties.InputName, OnReceiveEvent, deviceClient);
Console.WriteLine("INIT: Event Message Input handler created: [{0}]", ModuleProperties.InputName);
which is this:
static async Task<MessageResponse> OnReceiveEvent(Message message, object userContext) {
//HOW DO I REACH THE HUB FROM HERE SO I CAN SEND A MESSAGE TO THE LISTENERS?
}
I encountered a similar situation and here's how to resolve it:
In your Service layer, create an interface called something like ISendHubMessage. Have a method called Send() that takes parameters that you're wanting to send via SignalR. Create a class within the same file called SendHubMessage that implements the interface. Have it just do a return.
In your top-level project (where your Startup.cs file is located) create another class called SendHubMessage that implements that same ISendHubMessage interface from your Service layer. Within this SendHubMessage, you can use DI to get at the hub as explained above. This method will do the actual logic of sending via SignalR.
In your Startup ConfigureServices() method, add the following line:
services.AddTransient<"Service".ISendHubMessage, "TopLevel".SendHubMessage>();
(where "Service" is the namespace to your Service-level project and "TopLevel" in the namespace to your top-level project).
What you're doing with this line is saying "Whenever an object requests the ISendHubMessage dependency from the Service layer, supply it with the SendHubMessage class defined in my top-level project".
Finally, in all the places in code outside of your top-level project that you're wanting to send messages through your hub, inject that ISendHubMessage dependency in the constructor. You can then refer to it in the class methods and when you call Send(), it will call the Send() method defined in your SendHubMessage class in your top-level project.
This line of code:
app.UseSignalR(routes => {routes.MapHub<UserInterfaceHub>("/uihub");});
will register your hub with the DI container. Then to get access to it, you either use constructor injection to inject in the IHubContext<UserInterfaceHub> (this works for example in a Web Controller) or access it directly from the DI container by doing the following:
var hub = app.ApplicationServices.GetRequiredService<IHubContext<UserInterfaceHub>>();
(for example if executed in the startup.cs Configure method)
If you don't have access to the app.ApplicationServices which is basically an IServiceProvider at the location you need to access the hub, then you will need to either 1) get that class to work with dependency injection to inject in the IHubContext<UserInterfaceHub> or IServiceProvider 2) Setup a static Services global var via Configure so that you can have access to one of them globally, or find some other way to access the DI container (aka IServiceProvider) to get your hub via the above line of code.
Once you have your hub, then sending the message to the registered clients is a simple as calling the method on your hub.
await hub.Clients.All.SendAsync("ReceiveEvent", message);
You're question is a little unclear, but I'm assuming you mean you want to replace the following with something that can send a message through your hub:
app.Run(async (context) =>{await context.Response.WriteAsync("Active");});
Since this is in your Configure method, you can simply add IServiceCollection services to your Configure methods params. Then, you can do:
var hub = services.GetRequiredService<IHubContext<MyHub>>();
However, I'm not sure that this will actually do anything useful ultimately. At startup, you'd logically have no clients with subscriptions yet. As a result, sending a message through your hub at this point, would essentially go nowhere. By the time a user actually hits your site and gets connected to your hub, this part of your application has already run, and won't be hit again.

WCF Service In Orchard Module With Multi Tenancy

I have a WCF service that is defined in a module. When we try to call this service from a non-default tenant, the content manager always references our default tenants settings. In debugging, inside of OrchardServiceHostFactory,
I notice that it ends up getting the settings for the default tenant because the base address that is passed into the CreateServiceHost method is always our default tenants uri.
Given that I am not wholly familiar with WCF, is there a configuration option that I am missing that is causing the WCF service to be created with the default tenants address, instead of the non-default tenant?
Relevant code:
private static readonly Route _SITEMAP_SERVICE_ROUTE = new ServiceRoute("api/SitemapService", new OrchardServiceHostFactory(), typeof(ISitemapService))
{
DataTokens = new RouteValueDictionary
{
{
"area", "Project.Localization"
}
}
};
public interface ISitemapService : IOrchardSitemapService, IDependency
{
}
[ServiceContract]
public interface IOrchardSitemapService
{
[OperationContract]
int GetNavigableContentCount();
[OperationContract]
List<SitemapEntry> GetNavigableContent();
}
I was able to fix this by adding an additional site to IIS that pointed to the same file system location, and used the same application pool. This new site then references the non-default's tenant, and the service will now be created with the correct base address.

HttpContext.Current.Request is not available in RegisterGlobalFilters

I am trying to add RequireHttpsAttribute attribute to MVC filters collection to push web site to HTTPS when it is deployed on prod server. The problem is with HttpContext.Current.Request.IsLocal line, the Request object is not available yet. Then how to check is site running localy or on prod server in RegisterGlobalFilters?
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
if (!HttpContext.Current.Request.IsLocal) //Exception here!!!
{
filters.Add(new RequireHttpsAttribute());
}
}
In this method you are to register the filters that will do the checking when the request comes in. This method will only get called once each time the application is started. So here you need to do something along the lines of:
filters.Add(new MyAuthorizeAttribute());
With MyAuthorizeAttribute being something along the lines of:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
If(!httpContext.Request.IsLocal)
{
**//Check for HTTPS and return false if need be**
}
}
Of course it does not need to be an AuthorizeAttribute.
EDIT
As I said before this method is called only once at the start of the application so there is no request for you to check in here. Here you can only apply filters that will be called every time a request is received. It is inside those filters that you can check request specific properties.
If you insist on using the RequireHttpsAttribute, than you either have to apply it to all methods regardless of whether the request is local or not or you have to extend RequireHttpsAttribute and override HandleNonHttpsRequest to handle local requests.

Limit connection in RESTful web service

Is there a way to configure a rest web service to allow only one connection at a time?
I am using Wildfly 9.0.1-Final with the resteasy 3.0.11.Final implementation.
You can use synchronized block on static field:
private static final Object LOCK = new Object();
#GET
#Path("find")
#Produces(MediaType.APPLICATION_JSON)
public Response find(){
synchronized(LOCK){
//your code
}
}