MVC5 Integration

Table of Contents

Introduction

This tutorial will teach you the basics of embedding Izenda into an ASP.NET MVC 5 web application using Visual Studio 2015.

Download the completed project. This is important! You’ll need to use files and code samples from this codebase throughout the guide.

This starter kit will guide you through the implementation and deployment of the full integration mode, this means both front-end (UI) and back-end (API Service) are integrated into one MVC 5 application.

See more details for Deployment Mode 1: Back End Standalone, Front End Integrated.

Prerequisites

To complete this walkthrough, you will need the following:

  • Visual Studio 2015
  • .NET Version 4.5.2
  • Izenda API package
  • Izenda Embedded UI package
  • Sql Server 2016

The Izenda API service is built on .NET framework 4.0, you can build your integrated application on .NET Framework 4.0 or higher, this document will guide you step by step on integrating Izenda into your application. Please note this guide was developed using the versions of each component specified above. For more details on supported components please see other sections in the Izenda documentation.

Create Your MVC 5 Starter Kit Application

Open Visual Studio IDE then select menu New > New Project

../_images/mvc_new_project.png

New ASP.NET Web Application

On New Project dialog, select Templates > Visual C# > Web then select target .NET Framework and ASP.NET Web Application.

Name your project “Mvc5StarterKit” then click OK. This is important! It will affect reference names throughout the code base.

In the New ASP.NET Project dialog, click MVC and click Change Authentication then chose Individual User Accounts, click OK buttons to close dialogs.

../_images/mvc_new_project_template.png

MVC Template

The default template of MVC 5 is created, you will use this template to implement Izenda integrated application from now.

Copying Izenda Resources and References

In Solution Explorer of Mvc5StarterKit project, add new folders below:

  • IzendaBoundary
  • IzendaReferences
  • IzendaResources
  • Scripts\izenda

Copy Izenda Resource to starter kit project:

  • Download and copy izendadb.config file into ~\Mvc5StarterKit
  • Download latest Izenda API package (API.zip) extract zip file and then:
    • Copy all sub folders and files in ~\API\bin to ~ \Mvc5StarterKit\IzendaReferences
    • Copy 3 folders API\Content, API\EmailTemplates and API\Export to ~ \Mvc5StarterKit\IzendaResources
  • Download latest Izenda Embedded UI package (EmbeddedUI.zip), extract zip file then copy all sub folders and files to ~\Mvc5StarterKit\Scripts\izenda
  • Download izenda.integrate.js, izenda.utils.js and alertify.js then copy to ~\Mvc5StarterKit\Scripts
  • Download alertify.css and copy to ~\Mvc5StarterKit\Content

Configuring the Project Build

Add Izenda DLLs reference and other dependencies

  1. Right click Reference node in Mvc5StarterKit Solution Explorer then select Add Reference…

    ../_images/mvc_add_reference.png

    Add reference

  2. On Reference Manager dialog click Browse… button then browse to folder ~\Mvc5StarterKit\IzendaReferences, select all DLL files to reference but ignore below dlls (because those dlls have already been referenced in MVC 5 project template by default):

    • Microsoft.Web.Infrastructure.dll
    • Newtonsoft.Json.dll
    • System.Net.Http.dll
    • System.Web.Razor.dll

    Note

    In this step if you encounter errors regarding reference conflicts, you must configure Redirecting Assembly as required or contact Izenda Support.

  3. You also need to add System.ComponentModel.Composition.
    Go to Add References again, Assemblies-> Framework and check System.ComponentModel.Composition.

  4. Finally, go to Tools->NuGet Packet Manager->Package Manager Console and run the following:

    Install-Package Microsoft.AspNet.WebApi.Client
    

Adding post build events to copy the resources

Adding post build events to copy the resources Open Project Properties page select Build Events then click Edit Post-build… button and then enter the commands below into Post-build event command line box:

XCOPY /S /I /Y  "$(ProjectDir)IzendaResources\Content" "$(ProjectDir)\bin\Content\"
XCOPY /S /I /Y  "$(ProjectDir)IzendaResources\EmailTemplates" "$(ProjectDir)\bin\EmailTemplates\"
XCOPY /S /I /Y  "$(ProjectDir)IzendaResources\Export" "$(ProjectDir)\bin\Export\"
../_images/mvc_post_build_events.png

Copy Resources on Post build event

Configuring the Script and Style Bundle

Open ~\Mvc5StarterKit\App_Start\BundleConfig.cs then config for Izenda as below:

            bundles.Add(new StyleBundle("~/Content/css").Include(
                      "~/Content/bootstrap.css",
                      "~/Content/site.css"));

            // #izenda
            bundles.Add(new ScriptBundle("~/bundles/izenda").Include(
                       "~/Scripts/izenda/izenda_common.js",
                       "~/Scripts/izenda/izenda_locales.js",
                       "~/Scripts/izenda/izenda_vendors.js",
                       "~/Scripts/izenda/izenda_ui.js",
                       "~/Scripts/izenda.integrate.js",
                       "~/Scripts/izenda.utils.js"));

            bundles.Add(new StyleBundle("~/Content/css").Include(
                      "~/Content/bootstrap.css",
                      "~/Content/alertify.css", // #izenda
                      "~/Content/site.css"));
        }
    }
}

View Code Here

Open ~\Views\Shared\_Layout.cshtml and add bundle script:

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @* #izenda *@
    <script src="~/Scripts/alertify.js"></script>
    @Scripts.Render("~/bundles/izenda")

Configuring the Routes

Open ~\Mvc5StarterKit\App_Start\RouteConfig.cs then modify the routing config as shown below:

    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.MapMvcAttributeRoutes();

            routes.MapRoute(
                name: "ReportPart",
                url: "viewer/reportpart/{id}",
                defaults: new { controller = "Home", action = "ReportPart" }
            );

            routes.MapRoute(
                name: "ReportViewer",
                url: "report/view/{id}",
                defaults: new { controller = "Report", action = "ReportViewer" }
            );

            routes.MapRoute(
                name: "DashboardViewer",
                url: "dashboard/view/{id}",
                defaults: new { controller = "Dashboard", action = "DashboardViewer" }
            );

            routes.MapRoute(
               name: "CustomAuth",
               url: "api/user/login",
               defaults: new { controller = "Home", action = "CustomAuth" }
           );

            routes.IgnoreRoute("api/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

View Code Here

The statement routes.IgnoreRoute("api/{*pathInfo}") is important, do not miss it. This route will instruct MVC routine ignore any request API which is begin with api/ prefix (instead of response as Controller and View request, it will treat as API Service request), and this prefix must be same with value of Izenda API prefix setting <add key="izendaapiprefix" value="api" /> that you will config in section Izenda API Service Hosting Config later.

Some of the routing configuration may seem strange, but they will be explained later. The table below details the purpose of each URL route:

Name URL Description
ReportPart viewer/reportpart/{id} This route is used by Izenda BE Service to capture html content of report party type chart, gauge or map. Mostly used for report exporting function
ReportViewer report/view/{id} This route is used by Izenda BE Service to capture report content in html format. It is using in exporting report content to embedded html and send it to email (send to email directly or send by report subscription function)
DashboardViewer dashboard/view/{id} The route used by Izenda BE Service to capture dashboard content in html format. Using for exporting dashboard content to email.
CustomAuth api/user/login The route used by the CopyConsole tool to login to your integrated Izenda API for executing Copy Management feature.

Setting up the Database

In this starter kit, we use SQL Server for the database server. Izenda supports many database engines such as: [MSSQL] SQL Server, [AZSQL] AzureSQL, [MYSQL] MySQL, [ORACL] Oracle and [PGSQL] PostgreSQL. The steps to setting up these databases are similar.

Create Izenda DB

On your SQL Server create an empty database named IzendaMvc. This database stores Izenda data (report definitions, dashboards, etc.) and the configuration necessary to run Izenda.

Download IzendaMvc.sql then execute on IzendaMvc database to generate the schema and default data.

Updating the Izenda DB

The IzendaMvc.sql script will generate a configuration database for Izenda 1.24.4.

If you use an EmbeddedUI and API for a later version of Izenda you will need to run update scripts against your IzendaMvc database.

Upgrade scripts can be compiled and downloaded using the Schema Migration Assistant . Here, you will specify your current version (1.24.4) and your target version for upgrade and download the resulting SQL script.

Create Authentication DB

On your SQL Server create an empty database named Mvc5StarterKit. This database is used for storing user authentication information. In your real integrated application, it can be replaced by your system database with your custom user credential information.

Download Mvc5StarterKit.sql and execute on Mvc5StarterKit database to create schema and default user authentication settings.

Verifying Izenda DB and Authentication DB

After creating the IzendaDB, select all rows in the IzendaSystemSetting table and verify that the values are the same as below:

Name Value Description
DeploymentMode 3 The setting for deployment mode, in this starter kit it is fully integrated.
WebUrl http://localhost:14809/ The setting value must be base address of your front-end web page url.

Next you must ensure the UserName value of records in AspNetUsers table in Mvc5StarterKit DB must be matched with UserName value in IzendaUser table of IzendaMvc DB.

Configuring the Izenda API Service

The full configuration file Web.config of Izenda API Service is available on GitHub.

View Code Here

Izenda API Service Hosting Config

Izenda uses the Nancy Framework to host the REST API service. To configure Nancy, open the Mvc5StarterKit\Web.config and add a new section named name=”nancyFx” like below:

<configuration>
  <configSections>
    <!-- Izenda-->
    <section name="nancyFx" type="Nancy.Hosting.Aspnet.NancyFxSection" />
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a" />
    <!-- Izenda - End -->

    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>

  <!--Izenda-->
  <nancyFx>
    <bootstrapper assembly="Izenda.BI.API" type="Izenda.BI.API.Bootstrappers.IzendaBootstraper" />
  </nancyFx>

Then add the following email config after </nancyFx> close tag:

  <!--Izenda-->
  <nancyFx>
    <bootstrapper assembly="Izenda.BI.API" type="Izenda.BI.API.Bootstrappers.IzendaBootstraper" />
  </nancyFx>

  <system.net>
    <mailSettings>
      <smtp deliveryMethod="Network" from="">
        <network defaultCredentials="false" host="" enableSsl="true" port="587" userName="" password="" />
      </smtp>
    </mailSettings>
  </system.net>
  <!-- Izenda End -->

Add below Izenda setting key into <appSettings> node:

  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <!--Izenda-->
    <add key="izendaapiprefix" value="api" />
    <add key="izedapassphrase" value="vqL7SF+9c9FIQEKUOhSZapacQgUQh" />
    <add key="IzendaApiUrl" value="http://localhost:14809/api/" />
    <add key="izusername" value="IzendaAdmin@system.com" />
    <add key="iztenantname" value="" />
    <!--Izenda End-->
  </appSettings>
Key Name Value Description
izendaapiprefix api This is prefix of your Izenda API service, it is used for distinguish the Izenda API with other api in your application if it expose more than one service endpoint.
izendapassphrase vqL7SF+9c9FIQEKUOhSZapacQgUQh The key used for encryption
IzendaApiUrl http://localhost:14809/api/ Your Izenda API endpoint, used to call the Izenda API in your .NET code
izusername IzendaAdmin@system.com Default system level account, in this kit it is used for authentication of specific functions like adding new users, updating the data model, etc. This value must match with the Username in Mvc5StarterKit.AspNetUsers table and IzendaMvc.IzendaUser table
iztenantname   The tenant of system level account, have same purpose with izusername

In <system.webServer> config node, add below config:

  <system.webServer>
    <modules>
      <remove name="FormsAuthentication" />
    </modules>
    
    <!--Izenda-->
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Accept, Origin, Content-Type, access_token, current_tenant, selected_tenant" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
      </customHeaders>
    </httpProtocol>

    <handlers>
      <add name="Nancy" verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" path="api/*" />
    </handlers>
    <httpErrors existingResponse="PassThrough" />
    <!--Izenda end-->
  </system.webServer>

This config includes the http header attribute and api action verb for Izenda API. It is also used to config http handler for the API url convention which will be reserved by Izenda API Service. In config above all API urls with a prefix of api/ will be handled by Izenda, note that this handler API prefix must match value of izendaapiprefix config.

Logging Config

In <configSections> add a new section named name=”log4net” to config logging for Izenda API.

    <!-- Izenda-->
    <section name="nancyFx" type="Nancy.Hosting.Aspnet.NancyFxSection" />
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a" />
    <!-- Izenda - End -->

Then add log4net node below:

  <log4net threshold="ALL" debug="false">
    <root>
      <appender-ref ref="OutputDebugStringAppender" />
      <appender-ref ref="RollingFileAppender" />
    </root>

    <logger name="Mvc5StarterKit" additivity="false">
      <level value="ALL" />
      <appender-ref ref="Mvc5StarterKit" />
    </logger>

    <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender,log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a">
      <filter type="log4net.Filter.LevelRangeFilter, log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a">
        <levelMin value="INFO" />
      </filter>
      <file value="logs\izenda-log.log" />
      <appendToFile value="true" />
      <rollingStyle value="Composite" />
      <datePattern value="yyyyMMdd" />
      <staticLogFileName value="true" />
      <preserveLogFileNameExtension value="true" />
      <maximumFileSize value="5MB" />
      <maxSizeRollBackups value="1000" />
      <layout type="log4net.Layout.PatternLayout,log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a">
        <param name="ConversionPattern" value=" %date [%-5thread][%-5level][%-36logger{1}] %message %newline" />
      </layout>
    </appender>

    <appender name="Mvc5StarterKit" type="log4net.Appender.FileAppender">
      <file value="logs\mvc5kit-log.log" />
      <appendToFile value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%-5level] %message%newline" />
      </layout>
    </appender>

    <appender name="OutputDebugStringAppender" type="log4net.Appender.OutputDebugStringAppender,log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a">
      <layout type="log4net.Layout.PatternLayout,log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a">
        <param name="ConversionPattern" value="[%-5level][%-24logger{1}/%line] %message (/T:%thread /D:%date) %newline" />
      </layout>
    </appender>
  </log4net>

Configuring Database Connection

Open ~\ Mvc5StarterKit\izendadb.config then update the connection string to your IzendaMvc DB which was created in the steps above. For example with [MSSQL] SQLServer:

{"ServerTypeId":"572bd576-8c92-4901-ab2a-b16e38144813","ServerTypeName":"[MSSQL] SQLServer","ConnectionString":"[your connection string to IzendaMvc]","ConnectionId":"00000000-0000-0000-0000-000000000000"}

The ServerTypeName would be one of [MSSQL] SQL Server, [AZSQL] AzureSQL, [MYSQL] MySQL, [ORACL] Oracle and [PGSQL] PostgreSQL.

DatasourceName Id
[AZSQL] AzureSQL d968e96f-91dc-414d-9fd8-aef2926c9a18
[MYSQL] MySQL 3d4916d1-5a41-4b94-874f-5bedacb89656
[ORACL] Oracle 93942448-c715-4f98-85e2-9292ed7ca4bc
[PGSQL] PostgreSQL f2638ed5-70e5-47da-a052-4da0c1888fcf
[MSSQL] SQLServer 572bd576-8c92-4901-ab2a-b16e38144813

For starter kit authentication database open ~\Mvc5StarterKit\Web.config then modify the DefaultConnection value in connectionStrings node:

  <connectionStrings>
    <add name="DefaultConnection" connectionString="[your connection string to Mvc5StarterKit]" providerName="System.Data.SqlClient" />
  </connectionStrings>

The first run of Izenda API Service

64-bit Oracle DLL Dependencies

If you encounter an error related to OracleDataAccesssDTC in Visual Studio navigate to Tools -> Options -> Projects and Solutions -> Web Projects -> Turn on Use the 64 bit version of IIS Express for web sites and projects.

../_images/mvc_64bit_iis_express.png

64-bit IIS Express

Run Starter Kit

In Visual Studio you can press F5 to run your starter kit for the first time, the application page will look like the screenshot below:

../_images/mvc_run_starter_kit.png

Starter kit screen

There is nothing present from the Izenda UI at this time. At this step we are just checking whether Izenda API is able to start up completely or not. On your browser address add /api/ to the end of url (http://localhost:14809/api/) then press enter, you will see Izenda API is hosted completely:

../_images/mvc_api_url_404.png

API screen

In ~\Mvc5StarterKit project folder you will see a logs folder with the log files mvc5kit-log.log (~\Mvc5StarterKit\logs).

Izenda API Hosting Troubleshooting

If you do not see logs file and 404 page above check following:

  • Nany hosting configuration in Web.config
  • Ensure Nancy.dll and Nancy.Hosting.Aspnet.dll are referenced in the project.
  • Check your routing configuration in RouteConfig.cs

Adding Izenda Boundary

Get all files in IzendaBoundary folder from GitHub repository then include into Mvc5StarterKit project. Build the project, if there are any errors regarding missing references, update the references as needed.

The table below describes the functionality of each source code file:

File and Folder Classes Description
Models Model Entity The definition of transferring model data which is used when communicating between your code and Izenda API.
CustomAdhocReport.cs CustomAdhocReport The IAdHocExtension is used to override many default functions in your Izenda installation. This extension will be automatically injected into the runtime by the Izenda API application. More information on this can be found here
IzendaTokenAuthorization.cs IzendaTokenAuthorization The helper class to decrypt and deserialize authentication token to UserInfo object and vice versa (serialize UserInfo object then encrypt to token key).
IzendaUtility.cs IzendaUtility Helper class to get list of data connection info for specific tenant.
StringCipher.cs StringCipher The helper class to encrypt and decrypt string value.
WebAPIService.cs WebAPIService The service proxy class supports to call to Izenda API Service.

These classes just demonstrate simple examples of using each functionality. It is recommended that in your actual integration you use security best practices that follow your organizations established security policies and procedures.

Custom Authentication Logic

In this starter kit, we use simple authentication with username, password and tenant info. Basically, authorization logic (role, permissions …etc.) will depend on implementation of Izenda backend.

In Mvc5StarterKit database we add a table named Tenants and add a column named TenantID into AspNetUsers table, this new column is a foreign key reference to the Tenants table which demonstrates a one to many relationship between Tenant and User. Other tables are kept same as ASP.NET MVC 5 template, that helps us modify login logic at little as possible, but allows us to show a simple use case for multi-tenant support.

../_images/mvc_AspNetUsers_Tenants.png

Users and Tenants

In integrated mode, authentication is handled by the MVC 5 application, not Izenda. In this starter kit, the Izenda backend will get a token via the UserIntegrationConfig.GetAccessToken method. You must implement your own code logic to provide this token containing the authenticated user’s information. To ensure security, each request to the Izenda API service will call back to MVC application to validate the token it received from the originating request. You must provide this validation logic in your app inside the UserIntegrationConfig.ValidateToken method. The diagram below illustrates the token retrieval and validation process in fully integrated mode:

../_images/mvc_authentication_sequence_diagram.png

Authentication Sequence Diagram

To customize the authentication logic, we must modify some existing classes in the ASP.NET MVC 5 application template. The table below lists all the modified classes:

File Class State Description
IzendaConfig.cs IzendaConfig New The extension class provides Izenda token and token validation for the Izenda backend.
Models\IdentityModels.cs ApplicationUser Modify The entity class present user identity model. This model maps to AspNetUsers table.
Models\IdentityModels.cs Tenant New The entity model presents a tenant, maps to Tenants table.
Models\IdentityModels.cs ApplicationDBContext Modify Entity Framework DB context.
App_Start\IdentityConfig.cs ApplicationUserManager Modify Manage authentication user and login context.
App_Start\IdentityConfig.cs ApplicationSignInManager Modify Custom sign in logic.
Controllers\AccountController.cs AccountController Modify Use custom login logic in Login action..

IzendaConfig

Create IzendaConfig.cs file within the top level of the project and implement subscription for UserIntegrationConfig.GetAccessToken action and UserIntegrationConfig.ValidateToken action.

    public static class IzendaConfig
    {
        public static void RegisterLoginLogic()
        {
            UserIntegrationConfig.GetAccessToken = (args) =>
            {
                return IzendaBoundary.IzendaTokenAuthorization.GetToken(new Models.UserInfo()
                {
                    UserName = args.UserName,
                    TenantUniqueName = args.TenantId
                });
            };

            UserIntegrationConfig.ValidateToken = (ValidateTokenArgs args) =>
            {
                var token = args.AccessToken;
                var user = IzendaBoundary.IzendaTokenAuthorization.GetUserInfo(token);
                return new ValidateTokenResult { UserName = user.UserName, TenantUniqueName = user.TenantUniqueName };
            };
        }
    }

Action UserIntegrationConfig.GetAccessToken will convert user info to a token value.

Action UserIntegrationConfig.ValidateToken convert access token to user info.

Identity Models – Namespaces

  1. Open Models\IdentityModels.cs.
  2. Add the following namespaces so that it appears as below:
using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;

Identity Models – ApplicationUser class

  1. Open Models\IdentityModels.cs.
  2. Add Tenant_Id property and custom claim identity like below:
    public class ApplicationUser : IdentityUser
    {
        public int Tenant_Id { get; set; }
        [ForeignKey("Tenant_Id")]
        public Tenant Tenant { get; set; }
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

            if (Tenant != null)
            {
                userIdentity.AddClaims(new[] {
                    new Claim("tenantName",Tenant.Name),
                    new Claim("tenantId",Tenant.Id.ToString()),
                });
            }
            var role = (await manager.GetRolesAsync(this.Id)).FirstOrDefault();
            userIdentity.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, role));

            // Add custom user claims here
            return userIdentity;
        }
    }

The ApplicationUser class represents the user model and maps to AspNetUsers table. The Tenant_Id property is the foreign key reference to the tenant of the user. In the GenerateUserIdentityAsync method we add tenantId into user identity, this value will be used to establish “claims” based on the user’s tenant.

Identity Models – Tenant

  1. Open Models\IdentityModels.cs.
  2. Add a new Tenant class to represent a tenant object. This class is an entity model that maps to the Tenants table. The example below utilizes a very simple tenant model. You can customize this model to store more complex information.
1
2
3
4
5
    public class Tenant
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

Identity Models – ApplicationDBContext

This is the Entity Framework DB context of the entire authentication database. Because we added a new Tenants table, we must update the DB context to set of Tenant into this class.

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }

        public virtual IDbSet<Tenant> Tenants { get; set; }
        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }

You may need to remove the duplicate ApplicationDBContext to prevent errors.

Identity Config – Namespaces

  1. Open App_Start\IdentityConfig.cs.
  2. Add the following namespaces so that it appears as below:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Mvc5StarterKit.Models;

Identity Config – ApplicationUserManager

  1. Open App_Start\IdentityConfig.cs.
  2. Add a new method FindTenantUserAsync. This will be used to query and retrieve users by tenant, username, and password.
        public async Task<ApplicationUser> FindTenantUserAsync(string tenant, string username, string password)
        {
            var context = ApplicationDbContext.Create();

            var user = await context.Users
                .Include(x => x.Tenant)
                .Where(x => x.UserName.Equals(username, StringComparison.InvariantCultureIgnoreCase))
                .Where(x => x.Tenant.Name.Equals(tenant, StringComparison.InvariantCultureIgnoreCase))
                .SingleOrDefaultAsync();

            //var user = users.FirstOrDefault();

            if (await CheckPasswordAsync(user, password))
                return user;

            return null;
        }

Identity Configs – ApplicationSignInManager

  1. Open App_Start\IdentityConfig.cs.
  2. Add a new method PasswordSigninAsync to customize sign in logic. This method utilizes the FindTenantUserAsync method added above to find a user with the specified username, password and tenant.
    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public async Task<bool> PasswordSigninAsync(string tenant, string username, string password, bool remember)
        {
            var user = await (this.UserManager as ApplicationUserManager).FindTenantUserAsync(tenant, username, password);

            if (user != null)
            {
                await SignInAsync(user, remember, true);
                return true;
            }

            return false;
        }

AccountController – Namespace

  1. Open Controllers\mvc_AccountController.cs.
  2. Add the following namespaces so that it appears as below:
using System;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Mvc5StarterKit.Models;

AccountController

  1. Open Controllers\AccountController.cs.
  2. In Login POST method, implement the ApplicationSignInMangager/PasswordSigninAsync method as shown below.
        // POST: /Account/Login
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            // This doesn't count login failures towards account lockout
            // To enable password failures to trigger account lockout, change to shouldLockout: true
            var result = await SignInManager.PasswordSigninAsync(model.Tenant, model.Email, model.Password, model.RememberMe);
            if(result)
                return RedirectToLocal(returnUrl);
            else
            {
                ModelState.AddModelError("", "Invalid login attempt.");
                return View(model);
            }
            //var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
            //switch (result)
            //{
            //    case SignInStatus.Success:
            //        return RedirectToLocal(returnUrl);
            //    case SignInStatus.LockedOut:
            //        return View("Lockout");
            //    case SignInStatus.RequiresVerification:
            //        return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
            //    case SignInStatus.Failure:
            //    default:
            //        ModelState.AddModelError("", "Invalid login attempt.");
            //        return View(model);
            //}
        }

UserInfo

  1. Create Models\UserInfo.cs
  2. Add the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Mvc5StarterKit.Models
{
    public class UserInfo
    {
        public string UserName { get; set; }
        public string TenantUniqueName { get; set; }
    }
}

AccountViewModels

  1. Open Models\AccountViewModels.cs.
  2. Edit public class LoginViewModel so that it appears as below.
    public class LoginViewModel
    {
        [Required]
        [Display(Name = "Tenant")]
        public string Tenant { get; set; }

        [Required]
        [Display(Name = "Email")]
        [EmailAddress]
        public string Email { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }

Update Login Page

  1. Open Views\Account\Login.cshtml.
  2. Replace the contents of this file with the following from the GitHub MVC5StarterKit equivalent. This will add a Tenant section to the login page.
                <h4>Use a local account to log in.</h4>
                <hr />
                @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                <div class="form-group">
                    @Html.LabelFor(m => m.Tenant, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.TextBoxFor(m => m.Tenant, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.Tenant, "", new { @class = "text-danger" })
                    </div>
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
                    </div>
                </div>

Run First Login

Press F5 to run application, navigate to login page and enter the values below for the respective fields:

Then click Login. You will see the home page with the logged in user’s email on top right of the page.

../_images/mvc_current_user_ui.png

Current user and Log off button

Update Shared Layout

Construct Izenda Menu Items

Open ~\Views\Shared\_Layout.cshtml then add menu items for Izenda pages:

                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>

                    @* #izenda *@
                    <li>@Html.ActionLink("Izenda", "Izenda", "Home")</li>
                    <li>@Html.ActionLink("Settings", "Settings", "Home")</li>

                    @* Reports *@
                    <li class="dropdown">
                        <a class="dropdown-toggle" data-toggle="dropdown" href="#">
                            Reports
                            <span class="caret"></span>
                        </a>
                        <ul class="dropdown-menu">
                            <li>@Html.ActionLink("New Report", "ReportDesigner", "Home")</li>
                            <li>@Html.ActionLink("Report List", "Reports", "Home")</li>
                            @* Add to report Viewer to Demo, hardcode Report ID to link user to a specific report - please update the report id with one created in your enviroment*@
                            <li>@Html.ActionLink("Report Viewer", "ReportViewer", "Report", new { id = "[your report id]" }, null)</li>
                            <li>@Html.ActionLink("Report Parts", "ReportParts", "Report")</li>

                        </ul>
                    </li>

                    @* Dashboards *@
                    <li class="dropdown">
                        <a class="dropdown-toggle" data-toggle="dropdown" href="#">
                            Dashboards
                            <span class="caret"></span>
                        </a>
                        <ul class="dropdown-menu">
                            <li>@Html.ActionLink("New Dashboard", "DashboardDesigner", "Home")</li>
                            <li>@Html.ActionLink("Dashboard List", "Dashboards", "Home")</li>
                            <li>@Html.ActionLink("Dashboard Viewer", "Dashboard", "DashboardViewer")</li>
                        </ul>
                    </li>

                </ul>

Note that the menu routing requires the creation of 2 new controllers: ReportController and DashboardController. These will be created in a later section.

Implement Izenda Configuration Initialization

Add Javascript code below into the bottom of the _Layout.cshtml to initialize Izenda Config and sub menu dropdown display. The function DoIzendaConfig() is located in ~\Scripts\izenda.integrate.js. It initializes main config value needed to run Izenda’s integrated UI such as API Service Url, the path of the Izenda embedded JavaScript package, custom CSS styling, Izenda UI routing config, and the request timeout.

    <script type="text/javascript">
        $(document).ready(function () {
            DoIzendaConfig();
            $('[data-toggle="tooltip"]').tooltip();
            $('[data-toggle="dropdown"]').dropdown();
            $("#reportPartLoader").hide();
            $("#reportPartLoaderText").hide();
        });
    </script>

    @RenderSection("scripts", required: false)
</body>
</html>

Embedding Front-end Izenda (Izenda UI)

Embedding Izenda full page

In the HomeController add a new action named Izenda and then create the view Izenda.cshtml for it.

        [Route("izenda/settings")]
        [Route("izenda/new")]
        [Route("izenda/dashboard")]
        [Route("izenda/report")]
        [Route("izenda/reportviewer")]
        [Route("izenda/reportviewerpopup")]
        [Route("izenda")]
        public ActionResult Izenda()
        {
            return View();
        }

This action is used to embedded the entire Izenda application into one view. The Izenda/* routes are subroutes inside of the fully embedded Izenda page.

In the Izenda.cshtml view, call function izendaInit(), which is defined in ~\Scripts\izenda.integrate.js. The full Izenda UI will be rendered when opening this page, including Report, Dashboard and Setting page.


@{
    ViewBag.Title = "Izenda";
}

@*@section title{ Izenda }*@
@*@section customHeader{}*@
@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            izendaInit();
        });
    </script>
}
<div class="loader" id="progressLoader"> </div>
<div class="izenda-container" id="izenda-root"></div>

We have 2 empty div elements for each Izenda page, the loader div is used for display progress bar when the page is waiting for response from Izenda back-end, the div izenda-container is the container for Izenda page content.

Embedding Specific Izenda Pages

Embedding the Izenda Settings Page

Create the Settings action in the HomeController

        public ActionResult Settings()
        {
            return View();
        }

The settings page view Settings.cshtml:


@{
    ViewBag.Title = "Settings";
}

@*@section title{ Izenda }*@
@*@section customHeader{}*@
@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            izendaInitSetting();
        });
    </script>
}
<div class="loader" id="progressLoader"> </div>
<div class="izenda-container" id="izenda-root"></div>

Embedding the Izenda Report List Page

Create the Reports action in HomeController

        public ActionResult Reports()
        {
            return View();
        }

The report list view Reports.cshtml:


@{
    ViewBag.Title = "Reports";
}

@*@section title{ Izenda }*@
@*@section customHeader{}*@
@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            izendaInitReport();
        });
    </script>
}
<div class="loader" id="progressLoader"> </div>
<div class="izenda-container" id="izenda-root"></div>

Embedding the Izenda Report Designer Page

Create the ReportDesigner action in HomeController

        public ActionResult ReportDesigner()
        {
            return View();
        }

The report designer view ReportDesigner.cshtml:


@{
    ViewBag.Title = "Report Designer";
}

@*@section title{ Izenda }*@
@*@section customHeader{}*@
@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            izendaInitReportDesigner();
        });
    </script>
}
<div class="loader" id="progressLoader"> </div>
<div class="izenda-container" id="izenda-root"></div>

Report Part for Exporting

This action is not displayed directly to the end-users. It is used for rendering report parts (charts, gauges, and maps) for exporting. Reports containing these report part types must be rendered to capture html content then converted it to image when creating exported material (pdf, word, excel, etc.).

This view is requested by Izenda backend over the route url viewer/reportpart/{id} in route config ~\View_Start\RouteConfig.cs.

Add the ReportPart action in HomeController:

        public ActionResult ReportPart(Guid id, string token)
        {
            ViewBag.Id = id;
            ViewBag.Token = token;
            return View();
        }

This view is only used by Izenda backend, so it requires a minimal view containing only the report part content. Create a new simple layout named Izenda_Layout.cshtml. This layout does not contain UI for menu item, signed in user information.

The Izenda_Layout.cshtml content:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")

    @* Apply css based on the users tenant *@
    <link href="~/Scripts/izenda/izenda-ui.css" rel="stylesheet">
</head>
<body>
    <div class="container-fluid body-content">
        @RenderBody()
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")


    @* #izenda *@
    <script src="~/Scripts/alertify.js"></script>
    @Scripts.Render("~/bundles/izenda")

    <script type="text/javascript">
        $(document).ready(function () {
            DoIzendaConfig();
            $('[data-toggle="tooltip"]').tooltip();
            $('[data-toggle="dropdown"]').dropdown();
            $("#reportPartLoader").hide();
            $("#reportPartLoaderText").hide();
        });
    </script>
    @RenderSection("scripts", required: false)
</body>
</html>

The ReportPart.cshtml content:

@{
    Layout = "~/Views/Shared/Izenda_Layout.cshtml";
    ViewBag.Title = "Report Viewer";
}
@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            izendaInitReportPartExportViewer('@ViewBag.Id', '@ViewBag.Token');
           // $('#izenda-root').children().first().removeClass('izenda');
            //var classList = $('#izenda-root').children().first().attr('class').split(/\s+/);
            //$.each(classList, function (index, item) {
            //    console.log(item);
            //});

        });
    </script>
}
<style>
    #izenda-root > .izenda {
        background-color: transparent !important;
    }
</style>
<div class="izenda-container" id="izenda-root" style="margin-top:0px;"></div>

Embedding the Report Viewer

Create the ReportController and add a new action named ReportViewer:

        // show report Viewer by id
        public ActionResult ReportViewer(string id)
        {
            ViewBag.Id = id;
            return View();
        }

Create the view ReportViewer.cshtml:

@using Mvc5StarterKit.Models
@model ReportViewerModel
@{
    ViewBag.Title = "Report Viewer";
}

@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            izendaInitReportViewer('@ViewBag.Id');
        });
    </script>
}

<div class="loader" id="progressLoader"> </div>
<div class="izenda-container" id="izenda-root"></div>

Note that this page also used by Izenda backend in function to export report html content when user wants to send a report via email (directly or over subscription scheduler). The Izenda backend uses the route report/view/{id} to get report content, then in RouteConfig.cs we have a custom route for this. You must ensure your report viewer page has the same controller and action with the custom route ReportViewer.

            routes.MapRoute(
                name: "ReportViewer",
                url: "report/view/{id}",
                defaults: new { controller = "Report", action = "ReportViewer" }
            );

Embedding Report Parts

This page demonstrates how to display multiple report parts on a page outside of the Report Viewer. The function izendaInitReportPartDemo in ~\Scripts\izenda.integrate.js is implemented to handle this.

// Render report parts to specific <div> tag by report part id
var izendaInitReportPartDemo = function () {

    function successFunc(data, status) {
        console.info(data);
        var currentUserContext = {
            token: data.token
        };

        // You can add report parts after creating reports using the context below 
        // Add the report part ID's in the [your report nth id] area
        IzendaSynergy.setCurrentUserContext(currentUserContext);
        IzendaSynergy.renderReportPart(document.getElementById('izenda-report-part1'), {
            "id": "[your report 1st id]",
        });

        IzendaSynergy.renderReportPart(document.getElementById('izenda-report-part2'), {
            "id": "[your report 2nd id]",
        });
 
        IzendaSynergy.renderReportPart(document.getElementById('izenda-report-part3'), {
            "id": ""[your report 3rd id]"
        });
    }
    this.DoRender(successFunc);
};

In the ReportController create the action ReportParts:

        public ActionResult ReportParts()
        {
            return View();
        }

Create the ReportParts.cshtml view:

@using Mvc5StarterKit.Models
@using System.Web.Script.Serialization;
@{
    ViewBag.Title = "Report Parts";
}

@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            izendaInitReportPartDemo();
        });
    </script>
}
<style>
    .report-part-chart {
        float: left;
        width: 40%;
        height:  40%;
        margin-left: 20px;
        margin-top: 20px;
        background-color: white;
    }

    .report-part-grid {
        float: left;
        width: 40%;
        height:  50%;
        margin-left: 20px;
        margin-top: 20px;
        background-color: white;
    }
</style>

<div class="loader" id="reportPartLoader"> </div>
<div class="izenda-container">
    <div class="report-part-chart" id="izenda-report-part1"></div>
    <div class="report-part-chart" id="izenda-report-part2"></div>
    <div class="report-part-grid" id="izenda-report-part3"></div>
</div>

Embedding the Dashboard

Dashboard List

This page displays the dashboard list.

In the HomeController create the action Dashboards:

        public ActionResult Dashboards()
        { 
            return View();
        }

Create the Dashboards.cshtml view:

@{
    ViewBag.Title = "Dashboards";
}

@*@section title{ Izenda }*@
@*@section customHeader{}*@
@section scripts
{

    <script type="text/javascript">
        $(document).ready(function () {
            izendaInitDashboard();
        });
    </script>
}

<div class="loader" id="progressLoader"> </div>
<div class="izenda-container" id="izenda-root"></div>

New Dashboard

The page for creating a new dashboard.

In the HomeController create the action DashboardDesigner:

        public ActionResult DashboardDesigner()
        { 
            return View();
        }

Create the DashboardDesigner.cshtml view:

@{
    ViewBag.Title = "Dashboard Designer";
}

@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            izendaInitNewDashboard();
        });
    </script>
}

<div class="loader" id="progressLoader"> </div>
<div class="izenda-container" id="izenda-root"></div>

Dashboard Viewer

Display detail of dashboard, this page also is requested by Izenda backend for dashboard exporting function over route dashboard/view/{id}. In RouteConfig.cs we have a custom route named DashboardViewer, this custom route will map to the controller and view of this page.

Create the DashboardController and action DashboardViewer:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Mvc5StarterKit.Controllers
{
    public class DashboardController : Controller
    {
        // GET: DashboardViewer
        public ActionResult DashboardViewer(string id)
        {
            ViewBag.Id = id;
            return View();
        }
    }
}

Create the DashboardViewer.cshtml view:

@using Mvc5StarterKit.Models
@model ReportViewerModel
@{
    ViewBag.Title = "Dashboard Viewer";
}

@section scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            izendaInitDashboardViewer('@ViewBag.Id');
        });
    </script>
}

<div class="loader" id="progressLoader"> </div>
<div class="izenda-container" id="izenda-root"></div>

Embedding the Export Manager

Export Manager Queue List

Create the ExportManager action in the HomeController

        public ActionResult ExportManager()
        {
            return View();
        }

The ExportManager page view ExportManager.cshtml:

@{
    ViewBag.Title = "Dashboards";
}

@*@section title{ Izenda }*@
@*@section customHeader{}*@
@section scripts
{

    <script type="text/javascript">
        $(document).ready(function () {
            izendaInitExportManager();
        });
    </script>
}

<div class="loader" id="progressLoader"> </div>
<div class="izenda-container" id="izenda-root"></div>

Run Your Izenda Integrated Application

Press F5 to run debug

Navigate to login page and enter the values below for the respective fields:

On top menu click Settings link then add your license key

../_images/mvc_license_key.png

Settings license key

Stop your application and start again (this step ensures Izenda reloads the cache).

Click Izenda link on top menu to open full Izenda app

../_images/mvc_full_izenda_app.png

Full Izenda app

Now feel free to explore Izenda integrated application.

Implement Register New Account

Modify register new user layout in ASP.NET MVC 5

../_images/mvc_new_account.png

New account screen

Add new Tenant property into class RegisterViewModel in ~\Models\AccountViewModels.c

    public class RegisterViewModel
    {
        [Required]
        public string Tenant { get; set; }

        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }

Add new tenant input element into ~View\Account\Register.cshtml

    <div class="form-group">
        @Html.LabelFor(m => m.Tenant, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Tenant, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>

Create Managers folder and Add new TenantManager class to project (~\Managers\TenantManager.cs), this class is used to get Tenant and create new tenant info from your authentication DB.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;

namespace Mvc5StarterKit.Managers
{
    public class TenantManager
    {
        public Models.Tenant GetTenantByName(string name)
        {
            using (var context = Models.ApplicationDbContext.Create())
            {
                var tenant = context.Tenants.Where(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).SingleOrDefault();

                return tenant;
            }
        }


        public async Task<Models.Tenant> SaveTenantAsync(Models.Tenant tenant)
        {
            using (var context = Models.ApplicationDbContext.Create())
            {
                context.Tenants.Add(tenant);
                await context.SaveChangesAsync();

                return tenant;
            }
        }
    }
}

Open AccountController.cs and modify Register http post action to handle new user registration logic.

When registering a new user the new user’s information is stored on both the authentication DB and the Izenda DB, following that we split the registration logic into the steps below:

  • Save new Tenant if needed
  • Save User and Role in authentication DB
  • Save User and Role in Izenda DB
  • Sign in with new user

All step are encapsulated in the Register action in AccountController.

Register tenant:

                var tenant = new Tenant { Name = model.Tenant };
                var tenantManager = new Managers.TenantManager();
                var roleName = IsSystemTenant(tenant.Name) ? "Administrator" : "Manager";
                var exstingTenant = tenantManager.GetTenantByName(model.Tenant);

                if (exstingTenant != null)
                    tenant = exstingTenant;
                else
                    tenant = await tenantManager.SaveTenantAsync(tenant);

Save user and role information into authentication DB:

                var user = new ApplicationUser { UserName = model.Email, Email = model.Email, Tenant_Id = tenant.Id };
                var result = await UserManager.CreateAsync(user, model.Password);
                //This line below will hard code users who register to a role of Manager and can be changed by altering the role below
                await UserManager.AddToRoleAsync(user.Id, roleName);

Save user and role into IzendaDB:

                if (result.Succeeded)
                {
                    //izenda

                    //determine tenant
                    Tenants izendaTenant = null;
                    if (!IsSystemTenant(tenant.Name))
                    {
                        izendaTenant = new Tenants();
                        izendaTenant.Active = true;
                        izendaTenant.Deleted = false;

                        izendaTenant.Name = tenant.Name;
                        izendaTenant.TenantID = tenant.Name;
                        TenantIntegrationConfig.AddOrUpdateTenant(izendaTenant);
                    }

                    //determine roles
                    var roleDetail = new RoleDetail()
                    {
                        Name = roleName,
                        TenantUniqueName = tenant.Name,
                        Active = true,
                        Permission = new Izenda.BI.Framework.Models.Permissions.Permission(),
                    };

                    var izendaUser = new UserDetail()
                    {
                        UserName = model.Email,
                        EmailAddress = model.Email,
                        FirstName = "John", //todo fix this
                        LastName = "Doe",
                        TenantDisplayId = izendaTenant?.Name,
                        SystemAdmin = IsSystemTenant(tenant.Name),
                        Deleted = false,
                        Active = true,
                        Roles = new List<Role>()
                    };

                    izendaUser.Roles.Add(new Role()
                    {
                        Name = roleDetail.Name
                    });

                    RoleIntegrationConfig.AddOrUpdateRole(roleDetail);
                    UserIntegrationConfig.AddOrUpdateUser(izendaUser);

Login with newly created user:

                    user.Tenant = tenant;
                    await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);

                    return RedirectToAction("Index", "Home");

You can refer to completed implementation of Register in AccountController on Github here.

Call Izenda API Service in C#

On completed sample starter kit on Github, we provide an example demonstrating how to call Izenda API service in C# code, reference to the ~\IzendaBoundary\WebAPIService.cs and ~\IzendaBoundary\IzendaUtility.cs for more detail.