Click or drag to resize

Telepítés / Inicializálás

RecroGrid Framework for .NET Framework

Automatikus telepítés

Telepítés, első RecroGrid elhelyezése (csak a lényeg)

A RecroGrid Framework integrálása, legegyszerűbben az erre a célra készített NuGet csomag telepítésével lehetséges. A csomag telepítése az összes szükséges fájlt hozzáadja a projekthez, és elvégzi a szükséges konfigurációs beállításokat. A telepítéshez a Package Manager Console-ban következő parancsot kell végrehajtani.

Install-Package RecroGrid

Adatbázis inicializálása

A telepítést követően az alkalmazást elindítva a RecroGrid adminisztrátori felületén engedélyezni kell a működéshez szükséges adatbázis táblák létrehozását. Az adminisztrátori felület az alkalmazás /RGF/Admin útvonalon érhető el.

Manuális telepítés

Bonyolultabb élő projektek esetén szükség lehet manuális telepítésre, esetleg az automatikus telepítés javítására vagy módosítására. Ez a szekció részletesen leírja, hogy a RecroGrid Framework működéséhez milyen fájlok és beállítások szükségesek.

Legújabb verzió letöltése

This section contains the following subsections:

Működéshez szükséges csomagok és fájlok

  1. Szükséges csomagok telepítése:

    • EntityFramework (>= 6.1.3 && < 7.0.0)

    • jQuery (>= 1.12.4 && < 3.4.0)

    • jQuery.UI.Combined (= 1.12.1)

  2. Referenciák hozzáadása:

    • Recrovit.RecroGrid.dll

    • Online help megjelenítéséhez szükséges Recrovit.RecroGrid.xml fájl a projekt bin könyvtárába.

  3. Létre kell hozni a projektben egy RecroGrid nevű könyvtárat, amibe az alapvető template és stílus fájlok kerülnek:

    • RecroGrid_Skeleton.html - List nézet template

    • *.css (RecroGrid_*.css, RecroDict_*.css, ...) - Adminisztrátori felület, alap stílusok

  4. jQuery UI Témák a Content\jQueryUIThemes könyvtárba

  5. Adminisztrátori felület megjelenítéséhez és a MenuDesigner használatához szükséges fájlok:

    • Areas/RGF - könyvtár fájlok

  6. Adatbázis beállításhoz szükséges fájlok az App_Data\RecroGrid\MSSQL (MySQL, Oracle, PostgreSQL) könyvtárba:

    • ASP.NET Identity.sql

    • RecroDict-data.sql

    • RecroGrid-data.sql

    • RecroGrid.sql

  7. Entity Framework model

    • DBModel/DatabaseContext.cs

Működéshez szükséges beállítások

A hozzáadott fájlokban az $AssemblyName$ és a $rootnamespace$ definíciókat a projektnek megfelelően be kell állítani.

RouteConfig.cs settings
public static void RegisterRoutes(RouteCollection routes)
{
    //routes.IgnoreRoute ...

    Recrovit.RecroGridFramework.RecroGridController.InitRouting(routes);

    //routes.MapRoute ...
}
_Layout.cshtml referenciák beállítása
<head>
    <link href="~/recrogrid/resource/bootstrap-submenu.css" rel="stylesheet" />
    <link href="~/Content/jQueryUIThemes/@(this.Context.Session["theme"]??"base")/jquery-ui.css" rel="stylesheet" />
    @RenderSection("htmlhead", required: false)
</head>
<body>
    ...
    <ul class="nav navbar-nav rgf-menu">
        <li>
            ...
        </li>
        @Html.Raw(Recrovit.RecroGridFramework.UI.MenuDesigner.Create(this.Context, 10, true))
    </ul>
    ...
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    @Html.Raw(Recrovit.RecroGridFramework.RecroGrid.GetRecroGridScriptReferences(this.Context))
    @RenderSection("Scripts", required: false)
</body>
web.config beállítások
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="applicationSettings">
      <section name="Recrovit.RecroGrid" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
      <section name="Recrovit.RecroTrack" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
      <section name="Recrovit.RecroSec" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
      <section name="Recrovit.RecroDict" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    </sectionGroup>
  </configSections>
  <connectionStrings>
    <!--<add name="DefaultConnection" connectionString="Data Source=SqlServer2012;Initial Catalog=$AssemblyName$;Integrated Security=True" providerName="System.Data.SqlClient" />-->
    <!--<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\$AssemblyName$.mdf;Initial Catalog=$AssemblyName$;Integrated Security=True" providerName="System.Data.SqlClient" />-->
    <!--<add name="DefaultConnection" providerName="MySql.Data.MySqlClient" connectionString="server=centOS7;database=$AssemblyName$;User ID=username;password=P@ssw0rd;CharSet=utf8;AllowUserVariables=true;" />-->
    <!--<add name="DefaultConnection" providerName="Oracle.ManagedDataAccess.Client" connectionString="User Id=USERNAME;Password=P@ssw0rd;Data Source=XE" />-->
    <!--<add name="DefaultConnection" connectionString="Host=postgresql.lan;Database=$AssemblyName$;Username=username;Password=P@ssw0rd;Persist Security Info=True" providerName="Npgsql" />-->
  </connectionStrings>
  <applicationSettings>
    <Recrovit.RecroGrid>
      <setting name="DefaultNamesapce" serializeAs="String">
        <value>$rootnamespace$.DBModel,$AssemblyName$</value>
      </setting>
      <setting name="DefaultDbContextType" serializeAs="String">
        <value>$rootnamespace$.DBModel.DatabaseContext,$AssemblyName$</value>
      </setting>
      <setting name="DefaultConnectionName" serializeAs="String">
        <value>DefaultConnection</value>
      </setting>
      <setting name="SQLTimeout" serializeAs="String">
        <value>15</value>
      </setting>
      <setting name="ApplicationPath" serializeAs="String">
        <value>/</value>
      </setting>
      <!--<setting name="StylesPath" serializeAs="String">
        <value>Content/RGF/styles</value>
      </setting>-->
      <!--<setting name="DefaultSchema" serializeAs="String">
        <value></value>
      </setting>-->
      <!--<setting name="ItemsPerPage" serializeAs="String">
        <value>15</value>
      </setting>-->
      <!--<setting name="KeepSessionAlive" serializeAs="String">
        <value>300</value>
      </setting>-->
      <setting name="SessionStateBehavior" serializeAs="String">
        <!--Disabled, ReadOnly, Required-->
        <value>Disabled</value>
      </setting>
    </Recrovit.RecroGrid>
    <Recrovit.RecroSec>
      <setting name="Enabled" serializeAs="String">
        <!--true eseten minden objektumot paraméterezni kell, egyébként az entitásban lehet jelezni, hogy RecroSec kell-->
        <value>false</value>
      </setting>
      <setting name="SingleUserMode" serializeAs="String">
        <value>true</value>
      </setting>
      <setting name="AdministratorGroupName" serializeAs="String">
        <value>Administrators</value>
      </setting>
      <setting name="CacheMode" serializeAs="String">
        <!--Process, RGCache, Disable-->
        <value>Process</value>
      </setting>
      <setting name="AnonymousId" serializeAs="String">
        <value>Anonymous</value>
      </setting>
      <setting name="CurrentUserId" serializeAs="String">
        <!--e.g. ApplicationUserManager -> public static string GetCurrentUserId(HttpContext context) { return context.User.Identity.GetUserId(); }-->
        <!--<value>GetCurrentUserId,$rootnamespace$.ApplicationUserManager,$AssemblyName$</value>-->
        <value></value>
      </setting>
      <!--<setting name="VirtualUserId" serializeAs="String">
        <value>GetVirtualUserId,$rootnamespace$.ApplicationUserManager,$AssemblyName$</value>
      </setting>-->
    </Recrovit.RecroSec>
    <Recrovit.RecroDict>
      <setting name="DefaultLanguage" serializeAs="String">
        <value>eng</value>
      </setting>
    </Recrovit.RecroDict>
    <Recrovit.RecroTrack>
      <setting name="Enabled" serializeAs="String">
        <value>true</value>
      </setting>
      <setting name="RowVersionName" serializeAs="String">
        <value>rowversion</value>
      </setting>
    </Recrovit.RecroTrack>
  </applicationSettings>
  <system.webServer>
    <handlers>
      <add name="RecroGridHandler" path="RecroGrid/Resource/*" verb="GET" type="System.Web.Handlers.TransferRequestHandler" />
    </handlers>
  </system.webServer>
</configuration>

A web.config fájlban a DatabaseContext connectionString-ben meg kell adni a helyes Data Source és Initial Catalog beállításokat.

Javasolt globális stílus beállítása
div.recro-grid-base { font-size: small; }

MySQL

MySQL használata esetén telepíteni kell

Javasolt előre megadni a connectionString-et.
ASP.NET Identity használata esetén - a Linux kompatibilitás miatt - a táblák nevét kisbetűsre konvertáljuk.

Note Note

A példában a MySQL for Visual Studio 1.2.7, MySql.Data.Entity 6.9.12 és a Connector/Net 6.9.12 verziókat használjuk.

MySQL settings
<connectionStrings>
  <add name="DefaultConnection" providerName="MySql.Data.MySqlClient" connectionString="server=centOS7;database=recrogrid_mysql;User ID=recrogrid;password=P@ssw0rd;CharSet=utf8;AllowUserVariables=true;" />
</connectionStrings>
<system.data>
  <DbProviderFactories>
    <remove invariant="MySql.Data.MySqlClient" />
    <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.12.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
  </DbProviderFactories>
</system.data>
<entityFramework codeConfigurationType="MySql.Data.Entity.MySqlEFConfiguration, MySql.Data.Entity.EF6">
  <defaultConnectionFactory type="MySql.Data.Entity.MySqlConnectionFactory, MySql.Data.Entity.EF6" />
  <providers>
    <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    <provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.9.12.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"></provider>
  </providers>
</entityFramework>
ApplicationDbContext : IdentityDbContext<ApplicationUser>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<ApplicationUser>().ToTable("aspnetusers");
    modelBuilder.Entity<IdentityRole>().ToTable("aspnetroles");
    modelBuilder.Entity<IdentityUserRole>().ToTable("aspnetuserroles");
    modelBuilder.Entity<IdentityUserClaim>().ToTable("aspnetuserclaims");
    modelBuilder.Entity<IdentityUserLogin>().ToTable("aspnetuserlogins");
}

Oracle

Oracle használata esetén telepíteni kell

Javasolt előre megadni a connectionString-et, illetve további web.config beállítások megadása szükséges.
ASP.NET Identity használata esetén a táblák nevét nagybetűsre konvertáljuk.

Note Note

A példában a Oracle Developer Tools for Visual Studio 2015 (12.2.0.1.0), Oracle.ManagedDataAccess.EntityFramework 18.3.0 verziókat használjuk.

Oracle settings
<configSections>
  <section name="oracle.manageddataaccess.client" type="OracleInternal.Common.ODPMSectionHandler, Oracle.ManagedDataAccess, Version=4.122.18.3, Culture=neutral, PublicKeyToken=89b483f429c47342" />
</configSections>
<connectionStrings>
  <add name="DefaultConnection" providerName="Oracle.ManagedDataAccess.Client" connectionString="User Id=recrogrid_user;Password=P@ssw0rd;Data Source=XE" />
</connectionStrings>
<system.data>
  <DbProviderFactories>
    <remove invariant="Oracle.ManagedDataAccess.Client" />
    <add name="ODP.NET, Managed Driver" invariant="Oracle.ManagedDataAccess.Client" description="Oracle Data Provider for .NET, Managed Driver" type="Oracle.ManagedDataAccess.Client.OracleClientFactory, Oracle.ManagedDataAccess, Version=4.122.18.3, Culture=neutral, PublicKeyToken=89b483f429c47342" />
  </DbProviderFactories>
</system.data>
<entityFramework>
  <defaultConnectionFactory type="Oracle.ManagedDataAccess.EntityFramework.OracleConnectionFactory, Oracle.ManagedDataAccess.EntityFramework" />
  <providers>
    <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    <provider invariantName="Oracle.ManagedDataAccess.Client" type="Oracle.ManagedDataAccess.EntityFramework.EFOracleProviderServices, Oracle.ManagedDataAccess.EntityFramework, Version=6.122.18.3, Culture=neutral, PublicKeyToken=89b483f429c47342" />
  </providers>
</entityFramework>
<oracle.manageddataaccess.client>
  <version number="*">
    <dataSources>
      <dataSource alias="XE" descriptor="(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=OracleXE)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE))) " />
    </dataSources>
    <edmMappings>
      <edmNumberMapping>
        <add NETType="bool" MinPrecision="1" MaxPrecision="1" DBType="Number" />
        <add NETType="byte" MinPrecision="2" MaxPrecision="3" DBType="Number" />
        <add NETType="int16" MinPrecision="4" MaxPrecision="5" DBType="Number" />
        <add NETType="int32" MinPrecision="6" MaxPrecision="10" DBType="Number" />
        <add NETType="int64" MinPrecision="11" MaxPrecision="19" DBType="Number" />
      </edmNumberMapping>
    </edmMappings>
    <implicitRefCursor>
      <storedProcedure schema="RECROGRID_USER" name="RECROSEC_GETOBJECTPERMISSION">
        <refCursor name="RES">
          <bindInfo mode="Output" />
        </refCursor>
      </storedProcedure>
      <storedProcedure schema="RECROGRID_USER" name="RECROSEC_GETPERMISSIONFORUSER">
        <refCursor name="RES">
          <bindInfo mode="Output" />
        </refCursor>
        <refCursor name="RES2">
          <bindInfo mode="Output" />
        </refCursor>
      </storedProcedure>
    </implicitRefCursor>
  </version>
</oracle.manageddataaccess.client>
ApplicationDbContext : IdentityDbContext<ApplicationUser
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    var builder = new System.Data.Common.DbConnectionStringBuilder();
    builder.ConnectionString = this.Database.Connection.ConnectionString;
    string schemaName = (builder["User ID"] as string).ToUpper();

    modelBuilder.HasDefaultSchema(schemaName);
    modelBuilder.Types().Configure(c => c.ToTable(c.ClrType.Name.ToUpper()));
    modelBuilder.Properties().Configure(c => c.HasColumnName(c.ClrPropertyInfo.Name.ToUpper()));

    modelBuilder.Entity<ApplicationUser>().ToTable("ASPNETUSERS");
    modelBuilder.Entity<IdentityRole>().ToTable("ASPNETROLES");
    modelBuilder.Entity<IdentityUserRole>().ToTable("ASPNETUSERROLES");
    modelBuilder.Entity<IdentityUserClaim>().ToTable("ASPNETUSERCLAIMS");
    modelBuilder.Entity<IdentityUserLogin>().ToTable("ASPNETUSERLOGINS");
}

PostgreSQL

PostgreSQL használata esetén telepíteni kell

Note Note

A példában a EntityFramework6.Npgsql 3.2.0, és az Npgsql 4.0.4 verziókat használjuk.

PostgreSQL settings
<system.data>
  <DbProviderFactories>
    <remove invariant="Npgsql" />
    <add name="Npgsql Data Provider" invariant="Npgsql" description=".Net Data Provider for PostgreSQL" type="Npgsql.NpgsqlFactory, Npgsql, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7" />
  </DbProviderFactories>
</system.data>
<entityFramework>
  <defaultConnectionFactory type="Npgsql.NpgsqlConnectionFactory, EntityFramework6.Npgsql"></defaultConnectionFactory>
  <providers>
    <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    <provider invariantName="Npgsql" type="Npgsql.NpgsqlServices, EntityFramework6.Npgsql" />
  </providers>
</entityFramework>
ApplicationDbContext : IdentityDbContext<ApplicationUser
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Types().Configure(c => c.ToTable(c.ClrType.Name.ToLower()));
    modelBuilder.Properties().Configure(c => c.HasColumnName(c.ClrPropertyInfo.Name.ToLower()));
    modelBuilder.HasDefaultSchema("public");

    modelBuilder.Entity<ApplicationUser>().ToTable("aspnetusers");
    modelBuilder.Entity<IdentityRole>().ToTable("aspnetroles");
    modelBuilder.Entity<IdentityUserRole>().ToTable("aspnetuserroles");
    modelBuilder.Entity<IdentityUserClaim>().ToTable("aspnetuserclaims");
    modelBuilder.Entity<IdentityUserLogin>().ToTable("aspnetuserlogins");
}
RecroGrid Framework for .NET Core

Automatikus telepítés

.NET Core alkalmazás esetén a RecroGrid Framework NuGet csomag telepítésével a projektben beállításra kerülnek a referenciák. A telepítését követően, az alkalmazás elindításával további inicializálás szükséges.

A NuGet csomag telepítése elhelyez egy contoller referenciát, ami az alkalmazást elindítva a /RGFInstall útvonalon érhető el. A modul a projekthez hozzáad fájlokat, illetve néhány fájlban (pl. Startup.cs, _Layout.cshtml) egyéb beállításokat hajt végre. Ezen beállításokat manuálisan is el lehet végezni, ami a Manuális telepítés szekcióban kerül részletezésre.

Caution note Caution

Javasolt debugging módban (F5) elindítani az alkalmazást, hogy a módosítások befejezéséig ne forduljon újra az alkalmazás.

A projekt inicializálása után az alkalmazást újra kell fordítani, és azt elindítva a RecroGrid adminisztrátori felületén engedélyezni kell a működéshez szükséges adatbázis táblák létrehozását. Az adminisztrátori felület az alkalmazás /RGF/Admin útvonalon érhető el.

Manuális telepítés

A projekthez szükséges fájlokat a RGF_v1.x.Content.zip csomagból a projekt könyvtárba kell másolni. A Recrovit.RecroGrid.xml fájl másolását engedélyezni kell az output könyvtárba.

Startup beállítások

Startup.cs beállítások
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;

            Recrovit.RecroGridFramework.RecroGridConfig.Startup(configuration);
        }

        public void ConfigureServices(IServiceCollection services)
        {
            ...

#if PGSQL
            //PostgreSQL 
            services.AddDbContext<DBModel.DatabaseContext>(options => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
#else
            services.AddDbContext<DBModel.DatabaseContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
#endif
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
                .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver());

            Recrovit.RecroGridFramework.RecroGridConfig.ConfigureServices(services);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            ...

            Recrovit.RecroGridFramework.RecroGridConfig.Configure(app, env);
            app.UseMvc(routes =>
            {
                routes.MapRoute(name: "areas", template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
_Layout.cshtml referenciák beállítása
<head>
    <link href="~/recrogrid/resource/bootstrap-submenu.css" rel="stylesheet" />
    <link href="~/jQueryUIThemes/@(this.Context.Request.Cookies["rgf_theme"]??"base")/jquery-ui.css" rel="stylesheet" />
    @RenderSection("htmlhead", required: false)
</head>
<body>
    ...
    <ul class="nav navbar-nav rgf-menu">
        <li>
            ...
        </li>
        @Html.Raw(Recrovit.RecroGridFramework.UI.MenuDesigner.Create(this.Context, 10, true))
    </ul>
    ...
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    @Html.Raw(Recrovit.RecroGridFramework.RecroGrid.GetRecroGridScriptReferences(this.Context))
    @RenderSection("Scripts", required: false)
</body>
appsettings.json beállítások
{
  //"ConnectionStrings": {
  //  "DefaultConnection": "Server=SQLServer,8762;Database=$AssemblyName$;Trusted_Connection=True;MultipleActiveResultSets=true"
  //  "DefaultConnection": "Server=.\\SQLServer2012,8762;Database=$AssemblyName$;User ID=username;password=P@ssw0rd;MultipleActiveResultSets=true"
  //  "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;AttachDBFilename=C:\\$AssemblyName$\\App_Data\\$AssemblyName$.mdf;Database=$AssemblyName$;Trusted_Connection=true;MultipleActiveResultSets=true"
  //  "DefaultConnection": "Host=postgresql.lan;Database=$AssemblyName$;Username=username;Password=P@ssw0rd;Persist Security Info=True"
  //  "DefaultConnection": "Data Source=Oracle12c.lan:1521/ora12;User Id=C##RECROGRID_USER_CORE;Password=P@ssw0rd;Persist Security Info=True;"
  //},
  "Recrovit": {
    "RecroGrid": {
      "DefaultNamesapce": "$rootnamespace$.DBModel,$AssemblyName$",
      "DefaultDbContextType": "$rootnamespace$.DBModel.DatabaseContext,$AssemblyName$",
      "DefaultConnectionName": "DefaultConnection",
      "SQLTimeout": 15,
      "ApplicationPath": "/"
      //"StylesPath": "rgf/styles"
    },
    "RecroSec": {
      "Enabled": false,
      "SingleUserMode": true,
      "AdministratorGroupName": "Administrators",
      "CacheMode": "Process",
      "AnonymousId": "Anonymous"
      //,"CurrentUserId": "GetCurrentUserId,$rootnamespace$.Program,$AssemblyName$"// public static string GetCurrentUserId(Microsoft.AspNetCore.Http.HttpContext context) { return context.User.FindFirstValue(ClaimTypes.NameIdentifier); }
    },
    "RecroDict": {
      "DefaultLanguage": "eng"
    },
    "RecroTrack": {
      "Enabled": true,
      "RowVersionName": "rowversion"
    }
  }
}

PostgreSQL

PostgreSQL használata esetén telepíteni kell

See Also

Other Resources