I would like to see if/how it would be possible to plug into the deserialization process for a parameter that's decorated with the ServiceBusTrigger?
Say I have a function that looks like:
public static void HandleMessage([ServiceBusTrigger("myqueue")] MyCustomType myCustomType) { }
How would I go about taking over the deserialization? I know that there is a notion of an IArgumentBindingProvider and IArgumentBinding but it does not look like ServiceBusTrigger supports these concepts.
I know I can use GetBody<Stream>() and deserialize that way but I'd like to know if I can plug into the ServiceBusTrigger's pipeline. By the looks at the SDK, the ServiceBusTrigger has a hard coded list of IQueueArgumentBindingProviders and so I can't add my own.
If you have a look at the Azure WebJobs SDK Extensions, there is an overview on how to create your own bindings :
Binding Extensions Overview
Otherwise the ServiceBusConfiguration exposes a MessagingProvider property that allows you to intercept the ServiceBusTrigger pipeline:
private static void Main()
{
var sbConfig = new ServiceBusConfiguration()
{
MessagingProvider = // you implemetation of the MessagingProvider class goes here !!!
};
var config = new JobHostConfiguration();
config.UseServiceBus(sbConfig);
new JobHost(config).RunAndBlock();
}
Here is a simple skeleton of a MessagingProvider implementation:
public sealed class MyMessagingProvider : MessagingProvider
{
private readonly ServiceBusConfiguration _config;
public MyMessagingProvider(ServiceBusConfiguration config)
: base(config)
{
_config = config;
}
public override MessageProcessor CreateMessageProcessor(string entityPath)
{
return new MyMessageProcessor(_config.MessageOptions);
}
private class MyMessageProcessor : MessageProcessor
{
public MyMessageProcessor(OnMessageOptions messageOptions)
: base(messageOptions)
{
}
public override Task<bool> BeginProcessingMessageAsync(BrokeredMessage message, CancellationToken cancellationToken)
{
// Intercept the message before the execution of the triggerred function
return base.BeginProcessingMessageAsync(message, cancellationToken);
}
public override Task CompleteProcessingMessageAsync(BrokeredMessage message, FunctionResult result, CancellationToken cancellationToken)
{
// Intercept the message after the execution of the triggerred function and before being completed
return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
}
}
}
So you're main function now looks like that:
private static void Main()
{
var sbConfig = new ServiceBusConfiguration();
sbConfig.MessagingProvider = new MyMessagingProvider(sbConfig);
var config = new JobHostConfiguration();
config.UseServiceBus(sbConfig);
new JobHost(config).RunAndBlock();
}
Related
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.
I am looking for a ZeroMQ implementation of an ICommunicationListener that I can use with a service fabric to run a ZeroMQ endpoint on Azure.
I looked for hours and I can't find any. Does anyone know a solution for this? I currently use the "Service App Fabric / .net core 2.0 stateless service" template,
which allows me to override
IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners(),
when I have the ICommunicationListener implementation for ZeroMQ,
or to override a Task RunAsync(CancellationToken cancellationToken),
when I want to setup the sockets myself.
My first try won't work:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
using (var server = new ResponseSocket("tcp://xx.xx.xx.xx:xxxxx"))
{
while (!cancellationToken.IsCancellationRequested)
{
var message = server.ReceiveFrameBytes();
ServiceEventSource.Current.ServiceMessage(this.Context, "Message {0}",
System.Text.Encoding.UTF8.GetString(message));
}
}
}
Result of the above is a service that won't start. Can't find much logging except this:
"There was an error during CodePackage activation.The service host terminated with exit code:255"
If none exist, you can create your own, by creating an implementation of ICommunicationListener and returning that from CreateServiceInstanceListeners.
Use OpenAsync to open a channel and start listening. Use CloseAsync to stop listening.
Have a look at this implementation for Service Bus, for inspiration.
Here is an rough example of an ICommunicationListener implementation for ZeroMQ. This implementation will act as ZeroMQ ResponseSocket, but can easily be changed to RequestSocket, SubscriberSocket or any kind of NetMQ.Sockets.* socket implementation that you like. Of course it will need some more detail in the implementation like not throwing an exception on retrieving a message, but it should give a clear view of how its done. Its greatly inspired by existing dotnetcore implementations of the ICommunicationListener interface.
public class ZeroMqResponseSocketCommunicationListener : ICommunicationListener, IDisposable
{
private readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource();
private readonly ResponseSocket _responseSocket = new ResponseSocket();
private readonly ServiceContext _serviceContext;
private readonly string _endpointName;
public ZeroMqResponseSocketCommunicationListener(ServiceContext serviceContext, string endpointName)
{
if (string.IsNullOrEmpty(endpointName))
throw new ArgumentException("endpointName cannot be null or empty string.");
_serviceContext = serviceContext;
_endpointName = endpointName;
}
public Task<string> OpenAsync(CancellationToken cancellationToken)
{
var address = GetListenerUrl();
if (address == null)
throw new InvalidOperationException("No Url returned from ZeroMqResponseSocketCommunicationListener.GetListenerUrl");
_responseSocket.Bind(address);
ThreadPool.QueueUserWorkItem(state => MessageHandler(_cancellationToken.Token));
return Task.FromResult(address);
}
public Task CloseAsync(CancellationToken cancellationToken)
{
_responseSocket.Close();
return Task.FromResult(true);
}
public void Abort()
{
_responseSocket.Close();
}
private void MessageHandler(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var message = _responseSocket.ReceiveFrameBytes();
if (message != null)
throw new Exception($"Message {Encoding.UTF8.GetString(message)}");
}
}
private string GetListenerUrl()
{
var endpoints = _serviceContext.CodePackageActivationContext.GetEndpoints();
if (!endpoints.Contains(_endpointName))
throw new InvalidOperationException($"{_endpointName} not found in Service Manifest.");
var serviceEndpoint = _serviceContext.CodePackageActivationContext.GetEndpoint(_endpointName);
if (string.IsNullOrEmpty(serviceEndpoint.IpAddressOrFqdn))
throw new InvalidOperationException("IpAddressOrFqdn not set on endpoint");
if (serviceEndpoint.Port <= 0)
throw new InvalidOperationException("Port not set on endpoint");
var listenUrl = $"{serviceEndpoint.Protocol.ToString().ToLower()}://{serviceEndpoint.IpAddressOrFqdn}:{serviceEndpoint.Port}";
return listenUrl;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposing || _responseSocket == null) return;
try
{
_responseSocket.Close();
_responseSocket.Dispose();
}
catch (Exception ex)
{
ServiceEventSource.Current.Message(ex.Message);
}
}
}
And return the ZeroMqResponseSocketCommunicationListener in your app fabric service:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
yield return new ServiceInstanceListener(listener => new ZeroMqResponseSocketCommunicationListener(listener, "EndpointName"));
}
Make sure you have an endpoint specified in the ServiceManifest.xml of your service:
<Resources>
<Endpoints>
<Endpoint Name="EndpointName" Port="80" Protocol="tcp" />
</Endpoints>
</Resources>
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.
I'm trying to get windows workflows working, and I've become a little stumped.
I've gotten a single workflow working, but now I am trying to do something a little more complex: start a workflow, where each activity itself contains a workflow. (Picture something like the main program starts the activities "Input, logic, and output", and then each of those have additional activities like "prompt user, get input, etc.")
I've had it working fine, with the example from here (http://msdn.microsoft.com/en-us/magazine/gg535667.aspx), when I am not passing any parameters from the main program to the activites. My question is, how exactly does the 'Variables' and 'metadata.SetVariablesCollection' work in the NativeActivity, and how to I get the parameters to the low level activities?
This is what I am currently trying:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Collections.ObjectModel;
using System.Activities.Statements;
namespace Project1
{
internal class MainProgram
{
internal static void Main(string[] args)
{
try
{
var act = new SimpleSequence();
act.Activities.Add((Activity)(new WriteSomeText()));
act.Activities.Add((Activity)(new WriteSomeText()));
act.Activities.Add((Activity)(new WriteSomeText()));
act.Variables.Add(new Variable<string> ("stringArg", "TEXT"));
WorkflowInvoker.Invoke(act);
}
catch (Exception ex)
{
System.Console.WriteLine("EXCEPTION: {0}", ex);
}
}
public class WriteSomeText : CodeActivity
{
[RequiredArgument]
public InArgument<string> stringArg { get; set; }
protected override void Execute(CodeActivityContext context)
{
string output = context.GetValue(stringArg);
System.Console.WriteLine(output);
}
}
public class SimpleSequence : NativeActivity
{
Collection<Activity> activities;
Collection<Variable> variables;
Variable<int> current = new Variable<int> { Default = 0 };
public Collection<Activity> Activities
{
get
{
if (this.activities == null)
this.activities = new Collection<Activity>();
return this.activities;
}
set
{
this.activities = value;
}
}
public Collection<Variable> Variables
{
get
{
if (this.variables == null)
this.variables = new Collection<Variable>();
return this.variables;
}
set
{
this.variables = value;
}
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
metadata.SetChildrenCollection(this.activities);
metadata.SetVariablesCollection(this.variables);
metadata.AddImplementationVariable(this.current);
}
protected override void Execute(NativeActivityContext context)
{
if (this.Activities.Count > 0)
context.ScheduleActivity(this.Activities[0], onChildComplete);
}
void onChildComplete(NativeActivityContext context, ActivityInstance completed)
{
int currentExecutingActivity = this.current.Get(context);
int next = currentExecutingActivity + 1;
if (next < this.Activities.Count)
{
context.ScheduleActivity(this.Activities[next], this.onChildComplete);
this.current.Set(context, next);
}
}
}
}
}
This ends up throwing the following exception:
EXCEPTION: System.Activities.InvalidWorkflowException: The following errors were encountered while processing the workflow tree:
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
at System.Activities.Validation.ActivityValidationServices.ThrowIfViolationsExist(IList`1 validationErrors)
at System.Activities.Hosting.WorkflowInstance.ValidateWorkflow(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.Hosting.WorkflowInstance.RegisterExtensionManager(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.WorkflowApplication.EnsureInitialized()
at System.Activities.WorkflowApplication.RunInstance(WorkflowApplication instance)
at System.Activities.WorkflowApplication.Invoke(Activity activity, IDictionary`2 inputs, WorkflowInstanceExtensionManager extensions, TimeSpan timeout)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow)
at Project1.MainProgram.Main(String[] args) in c:\users\user\documents\visual studio 2010\Projects\ModelingProject1\Project1\MainProgram.cs:line 25
I know, I only pass 1 parameter, but the exception still says that I am missing 3 parameters. I am missing something as to how to do this properly.
You're correctly declaring stringArg as an InArgument but you're not passing any value to it when calling it inside SimpleSequence.
You can pass something using the constructor, while constructing the all activity itself, like this:
public class WriteSomeText : CodeActivity
{
[RequiredArgument]
public InArgument<string> stringArg { get; set; }
public WriteSomeText(string stringArg)
{
this.stringArg = stringArg;
}
protected override void Execute(CodeActivityContext context
{
string output = context.GetValue(stringArg);
System.Console.WriteLine(output);
}
}
// Calling the activity like this:
internal static void Main(string[] args)
{
var act = new SimpleSequence()
{
Activities =
{
new WriteSomeText("hello"),
new WriteSomeText("world"),
new WriteSomeText("!")
}
};
WorkflowInvoker.Invoke(act);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
Also notice that is a best practice to use the constructor to initialize collections:
public SimpleSequence()
{
activities = new Collection<Activity>();
variables = new Collection<Variable>();
}
This way is even more intuitive to initialize the activity:
var act = new SimpleSequence()
{
Activities =
{
new WriteSomeText("hello"),
new WriteSomeText("world"),
new WriteSomeText("!")
},
Variables =
{
new Variable<int>("myNewIntVar", 10),
// ....
}
};
EDIT:
There are a couple of other ways to approach the problem. This is your best friend while starting in the WF4 world.
Check WF\Basic\CustomActivities\Code-Bodied for a little push with this particular case.
I have a WCF REST service that takes some parameters and sends an email. The template for the email is an MVC3 action. Essentially I want to render that action to a string.
If it were an ASP.NET WebForm, I could simply use Server.Execute(path, stringWriter, false). However when I plug in the path to my action, I get Error executing child request.
I have full access to HttpContext from my service (AspNetCompatibilityRequirementsMode.Allowed).
I know there are other answers out there for rendering actions to strings from within the context of a controller. How do I do this when I'm outside that world, but still on the same server (and, for that matter, in the same app)?
I cobbled together an answer based on several different google searches. It works, but I'm not 100% sure it's as lean as it could be. I'll paste the code for others to try.
string GetEmailText(TemplateParameters parameters) {
// Get the HttpContext
HttpContextBase httpContextBase =
new HttpContextWrapper(HttpContext.Current);
// Build the route data
var routeData = new RouteData();
routeData.Values.Add("controller", "EmailTemplate");
routeData.Values.Add("action", "Create");
// Create the controller context
var controllerContext = new ControllerContext(
new RequestContext(httpContextBase, routeData),
new EmailTemplateController());
var body = ((EmailTemplateController)controllerContext.Controller)
.Create(parameters).Capture(controllerContext);
return body;
}
// Using code from here:
// http://blog.approache.com/2010/11/render-any-aspnet-mvc-actionresult-to.html
public class ResponseCapture : IDisposable
{
private readonly HttpResponseBase response;
private readonly TextWriter originalWriter;
private StringWriter localWriter;
public ResponseCapture(HttpResponseBase response)
{
this.response = response;
originalWriter = response.Output;
localWriter = new StringWriter();
response.Output = localWriter;
}
public override string ToString()
{
localWriter.Flush();
return localWriter.ToString();
}
public void Dispose()
{
if (localWriter != null)
{
localWriter.Dispose();
localWriter = null;
response.Output = originalWriter;
}
}
}
public static class ActionResultExtensions
{
public static string Capture(this ActionResult result, ControllerContext controllerContext)
{
using (var it = new ResponseCapture(controllerContext.RequestContext.HttpContext.Response))
{
result.ExecuteResult(controllerContext);
return it.ToString();
}
}
}