Providing workflow extensions to a workflow service - WF 4.0 - wcf

Greetings one and all!
I'm new to WF 4.0 and WWF in general so forgive me if this seems like a newbie type of question, but believe me I've scoured the depths of the Internet for a solution to this problem, but to no avail.
I have created a sample WF application with a custom CodeActivity that requires an extension be provided, as per below:
public sealed class PreparePizza : CodeActivity
{
public InArgument<Order> Order { get; set; }
protected override void CacheMetadata(CodeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
if (this.Order == null)
metadata.AddValidationError("You must supply an Order.");
metadata.RequireExtension<IPreparePizzaExtension>();
}
// If your activity returns a value, derive from CodeActivity<TResult>
// and return the value from the Execute method.
protected override void Execute(CodeActivityContext context)
{
// Obtain the runtime value of the Text input argument
Order order = context.GetValue(this.Order);
var extension = context.GetExtension<IPreparePizzaExtension>();
extension.Prepare(order);
}
}
public interface IPreparePizzaExtension
{
void Prepare(Order order);
}
I then slot this activity into a workflow service and attempt to consume via my web app by adding a service reference. However, when I add the reference I get:
System.Activities.ValidationException: An extension of type 'PizzaMan.ActivityLibrary.IPreparePizzaExtension' must be configured in order to run this workflow.
Fair enough - of course my activity requires that I pass it an implementation of IPreparePizzaExtension - after all, I've told it to!
So my question is, how on earth do I pass this to the service? I can manage this easily enough in a console app scenario, using the WorkflowInvoker, but I cannot see any obvious way to do this via the service approach. I would assume that obviously a programmatic approach to adding the reference is what's needed, but again I'm at a loss as to precisely how to go about this.
Any help would be greatly appreciated.
Best regards
Ian

The WorkflowServiceHost has a WorkflowExtensions property where you can add the workflow extenstion. There are several ways you can do that. If you are self hosting this is easy as you create the WorkflowServiceHost. If you are usign IIS you need to create a ServiceHostFactory to configure you WorkflowServiceHost. Finally there is an option to add the workflow extension in the CacheMetadata of your activity using the metadata.AddDefaultExtensionProvider() function.

Solved it as follows, self-hosting style:
static void Main(string[] args)
{
Workflow1 workflow = new Workflow1();
// Provide some default values; note: these will be overriden once method on the service is called.
workflow.productID = -1;
Uri address = new Uri("http://localhost:1234/WorkflowService1");
WorkflowServiceHost host = new WorkflowServiceHost(workflow, address);
// Behaviours
host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
host.Description.Behaviors.Remove(typeof(ServiceDebugBehavior));
host.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });
// Persistence
var connStr = #"";
var behavior = new SqlWorkflowInstanceStoreBehavior(connStr);
behavior.InstanceCompletionAction = InstanceCompletionAction.DeleteNothing;
behavior.InstanceLockedExceptionAction = InstanceLockedExceptionAction.AggressiveRetry;
behavior.InstanceEncodingOption = InstanceEncodingOption.None;
host.Description.Behaviors.Add(behavior);
// Add extension implementations
if (!TEST_MODE)
{
host.WorkflowExtensions.Add(new MyExtension());
}
else
{
host.WorkflowExtensions.Add(new MyExtensionTest());
}
host.Faulted += new EventHandler(host_Faulted);
host.Open();
foreach (System.ServiceModel.Description.ServiceEndpoint endpoint in host.Description.Endpoints)
{
Console.WriteLine(endpoint.Address);
}
Console.WriteLine("Listening...");
Console.ReadLine();
host.Close();
}

My toolkit has configuration support for this. See http://neovolve.codeplex.com/wikipage?title=Neovolve.Toolkit.Workflow.dll%20-%201.1

There is also this method of doing things:
http://wf.codeplex.com/wikipage?title=How%20do%20I%20add%20an%20extension%20to%20a%20WCF%20Workflow%20Service?

Related

Dependency Injection Access While Configuring Service Registrations in asp.net Core (3+)

I have cases, where I want to configure services based on objects which are registered in the dependency injection container.
For example I have the following registration for WS Federation:
authenticationBuilder.AddWsFederation((options) =>{
options.MetadataAddress = "...";
options.Wtrealm = "...";
options.[...]=...
});
My goal in the above case is to use a configuration object, which is available via the DI container to configure the WsFederation-middleware.
It looks to me that IPostConfigureOptions<> is the way to go, but until now, I have not found a way to accomplish this.
How can this be done, or is it not possible?
See https://andrewlock.net/simplifying-dependency-injection-for-iconfigureoptions-with-the-configureoptions-helper/ for the I(Post)ConfigureOptions<T> way, but I find that way too cumbersome.
I generally use this pattern:
// Get my custom config section
var fooSettingsSection = configuration.GetSection("Foo");
// Parse it to my custom section's settings class
var fooSettings = fooSettingsSection.Get<FooSettings>()
?? throw new ArgumentException("Foo not configured");
// Register it for services who ask for an IOptions<FooSettings>
services.Configure<FooSettings>(fooSettings);
// Use the settings instance
services.AddSomeOtherService(options => {
ServiceFoo = fooSettings.ServiceFoo;
})
A little more explicit, but you have all your configuration and DI code in one place.
Of course this bypasses the I(Post)ConfigureOptions<T> entirely, so if there's other code that uses those interfaces to modify the FooSettings afterwards, my code won't notice it as it's reading directly from the configuration file. Given I control FooSettings and its users, that's no problem for me.
This should be the approach if you do want to use that interface:
First, register your custom config section that you want to pull the settings from:
var fooSettingsSection = configuration.GetSection("Foo");
services.Configure<FooSettings>(fooSettingsSection);
Then, create an options configurer:
public class ConfigureWSFedFromFooSettingsOptions
: IPostConfigureOptions<Microsoft.AspNetCore.Authentication.WsFederation.WsFederationOptions>
{
private readonly FooSettings _fooSettings;
public ConfigureWSFedFromFooSettingsOptions(IOptions<FooSettings> fooSettings)
{
_fooSettings = fooSettings.Value;
}
public void Configure(WsFederationOptions options)
{
options.MetadataAddress = _fooSettings.WsFedMetadataAddress;
options.Wtrealm = _fooSettings.WsFedWtRealm;
}
}
And finally link the stuff together:
services.AddTransient<IPostConfigureOptions<WsFederationOptions>, ConfigureWSFedFromFooSettingsOptions>();
The configurer will get your IOptions<FooSettings> injected, instantiated from the appsettings, and then be used to further configure the WsFederationOptions.

Add extra parameter to every WCF call

We have WCF services (web hosted in IIS) separatd by domains. We added some claim based security by adding ClaimsAuthorizationManager and overriding the CheckAccess method. Basicaly, we check if the ClaimsPrincipal have the permission needed to execute the method.
Inside the service method :
public DemandePaeDto ObtenirInfoDemandePAE(int idPreDemande, int numeroSequencePreDemande)
{
if (!ClaimsPrincipal.Current.CheckAccess(Management, "DoSomething"))
return null;
Inside the ClaimsAuthorizationManager :
public class FacadeClaimsAuthorizationManager : ClaimsAuthorizationManager
{
public override bool CheckAccess(AuthorizationContext context)
{
var user = context.Principal;
var resource = context.Resource.First().Value;
foreach (var action in context.Action)
{
switch (resource)
{
case "Management":
case "Sales":
case "Product":
if (user.HasClaim(OurOwnCustomClaimTypes.Permission, action.Value))
{
return true;
}
break;
//Pour les cas non gérés
default:
return false;
}
}
return false;
}
}
The problem is when we need to test the services functionalities with WCF Storm of other test tool, we don't have the claims because we set the security mode to false. So we would like to add a custom parameter to all calls. That way we'd be able to bypass by returning true in the CheckAccess method. I've think about MessageInpectors, but still am wondering if it's the right approach. I wouldn't like to add the param to the contracts, because I'd have to modify every call in the application.
Do you have a suggestion ?
You will need to use a different ClaimsAuthorizationManager for your tests. Just derive a different version and use that in your testing environment. It could return true on all call for example.

UserNamePasswordValidator and Session Management

I'm using WCF custom Validator with HTTPS (.NET 4.5). Validate on success returns Customer object which I would like to use later. Currently I'm able to do it with Static variables which I like to avoid if possible. I tried to use HttpContext which becomes null in main thread. My understanding Validate runs under different thread. Is there any way I could share session info without involving DB or File share. See related threads here and here.
In Authentication.cs
public class CustomValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
//If User Valid then set Customer object
}
}
In Service.cs
public class Service
{
public string SaveData(string XML)
{
//Need Customer object here. Without it cannot save XML.
//HttpContext null here.
}
}
I can suggest you an alternative approach. Assuming that the WCF service is running in ASP.Net compatibility mode and you are saving the customer object to session storage. Create a class such as AppContext
The code would look something like this
public class AppContext {
public Customer CurrentCustomer {
get {
Customer cachedCustomerDetails = HttpContext.Current.Session[CUSTOMERSESSIONKEY] as Customer;
if (cachedCustomerDetails != null)
{
return cachedCustomerDetails;
}
else
{
lock (lockObject)
{
if (HttpContext.Current.Session[CUSTOMERSESSIONKEY] != null) //Thread double entry safeguard
{
return HttpContext.Current.Session[CUSTOMERSESSIONKEY] as Customer;
}
Customer CustomerDetails = ;//Load customer details based on Logged in user using HttpContext.Current.User.Identity.Name
if (CustomerDetails != null)
{
HttpContext.Current.Session[CUSTOMERSESSIONKEY] = CustomerDetails;
}
return CustomerDetails;
}
}
}
}
The basic idea here is to do lazy loading of data, when both WCF and ASP.Net pipelines have executed and HTTPContext is available.
Hope it helps.
Alright this should have been easier. Since the way UserNamePasswordValidator works, I needed to use custom Authorization to pass UserName/Password to the main thread and get customer info again from the database. This is an additional DB call but acceptable workaround for now. Please download code from Rory Primrose's genius blog entry.

Using an existing WCF service

I am very new at programming WCF services, so I hope that if you answer my question - you will take that into account and explain it to me as if I was a kid (wcf services for dummies :). I have an existing WCF service which I need to connect to. I am supposed to make my own WCF service that will communicate with the existing one and share some request and response objects which are already defined in the existing service. Can anyone tell me how to do that (establish the communication between the two and use the same type of object in the service which I need to make as it is in the existing one), step by step? I have tried to find the answer online but it is all a bit confusing (referencing, using contracts...). As I said, you are free to explain as if you would to a real beginner. Any help is more than welcome...
"I am supposed to make my own WCF service that will communicate with the existing one and share some request and response objects which are already defined in the existing service." - This sounds like you need to create a client to connect to the service (see below how to create client). You can create WCF service to communicate with another service but you would need bit more background than this format allows.
You can get up to speed with WCF through WCF examples. Under WF_WCF_Samples\WCF\Basic in the examples you can find many Service/Client setups that you should go through first. MSDN Magazine has tons of articles on this topic.
In a 10,000 foot view of things:
Client - To consume service create a test console application. Add Service Reference in your project (when you right click references you will see that option). Point the address of the Service Reference dialog to the service you would like to consume and lot of stuff will happen. Final result is that you can call service methods on your service with something like below (where Service1 will be replaced with what ever service you are calling)
static void Main(string[] args)
{
var proxy = new ServiceReference1.Service1Client();
var test = proxy.GetData(1);
}
Service - you would create an interface with methods and types then decorate this interface with attributes for example:
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
// TODO: Add your service operations here
}
These are operations (OperationContract) that your serive can perform. Service methods can return primitive or complex type (string vs. CompositeType) as well as take parameters that are complex or primitive.
You would implement this contract:
public class Service1 : IService1
{
public string GetData(int value)
{
throw new ApplicationException("Boom");
return string.Format("You entered: {0}", value);
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
if (composite == null)
{
throw new ArgumentNullException("composite");
}
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
}
Next you need to host your service. You have many options to accomplish this depending on your hosting requirements. The simplest hosting you can do is using Console application:
class Program
{
static void Main(string[] args)
{
var host = new ServiceHost(typeof(Service1), new Uri("http://localhost:8999/"));
host.AddServiceEndpoint(typeof(IService1), new BasicHttpBinding(), "");
var metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (metadataBehavior == null)
{
metadataBehavior = new ServiceMetadataBehavior();
metadataBehavior.HttpGetEnabled = true;
host.Description.Behaviors.Add(metadataBehavior);
}
host.Open();
Console.WriteLine("Running..");
Console.ReadLine();
}
}

Can we host a Workflow Service as a Windows Service?

I am working on a logging application that requires me to have a Workflow that is exposed as a Service (Workflow Service). We want to host it as a Windows Service (don't want to host workflow service as .svc file in IIS). Another reason for having it as windows service is to be able to communicate with the service through the Named pipes.
Can we expose a Workflow Service through Named Pipes without hosting it in IIS?
Yep bep, you sure can. At least, I have accomplished as much with Workflow 4 Release Candidate.
Consider,
// a generic self-hosted workflow service hosting thingy. Actual
// implementation should contain more logging and thread safety, this
// is an abbreviated version ;)
public class WorkflowHost
{
// NOTE: with Workflow, it helps to maintain a concept of
// Workflow definition [the Activity or WorkflowService from
// a designer] and a Workflow instance [what is running within
// WorkflowInvoker, WorkflowApplication, WorkflowServiceHost].
// a definition may be used to generate an instance. an instance
// contains run-time state and cannot be recycled into a new
// instance. therefore, to repeatedly re-host a WorkflowService
// we need to maintain references to original definitions and
// actual instances. ergo services and hosts maps
//
// if you are special purpose and require support for one and
// only one service and endpoint\uri, then you may reduce this
// to a simple tuple of Uri, WorkflowService, WorkflowServiceHost
// services represents a definition of hosted services
private readonly Dictionary<Uri, WorkflowService> _services =
new Dictionary<Uri, WorkflowService> ();
// hosts represents actual running instances of services
private readonly Dictionary<Uri, WorkflowServiceHost> _hosts =
new Dictionary<Uri, WorkflowServiceHost> ();
// constructor accepts a map of Uris (ie service endpoints) to
// workflow service definitions
public WorkflowHost (IDictionary<Uri, WorkflowService> services)
{
foreach (KeyValuePair<Uri, WorkflowService> servicePair in services)
{
_services.Add (servicePair.Key, servicePair.Value);
}
}
// have your windows service invoke this to start hosting
public void Start ()
{
if (_hosts.Count > 0)
{
Stop ();
}
foreach (KeyValuePair<Uri, WorkflowService> servicePair in _services)
{
WorkflowService service = servicePair.Value;
Uri uri = servicePair.Key;
WorkflowServiceHost host = new WorkflowServiceHost (service, uri);
host.Open ();
_hosts.Add (uri, host);
}
}
// have your windows service invoke this to stop hosting
public void Stop ()
{
if (_hosts.Count > 0)
{
foreach (KeyValuePair<Uri, WorkflowService> servicePair in
_services)
{
WorkflowService service = servicePair.Value;
Uri uri = servicePair.Key;
IDisposable host = _hosts[uri];
host.Dispose ();
}
_hosts.Clear ();
}
}
}
I believe endpoint configuration may be set via standard Wcf service configuration sections in App.config. I have not personally attempted a change to default transport layer in my experiments with Workflow.
The above represents a generic pure hosting class [ie it self-hosts WorkflowServices]. This allows us to re-use this hosting functionality within a console, WinForm, WPF, or yes, even a WindowsService application. Below is a WindowsService that leverages our host class
// windows service. personally i would abstract service behind
// an interface and inject it, but again, for brevity ;)
public partial class WorkflowWindowsService : ServiceBase
{
WorkflowHost _host;
public WorkflowWindowsService ()
{
InitializeComponent();
Dictionary<Uri, WorkflowService> services =
new Dictionary<Uri, WorkflowService> ();
// do your service loading ...
// create host
_host = new WorkflowHost (services);
}
protected override void OnStart(string[] args)
{
_host.Start ();
}
protected override void OnStop()
{
_host.Stop ();
}
}
If you have fiddled with WorkflowServices in VS2010RC, then you may already know that WorkflowServices are not first class Xaml classes like their Workflow cousins. Instead, they are saved as loose Xaml files with the .xamlx extension. There is no design-time intellisense support for WorkflowServices [as far as I know] and are not recognized as declared types, so our only options to load a WorkflowService at run-time are
Read pure Xaml markup from .xamlx file directly
Read pure Xaml markup from some other source [embedded string, resource, or other source]
Either way, we must interpret markup and create a WorkflowService definition. The following will transform a string [that may be a filename or markup] into a WorkflowService. Keeners may also note that there is a difference between this process and the process for transforming Workflow markup to Workflow definitions.
// converts a string value [either pure xaml or filename] to a
// WorkflowService definition
public WorkflowService ToWorkflowService (string value)
{
WorkflowService service = null;
// 1. assume value is Xaml
string xaml = value;
// 2. if value is file path,
if (File.Exists (value))
{
// 2a. read contents to xaml
xaml = File.ReadAllText (value);
}
// 3. build service
using (StringReader xamlReader = new StringReader (xaml))
{
object untypedService = null;
// NOTE: XamlServices, NOT ActivityXamlServices
untypedService = XamlServices.Load (xamlReader);
if (untypedService is WorkflowService)
{
service = (WorkflowService)(untypedService);
}
else
{
throw new ArgumentException (
string.Format (
"Unexpected error reading WorkflowService from " +
"value [{0}] and Xaml [{1}]. Xaml does not define a " +
"WorkflowService, but an instance of [{2}].",
value,
xaml,
untypedService.GetType ()));
}
}
return service;
}
Yes it is possible. You will have to create your own service. See Hosting and Consuming WCF Services on MSDN, especially the section Hosting in Windows Services.