ONE MORE CORRECTION, SORRY! This time I tested it ;)
using Abp.Authorization;
using Abp.Localization;
using Abp.MultiTenancy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace MyApp.Authorization
{
public class MyAppAuthorizationProvider : AuthorizationProvider
{
public override void SetPermissions(IPermissionDefinitionContext context)
{
List<PermissionInfo> permissionInfos = new List<PermissionInfo>();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (!assembly.IsDynamic)
{
foreach (TypeInfo typeInfo in assembly.GetExportedTypes())
{
// Attributes on Fields (Constants)
foreach (FieldInfo fieldInfo in typeInfo.GetFields())
{
var permissionAttribute = fieldInfo.GetCustomAttribute<PermissionAttribute>(false);
if (permissionAttribute != null)
{
permissionInfos.Add(
new PermissionInfo()
{
Name = (string)fieldInfo.GetRawConstantValue(),
DisplayName = permissionAttribute.DisplayName,
IsGrantedByDefault = permissionAttribute.IsGrantedByDefault,
MultiTenancySides = permissionAttribute.MultiTenancySides
});
}
}
}
}
}
var parentNameBuilder = new StringBuilder();
foreach (PermissionInfo permissionInfo in permissionInfos.OrderBy(p => p.Name))
{
if (!permissionInfo.Name.Contains("."))
{
// Create top level permission
var newPermission = context.CreatePermission(
name: permissionInfo.Name,
displayName: F(permissionInfo.DisplayName),
isGrantedByDefault: permissionInfo.IsGrantedByDefault,
description: F(permissionInfo.Description),
multiTenancySides: permissionInfo.MultiTenancySides);
}
else
{
// Create child permission under appropriate parent
var nameParts = permissionInfo.Name.Split('.');
parentNameBuilder.Clear();
parentNameBuilder.Append(nameParts[0]);
Permission parentPermission = context.GetPermissionOrNull(parentNameBuilder.ToString());
for (int i = 1; i < nameParts.Length - 1; i++)
{
parentNameBuilder.Append(".");
parentNameBuilder.Append(nameParts[i]);
parentPermission = parentPermission.Children.Where(p => string.Compare(p.Name, parentNameBuilder.ToString(), true) == 0).FirstOrDefault();
}
if (parentPermission == null)
{
throw new Exception("Parent permission not defined: '" + parentNameBuilder.ToString() + "'");
}
var newPermission = parentPermission.CreateChildPermission(
name: permissionInfo.Name,
displayName: F(permissionInfo.DisplayName),
isGrantedByDefault: permissionInfo.IsGrantedByDefault,
description: F(permissionInfo.Description),
multiTenancySides: permissionInfo.MultiTenancySides);
}
}
}
public class PermissionInfo
{
public string Name;
public string DisplayName;
public string Description;
public MultiTenancySides MultiTenancySides;
public bool IsGrantedByDefault;
}
/// <summary>
/// Creates a fixed localizable string (avoids using localization - just pass it the text directly)
/// </summary>
private static ILocalizableString F(string value)
{
return new FixedLocalizableString(value);
}
private static ILocalizableString L(string name)
{
return new LocalizableString(name, MyAppConstants.LocalizationSourceName);
}
}
}
You can just comment out the model builder stuff that references constants in my other projects.
This assumes you have configured your projects' build output folder to "bin" instead of "bin\debug" and "bin\release". The reason for that is you don't want it referencing a debug dll if you are building in release to deploy to Azure or something. So I recommend you change your build output paths to "bin".
But actually it works just fine if you don't, you just need to change the paths to the DLLs at the top.
You'll want to clean it up a bit I'm sure, but here's my working solution.
Install the "Auto T4" visual studio extension to trigger a regeneration when you build your solution, and never mess with updating your DbContext again!
<#@ template language="C#" debug="True" #> <#@ output extension="cs" #> <#@ assembly name="System" #> <#@ assembly name="System.Core" #> <#@ assembly name="System.ComponentModel.DataAnnotations" #> <#@ assembly name="System.Data" #> <#@ assembly name="$(SolutionDir)Trident.Core\bin\EntityFramework.dll" #> <#@ assembly name="Microsoft.CSharp" #> <#@ assembly name="$(SolutionDir)Trident.Core\bin\Abp.dll" #> <#@ assembly name="$(SolutionDir)Trident.Core\bin\Abp.Zero.dll" #> <#@ assembly name="$(SolutionDir)Trident.Core\bin\Trident.Core.dll" #> <#@ import namespace="System" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.ComponentModel.DataAnnotations.Schema" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Threading.Tasks" #> <#@ import namespace="Trident.Domain" #> <#
var coreAssembly = typeof(Trident.TridentCoreModule).Assembly;
var persistables =
coreAssembly.GetExportedTypes()
.Where(ct => !ct.IsAbstract)
.Where(ct => ct.GetCustomAttributes(typeof(TableAttribute), false).Any())
.OrderBy(ct => ct.Name)
.Select(ct => new { Type = ct, TableAttribute = (TableAttribute)ct.GetCustomAttributes(typeof(TableAttribute), false).First() })
.ToArray();
#> using System; using System.Data.Entity; using System.Data.Entity.ModelConfiguration.Conventions; using System.Data.Common; using System.Data.Entity.Migrations.History; using System.Data.Entity.Infrastructure.Interception; using System.Data.Entity.SqlServer; using System.Data.SqlClient; using System.Diagnostics; using System.Reflection; using System.Linq; using Abp.Zero.EntityFramework; using Trident.Authorization.Roles; using Trident.MultiTenancy; using Trident.Users;
namespace Trident.EntityFramework { public partial class TridentDbContext : AbpZeroDbContext<Tenant, Role, User> {
<# var pluralizationService = new System.Data.Entity.Infrastructure.Pluralization.EnglishPluralizationService(); foreach(var p in persistables) { var propName = p.TableAttribute.Name ?? pluralizationService.Pluralize(p.Type.Name); WriteLine(" public virtual IDbSet<" + p.Type.FullName + "> " + propName + " { get; set; }"); } #>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Uncomment this if you want to use non-plural table names
//modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
//modelBuilder.HasDefaultSchema(Trident.TridentConstants.SchemaName);
//modelBuilder.ChangeAbpTablePrefix<Tenant, Role, User>("", Trident.TridentConstants.SchemaName);
/* NOTE:
* Setting "Default" to base class helps us when working migration commands on Package Manager Console.
* But it may cause problems when working Migrate.exe of EF. If you will apply migrations on command line, do not
* pass connection string name to base classes. ABP works either way.
* Use the context's full namespace and name, if deploying to Azure, and make sure the connection string's name matches it.
*/
public TridentDbContext()
: base(typeof(TridentDbContext).FullName)
{
}
/* NOTE:
* This constructor is used by ABP to pass connection string defined in TridentDataModule.PreInitialize.
* Notice that, actually you will not directly create an instance of TridentDbContext since ABP automatically handles it.
*/
public TridentDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
//This constructor is used in tests
public TridentDbContext(DbConnection connection)
: base(connection, true)
{
}
}
} <# #>
So for as long as I've used this framework, I've been baffled as to why I couldn't seem to get any of the samples or a fresh template solution to deploy to Azure and perform migrations.
I solved the problem, but I continued thinking I was missing something and that others were doing this successfully.
After reading some forum posts on the subject, I think this just hasn't been confronted yet by the framework's developer.
So, here's how you can get your solution to work like you expect on Azure:
(Xyz = Name of your app)
I wrote:
// Note: The Name (not display name) of the property is defined by the name of the constant the attribute is applied to.
But it should actually say this:
// Note: The Name (not display name) of the property is defined by the value of the constant the attribute is applied to.
Here you go. Adapt this to your needs:
Core project - MyAppAuthorizationProvider.cs:
using Abp.Authorization;
using Abp.Localization;
using Abp.MultiTenancy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace MyApp.Authorization
{
public class MyAppAuthorizationProvider : AuthorizationProvider
{
public override void SetPermissions(IPermissionDefinitionContext context)
{
List<PermissionInfo> permissionInfos = new List<PermissionInfo>();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (!assembly.IsDynamic)
{
foreach (TypeInfo typeInfo in assembly.GetExportedTypes())
{
// Attributes on Fields (Constants)
foreach (FieldInfo fieldInfo in typeInfo.GetFields())
{
var permissionAttribute = fieldInfo.GetCustomAttribute<PermissionAttribute>(false);
if (permissionAttribute != null)
{
permissionInfos.Add(
new PermissionInfo()
{
Name = (string)fieldInfo.GetRawConstantValue(),
DisplayName = permissionAttribute.DisplayName,
IsGrantedByDefault = permissionAttribute.IsGrantedByDefault,
MultiTenancySides = permissionAttribute.MultiTenancySides
});
}
}
}
}
}
foreach (PermissionInfo permissionInfo in permissionInfos.OrderBy(p => p.Name))
{
Permission parentPermission = null;
Permission newPermission = null;
if (permissionInfo.Name.Contains("."))
{
string parentName = permissionInfo.Name.Substring(0, permissionInfo.Name.LastIndexOf('.'));
parentPermission = context.GetPermissionOrNull(parentName);
if (parentPermission == null)
{
throw new Exception("Permission not defined: '" + parentName + "'");
}
newPermission = parentPermission.CreateChildPermission(
name: permissionInfo.Name,
displayName: F(permissionInfo.DisplayName),
isGrantedByDefault: permissionInfo.IsGrantedByDefault,
description: F(permissionInfo.Description),
multiTenancySides: permissionInfo.MultiTenancySides);
}
else
{
newPermission = context.CreatePermission(
name: permissionInfo.Name,
displayName: F(permissionInfo.DisplayName),
isGrantedByDefault: permissionInfo.IsGrantedByDefault,
description: F(permissionInfo.Description),
multiTenancySides: permissionInfo.MultiTenancySides);
}
}
}
public class PermissionInfo
{
public string Name;
public string DisplayName;
public string Description;
public MultiTenancySides MultiTenancySides;
public bool IsGrantedByDefault;
}
/// <summary>
/// Creates a fixed localizable string (avoids using localization - just pass it the text directly)
/// </summary>
private static ILocalizableString F(string value)
{
return new FixedLocalizableString(value);
}
private static ILocalizableString L(string name)
{
return new LocalizableString(name, MyAppConstants.LocalizationSourceName);
}
}
}
Core project - PermissionAttribute.cs
using Abp.MultiTenancy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyApp.Authorization
{
public partial class PermissionAttribute : Attribute
{
// Note: The Name (not display name) of the property is defined by the name of the constant the attribute is applied to.
public string DisplayName { get; set; }
public string Description { get; set; }
private MultiTenancySides _multiTenancySides = MultiTenancySides.Host | MultiTenancySides.Tenant;
public MultiTenancySides MultiTenancySides
{
get { return _multiTenancySides; }
set { _multiTenancySides = value; }
}
public bool IsGrantedByDefault { get; set; }
}
}
Then, ANYWHERE in your solution, in ANY PROJECT, you can define classes like this:
using MyApp.Authorization;
using System.ComponentModel;
namespace MyApp.Authorization
{
public static partial class PermissionNames
{
[Permission(DisplayName = "Administration")] // MultiTenancySides defaults to Host & Tenant if not specified. This way, the host AND customers (tenants) can have admin sections.
public const string Administration = "Administration";
[Permission(DisplayName = "Customer Management", MultiTenancySides = Abp.MultiTenancy.MultiTenancySides.Host)]
public const string CustomerManagement = "Administration.CustomerManagement"; // Permissions will automatically be arranged into the proper hierarchy (parent/child) based on the Parent.Child.Grandchild dot-based naming pattern.
[Permission(DisplayName = "Customer Portal", MultiTenancySides = Abp.MultiTenancy.MultiTenancySides.Tenant)]
public const string CustomerPortal = "CustomerPortal"; // Only customers (tenants) can be assigned this permission
}
}
And the core auth code will find the attributes on the constants and automagically create your permissions with the correct hierarchical structure