Skip to content

Instantly share code, notes, and snippets.

@lnickers2004
Created January 19, 2014 08:09
Show Gist options
  • Save lnickers2004/8501845 to your computer and use it in GitHub Desktop.
Save lnickers2004/8501845 to your computer and use it in GitHub Desktop.
WebApi 2: Implemented Post of DiaryEntries. Added a Parse method to the ModelFactory to build the DiaryEntry from the model. Return appropriate Http Status codes in success and failure cases. Updated Base api controller to pass the the repository into the ModelFactory so the model factory's new Parse() method can use the repository to get data t…
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,TheRepository);
}
return _modelFactory;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using CountingKs.Data;
using CountingKs.Services;
using CountingKs.Models;
namespace CountingKs.Controllers
{
public class DiaryEntriesController : BaseApiController
{
private ICountingKsIdentityService _identityService;
public DiaryEntriesController(ICountingKsRepository repo,ICountingKsIdentityService identityService)
: base(repo)
{
_identityService = identityService;
}
//return all the diary entries for the current user on the given date
public IEnumerable<DiaryEntryModel> Get(DateTime diaryId)
{
var results = TheRepository.GetDiaryEntries(_identityService.CurrentUser, diaryId)
.ToList()
.Select(e => TheModelFactory.Create(e));
//NOTE: if results = null we will return empty collection
return results;
}
//return the specific diary entry for the current user by diary entry id
public HttpResponseMessage Get(DateTime diaryId, int id)
{
var result = TheRepository.GetDiaryEntry(_identityService.CurrentUser,diaryId,id);
if(result == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK, TheModelFactory.Create(result));
}
}
public HttpResponseMessage Post( DateTime diaryId, [FromBody]DiaryEntryModel model)
{
try
{
var entity = TheModelFactory.Parse(model);
if(entity == null)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest,
"Could not read diary entry in bode");
}
var diary = TheRepository.GetDiary(_identityService.CurrentUser,diaryId);
if(diary == null)
{
//trying to create an entry for a diary that doesn't exist
return Request.CreateResponse(HttpStatusCode.NotFound);
}
//add the diary entry to the diary we retrieved
diary.Entries.Add(entity);
//BUSINESS RULE
//MAKE SURE ITS NOT A DUPLICATE FOOD ITEM
if(diary.Entries.Any(e=>e.Measure.Id == entity.Measure.Id))
{
return Request.CreateResponse(HttpStatusCode.BadRequest, "Duplicate Measure not allowed");
}
//SAVE THE NEW ENTRY
//store the updated diary with its new entry
//NOTE: if save fails the catch will handle it
if(TheRepository.SaveAll())
{
//Posts should always return Created upon success
return Request.CreateResponse(HttpStatusCode.Created, TheModelFactory.Create(entity));
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest, "Could not Save to the database");
}
}
catch(Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest,ex);
}
}
}
}
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;
using CountingKs.Data;
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;
private ICountingKsRepository _repo;//needed for filling entries based on input model ... see below
//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 ,ICountingKsRepository repo)
{
_repo = repo;
//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)
};
}
public DiaryModel Create( Diary d )
{
return new DiaryModel()
{
//NOTE: IMPORTANT, we are not using the Id property for the diary ID
//we are using the created date, or current date. There will be one diary per day per user.
//We need to insure that only one diary exists per user for any given date!!! We will do that
//elsewhere. We will assume that the database has unique constraint set on this field also
Url = _urlHelper.Link("Diaries", new { diaryid = d.CurrentDate.ToString("yyyy-MM-dd") }),
CurrentDate = d.CurrentDate,
Entries = d.Entries
.ToList()
.Select(e => Create(e))
};
}
public DiaryEntryModel Create( DiaryEntry e )
{
return new DiaryEntryModel()
{
Url = _urlHelper.Link("DiaryEntries", new { diaryid = e.Diary.CurrentDate.ToString("yyyy-MM-dd"), id=e.Id }),
FoodDescription = e.FoodItem.Description,
MeasureDescription = e.Measure.Description,
MeasureUrl = _urlHelper.Link("Measures", new { foodid = e.Measure.Food.Id, id = e.Measure.Id }),
Quantity= e.Quantity
};
}
//create a diary entry from the input model
public DiaryEntry Parse( DiaryEntryModel model )
{
try
{
var entry = new DiaryEntry();
//did user set the quantity?
if(model.Quantity != default(double))
{
entry.Quantity= model.Quantity;
}
var uri = new Uri(model.MeasureUrl);
//get id from the measureUrl passed via the model
//if parsing fails we'll be catching the exception and returning a null DiaryEntry
var measureId = int.Parse(uri.Segments.Last());
var measure =_repo.GetMeasure(measureId);
entry.Measure = measure;
entry.FoodItem = measure.Food;
return entry;
}
catch(Exception)
{
//the caller can test for null to see if parsing failed
return null;
}
}
}
}
@lnickers2004
Copy link
Author

implemented a simple business rule to disallow creating duplicate measures for a given day's diary entry

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