Not answer, just to understand, why you need SiteId field in mapping table UserRoles ?
Hi, the proposed approach implies two submits to server:
Is it correct ? Then how about validation or other erors on second step, we have a risk to loose related file data. Maybe someone can propose other approach with single submit and server side data validation like it works for DTO's ?
Yes, this is it. Why customErrors setting change content type of a response? - this this unexpected behavior for UI. Developers should switch off custom errors to check appropriate response handlers... not very useful.
Good qustion, I've used userRoleRepository to do this
All permissions could be defined in your custom AuthorizationProvider
public class MyAuthorizationProvider : AuthorizationProvider
{
public override void SetPermissions(IPermissionDefinitionContext context)
{
SetPagePermissions(context);
SetEntityPermissions(context);
var permission = context.GetPermissionOrNull(PermissionNames.Impersonation.Name);
if (permission == null)
{
context.CreatePermission(PermissionNames.Impersonation.Name);
}
}
}
Also permissions should be assigned to users or roles, you can do it easely in EF Seeds:
private Role CreateAdminRole(int? tenantId)
{
var adminRole = _context.Roles.Include(e => e.Permissions).FirstOrDefault(e => e.Name == StaticRoleNames.Tenants.Admin && e.TenantId == tenantId);
if(adminRole == null)
{
adminRole = _context.Roles.Add(new Role(tenantId, StaticRoleNames.Tenants.Admin, StaticRoleNames.Tenants.Admin) { IsStatic = true, IsActive = false });
_context.SaveChanges();
}
// Grant all permissions to admin role
var permissions = PermissionFinder
.GetAllPermissions(new MyAuthorizationProvider())
.Where(p => p.MultiTenancySides.HasFlag(tenantId.HasValue?MultiTenancySides.Tenant: MultiTenancySides.Host))
.ToList();
var existPermissions = adminRole.Permissions?.ToList() ?? new List<RolePermissionSetting>(0);
if (existPermissions.Count != permissions.Count)
{
foreach (var permission in permissions)
{
if (existPermissions.Any(e => e.Name == permission.Name && e.TenantId == tenantId)) continue;
AddPermissionForRole(adminRole, permission, tenantId);
}
// remove obsolete permissions
foreach (var permission in existPermissions)
{
if (!permissions.Any(e => e.Name == permission.Name)) {
_context.Permissions.Remove(permission);
}
}
_context.SaveChanges();
}
return adminRole;
}
private void AddPermissionForRole(Role role, Permission permission, int? tenantId)
{
_context.Permissions.Add(
new RolePermissionSetting
{
TenantId = tenantId,
Name = permission.Name,
IsGranted = true,
RoleId = role.Id
});
}
Seems I found small issues with that approach and looks like it should be fixed in Abp core:
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
this.Logger.Warn(FriendlyMessages.FormDataValidationFailed, ex);
var validationErrors = ex.EntityValidationErrors?.SelectMany(e => e.ValidationErrors?.Select(v => new ValidationResult(v.ErrorMessage, new string[] { v.PropertyName }))).ToList();
throw new AbpValidationException(FriendlyMessages.FormDataValidationFailed, validationErrors);
}
}
Below exception rised by running seed with invalid data for entity. Ideally there should be AbpValidationException details but another SerializationException raised instead...
System.Runtime.Serialization.SerializationException: Type is not resolved for member 'Abp.Runtime.Validation.AbpValidationException,Abp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.Data.Entity.Migrations.Design.ToolingFacade.Run(BaseRunner runner)
at System.Data.Entity.Migrations.Design.ToolingFacade.Update(String targetMigration, Boolean force)
at System.Data.Entity.Migrations.UpdateDatabaseCommand.<>c__DisplayClass2.<.ctor>b__0()
at System.Data.Entity.Migrations.MigrationsDomainCommand.Execute(Action command)
Type is not resolved for member 'Abp.Runtime.Validation.AbpValidationException,Abp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
I've decided to use SaveChanges approaches, works good, thank you.
Hi, thank you for answer. Yes, the issue was in migration scripts... Resolved by recreating database and new migration scripts.
Correct, but I've fixed my issue by removing method overloads: Was
IEnumerable<EntityDto> GetAllAssignments(int p1);
IEnumerable<EntityDto> GetAllAssignments(int p1, int p2);
Now
IEnumerable<EntityDto> GetAllAssignments(int p1, int p2=0);
Same issue with api method
http://localhost:6234/api/services/app/program/GetAllAssignments?id=1
{"message":"An error has occurred.","exceptionMessage":"There is an action GetAllAssignments defined for api controller app/program but with a different HTTP Verb. Request verb is GET. It should be Post","exceptionType":"Abp.AbpException","stackTrace":" at Abp.WebApi.Controllers.Dynamic.Selectors.AbpApiControllerActionSelector.GetActionDescriptorByActionName(HttpControllerContext controllerContext, DynamicApiControllerInfo controllerInfo, String actionName) in D:\\Halil\\GitHub\\aspnetboilerplate\\src\\Abp.Web.Api\\WebApi\\Controllers\\Dynamic\\Selectors\\AbpApiControllerActionSelector.cs:line 95\r\n at Abp.WebApi.Controllers.Dynamic.Selectors.AbpApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext) in D:\\Halil\\GitHub\\aspnetboilerplate\\src\\Abp.Web.Api\\WebApi\\Controllers\\Dynamic\\Selectors\\AbpApiControllerActionSelector.cs:line 48\r\n at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n at Castle.Proxies.DynamicApiController`1Proxy_3.ExecuteAsync_callback(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n at Castle.Proxies.Invocations.ApiController_ExecuteAsync_3.InvokeMethodOnTarget()\r\n at Castle.DynamicProxy.AbstractInvocation.Proceed()\r\n at Abp.WebApi.Controllers.Dynamic.Interceptors.AbpDynamicApiControllerInterceptor`1.Intercept(IInvocation invocation) in D:\\Halil\\GitHub\\aspnetboilerplate\\src\\Abp.Web.Api\\WebApi\\Controllers\\Dynamic\\Interceptors\\AbpDynamicApiControllerInterceptor.cs:line 57\r\n at Castle.DynamicProxy.AbstractInvocation.Proceed()\r\n at Castle.Proxies.DynamicApiController`1Proxy_3.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"}