Set a variable from an AOP advice - aop

I use an AOP advice to convert a User object into a Owner object. The conversion is done in the advice but I would like to pass that Owner object to the caller.
#Aspect
public class UserAuthAspect {
#Inject
private OwnerDao ownerDao;
#Pointcut("#annotation(com.google.api.server.spi.config.ApiMethod)")
public void annotatedWithApiMethod() {
}
#Pointcut("execution(* *.*(.., com.google.appengine.api.users.User)) && args(.., user)")
public void allMethodsWithUserParameter(User user) {
}
#Before("annotatedWithApiMethod() && allMethodsWithUserParameter(user)")
public void checkUserLoggedIn(com.google.appengine.api.users.User user)
throws UnauthorizedException {
if (user == null) {
throw new UnauthorizedException("Must log in");
}
Owner owner = ownerDao.readByUser(user);
}
}
A class with advised methods:
public class RealEstatePropertyV1 {
#ApiMethod(name = "create", path = "properties", httpMethod = HttpMethod.POST)
public void create(RealEstateProperty property, User user) throws Exception {
Owner owner = the value set by the advice
}
}

I don't know if it's the right way to do it, so feel free to comment. I created an interface Authed and its implementation AuthedImpl.
public interface Authed {
void setOwner(Owner owner);
}
Then I made RealEstatePropertyV1 extends from AuthedImpl. I added a pointcut for all classes extending from AuthedImpl and also changed the pointcut so I can access to the target.
#Pointcut("execution(* *..AuthedImpl+.*(..))")
public void extendsAuthedImpl() {
}
#Pointcut("execution(* *.*(.., com.google.appengine.api.users.User)) && args(.., user) && target(callee)")
public void allMethodsWithUserParameter(User user, Authed callee) {
}
Finally the advice uses all pointcuts:
#Before("annotatedWithApiMethod() && allMethodsWithUserParameter(user, callee) && extendsAuthedImpl()")
public void checkUserLoggedIn(com.google.appengine.api.users.User user,
Authed callee) throws UnauthorizedException {
System.out.println(callee.getClass());
if (user == null) {
throw new UnauthorizedException("Must log in");
}
Owner owner = ownerDao.readByUser(user);
callee.setOwner(owner);
}

Related

How to create new Principal Object using Jackrabbit/JCR

I'm a new developer trying to tackle the Jackrabbit/JCR library. My team and I have been using the EveryonePrincipal for a couple of months now, but we've been wanting to implement more capabilities per user roles/principals so we can grant them necessary read/write access to each node. However, we're having some difficulties figuring out how to create a new Principal object.
I've been using:
PrincipalImpl newPrincipal = new PrincipalImpl("MyPrincipal");
Then creating a new RolePrincipal class matching the EveryonePrincipal, except the name would be "MyPrincipal" inside the RolePrincipal class. This method doesn't work unfortunately. Is there anything else we're missing from this? And how does the 'everyone' principal gets stored?
EveryonePrincipal.java
public final class EveryonePrincipal implements JackrabbitPrincipal, java.security.acl.Group {
public static final String NAME = "everyone";
private static final EveryonePrincipal INSTANCE = new EveryonePrincipal();
private EveryonePrincipal() { }
public static EveryonePrincipal getInstance() {
return INSTANCE;
}
//----------------------------------------------------------< Principal >---
#Override
public String getName() {
return NAME;
}
//--------------------------------------------------------------< Group >---
#Override
public boolean addMember(Principal user) {
return false;
}
#Override
public boolean removeMember(Principal user) {
throw new UnsupportedOperationException("Cannot remove a member from the everyone group.");
}
#Override
public boolean isMember(Principal member) {
return !member.equals(this);
}
#Override
public Enumeration<? extends Principal> members() {
throw new UnsupportedOperationException("Not implemented.");
}
//-------------------------------------------------------------< Object >---
#Override
public int hashCode() {
return NAME.hashCode();
}
#Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof JackrabbitPrincipal && obj instanceof Group) {
JackrabbitPrincipal other = (JackrabbitPrincipal) obj;
return NAME.equals(other.getName());
}
return false;
}
#Override
public String toString() {
return NAME + " principal";
}
}

How does the following test pass?

I have a simple test for the following controller action method
Admin controller
namespace TestingDemo {
public class AdminController : Controller {
private IUserRepository repository;
public AdminController(IUserRepository repo) {
repository = repo;
}
public ActionResult ChangeLoginName(string oldName, string newName) {
User user = repository.FetchByLoginName(oldName);
user.LoginName = newName;
repository.SubmitChanges();
// render some view to show the result
return View();
}
}
}
The test I have is this
namespace TestingDemo.Tests {
[TestClass]
public class AdminControllerTests {
[TestMethod]
public void CanChangeLoginName() {
// Arrange (set up a scenario)
User user = new User() { LoginName = "Bob" };
FakeRepository repositoryParam = new FakeRepository();
repositoryParam.Add(user);
AdminController target = new AdminController(repositoryParam);
string oldLoginParam = user.LoginName;
string newLoginParam = "Joe";
// Act (attempt the operation)
target.ChangeLoginName(oldLoginParam, newLoginParam);
// Assert (verify the result)
Assert.AreEqual(newLoginParam, user.LoginName);
Assert.IsTrue(repositoryParam.DidSubmitChanges);
}
}
class FakeRepository : IUserRepository {
public List<User> Users = new List<User>();
public bool DidSubmitChanges = false;
public void Add(User user) {
Users.Add(user);
}
public User FetchByLoginName(string loginName) {
return Users.First(m => m.LoginName == loginName);
}
public void SubmitChanges() {
DidSubmitChanges = true;
}
}
}
my question is how does user.LoginName get changed? the only change that happens to user is in the action method, and that is a local user variable. How is the user variable in the test getting updated?
The controller test first adds a user to List<User> Users in the fake repository, using the Users.Add method.
The repository this is added to is passed into the constructor of the controller in the test:
AdminController target = new AdminController(repositoryParam);

Using NHibernate interceptor together with Ninject to retrieve the logged in user

I was reading this article and found it quite interesting (thanks #Aaronaught). Was what came closest to solve my problem.
The only detail is that in my case I would use the NHibernate interceptor, but an exception is thrown An unhandled exception of type 'System.StackOverflowException' occurred in System.Core.dll
Code
Session factory:
public class SessionFactoryBuilder : IProvider
{
private ISessionFactory _sessionFactory;
private readonly Configuration _configuration;
public SessionFactoryBuilder(AuditInterceptor auditInterceptor)
{
_configuration = Fluently.Configure(new Configuration().Configure())
.Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<IEntidade>(new AutomappingConfiguration())))
.ExposeConfiguration(SetupDatabase)
.BuildConfiguration();
_configuration.SetInterceptor(auditInterceptor);
_sessionFactory = _configuration.BuildSessionFactory();
}
private static void SetupDatabase(Configuration config)
{
var schema = new SchemaExport(config);
//schema.Execute(true, true, false);
}
public object Create(IContext context)
{
return _sessionFactory;
}
public Type Type
{
get { return typeof(ISessionFactory); }
}
}
I have a module that sets up my repositories and ORM (NHibernate)
public class RepositoriosModule : NinjectModule
{
public override void Load()
{
Bind<AuditInterceptor>().ToSelf().InRequestScope();
// NHibernate
Bind<ISessionFactory>().ToProvider<SessionFactoryBuilder>().InSingletonScope();
Bind<ISession>().ToMethod(CreateSession).InRequestScope();
Bind<NHUnitOfWork>().ToSelf().InRequestScope();
//Model Repositories
Bind<IRepositorio<Usuario>, IUsuariosRepositorio>().To<UsuariosRepositorio>().InRequestScope();
}
private ISession CreateSession(IContext context)
{
return context.Kernel.Get<ISessionFactory>().OpenSession();
}
}
Interceptor to update auditable properties (CriadoEm (create at), CriadoPor (create by), AtualizadoEm and AtualizadoPor)
public class AuditInterceptor : EmptyInterceptor
{
private readonly IUsuario _usuarioLogado;
public AuditInterceptor(IUsuario usuarioLogado)
{
_usuarioLogado = usuarioLogado;
}
public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, NHibernate.Type.IType[] types)
{
var auditableObject = entity as IAuditavel;
if (auditableObject != null)
{
currentState[Array.IndexOf(propertyNames, "AtualizadoEm")] = DateTime.Now;
return true;
}
return false;
}
public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
{
var auditableObject = entity as IAuditavel;
if (auditableObject != null)
{
var currentDate = DateTime.Now;
state[Array.IndexOf(propertyNames, "CriadoEm")] = currentDate;
return true;
}
return false;
}
}
A provider to retrieve the logged in user:
public class UsuarioProvider : Provider
{
private Usuario _usuario;
protected override Usuario CreateInstance(IContext context)
{
var usuariosRepositorio = context.Kernel.Get<IUsuariosRepositorio>(); // Stackoverflow on this line!!
if (_usuario == null && WebSecurity.IsAuthenticated)
_usuario = usuariosRepositorio.Get(WebSecurity.CurrentUserId);
return _usuario;
}
}
And the class NinjectWebCommon (web application) define:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IUsuario>().ToProvider<UsuarioProvider>().InRequestScope(); //.When((req) => WebSecurity.IsAuthenticated)
kernel.Load(new RepositoriosModule(), new MvcSiteMapProviderModule());
}
[Add] Repository class
public class UsuariosRepositorio : Repositorio<Usuario>, IUsuariosRepositorio
{
public UsuariosRepositorio(NHUnitOfWork unitOfWork)
: base(unitOfWork)
{ }
}
public class Repositorio<T> : IRepositorio<T>
where T : class, IEntidade
{
private readonly NHUnitOfWork _unitOfWork;
public IUnitOfWork UnitOfWork { get { return _unitOfWork; } }
private readonly ISession _session;
public Repositorio(IUnitOfWork unitOfWork)
{
_unitOfWork = (NHUnitOfWork)unitOfWork;
_session = _unitOfWork.Context.SessionFactory.GetCurrentSession();
}
public void Remover(T obj)
{
_session.Delete(obj);
}
public void Armazenar(T obj)
{
_session.SaveOrUpdate(obj);
}
public IQueryable<T> All()
{
return _session.Query<T>();
}
public object Get(Type entity, int id)
{
return _session.Get(entity, id);
}
public T Get(Expression<Func<T, bool>> expression)
{
return Query(expression).SingleOrDefault();
}
public T Get(int id)
{
return _session.Get<T>(id);
}
public IQueryable<T> Query(Expression<Func<T, bool>> expression)
{
return All().Where(expression);
}
}
Problem
The problem occurs in the class UsuarioProvider while trying to retrieve the user repository.
Stackoverflow error:
An unhandled exception of type 'System.StackOverflowException' occurred in System.Core.dll
I see two problems :
The main problem I see is that SessionFactoryBuilder needs an AuditInterceptor which needs an IUsuario, which needs a UsuarioProvider, which needs a SessionFactoryBuilder, thus introducing a cycle, and a stack-overflow.
The second problem I see is that your AuditInterceptor is linked to a request when your SessionFactoryBuilder is singleton like. I must confess I can't see how it work with several logged users.
You should instantiate and attach the AuditInterceptor as part of the CreateSession, instead of trying to create it once and for all as part of the Session builder. Once this is done, your interceptor should not rely on a Session that needs an AuditInterceptor as part of its creation (you may need a separate Session creation mechanism for that. A stateless Session might do the trick)

Custom RoleProvider failing when AuthorizeAttribute applied with role

I'm having an issue with a custom role provider in ASP.net MVC4. I implemented a very light weight RoleProvider which seems to work fine right up until I change
[Authorize]
public class BlahController:....
}
to
[Authorize(Roles="Administrator")]
public class BlahController:....
}
as soon as I make that change users are no longer authenticated and I get 401 errors. This is odd because my RoleProvider basically returns true for IsUSerInRole and a list containing "Administrator" for GetUserRoles. I had breakpoints in place on every method in my custom RoleProvider and found that none of them were being called.
Next I implemented my own authorize attribute which inherited from AuthorizeAttribute. In this I put in break points so I could see what was going on. It turned out that User.IsInRole(), which is called by the underlying attribute was returning false.
I am confident that the role provider is properly set up. I have this in my config file
<roleManager enabled="true" defaultProvider="SimplicityRoleProvider">
<providers>
<clear />
<add name="SimplicityRoleProvider" type="Simplicity.Authentication.SimplicityRoleProvider" applicationName="Simplicity" />
</providers>
</roleManager>
and checking which role provider is the current one using the method described here: Reference current RoleProvider instance? yields the correct result. However User.IsInRole persists in returning false.
I am using Azure Access Control Services but I don't see how that would be incompatible with a custom role provider.
What can I do to correct the IPrincipal User such that IsInRole returns the value from my custom RoleProvider?
RoleProvider source:
public class SimplicityRoleProvider : RoleProvider
{
private ILog log { get; set; }
public SimplicityRoleProvider()
{
log = LogManager.GetLogger("ff");
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
log.Warn(usernames);
log.Warn(roleNames);
}
public override string ApplicationName
{
get
{
return "Simplicity";
}
set
{
}
}
public override void CreateRole(string roleName)
{
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
return true;
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
log.Warn(roleName);
log.Warn(usernameToMatch);
return new string[0];
}
public override string[] GetAllRoles()
{
log.Warn("all roles");
return new string[0];
}
public override string[] GetRolesForUser(string username)
{
log.Warn(username);
return new String[] { "Administrator" };
}
public override string[] GetUsersInRole(string roleName)
{
log.Warn(roleName);
return new string[0];
}
public override bool IsUserInRole(string username, string roleName)
{
log.Warn(username);
log.Warn(roleName);
return true;
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
}
public override bool RoleExists(string roleName)
{
log.Warn(roleName);
return true;
}
}
It seems that System.Web.Security.Roles.GetRolesForUser(Username) does not get automatically hooked up when you have a custom AuthorizeAttribute and a custom RoleProvider.
So, in your custom AuthorizeAttribute you need to retrieve the list of roles from your data source and then compare them against the roles passed in as parameters to the AuthorizeAttribute.
I have seen in a couple blog posts comments that imply manually comparing roles is not necessary but when we override AuthorizeAttribute it seems that we are suppressing this behavior and need to provide it ourselves.
Anyway, I'll walk through what worked for me. Hopefully it will be of some assistance.
I welcome comments on whether there is a better way to accomplish this.
Note that in my case the AuthorizeAttribute is being applied to an ApiController although I'm not sure that is a relevant piece of information.
public class RequestHashAuthorizeAttribute : AuthorizeAttribute
{
bool requireSsl = true;
public bool RequireSsl
{
get { return requireSsl; }
set { requireSsl = value; }
}
bool requireAuthentication = true;
public bool RequireAuthentication
{
get { return requireAuthentication; }
set { requireAuthentication = value; }
}
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext ActionContext)
{
if (Authenticate(ActionContext) || !RequireAuthentication)
{
return;
}
else
{
HandleUnauthorizedRequest(ActionContext);
}
}
protected override void HandleUnauthorizedRequest(HttpActionContext ActionContext)
{
var challengeMessage = new System.Net.Http.HttpResponseMessage(HttpStatusCode.Unauthorized);
challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
throw new HttpResponseException(challengeMessage);
}
private bool Authenticate(System.Web.Http.Controllers.HttpActionContext ActionContext)
{
if (RequireSsl && !HttpContext.Current.Request.IsSecureConnection && !HttpContext.Current.Request.IsLocal)
{
//TODO: Return false to require SSL in production - disabled for testing before cert is purchased
//return false;
}
if (!HttpContext.Current.Request.Headers.AllKeys.Contains("Authorization")) return false;
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
IPrincipal principal;
if (TryGetPrincipal(authHeader, out principal))
{
HttpContext.Current.User = principal;
return true;
}
return false;
}
private bool TryGetPrincipal(string AuthHeader, out IPrincipal Principal)
{
var creds = ParseAuthHeader(AuthHeader);
if (creds != null)
{
if (TryGetPrincipal(creds[0], creds[1], creds[2], out Principal)) return true;
}
Principal = null;
return false;
}
private string[] ParseAuthHeader(string authHeader)
{
if (authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic")) return null;
string base64Credentials = authHeader.Substring(6);
string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new char[] { ':' });
if (credentials.Length != 3 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1]) || string.IsNullOrEmpty(credentials[2])) return null;
return credentials;
}
private bool TryGetPrincipal(string Username, string ApiKey, string RequestHash, out IPrincipal Principal)
{
Username = Username.Trim();
ApiKey = ApiKey.Trim();
RequestHash = RequestHash.Trim();
//is valid username?
IUserRepository userRepository = new UserRepository();
UserModel user = null;
try
{
user = userRepository.GetUserByUsername(Username);
}
catch (UserNotFoundException)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
//is valid apikey?
IApiRepository apiRepository = new ApiRepository();
ApiModel api = null;
try
{
api = apiRepository.GetApi(new Guid(ApiKey));
}
catch (ApiNotFoundException)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
if (user != null)
{
//check if in allowed role
bool isAllowedRole = false;
string[] userRoles = System.Web.Security.Roles.GetRolesForUser(user.Username);
string[] allowedRoles = Roles.Split(','); //Roles is the inherited AuthorizeAttribute.Roles member
foreach(string userRole in userRoles)
{
foreach (string allowedRole in allowedRoles)
{
if (userRole == allowedRole)
{
isAllowedRole = true;
}
}
}
if (!isAllowedRole)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
Principal = new GenericPrincipal(new GenericIdentity(user.Username), userRoles);
Thread.CurrentPrincipal = Principal;
return true;
}
else
{
Principal = null;
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
}
}
The custom authorize attribute is governing the following controller:
public class RequestKeyAuthorizeTestController : ApiController
{
[RequestKeyAuthorizeAttribute(Roles="Admin,Bob,Administrator,Clue")]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, "RequestKeyAuthorizeTestController");
}
}
In the custom RoleProvider, I have this method:
public override string[] GetRolesForUser(string Username)
{
IRoleRepository roleRepository = new RoleRepository();
RoleModel[] roleModels = roleRepository.GetRolesForUser(Username);
List<string> roles = new List<string>();
foreach (RoleModel roleModel in roleModels)
{
roles.Add(roleModel.Name);
}
return roles.ToArray<string>();
}
So the issue is not how you implement the role provider, but rather how you configure your application to use it. I could not find any issues in your configuration, though. Please make sure this is indeed how you configure your application. This post may help: http://brianlegg.com/post/2011/05/09/Implementing-your-own-RoleProvider-and-MembershipProvider-in-MVC-3.aspx. If you use the default MVC template to create the project, please check the AccountController. According to that post, you may need to do a few modifications to make a custom membership provider work. But that would not affect role providers.
Best Regards,
Ming Xu.
I don't like the custom authorization attribute because I have to remind people to use it. I chose to implement the my own IIdentity/IPrincipal class and wire it up on authorization.
The custom UserIdentity that calls the default RoleProvider:
public class UserIdentity : IIdentity, IPrincipal
{
private readonly IPrincipal _original;
public UserIdentity(IPrincipal original){
_original = original;
}
public string UserId
{
get
{
return _original.Identity.Name;
}
}
public string AuthenticationType
{
get
{
return _original.Identity.AuthenticationType;
}
}
public bool IsAuthenticated
{
get
{
return _original.Identity.IsAuthenticated;
}
}
public string Name
{
get
{
return _original.Identity.Name;
}
}
public IIdentity Identity
{
get
{
return this;
}
}
public bool IsInRole(string role){
return Roles.IsUserInRole(role);
}
}
and added this to global.asax.cs:
void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
if(false == HttpContext.Current.User is UserIdentity){
HttpContext.Current.User = new UserIdentity(HttpContext.Current.User);
}
}
What stimms wrote in his comment: "What I'm seeing is that the IPrincipal doesn't seem to have the correct RoleProvider set" got me looking at the implementation of my custom authentication attribute which inherits from Attribute and IAuthenticationFilter.
using System.Web.Security;
....
protected override async Task<IPrincipal> AuthenticateAsync(string userName, string password, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(password))
{
// No user with userName/password exists.
return null;
}
var membershipProvider = Membership.Providers["CustomMembershipProvider"];
if (membershipProvider != null && membershipProvider.ValidateUser(userName, password))
{
ClaimsIdentity identity = new GenericIdentity(userName, "Basic");
return new RolePrincipal("CustomRoleProvider", identity);
}
return null;
}
The key is in returning RolePrincipal, which points to your custom role provider.
Initially I returned new ClaimsPrincipal(identity), which gave me the problem described in the OP.

WCF Web API UriTemplate Elements Found in Multiple Methods

Let's say I am using the new WCF Web API to build a RESTful service and, in my service, I have a section of the URI that will describe the target resource, but is used on (nearly) all methods of the contract. For example, if I have a User service that deals with eCommerce and may look like:
[ServiceContract]
public class MyUserService
{
private MyUserRepository _UserRepo;
private MyOrganizationRepository _OrgRepo;
[WebGet (UriTemplate = "{OrganizationName}/Users")]
public IEnumerable<User> GetUsers (string OrganizationName)
{
IEnumerable<User> Users = null;
var Organization = _OrgRepo.GetOrgByName (OrganizationName);
if (Organization != null)
{
Users = Organization.GetUsers ();
}
else
{
throw new WebFaultException<string> ("Organization not found.", HttpStatusCode.NotFound);
}
return Users;
}
[WebInvoke (UriTemplate = "{OrganizationName}/Users", /*yada...yada...yada*/)]
public User AddNewUser (string OrganizationName, User User)
{
// Find the organization, like above, and throw if null.
}
}
If I have to continually load the organization and test for null, this will bog down my code and is not very DRY. (So tempted to spell out DRY...) What I would like to do is load up a property in the MyUserService class that is populated when {OrganizationName} is included in the URI and throw a WebFaultException otherwise. Because this is apart of the URI, what would be the best way to accomplish this?
EDIT:
For those that may be interested, here is an example of the HttpOperationHandler I came up with. There doesn't seem to be a whole lot of information out there covering this. I also found more information about Processors that will be coming with the WCF Web Api suite and it looks like they will handle this sort of thing better replace HttpOperationHandlers and it seems they may be easier to use. (This is just a for-instance to cover some things I found hard to find. I wrote it up a bit differently in my application.)
using Microsoft.ApplicationServer.Http.Dispatcher; // For HttpOperationHandler
using Microsoft.ApplicationServer.Http.Description; // For HttpOperationHandlerFactory
public class OrganizationHandler : HttpOperationHandler<string, Organization>
{
private Repository<Organization> _OrganizationRepository;
public OrganizationHandler (UnitOfWork Work)
: base ("OrganizationName")
{
_OrganizationRepository = Work.Organizations;
}
public override Organization OnHandle (string OrganizationName)
{
var Result = _OrganizationRepository
.Get (O => O.UrlSafeName.Equals (OrganizationName,
StringComparison.InvariantCultureIgnoreCase));
if (Result == null)
{
throw new WebFaultException<string> ("Organization not found.");
}
return Result;
}
}
public class OrganizationHandlerFactory : HttpOperationHandlerFactory
{
private UnitOfWork _Work;
public OrganizationHandlerFactory (UnitOfWork Work)
{
_Work = Work;
}
protected override Collection<HttpOperationHandler> OnCreateRequestHandlers
(ServiceEndpoint endpoint, HttpOperationDescription operation)
{
var Collection = base.OnCreateRequestHandlers (endpoint, operation);
if (operation.InputParameters.Any (IP => IP.Type.Equals (typeof (Organization))))
{
var Binding = endpoint.Binding as HttpBinding;
if (Binding != null)
{
Collection.Add (new OrganizationHandler (_Work));
}
}
return Collection;
}
}
And then to wire it up in Global.asax (I am using Ninject for IoC):
// Add this reference to get the MapServiceRoute<T> extension
using Microsoft.ApplicationServer.Http.Activation;
public class Global : HttpApplication
{
protected void Application_Start (object sender, EventArgs e)
{
var Kernel = BuildKernel ();
var Config = HttpHostConfiguration.Create ()
.SetOperationHandlerFactory
(Kernel.Get (typeof (OrganizationHandlerFactory)) as OrganizationHandlerFactory)
.SetResourceFactory (new NinjectResourceFactory (Kernel));
RouteTable.Routes.MapServiceRoute<OrganizationService> ("Organizations", Config);
}
protected IKernel BuildKernel ()
{
IKernel Kernel = new Ninject.StandardKernel ();
// Load up the Kernel
return Kernel;
}
}
public class NinjectResourceFactory : IResourceFactory
{
private readonly IKernel _Kernel;
public NinjectResourceFactory (IKernel Kernel)
{
_Kernel = Kernel;
}
public object GetInstance (Type serviceType, InstanceContext instanceContext, HttpRequestMessage request)
{
return Resolve (serviceType);
}
public void ReleaseInstance (InstanceContext instanceContext, object service)
{
throw new NotImplementedException ();
}
private object Resolve (Type type)
{
return _Kernel.Get (type);
}
}
And here it is in my Service:
[ServiceContract]
[ServiceBehavior (InstanceContextMode = InstanceContextMode.PerCall)]
public class OrganizationService
{
[WebGet (UriTemplate = "{OrganizationName}/Products")]
public IEnumerable<Product> GetProducts (Organization Organization)
{
return Organization.Products;
}
}
This is exactly what OperationHandlers are for. You create a single OperationHandler that converts the URI parameter into a strongly typed object that you can just accept as a parameter on the operation.