0

Quick version of the question:

How do I configure a .Net Core windows service to target the correct database in a multi-tenant environment where each tenant has their own database and they all run on the same self-hosted server?

Some background info:

I am working on a new windows service and, since it is completely new, we are going to use .Net Core. I have read this page and it does talk about how to set environment variable in an IIS app, azure, globally, per command window but it does not really mention a windows service, azure devops or how to handle a multi-tenant environment.

The best I can figure is that you are supposed to set the environment variable in the start parameters for the windows service once it is created but that seems very fragile. This becomes more of a problem when you are looking at 25 with a potential of 100 or more (we are a small growing company). The thought of having to go back and set all of these variables manually if we decide to migrate services to another server is not pleasant.

So the longer version of the question is: Am I on the correct track or is there some better way to set this up so that this does not become such a manual process? Perhaps setting a variable when the service is deployed to the server would do the trick? How would I do that with azure devops though?

Edit 1

Here is a representation of the environment we are running these services in.

Databases (separate machine):
Shared1
Db1
Db2
Db3

Machines:
Server1

Windows Services:
Service1
Service2
Service3

The databases are running on the db server. There is one database per service and there is a shared database which stores some common information (don't think this is relevant for the question). There is one server. On that one server there are copies of the code that needs to run as a windows service. The number of copies of the service corresponds to the number of databases. So for the above scenario: Serivce1 connects to Db1, Service2 connects to Db2, Service3 connects to Db3, etc...

Since I only have one machine, if I set an environment variable to something like ASPNETCORE_DB1 then all three services will read that variable and connect to Db1 which is not what needs to happen.

If I set multiple environment variables: ASPNETCORE_Db1, ASPNETCORE_Db2, ASPNETCORE_Db2, how do each of the services know which environment variable to read?

Thomas927
  • 853
  • 1
  • 11
  • 19
  • Not get your response for several days, was starian's answer helpful? If you have any concern, feel free to share it here. – Hugh Lin Apr 20 '20 at 02:15
  • @Hugh Lin - MSFT From what I understood, no. I read it like: Server1: EnvVar_Db1, EnvVar_Db2, EnvVar_Db2. So how does each instance of the service know which environment variable to look for? The only way I see this working is by defining a build value with each tenant requiring their own build. I would really like to avoid that scenario. Plus it seems like .Net Core was developed with build once, deploy many in mind but this seems to break down in multi-tenant environments. – Thomas927 Apr 20 '20 at 21:16

2 Answers2

1

I have a feeling that you are mixing few things here.

  1. If you want to have different settings per environment you just need to use CreateHostBuilder method. You can read about this here. Then you need set ASPNETCORE_ENVIRONMENT environment variable on machine where you host app. In this approach application at runtime selects proper configuration. However, please do not keep sensitive data in your config files. In this approach you have one compiled code for all tenants.
  2. You can pass configuration when you build an app. To do that you should define variable groups (on Azure DevOps) (one per tenat) and flush your secrets to config file. In addition you can use runtime parameters to define target tenant. Still not secure enough as in artifact (compiled code) you can find secrets. In this approach you have one compiled code per tenant.
  3. If you can use Azure KeyVault I would recommend this approch. You can create one KeyVault per tenat and then in CreateHostBuilder load secrets from proper KeyVault (if you name KeyVault with tenant name, it will simplify selecting proper KeyVault). You can read about this here.

According to you question I assumed that your tenat = envrionemnt. If not then you can have one KeyVault per env like Dev, QA and PROD. Then you should name your secrets with following pattern {tenant_name}_{secret_name}. You should load all secrets atarting app and then at runtime (based on tenant id) select proper secret.

I hope it will help you make good decision.

Krzysztof Madej
  • 32,704
  • 10
  • 78
  • 107
  • For #1 it does not sound like I will be able to have multiple services on the same server since you talk about setting the environment variable at the server level. This will not work for me since one server is going to host copies of this application where each tenant has one copy. I am trying to avoid #2 because I do not want a build per tenant. I am not sure how #3 would work with it building on top of #1. If #1 is not going to work then this would leave me with not options to have one build? – Thomas927 Apr 03 '20 at 17:05
0

Getting corresponding database connectionstring per environment variable is the better way. Simple steps:

  1. Define the same environment variable with different value (connectionstring) in each environment/machine manually
  2. Get that environment variable value in your windows service app and use that value as database connectionstring if not null, otherwise use the default connectionstring.

Edit: For multiple windows services on the same machine, I suggest that you could specify connectionstring per to current service name.

How can a Windows Service determine its ServiceName?

starian chen-MSFT
  • 33,174
  • 2
  • 29
  • 53
  • Guess I will reply here too: how does each service know which environment variable to get without some kind of special build flag? The documentation talks about how you can set the environment variable to determine which appSettings file to use for configuration but does not mention anything about what to do when multiple instances of the same service are on the same machine and need to point to different DBs. .Net Core seems to be build once, deploy many but I cannot figure out how to apply this idea (which I love) to our specific case. – Thomas927 Apr 20 '20 at 21:26
  • @Thomas927 You could just use the same environment variable in each machine and the setting file are the same with the default value. – starian chen-MSFT Apr 21 '20 at 08:14
  • I only have one machine in this case so all of the services are reading the same environment variables. I will edit my question to better explain the environment setup we have. – Thomas927 Apr 21 '20 at 14:10
  • @Thomas927 For this scenario, I am afraid you can't use environment variable, I suggest that you could use different connectionstring per to current service name. There is thread about getting service name: [How can a Windows Service determine its ServiceName?](https://stackoverflow.com/questions/1841790/how-can-a-windows-service-determine-its-servicename) – starian chen-MSFT Apr 23 '20 at 02:25