This post describes data API app for tutorial Create Azure API App with AAD authentication and web jobs. This app is created with Azure API App template, and used in the scenario without authentication.
Create Azure API app
- Open Azure portal, log in with your Microsoft account.
- Click “+ New” in the left pane, and search from “API app” object. Search blade shows the list of found objects, where the first should be “API app” by Microsoft. Click on it, and click “Create” on the next blade.
- Use the following values for properties of API app:
Property Value App name azureapiappdataapi Subscription Your subscription Resource group Use existent, and choose “Azure_API_app_with_AAD_Auth” App Service plan/Location Click on default name and choose “AzureApiAppPlan” on the next blade Application Insights Keep “Off” setting Let’s note, that app name is used as part of url, so it should be unique. That is why proposed name “azureapiappdataapi” should be extended by unique prefix like your second name, project title, so on.
Click “Create” to create Azure API app. - Dashboard is shown. Click on API app’s name in Resource group tile and go to the blade for Data API app.
- In the middle of the blade chart with name “Http 5xx” is located. Click on the pin in the upper right corner and pin chart to the dashboard.
- Click on “Microsoft Azure” and open dashboard. Drag new chart and put it below SQL database chart. Click “Done customization” and save updated dashboard. Double click on new tile, and open chart blade.
- There are plenty various settings could be set. For example, rename title to “Data API app”, change period to “Past 24 hours”, check such settings as “Http 2xx”, “Http 403”, “Http 404”, “Http Server Errors”, and “Requests”. Click “Save and close”. Dashboard with updated tile is shown.
- Click on API app’s name in Resource group tile and go to overview blade for Data API app.
- Click on the link “Get publish profile”, and save the file with publish profile to disk. It is recommended to save publish profile outside project folder, as it contains sensitive information.
Get publish profile
Data API app project
Solution for this post could be found at GitHub repository. It includes projects from the previous post “Create Azure API App with AAD authentication and web jobs – Data source” and new web project DataApi
which is constructed by the following steps:
- Create new project
ASP.NET Web Application
, name itDataApi
, chooseAzure API App
template, don’t host in the cloud; - Add
EntityFramework
,Ikc5.TypeLibrary
NuGet packages, update rest of added packages to the latest versions; - Turn on SSL in a web application;
- Add
RequiredHttpsAttribute
class that requires the using ofhttps
scheme in requests and updateWebApiConfig
class:// Web API configuration and services config.Filters.Add(new RequireHttpsAttribute());
- Turn on XML documentation file in all build configurations, add XML comments to standard classes.
- Update
SwaggerConfig.cs
with API description and XML documentation file.
As in the previous post Models
project was connected to deployed Azure SQL database, its App.config
file should be copied to the current Models
project.
Let’s update some code and settings in DataApi
project.
- Open
App.config
file inModels
project and copy wholeconnectionStrings
section toWeb.config
file ofDataApi
project.<connectionStrings> <add name="GoodEntities" connectionString="metadata=res://*/Goods.csdl|res://*/Goods.ssdl|res://*/Goods.msl;provider=System.Data.SqlClient;provider connection string="data source=your_server.database.windows.net;initial catalog=your_database;persist security info=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" /> </connectionStrings>
- Open
SwaggerConfig.cs
, and update root url by url of your DataApi at line 30c.RootUrl(req => @"https://azureapiappdataapi.azurewebsites.net/");
Then update contact information at line 44:
.Contact(contact => { contact.Email("Your Email"); contact.Name("Your Name"); contact.Url("Your Contact Number"); });
Data controllers
Data API REST service provides CRUD methods for Ad
and Categories
objects. These methods are grouped to two web API controllers and are described by Swagger
attributes. Attributes are used by Swagger
UI when REST service is tested in a browser and is used as method description when REST service is consumed by other projects. It lists all correct responses with description and, probably, expected return type.
Add controllers
DataApi
project refers toModels
project, so controllers could be easily added. Right click onControlles
folder inSolution Explorer
, choose “Add|Controller…”.- “Add Scaffold” dialog window is shown, and select “Web API 2 Controller with actions, using Entity Framework”. “Add Controller” dialog window is shown.
- Use the following values for properties of controller:
Property Value Model class Category (Models) Data context class GoodEntities (Models) Use async controller actions Check Controller name Keep default name, CategoriesController
- Repeat the last step for
Ad
class:
Property Value Model class Ad (Models) Data context class GoodEntities (Models) Use async controller actions Check Controller name Keep default name, AdsController
Add swagger attributes
Let’s start with CategoriesController
class.
- Open
CategoriesController.cs
file. Add namespace forSwagger
annotations:using Swashbuckle.Swagger.Annotations;
-
Add the following attribute and comment to the method
GetCategories
/// GET: api/Categories /// <summary> /// Return all Categories from data source. /// </summary> /// <returns></returns> [SwaggerResponse(HttpStatusCode.OK, Type = typeof(IQueryable<Category>), Description = "List of Categories was returned")] public IQueryable<Category> GetCategories() { // ... }
-
Remove current attribute
[ResponseType(typeof(Category))]
of the methodGetCategory
and add two attributes and comment/// GET: api/Categories/5 /// <summary> /// Return Category by id. /// </summary> /// <param name="id">id of Category</param> /// <returns></returns> [SwaggerResponse(HttpStatusCode.OK, Type = typeof(Category), Description = "Required Category was found and successfully returned")] [SwaggerResponse(HttpStatusCode.NotFound, description: "Category with provided id was not found")] public async Task<IHttpActionResult> GetCategory(long id) { // ... }
-
Add the following attributes and comment to the method
PutCategory
/// PUT: api/Categories/5 /// <summary> /// Put updated Category. /// </summary> /// <param name="id">id of the Category</param> /// <param name="category">Existent Category</param> /// <returns></returns> [ResponseType(typeof(void))] [SwaggerResponse(HttpStatusCode.NoContent, description: "Category was updated")] [SwaggerResponse(HttpStatusCode.NotFound, description: "Category was already changed")] public async Task<IHttpActionResult> PutCategory(long id, Category category) { // ... }
-
Add the following attribute and comment to the method
PostCategory
/// POST: api/Categories /// <summary> /// Create new or updated existent Category. /// </summary> /// <param name="category">New or existent Category</param> /// <returns></returns> [ResponseType(typeof(Category))] [SwaggerResponse(HttpStatusCode.Created, Type = typeof(Category), Description = "Category was created")] public async Task<IHttpActionResult> PostCategory(Category category) { // ... }
-
Remove current attribute
[ResponseType(typeof(Category))]
of the methodDeleteCategory
and add the following code/// DELETE: api/Categories/5 /// <summary> /// Deletes the Category. /// </summary> /// <param name="id">id of the Category</param> /// <returns></returns> [SwaggerResponse(HttpStatusCode.OK, Type = typeof(Category), Description = "Category was deleted")] [SwaggerResponse(HttpStatusCode.NotFound, description: "Category was already removed")] public async Task<IHttpActionResult> DeleteCategory(long id) { // ... }
There is complete code of the controller
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; using System.Web.Http.Description; using Models; using Swashbuckle.Swagger.Annotations; namespace DataApi.Controllers { /// <summary> /// Controller for categories. Implements standard get-post operations. /// </summary> public class CategoriesController : ApiController { private readonly GoodEntities db = new GoodEntities(); /// GET: api/Categories /// <summary> /// Return all Categories from data source. /// </summary> /// <returns></returns> [SwaggerResponse(HttpStatusCode.OK, Type = typeof(IQueryable<Category>), Description = "List of Categories was returned")] public IQueryable<Category> GetCategories() { return db.Categories; } /// GET: api/Categories/5 /// <summary> /// Return Category by id. /// </summary> /// <param name="id">id of Category</param> /// <returns></returns> [SwaggerResponse(HttpStatusCode.OK, Type = typeof(Category), Description = "Required Category was found and successfully returned")] [SwaggerResponse(HttpStatusCode.NotFound, description: "Category with provided id was not found")] public async Task<IHttpActionResult> GetCategory(long id) { var category = await db.Categories.FindAsync(id); if (category == null) { return NotFound(); } return Ok(category); } /// PUT: api/Categories/5 /// <summary> /// Put updated Category. /// </summary> /// <param name="id">id of the Category</param> /// <param name="category">Existent Category</param> /// <returns></returns> [ResponseType(typeof(void))] [SwaggerResponse(HttpStatusCode.NoContent, description: "Category was updated")] [SwaggerResponse(HttpStatusCode.NotFound, description: "Category was already changed")] public async Task<IHttpActionResult> PutCategory(long id, Category category) { if (!ModelState.IsValid) { return BadRequest(ModelState); } if (id != category.Id) { return BadRequest(); } db.Entry(category).State = EntityState.Modified; try { await db.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!CategoryExists(id)) { return NotFound(); } else { throw; } } return StatusCode(HttpStatusCode.NoContent); } /// POST: api/Categories /// <summary> /// Create new or updated existent Category. /// </summary> /// <param name="category">New or existent Category</param> /// <returns></returns> [ResponseType(typeof(Category))] [SwaggerResponse(HttpStatusCode.Created, Type = typeof(Category), Description = "Category was created")] public async Task<IHttpActionResult> PostCategory(Category category) { if (!ModelState.IsValid) { return BadRequest(ModelState); } db.Categories.Add(category); await db.SaveChangesAsync(); return CreatedAtRoute("DefaultApi", new { id = category.Id }, category); } /// DELETE: api/Categories/5 /// <summary> /// Deletes the Category. /// </summary> /// <param name="id">id of the Category</param> /// <returns></returns> [SwaggerResponse(HttpStatusCode.OK, Type = typeof(Category), Description = "Category was deleted")] [SwaggerResponse(HttpStatusCode.NotFound, description: "Category was already removed")] public async Task<IHttpActionResult> DeleteCategory(long id) { Category category = await db.Categories.FindAsync(id); if (category == null) { return NotFound(); } db.Categories.Remove(category); await db.SaveChangesAsync(); return Ok(category); } /// <summary> /// Implentation of IDispose interface. /// </summary> /// <param name="disposing"></param> protected override void Dispose(bool disposing) { if (disposing) { db.Dispose(); } base.Dispose(disposing); } /// <summary> /// Check that already exists Category with id. /// </summary> /// <param name="id"></param> /// <returns></returns> private bool CategoryExists(long id) { return db.Categories.Count(e => e.Id == id) > 0; } } }
AdsController
should be modified in the similar way, so these changes are not listed here, except GetAds
method. As lazy loading is turned off, it is necessary include Category
in Ad
object.
/// GET: api/Ads /// <summary> /// Return all Ads from data source. /// </summary> /// <returns></returns> [SwaggerResponse(HttpStatusCode.OK, Type = typeof(IQueryable<Ad>), Description = "List of Ads was returned")] public IQueryable<Ad> GetAds() { //return db.Ads; var ads = db.Ads.Include(ad => ad.Category); return ads; }
In addition, new method GetAdsByCategory
is added:
/// GET: api/Ads/categoryId /// <summary> /// Return all Ads from data source. /// </summary> /// <param name="categoryId">Id of category that filter ads.</param> /// <returns></returns> [Route("api/Ads/Category/{categoryId:long}")] [SwaggerResponse(HttpStatusCode.OK, Type = typeof(IQueryable<Ad>), Description = "List of Ads was returned")] public IQueryable<Ad> GetAdsByCategory(long categoryId) { var ads = db.Ads.Include(ad => ad.Category).Where(ad => ad.CategoryId == categoryId); return ads; }
There is complete code of AdsController
controller
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; using System.Web.Http.Description; using Models; using Swashbuckle.Swagger.Annotations; namespace DataApi.Controllers { /// <summary> /// Controller for ads. Implements standard get-post operations. /// </summary> public class AdsController : ApiController { private readonly GoodEntities db = new GoodEntities(); /// GET: api/Ads /// <summary> /// Return all Ads from data source. /// </summary> /// <returns></returns> [SwaggerResponse(HttpStatusCode.OK, Type = typeof(IQueryable<Ad>), Description = "List of Ads was returned")] public IQueryable<Ad> GetAds() { //return db.Ads; var ads = db.Ads.Include(ad => ad.Category); return ads; } /// GET: api/Ads/categoryId /// <summary> /// Return all Ads from data source. /// </summary> /// <param name="categoryId">Id of category that filter ads.</param> /// <returns></returns> [Route("api/Ads/Category/{categoryId:long}")] [SwaggerResponse(HttpStatusCode.OK, Type = typeof(IQueryable<Ad>), Description = "List of Ads was returned")] public IQueryable<Ad> GetAdsByCategory(long categoryId) { var ads = db.Ads.Include(ad => ad.Category).Where(ad => ad.CategoryId == categoryId); return ads; } /// GET: api/Ads/AdId /// <summary> /// Return Ad by id. /// </summary> /// <param name="id">id of Ad</param> /// <returns></returns> [SwaggerResponse(HttpStatusCode.OK, Type = typeof(Ad), Description = "Required Ad was found and successfully returned")] [SwaggerResponse(HttpStatusCode.NotFound, description: "Ad with provided id was not found")] public async Task<IHttpActionResult> GetAd(long id) { var ad = await db.Ads.FindAsync(id); if (ad == null) { return NotFound(); } return Ok(ad); } /// PUT: api/Ads/5 /// <summary> /// Put updated Ad. /// </summary> /// <param name="id">id of the Ad</param> /// <param name="ad">Existent Ad</param> /// <returns></returns> [ResponseType(typeof(void))] [SwaggerResponse(HttpStatusCode.NoContent, description: "Ad was updated")] [SwaggerResponse(HttpStatusCode.NotFound, description: "Ad was already changed")] public async Task<IHttpActionResult> PutAd(long id, Ad ad) { if (!ModelState.IsValid) { return BadRequest(ModelState); } if (id != ad.Id) { return BadRequest(); } db.Entry(ad).State = EntityState.Modified; try { await db.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!AdExists(id)) { return NotFound(); } else { throw; } } return StatusCode(HttpStatusCode.NoContent); } /// POST: api/Ads /// <summary> /// Create new or updated existent Ad. /// </summary> /// <param name="ad">New or existent Ad</param> /// <returns></returns> [ResponseType(typeof(Ad))] [SwaggerResponse(HttpStatusCode.Created, Type = typeof(Ad), Description = "Ad was created")] public async Task<IHttpActionResult> PostAd(Ad ad) { if (!ModelState.IsValid) { return BadRequest(ModelState); } db.Ads.Add(ad); await db.SaveChangesAsync(); return CreatedAtRoute("DefaultApi", new { id = ad.Id }, ad); } /// DELETE: api/Ads/5 /// <summary> /// Deletes the Ad. /// </summary> /// <param name="id">id of the Ad</param> /// <returns></returns> [SwaggerResponse(HttpStatusCode.OK, Type = typeof(Ad), Description = "Ad was deleted")] [SwaggerResponse(HttpStatusCode.NotFound, description: "Ad was already removed")] public async Task<IHttpActionResult> DeleteAd(long id) { var ad = await db.Ads.FindAsync(id); if (ad == null) { return NotFound(); } db.Ads.Remove(ad); await db.SaveChangesAsync(); return Ok(ad); } /// <summary> /// Implentation of IDispose interface. /// </summary> /// <param name="disposing"></param> protected override void Dispose(bool disposing) { if (disposing) { db.Dispose(); } base.Dispose(disposing); } /// <summary> /// Check that already exists Ad with id. /// </summary> /// <param name="id"></param> /// <returns></returns> private bool AdExists(long id) { return db.Ads.Count(e => e.Id == id) > 0; } } }
Deploy Data API app to Azure API app
After controllers are created, DataApi application should be deployed to Azure API app.
- Right click on
DataApi
project and click on “Publish…”. “Publish” window is shown. - Click on “Import”, “Import Publish Settings” dialog is opened. Click “Browse” and select
azureapiappdataapi.PublishSettings
file that was downloaded earlier. This file contains two publishing profiles:azureapiappdataapi - Web Deploy
andazureapiappdataapi - FTP
. Both profiles deploy web application to Azure app, but use different methods.
Click “OK”, “Connection” step is shown. In this post web deploy is used, and it is chosen as default publish method. - Update destination URL to
https://azureapiappdataapi.azurewebsites.net/swagger
and click “Validate Connection”. It should be successful, and click “Next >”. - “Settings” step is shown. Open drop down box below “GoodEntities” and choose item with the name of your Azure database. Otherwise, connection string could be set manually
Data Source=your_server.database.windows.net;Initial Catalog=your_database;Persist Security Info=True;User ID=your_admin;Password=******
Click “Next >”.
- “Preview” step is shown. Click “Start Preview” and take a look on files that would be uploaded to Azure API app. Click “Publish” and wait. Solution is built, and if there are no errors, the publishing would be started. Window “Web Publish Activity” in Visual Studio shows overall status of the deploying and details.
- Page with
Swagger
UI for Azure API App is opened in a browser. It contains description of the service, contact information and the list of controllers. Each controller could be expanded to the list of methods. Each method could be called by clicking “Try it out!”.
Let’s note, that at first time response body could be{ "Message: "An error has occurred." }
In this case try call method once more time. As
DataApi
Azure API app is not set to “Always On”, it is shut down by Azure infrastructure due to inactivity. - Return back to Visual Studio. Window “Web Publish Activity” shows that publish was succeeded. In addition,
DataApi
project now contains new folderPublishProfiles
with two filesazureapiappdataapi - Web Deploy.pubxml
andazureapiappdataapi - FTP.pubxml
.
Let’s note that it is not recommended put these files to source code storage. Password that are used for the deploying is stored separately in the file withprofile_name.pubxml.user
name (are not included to project). But publish profile file keeps database connection strings as it was inputted, i.e. with user name and password.
1. All used IP-addresses, names of servers, workstations, domains, are fictional and are used exclusively as a demonstration only.
2. Information is provided «AS IS».
I have some doubts regarding this application implementation , how could I contact , May I know your email id
Hello,
You could write your questions here.
Is it not possible to run the application before publishing, from the source code, if i build it shows build succeeded, publish suceeded, but could not run the application, in browser
Web application could be run locally (by default, in IIS Express) with (F5) or without (Ctrl+F5) Debug mode. In Debug mode you may set breakpoints inside controller and analyze what is going on. Even more, you may run all applications locally, i.e. web application could send requests to API that is run locally as another web site, or web application could connect to Web API already deployed in Azure. It allows separate errors in deployment/configuration from errors in code.
Hope it helps.
I set only clientapp as startup project and run the application, I am getting error ‘System.FormatException: ‘No valid combination of account information found.’ while running the application, I have created storage account in the portal and followed as mentioned in the blog.
It shows runtime exception in public static void InitializeStorage()
{
// Open storage account using credentials from .cscfg file.
var storageAccount = CloudStorageAccount.Parse(StorageConnectionString);
inside app.config
I could not able to see the result
do we need to run two instances of visualstudio locally , one by setting DataAPI as startup project, another by setting clientApp as startup project, I am new to azure, // Open storage account using credentials from .cscfg file. — do we need to do anything explicitly
>do we need to run two instances of visualstudio locally , one by setting DataAPI as startup project,
>another by setting clientApp as startup project
Yes, you are right. But you need be sure that these projects run on different ports. Also in ClientApp, in web,config, at line 15, correct the following settings: <add key=”DataApiUrl” value=”https://localhost:current_ssl_port/” /> to set correct DataAPI port.
In addition, in web.config value of AzureWebJobsStorage points to special Development storage that is run by Azure Storage Emulator. For details, see https://docs.microsoft.com/en-us/azure/storage/common/storage-use-emulator. It allows run application in “fully” Debug mode without using Azure resources at all.
Otherwise, you may use connection string to created Azure storage.
Hi,
I could not see thumbnail images in the index page , it s shown for some records, why it s so, How do we need to show the thumbnail picture in index as and when it s created.
Hello Illya,
Will be helpful if you reply me back for the query!! Thanks