Skip to content

Instantly share code, notes, and snippets.

@JasonOffutt
Created October 11, 2011 23:02
Show Gist options
  • Save JasonOffutt/1279738 to your computer and use it in GitHub Desktop.
Save JasonOffutt/1279738 to your computer and use it in GitHub Desktop.
WCF REST Service API proposal
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Web;
using Rock.Models.Crm;
[ServiceContract]
public interface IPersonService
{
/// <summary>
/// 'GET' /api/v1/people.json
/// </summary>
/// <returns>A list of people</returns>
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "v1/people.{format}")]
IEnumerable<Person> List();
/// <summary>
/// 'GET' /api/v1/people/1.json
/// </summary>
/// <param name="id"></param>
/// <returns>A person</returns>
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "v1/people/{id}.{format}")]
Person Show(int id = -1);
/// <summary>
/// 'POST' /api/v1/people
/// </summary>
/// <param name="person">Hash of the person to be created</param>
/// <returns>True of false based on successful person creation</returns>
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "v1/people.{format}")]
bool Create(Person person);
/// <summary>
/// 'PUT' /api/v1/people/1
/// </summary>
/// <param name="person">Hash of the person to update</param>
/// <param name="id">ID of the person to update</param>
/// <returns>True or false based on successful person update</returns>
[OperationContract]
[WebInvoke(Method = "PUT", UriTemplate = "v1/people/{id}.{format}")]
bool Update(Person person, int id = -1);
/// <summary>
/// 'DELETE' /api/v1/people/1
/// </summary>
/// <param name="id">ID of the person to delete</param>
/// <returns>True or false based on successful person delete</returns>
[OperationContract]
[WebInvoke(Method = "DELETE", UriTemplate = "v1/people/{id}.{format}")]
bool Destroy(int id = -1);
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Web.Security;
using Rock.Helpers;
using Rock.Models.Crm;
public class PersonService : IPersonService
{
private readonly bool isAuthenticated;
private readonly MembershipUser currentUser;
public PersonService()
{
currentUser = Membership.GetUser();
isAuthenticated = currentUser != null;
}
public IEnumerable<Person> List()
{
VerifyAuthentication();
using (var uow = new UnitOfWorkScope())
{
uow.objectContext.Configuration.ProxyCreationEnabled = false;
var personService = new Rock.Services.Crm.PersonService();
return personService.Queryable().Where(p => p.Authorized("View", currentUser));
}
}
public Person Show(int id = -1)
{
VerifyAuthentication();
using (var uow = new UnitOfWorkScope())
{
uow.objectContext.Configuration.ProxyCreationEnabled = false;
var personService = new Rock.Services.Crm.PersonService();
var person = personService.GetPerson(id);
if (person.Authorized("View", currentUser))
{
return person;
}
throw new FaultException("Unauthorized");
}
}
public bool Create(Person person)
{
VerifyAuthentication();
using (var uow = new UnitOfWorkScope())
{
var ctx = WebOperationContext.Current;
try
{
uow.objectContext.Configuration.ProxyCreationEnabled = false;
var personService = new Rock.Services.Crm.PersonService();
personService.AttachPerson(person);
personService.Save(person, (int)currentUser.ProviderUserKey);
return true;
}
catch
{
ctx.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError;
return false;
}
}
}
public bool Update(Person person, int id = -1)
{
VerifyAuthentication();
using (var uow = new UnitOfWorkScope())
{
var ctx = WebOperationContext.Current;
try
{
uow.objectContext.Configuration.ProxyCreationEnabled = false;
var personService = new Rock.Services.Crm.PersonService();
var existingPerson = personService.GetPerson(id);
if (existingPerson == null)
{
ctx.OutgoingResponse.StatusCode = HttpStatusCode.NotFound;
return false;
}
if (existingPerson.Authorized("Edit", currentUser))
{
uow.objectContext.Entry(existingPerson).CurrentValues.SetValues(person);
personService.Save(existingPerson, (int)currentUser.ProviderUserKey);
}
else
{
ctx.OutgoingResponse.StatusCode = HttpStatusCode.Forbidden;
return false;
}
return true;
}
catch (Exception)
{
ctx.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError;
return false;
}
}
}
public bool Destroy(int id = -1)
{
VerifyAuthentication();
using (var uow = new UnitOfWorkScope())
{
var ctx = WebOperationContext.Current;
try
{
uow.objectContext.Configuration.ProxyCreationEnabled = false;
var personService = new Rock.Services.Crm.PersonService();
var person = personService.GetPerson(id);
if (person == null)
{
ctx.OutgoingResponse.StatusCode = HttpStatusCode.NotFound;
return false;
}
if (person.Authorized("Edit", currentUser))
{
personService.DeletePerson(person);
return true;
}
ctx.OutgoingResponse.StatusCode = HttpStatusCode.Forbidden;
return false;
}
catch (Exception)
{
ctx.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError;
return false;
}
}
}
private void VerifyAuthentication()
{
if (isAuthenticated)
{
// TODO: Consider returning a 401 (unauthenticated) or 403 (forbidden) rather than throwing an exception
throw new FaultException("Must be logged in");
}
}
}
@JasonOffutt
Copy link
Author

Yeah, I think the MEF technique is really solid. I think it will deliver the desired effect of a more extensible and easy to maintain API. It's still not quite fully baked yet though.

There's some pretty hard core functionality composition happening. And it actually builds. I'm using a lot of generics, though, to keep my solution as DRY as possible. The downside is that in the implementation, I'm having to cast to ISecured often to check security (that won't effect the community dev who's looking to append their own service, however, as long as their custom entities implement ISecured).

Today, I'm going to write some tests to double check my assertions. Hopefully I'll have something for you guys to look at soon.

@JasonOffutt
Copy link
Author

OK, update time.

I've hit a bit of a snag. It seems WCF doesn't offer a very good means of dynamically setting up endpoints. You still have to enter each service individually into the web.config. I'll post another Gist with what I've got so far, so you guys can see what I've been doing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment