Archive for the ‘extension methods’ tag
Web Client Extensibility – Part 3 Extending the Service Request Behaviour (Server)
In part 3 of my series of posts discussing web extensibility I plan to give you a step-by-step guide on how as a developer you can extend the behaviour of Sage 200 services. As mentioned in my initial post, although the posts are primarily aimed at Sage 200 Business Channel Developers (or 3rd parties), the ‘downloads’ are not specific to any current or future Sage 200 library (assembly) or service, it is the concepts and patterns that are important.
This post is not intended to be used as a reference guide for WCF, various links can be found here.
If you haven’t already can I recommend you read the previous post article. In that post I included a reference diagram that described the ‘hook’ points that are surfaced to enable developers to extend the operation behaviour.
This post will concentrate on ‘hook’ points 2,3,5 and 6. Note I haven’t included hook 4 – this hook point requires knowledge of the Sage 200 business objects (not part of this scope). Hook points 1 and 7 will be discussed in my next post.
Code
The Expense Scenario – Part 2
The functionality of the out-of-the-box application itself hasn’t changed since my previous post. As before all the application will do is allow a user to login and then either view, create or delete an expense.
However, through an extension discovered at runtime (not compile time), the application now includes some extra validation. What is special about this validation is that it has not been written by a Sage 200 (employee) developer but rather a Sage 200 Business Partner to add additional behaviour not part of the core product.
The solution can be downloaded here
Try out the application now…you should find that if you create an expense for a value greater than £600 or try to remove an expense that has an Id which is an odd number, a new message will appear in the console and the request is aborted.
Have a look in the Expenses.Logic.Library, there is no code that implements this logic. Now look in the ‘Extensions’ Solution Folder in the project Expenses.Services.Server.Extensions. There are 2 classes CreateExtension and DeleteExtension…the logic you are looking for can be found in the method ValidateBeforeCall.
However, if you look at the projects included in Service Items solution folder you will notice that none of the projects reference the project Expenses.Services.Server.Extensions. As said previously the extension assembly is discovered at runtime and not compile time. This is achieved using the Microsoft Managed Extensibility Framework (also known as MEF) a new component included in .Net 4 framework (the assembly is called System.ComponentModel.Composition). I do not intend to cover the detail of this component but if you are new to MEF then I suggest you look here for a description and overview its capabilities, but in summary MEF:
- simplifies the creation of extensible applications,
- offers discovery and composition capabilities that you can leverage to load application extensions.
From a 3rd party point of view, adding new extensions is quite straightforward. If we look closely at the type DeleteExtension.cs (in the project Expenses.Service.Server.Extensions) the only task for the 3rd party developer is to realise the interface IOperationBehaviorExtender (the proposal is that the interface will be shipped as part of a Sage 200 Web SDK).
-
/// <summary>
-
/// An Extender to extend the behavior of deleting an Expense Record
-
/// </summary>
-
public class DeleteExtension : IOperationBehaviorExtender
-
{
-
#region IOperationBehaviorExtender Members
-
-
/// <summary>
-
/// The name of the action being extended
-
/// </summary>
-
public string Action
-
{
-
get { return "Expense/Delete"; }
-
}
-
-
/// <summary>
-
/// Executed before the ‘core’ implemenation of the method is executed.
-
/// </summary>
-
/// <param name="action"></param>
-
/// <param name="inputs"></param>
-
/// <param name="faultMessage"></param>
-
/// <returns>returning False will cancel the execution of the Invoke method, otherwise return true</returns>
-
public bool BeforeInvoke(string action, object[] inputs, out string faultMessage)
-
{
-
faultMessage = string.Empty;
-
-
return true;
-
}
-
-
/// <summary>
-
/// Executed after the ‘core’ implementation of the method has been executed
-
/// </summary>
-
/// <param name="action"></param>
-
/// <param name="inputs"></param>
-
/// <param name="outputs"></param>
-
/// <param name="returnValue"></param>
-
/// <remarks>
-
/// If a fault occurs within this method ensure an Exception is thrown to rollback (if possible) the Invoke and BeforeInvoke implementations
-
/// </remarks>
-
public void AfterInvoke(string action, object[] inputs, ref object[] outputs, ref object returnValue)
-
{
-
-
}
-
-
/// <summary>
-
/// Very simple validation…don’t let an expense be deleted if the id is an odd number
-
/// </summary>
-
/// <param name="action"></param>
-
/// <param name="inputs"></param>
-
/// <param name="message"></param>
-
/// <returns>bool (false will cancel the method being invoked)</returns>
-
/// <remarks>
-
/// If a fault occurs within this method ensure an Exception is thrown to rollback (if possible) the Invoke and BeforeInvoke implementations
-
/// </remarks>
-
public bool ValidateBeforeCall(string action, object[] inputs, out string message)
-
{
-
bool result = true;
-
message = string.Empty;
-
-
// cast the input to the correct type of object
-
if (inputs[0] is int)
-
{
-
int? id = (int?)inputs[0];
-
-
if ((id % 2) > 0)
-
{
-
result = false;
-
message = "This Expense is still being processed and cannot be deleted";
-
}
-
}
-
-
return result;
-
}
-
-
/// <summary>
-
/// Allows extra processing to occur after the method has been fully processed
-
/// </summary>
-
/// <param name="action"></param>
-
/// <param name="outputs"></param>
-
/// <param name="returnValue"></param>
-
/// <param name="correlationState"></param>
-
public void AfterCall(string action, object[] outputs, object returnValue, object correlationState)
-
{
-
-
}
-
-
#endregion
-
}
In the code example above the 3rd party developer has decided to only extend the behaviour of the ValidateBeforeCall method. This is where they have injected the logic to abort the execution of the DeleteExpense logic if the id of the Expense is odd (I know not real-world but hopefully it gets the point across…I promise that in later editions of the series I’ll do something more realistic). Note, the 3rd party developer has not had to worry about how the discovery process happens, for example they have not had to include any reference to the MEF assembly System.ComponentModel.Compositon. All they need to ensure is they have fully realised the interface IOperationBehaviorExtender and they have included the correct Action value (the proposal is that all actions that are extendable will be documented as part of a Sage 200 Web SDK)
Next, what has the Sage 200 Developer had to do so that their method is marked as an extensible operation? Well I’ve taken the decision that developers must opt in if they require their operation to be extensible, rather than all operations being extensible by default. To do so the Sage 200 Developer must decorate the concrete implementation of their method with a custom attribute.
-
/// <summary>
-
/// Deletes an expense based on its primary key
-
/// </summary>
-
/// <param name="id"></param>
-
[OperationBehaviorExtension]
-
public void DeleteExpense(int id)
-
{
-
_coordinator.DeleteExpense(id);
-
}
They must also ensure that they include a unique action name for the interface definition of the service operation. It needs to be unique as this will be the Action value the 3rd party will set within their extension implementation (see snippet above).
-
[OperationContract(Action = "Expense/Delete")]
-
[FaultContract(typeof(ContractFault))]
-
void DeleteExpense(int id);
Finally the discovery process…the code for discovering 3rd party extensions can be found in the type ExtensionsFactory in the project Services.Extensibility.Server. Please consider this code as a first draft of what the final (release) code may look like, for example the final location for all 3rd party extensions will be predetermined most probably by the Sage 200 Add-on Manager not the bin folder of the hosting WCF application. But for now consider this code as an introduction to the MEF discovery process. One thing worth noting is how implementations of IOperationBehaviorExtender become discoverable…as I noted above, the 3rd party developer has not had to include any references to MEF in their implementation. The part is discoverable because I have included the attribute [InheritedExport] on the declaration of the type IOperationBehaviorExtender.
-
[InheritedExport]
-
public interface IOperationBehaviorExtender
-
{
-
string Action { get; }
-
bool BeforeInvoke(string action, object[] inputs, out string faultMessage);
-
void AfterInvoke(string action, object[] inputs, ref object[] outputs, ref object returnValue);
-
bool ValidateBeforeCall(string action, object[] inputs, out string message);
-
void AfterCall(string action, object[] outputs, object returnValue, object correlationState);
-
}
James Eggers has a good blog post outlining the benefits of using this approach. Later on we’ll see that this approach is not always appropriate when there becomes a need to include / exclude extensions on a case by case basis.
The Next Post
In my next post I intend to build on this solution by including additional service behaviours within the Client (consumer) of the service. It will show the changes that are required to the Service Contract and though the use of Microsoft’s Managed Extensibility Framework (MEF) show how 3rd party extensions can be included in the WCF Service Pipeline.
Feedback and comments are very welcome including any suggestions for future content.
(My disclaimer) Please be aware, this series of blog posts are being written at the same time as the next release of Sage 200 2011. Although many of the concepts have been qualified and included within the 2011 development project plan there is no guarantee they will make the final release.
Lambda Expressions and Extension Methods
Please note: This is a long and very technical discussion of some of the .NET language features we use in Workspaces. It is not Sage 200 specific.
Please let us know what you think of the technical level of the article – we’d love to get feedback on how we’re doing that helps us improve the value of this blog!
Lambda Expressions and Extension Methods are powerful new features in .NET 3.0 and are often used extensively when writing LINQ. In fact, these features are little more than syntactic sugar; they don’t enable functionality that wasn’t previously possible, but they do allow code that would previously have been quite complex to be written in a succinct, clear and elegant way.
While they are quite straightforward to use, they are not particularly easy to explain or intuitive to understand. This document aims to explain how these language features work.
Error checking has been excluded from all examples for brevity and clarity – the code is not intended to be production quality. Some of the examples are also quite weak; they are intended to demonstrate use of the language, not necessarily good examples of when to use the features!
Many of the examples make use of IEnumerable collections. This was to allow complete examples to be given, including the data types. When using LINQ to SQL the collection types are based on the IQueryable interface, but this makes little difference to the code using the collections.
Simple Extension Methods
At their simplest, Extension Methods allow the definition of a class to be extended with additional methods; to add new methods to the class that appear identical to member functions.
For example, given a very simple “bank account” class:
class Account
{
public Account(string name, decimal balance)
{
this.name = name;
this.balance = balance;
}
public string name;
public decimal balance;
}
We may wish to create a function that allows funds to be deposited in the account.
This could be added as a member function, or as a public function taking the account as a parameter:
public void Deposit(Account account, decimal deposit)
{
account.balance += deposit;
}
// ....
// To deposit £10~~
Deposit(account, 10);
Alternatively, we could write this as an Extension Method.
static class Extensions
{
public static void Deposit(this Account account, decimal deposit)
{
account.balance += deposit;
}
}
// ....
// To deposit £10
myAccount.Deposit(10);
The defining characteristics of the extension method are:
• It’s a static function in a static class
• Use of the “this” keyword in the parameter list
Note that the method is not a “true” member function; private members of the class cannot be accessed, for example.
Extending Collections
Extension methods are not limited to concrete types; they can be used to extend anything that the compiler recognises as a strong type such as a collection of objects:
public List accounts = … ;
public static bool Deposit(this IEnumerable accounts, string accountName, decimal deposit)
{
foreach ( Account account in accounts )
{
if (account.name == accountName)
{
account.balance += deposit;
return true;
}
}
return false;
}
// ....
// To deposit £10 in Steve’s account
accounts.Deposit(“Steve”, 10);
Alternatively, we might want a method that finds all of the overdrawn accounts:
public static IEnumerable GetOverdrawnAccounts(this IEnumerable accounts)
{
foreach (Account account in accounts)
{
if (account.balance < 0)
yield return account;
}
}
Note the use of the “yield” keyword, which essentially says “add the item to the return set and continue”
Extension Methods and Delegates
A very common pattern with Extension Methods is to have a function delegate as one of the parameters, allowing function logic to be determined by the caller.
For example, we could rewrite our “GetOverdrawnAccounts” example as:
public static IEnumerable GetAccounts(this IEnumerable accounts, Func predicate)
{
foreach (Account account in accounts)
{
if (predicate(account))
yield return account;
}
}
static public bool IsOverdrawn(Account a)
{
return a.balance < 0;
}
IEnumerable overdrawnAccounts = accounts.GetAccounts(IsOverdrawn);
Note the use of the “Func” delegate overload, specifying a delegate parameter which takes an Account as its parameter and returns a bool.
The advantage of this approach is that we can now supply an alternative delegate without changing the code of the extension method:
static public bool IsStevesAccount(Account a)
{
return a.name == "Steve";
}
… = accounts.GetAccounts(IsStevesAccount);
This can be written (slightly) more succinctly using an anonymous delegate:
… = accounts.GetAccounts(delegate(Account a)
{
return a.name == "Steve";
});
This approach has an additional advantage in that the anonymous delegate can access variables from the calling scope.
In the examples above, we have determined the name of the account to be found at design time – if, however, the account name we want is not known until run-time then the named delegate approach would have a problem. As the prototype of the predicate is controlled by the extension method, we have no way to pass in a variable containing the name we want to find: we would need to create an alternative extension method, or use a variable at a higher scope (e.g. a global variable).
Using an anonymous delegate we can keep everything else the same, and:
string requiredAccount = "Steve";
… = accounts.GetAccounts(delegate(Account a)
{
return a.name == requiredAccount;
});
Similarly, we can now allow for an arbitrary overdraft limit in our list of the overdrawn accounts:
int overdraftLimit = 25;
… = accounts.GetAccounts(delegate(Account a)
{
return a.balance + overdraftLimit < 0;
});
Standard Extension Methods for Collections
In the examples given so far, we have written all of our own Extension Methods. This has been largely for expository purposes; a wide variety of extension methods are provided as part of the .NET framework which implement many of the standard operations you might wish to perform on a collection of objects (such as an IEnumerable or an IQueryable).
Here we give some simple examples of the standard extension methods, including versions of some of the earlier examples:
string requiredAccount = "Steve";
… = accounts.Where(delegate(Account a)
{
return a.name == requiredAccount;
});
int overdraftLimit = 25;
… = accounts.Where(delegate(Account a)
{
return a.balance + overdraftLimit < 0;
});
decimal totalBalance = accounts.Sum(delegate(Account a)
{
return a.balance;
});
decimal averageBalance = accounts.Average(delegate(Account a)
{
return a.balance;
});
For lists of the standard methods, see the MSDN articles IEnumberable
Lambda Expressions
Lambda expressions are nothing more than an alternative syntax for defining an anonymous function delegate.
The expression follows the pattern:
Parameter list => Expression
Therefore :
delegate(Account a)
{
return a.name == requiredAccount;
}
Can be re-written as:
(Account a) => a.name == requiredAccount
As the compiler can infer the return type of the expression, it is not actually necessary to supply the type of the parameter, so this can be furthered simplified to:
a => a.name == requiredAccount
So we can now re-write our previous examples as:
string requiredAccount = "Steve";
… = accounts.Where(a => a.name == requiredAccount);
int overdraftLimit = 25;
… = accounts.Where(a => a.balance + overdraftLimit < 0);
decimal totalBalance = accounts.Sum(a => a.balance);
decimal averageBalance= accounts.Average(a => a.balance);
For more detail on other possibilities for Lambda expressions, including support for multiple parameters and multi-statement expressions, see the MSDN “Lambda Expressions in LINQ
One of the places we make most use of lambda expressions and the standard extension methods is in the LINQ for the Sage 200 Workspaces.
So an example of a simple query which returns a stock item code and the total confirmed quantity in stock across all warehouses could be written as:
var q = from si in StockItems
let wiSet = WarehouseItems.Where(wi => si.ItemID == wi.ItemID)
select new
{
si.Code,
ConfirmedQtyInStock = (decimal?)wiSet.Sum(wi => wi.ConfirmedQtyInStock)
};
Replacing the lambda expressions with anonymous delegate syntax:
var q = from si in StockItems
let wiSet = WarehouseItems.Where(delegate(WarehouseItem wi)
{
return (si.ItemID == wi.ItemID);
})
select new
{
si.Code,
ConfirmedQtyInStock = (decimal?)wiSet.Sum(delegate(WarehouseItem wi)
{
return wi.ConfirmedQtyInStock;
})
};
return q;
And without using extension methods (assuming very simplistic implementation of the query functions):
var q = from si in StockItems
select new
{
si.Code,
ConfirmedQtyInStock = GetTotalConfirmedQtyInStock(
GetWarehouseItemsForStockItem(WarehouseItems, si))
};
return q;
// Where...
public static IEnumerable
GetWarehouseItemsForStockItem(IEnumerable WarehouseItems, StockItem si)
{
foreach (WarehouseItem wi in WarehouseItems)
{
if (si.ItemID == wi.ItemID)
yield return wi;
}
}
public static decimal?
GetTotalConfirmedQtyInStock(IEnumerable WarehouseItems)
{
if (WarehouseItems.Count() == 0)
return null;
decimal result = 0;
foreach (WarehouseItem wi in WarehouseItems)
{
result += wi.ConfirmedQtyInStock;
}
return result;
}
