PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/articles/mobile-services-dotnet-backend-use-existing-sql-database.md

https://github.com/deaquino/azure-content
Markdown | 609 lines | 461 code | 148 blank | 0 comment | 0 complexity | 925da1c2da10df92412818cc01319bbd MD5 | raw file
  1. <properties linkid="mobile-services-dotnet-backend-use-existing-sql-database" urlDisplayName="Build a service using an existing SQL database with the Mobile Services .NET backend" pageTitle="Build a service using an existing SQL database with the Mobile Services .NET backend - Azure Mobile Services" metaKeywords="" description="Learn how to use an existing cloud or on-premises SQL database with your .NET based mobile service" metaCanonical="" services="mobile-services,biztalk-services" documentationCenter="Mobile" title="Build a service using an existing SQL database with the Mobile Services .NET backend" authors="yavorg" solutions="" manager="" editor="mollybos" />
  2. <tags ms.service="mobile-services" ms.workload="mobile" ms.tgt_pltfrm="mobile-multiple" ms.devlang="multiple" ms.topic="article" ms.date="01/01/1900" ms.author="yavorg" />
  3. # Build a service using an existing SQL database with the Mobile Services .NET backend
  4. The Mobile Services .NET backend makes it easy to take advantage of existing assets in building a mobile service. One particularly interesting scenario is using an existing SQL database (either on-premises or in the cloud), that may already be used by other applications, to make existing data available to mobile clients. In this case it's a requirement that database model (or *schema*) remain unchanged, in order for existing solutions to continue working.
  5. This tutorial consists of the following sections:
  6. 1. [Exploring the existing database model](#ExistingModel)
  7. 2. [Creating data transfer objects (DTOs) for your mobile service](#DTOs)
  8. 3. [Establishing a mapping between DTOs and model](#Mapping)
  9. 4. [Implementing domain-specific logic](#DomainManager)
  10. 5. [Implementing a TableController using DTOs](#Controller)
  11. <a name="ExistingModel"></a>
  12. ## Exploring the existing database model
  13. For this tutorial we will use the database that was created with your mobile service, but we will not use the default model that is created. Instead, we will manually create an arbitrary model that will represent an existing application that you may have. For full details about how to connect to an on-premises database instead, check out [Connect to an on-premises SQL Server from an Azure mobile service using Hybrid Connections](/en-us/documentation/articles/mobile-services-dotnet-backend-hybrid-connections-get-started/).
  14. 1. Start by creating a Mobile Services server project in **Visual Studio 2013 Update 2** or by using the quickstart project that you can download on the Mobile Services tab for your service in the [Azure Management Portal](http://manage.windowsazure.com). For the purposes of this tutorial, we will assume your server project name is **ShoppingService**.
  15. 2. Create a **Customer.cs** file inside the **Models** folder and use the following implementation. You will need to add an assembly reference to **System.ComponentModel.DataAnnotations** to your project.
  16. using System.Collections.Generic;
  17. using System.ComponentModel.DataAnnotations;
  18. namespace ShoppingService.Models
  19. {
  20. public class Customer
  21. {
  22. [Key]
  23. public int CustomerId { get; set; }
  24. public string Name { get; set; }
  25. public virtual ICollection<Order> Orders { get; set; }
  26. }
  27. }
  28. 3. Create an **Order.cs** file inside the **Models** folder and use the following implementation:
  29. using System.ComponentModel.DataAnnotations;
  30. namespace ShoppingService.Models
  31. {
  32. public class Order
  33. {
  34. [Key]
  35. public int OrderId { get; set; }
  36. public string Item { get; set; }
  37. public int Quantity { get; set; }
  38. public bool Completed { get; set; }
  39. public int CustomerId { get; set; }
  40. public virtual Customer Customer { get; set; }
  41. }
  42. }
  43. You will note that these two classes have a *relationship*: every **Order** is associated with a single **Customer** and a **Customer** can be associated with multiple **Orders**. Having relationships is common in existing data models.
  44. 4. Create an **ExistingContext.cs** file inside the **Models** folder and implement it as so:
  45. using System.Data.Entity;
  46. namespace ShoppingService.Models
  47. {
  48. public class ExistingContext : DbContext
  49. {
  50. public ExistingContext()
  51. : base("Name=MS_TableConnectionString")
  52. {
  53. }
  54. public DbSet<Customer> Customers { get; set; }
  55. public DbSet<Order> Orders { get; set; }
  56. }
  57. }
  58. The structure above approximates an existing Entity Framework model that you may already be using for an existing application. Please note that the model is not aware of Mobile Services in any way at this stage.
  59. <a name="DTOs"></a>
  60. ## Creating data transfer objects (DTOs) for your mobile service
  61. The data model you would like to use with your mobile service may be arbitrarily complex; it could contain hundreds of entities with a variety of relationships between them. When building a mobile app, it is usually desirable to simplify the data model and eliminate relationships (or handle them manually) in order to minimize the payload being sent back and forth between the app and the service. In this section, we will create a set of simplified objects (known as "data transfer objects" or "DTOs"), that are mapped to the data you have in your database, yet contain only the minimal set of properties needed by your mobile app.
  62. 1. Create the **MobileCustomer.cs** file in the **DataObjects** folder of your service project and use the following implementation:
  63. using Microsoft.WindowsAzure.Mobile.Service;
  64. namespace ShoppingService.DataObjects
  65. {
  66. public class MobileCustomer : EntityData
  67. {
  68. public string Name { get; set; }
  69. }
  70. }
  71. Note that this class is similar to the **Customer** class in the model, except the relationship property to **Order** is removed. For an object to work correctly with Mobile Services offline sync, it needs a set of *system properties* for optimistic concurrency, so you will notice that the DTO inherits from [**EntityData**](http://msdn.microsoft.com/library/microsoft.windowsazure.mobile.service.entitydata.aspx), which contains those properties. The int-based **CustomerId** property from the original model is replaced by the string-based **Id** property from **EntityData**, which will be the **Id** that Mobile Services will use.
  72. 2. Create the **MobileOrder.cs** file in the **DataObjects** folder of your service project.
  73. using Microsoft.WindowsAzure.Mobile.Service;
  74. using Newtonsoft.Json;
  75. using System.ComponentModel.DataAnnotations;
  76. using System.ComponentModel.DataAnnotations.Schema;
  77. namespace ShoppingService.DataObjects
  78. {
  79. public class MobileOrder : EntityData
  80. {
  81. public string Item { get; set; }
  82. public int Quantity { get; set; }
  83. public bool Completed { get; set; }
  84. [NotMapped]
  85. public int CustomerId { get; set; }
  86. [Required]
  87. public string MobileCustomerId { get; set; }
  88. public string MobileCustomerName { get; set; }
  89. }
  90. }
  91. The **Customer** relationship property has been replaced with the **Customer** name and a **MobileCustomerId** property that can be used to manually model the relationship on the client. For now you can ignore the **CustomerId** property, it is only used later on.
  92. 3. You might notice that with the addition of the system properties on the **EntityData** base class, our DTOs now have more properties than the model types. Clearly we need a place to store these properties, so we will add a few extra columns to the original database. While this does change the database, it will not break existing applications since the changes are purely additive (adding new columns to the schema). To do that, add the following statements to the top of **Customer.cs** and **Order.cs**:
  93. using System.ComponentModel.DataAnnotations.Schema;
  94. using Microsoft.WindowsAzure.Mobile.Service.Tables;
  95. using System.ComponentModel.DataAnnotations;
  96. using System;
  97. Then, add these extra properties to each of the classes:
  98. [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  99. [Index(IsClustered = true)]
  100. [TableColumn(TableColumnType.CreatedAt)]
  101. public DateTimeOffset? CreatedAt { get; set; }
  102. [TableColumn(TableColumnType.Deleted)]
  103. public bool Deleted { get; set; }
  104. [Index]
  105. [TableColumn(TableColumnType.Id)]
  106. [MaxLength(36)]
  107. public string Id { get; set; }
  108. [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
  109. [TableColumn(TableColumnType.UpdatedAt)]
  110. public DateTimeOffset? UpdatedAt { get; set; }
  111. [TableColumn(TableColumnType.Version)]
  112. [Timestamp]
  113. public byte[] Version { get; set; }
  114. 4. The system properties just added have some built-in behaviors (for example automatic update of created/updated at) that happen transparently with database operations. To enable these behaviors, we need to make a change to **ExistingContext.cs**. At the top of the file, add the following:
  115. using System.Data.Entity.ModelConfiguration.Conventions;
  116. using Microsoft.WindowsAzure.Mobile.Service.Tables;
  117. using System.Linq;
  118. Then, in the body of **ExistingContext**, override [**OnModelCreating**](http://msdn.microsoft.com/library/system.data.entity.dbcontext.onmodelcreating.aspx):
  119. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  120. {
  121. modelBuilder.Conventions.Add(
  122. new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
  123. "ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
  124. base.OnModelCreating(modelBuilder);
  125. }
  126. 5. Let's populate the database with some example data. Open the file **WebApiConfig.cs**. Create a new [**IDatabaseInitializer**](http://msdn.microsoft.com/library/gg696323.aspx) and configure it in the **Register** method as shown below.
  127. using Microsoft.WindowsAzure.Mobile.Service;
  128. using ShoppingService.Models;
  129. using System;
  130. using System.Collections.Generic;
  131. using System.Collections.ObjectModel;
  132. using System.Data.Entity;
  133. using System.Web.Http;
  134. namespace ShoppingService
  135. {
  136. public static class WebApiConfig
  137. {
  138. public static void Register()
  139. {
  140. ConfigOptions options = new ConfigOptions();
  141. HttpConfiguration config = ServiceConfig.Initialize(new ConfigBuilder(options));
  142. Database.SetInitializer(new ExistingInitializer());
  143. }
  144. }
  145. public class ExistingInitializer : ClearDatabaseSchemaIfModelChanges<ExistingContext>
  146. {
  147. protected override void Seed(ExistingContext context)
  148. {
  149. List<Order> orders = new List<Order>
  150. {
  151. new Order { OrderId = 10, Item = "Guitars", Quantity = 2, Id = Guid.NewGuid().ToString()},
  152. new Order { OrderId = 20, Item = "Drums", Quantity = 10, Id = Guid.NewGuid().ToString()},
  153. new Order { OrderId = 30, Item = "Tambourines", Quantity = 20, Id = Guid.NewGuid().ToString() }
  154. };
  155. List<Customer> customers = new List<Customer>
  156. {
  157. new Customer { CustomerId = 1, Name = "John", Orders = new Collection<Order> {
  158. orders[0]}, Id = Guid.NewGuid().ToString()},
  159. new Customer { CustomerId = 2, Name = "Paul", Orders = new Collection<Order> {
  160. orders[1]}, Id = Guid.NewGuid().ToString()},
  161. new Customer { CustomerId = 3, Name = "Ringo", Orders = new Collection<Order> {
  162. orders[2]}, Id = Guid.NewGuid().ToString()},
  163. };
  164. foreach (Customer c in customers)
  165. {
  166. context.Customers.Add(c);
  167. }
  168. base.Seed(context);
  169. }
  170. }
  171. }
  172. <a name="Mapping"></a>
  173. ## Establishing a mapping between DTOs and model
  174. We now have the model types **Customer** and **Order** and the DTOs **MobileCustomer** and **MobileOrder**, but we need to instruct the backend to automatically transform between the two. Here Mobile Services relies on [**AutoMapper**](http://automapper.org/), an object relational mapper, which is already referenced in the project.
  175. 1. Add the following to the top of **WebApiConfig.cs**:
  176. using AutoMapper;
  177. using ShoppingService.DataObjects;
  178. 2. To define the mapping, add the following to the **Register** method of the **WebApiConfig** class.
  179. Mapper.Initialize(cfg =>
  180. {
  181. cfg.CreateMap<MobileOrder, Order>();
  182. cfg.CreateMap<MobileCustomer, Customer>();
  183. cfg.CreateMap<Order, MobileOrder>()
  184. .ForMember(dst => dst.MobileCustomerId, map => map.MapFrom(x => x.Customer.Id))
  185. .ForMember(dst => dst.MobileCustomerName, map => map.MapFrom(x => x.Customer.Name));
  186. cfg.CreateMap<Customer, MobileCustomer>();
  187. });
  188. AutoMapper will now map the objects to one another. All properties with corresponding names will be matched, for example **MobileOrder.CustomerId** will get automatically mapped to **Order.CustomerId**. Custom mappings can be configured as shown above, where we map the **MobileCustomerName** property to the **Name** property of the **Customer** relationship property.
  189. <a name="DomainManager"></a>
  190. ## Implementing domain-specific logic
  191. The next step is to implement a [**MappedEntityDomainManager**](http://msdn.microsoft.com/library/dn643300.aspx), which serves as an abstraction layer between our mapped data store and the controller which will serve HTTP traffic from our clients. We will be able to write our controller in the next section purely in terms of the DTOs and the **MappedEntityDomainManager** we add here will handle the communication with the original data store, while also giving us a place to implement any logic specific to it.
  192. 1. Add a **MobileCustomerDomainManager.cs** to the **Models** folder of your project. Paste in the following implementation:
  193. using AutoMapper;
  194. using Microsoft.WindowsAzure.Mobile.Service;
  195. using ShoppingService.DataObjects;
  196. using System.Data.Entity;
  197. using System.Linq;
  198. using System.Net.Http;
  199. using System.Threading.Tasks;
  200. using System.Web.Http;
  201. using System.Web.Http.OData;
  202. namespace ShoppingService.Models
  203. {
  204. public class MobileCustomerDomainManager : MappedEntityDomainManager<MobileCustomer, Customer>
  205. {
  206. private ExistingContext context;
  207. public MobileCustomerDomainManager(ExistingContext context, HttpRequestMessage request, ApiServices services)
  208. : base(context, request, services)
  209. {
  210. Request = request;
  211. this.context = context;
  212. }
  213. public static int GetKey(string mobileCustomerId, DbSet<Customer> customers, HttpRequestMessage request)
  214. {
  215. int customerId = customers
  216. .Where(c => c.Id == mobileCustomerId)
  217. .Select(c => c.CustomerId)
  218. .FirstOrDefault();
  219. if (customerId == 0)
  220. {
  221. throw new HttpResponseException(request.CreateNotFoundResponse());
  222. }
  223. return customerId;
  224. }
  225. protected override T GetKey<T>(string mobileCustomerId)
  226. {
  227. return (T)(object)GetKey(mobileCustomerId, this.context.Customers, this.Request);
  228. }
  229. public override SingleResult<MobileCustomer> Lookup(string mobileCustomerId)
  230. {
  231. int customerId = GetKey<int>(mobileCustomerId);
  232. return LookupEntity(c => c.CustomerId == customerId);
  233. }
  234. public override async Task<MobileCustomer> InsertAsync(MobileCustomer mobileCustomer)
  235. {
  236. return await base.InsertAsync(mobileCustomer);
  237. }
  238. public override async Task<MobileCustomer> UpdateAsync(string mobileCustomerId, Delta<MobileCustomer> patch)
  239. {
  240. int customerId = GetKey<int>(mobileCustomerId);
  241. Customer existingCustomer = await this.Context.Set<Customer>().FindAsync(customerId);
  242. if (existingCustomer == null)
  243. {
  244. throw new HttpResponseException(this.Request.CreateNotFoundResponse());
  245. }
  246. MobileCustomer existingCustomerDTO = Mapper.Map<Customer, MobileCustomer>(existingCustomer);
  247. patch.Patch(existingCustomerDTO);
  248. Mapper.Map<MobileCustomer, Customer>(existingCustomerDTO, existingCustomer);
  249. await this.SubmitChangesAsync();
  250. MobileCustomer updatedCustomerDTO = Mapper.Map<Customer, MobileCustomer>(existingCustomer);
  251. return updatedCustomerDTO;
  252. }
  253. public override async Task<MobileCustomer> ReplaceAsync(string mobileCustomerId, MobileCustomer mobileCustomer)
  254. {
  255. return await base.ReplaceAsync(mobileCustomerId, mobileCustomer);
  256. }
  257. public override async Task<bool> DeleteAsync(string mobileCustomerId)
  258. {
  259. int customerId = GetKey<int>(mobileCustomerId);
  260. return await DeleteItemAsync(customerId);
  261. }
  262. }
  263. }
  264. An important part of this class is the **GetKey** method where we indicate how to locate the ID property of the object in the original data model.
  265. 2. Add a **MobileOrderDomainManager.cs** to the **Models** folder of your project:
  266. using AutoMapper;
  267. using Microsoft.WindowsAzure.Mobile.Service;
  268. using ShoppingService.DataObjects;
  269. using System.Linq;
  270. using System.Net.Http;
  271. using System.Threading.Tasks;
  272. using System.Web.Http;
  273. using System.Web.Http.OData;
  274. namespace ShoppingService.Models
  275. {
  276. public class MobileOrderDomainManager : MappedEntityDomainManager<MobileOrder, Order>
  277. {
  278. private ExistingContext context;
  279. public MobileOrderDomainManager(ExistingContext context, HttpRequestMessage request, ApiServices services)
  280. : base(context, request, services)
  281. {
  282. Request = request;
  283. this.context = context;
  284. }
  285. protected override T GetKey<T>(string mobileOrderId)
  286. {
  287. int orderId = this.context.Orders
  288. .Where(o => o.Id == mobileOrderId)
  289. .Select(o => o.OrderId)
  290. .FirstOrDefault();
  291. if (orderId == 0)
  292. {
  293. throw new HttpResponseException(this.Request.CreateNotFoundResponse());
  294. }
  295. return (T)(object)orderId;
  296. }
  297. public override SingleResult<MobileOrder> Lookup(string mobileOrderId)
  298. {
  299. int orderId = GetKey<int>(mobileOrderId);
  300. return LookupEntity(o => o.OrderId == orderId);
  301. }
  302. private async Task<Customer> VerifyMobileCustomer(string mobileCustomerId, string mobileCustomerName)
  303. {
  304. int customerId = MobileCustomerDomainManager.GetKey(mobileCustomerId, this.context.Customers, this.Request);
  305. Customer customer = await this.context.Customers.FindAsync(customerId);
  306. if (customer == null)
  307. {
  308. throw new HttpResponseException(Request.CreateBadRequestResponse("Customer with name '{0}' was not found", mobileCustomerName));
  309. }
  310. return customer;
  311. }
  312. public override async Task<MobileOrder> InsertAsync(MobileOrder mobileOrder)
  313. {
  314. Customer customer = await VerifyMobileCustomer(mobileOrder.MobileCustomerId, mobileOrder.MobileCustomerName);
  315. mobileOrder.CustomerId = customer.CustomerId;
  316. return await base.InsertAsync(mobileOrder);
  317. }
  318. public override async Task<MobileOrder> UpdateAsync(string mobileOrderId, Delta<MobileOrder> patch)
  319. {
  320. Customer customer = await VerifyMobileCustomer(patch.GetEntity().MobileCustomerId, patch.GetEntity().MobileCustomerName);
  321. int orderId = GetKey<int>(mobileOrderId);
  322. Order existingOrder = await this.Context.Set<Order>().FindAsync(orderId);
  323. if (existingOrder == null)
  324. {
  325. throw new HttpResponseException(this.Request.CreateNotFoundResponse());
  326. }
  327. MobileOrder existingOrderDTO = Mapper.Map<Order, MobileOrder>(existingOrder);
  328. patch.Patch(existingOrderDTO);
  329. Mapper.Map<MobileOrder, Order>(existingOrderDTO, existingOrder);
  330. // This is required to map the right Id for the customer
  331. existingOrder.CustomerId = customer.CustomerId;
  332. await this.SubmitChangesAsync();
  333. MobileOrder updatedOrderDTO = Mapper.Map<Order, MobileOrder>(existingOrder);
  334. return updatedOrderDTO;
  335. }
  336. public override async Task<MobileOrder> ReplaceAsync(string mobileOrderId, MobileOrder mobileOrder)
  337. {
  338. await VerifyMobileCustomer(mobileOrder.MobileCustomerId, mobileOrder.MobileCustomerName);
  339. return await base.ReplaceAsync(mobileOrderId, mobileOrder);
  340. }
  341. public override Task<bool> DeleteAsync(string mobileOrderId)
  342. {
  343. int orderId = GetKey<int>(mobileOrderId);
  344. return DeleteItemAsync(orderId);
  345. }
  346. }
  347. }
  348. In this case the **InsertAsync** and **UpdateAsync** methods are interesting; that's where we enforce the relationship that each **Order** must have a valid associated **Customer**. In **InsertAsync** you'll notice that we populate the **MobileOrder.CustomerId** property, which maps to the **Order.CustomerId** property. We get that value by based looking up the **Customer** with the matching **MobileOrder.MobileCustomerId**. This is because by default the client is only aware of the Mobile Services ID (**MobileOrder.MobileCustomerId**) of the **Customer**, which is different than its actual primary key needed to set the foreign key (**MobileOrder.CustomerId**) from **Order** to **Customer**. This is only used internally within the service to facilitate the insert operation.
  349. We are now ready to create controllers to expose our DTOs to our clients.
  350. <a name="Controller"></a>
  351. ## Implementing a TableController using DTOs
  352. 1. In the **Controllers** folder, add the file **MobileCustomerController.cs**:
  353. using Microsoft.WindowsAzure.Mobile.Service;
  354. using Microsoft.WindowsAzure.Mobile.Service.Security;
  355. using ShoppingService.DataObjects;
  356. using ShoppingService.Models;
  357. using System.Linq;
  358. using System.Threading.Tasks;
  359. using System.Web.Http;
  360. using System.Web.Http.Controllers;
  361. using System.Web.Http.OData;
  362. namespace ShoppingService.Controllers
  363. {
  364. public class MobileCustomerController : TableController<MobileCustomer>
  365. {
  366. protected override void Initialize(HttpControllerContext controllerContext)
  367. {
  368. base.Initialize(controllerContext);
  369. var context = new ExistingContext();
  370. DomainManager = new MobileCustomerDomainManager(context, Request, Services);
  371. }
  372. public IQueryable<MobileCustomer> GetAllMobileCustomers()
  373. {
  374. return Query();
  375. }
  376. public SingleResult<MobileCustomer> GetMobileCustomer(string id)
  377. {
  378. return Lookup(id);
  379. }
  380. [AuthorizeLevel(AuthorizationLevel.Admin)]
  381. protected override Task<MobileCustomer> PatchAsync(string id, Delta<MobileCustomer> patch)
  382. {
  383. return base.UpdateAsync(id, patch);
  384. }
  385. [AuthorizeLevel(AuthorizationLevel.Admin)]
  386. protected override Task<MobileCustomer> PostAsync(MobileCustomer item)
  387. {
  388. return base.InsertAsync(item);
  389. }
  390. [AuthorizeLevel(AuthorizationLevel.Admin)]
  391. protected override Task DeleteAsync(string id)
  392. {
  393. return base.DeleteAsync(id);
  394. }
  395. }
  396. }
  397. You will note the use of the AuthorizeLevel attribute to restrict public access to the Insert/Update/Delete operations on the controller. For the purposes of this scenario, the list of Customers will be read-only, but we will allow the creation of new Orders and associating them with existing customers.
  398. 2. In the **Controllers** folder, add the file **MobileOrderController.cs**:
  399. using Microsoft.WindowsAzure.Mobile.Service;
  400. using ShoppingService.DataObjects;
  401. using ShoppingService.Models;
  402. using System.Linq;
  403. using System.Threading.Tasks;
  404. using System.Web.Http;
  405. using System.Web.Http.Controllers;
  406. using System.Web.Http.Description;
  407. using System.Web.Http.OData;
  408. namespace ShoppingService.Controllers
  409. {
  410. public class MobileOrderController : TableController<MobileOrder>
  411. {
  412. protected override void Initialize(HttpControllerContext controllerContext)
  413. {
  414. base.Initialize(controllerContext);
  415. ExistingContext context = new ExistingContext();
  416. DomainManager = new MobileOrderDomainManager(context, Request, Services);
  417. }
  418. public IQueryable<MobileOrder> GetAllMobileOrders()
  419. {
  420. return Query();
  421. }
  422. public SingleResult<MobileOrder> GetMobileOrder(string id)
  423. {
  424. return Lookup(id);
  425. }
  426. public Task<MobileOrder> PatchMobileOrder(string id, Delta<MobileOrder> patch)
  427. {
  428. return UpdateAsync(id, patch);
  429. }
  430. [ResponseType(typeof(MobileOrder))]
  431. public async Task<IHttpActionResult> PostMobileOrder(MobileOrder item)
  432. {
  433. MobileOrder current = await InsertAsync(item);
  434. return CreatedAtRoute("Tables", new { id = current.Id }, current);
  435. }
  436. public Task DeleteMobileOrder(string id)
  437. {
  438. return DeleteAsync(id);
  439. }
  440. }
  441. }
  442. 3. You are now ready to run your service. Press **F5** and use the test client built into the help page to modify the data.
  443. Please note that both controller implementations make exclusive use of the DTOs **MobileCustomer** and **MobileOrder** and are agnostic of the underlying model. These DTOs are readily serialized to JSON and can be used to exchange data with the Mobile Services client SDK on all platforms. For example, if building a Windows Store app, the corresponding client-side type would look as shown below. The type would be analogous on other client platforms.
  444. using Microsoft.WindowsAzure.MobileServices;
  445. using System;
  446. namespace ShoppingClient
  447. {
  448. public class MobileCustomer
  449. {
  450. public string Id { get; set; }
  451. public string Name { get; set; }
  452. [CreatedAt]
  453. public DateTimeOffset? CreatedAt { get; set; }
  454. [UpdatedAt]
  455. public DateTimeOffset? UpdatedAt { get; set; }
  456. public bool Deleted { get; set; }
  457. [Version]
  458. public string Version { get; set; }
  459. }
  460. }
  461. As a next step, you can now build out the client app to access the service.