Hi
We have had some difficulty getting a new developer (who has a license) up and running with an already existing abp solution.
We tried to run the application, and could see in the log that there was a license validation problem. So, we then used the cli to login to abp "abp login..." and were logged in succesfully. When trying to run the application again, there were no issues is the log file, but everytime we tried to run the application it kept coming up with a 404 error.
We could see that the application worked succesfully when in Release mode, but not in debug mode. Trying to replace the ASPNETCORE_ENVIRONMENT=Development to Production did not fix the problem either (just different error page but stil 404 error).
After trying different things, we then decided to try the abp suite. After installing it for the first time and then adding the existing solution to abp, we then found that that after running the solution again (debug, ASPNETCORE_ENVIRONMENT=Development ), the application now started correctly.
Should simply logging in using the cli "abp login..." be enough to be start working with a solution? Is this the best way to get a new developer started? Or is there a better way?
Best regards,
Mike
I cannot find a nice way to specify the AggregationOptions used when using the GetMongoQueryableAsync or GetAggregateAsync methods.
Ideally, the following changes could be made:
By being able to control the AggregationOptions in this way, we can then specify the Collation setting for a whole module, repository or specific query. Ideally, I would also like to specify the default AggregationOptions used for my entire application. E.g. by configuring some mongodb options.
I would also want to ablity to override the aggregation options specified in other modules (presumably this would be possible by replacing the DBContext).
An example why this is neccesary... Without specifying the collation (and accompanying index within the database) we cannot make results be ordered in a case insensitive way. E.g. By default the list "A, b, D, e" is ordered as "A, D, b, e" if a collation is not specified (resulting in a binary simple collation always being used). By specifying a collation, we can ensure that a particular index is used, which is defined as having a strength of 1 or 2 (case and diacritic insensitive).
Another minor improvement, which would be useful during aggregation pipelines (e.g. lookup generation) would be if the AbpMongoDbContext.GetCollectionName<> method was made public.
If I set the page size to a large number (e.g. 500), a request is pending. If I then subsequently change the page size to a small number (e.g. 10) the request is created and completed relatively quickly. The previous request is then still pending, and eventually completes resulting in the table being incorrectly updated.
Is there a way to cancel previous requests automatically when calling dataTable.ajax.reload()? Or is there a way to manually cancel a previous request?
I have looked at this file, but cannot figure out what to do to change the behaviour: https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js
The AbpUserClaimsPrincipalFactory is using the ClaimsIdentityExtensions.AddIfNotContains extension method, which is not adding any claims of the same type.
What is the best way to replace or override this factory?
If I set the permissions of a role and then rename the role, the permissions of the role are cleared.
Hi,
It is already possible to assign members and roles to an OU, but could it also be possible (out of the box) to assign claims to an OU? Does it make sense to be able to do this? The intention being that the claims will be assigned to any members of that OU, in the same way that the roles are applied to its members? Either way, could you point me to the relevant places where I should implement this myself?
Many thanks,
Mike
Do you think that it could be possible to extend the abp suite / cli to be able to specify if the generated MongoDB repositories should use either IQueryable
or IAggregateFluent
?
Or maybe after reviewing the details below, newly generated MongoDB repositories should be generated to use IAggregateFluent
and not use IQueryable
?
The automatically generated Entity Framework repositories include join
statements to include navigation properties, so that the navigation properties can be used in the OrderBy calls and returned. However, the MongoDB repositories do not have this functionality and perform separate database calls to fetch the navigation properties. This means that the navigation properties cannot be used in sort or filters (this results in a bug where sorting does not work from the UI, when it appears to a user like it should).
The default generated MongoDB repositories currently internally use IQueryable to perform any filtering. However, assuming that a developer wants to enable similar functionality as the EntityFramework repositories or take advantage of the full capabilities of MongoDB, then they should use aggregation pipelines instead and work with an IAggregateFluent
pipeline.
We have found ourselves doing exactly this, and have replaced all of the GetMongoQueryable()
calls in the repositories with an GetMongoAggregated()
extension method to return an IAggregateFluent
.
public static IAggregateFluent<TEntity> GetMongoAggregated<TMongoDbContext, TEntity>(this MongoDbRepository<TMongoDbContext, TEntity> repository)
where TMongoDbContext : IExtendedMongoDbContext
where TEntity : class, IEntity
{
IAggregateFluent<TEntity> aggregate = repository.SessionHandle != null ? repository.Collection.Aggregate(repository.SessionHandle) : repository.Collection.Aggregate();
return aggregate.ApplyDataFilters(repository);
}
private static IAggregateFluent<TEntity> ApplyDataFilters<TMongoDbContext, TEntity>(this IAggregateFluent<TEntity> aggregate, MongoDbRepository<TMongoDbContext, TEntity> repository)
where TMongoDbContext : IExtendedMongoDbContext
where TEntity : class, IEntity
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)) && repository.DataFilter.IsEnabled<ISoftDelete>())
{
aggregate = aggregate.Match(e => ((ISoftDelete)e).IsDeleted == false);
}
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)) && repository.DataFilter.IsEnabled<IMultiTenant>())
{
var tenantId = repository.CurrentTenant.Id;
aggregate = aggregate.Match(e => ((IMultiTenant)e).TenantId == tenantId);
}
return aggregate;
}
The main reason for us to do this was that we can now use the aggregation pipelines to easily "Include" any entities referenced by navigation properties, which can then be used within a single database call (e.g. for sorting) and/or returned within that same query (no additional database queries needed to get the navigation property entities).
Some example usages are:
public async Task<ChannelWithNavigationProperties> GetWithNavigationPropertiesAsync(Guid id, CancellationToken cancellationToken = default)
{
var channel = await this.GetMongoAggregated()
.Match(e => e.Id == id)
.Include<Channel, ProductVersion>(DbContext)
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
return new ChannelWithNavigationProperties
{
Channel = channel,
ProductVersion = channel.GetIncluded<ProductVersion>(),
};
}
public async Task<List<ChannelWithNavigationProperties>> GetListWithNavigationPropertiesAsync(
string filterText = null,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
var query = ApplyFilter(this.GetMongoAggregated(), filterText);
var channels = await query
.Include<Channel, ProductVersion>(DbContext)
.Sort(sorting, ChannelConsts.GetDefaultSorting(false))
.Skip(skipCount).Limit(maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
List<ChannelWithNavigationProperties> result = new List<ChannelWithNavigationProperties>();
foreach (var s in channels)
{
result.Add(new ChannelWithNavigationProperties
{
Channel = s,
ProductVersion = s.GetIncluded<ProductVersion>()
});
}
return result;
}
In the example, a Channel has a "ProductVersionId" navigation property. Any ApplyFilter methods can also easily be changed to use the GetMongoAggregated
extension method for consistency.
We have some extension methods which make this very easy to use.
public static IAggregateFluent<TEntity> Include<TEntity, TLookupEntity>(this IAggregateFluent<TEntity> query, IExtendedMongoDbContext dbContext, string localFieldName = null)
where TEntity : class
{
string entityName = typeof(TLookupEntity).Name;
if (string.IsNullOrEmpty(localFieldName))
{
localFieldName = entityName + "Id";
}
var queryBson = query.As<BsonDocument>();
// Perform a lookup. If it exists then it will be included in an array called entityName
queryBson = queryBson.Lookup(dbContext.GetCollectionNamePublic<TLookupEntity>(), localFieldName, "_id", entityName);
// convert the single value in the array to a property instead (use the options to handle the scenario that the foreign entity does not exist)
queryBson = queryBson.Unwind(new StringFieldDefinition<BsonDocument>(entityName), new AggregateUnwindOptions<BsonDocument>() {PreserveNullAndEmptyArrays = true});
return queryBson.As<TEntity>();
}
public static TEntity GetIncluded<TEntity>(this IHasExtraProperties entity, string fieldName = null) where TEntity : class
{
var properties = entity.ExtraProperties;
if (string.IsNullOrEmpty(fieldName))
{
fieldName = typeof(TEntity).Name;
}
if (properties.TryGetValue(fieldName, out object entityObject) && entityObject is Dictionary<string, object> dictionary)
{
return BsonSerializer.Deserialize<TEntity>(dictionary.ToBsonDocument());
}
return null;
}
/// <summary>
/// Support sort of the format "myColumn asc" or "myOtherColumn desc".
/// The first letter of any sort field is automatically capitalized.
/// Can also sort on multiple columns "myColumn asd, myOtherColumn desc".
/// Can also sort by sub properties "myColumn.something desc"
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="query"></param>
/// <param name="sorting"></param>
/// <param name="defaultSorting"></param>
/// <returns></returns>
public static IAggregateFluent<TEntity> Sort<TEntity>(this IAggregateFluent<TEntity> query, string sorting, string defaultSorting)
{
sorting = string.IsNullOrWhiteSpace(sorting) ? defaultSorting : sorting;
if (string.IsNullOrEmpty(sorting))
return query;
string[] sortParts = sorting.Split(',');
List<SortDefinition<TEntity>> sortDefinitions = new List<SortDefinition<TEntity>>();
// check if a sort definition is sorting using a property of the top level entity
string topLevelEntitySortPrefix = typeof(TEntity).Name + ".";
foreach (var sortPart in sortParts)
{
var parts = sortPart.Split(" ");
if (parts.Length == 0)
continue;
string fieldName = CorrectCase(parts[0]);
if (fieldName.StartsWith(topLevelEntitySortPrefix))
{
fieldName = fieldName.Substring(topLevelEntitySortPrefix.Length);
}
if (parts.Length == 1)
{
sortDefinitions.Add(Builders<TEntity>.Sort.Ascending(fieldName));
}
if (parts.Length > 1)
{
if (PartIsDescending(parts[1]))
{
sortDefinitions.Add(Builders<TEntity>.Sort.Descending(fieldName));
}
else
{
sortDefinitions.Add(Builders<TEntity>.Sort.Ascending(fieldName));
}
}
}
if (sortDefinitions.Any())
{
query = query.Sort(Builders<TEntity>.Sort.Combine(sortDefinitions));
}
return query;
}
private static bool PartIsDescending(string part)
{
if (string.IsNullOrEmpty(part))
return false;
return part.ToLower().Contains("des");
}
public static string CorrectCase(string input)
{
var parts = input.Split('.');
return String.Join(".", parts.Select(FirstCharToUpper));
}
private static string FirstCharToUpper(string input)
{
if (string.IsNullOrEmpty(input))
throw new ArgumentNullException(nameof(input));
return input.First().ToString().ToUpper() + input.Substring(1);
}
The IExtendedMongoDbContext
simply exposes the protected GetCollectionName<T>()
method publicly as GetCollectionNamePublic<T>()
.
The Sort
method supports being able to sort on foreign entity properties and also supports sorting on multiple columns (datatables.net => shift + click).
Whatever the outcome, I hope that this code that I am sharing might be useful for other people wanting to use MongoDB.
Hi,
When we are wanting to search for a user, we are generally replacing the default module repositories with appropriate methods overriden to allow the filter text search to be case insensitive. We are changing the filter to perform a regex (MongoDB) search with the ignore case flag set (new BsonRegularExpression(Regex.Escape(filterText), "i");
).
However, when we wanted to do this for the the MongoOrganizationUnitRepository
, the ...Unadded...
methods are not marked as virtual, so we are unable to override and change their behaviour. Our only option is to replace the whole repository and then make the changes. Could you make these methods virtual please?
Is it the intention that the filters in repositories are case sensitive? Is there a better way to change the search to be case insensitive without overriding each repository?
Mike
I have a single solution which includes a single module. I updated both using the cli to version 4.1. After the update, I resolved the documented breaking change (where repositories had to be passed by interface) in once place.
Then I hit a problem that I am unable to resolve. My module localisation resources are not loaded correctly (menu text is not subsituted) and trying to access a page gives the following error.
AbpException: Could not find the bundle file '/Pages/LicenseManagement/Accounts/index.js' from IWebContentFileProvider Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers.AbpTagHelperResourceService.ProcessAsync(ViewContext viewContext, TagHelperContext context, TagHelperOutput output, List<BundleTagHelperItem> bundleItems, string bundleName) Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers.AbpBundleTagHelperService<TTagHelper, TService>.ProcessAsync(TagHelperContext context, TagHelperOutput output) Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, int i, int count) AspNetCore.Pages_LicenseManagement_Accounts_Index.<ExecuteAsync>b__37_0() in Index.cshtml + <abp-script-bundle name="@typeof(IndexModel).FullName"> Microsoft.AspNetCore.Mvc.Razor.RazorPage.RenderSectionAsyncCore(string sectionName, bool required) AspNetCore.Themes_Lepton_Layouts_Application_Default+<>c__DisplayClass28_0+<<ExecuteAsync>b__1>d.MoveNext() Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync() AspNetCore.Themes_Lepton_Layouts_Application_Default.ExecuteAsync() Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context) Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts) Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderLayoutAsync(ViewContext context, ViewBufferTextWriter bodyWriter) Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultAsync>g__Logged|21_0(ResourceInvoker invoker, IActionResult result) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultFilters>g__Awaited|27_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) Volo.Abp.AspNetCore.Auditing.AbpAuditingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Volo.Abp.AspNetCore.Auditing.AbpAuditingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass6_1+<<UseMiddlewareInterface>b__1>d.MoveNext() Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events, IBackChannelLogoutService backChannelLogoutService) IdentityServer4.Hosting.MutualTlsEndpointMiddleware.Invoke(HttpContext context, IAuthenticationSchemeProvider schemes) Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Builder.ApplicationBuilderAbpJwtTokenMiddlewareExtension+<>c__DisplayClass0_0+<<UseJwtTokenMiddleware>b__0>d.MoveNext() Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.RequestLocalization.AbpRequestLocalizationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass6_1+<<UseMiddlewareInterface>b__1>d.MoveNext() Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)