WCF Service In Orchard Module With Multi Tenancy - wcf

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.

Related

Is there a way to specify which IAuthProvider to use for authentication on a particular Service class?

I have two services within the same project:
[Authenticate]
public class OnlyDoesBasicAuth : Service
{
}
[Authenticate]
public class OnlyDoesJwtAuth : Service
{
}
//AppHost
public override void Configure(Container container)
{
Plugins.Add(new AuthFeature(
() => new AuthUserSession(),
new IAuthProvider[]
{
new BasicAuthProvider(AppSettings),
new JwtAuthProvider(AppSettings)
}
)
{
HtmlRedirect = null
});
}
The way this is set up, I can get into both services using Basic Authentication or with a valid Bearer token. I'd prefer to have it so that only one service can do one means of authentication. Is there a way to specify with provider to use on authentication?
I'm at a loss on how to approach this. I was thinking that maybe there was a way via global request filter or something to that effect, but maybe that's not the way to go. I could split the project into two projects, which will definitely work, but it's not the way I want to approach it.
I'm just looking for a way for the OnlyDoesBasicAuth to only use the BasicAuthProvider on authentication and OnlyDoesJwtAuth to only use the JwtAuthProvider within the same project. Any and all suggestions will be greatly appreciated.
Thanks in advance.
Typically once you're authenticated using any of the Auth Providers you're considered as an Authenticated User everywhere in ServiceStack.
You can restrict access so that a Service needs to be Authenticated with by specifying the AuthProvider name in the [Authenticate] attribute, e.g:
[Authenticate(BasicAuthProvider.Name)]
public class OnlyDoesBasicAuth : Service
{
}
[Authenticate(JwtAuthProvider.Name)]
public class OnlyDoesJwtAuth : Service
{
}
Alternatively you can validate within your Service that they need to be authenticated with a specific Auth Provider, e.g:
if (SessionAs<AuthUserSession>().AuthProvider != JwtAuthProvider.Name)
throw HttpError.Forbidden("JWT Required");

Can I add a service info / health check endpoint to my Identity Server 3-based service?

I have a set of AspNet WebApi-based web services and an IdentityServer3-based authentication service. All of the web services support a simple service info endpoint that we use for monitoring and diagnosis. It reports the service version and the server name. The only service that currently does not support the service info endpoint is the IdentityServer3-based authentication service.
Is there a way to add a simple endpoint to an IdentityServer3-based service? In GitHub issue 812 Brock Allen says "We have a way to add custom controllers, but it's undocumented, current unsupported, and not really done." I'd rather not take that indocumented, unsupported route.
Is there a way to modify/extend the discovery endpoint to include additional information?
Here's how I ended up coding this up. At a high level, basically I added a Controllers folder, created a AuthenticationServiceInfoController class with a single GET action method and then registered that controller during Startup. As noted in comment above, my solution had some extra complexity because my AuthenticationServiceInfoController inherited from a base ServiceInfoController defined elsewhere, but I've tried to eliminate that from this sample. So, the controller code looks like this:
[RoutePrefix("api/v1/serviceinfo")]
public class AuthencticationServiceInfoController : IServiceInfoController
{
[Route("")]
[Route("~/api/serviceinfo")]
public IHttpActionResult Get()
{
try
{
ServiceInformation serviceInfo = new ServiceInformation();
serviceInfo.ServiceVersion = Global.serviceVersion;
return Ok(serviceInfo);
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
}
It implements a simple interface:
public interface IServiceInfoController
{
IHttpActionResult Get();
}
And in my Startup.Configuration method where I configure Identity Server, I've got:
var idSrvFactory = new IdentityServerServiceFactory();
idSrvFactory.Register(new Registration<IServiceInfoController, Controllers.AuthencticationServiceInfoController>());
I think that's all that it took. It's in place and working in my Identity Server 3-based service.

Server Side WCF Service : How to reference/interact with other server components

I have a fairly simple design question about interaction between a self hosted WCF Service and other business classes.
Here is the WCF service contract :
/// <summary>
/// Represent requests on hardware components made by a client to the controler service
/// </summary>
[ServiceContract(CallbackContract = typeof(IHardwareServiceCallback))]
public interface IHardwareService
{
[OperationContract(IsOneWay = true)]
void OpenLeftDrawer();
[OperationContract(IsOneWay = true)]
void OpenRightDrawer();
}
The service implementation
public class HardwareService : IHardwareService
{
public void OpenLeftDrawer()
{
}
public void OpenRightDrawer()
{
}
}
A class which purpose is to handle the business logic regarding client calls on the server
class DrawerRequestManager
{
// Server side Business logic to handle OpenDrawer requests from client
}
Hosting scenario
Uri adrbase = new Uri(srvConfig.Address);
var host = new ServiceHost(typeof(HardwareService), adrbase);
host.AddServiceEndpoint(typeof(IHardwareService), srvConfig.Binding, srvConfig.Address);
host.Open();
Since this is the host that is managing the service instance lifetime, what is the proper way to handle the link between the service instance and business logic classes (DrawerRequestManager for exemple).
I'm using IOC container but i'm also interested in the response when not using IOC container.
Thanks in advance !
WCF uses parameterless constructor to create service objects, but there is a way to alter that. You need to implement your own instance provider.
You can inject your instance provider via ServiceHostFactory: see here.
Alternatively you can inject instance provider by using custom attribute for your service: see here.
Either way gives you full control of how services instances are created. You can use IOC there or just call constructor manually passing any parameters you want (e.g. reference to DrawerRequestManager instance).

How to get base url without accessing a request

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

pointer to service from ServiceHost

I have the following WCF code:
ServiceHost host = null;
if (host == null)
host = new ServiceHost(typeof(RadisService));
How can i get a pointer to my RadisService, to make calls with it?
Well it was really for testing purposes, but please allow me to ask the question anyway, for educational purposes. What happens if my service is running on a machine (using a GUI host), several clients from different remote machines connect to the service and through the GUI leave comments on my service.
The code on my service looks like this:
public class MyClass
{
[DataMember]
static Dictionary<String, Variable> m_Variables = new
Dictionary<String, Variable>();
....
}
[ServiceContract]
public interface IMyClassService
{
[OperationContract]
bool AddVariable(String name, Variable value);
[OperationContract]
bool RemoveVariable(String name);
[OperationContract]
bool GetVariable(string name, Variable variable);
[OperationContract] List<String> GetVariableDetails();
...
}
So from my service host GUI i would like to be able to access GetVariableDetails(), and preview all the comments added from all the different clients at this point. How would i achieve this?
If you make your service a singleton you can create an instance of the service and give it to the ServiceHost:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class CalculatorService: ICalculatorService
{
....
CalculatorService service = new CalculatorService();
ServiceHost serviceHost = new ServiceHost(service, baseAddress);
You cannot. The ServiceHost will host 1-n service class instances to handle incoming requests, but those are typically "per-call", e.g. a service class instance is created when a new request comes in, a method is called on the service class, and then it's disposed again.
So the ServiceHost doesn't really have any "service" class instance at hand that it can use and call methods on.
What exactly are you trying to achieve?
Update: the service host should really not do anything besides hosting the service - it should definitely not be calling into the service itself.
What you're trying to achieve is some kind of an administrative console - a GUI showing the current comments in your system. Do this either via a direct database query, or then just have a GUI console to call into your service and get those entries - but don't put that burden on the ServiceHost - that's the wrong place to put this functionality.