Hi,
I had the opportunity to present during our main event The Techdays, in France, a new feature in Windows Azure Mobile Services : The ability to create a C# backend within Mobile Services, while my colleague Benjamin Talmard presented an other new feature : The Azure Directory authentication integration within Mobile Services.
A few days later, Scott Guthrie made the announcement of those features (and some other)
Today we will focus deeper in the development process of a complete C# backend.
For purpose, here is some useful links :
- Scott Guthrie : http://weblogs.asp.net/scottgu/archive/2014/02/20/azure-expressroute-dedicated-networking-web-site-backup-restore-mobile-services-net-support-hadoop-2-2-and-more.aspx
- MSDN : http://www.windowsazure.com/en-us/documentation/articles/mobile-services-dotnet-backend-windows-store-dotnet-get-started/
- Tutorial : http://www.windowsazure.com/en-us/documentation/articles/mobile-services-windows-store-get-started/
You will find the complete source code in a zip package here : FabrikamFiberArticle.zip
Backend creation and deployment
This section is largely treated in the MSDN tutorials, so we will focus on the development part.
The C# backend project
Here is my final project :
- WebApiConfig : Web API configuration of our Backend. You will find the creation and initialization of the database.
- ServiceTicketController et ImageController : Table controller. We can compare them to the data components in Mobile Services node.js
- CustomerController : It is an ApiController : Comparable to the API services in Mobile Services node.js
- FabrikamFiberContext : Database context, Code First item.
- Customer, Image, ServiceTicket : Represents our data models.
We will go deeper within the two main controllers : TableController and ApiController.
TableController
All the classes inheriting TableController<T> are in charge of interacting with your HTTP requests and manage your tables
If we made a simple comparison, we can say that TableController of C# backend is equivalent to the data’s script in Mobiles Service node.js :
TableController contains three essentials elements :
- FabrikamFiberContext : It’s the DbContext, required to query our datas.
- EntityDomainManager : It’s an helper implementing IDomainManager. The domain managers are primarily intended for mapping the table controller CRUD to a backend.
- Services : ApiServices class in charge of various operations like Log, Push, accessing configuration ….
FabrikamFiberContext context = new FabrikamFiberContext(Services.Settings.Name.Replace('-', '_')); DomainManager = new EntityDomainManager<ServiceTicket>(context, Request, Services);
Every calls in the TableController are nomenclatured to intercept HTTP requests :
// GET tables/ServiceTicketpublic IQueryable<ServiceTicket> GetAllServiceTickets() {return Query(); }// GET tables/ServiceTicket/48D68C86-6EA6-4C25-AA33-223FC9A27959public SingleResult<ServiceTicket> GetServiceTicket(string id) {return Lookup(id); }// PATCH tables/ServiceTicket/48D68C86-6EA6-4C25-AA33-223FC9A27959public Task<ServiceTicket> PatchServiceTicket(string id, Delta<ServiceTicket> patch) {return UpdateAsync(id, patch); }// POST tables/ServiceTicket/48D68C86-6EA6-4C25-AA33-223FC9A27959public async Task<IHttpActionResult> PostServiceTicket(ServiceTicket item) { ServiceTicket current = await InsertAsync(item);return CreatedAtRoute("Tables", new { id = current.Id }, current); }// DELETE tables/ServiceTicket/48D68C86-6EA6-4C25-AA33-223FC9A27959public Task DeleteServiceTicket(string id) {return DeleteAsync(id); }
IDomainManager
Each TableController has its own domain manager (IDomainManager)
To make it simple, we can say :
- TableController : Maintainer of objects structure (ITableData), Intercepts HTTP requests, contains services like ApiServices…
- IDomainManager : Map and implement all the table controller CRUD operations from within your backend database.
publicinterface IDomainManager<TData> where TData : class, ITableData { Task<bool> DeleteAsync(string id); Task<TData> InsertAsync(TData data); SingleResult<TData> Lookup(string id); Task<SingleResult<TData>> LookupAsync(string id); IQueryable<TData> Query(); Task<IEnumerable<TData>> QueryAsync(ODataQueryOptions query); Task<TData> ReplaceAsync(string id, TData data); Task<TData> UpdateAsync(string id, Delta<TData> patch); }
We can imagine to create other kind of domain manager like MongoDB domain manager, or maybe Table storage domain manager or even a SQLite domain manager …
For now, you have access to two domain managers implementing IDomainManager :
- EntityDomainManager : Standard domain manager to manage SQL Azure tables
- MappedEntityDomainManager : In charge to manage SQL tables with entities not directly mapped to the tables structure.
ApiController
All the controllers inheriting from ApiController can be compare to the API part in Mobile Services node.js :
You have all the flexibility with ApiController to implement your own services. You can create methods (intercepting POST, GET and others operations) and create custom routes.
To work with ApiController, you don’t need any domain manager, just your own DbContext.
You could (but it’s not mandatory) use RoutePrefix and Route to customize your urls :
[RoutePrefix("api/Customers")]publicclass CustomerController : ApiController {public ApiServices ApiServices { get; set; } FabrikamFiberContext context;protectedoverridevoid Initialize(HttpControllerContext controllerContext) {base.Initialize(controllerContext); context = new FabrikamFiberContext(ApiServices.Settings.Name.Replace('-', '_')); } [RequiresAuthorization(AuthorizationLevel.Application)] publicvoid Get() { ApiServices.Log.Error("Trying access API with GET ");this.Request.CreateBadRequestResponse("Get Customers not allowed. Try /all "); } [Route("all")] [RequiresAuthorization(AuthorizationLevel.Application)]public IQueryable<Customer> GetAll() {return context.Customers; } }
In this sample, I explicitly forbid the use of GET request, and send back a bad request response.
Here is a full example of a merge method :
[Route("merge")] [RequiresAuthorization(AuthorizationLevel.Application)]public Customer MergeCustomer(Delta<Customer> patch) { Customer current;// Get partial entity and the Id var tmp = patch.GetEntity();if (tmp == null) { ApiServices.Log.Error("Trying Merge customer is in error : Entity not valid ");thrownew HttpResponseException (this.Request.CreateBadRequestResponse("Entity is not valid")); } var customerId = tmp.Id; if (string.IsNullOrEmpty(customerId)) {// get entity current = patch.GetEntity();// Insert new customerif (String.IsNullOrEmpty(current.Id) || current.Id == Guid.Empty.ToString()) current.Id = Guid.NewGuid().ToString(); context.Customers.Add(current); ApiServices.Log.Info("Customer created with Id : " + current.Id.ToString()); }else { current = context.Customers.Find(new[] { customerId });if (current == null) {// insert customer context.Customers.Add(current); ApiServices.Log.Info("Customer created with Id : " + current.Id.ToString()); }else {// update original patch.Patch(current); ApiServices.Log.Info("Customer updated with Id : " + current.Id.ToString()); } } // Check propertiesif (String.IsNullOrEmpty(current.FirstName) || string.IsNullOrEmpty(current.LastName)) { ApiServices.Log.Warn("FirstName and LastName are mandatory for merging a customer");thrownew HttpResponseException (this.Request.CreateBadRequestResponse("FirstName and LastName are mandatory")); } // save entity context.SaveChanges();return current; }
Note :
- Delta<T> : Is in charge to provide a partial entity. Useful when you send a request with only the properties to update.
- Delta<T>.GetEntity() : In charge to get the complete entity from within the Delta<T> object (all the properties not provided are set to default)
- Delta<T>.Patch(item) : In charge to merge an existing entity and the Delta<T> object. Each modified property are set to Modified state. So usefule for update.
- ApiServices.Log() : Logging all the actions.
You will find all the code in the source code provide (file CustomerController.cs)
Quick tips !
Get user informations
Whether from a TableController or from an ApiController, get the user information is pretty straightforward.
The tip is to make a direct cast of the User property to ServiceUser :
ServiceUser user = (ServiceUser)this.User;
var level = user.Level;
var identities = user.Identities;
Get a personalized entity :
We can imagine to send an entity which we don’t know the structure.
To create and send back such entity, we can rely on the JObject object from the JSON.NET Library.
The JObject can describe the properties and with the help of a JsonSerializerSettings, we can send back a personalized structure.
Here is an example to send back the user details :
private JObject GetUserDetails() { ServiceUser user = (ServiceUser)this.User; JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() { DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, ContractResolver = new CamelCasePropertyNamesContractResolver() };string json = JsonConvert.SerializeObject(user.Identities, Formatting.None, jsonSerializerSettings); JArray identities = JArray.Parse(json);returnnew JObject { { "id", user.Id }, { "level", user.Level.ToString() }, { "identities", identities } }; }
Get a personalized collections
This time, it’s the JArray object that will help us to get a full collections of personalized entities :
[Route("fields")] [RequiresAuthorization(AuthorizationLevel.Application)]public JArray GetCustomerFields() { JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() { DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, ContractResolver = new CamelCasePropertyNamesContractResolver() }; var customers = from c in context.Customers select new { Id = c.Id, LastName = c.LastName, FirstName = c.FirstName };string json = JsonConvert.SerializeObject(customers, Formatting.None, jsonSerializerSettings); JArray customersArray = JArray.Parse(json);return customersArray; }
Client Windows 8.1 / Windows Phone 8
From the client side, the code is exactly the same code, whether from a node.js backend or from a C# backend
You will find the complete code in the DataService class in the sample provided within this article.
MobileService object initialization :
// This MobileServiceClient has been configured to communicate with your local// test project for debugging purposes. Comment out this declaration and use the one// provided below when you are done developing and deploy your service to the cloud. MobileService = new MobileServiceClient("http://localhost.fiddler:59217");// This MobileServiceClient has been configured to communicate with your Mobile Service's url// and application key. You're all set to start working with your Mobile Service!// public static MobileServiceClient MobileService = new MobileServiceClient(//MobileService = new MobileServiceClient(// "https://fabrikamfiber.azure-mobile.net/",// "QMizgjEBYjDOW………….eCLLoqMralUv88"// );
Get information from a table (TableController)
ticketTable = Context.Current.MobileService.GetTable<ServiceTicket>();return await ticketTable.LookupAsync(id);
Get informations from a specialized service (ApiController)
var c = await Context.Current .MobileService .InvokeApiAsync<List<Customer>>("Customers/all", HttpMethod.Get, null);
Happy coding !