Requesting a DbContext from a scope in a SignalR Core hub, passing it a connection string - scope

I have an ASP .Net Core 2.2 Web API with a SignalR hub.
When the API receives a message form the client, it needs to save this message to the database. It does this as follows:
The SignalR Hub:
public class ChatHub : Hub
{
public async Task SendMessageToGroup(int clientId, int groupName, string message)
{
await SaveMessage(clientId, groupName, message);
await Clients.Group(groupName).SendAsync("ReceiveMessage", message);
}
private async Task<bool> SaveMessage(int clientId, string groupName, string message)
{
using (var scope = _serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<TenantContext>();
Message newMessage = new Message()
{
Message = message,
GroupName = groupName,
Timestamp = DateTime.Now
};
dbContext.Messages.Add(pwMessage);
dbContext.SaveChanges();
}
return true;
}
}
All would be well except for the fact that this is a multi-tenant application. Normally, when the client calls the API's controller methods using HTTP requests, the client sends through a "TenantId" header with each request. I then have middleware that intercepts this request, grabs the TenantId from the header, calls a service to retrieve this Tenant using the tenantId, and saves the Tenant object in the HttpContext. Then, on the DbContext's OnConfiguring() method, I use this Tenant Object (stored in the HttpContext) to set the connectionString of the dbContext to whatever database this tenant uses. So:
Middleware:
public class TenantIdentifier
{
private readonly RequestDelegate _next;
public TenantIdentifier(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
string tenantId = httpContext.Request.Headers["tenantId"].FirstOrDefault();
Tenant tenant = await GetTenant(tenantId);
httpContext.Items["Tenant"] = tenant;
await _next.Invoke(httpContext);
}
}
DbContext.cs:
public TenantContext(DbContextOptions<TenantContext> options) : base(options)
{
}
public TenantContext(DbContextOptions<TenantContext> options, IHttpContextAccessor httpContextAccessor) : base(options)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
Tenant tenant = (Tenant)_httpContextAccessor.HttpContext.Items["Tenant"];
string connectionString = $"server={tenant.DbUrl};user id={tenant.DbUserName};Pwd={tenant.DbPassword};database={tenant.DbName};persistsecurityinfo=True;TreatTinyAsBoolean=false";
optionsBuilder.UseMySql(connectionString);
}
Now, when the client calls the SignalR hub, and I create a new scope in the hub and request the DbContext, it's connection string is null. This appears to be because, unlike an HTTP request, calling a SignalR hub doesn't trigger the middleware (which is responsible fro identifying the tenant)
How can I, when requesting a DbContext from the scope, manually pass it the connection string, instead of relying on it to try and generate the connectionString in the OnConfiguring() event (which won't work)
Hope this makes sense :/ Thank you

If you add the IHttpContextAccessor to your Hub class constructor - are you able to access the current context (and headers) there?
public class ChatHub : Hub
{
private IHttpContextAccessor currentContext;
public ChatHub(IHttpContextAccessor currentContext)
{
this.currentContext = currentContext;
}
}
Of course, remembering to register the HttpContextAccessor in the DI too:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddHttpContextAccessor();
services.AddTransient<IUserRepository, UserRepository>();
}

Related

Azure Service Bus - Subscribe multiple topics inside the same worker/hosted service

we have a scenario where we must integrate requests with the same destination system, which exposes its operations with REST APIs (provided by a third party, most likely not Azure). So this is a scenario where n messages are mapped in n actions on the same destination system. There is no multicast or broadcast.
So we are considering Service Bus to achieve this, based on previous experiences on other use cases, and taking advantage of dead letter mechanism among other things.
We need to integrate 6 or 7 different actions with the 3rd party. So on Service Bus we can achieve this by creating 1 topic per action, and this is important because the data that travels on the message is different from action to action.
But we are facing a situation when consuming topics. We are able to have an hosted service in Azure (App Service) that listens on a specific topic and does its stuff.
But since we are trying to listen on several topics, we would like to avoid writing and deploying multiple app services, we would like (if possible) to have a single app service where we 'trigger' each ServiceBusProcessor (one per topic) and even though they all rely on the limits of the app service itself, each processor is independent and is listening on its topic in parallel and processing.
I'll share a code sample below of our hosted service, but we found out two options, we would like to have opinions:
Option 1: we send all messages to the same topic, then by using filters we determine which is the appropriate action. This would make code simple, but it would put all messages on the same 'line' which would make the topic an all purpose topic, which seems wrong
Option 2: based on our sample below, which represents a single hosted service which listens on a single topic, we would break it and inject a List of listeners that implement the same interface, and each one of them would be working independently on its topic and its message. We are not sure if this is feasible and if it works properly, because the app service would have to handle multiple ServiceBusProcessors side by side.
We'd like to know if we are missing some option, or if there is any other better way to achieve this. Hope I've explained it well.
I send below a sample of our hosted service. Thanks a lot.
public class MyService : IHostedService, IMyService
{
private ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
ServiceBusClient client = new ServiceBusClient("connectionString");
ServiceBusProcessor processor = client.CreateProcessor("topicName", "subscriptionName");
processor.ProcessMessageAsync += ProcessMessageAsync;
processor.ProcessErrorAsync += ProcessErrorAsync;
_logger.LogInformation("Listener initialized");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public async Task ProcessMessageAsync(ProcessMessageEventArgs args)
{
var body = args.Message.Body;
// Do stuff with this body...
await args.CompleteMessageAsync(args.Message);
}
public Task ProcessErrorAsync(ProcessErrorEventArgs args)
{
_logger.LogError($"Error ocurred: {args.Exception.ToString()} with message: {args.Exception.Message}");
return Task.CompletedTask;
}
}
Then at ConfigureServices:
services.AddHostedService<MyService>();
So, following option 2, the sample above would be transformed in the following, considering 2 listeners:
public interface IMyService
{
}
public interface IMyListener
{
Task Initialize();
Task ProcessMessageAsync(ProcessMessageEventArgs args);
Task ProcessErrorAsync(ProcessErrorEventArgs args);
}
public class BaseListener
{
private string _connectionString;
private string _topicName;
private string _subscriptionName;
private ILogger<BaseListener> _logger;
public BaseListener(ILogger<BaseListener> logger, string connectionString, string topicName, string subscriptionName)
{
this._connectionString = connectionString;
this._topicName = topicName;
this._subscriptionName = subscriptionName;
this._logger = logger;
}
public Task Initialize()
{
ServiceBusClient client = new ServiceBusClient(this._connectionString);
ServiceBusProcessor processor = client.CreateProcessor(this._topicName, this._subscriptionName);
processor.ProcessMessageAsync += ProcessMessageAsync;
processor.ProcessErrorAsync += ProcessErrorAsync;
_logger.LogInformation("Listener initialized");
return Task.CompletedTask;
}
public async Task ProcessMessageAsync(ProcessMessageEventArgs args)
{
var body = args.Message.Body;
// Do stuff with this body...
await args.CompleteMessageAsync(args.Message);
}
public Task ProcessErrorAsync(ProcessErrorEventArgs args)
{
return Task.CompletedTask;
}
}
public class MyListener1: BaseListener, IMyListener
{
public MyListener1(ILogger<MyListener1> logger) : base(logger, "connectionString", "topic1", "subscription")
{
}
}
public class MyListener2 : BaseListener, IMyListener
{
public MyListener2(ILogger<MyListener2> logger) : base(logger, "connectionString", "topic2", "subscription")
{
}
}
public class MyService : IHostedService, IMyService
{
private ILogger<MyService> _logger;
private IEnumerable<IMyListener> _listeners;
public MyService(ILogger<MyService> logger, IEnumerable<IMyListener> listeners)
{
_logger = logger;
_listeners = listeners;
}
public Task StartAsync(CancellationToken cancellationToken)
{
foreach(var listener in this._listeners)
{
listener.Initialize();
}
_logger.LogInformation("Listeners initialized");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
And on ConfigureServices:
services.AddHostedService<MyService>();
services.AddSingleton<IMyListener, MyListener1>();
services.AddSingleton<IMyListener, MyListener2>();

Application Insights with multiple applications

I have an Application Insights which logs traces from an App Service and an App Function (one resource for 2 functions).
I need to filter traces according to the resource (App Service or App Function) and, if possible, for the App Function which function is actually logging.
Looking at the traces I see the following list of properties:
I thought to find the resource name in the appName property, instead there is the Application Insights resource name, which is useless for me, since all those traces are from that resource.
Note: I don't like the workaround to set a prefix in the message to filter the traces.
UPDATE
I followed Peter Bons suggestions and I created a brand new Function V3 project. The basic version of the project worked also without the Telemetry Initializer, I mean that the Cloud_RoleName property was correctly populated.
Then, I added my changes to adapt the sample code and I found that the problem comes up when I inject a new Telemetry Client. I know, it is not recommended to manually inject TelemetryClient in App Function, but I absolutely need to send Custom Event to Application Insights and, as far as I know, it is not possible with ILogger interface used by default in App Function.
Startup.cs
public class Startup : FunctionsStartup
{
private TelemetryConfiguration telemetryConfiguration;
public override void Configure(IFunctionsHostBuilder builder)
{
var localRoot = Environment.GetEnvironmentVariable("AzureWebJobsScriptRoot");
var azureRoot = $"{Environment.GetEnvironmentVariable("HOME")}/site/wwwroot";
var configBuilder = new ConfigurationBuilder()
.SetBasePath(localRoot ?? azureRoot)
.AddEnvironmentVariables()
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true);
var configuration = configBuilder.Build();
if (builder != null)
{
this.ConfigureServices(builder.Services, configuration);
}
}
private void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<ITelemetryInitializer>(x => new CustomTelemetryInitializer(configuration["appFunctionName"]));
telemetryConfiguration = new TelemetryConfiguration(configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
telemetryConfiguration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
var telemetryClient = new TelemetryClient(telemetryConfiguration);
services.AddSingleton(telemetryClient);
services.AddSingleton<ISampleInterface, SampleService>();
}
}
CustomTelemetryInitializer.cs
public class CustomTelemetryInitializer : ITelemetryInitializer
{
private readonly string roleName;
public CustomTelemetryInitializer(string roleName)
{
this.roleName = roleName;
}
public void Initialize(ITelemetry telemetry)
{
if (string.IsNullOrEmpty(telemetry?.Context?.Cloud?.RoleName))
{
telemetry.Context.Cloud.RoleName = roleName;
}
}
}
SampleService.cs
public class SampleService : ISampleInterface
{
private TelemetryClient telemetryClient;
public SampleService(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
public void TestAppInsights()
{
telemetryClient.TrackEvent("Sample Custom Event with init");
telemetryClient.TrackTrace("Sample Custom Trace with init");
}
}
Function.cs
public class Function1
{
private ISampleInterface service;
public Function1(ISampleInterface service)
{
this.service = service;
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request with init.");
this.service.TestAppInsights();
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
return new OkObjectResult(responseMessage);
}
}
How about inspecting the cloud_RoleName property, available to all telemetry? By default it will have the name of the webapp or function (including slot names) as the value.
Otherwise, if you want to add custom properties or modify properties for all telemetry at one place you can make use of a telemetry initializer as demonstrated here:
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;
namespace CustomInitializer.Telemetry
{
public class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.Cloud.RoleName = "HttpTriggered";
}
}
}
This avoids having to prefix all traces as you mentioned as a work around by having a single piece of code all telemetry passes through:
Another thing
[...] but I absolutely need to send Custom Event to Application Insights and, as far as I know, it is not possible with ILogger interface used by default in App Function.
Do note that you can redirect the output emitted by using the ILogger interface to Application Insights. It will show up as a trace.

Passing user and auditing information in calls to Reliable Services in Service Fabric transport

How can I pass along auditing information between clients and services in an easy way without having to add that information as arguments for all service methods? Can I use message headers to set this data for a call?
Is there a way to allow service to pass that along downstream also, i.e., if ServiceA calls ServiceB that calls ServiceC, could the same auditing information be send to first A, then in A's call to B and then in B's call to C?
There is actually a concept of headers that are passed between client and service if you are using fabric transport for remoting. If you are using Http transport then you have headers there just as you would with any http request.
Note, below proposal is not the easiest solution, but it solves the issue once it is in place and it is easy to use then, but if you are looking for easy in the overall code base this might not be the way to go. If that is the case then I suggest you simply add some common audit info parameter to all your service methods. The big caveat there is of course when some developer forgets to add it or it is not set properly when calling down stream services. It's all about trade-offs, as alway in code :).
Down the rabbit hole
In fabric transport there are two classes that are involved in the communication: an instance of a IServiceRemotingClient on the client side, and an instance of IServiceRemotingListener on the service side. In each request from the client the messgae body and ServiceRemotingMessageHeaders are sent. Out of the box these headers include information of which interface (i.e. which service) and which method are being called (and that's also how the underlying receiver knows how to unpack that byte array that is the body). For calls to Actors, which goes through the ActorService, additional Actor information is also included in those headers.
The tricky part is hooking into that exchange and actually setting and then reading additional headers. Please bear with me here, it's a number of classes involved in this behind the curtains that we need to understand.
The service side
When you setup the IServiceRemotingListener for your service (example for a Stateless service) you usually use a convenience extension method, like so:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
yield return new ServiceInstanceListener(context =>
this.CreateServiceRemotingListener(this.Context));
}
(Another way to do it would be to implement your own listener, but that's not really what we wan't to do here, we just wan't to add things on top of the existing infrastructure. See below for that approach.)
This is where we can provide our own listener instead, similar to what that extention method does behind the curtains. Let's first look at what that extention method does. It goes looking for a specific attribute on assembly level on your service project: ServiceRemotingProviderAttribute. That one is abstract, but the one that you can use, and which you will get a default instance of, if none is provided, is FabricTransportServiceRemotingProviderAttribute. Set it in AssemblyInfo.cs (or any other file, it's an assembly attribute):
[assembly: FabricTransportServiceRemotingProvider()]
This attribute has two interesting overridable methods:
public override IServiceRemotingListener CreateServiceRemotingListener(
ServiceContext serviceContext, IService serviceImplementation)
public override IServiceRemotingClientFactory CreateServiceRemotingClientFactory(
IServiceRemotingCallbackClient callbackClient)
These two methods are responsible for creating the the listener and the client factory. That means that it is also inspected by the client side of the transaction. That is why it is an attribute on assembly level for the service assembly, the client side can also pick it up together with the IService derived interface for the client we want to communicate with.
The CreateServiceRemotingListener ends up creating an instance FabricTransportServiceRemotingListener, however in this implementation we cannot set our own specific IServiceRemotingMessageHandler. If you create your own sub class of FabricTransportServiceRemotingProviderAttribute and override that then you can actually make it create an instance of FabricTransportServiceRemotingListener that takes in a dispatcher in the constructor:
public class AuditableFabricTransportServiceRemotingProviderAttribute :
FabricTransportServiceRemotingProviderAttribute
{
public override IServiceRemotingListener CreateServiceRemotingListener(
ServiceContext serviceContext, IService serviceImplementation)
{
var messageHandler = new AuditableServiceRemotingDispatcher(
serviceContext, serviceImplementation);
return (IServiceRemotingListener)new FabricTransportServiceRemotingListener(
serviceContext: serviceContext,
messageHandler: messageHandler);
}
}
The AuditableServiceRemotingDispatcher is where the magic happens. It is our own ServiceRemotingDispatcher subclass. Override the RequestResponseAsync (ignore HandleOneWay, it is not supported by service remoting, it throws an NotImplementedException if called), like this:
public class AuditableServiceRemotingDispatcher : ServiceRemotingDispatcher
{
public AuditableServiceRemotingDispatcher(ServiceContext serviceContext, IService service) :
base(serviceContext, service) { }
public override async Task<byte[]> RequestResponseAsync(
IServiceRemotingRequestContext requestContext,
ServiceRemotingMessageHeaders messageHeaders,
byte[] requestBodyBytes)
{
byte[] userHeader = null;
if (messageHeaders.TryGetHeaderValue("user-header", out auditHeader))
{
// Deserialize from byte[] and handle the header
}
else
{
// Throw exception?
}
byte[] result = null;
result = await base.RequestResponseAsync(requestContext, messageHeaders, requestBodyBytes);
return result;
}
}
Another, easier, but less flexible way, would be to directly create an instance of FabricTransportServiceRemotingListener with an instance of our custom dispatcher directly in the service:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
yield return new ServiceInstanceListener(context =>
new FabricTransportServiceRemotingListener(this.Context, new AuditableServiceRemotingDispatcher(context, this)));
}
Why is this less flexible? Well, because using the attribute supports the client side as well, as we see below
The client side
Ok, so now we can read custom headers when receiving messages, how about setting those? Let's look at the other method of that attribute:
public override IServiceRemotingClientFactory CreateServiceRemotingClientFactory(IServiceRemotingCallbackClient callbackClient)
{
return (IServiceRemotingClientFactory)new FabricTransportServiceRemotingClientFactory(
callbackClient: callbackClient,
servicePartitionResolver: (IServicePartitionResolver)null,
traceId: (string)null);
}
Here we cannot just inject a specific handler or similar as for the service, we have to supply our own custom factory. In order not to have to reimplement the particulars of FabricTransportServiceRemotingClientFactory I simply encapsulate it in my own implementation of IServiceRemotingClientFactory:
public class AuditedFabricTransportServiceRemotingClientFactory : IServiceRemotingClientFactory, ICommunicationClientFactory<IServiceRemotingClient>
{
private readonly ICommunicationClientFactory<IServiceRemotingClient> _innerClientFactory;
public AuditedFabricTransportServiceRemotingClientFactory(ICommunicationClientFactory<IServiceRemotingClient> innerClientFactory)
{
_innerClientFactory = innerClientFactory;
_innerClientFactory.ClientConnected += OnClientConnected;
_innerClientFactory.ClientDisconnected += OnClientDisconnected;
}
private void OnClientConnected(object sender, CommunicationClientEventArgs<IServiceRemotingClient> e)
{
EventHandler<CommunicationClientEventArgs<IServiceRemotingClient>> clientConnected = this.ClientConnected;
if (clientConnected == null) return;
clientConnected((object)this, new CommunicationClientEventArgs<IServiceRemotingClient>()
{
Client = e.Client
});
}
private void OnClientDisconnected(object sender, CommunicationClientEventArgs<IServiceRemotingClient> e)
{
EventHandler<CommunicationClientEventArgs<IServiceRemotingClient>> clientDisconnected = this.ClientDisconnected;
if (clientDisconnected == null) return;
clientDisconnected((object)this, new CommunicationClientEventArgs<IServiceRemotingClient>()
{
Client = e.Client
});
}
public async Task<IServiceRemotingClient> GetClientAsync(
Uri serviceUri,
ServicePartitionKey partitionKey,
TargetReplicaSelector targetReplicaSelector,
string listenerName,
OperationRetrySettings retrySettings,
CancellationToken cancellationToken)
{
var client = await _innerClientFactory.GetClientAsync(
serviceUri,
partitionKey,
targetReplicaSelector,
listenerName,
retrySettings,
cancellationToken);
return new AuditedFabricTransportServiceRemotingClient(client);
}
public async Task<IServiceRemotingClient> GetClientAsync(
ResolvedServicePartition previousRsp,
TargetReplicaSelector targetReplicaSelector,
string listenerName,
OperationRetrySettings retrySettings,
CancellationToken cancellationToken)
{
var client = await _innerClientFactory.GetClientAsync(
previousRsp,
targetReplicaSelector,
listenerName,
retrySettings,
cancellationToken);
return new AuditedFabricTransportServiceRemotingClient(client);
}
public Task<OperationRetryControl> ReportOperationExceptionAsync(
IServiceRemotingClient client,
ExceptionInformation exceptionInformation,
OperationRetrySettings retrySettings,
CancellationToken cancellationToken)
{
return _innerClientFactory.ReportOperationExceptionAsync(
client,
exceptionInformation,
retrySettings,
cancellationToken);
}
public event EventHandler<CommunicationClientEventArgs<IServiceRemotingClient>> ClientConnected;
public event EventHandler<CommunicationClientEventArgs<IServiceRemotingClient>> ClientDisconnected;
}
This implementation simply passes along anything heavy lifting to the underlying factory, while returning it's own auditable client that similarily encapsulates a IServiceRemotingClient:
public class AuditedFabricTransportServiceRemotingClient : IServiceRemotingClient, ICommunicationClient
{
private readonly IServiceRemotingClient _innerClient;
public AuditedFabricTransportServiceRemotingClient(IServiceRemotingClient innerClient)
{
_innerClient = innerClient;
}
~AuditedFabricTransportServiceRemotingClient()
{
if (this._innerClient == null) return;
var disposable = this._innerClient as IDisposable;
disposable?.Dispose();
}
Task<byte[]> IServiceRemotingClient.RequestResponseAsync(ServiceRemotingMessageHeaders messageHeaders, byte[] requestBody)
{
messageHeaders.SetUser(ServiceRequestContext.Current.User);
messageHeaders.SetCorrelationId(ServiceRequestContext.Current.CorrelationId);
return this._innerClient.RequestResponseAsync(messageHeaders, requestBody);
}
void IServiceRemotingClient.SendOneWay(ServiceRemotingMessageHeaders messageHeaders, byte[] requestBody)
{
messageHeaders.SetUser(ServiceRequestContext.Current.User);
messageHeaders.SetCorrelationId(ServiceRequestContext.Current.CorrelationId);
this._innerClient.SendOneWay(messageHeaders, requestBody);
}
public ResolvedServicePartition ResolvedServicePartition
{
get { return this._innerClient.ResolvedServicePartition; }
set { this._innerClient.ResolvedServicePartition = value; }
}
public string ListenerName
{
get { return this._innerClient.ListenerName; }
set { this._innerClient.ListenerName = value; }
}
public ResolvedServiceEndpoint Endpoint
{
get { return this._innerClient.Endpoint; }
set { this._innerClient.Endpoint = value; }
}
}
Now, in here is where we actually (and finally) set the audit name that we want to pass along to the service.
Call chains and service request context
One final piece of the puzzle, the ServiceRequestContext, which is a custom class that allows us to handle an ambient context for a service request call. This is relevant because it gives us an easy way to propagate that context information, like the user or a correlation id (or any other header information we want to pass between client and service), in a chain of calls. The implementation ServiceRequestContext looks like:
public sealed class ServiceRequestContext
{
private static readonly string ContextKey = Guid.NewGuid().ToString();
public ServiceRequestContext(Guid correlationId, string user)
{
this.CorrelationId = correlationId;
this.User = user;
}
public Guid CorrelationId { get; private set; }
public string User { get; private set; }
public static ServiceRequestContext Current
{
get { return (ServiceRequestContext)CallContext.LogicalGetData(ContextKey); }
internal set
{
if (value == null)
{
CallContext.FreeNamedDataSlot(ContextKey);
}
else
{
CallContext.LogicalSetData(ContextKey, value);
}
}
}
public static Task RunInRequestContext(Func<Task> action, Guid correlationId, string user)
{
Task<Task> task = null;
task = new Task<Task>(async () =>
{
Debug.Assert(ServiceRequestContext.Current == null);
ServiceRequestContext.Current = new ServiceRequestContext(correlationId, user);
try
{
await action();
}
finally
{
ServiceRequestContext.Current = null;
}
});
task.Start();
return task.Unwrap();
}
public static Task<TResult> RunInRequestContext<TResult>(Func<Task<TResult>> action, Guid correlationId, string user)
{
Task<Task<TResult>> task = null;
task = new Task<Task<TResult>>(async () =>
{
Debug.Assert(ServiceRequestContext.Current == null);
ServiceRequestContext.Current = new ServiceRequestContext(correlationId, user);
try
{
return await action();
}
finally
{
ServiceRequestContext.Current = null;
}
});
task.Start();
return task.Unwrap<TResult>();
}
}
This last part was much influenced by the SO answer by Stephen Cleary. It gives us an easy way to handle the ambient information down a hierarcy of calls, weather they are synchronous or asyncronous over Tasks. Now, with this we have a way of setting that information also in the Dispatcher on the service side:
public override Task<byte[]> RequestResponseAsync(
IServiceRemotingRequestContext requestContext,
ServiceRemotingMessageHeaders messageHeaders,
byte[] requestBody)
{
var user = messageHeaders.GetUser();
var correlationId = messageHeaders.GetCorrelationId();
return ServiceRequestContext.RunInRequestContext(async () =>
await base.RequestResponseAsync(
requestContext,
messageHeaders,
requestBody),
correlationId, user);
}
(GetUser() and GetCorrelationId() are just helper methods that gets and unpacks the headers set by the client)
Having this in place means that any new client created by the service for any aditional call will also have the sam headers set, so in the scenario ServiceA -> ServiceB -> ServiceC we will still have the same user set in the call from ServiceB to ServiceC.
what? that easy? yes ;)
From inside a service, for instance a Stateless OWIN web api, where you first capture the user information, you create an instance of ServiceProxyFactoryand wrap that call in a ServiceRequestContext:
var task = ServiceRequestContext.RunInRequestContext(async () =>
{
var serviceA = ServiceProxyFactory.CreateServiceProxy<IServiceA>(new Uri($"{FabricRuntime.GetActivationContext().ApplicationName}/ServiceA"));
await serviceA.DoStuffAsync(CancellationToken.None);
}, Guid.NewGuid(), user);
Ok, so to sum it up - you can hook into the service remoting to set your own headers. As we see above there is some work that needs to be done to get a mechanism for that in place, mainly creating your own subclasses of the underlying infrastructure. The upside is that once you have this in place, then you have a very easy way for auditing your service calls.

Unable to use multiple instances of MobileServiceClient concurrently

I structured my project into multiple mobile services, grouped by the application type eg:
my-core.azure-mobile.net (user, device)
my-app-A.azure-mobile.net (sales, order, invoice)
my-app-B.azure-mobile.net (inventory & parts)
I'm using custom authentication for all my services, and I implemented my own SSO by setting the same master key to all 3 services.
Things went well when I tested using REST client, eg. user who "logged in" via custom api at my-core.azure-mobile.net is able to use the returned JWT token to access restricted API of the other mobile services.
However, in my xamarin project, only the first (note, in sequence of creation) MobileServiceClient object is working properly (eg. returning results from given table). The client object are created using their own url and key respectively, and stored in a dictionary.
If i created client object for app-A then only create for app-B, I will be able to perform CRUD+Sync on sales/order/invoice entity, while CRUD+Sync operation on inventory/part entity will just hang there. The situation is inverse if I swap the client object creation order.
I wonder if there is any internal static variables used within the MobileServiceClient which caused such behavior, or it is a valid bug ?
=== code snippet ===
public class AzureService
{
IDictionary<String, MobileServiceClient> services = new Dictionary<String, MobileServiceClient>();
public MobileServiceClient Init (String key, String applicationURL, String applicationKey)
{
return services[key] = new MobileServiceClient (applicationURL, applicationKey);
}
public MobileServiceClient Get(String key)
{
return services [key];
}
public void InitSyncContext(MobileServiceSQLiteStore offlineStore)
{
// Uses the default conflict handler, which fails on conflict
// To use a different conflict handler, pass a parameter to InitializeAsync.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=521416
var syncHandler = new MobileServiceSyncHandler ();
foreach(var client in services) {
client.Value.SyncContext.InitializeAsync (offlineStore, syncHandler);
}
}
public void SetAuthenticationToken(String uid, String token)
{
var user = new MobileServiceUser(uid);
foreach(var client in services) {
client.Value.CurrentUser = user;
client.Value.CurrentUser.MobileServiceAuthenticationToken = token;
}
}
public void ClearAuthenticationToken()
{
foreach(var client in services) {
client.Value.CurrentUser = null;
}
}
}
=== more code ===
public class DatabaseService
{
public static MobileServiceSQLiteStore LocalStore = null;
public static string Path { get; set; }
public static ISet<IEntityMappingProvider> Providers = new HashSet<IEntityMappingProvider> ();
public static void Init (String dbPath)
{
LocalStore = new MobileServiceSQLiteStore(dbPath);
foreach(var provider in Providers) {
var types = provider.GetSupportedTypes ();
foreach(var t in types) {
JObject item = null;
// omitted detail to create JObject using reflection on given type
LocalStore.DefineTable(tableName, item);
}
}
}
}
=== still code ===
public class AzureDataSyncService<T> : IAzureDataSyncService<T>
{
public MobileServiceClient ServiceClient { get; set; }
public virtual Task<List<T>> GetAll()
{
try
{
var theTable = ServiceClient.GetSyncTable<T>();
return theTable.ToListAsync();
}
catch (MobileServiceInvalidOperationException msioe)
{
Debug.WriteLine("GetAll<{0}> EXCEPTION TYPE: {1}, EXCEPTION:{2}", typeof(T).ToString(), msioe.GetType().ToString(), msioe.ToString());
}
catch (Exception e)
{
Debug.WriteLine("GetAll<{0}> EXCEPTION TYPE: {1}, EXCEPTION:{2}", typeof(T).ToString(), e.GetType().ToString(), e.ToString());
}
List<T> theCollection = Enumerable.Empty<T>().ToList();
return Task.FromResult(theCollection);
}
}
=== code ===
public class UserService : AzureDataSyncService<User>
{
}
public class PartService : AzureDataSyncService<Part>
{
}
const string coreApiURL = #"https://my-core.azure-mobile.net/";
const string coreApiKey = #"XXXXX";
const string invApiURL = #"https://my-inventory.azure-mobile.net/";
const string invApiKey = #"YYYYY";
public async void Foo ()
{
DatabaseService.Providers.Add (new CoreDataMapper());
DatabaseService.Providers.Add (new InvDataMapper ());
DatabaseService.Init (DatabaseService.Path);
var coreSvc = AzureService.Instance.Init ("Core", coreApiURL, coreApiKey);
var invSvc = AzureService.Instance.Init ("Inv", invApiURL, invApiKey);
AzureService.Instance.InitSyncContext (DatabaseService.LocalStore);
AzureService.Instance.SetAuthenticationToken("AAA", "BBB");
UserService.Instance.ServiceClient = coreSvc;
PartService.Instance.ServiceClient = invSvc;
var x = await UserService.GetAll(); // this will work
var y = await PartService.GetAll(); // but not this
}
It's ok to use multiple MobileServiceClient objects, but not with the same local database. The offline sync feature uses a particular system tables to keep track of table operations and errors, and it is not supported to use the same local store across multiple sync contexts.
I'm not totally sure why it is hanging in your test, but it's possible that there is a lock on the local database file and the other sync context is waiting to get access.
You should instead use different local database files for each service and doing push and pull on each sync context. With your particular example, you just need to move LocalStore out of DatabaseService and into a dictionary in AzureService.
In general, it seems like an unusual design to use multiple services from the same client app. Is there a particular reason that the services need to be separated from each other?

(Not Found) Error in Azure Mobile Services .NET Backend

Been stuck with that error till madness phases ... Please help
I have created an Azure Mobile Service .NET backend, and am now trying to call its Post function from a Xamarin Android client
I initialize and call the Insert async function (these are just snippets from my code)
private static IMobileServiceTable<Todo> _todoMobileServiceTable;
public static bool? InitializeAms()
{
try
{
CurrentPlatform.Init();
_mobileServiceClient = new MobileServiceClient(applicationUrl, applicationKey);
_todoMobileServiceTable = _mobileServiceClient.GetTable<Todo>();
return true;
}
catch (MalformedURLException malformedUrlException)
{
ReportHelper.Report(Tag, "There was an error creating the Mobile Service. Verify the URL", true, malformedUrlException);
}
catch (Exception exception)
{
ReportHelper.Report(Tag, "Error occurred during initialization of Azure Mobile Services", true, exception);
}
return null;
}
_todoMobileServiceTable.InsertAsync(Todo);
I get the following error when calling .InsertAsync(Todo)
The request could not be completed. (Not Found)
N.B:
Azure storage client is not available for xamarin yet, and I have no other choice other than to use this dirty fork which is 1 year old and is made for iOS not Android (although it works fine with azure mobile service javascript) https://github.com/zgramana/IOSAzureBlobUploader
It works if I use the browser 'try it out' button but it doesn't work when I call it from the xamarin client app.
It works from the xamarin client app if I use the javascript mobile service
This error occurs both on the local azure mobile service and the published one online
Here is the WebApiConfig class
namespace Service.Ams
{
public static class WebApiConfig
{
public static void Register()
{
// Use this class to set configuration options for your mobile service
ConfigOptions options = new ConfigOptions();
// Use this class to set WebAPI configuration options
HttpConfiguration config = ServiceConfig.Initialize(new ConfigBuilder(options));
// To display errors in the browser during development, uncomment the following
// line. Comment it out again when you deploy your service for production use.
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
Database.SetInitializer(new ServiceAmsInitializer());
}
}
public class ServiceAmsInitializer : ClearDatabaseSchemaIfModelChanges<ServiceAmsDbContext>
{}
}
Here is the TableController class
namespace Service.Ams.Controllers
{
public class TodoItemController : TableController<TodoItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
ServiceAmsDbContext serviceAmsDbContext = new ServiceAmsDbContext();
DomainManager = new EntityDomainManager<TodoItem>(serviceAmsDbContext, Request, Services);
}
// GET tables/TodoItem
[AuthorizeLevel(AuthorizationLevel.Admin)]
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
// GET tables/TodoItem/55D11C86-6EA6-4C44-AA33-337FC9A27525
[AuthorizeLevel(AuthorizationLevel.Admin)]
public SingleResult<TodoItem> GetTodoItem(string id)
{
return Lookup(id);
}
// PATCH tables/TodoItem/55D11C86-6EA6-4C44-AA33-337FC9A27525
[AuthorizeLevel(AuthorizationLevel.Admin)]
public Task<TodoItem> PatchTodoItem(string id, Delta<TodoItem> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/TodoItem/55D11C86-6EA6-4C44-AA33-337FC9A27525
[AuthorizeLevel(AuthorizationLevel.Anonymous)]
public async Task<IHttpActionResult> PostTodoItem(TodoItem item)
{
string storageAccountName;
string storageAccountKey;
// Try to get the Azure storage account token from app settings.
if (
!(Services.Settings.TryGetValue("STORAGE_ACCOUNT_NAME", out storageAccountName) |
Services.Settings.TryGetValue("STORAGE_ACCOUNT_ACCESS_KEY", out storageAccountKey)))
Services.Log.Error("Could not retrieve storage account settings.");
// Set the URI for the Blob Storage service.
Uri blobEndpoint = new Uri(string.Format("http://127.0.0.1:10000/{0}/", storageAccountName));
// Create the BLOB service client.
CloudBlobClient blobClient = new CloudBlobClient(blobEndpoint, new StorageCredentials(storageAccountName, storageAccountKey));
// Create a container, if it doesn't already exist.
CloudBlobContainer container = blobClient.GetContainerReference(item.ContainerName);
await container.CreateIfNotExistsAsync();
// Create a shared access permission policy.
BlobContainerPermissions containerPermissions = new BlobContainerPermissions
{
PublicAccess = BlobContainerPublicAccessType.Blob
};
// Enable anonymous read access to BLOBs.
container.SetPermissions(containerPermissions);
// Define a policy that gives write access to the container for 5 minutes.
SharedAccessBlobPolicy sasPolicy = new SharedAccessBlobPolicy
{
SharedAccessStartTime = DateTime.UtcNow,
SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(5),
Permissions = SharedAccessBlobPermissions.Write
};
// Get the SAS as a string.
item.SasQueryString = container.GetSharedAccessSignature(sasPolicy);
// Set the URL used to store the image.
item.ImageLqUri = string.Format("{0}{1}/{2}", blobEndpoint, item.ContainerName, item.ResourceNameLq);
item.ImageHqUri = string.Format("{0}{1}/{2}", blobEndpoint, item.ContainerName, item.ResourceNameHq);
// Complete the insert operation.
TodoItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new {id = current.Id}, current);
}
// DELETE tables/TodoItem/55D11C86-6EA6-4C44-AA33-337FC9A27525
[AuthorizeLevel(AuthorizationLevel.Admin)]
public Task DeleteTodoItem(string id)
{
return DeleteAsync(id);
}
}
}
Here is the EntityData class
namespace Service.Ams.DataObjects
{
[Table("dbo.TodoItems")]
public class TodoItem : EntityData
{
public string ContainerName { get; set; }
public string ResourceNameLq { get; set; }
public string ResourceNameHq { get; set; }
public string SasQueryString { get; set; }
public string ImageLqUri { get; set; }
public string ImageHqUri { get; set; }
}
}
Is there any way you can get a dump of what the HTTP request looks like?
I don't have an android client handy here but we can have a look on Monday.
Henrik
TableController and client corresponding class must have the same name for example TodoController and TodoClass. I don't know if there is an attribute that modifies this rule and how to use, if at server side decorating TableController class or at client side decorating data class.

Resources