Skip to content

Instantly share code, notes, and snippets.

@lnickers2004
Forked from anonymous/gist:8486727
Last active January 3, 2016 15:59
Show Gist options
  • Save lnickers2004/8486730 to your computer and use it in GitHub Desktop.
Save lnickers2004/8486730 to your computer and use it in GitHub Desktop.
WebApi2: DTO ModelFactory which has been refactored to include the creation of HATEOAS URIs-- HyperMedia Links NOTE: we were not able to use automapper to Map between Entities and DTO's because we wanted to create Hypertext URIs for the DTOs inside the Factory. So we opted to use the Factory Pattern for the creation of our DTO's
using CountingKs.Data;
using CountingKs.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace CountingKs.Controllers
{
//make base controller abstract so it cannot be instantiated
//all our controllers will derive from base controller to
//inherit common funtionality
public abstract class BaseApiController : ApiController
{
private ModelFactory _modelFactory;
private ICountingKsRepository _repo;
public BaseApiController( ICountingKsRepository repo )
{
_repo = repo;
}
protected ICountingKsRepository TheRepository
{
get
{
return _repo;
}
}
//KEY POINT: we need to defer the creation of the model factory until
// it is needed. So we create a deferred property
//Note: this is basically a singleton pattern..
//the factory will exist for the lifetime of the controller
protected ModelFactory TheModelFactory
{
//remember, we're using factory pattern to copy database entities into DTO's that contain
//discoverable Hyperlinks for each resource returned to the user
get
{
//the first get will cause the factory to be created
if(_modelFactory == null)
{
//it should be late enough for the request to not be null
//the model factory will be crated as a result of a user request
//so we can create the model factory and pass it the request so we may
//use it and the Url helper to generate the requested resource's URI Hyperlinks for including
//in the DTO that we pass back to the user
_modelFactory = new ModelFactory(this.Request);
}
return _modelFactory;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace CountingKs.Models
{
public class FoodModel
{
public string Url { get; set; }
public string Description { get; set; }
public IEnumerable<MeasureModel> Measures { get; set; }
}
}
using CountingKs.Data;
using CountingKs.Data.Entities;
using CountingKs.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace CountingKs.Controllers
{
public class FoodsController : BaseApiController
{
//hand off the injected repo to our base controller
public FoodsController( ICountingKsRepository repo ): base(repo)
{
}
//users may optionally supply ?includeMeasures=true on query string
//we defualt it to true so the route is matched even if the querystring does not have includeMeasures
//supplied
public IEnumerable<FoodModel> Get( bool includeMeasures = true )
{
//The following code causes exception
//
//message: "An error has occurred."
//exceptionMessage: "The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'."
//exceptionType: "System.InvalidOperationException"
//stackTrace: null
//innerException: {
//message: "An error has occurred."
//exceptionMessage: "Self referencing loop detected for property 'food' with type 'CountingKs.Data.Entities.Food'. Path '[0].measures[0]'."
//exceptionType: "Newtonsoft.Json.JsonSerializationException"
//
//var results = _repo.GetAllFoodsWithMeasures()
// .OrderBy(f => f.Description)
// .Take(25)
// .ToList();
//There are a few ways to fix this
//Method 1: project the childe object without a reference back to the parent of the relation
//this is a fragile...
//var results = _repo.GetAllFoodsWithMeasures()
// .OrderBy(f => f.Description)
// .Take(25)
// .ToList().Select(f => new{
// Description = f.Description,
// Measures = f.Measures.Select(m =>
// new
// {
// Description = m.Description,
// Calories = m.Calories
// })
// });
// return results;
//Method 2 add a DTO, ViewModel that holds only what you wish to serialize to clients
//(Automapper can help here-- See http://lostechies.com/jimmybogard/2013/08/25/automapper-3-0-released/
//var results = _repo.GetAllFoodsWithMeasures()
// .OrderBy(f => f.Description)
// .Take(25)
// .ToList().Select(f => new FoodModel
// {
// Description = f.Description,
// Measures = f.Measures.Select(m =>
// new MeasureModel
// {
// Description = m.Description,
// Calories = m.Calories
// })
// });
// return results;
//Method 3: Use a Factory Pattern to return our DTO view models
//NOTE: WE CHOOSE NOT to use Automapper, because we want to be able to build return URI's etc into
//to return data e.g. HATEOS Hypermedia etc
//using a Model Factory, we have only one place to handling Mapping from
//Database entities to DTO's
//NOTE: the query returns a collection of foods
IEnumerable<Food> query;
if(includeMeasures)
{
query = TheRepository.GetAllFoodsWithMeasures();
}
else
{
query = TheRepository.GetAllFoods();
};
//note results is a list of FoodModels from the factory
var results = query.OrderBy(f => f.Description)
.Take(25)
.ToList().Select(f => TheModelFactory.Create(f));
return results;
}
public FoodModel Get( int foodid )
{
return TheModelFactory.Create(TheRepository.GetFood(foodid));
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CountingKs.Models
{
public class MeasureModel
{
public string Url { get; set; }
public string Description { get; set; }
public double Calories { get; set; }
}
}
using CountingKs.Data;
using CountingKs.Data.Entities;
using CountingKs.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace CountingKs.Controllers
{
public class MeasuresController : BaseApiController
{
//hand off the injected repo to our base controller
public MeasuresController( ICountingKsRepository repo): base( repo)
{
}
public IEnumerable<MeasureModel> Get( int foodid)
{
//we call ToList before the select to force the retrieval of data before we transform it
//with the select projection
var results = TheRepository.GetMeasuresForFood(foodid)
.ToList()
.Select(m => TheModelFactory.Create(m));
return results;
}
public MeasureModel Get( int foodid, int id)
{
var results = TheRepository.GetMeasure(id);
if ( results.Food.Id == foodid)
{
return TheModelFactory.Create(results);
}
else
{
return null;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using CountingKs.Data.Entities;
using System.Web.Http.Routing;
using System.Net.Http;
namespace CountingKs.Models
{
//using a Model Factory, we have only one place to handling Mapping from
//Database entities to DTO's
public class ModelFactory
{
private UrlHelper _urlHelper;
//LEN NOTE: THE REASON WE DID NOT USE AUTOMAPPER IS BECAUSE WE WANTED TO USE THE REQUEST
//AND THE URL HELPER TO AID US IN CREATING OUR HYPERLINKS
public ModelFactory( HttpRequestMessage request )
{
//Remember to use the Url helper for WebAPI
//which is in System.Web.Http.Routing
_urlHelper = new UrlHelper(request); //this will help us to build
//the URLs for developer API discovery
}
public FoodModel Create( Food food )
{
return new FoodModel()
{
//get an url for the Food roud and supply the id of the food that we're returning
Url = _urlHelper.Link("Food", new { foodid = food.Id }),
Description = food.Description,
Measures = food.Measures.Select(m => Create(m))
};
}
public MeasureModel Create( Measure measure )
{
return new MeasureModel()
{
Url = _urlHelper.Link("Measures", new { foodid = measure.Food.Id, id = measure.Id }),
Description = measure.Description,
Calories = Math.Round(measure.Calories)
};
}
}
}
@lnickers2004
Copy link
Author

oops originally created this before i logged in so i forked it from anonymous user... oh well

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