Windows Azure Mobiles Services C# Backend (EN version)
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 : https://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 : https://www.windowsazure.com/en-us/documentation/articles/mobile-services-dotnet-backend-windows-store-dotnet-get-started/
- Tutorial : https://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/ServiceTicket
public IQueryable<ServiceTicket> GetAllServiceTickets()
{
return Query();
}
// GET tables/ServiceTicket/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<ServiceTicket> GetServiceTicket(string id)
{
return Lookup(id);
}
// PATCH tables/ServiceTicket/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<ServiceTicket> PatchServiceTicket(string id, Delta<ServiceTicket> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/ServiceTicket/48D68C86-6EA6-4C25-AA33-223FC9A27959
public 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-223FC9A27959
public 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.
public interface 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")]
public class CustomerController : ApiController
{
public ApiServices ApiServices { get; set; }
FabrikamFiberContext context;
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
context = new FabrikamFiberContext(ApiServices.Settings.Name.Replace('-', '_'));
}
[RequiresAuthorization(AuthorizationLevel.Application)]
public void 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 ");
throw new HttpResponseException
(this.Request.CreateBadRequestResponse("Entity is not valid"));
}
var customerId = tmp.Id;
if (string.IsNullOrEmpty(customerId))
{
// get entity
current = patch.GetEntity();
// Insert new customer
if (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 properties
if (String.IsNullOrEmpty(current.FirstName) || string.IsNullOrEmpty(current.LastName))
{
ApiServices.Log.Warn("FirstName and LastName are mandatory for merging a customer");
throw new 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);
return new 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("https://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 !
https://www.dotmim.com/SiteFiles/FabrikamFiberArticle.zip
Comments
Anonymous
February 28, 2014
The zip file appears corrupted!Anonymous
February 28, 2014
Oh ! I'm sorry, you're right. It's repaired now :) SébastienAnonymous
March 01, 2014
Exactly I've been waiting for.Anonymous
March 26, 2014
Thanks for the great article, really useful. I am looking at creating a Mobile Service project with a mixture of TableControllers and ApiControllers. However for authentication I don't want to use social Identity Providers and instead would like to use a username/password they can create. Is this something that I can do with a .Net backend Mobile Service? I looked at adding ASP.NET Identity but this seemed to be a non starter, do you have any thoughts on the best way to approach this? My other option is to just use Web Api 2 (with Individual Authentication) but: a) I couldn't see how to create a Web Api project outside of a MVC Project (I don't need a website) b) I wasn't sure how it scales compared to the instance scaling features of Mobile Services. Thanks again!Anonymous
May 03, 2014
Hi Sebastien, thank you for this great write-up :) I have a simple question: Is it possible to show custom action-names instead of only GET, POST and such within the documentation? And can I use custom actions like /mothership/CheckDBConnection instead of /mothership/GetCheckDBConnection ?