Create Azure API App with AAD authentication and web jobs – Data API app without authentication

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

  1. Open Azure portal, log in with your Microsoft account.
  2. 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.
  3. 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.

  4. Dashboard is shown. Click on API app’s name in Resource group tile and go to the blade for Data API app.
  5. 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.
  6. 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.
  7. 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.
  8. Click on API app’s name in Resource group tile and go to overview blade for Data API app.
  9. 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.

    API App (16)
    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:

  1. Create new project ASP.NET Web Application, name it DataApi, choose Azure API App template, don’t host in the cloud;
  2. Add EntityFramework, Ikc5.TypeLibrary NuGet packages, update rest of added packages to the latest versions;
  3. Turn on SSL in a web application;
  4. Add RequiredHttpsAttribute class that requires the using of https scheme in requests and update WebApiConfig class:
    // Web API configuration and services
    config.Filters.Add(new RequireHttpsAttribute());
    
  5. Turn on XML documentation file in all build configurations, add XML comments to standard classes.
  6. 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.

  1. Open App.config file in Models project and copy whole connectionStrings section to Web.config file of DataApi 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>
    
  2. Open SwaggerConfig.cs, and update root url by url of your DataApi at line 30
    c.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

  1. DataApi project refers to Models project, so controllers could be easily added. Right click on Controlles folder in Solution Explorer, choose “Add|Controller…”.
  2. “Add Scaffold” dialog window is shown, and select “Web API 2 Controller with actions, using Entity Framework”. “Add Controller” dialog window is shown.
  3. 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
  4. 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.

  1. Open CategoriesController.cs file. Add namespace for Swagger annotations:
    using Swashbuckle.Swagger.Annotations;
    
  2. 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()
    {
    	// ...
    }
    
  3. Remove current attribute [ResponseType(typeof(Category))] of the method GetCategory 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)
    {
    	// ...
    }
    
  4. 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)
    {
    	// ...
    }
    
  5. 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)
    {
    	// ...
    }
    
  6. Remove current attribute [ResponseType(typeof(Category))] of the method DeleteCategory 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.

  1. Right click on DataApi project and click on “Publish…”. “Publish” window is shown.
  2. 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 and azureapiappdataapi - 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.
  3. Update destination URL to https://azureapiappdataapi.azurewebsites.net/swagger and click “Validate Connection”. It should be successful, and click “Next >”.
  4. “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 >”.

  5. “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.
  6. 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.

  7. Return back to Visual Studio. Window “Web Publish Activity” shows that publish was succeeded. In addition, DataApi project now contains new folder PublishProfiles with two files azureapiappdataapi - Web Deploy.pubxml and azureapiappdataapi - 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 with profile_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».

9 thoughts on “Create Azure API App with AAD authentication and web jobs – Data API app without authentication

  1. 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

    1. 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.

  2. 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

  3. 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

    1. >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.

  4. 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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.