I'm trying to understand how to work with TableController in Azure Mobile Apps. Here's the sample TodoItemController:
public class TodoItemController : TableController<TodoItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileServiceContext context = new MobileServiceContext();
DomainManager = new EntityDomainManager<TodoItem>(context, Request, Services);
}
// GET tables/TodoItem
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
// GET tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<TodoItem> GetTodoItem(string id)
{
return Lookup(id);
}
// PATCH tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<TodoItem> PatchTodoItem(string id, Delta<TodoItem> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/TodoItem
public async Task<IHttpActionResult> PostTodoItem(TodoItem item)
{
TodoItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteTodoItem(string id)
{
return DeleteAsync(id);
}
}
Ideally, I'd like to avoid passing around whole models like TodoItem to reduce incoming/outgoing bandwidth and limit clients to only what they should care about. If I were to do that, how would offline sync and client-side SDKs be affected?
Is TableController intended for simple CRUD operations as suggested above? Any examples out on the Internet with complex queries?
The Mobile Apps TableController is the basis for an OData based CRUD interface. You will always transmit an entire model (which is based on an EntityData model, so it has four additional fields - version, createdAt, updatedAt and deleted) to the client. However, the client can use an OData search to get a specific set of entities. For more information on OData, check out http://www.odata.org/
In the specific case of Offline Sync and using the Mobile Apps SDK for clients, the client SDK will issue a GET but limit the results to the last update time (which will be zero for the first request and hence will get everything). It will then push up the changes from the client. In certain cases (where the version does not match), it will have to do conflict resolution. Check out "How Offline Sync Works" in their documentation: https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-offline-data-sync-preview/
Related
I have a API that accepts a submission request (that when accepted to return http code 202 Accepted), for some workflow to approve, and then the caller to retrieve the result later.
The URL should be returned on successful submission and I am trying to use AcceptedAtActionResult with Azure Functions, however I do not understand how to use it as they don't have Controllers per my understanding, but this is one of the parameters.
Regardless and trying to use string.Empty, or the class name EnrollmentFunctions, or path enroll, the library is generating strange paths (in this case pointing to a different function in a different file i.e. location: http://localhost:7071/api/attest/c588484e-8e57-47f6-bf6c-973cfa5b9214?action=GetStatus&controller=enroll).
I am looking to return of the form location: http://localhost:7071/api/enroll/c588484e-8e57-47f6-bf6c-973cfa5b9214 (but avoid hardcoding URLs, both for maintenance and also running in dev mode on local machine).
I believe this would get more complicated if we used a cloud WAF (like cloudflare) on the front-end, in that case I presume would create my own class to build the URL (i.e. https://api-protected-by-waf.mydomain.com/enroll/bf920104-a630-4b58-97cb-cc3ae45c31d3)? I can see this then becoming a configuration issue was looking to avoid on the previous para.
Research points to creating my own IUrlHelper and possibly specifying key:values for retrival via Environment.GetEnvironmentVariable("your_key_here") (for use by my IUrlHelper implementation).
Thanks in advance on how to solve this issue or if i am using the libraries incorrectly and alternate best practice. Code of the Azure functions are below:
public class EnrollmentFunctions
{
[FunctionName("Enroll")]
public async Task<IActionResult> Enroll(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "enroll")] HttpRequest req)
{
var id = Guid.NewGuid();
return new AcceptedAtActionResult("GetStatus", "enroll", new { id = id }, id);
// return new AcceptedAtRouteResult("GetStatus", (new { id }, new { Result = id.ToString() }));
}
[FunctionName("GetStatus")]
public async Task<IActionResult> GetStatus(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "enroll/{id}")] HttpRequest req)
{
return new OkObjectResult("OK");
}
}
we keep fighting with out multi tenant application.
This is an ASP MVC EF6 Code First web application.
We initialize a list of tenants in the Application_Start, getting a pair of values:
Host
TenantId
So we can associate any host with one TenantId, and store that list in cache.
We have configured a custom filter to get the current tenant.
public class TenantActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Items.Add("TenantId", GetCurrentTenant(filterContext.HttpContext.Request.Url.Host));
base.OnActionExecuting(filterContext);
}
}
The GetCurrentTenant function just access the list in cache and get the current one based on the host passed.
Is it correct to store the current tenant in an item in the context?
After that, we have created an Interceptor to get any query and add a filter to filter by TenantId. This is done and working good, we just need to add the tenantId from the context:
The problem we have is where we get the TenantId for each request.
if (HttpContext.Current.CurrentHandler == null) return;
var clientId = Convert.ToInt32(HttpContext.Current.Items["ClientId"]);
foreach (DbParameter param in command.Parameters)
{
if (param.ParameterName != TenantAwareAttribute.TenantIdFilterParameterName)
continue;
param.Value = clientId;
}
We don't know if this is the correct approach since there is a lot of informationon the net.
Thanks.
In my experience, the persistence of the tenant Id in the HTTP context is not right, as in some cases, the HTTP context becomes null.
You can try to get the tenant Id from the claims of the current principal. Creating a static class with a tenant identifier property that reads from the claims and gives is more reliable. Assuming you are using the owin pipeline, this should be easy to do. You can take a look at the reference sample application from github here
It looks like the below block,
public static class UserContext
{
public static string TenantId
{
get
{
return Threading.Thread.CurrentPrincipal.FindFirst("tenantid");
}
}
}
i have an oData enabled web api function
[EnableQuery()]
public IQueryable<StoreCommand> Get()
{
return _storeCommandService.GetAllStoreCommands().AsQueryable();
}
the service layer calling Mongodb based Repository pattern's implementation.
public IEnumerable<StoreCommand> GetAllStoreCommands()
{
return _uow.StoreCommands.GetAll();
}
where GetAll is implemented in Repository layer like
public IList<TEntity> GetAll()
{
return _collection.FindAllAs<TEntity>().ToList();
}
where _collection is a MongoCollection of c# driver.
when i make a call like
http://localhost:xxxx/api/storeCommandsrest?$skip=0&$top=10&$orderby=Name
i get top 10 records but it pulls all the records from the DB and send me back top 10.
Please guide how we can pull only the required set from the DB.
Comment moved to answer:
You aren't returning an IQueryable from GetAllStoreCommands(). Your return type must be an IQueryable(). To get that from the driver, it should be _collection.AsQueryable().
When exposing querystring parameters using GET I have the following base URL:
https://school.service.com/api/students
This will return the first 25 students.
What if I want to return a list of students based on ONE of the following criteria:
* have accepted a job
* have received a job offer
* have no job offers
The three above choices are essentially an enum.
Therefore, the query request for students who have no job offers I assume would look like:
https://school.service.com/api/students?jobOfferStatus=3
However, I'm wondering if jobOfferStatus=3 is the proper way to handle this. If so, how would I publish/provide to the clients a list of available options for that jobOfferStatus query parameter? What about other possible query parameters and their valid options? We'll have many possible query parameters like this.
I'd love to see an example of how this should be done properly. What are the best practices?
There are two main options: documenting it, or making it discoverable. A lot of APIs have documentation where they list all of the resources and parameters for reference. Otherwise, the client won't know.
You could also make it discoverable in some way by including the options in a response. For conventions on this, search for HATEOAS if you haven't already. (I'm not really knowledgeable enough about HATEOAS myself to make a suggestion.)
I will mention that "3" is not a very meaningful value for jobOfferStatus, and there's no need for the client to know that number. You can make it anything you want -- jobOfferStatus=none or even jobOffer=none. Your controller can do the work of matching that value to your enumeration. Try to design your interface to be intuitive for developers (and, of course, write good documentation).
To handle multiple query parameters, you can use optional parameters in your function:
public HttpResponseMessage GetStudents(string jobOffer = "",
string other1 = "",
string other2 = "")
{
if (jobOffer == "accepted" && other2 == "whatever") {
// return a response
}
else {
// return a different response
}
}
When the client uses parameters by those names, you can tailor your response appropriately.
You have some options to do this, let's try to help:
1) Configure a generic route to asp.net web api knows how to solve another action's name different from Get to a get method, on the App_Start\WebConfigApi.cs class, try to add this:
config.Routes.MapHttpRoute("DefaultApiWithActionAndId",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
Using it, you can have diferent methods on the api controller:
// request: get
// url: api/Students/GetStudents
public HttpResponseMessage GetStudents()
{
return Request.CreateResponse(...);
}
// request: get
// url: api/Students/GetStudentsWithJobOffer
public HttpResponseMessage GetStudentsWithJobOffer()
{
return Request.CreateResponse(...);
}
// request: get
// url: api/Students/GetStudentsAcceptedJob
public HttpResponseMessage GetStudentsAcceptedJob()
{
return Request.CreateResponse(...);
}
2) Use a simple parameter on the Get method:
// request: get
// url: api/Students?jobOfferStatus=1
public HttpResponseMessage GetStudents(int jobOfferStatus)
{
// use jobOfferStatus parameter to fill some list
return Request.CreateResponse(...);
}
3) Use a simple method with a parameter named id, to get a default friendly url by asp.net mvc web api.
// request: get
// url: api/Students/1
public HttpResponseMessage GetStudents(int id)
{
// use the id parameter to fill some list
return Request.CreateResponse(...);
}
I have written a REST service using Web API and after reading sections of this Web API Design from Brian Mulloy, was trying to figure out how I could implement associations with Web API.
Web API Design Extract:
Associations
Resources almost always have relationships to other
resources. What's a simple way to express these relationships in
aWebAPI?
Let's look again at the API we modeled in nouns are good,
verbs are bad -theAPI that interacts with our dogs resource.
Remember, we had two base URLs: /dogs and dogs/1234.
We're using HTTP
verbs to operate on the resources and collections. Our dogs belong to
owners. To get all the dogs belonging to a specific owner, or to
create a new dog for that owner, do a GET or a POST:
GET /owners/5678/dogs
POST /owners/5678/dogs
Now, the relationships can be
complex. Owners have relationships with veterinarians, who have
relationships with dogs, who have relationships with food, and so on.
It's not uncommon to see people string these together making a URL 5
or 6 levels deep. Remember that once you have the primary key for one
level, you usually don't need to include the levels above because
you've already got your specific object. In other words, you shouldn't
need too many cases where a URL is deeper than what we have above
/resource/identifier/resource.
So I tried to add a controller method for the association like follows:
public class EventsController : ApiController
{
// GET api/events
public IEnumerable<Event> Get()
{
// get list code
}
// GET api/events/5
public Event Get(int id)
{
// get code
}
// POST api/events
public void Post([FromBody]Event evnt)
{
// add code
}
// POST api/events/5
public void Post(int id, [FromBody]Event evnt)
{
// update code
}
// DELETE api/events/5
public void Delete(int id)
{
// delete code
}
// GET api/events/5/guests
public IEnumerable<Guest> Guests(int id)
{
// association code
}
}
I also modified my route templates to the following:
config.Routes.MapHttpRoute("ApiWithAssociations",
"api/{controller}/{id}/{action}");
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
Unfortunately, when I do an update/post of the event resource I now get a HTTP 500 Internal Server Error with a response body stating
Multiple actions were found that match the request
I've tried modifying the route templates in conjunction with adding System.Web.Http.HttpPostAttribute (and other HTTP verbs) as well but to no avail.
Has anyone tried this and got it working? Any help would be appreciated. If it is absolutely not possible to have multiples for an http verb then I guess I'll have to abandon associations with my REST service.
EDIT: SOLUTION
Using Radim Köhler's answer, I was able to get this working. Add the HttpGetAttribute to the Guests method like so:
// GET api/event/5/guests
[HttpGet]
public IEnumerable<Guest> Guests(int id)
{
// association code
}
And added an addition route to cater for the default GET action like follows:
config.Routes.MapHttpRoute("DefaultGet",
"api/{controller}/{id}",
new {action = "Get"},
new {httpMethod = new HttpMethodConstraint(HttpMethod.Get)});
config.Routes.MapHttpRoute("ApiWithAssociations",
"api/{controller}/{id}/{action}");
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
new {id = RouteParameter.Optional});
The solution, could be in an explicit POST mapping
Just add new definition, which will be used for events/5 POST
// explicit Post() mapping
config.Routes.MapHttpRoute(
name: "DefaultPost",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "Post" }
, constraints: new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) }
);
// existing
config.Routes.MapHttpRoute("ApiWithAssociations",
"api/{controller}/{id}/{action}");
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });