Unable to access Data Transfer object in WCF Service - asp.net

I have created a web application in .Net which will be used to consume multiple wcf service. The end points will be specified at runtime using below code:
ServiceWCFTest3.Service1Client client1 = new Service1Client();
client1.Endpoint.Address = new EndpointAddress("http://localhost:55473/Service1.svc");
Also please refer below one of the web methods exposed by the service:
EmployeeDetails empDtl= new EmployeeDetails("John");
ResultObject result = channel.ADMSAccess_Entry("Test",empDtl );
These classes are marked as DataContract in the specific wcf service.
Now, when the application is executed to access multiple wcf services by changing end point at runtime, it points to correct service and correct web methods. But the value of object EmployeeDetails and ResultObject is passed and received as null. I need to use these class objects to communicate. Can anybody please suggest how can I pass the values using these objects?

Related

Dynamic scoped dependency resolution in a console app for multiple tenants using dependency injection

In case of a Web API, each request is a distinct scope and dependencies registered as scoped will get resolved per request. So resolving dependencies per request per tenant is easy as the tenant information (like TenantId) can be passed in the HTTP Request headers like below:
services.TryAddScoped<ITenantContext>(x =>
{
var context = x.GetService<IHttpContextAccessor>().HttpContext;
var tenantId = context.Request.Headers["TenantId"].ToString();
var tenantContext = GetTenantContext(tenantId);
return tenantContext;
}
Other registrations first resolve TenantContext and use it to resolve other dependencies. For example, IDatabase will be registered as below. During resolution it will resolve and connect to specific tenant database.
services.TryAddScoped<IDatabase>(x =>
{
var tenantContext = x.GetService<ITenantContext>();
return new Database(tenantContext.DatabaseConnectionString);
}
This is all good in a Web API service because each request is a scope. I am facing challenges using dependency injection in a multi-tenant Console App. Suppose the app processes items from a
multi-tenant queue and each message can belong to a different tenant. While processing each message, it commits data to tenant specific database. So in this case the scope is each message in a queue and message contains the tenantId.
So when the app reads a message from queue, it needs to get TenantContext. Then resolve other dependencies based on this TenantContext.
One straightforward option I see how this dynamic resolution can be achieved is to create the dependent objects manually using the TenantContext but then I wouldn't be able to leverage dependency injection. All objects would get created manually and disposed after going out of scope after the message is processed.
var messgage = GetMessageFromQueue(queueName);
var tenantContext = GetTenantContext(message.TenantId);
var database = GetDatabaseObject(tenantContext);
// Do other processing now we got the database object connected to specific tenant DB
Is there an option in DI where I can pass in the TenantId dynamically so that TenantContext gets set for this scope and then all further resolution within this scope leverage this TenantContext?
Because the role of the tenancy goes beyond the implementation ("this uses X database") and is actually contextual to the action being performed ("this uses X database and must use this connection string based on the context being handled in the action"), there's some risk of assuming that ambient context is present in alternate implementations due to it not expressly being described in your interface in some way, which is where the DI issue is coming up here.
You might be able to:
Update your interfaces so that the tenancy information is an expected parameter of your methods. This ensures that regardless of future implementation, the presence of the tenant ID is explicit in their signature:
public interface ITenantDatabase {
public TResponse Get(string TenantId, int Id);
//... other methods ...
}
Add a factory wrapper around your existing interfaces to handle assigning the context at object creation and have that factory return the IDatabase instance. This is basically what you are proposing manually but with an abstraction around it that you could register and inject to keep the code that leverages it from being responsible for the logic:
public interface ITenantDatabaseFactory {
public IDatabase GetDatabaseForTenant(int TenantId);
}
// Add an implementation that manually generates and returns the scoped objects

How to load the caching layer with data when asp.net core web api is created?

I have created a web api that handles the creation of jwt token based on the encrypted user details that it receives in a post request.
In addition to this STS api should also handle the population of the caching layer (Redis or Hazelcast) with all the user data present in the database. Presently I have registered the caching service using dependency injection.This will happen only once when the api is first initialized.
services.AddSingleton<ICacheService, RedisCacheService>();
And in the TokenController added the service as a parameter to initialize the CachingService class and thereby initialize the caching layer.So that when the cacheService object is fist initialized it fetches all the user rows from the database and stores it as a key value pair inside Redis/Hazelcast database.
public TokenController(
ICryptographyService cryptographyService,
crudDBContext crudDBContext,
IConfiguration configuration,
ICacheService cacheService)
{
_cryptographyService = cryptographyService;
_context = crudDBContext;
_config = configuration;
_cacheService = cacheService;
}
But the Token Controller constructor is initialized only when an endpoint is called, so i had to create a separate default [HttpGet] endpoint to ensure that the constructor is called when the STS api is first initialized so as to ensure that the cacheService object gets created and the data gets loaded to the cache.
public ActionResult<string> Get()
{
return "STS";
}
Please let me know if there is a proper way of doing this without calling an endpoint, like be able to use dependency injection but at the same time call some code without the endpoint being called.I need to use dependency injection because i should be able to switch between Redis and Hazelcast by just changing the classname in the startup.cs file.
With respect to Hazelcast and dependency injection: First you would need to use the sources and not the Hazelcast NuGet version. Next the configuration depends on if you are in a Container Environment or a Hosted Environment. In both cases configuration keys will be gathered from the same sources and in the same order, and options will be registered in the service container, and available via dependency injection

Not able to connect to Azure Key Vault when using Service Identity

I am trying to retrieve secrets from Azure Key Vault using Service Identity in an ASPNet 4.6.2 web application. I am using the code as outlined in this article. Locally, things are working fine, though this is because it is using my identity. When I deploy the application to Azure I get an exception when keyVaultClient.GetSecretAsync(keyUrl) is called.
As best as I can tell everything is configured correctly. I created a User assigned identity so it could be reused and made sure that identity had get access to secrets and keys in the KeyVault policy.
The exception is an AzureServiceTokenProviderException. It is verbose and outlines how it tried four methods to authenticate. The information I'm concerned about is when it tries to use Managed Service Identity:
Tried to get token using Managed Service Identity. Access token could
not be acquired. MSI ResponseCode: BadRequest, Response:
I checked application insights and saw that it tried to make the following connection with a 400 result error:
http://127.0.0.1:41340/MSI/token/?resource=https://vault.azure.net&api-version=2017-09-01
There are two things interesting about this:
Why is it trying to connect to a localhost address? This seems wrong.
Could this be getting a 400 back because the resource parameter isn't escaped?
In the MsiAccessTokenProvider source, it only uses that form of an address when the environment variables MSI_ENDPOINT and MSI_SECRET are set. They are not set in application settings, but I can see them in the debug console when I output environment variables.
At this point I don't know what to do. The examples online all make it seem like magic, but if I'm right about the source of the problem then there's some obscure automated setting that needs fixing.
For completeness here is all of my relevant code:
public class ServiceIdentityKeyVaultUtil : IDisposable
{
private readonly AzureServiceTokenProvider azureServiceTokenProvider;
private readonly Uri baseSecretsUri;
private readonly KeyVaultClient keyVaultClient;
public ServiceIdentityKeyVaultUtil(string baseKeyVaultUrl)
{
baseSecretsUri = new Uri(new Uri(baseKeyVaultUrl, UriKind.Absolute), "secrets/");
azureServiceTokenProvider = new AzureServiceTokenProvider();
keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
}
public async Task<string> GetSecretAsync(string key, CancellationToken cancellationToken = new CancellationToken())
{
var keyUrl = new Uri(baseSecretsUri, key).ToString();
try
{
var secret = await keyVaultClient.GetSecretAsync(keyUrl, cancellationToken);
return secret.Value;
}
catch (Exception ex)
{
/** rethrows error with extra details */
}
}
/** IDisposable support */
}
UPDATE #2 (I erased update #1)
I created a completely new app or a new service instance and was able to recreate the error. However, in all instances I was using a User Assigned Identity. If I remove that and use a System Assigned Identity then it works just fine.
I don't know why these would be any different. Anybody have an insight as I would prefer the user assigned one.
One of the key differences of a user assigned identity is that you can assign it to multiple services. It exists as a separate asset in azure whereas a system identity is bound to the life cycle of the service to which it is paired.
From the docs:
A system-assigned managed identity is enabled directly on an Azure service instance. When the identity is enabled, Azure creates an identity for the instance in the Azure AD tenant that's trusted by the subscription of the instance. After the identity is created, the credentials are provisioned onto the instance. The lifecycle of a system-assigned identity is directly tied to the Azure service instance that it's enabled on. If the instance is deleted, Azure automatically cleans up the credentials and the identity in Azure AD.
A user-assigned managed identity is created as a standalone Azure resource. Through a create process, Azure creates an identity in the Azure AD tenant that's trusted by the subscription in use. After the identity is created, the identity can be assigned to one or more Azure service instances. The lifecycle of a user-assigned identity is managed separately from the lifecycle of the Azure service instances to which it's assigned.
User assigned identities are still in preview for App Services. See the documentation here. It may still be in private preview (i.e. Microsoft has to explicitly enable it on your subscription), it may not be available in the region you have selected, or it could be a defect.
To use a user-assigned identity, the HTTP call to get a token must include the identity's id.
Otherwise it will attempt to use a system-assigned identity.
Why is it trying to connect to a localhost address? This seems wrong.
Because the MSI endpoint is local to App Service, only accessible from within the instance.
Could this be getting a 400 back because the resource parameter isn't escaped?
Yes, but I don't think that was the reason here.
In the MsiAccessTokenProvider source, it only uses that form of an address when the environment variables MSI_ENDPOINT and MSI_SECRET are set. They are not set in application settings, but I can see them in the debug console when I output environment variables.
These are added by App Service invisibly, not added to app settings.
As for how to use the user-assigned identity,
I couldn't see a way to do that with the AppAuthentication library.
You could make the HTTP call manually in Azure: https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http.
Then you gotta take care of caching yourself though!
Managed identity endpoints can't handle a lot of queries at one time :)

Can a WebForms class be used to call a wcf (within the same .net .sln) service function?

We have a .net v4.5.1 solution (.sln) that contains a WebForms project and a WCF project. This was set up to "prove" or "test" the wcf service. The wcf function that does the work takes in a parameter of List(Of clsREC_TaskParam). I have declared all of the DataContract and DataMembers in the wcf code files. The 'clsREC_TaskParam' is a complex class that contains a number of other complex classes. The wcf wsdl reflects these complex classes as expected.
This wcf service is intended to be non-public, but to be used as a background task for the WebForms application so as to NOT hang up the page during the long-process of the WCF-service.
The same class code-file (containing all of these classes) is contained in each project.
The problem??? Question???
When I call the service from a button-click as follows:
Dim bRet As Boolean = False
Dim oSrvc = New GenPDFService.GenPDFServiceClient
m_lstTaskParams = CType(Session("clsTask01"), List(Of clsREC_TaskParam))
bRet = oSrvc.GenPDFs(m_lstTaskParams)
The compiler indicates on the last code line, that
Error 1 Value of type '1-dimensional array of clsREC_TaskParam' cannot be converted to '1-dimensional array of GenPDFService.clsREC_TaskParam' because 'clsREC_TaskParam' is not derived from 'GenPDFService.clsREC_TaskParam'.
This confuses me, since I thought the WebForm class parameter (or wcf return-object) can be serialized for a .Net to .Net wcf service-call.
Your comments and solutions are welcome...thanks.

Strange change in parameter types in WCF Workflow Service Application

I'll explain what I've done so far. I'm using VS2010.
Firstly I've created a ASP.NET Web Service Application (framewkork 3.5) with a service with these operations:
[WebMethod]
public Boolean ShoppingTripNeeded(DateTime d)
{
DBConnection db = new DBConnection();
return db.ShoppingTripNeeded(d);
}
[WebMethod]
public String[] ShopsToVisit(DateTime d)
{
DBConnection db = new DBConnection();
return db.ShopsToVisit(d);
}
[WebMethod]
public Item[] ItemsToBuy(DateTime d, String shop)
{
DBConnection db = new DBConnection();
return db.ItemsToBuy(d, shop);
}
And now I'm creating a WCF Workflow Service Application, in which I want to call sequently the 3 methods above, so I've added a Service Reference to my service wsdl here:
http://awtassignment3-shoppinglistservice1.cloudapp.net/Service1.asmx?WSDL
This referencing adds the 3 operations. The first one "ShoppingTripNeeded" seems to be fine (receiving a DateTime and returning a Boolean), but for the other operations, the parameters have changed in a strange way...
For example the operation ShopsToVisit now ask for a ShopsToVisitRequestBody and returns a ShopsToVisitResponseBody... I don't know why this happens! because the first operation is fine...
Moreover, as I'm working with a workflow, I can't "play" with this types to find out what's going on...
Have you any guess? any help will be fine...
Thanks very much!
Is there a reason you are using ASMX? WCF has replaced Web Services as far more superior service communication technology. See SO: Web Services — WCF vs. Standard, SO: Web Service vs WCF Service
To answer your question:
WF 3.5 will wrap any operation with Request/Response message pattern that is not a primitive (e.g. bool, int). String[] is not a primitive type hence it will be wrapped.
WF 4.0 adding service reference (dialog) will wrap all operations in Request/Response message pair by default regardless if this is primitive or complex type. On top of that, it will create Activity for each operation that it discovers.
Using Request/Response message pattern allows for controlling the message shape specifically message headers.
For Reference:
SO: When should I use Message Contracts instead of DataContract and why?
MSDN: Message Contracts

Resources