PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/RestCake/src/Examples/AddressBook.Services/WcfAndRestCakeDualService.cs

#
C# | 268 lines | 139 code | 37 blank | 92 comment | 16 complexity | 6463461c84f9cf9c2bf30120f3571e8d MD5 | raw file
Possible License(s): LGPL-2.1
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Data.Objects.DataClasses;
  5. using System.Linq;
  6. using System.ServiceModel;
  7. using System.ServiceModel.Activation;
  8. using System.ServiceModel.Web;
  9. using System.Text;
  10. using RestCake.AddressBook.DataAccess;
  11. namespace RestCake.AddressBook.Services
  12. {
  13. /// <summary>
  14. /// Remember that RestCake uses the same attributes as WCF for marking service classes and service methods (ServiceContract and WebGet/WebInvoke).
  15. /// For a service class to be accessible via both WCF and RestCake, you can't take advantage of all of the RestCake features, such as your service methods
  16. /// taking in non-string arguments.
  17. /// Look in Global.asax, in registerRoutes() to see how both the WCF and RestCake endpoints (routes) are set up for this class.
  18. /// </summary>
  19. [ServiceContract(Namespace = "RestCakeExamples", Name = "DualService")]
  20. [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
  21. [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
  22. public class WcfAndRestCakeDualService : RestHttpHandler
  23. {
  24. /// <summary>
  25. /// Since this class has no internal state, it can be reused.
  26. /// This is a RestCake thing, not a WCF thing. WCF's way is the ServiceBehavior attribute on the class.
  27. /// </summary>
  28. public override bool IsReusable
  29. {
  30. get { return true; }
  31. }
  32. [WebGet(UriTemplate="people")]
  33. public PersonDto[] GetPeople()
  34. {
  35. // Note that we only have to strip the cycles because of WCF. It would not be required for RestCake, which uses Json.NET
  36. return AddressBookDal.Instance.People.EagerLoad(p => p.Emails).ToDtos().StripCycles().ToArray();
  37. }
  38. /// <summary>
  39. /// Fetch an existing person record by id.
  40. /// Note that WCF can't pass an int into the service method, since the argument comes from the UriTemplate.
  41. /// It can do that with query string params, however. If this service were just RestCake, we could just pass an int
  42. /// right in, instead of having to TryParse it. We'll see this in other examples.
  43. /// </summary>
  44. /// <param name="sID"></param>
  45. /// <returns></returns>
  46. [WebGet(UriTemplate = "person/{sID}")]
  47. public PersonDto GetPerson(string sID)
  48. {
  49. int id;
  50. int.TryParse(sID, out id);
  51. if (id <= 0)
  52. throw new ArgumentException("id must be a valid positive integer");
  53. Person person = AddressBookDal.Instance.People
  54. .EagerLoad(p => p.Emails)
  55. .Where(p => p.ID == id)
  56. .SingleOrDefault();
  57. return (person == null) ? null : person.ToDto();
  58. }
  59. /// <summary>
  60. /// Create a new person record. ID is assigned server side, and the new person record is sent back to the caller
  61. /// </summary>
  62. /// <param name="person"></param>
  63. /// <returns></returns>
  64. [WebInvoke(Method = "POST", UriTemplate = "person")]
  65. public PersonDto CreatePerson(PersonDto person)
  66. {
  67. // When creating a new person, we don't accept an ID value from the DTO passed in. It's assigned here, as if it were an IDENTITY field in SQL server.
  68. Person newPerson = new Person()
  69. {
  70. DateCreated = DateTime.Now,
  71. DateModified = DateTime.Now,
  72. Fname = person.Fname,
  73. Lname = person.Lname,
  74. Emails = new EntityCollection<EmailAddress>()
  75. };
  76. if (person.Emails != null)
  77. person.Emails.ForEach(email => newPerson.Emails.Add(email.ToEntity()));
  78. AddressBookDal.Instance.People.AddObject(newPerson);
  79. AddressBookDal.Instance.SaveChanges();
  80. return newPerson.ToDto();
  81. }
  82. /// <summary>
  83. /// Update an existing person record by passing in a PersonDto. Returns the modified record.
  84. /// This approach just does individual assignments, as in p.Fname = dto.Fname;, etc
  85. /// </summary>
  86. /// <param name="person"></param>
  87. /// <returns></returns>
  88. [WebInvoke(Method = "PUT", UriTemplate = "person")]
  89. public PersonDto UpdatePerson(PersonDto person)
  90. {
  91. // Make sure the person exists
  92. Person existing = Person.GetByKey(person.ID);
  93. if (existing == null)
  94. throw new ArgumentException("Person with id " + person.ID + " does not exist. Cannot update.");
  95. existing.Fname = person.Fname;
  96. existing.Lname = person.Lname;
  97. if (person.Emails != null)
  98. person.Emails.ForEach(email => existing.Emails.Add(email.ToEntity()));
  99. existing.DateModified = DateTime.Now;
  100. AddressBookDal.Instance.SaveChanges();
  101. return existing.ToDto();
  102. }
  103. /// <summary>
  104. /// Update an existing person record by passing in a PersonDto. Returns the modified record.
  105. /// This approach takes the dto and creates an entity object from it, then attaches that entity to the current
  106. /// (singleton) ObjectContext with an ObjectStateEntry of modified, and then saves the changes.
  107. ///
  108. /// One reason I don't like this method, is you don't have fine grained control over which fields can or cannot be updated.
  109. /// Also, javascript is a dynamic language. It's ok if a property is missing, like Lname. But when that javascript object
  110. /// is deserialized to a PersonDto, if Lname is missing, it will contain null or "", and your object will be updated to that
  111. /// value, when what you probably really wanted was for the value to stay the same.
  112. ///
  113. /// See UpdatePerson3() for my favorite way to update entities.
  114. /// </summary>
  115. /// <param name="person"></param>
  116. /// <returns></returns>
  117. [WebInvoke(Method = "PUT", UriTemplate = "person2")]
  118. public PersonDto UpdatePerson2(PersonDto person)
  119. {
  120. Person ent = person.ToEntity();
  121. // Attach the person to the current ObjectContext, and mark it as modified
  122. AddressBookDal.Instance.Attach(ent);
  123. AddressBookDal.Instance.ObjectStateManager.ChangeObjectState(ent, EntityState.Modified);
  124. // Save the changes
  125. AddressBookDal.Instance.SaveChanges();
  126. return ent.ToDto();
  127. }
  128. /// <summary>
  129. /// Update an existing person record by passing in the ID of the person, and a dictionary that contains the values
  130. /// for the person's properties that we're updating. This uses Loef's ApplyValues() method.
  131. /// </summary>
  132. /// <param name="id"></param>
  133. /// <param name="values"></param>
  134. /// <returns></returns>
  135. /// [WebInvoke(Method = "PUT", UriTemplate = "person2")]
  136. public PersonDto UpdatePerson3(int id, Dictionary<string, object> values)
  137. {
  138. Person person = Person.GetByKey(id);
  139. if (person == null)
  140. throw new ArgumentException("Person with id " + id + " could not be found. Update failed.");
  141. // TODO: Test that the Emails collection updates properly as well
  142. string[] permittedFields = {"Fname", "Lname", "Emails"};
  143. person.ApplyValues(values.Where(pair => permittedFields.Contains(pair.Key)));
  144. person.DateModified = DateTime.Now;
  145. AddressBookDal.Instance.SaveChanges();
  146. return person.ToDto();
  147. }
  148. /// <summary>
  149. /// Delete a person record by id.
  150. /// </summary>
  151. /// <param name="sID"></param>
  152. [WebInvoke(Method = "DELETE", UriTemplate = "person/{sID}")]
  153. public void DeletePerson(string sID)
  154. {
  155. int id;
  156. int.TryParse(sID, out id);
  157. if (id <= 0)
  158. throw new ArgumentException("id must be a valid positive integer");
  159. // Make sure the person exists
  160. Person existing = Person.GetByKey(id);
  161. if (existing == null)
  162. throw new ArgumentException("Person with id " + id + " does not exist. Cannot update.");
  163. existing.Delete();
  164. }
  165. /// <summary>
  166. /// Same as Error_NeedsWrappedRequest, but with only a single post arg, so it should work.
  167. /// This is just to illustrate that multiple args are ok, as long as only *one* are is the "posted" arg. The others
  168. /// must either be uri args or query string args.
  169. /// </summary>
  170. [WebInvoke(Method = "POST", UriTemplate = "error/needsWrappedRequest/{dummyUriArg1}/{dummyUriArg2}?qarg1={qarg1}&qarg2={qarg2}")]
  171. public string NoError_NoWrappedRequest(string dummyUriArg1, string dummyUriArg2, string qarg1, string qarg2, string postArg1)
  172. {
  173. StringBuilder sb = new StringBuilder();
  174. sb.AppendLine("dummyUriArg1: " + dummyUriArg1);
  175. sb.AppendLine("dummyUriArg2: " + dummyUriArg2);
  176. sb.AppendLine("qarg1: " + qarg1);
  177. sb.AppendLine("qarg2: " + qarg2);
  178. sb.AppendLine("postArg1: " + postArg1);
  179. return sb.ToString();
  180. }
  181. [WebInvoke(Method = "POST", UriTemplate = "error/test_multiplePost_plusDto/{dummyUriArg1}/{dummyUriArg2}?qarg1={qarg1}&qarg2={qarg2}", BodyStyle = WebMessageBodyStyle.WrappedRequest)]
  182. public object Test_MultiplePost_PlusDto(string dummyUriArg1, string dummyUriArg2, string qarg1, string qarg2, string postArg1, int postArg2, DateTime postArg3, PersonDto person)
  183. {
  184. return new
  185. {
  186. dummyUriArg1,
  187. dummyUriArg2,
  188. qarg1,
  189. qarg2,
  190. postArg1,
  191. postArg2,
  192. postArg3,
  193. person
  194. };
  195. }
  196. /*
  197. /// <summary>
  198. /// This would cause an error when trying to get the js client (RestCake), or when calling the service (WCF), because
  199. /// the last 2 args are both POST arguments (not part of the uri or the query string).
  200. /// For this to work, the body style would have to be wrapped or wrapped request.
  201. /// </summary>
  202. /// <param name="dummyUriArg1"></param>
  203. /// <param name="dummyUriArg2"></param>
  204. /// <param name="qarg1"></param>
  205. /// <param name="qarg2"></param>
  206. /// <param name="postArg1"></param>
  207. /// <param name="postArg2"></param>
  208. [WebInvoke(Method = "POST", UriTemplate = "error/needsWrappedRequest/{dummyUriArg1}/{dummyUriArg2}?qarg1={qarg1}&qarg2={qarg2}")]
  209. public string Error_NeedsWrappedRequest(string dummyUriArg1, string dummyUriArg2, string qarg1, string qarg2, string postArg1, string postArg2)
  210. {
  211. StringBuilder sb = new StringBuilder();
  212. sb.AppendLine("dummyUriArg1: " + dummyUriArg1);
  213. sb.AppendLine("dummyUriArg2: " + dummyUriArg2);
  214. sb.AppendLine("qarg1: " + qarg1);
  215. sb.AppendLine("qarg2: " + qarg2);
  216. sb.AppendLine("postArg1: " + postArg1);
  217. sb.AppendLine("postArg2: " + postArg2);
  218. return sb.ToString();
  219. }
  220. */
  221. [WebGet(UriTemplate = "person/{sID}/addresses")]
  222. public AddressDto[] GetAddresses(string sID)
  223. {
  224. int personID;
  225. int.TryParse(sID, out personID);
  226. if (personID <= 0)
  227. throw new ArgumentException("id must be a valid positive integer");
  228. return AddressBookDal.Instance.Addresses.Where(addr => addr.PersonID == personID).ToDtos();
  229. }
  230. }
  231. }