Merge branch 'dev' into async-uow-transaction

pull/6809/head
Halil İbrahim Kalkan 4 years ago
commit d76c853ad3

@ -37,7 +37,7 @@
"CreateArticleTitleInfo": "Title of the article to be shown on the article list.",
"CreateArticleUrlInfo": "Original GitHub/External URL of the article.",
"CreateArticleSummaryInfo": "A short summary of the article to be shown on the article list.",
"CreateArticleCoverInfo": "For creating an effective article, add a cover photo. Upload 16:9 aspect ratio pictures for the best view.",
"CreateArticleCoverInfo": "For creating an effective article, add a cover photo. Upload 16:9 aspect ratio pictures for the best view. Maximum file size: 1MB.",
"ThisExtensionIsNotAllowed": "This extension is not allowed.",
"TheFileIsTooLarge": "The file is too large.",
"GoToTheArticle": "Go to the Article",

@ -735,6 +735,47 @@ Configure<AbpDbContextOptions>(options =>
});
````
### Customize Bulk Operations
If you have better logic or using an external library for bulk operations, you can override the logic via implementing`IEfCoreBulkOperationProvider`.
- You may use example template below:
```csharp
public class MyCustomEfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency
{
public async Task DeleteManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task InsertManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task UpdateManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
{
// Your logic here.
}
}
```
## See Also
* [Entities](Entities.md)

@ -382,3 +382,44 @@ context.Services.AddMongoDbContext<OtherMongoDbContext>(options =>
```
In this example, `OtherMongoDbContext` implements `IBookStoreMongoDbContext`. This feature allows you to have multiple MongoDbContext (one per module) on development, but single MongoDbContext (implements all interfaces of all MongoDbContexts) on runtime.
### Customize Bulk Operations
If you have better logic or using an external library for bulk operations, you can override the logic via implementing `IMongoDbBulkOperationProvider`.
- You may use example template below:
```csharp
public class MyCustomMongoDbBulkOperationProvider : IMongoDbBulkOperationProvider, ITransientDependency
{
public async Task DeleteManyAsync<TEntity>(IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task InsertManyAsync<TEntity>(IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task UpdateManyAsync<TEntity>(IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// Your logic here.
}
}
```

@ -87,6 +87,11 @@ If your entity is a soft-delete entity, you can use the `HardDeleteAsync` method
See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete.
## Bulk Operations
You can execute bulk operations with `InsertManyAsync`, `UpdateManyAsync`, `DeleteManyAsync` methods.
> **WARNING:** ConcurrencyStamp can't be checked at bulk operations!
## Custom Repositories
Default generic repositories will be sufficient for most cases. However, you may need to create a custom repository class for your entity.

@ -1,3 +1,9 @@
@using Volo.Abp.Ui.Branding
@using Volo.Abp.Ui.Branding
@inject IBrandingProvider BrandingProvider
<a class="navbar-brand" href="">@BrandingProvider.AppName</a>
<a class="navbar-brand" href="">
@if (!BrandingProvider.LogoUrl.IsNullOrWhiteSpace())
{
<img src="@BrandingProvider.LogoUrl" alt="@BrandingProvider.AppName" >
}
@BrandingProvider.AppName
</a>

@ -10,7 +10,7 @@
if (MenuItem.Url != null)
{
<li class="nav-item @cssClass @disabled" id="@elementId">
<a class="nav-link" href="@url">
<a class="nav-link" href="@url" target="@MenuItem.Target">
@if (MenuItem.Icon != null)
{
if (MenuItem.Icon.StartsWith("fa"))

@ -3,6 +3,7 @@
@using Volo.Abp.MultiTenancy
@inject ICurrentUser CurrentUser
@inject ICurrentTenant CurrentTenant
@inject IJSRuntime JsRuntime
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
@ -23,7 +24,7 @@
{
@foreach (var menuItem in Menu.Items)
{
<DropdownItem Clicked="@(() => NavigateTo(menuItem.Url))">@menuItem.DisplayName</DropdownItem>
<DropdownItem Clicked="@(() => NavigateToAsync(menuItem.Url, menuItem.Target))">@menuItem.DisplayName</DropdownItem>
}
}
<DropdownDivider />
@ -37,14 +38,21 @@
</AuthorizeView>
@code{
private void NavigateTo(string uri)
private async Task NavigateToAsync(string uri, string target = null)
{
if (target == "_blank")
{
await JsRuntime.InvokeVoidAsync("open", uri, target);
}
else
{
Navigation.NavigateTo(uri);
}
}
private async Task BeginSignOut()
{
await SignOutManager.SetSignOutState();
NavigateTo("authentication/logout");
await NavigateToAsync("authentication/logout");
}
}

@ -9,7 +9,7 @@
{
if (MenuItem.Url != null)
{
<a class="dropdown-item @cssClass @disabled" href="@url" id="@elementId">
<a class="dropdown-item @cssClass @disabled" href="@url" target="@MenuItem.Target" id="@elementId">
@if (MenuItem.Icon != null)
{
if (MenuItem.Icon.StartsWith("fa"))

@ -4,11 +4,13 @@ using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
@ -20,15 +22,18 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
private readonly HtmlEncoder _htmlEncoder;
private readonly IHtmlGenerator _htmlGenerator;
private readonly IServiceProvider _serviceProvider;
private readonly IStringLocalizer<AbpUiResource> _localizer;
public AbpDynamicFormTagHelperService(
HtmlEncoder htmlEncoder,
IHtmlGenerator htmlGenerator,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
IStringLocalizer<AbpUiResource> localizer)
{
_htmlEncoder = htmlEncoder;
_htmlGenerator = htmlGenerator;
_serviceProvider = serviceProvider;
_localizer = localizer;
}
public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@ -191,7 +196,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
var abpButtonTagHelper = _serviceProvider.GetRequiredService<AbpButtonTagHelper>();
var attributes = new TagHelperAttributeList { new TagHelperAttribute("type", "submit") };
abpButtonTagHelper.Text = "Submit";
abpButtonTagHelper.Text = _localizer["Submit"];
abpButtonTagHelper.ButtonType = AbpButtonType.Primary;
return await abpButtonTagHelper.RenderAsync(attributes, context, _htmlEncoder, "button", TagMode.StartTagAndEndTag);

@ -1,3 +1,9 @@
@using Volo.Abp.Ui.Branding
@inject IBrandingProvider BrandingProvider
<a class="navbar-brand" href="~/">@BrandingProvider.AppName</a>
<a class="navbar-brand" href="~/">
@if (!BrandingProvider.LogoUrl.IsNullOrWhiteSpace())
{
<img src="@BrandingProvider.LogoUrl" alt="@BrandingProvider.AppName" >
}
@BrandingProvider.AppName
</a>

@ -16,6 +16,7 @@ using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.Uow;
using Volo.Abp.Users;
@ -125,6 +126,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
protected ILogger Logger => _lazyLogger.Value;
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);
protected IAppUrlProvider AppUrlProvider => LazyGetRequiredService(ref _appUrlProvider);
private IAppUrlProvider _appUrlProvider;
protected virtual NoContentResult NoContent() //TODO: Is that true to return empty result like that?
{
return new NoContentResult();
@ -165,5 +169,42 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
return localizer;
}
protected RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
{
return Redirect(GetRedirectUrl(returnUrl, returnUrlHash));
}
protected virtual string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (!returnUrlHash.IsNullOrWhiteSpace())
{
returnUrl = returnUrl + returnUrlHash;
}
return returnUrl;
}
private string NormalizeReturnUrl(string returnUrl)
{
if (returnUrl.IsNullOrEmpty())
{
return GetAppHomeUrl();
}
if (Url.IsLocalUrl(returnUrl) || AppUrlProvider.IsRedirectAllowedUrl(returnUrl))
{
return returnUrl;
}
return GetAppHomeUrl();
}
protected virtual string GetAppHomeUrl()
{
return "~/"; //TODO: ???
}
}
}

@ -23,7 +23,7 @@
<ProjectReference Include="..\Volo.Abp.AspNetCore\Volo.Abp.AspNetCore.csproj" />
<ProjectReference Include="..\Volo.Abp.GlobalFeatures\Volo.Abp.GlobalFeatures.csproj" />
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
<ProjectReference Include="..\Volo.Abp.UI\Volo.Abp.UI.csproj" />
<ProjectReference Include="..\Volo.Abp.UI.Navigation\Volo.Abp.UI.Navigation.csproj" />
</ItemGroup>
<ItemGroup>

@ -39,6 +39,7 @@ using Volo.Abp.Json;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.UI;
using Volo.Abp.UI.Navigation;
namespace Volo.Abp.AspNetCore.Mvc
{
@ -47,7 +48,7 @@ namespace Volo.Abp.AspNetCore.Mvc
typeof(AbpLocalizationModule),
typeof(AbpApiVersioningAbstractionsModule),
typeof(AbpAspNetCoreMvcContractsModule),
typeof(AbpUiModule),
typeof(AbpUiNavigationModule),
typeof(AbpGlobalFeaturesModule)
)]
public class AbpAspNetCoreMvcModule : AbpModule

@ -14,6 +14,7 @@ using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Timing;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.Uow;
using Volo.Abp.Users;
@ -115,6 +116,9 @@ namespace Volo.Abp.AspNetCore.Mvc
}
private IStringLocalizer _localizer;
protected IAppUrlProvider AppUrlProvider => LazyGetRequiredService(ref _appUrlProvider);
private IAppUrlProvider _appUrlProvider;
protected Type LocalizationResource
{
get => _localizationResource;
@ -148,5 +152,42 @@ namespace Volo.Abp.AspNetCore.Mvc
return localizer;
}
protected RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
{
return Redirect(GetRedirectUrl(returnUrl, returnUrlHash));
}
private string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (!returnUrlHash.IsNullOrWhiteSpace())
{
returnUrl = returnUrl + returnUrlHash;
}
return returnUrl;
}
private string NormalizeReturnUrl(string returnUrl)
{
if (returnUrl.IsNullOrEmpty())
{
return GetAppHomeUrl();
}
if (Url.IsLocalUrl(returnUrl) || AppUrlProvider.IsRedirectAllowedUrl(returnUrl))
{
return returnUrl;
}
return GetAppHomeUrl();
}
protected virtual string GetAppHomeUrl()
{
return "~/";
}
}
}

@ -66,42 +66,5 @@ namespace Volo.Abp.AspNetCore.Mvc.Authentication
return NoContent();
}
protected RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
{
return Redirect(GetRedirectUrl(returnUrl, returnUrlHash));
}
private string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (!returnUrlHash.IsNullOrWhiteSpace())
{
returnUrl = returnUrl + returnUrlHash;
}
return returnUrl;
}
private string NormalizeReturnUrl(string returnUrl)
{
if (returnUrl.IsNullOrEmpty())
{
return GetAppHomeUrl();
}
if (Url.IsLocalUrl(returnUrl))
{
return returnUrl;
}
return GetAppHomeUrl();
}
protected virtual string GetAppHomeUrl()
{
return "~/";
}
}
}

@ -133,7 +133,7 @@ namespace System
{
if (str.IsNullOrEmpty())
{
return null;
return str;
}
if (postFixes.IsNullOrEmpty())
@ -174,7 +174,7 @@ namespace System
{
if (str.IsNullOrEmpty())
{
return null;
return str;
}
if (preFixes.IsNullOrEmpty())

@ -1,9 +1,13 @@
using System;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Linq;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
@ -17,6 +21,14 @@ namespace Volo.Abp.Domain.Repositories
{
public IServiceProvider ServiceProvider { get; set; }
public IDataFilter DataFilter { get; set; }
public ICurrentTenant CurrentTenant { get; set; }
public IAsyncQueryableExecuter AsyncExecuter { get; set; }
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
public ICancellationTokenProvider CancellationTokenProvider { get; set; }
protected BasicRepositoryBase()
@ -26,10 +38,59 @@ namespace Volo.Abp.Domain.Repositories
public abstract Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
public virtual async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await InsertAsync(entity, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
protected virtual Task SaveChangesAsync(CancellationToken cancellationToken)
{
if (UnitOfWorkManager?.Current != null)
{
return UnitOfWorkManager.Current.SaveChangesAsync(cancellationToken);
}
return Task.CompletedTask;
}
public abstract Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
public virtual async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await UpdateAsync(entity, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
public abstract Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
public virtual async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await DeleteAsync(entity, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
public abstract Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default);
public abstract Task<long> GetCountAsync(CancellationToken cancellationToken = default);
@ -69,5 +130,18 @@ namespace Volo.Abp.Domain.Repositories
await DeleteAsync(entity, autoSave, cancellationToken);
}
public async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var id in ids)
{
await DeleteAsync(id, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
}
}

@ -1,4 +1,5 @@
using System.Threading;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Domain.Entities;
@ -20,6 +21,18 @@ namespace Volo.Abp.Domain.Repositories
[NotNull]
Task<TEntity> InsertAsync([NotNull] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Inserts multiple new entities.
/// </summary>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <param name="entities">Entities to be inserted.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task InsertManyAsync([NotNull] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Updates an existing entity.
/// </summary>
@ -32,6 +45,17 @@ namespace Volo.Abp.Domain.Repositories
[NotNull]
Task<TEntity> UpdateAsync([NotNull] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Updates multiple entities.
/// </summary>
/// <param name="entities">Entities to be updated.</param>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.</param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task UpdateManyAsync([NotNull] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes an entity.
/// </summary>
@ -42,6 +66,18 @@ namespace Volo.Abp.Domain.Repositories
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
Task DeleteAsync([NotNull] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes multiple entities.
/// </summary>
/// <param name="entities">Entities to be deleted.</param>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task DeleteManyAsync([NotNull] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
}
public interface IBasicRepository<TEntity, TKey> : IBasicRepository<TEntity>, IReadOnlyBasicRepository<TEntity, TKey>
@ -57,5 +93,17 @@ namespace Volo.Abp.Domain.Repositories
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default); //TODO: Return true if deleted
/// <summary>
/// Deletes multiple entities by primary keys.
/// </summary>
/// <param name="ids">Primary keys of the each entity.</param>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default);
}
}

@ -1,4 +1,5 @@
using System;
using JetBrains.Annotations;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@ -16,14 +17,6 @@ namespace Volo.Abp.Domain.Repositories
public abstract class RepositoryBase<TEntity> : BasicRepositoryBase<TEntity>, IRepository<TEntity>, IUnitOfWorkManagerAccessor
where TEntity : class, IEntity
{
public IDataFilter DataFilter { get; set; }
public ICurrentTenant CurrentTenant { get; set; }
public IAsyncQueryableExecuter AsyncExecuter { get; set; }
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
[Obsolete("This method will be removed in future versions.")]
public virtual Type ElementType => GetQueryable().ElementType;
@ -129,5 +122,18 @@ namespace Volo.Abp.Domain.Repositories
await DeleteAsync(entity, autoSave, cancellationToken);
}
public async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var id in ids)
{
await DeleteAsync(id, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
}
}

@ -1,13 +1,15 @@
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
@ -55,6 +57,8 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
public virtual IGuidGenerator GuidGenerator { get; set; }
public IEfCoreBulkOperationProvider BulkOperationProvider { get; set; }
public EfCoreRepository(IDbContextProvider<TDbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
@ -84,6 +88,32 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
return savedEntity;
}
public override async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
CheckAndSetId(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.InsertManyAsync<TDbContext, TEntity>(
this,
entities,
autoSave,
cancellationToken
);
return;
}
await DbSet.AddRangeAsync(entities);
if (autoSave)
{
await DbContext.SaveChangesAsync();
}
}
public override async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
var dbContext = await GetDbContextAsync();
@ -100,6 +130,28 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
return updatedEntity;
}
public override async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
if (BulkOperationProvider != null)
{
await BulkOperationProvider.UpdateManyAsync<TDbContext, TEntity>(
this,
entities,
autoSave,
cancellationToken
);
return;
}
DbSet.UpdateRange(entities);
if (autoSave)
{
await DbContext.SaveChangesAsync();
}
}
public override async Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
var dbContext = await GetDbContextAsync();
@ -112,6 +164,27 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
}
}
public override async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
if (BulkOperationProvider != null)
{
await BulkOperationProvider.DeleteManyAsync<TDbContext, TEntity>(
this,
entities,
autoSave,
cancellationToken);
return;
}
DbSet.RemoveRange(entities);
if (autoSave)
{
await DbContext.SaveChangesAsync();
}
}
public override async Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
{
return includeDetails
@ -152,6 +225,11 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
return (await GetDbSetAsync()).AsQueryable();
}
protected override Task SaveChangesAsync(CancellationToken cancellationToken)
{
return DbContext.SaveChangesAsync(cancellationToken);
}
public override async Task<TEntity> FindAsync(
Expression<Func<TEntity, bool>> predicate,
bool includeDetails = true,
@ -318,5 +396,12 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
await DeleteAsync(entity, autoSave, cancellationToken);
}
public async virtual Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
var entities = await DbSet.Where(x => ids.Contains(x.Id)).ToListAsync();
await DeleteManyAsync(entities, autoSave, cancellationToken);
}
}
}

@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
{
public interface IEfCoreBulkOperationProvider
{
Task InsertManyAsync<TDbContext, TEntity>(
IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken
)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity;
Task UpdateManyAsync<TDbContext, TEntity>(
IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken
)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity;
Task DeleteManyAsync<TDbContext, TEntity>(
IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken
)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity;
}
}

@ -70,7 +70,14 @@ namespace Volo.Abp.Http.ProxyScripting
private string CreateCacheKey(ProxyScriptingModel model)
{
return _jsonSerializer.Serialize(model).ToMd5();
return _jsonSerializer.Serialize(new
{
model.GeneratorType,
model.Modules,
model.Controllers,
model.Actions,
model.Properties
}).ToMd5();
}
}
}

@ -1,3 +1,4 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
@ -315,5 +316,11 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
{
await DeleteAsync(x => x.Id.Equals(id), autoSave, cancellationToken);
}
public virtual async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
var entities = await AsyncExecuter.ToListAsync(GetQueryable().Where(x => ids.Contains(x.Id)));
DeleteManyAsync(entities, autoSave, cancellationToken);
}
}
}

@ -0,0 +1,39 @@
using MongoDB.Driver;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.MongoDB;
namespace Volo.Abp.MongoDB.Volo.Abp.Domain.Repositories.MongoDB
{
public interface IMongoDbBulkOperationProvider
{
Task InsertManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken
)
where TEntity : class, IEntity;
Task UpdateManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken
)
where TEntity : class, IEntity;
Task DeleteManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken
)
where TEntity : class, IEntity;
}
}

@ -14,5 +14,26 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
FilterDefinition<TEntity> CreateEntityFilter(TKey id, bool applyFilters = false);
FilterDefinition<TEntity> CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null);
/// <summary>
/// Creates filter for given entities.
/// </summary>
/// <remarks>
/// Visit https://docs.mongodb.com/manual/reference/operator/query/in/ to get more information about 'in' operator.
/// </remarks>
/// <param name="entities">Entities to be filtered.</param>
/// <param name="applyFilters">Set true to use GlobalFilters. Default is false.</param>
/// <returns>Created <see cref="FilterDefinition{TDocument}"/>.</returns>
FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool applyFilters = false);
/// <summary>
/// Creates filter for given ids.
/// </summary>
/// <remarks>
/// Visit https://docs.mongodb.com/manual/reference/operator/query/in/ to get more information about 'in' operator.
/// </remarks>
/// <param name="ids">Entity Ids to be filtered.</param>
/// <param name="applyFilters">Set true to use GlobalFilters. Default is false.</param>
FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TKey> ids, bool applyFilters = false);
}
}

@ -1,3 +1,4 @@
using JetBrains.Annotations;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using System;
@ -15,6 +16,7 @@ using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Guids;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB.Volo.Abp.Domain.Repositories.MongoDB;
namespace Volo.Abp.Domain.Repositories.MongoDB
{
@ -69,6 +71,8 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
public IAuditPropertySetter AuditPropertySetter { get; set; }
public IMongoDbBulkOperationProvider BulkOperationProvider { get; set; }
public MongoDbRepository(IMongoDbContextProvider<TMongoDbContext> dbContextProvider)
{
DbContextProvider = dbContextProvider;
@ -108,6 +112,34 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return entity;
}
public override async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await ApplyAbpConceptsForAddedEntityAsync(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.InsertManyAsync(this, entities, SessionHandle, autoSave, cancellationToken);
return;
}
if (SessionHandle != null)
{
await Collection.InsertManyAsync(
SessionHandle,
entities,
cancellationToken: cancellationToken);
}
else
{
await Collection.InsertManyAsync(
entities,
cancellationToken: cancellationToken);
}
}
public override async Task<TEntity> UpdateAsync(
TEntity entity,
bool autoSave = false,
@ -159,6 +191,59 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return entity;
}
public override async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
var isSoftDeleteEntity = typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity));
foreach (var entity in entities)
{
SetModificationAuditProperties(entity);
if (isSoftDeleteEntity)
{
SetDeletionAuditProperties(entity);
await TriggerEntityDeleteEventsAsync(entity);
}
else
{
await TriggerEntityUpdateEventsAsync(entity);
}
await TriggerDomainEventsAsync(entity);
SetNewConcurrencyStamp(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.UpdateManyAsync(this, entities, SessionHandle, autoSave, cancellationToken);
return;
}
var entitiesCount = entities.Count();
BulkWriteResult result;
List<WriteModel<TEntity>> replaceRequests = new List<WriteModel<TEntity>>();
foreach (var entity in entities)
{
replaceRequests.Add(new ReplaceOneModel<TEntity>(CreateEntityFilter(entity), entity));
}
if (SessionHandle != null)
{
result = await Collection.BulkWriteAsync(SessionHandle, replaceRequests);
}
else
{
result = await Collection.BulkWriteAsync(replaceRequests);
}
if (result.MatchedCount < entitiesCount)
{
ThrowOptimisticConcurrencyException();
}
}
public override async Task DeleteAsync(
TEntity entity,
bool autoSave = false,
@ -225,6 +310,73 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
}
}
public override async Task DeleteManyAsync(
IEnumerable<TEntity> entities,
bool autoSave = false,
CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await ApplyAbpConceptsForDeletedEntityAsync(entity);
var oldConcurrencyStamp = SetNewConcurrencyStamp(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.DeleteManyAsync(this, entities, SessionHandle, autoSave, cancellationToken);
return;
}
var entitiesCount = entities.Count();
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
UpdateResult updateResult;
if (SessionHandle != null)
{
updateResult = await Collection.UpdateManyAsync(
SessionHandle,
CreateEntitiesFilter(entities),
Builders<TEntity>.Update.Set(x => ((ISoftDelete)x).IsDeleted, true)
);
}
else
{
updateResult = await Collection.UpdateManyAsync(
CreateEntitiesFilter(entities),
Builders<TEntity>.Update.Set(x => ((ISoftDelete)x).IsDeleted, true)
);
}
if (updateResult.MatchedCount < entitiesCount)
{
ThrowOptimisticConcurrencyException();
}
}
else
{
DeleteResult deleteResult;
if (SessionHandle != null)
{
deleteResult = await Collection.DeleteManyAsync(
SessionHandle,
CreateEntitiesFilter(entities)
);
}
else
{
deleteResult = await Collection.DeleteManyAsync(
CreateEntitiesFilter(entities)
);
}
if (deleteResult.DeletedCount < entitiesCount)
{
ThrowOptimisticConcurrencyException();
}
}
}
public override async Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
{
cancellationToken = GetCancellationToken(cancellationToken);
@ -327,6 +479,13 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
);
}
protected virtual FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool withConcurrencyStamp = false)
{
throw new NotImplementedException(
$"{nameof(CreateEntitiesFilter)} is not implemented for MongoDB by default. It should be overriden and implemented by the deriving class!"
);
}
protected virtual async Task ApplyAbpConceptsForAddedEntityAsync(TEntity entity)
{
CheckAndSetId(entity);
@ -528,9 +687,23 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return DeleteAsync(x => x.Id.Equals(id), autoSave, cancellationToken);
}
public virtual async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
var entities = await GetMongoQueryable()
.Where(x => ids.Contains(x.Id))
.ToListAsync(GetCancellationToken(cancellationToken));
await DeleteManyAsync(entities, autoSave, cancellationToken);
}
protected override FilterDefinition<TEntity> CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null)
{
return RepositoryFilterer.CreateEntityFilter(entity, withConcurrencyStamp, concurrencyStamp);
}
protected override FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool withConcurrencyStamp = false)
{
return RepositoryFilterer.CreateEntitiesFilter(entities, withConcurrencyStamp);
}
}
}

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
@ -23,13 +24,13 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)) && DataFilter.IsEnabled<ISoftDelete>())
{
filters.Add(Builders<TEntity>.Filter.Eq(e => ((ISoftDelete) e).IsDeleted, false));
filters.Add(Builders<TEntity>.Filter.Eq(e => ((ISoftDelete)e).IsDeleted, false));
}
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
var tenantId = CurrentTenant.Id;
filters.Add(Builders<TEntity>.Filter.Eq(e => ((IMultiTenant) e).TenantId, tenantId));
filters.Add(Builders<TEntity>.Filter.Eq(e => ((IMultiTenant)e).TenantId, tenantId));
}
}
}
@ -72,8 +73,28 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return Builders<TEntity>.Filter.And(
Builders<TEntity>.Filter.Eq(e => e.Id, entity.Id),
Builders<TEntity>.Filter.Eq(e => ((IHasConcurrencyStamp) e).ConcurrencyStamp, concurrencyStamp)
Builders<TEntity>.Filter.Eq(e => ((IHasConcurrencyStamp)e).ConcurrencyStamp, concurrencyStamp)
);
}
public FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool applyFilters = false)
{
return CreateEntitiesFilter(entities.Select(s => s.Id), applyFilters);
}
public FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TKey> ids, bool applyFilters = false)
{
var filters = new List<FilterDefinition<TEntity>>()
{
Builders<TEntity>.Filter.In(e => e.Id, ids),
};
if (applyFilters)
{
AddGlobalFilters(filters);
}
return Builders<TEntity>.Filter.And(filters);
}
}
}

@ -1,12 +1,17 @@
namespace Volo.Abp.UI.Navigation.Urls
using System.Collections.Generic;
namespace Volo.Abp.UI.Navigation.Urls
{
public class AppUrlOptions
{
public ApplicationUrlDictionary Applications { get; }
public List<string> RedirectAllowedUrls { get; }
public AppUrlOptions()
{
Applications = new ApplicationUrlDictionary();
RedirectAllowedUrls = new List<string>();
}
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
@ -36,6 +37,11 @@ namespace Volo.Abp.UI.Navigation.Urls
);
}
public bool IsRedirectAllowedUrl(string url)
{
return Options.RedirectAllowedUrls.Any(url.StartsWith);
}
protected virtual Task<string> GetConfiguredUrl(string appName, string urlName)
{
var app = Options.Applications[appName];

@ -6,5 +6,7 @@ namespace Volo.Abp.UI.Navigation.Urls
public interface IAppUrlProvider
{
Task<string> GetUrlAsync([NotNull] string appName, [CanBeNull] string urlName = null);
bool IsRedirectAllowedUrl(string url);
}
}

@ -182,7 +182,10 @@ namespace System
public void RemovePostFix_Tests()
{
//null case
(null as string).RemovePreFix("Test").ShouldBeNull();
(null as string).RemovePostFix("Test").ShouldBeNull();
//empty case
string.Empty.RemovePostFix("Test").ShouldBe(string.Empty);
//Simple case
"MyTestAppService".RemovePostFix("AppService").ShouldBe("MyTest");
@ -202,7 +205,13 @@ namespace System
[Fact]
public void RemovePreFix_Tests()
{
"Home.Index".RemovePreFix("NotMatchedPostfix").ShouldBe("Home.Index");
//null case
(null as string).RemovePreFix("Test").ShouldBeNull();
//empty case
string.Empty.RemovePreFix("Test").ShouldBe(string.Empty);
"Home.Index".RemovePreFix("NotMatchedPrefix").ShouldBe("Home.Index");
"Home.About".RemovePreFix("Home.").ShouldBe("About");
//Ignore case

@ -4,6 +4,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
@ -311,6 +312,11 @@ namespace Volo.Abp.Domain.Repositories
{
throw new NotImplementedException();
}
public Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
public class MyTestCustomBaseRepository<TEntity> : MyTestDefaultRepository<TEntity>

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Domain.Repositories;
@ -115,5 +117,72 @@ namespace Volo.Abp.TestApp.Testing
person.Id.ShouldNotBe(Guid.Empty);
}
[Fact]
public async Task InserManyAsync()
{
var entities = new List<Person>
{
new Person(Guid.NewGuid(), "Person 1", 30),
new Person(Guid.NewGuid(), "Person 2", 31),
new Person(Guid.NewGuid(), "Person 3", 32),
new Person(Guid.NewGuid(), "Person 4", 33),
};
await PersonRepository.InsertManyAsync(entities);
foreach (var entity in entities)
{
var person = await PersonRepository.FindAsync(entity.Id);
person.ShouldNotBeNull();
}
}
[Fact]
public async Task UpdateManyAsync()
{
var entities = await PersonRepository.GetListAsync();
var random = new Random();
entities.ForEach(f => f.Age = random.Next());
await PersonRepository.UpdateManyAsync(entities);
foreach (var entity in entities)
{
var person = await PersonRepository.FindAsync(entity.Id);
person.ShouldNotBeNull();
person.Age.ShouldBe(entity.Age);
}
}
[Fact]
public async Task DeleteManyAsync()
{
var entities = await PersonRepository.GetListAsync();
await PersonRepository.DeleteManyAsync(entities);
foreach (var entity in entities)
{
var person = await PersonRepository.FindAsync(entity.Id);
person.ShouldBeNull();
}
}
[Fact]
public async Task DeleteManyAsync_WithId()
{
var entities = await PersonRepository.GetListAsync();
var ids = entities.Select(s => s.Id).ToArray();
await PersonRepository.DeleteManyAsync(ids);
foreach (var id in ids)
{
var person = await PersonRepository.FindAsync(id);
person.ShouldBeNull();
}
}
}
}

@ -32,22 +32,23 @@
<ul class="list-group">
@for (var i = 0; i < Model.ConsentInput.IdentityScopes.Count; i++)
{
var identityScope = Model.ConsentInput.IdentityScopes[i];
<li class="list-group-item">
<div class="form-check">
<label asp-for="@Model.ConsentInput.IdentityScopes[i].Checked" class="form-check-label">
<input asp-for="@Model.ConsentInput.IdentityScopes[i].Checked" class="form-check-input" />
@Model.ConsentInput.IdentityScopes[i].DisplayName
@if (Model.ConsentInput.IdentityScopes[i].Required)
<label asp-for="@identityScope.Checked" for="ConsentInput_IdentityScopes_@(i)__Checked" class="form-check-label">
<input abp-id-name="@Model.ConsentInput.IdentityScopes[i].Checked" asp-for="@identityScope.Checked" class="form-check-input" />
@identityScope.DisplayName
@if (identityScope.Required)
{
<span><em>(required)</em></span>
}
</label>
</div>
<input asp-for="@Model.ConsentInput.IdentityScopes[i].Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@
@if (Model.ConsentInput.IdentityScopes[i].Description != null)
<input abp-id-name="@Model.ConsentInput.IdentityScopes[i].Name" asp-for="@identityScope.Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@
@if (identityScope.Description != null)
{
<div class="consent-description">
@Model.ConsentInput.IdentityScopes[i].Description
@identityScope.Description
</div>
}
</li>
@ -62,22 +63,23 @@
<ul class="list-group">
@for (var i = 0; i < Model.ConsentInput.ApiScopes.Count; i++)
{
var apiScope = Model.ConsentInput.ApiScopes[i];
<li class="list-group-item">
<div class="form-check">
<label asp-for="@Model.ConsentInput.ApiScopes[i].Checked" class="form-check-label">
<input asp-for="@Model.ConsentInput.ApiScopes[i].Checked" class="form-check-input" disabled="@Model.ConsentInput.ApiScopes[i].Required" />
@Model.ConsentInput.ApiScopes[i].DisplayName
@if (Model.ConsentInput.ApiScopes[i].Required)
<label asp-for="@apiScope.Checked" for="ConsentInput_ApiScopes_@(i)__Checked" class="form-check-label">
<input abp-id-name="@Model.ConsentInput.ApiScopes[i].Checked" asp-for="@apiScope.Checked" class="form-check-input" disabled="@apiScope.Required" />
@apiScope.DisplayName
@if (apiScope.Required)
{
<span><em>(required)</em></span>
}
</label>
</div>
<input asp-for="@Model.ConsentInput.ApiScopes[i].Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@
@if (Model.ConsentInput.ApiScopes[i].Description != null)
<input abp-id-name="@Model.ConsentInput.ApiScopes[i].Name" asp-for="@apiScope.Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@
@if (apiScope.Description != null)
{
<div class="consent-description">
@Model.ConsentInput.ApiScopes[i].Description
@apiScope.Description
</div>
}
</li>

@ -25,11 +25,6 @@ namespace Volo.Abp.Account.Web.Pages.Account
ObjectMapperContext = typeof(AbpAccountWebModule);
}
protected virtual RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
{
return Redirect(GetRedirectUrl(returnUrl, returnUrlHash));
}
protected virtual void CheckIdentityErrors(IdentityResult identityResult)
{
if (!identityResult.Succeeded)
@ -40,33 +35,6 @@ namespace Volo.Abp.Account.Web.Pages.Account
//identityResult.CheckErrors(LocalizationManager); //TODO: Get from old Abp
}
protected virtual string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (!returnUrlHash.IsNullOrWhiteSpace())
{
returnUrl = returnUrl + returnUrlHash;
}
return returnUrl;
}
protected virtual string NormalizeReturnUrl(string returnUrl)
{
if (returnUrl.IsNullOrEmpty())
{
return GetAppHomeUrl();
}
if (Url.IsLocalUrl(returnUrl))
{
return returnUrl;
}
return GetAppHomeUrl();
}
protected virtual void CheckCurrentTenant(Guid? tenantId)
{
if (CurrentTenant.Id != tenantId)
@ -74,10 +42,5 @@ namespace Volo.Abp.Account.Web.Pages.Account
throw new ApplicationException($"Current tenant is different than given tenant. CurrentTenant.Id: {CurrentTenant.Id}, given tenantId: {tenantId}");
}
}
protected virtual string GetAppHomeUrl()
{
return "~/"; //TODO: ???
}
}
}

@ -32,14 +32,15 @@
<abp-tabs name="FeaturesTabs" tab-style="PillVertical" vertical-header-size="_4" class="custom-scroll-container">
@for (var i = 0; i < featureGroups.Count; i++)
{
<abp-tab title="@featureGroups[i].DisplayName" name="v-pills-tab-@featureGroups[i].GetNormalizedGroupName()">
<h4>@featureGroups[i].DisplayName</h4>
var featureGroup = featureGroups[i];
<abp-tab title="@featureGroup.DisplayName" name="v-pills-tab-@featureGroup.GetNormalizedGroupName()">
<h4>@featureGroup.DisplayName</h4>
<hr class="mt-2 mb-3"/>
<div class="custom-scroll-content">
<div class="pl-1 pt-1">
@for (var j = 0; j < featureGroups[i].Features.Count; j++)
@for (var j = 0; j < featureGroup.Features.Count; j++)
{
var feature = featureGroups[i].Features[j];
var feature = featureGroup.Features[j];
var disabled = Model.IsDisabled(feature.Provider.Name);
<div class="mt-2">
@ -48,7 +49,7 @@
@if (feature.ValueType is ToggleStringValueType)
{
<abp-input asp-for="@featureGroups[i].Features[j].Value"
<abp-input asp-for="@feature.Value"
type="checkbox"
name="FeatureGroups[@i].Features[@j].BoolValue"
label="@feature.DisplayName"
@ -67,8 +68,9 @@
type = "number";
}
<abp-input asp-for="@featureGroups[i].Features[j].Value"
<abp-input asp-for="@feature.Value"
label="@feature.DisplayName"
name="featureGroups[@i].Features[@j].Value"
disabled="@disabled"
type="@type"
group-data-feature-name="@feature.Name"

@ -59,8 +59,9 @@
<abp-tab title="@L["Roles"].Value">
@for (var i = 0; i < Model.Roles.Length; i++)
{
<abp-input asp-for="@Model.Roles[i].IsAssigned" label="@Model.Roles[i].Name" />
<input asp-for="@Model.Roles[i].Name" />
var role = Model.Roles[i];
<abp-input abp-id-name="@Model.Roles[i].IsAssigned" asp-for="@role.IsAssigned" label="@role.Name" />
<input abp-id-name="@Model.Roles[i].Name" asp-for="@role.Name" />
}
</abp-tab>
</abp-tabs>

@ -60,8 +60,9 @@
<abp-tab title="@L["Roles"].Value">
@for (var i = 0; i < Model.Roles.Length; i++)
{
<abp-input asp-for="@Model.Roles[i].IsAssigned" label="@Model.Roles[i].Name" />
<input asp-for="@Model.Roles[i].Name" />
var role = Model.Roles[i];
<abp-input abp-id-name="@Model.Roles[i].IsAssigned" asp-for="@role.IsAssigned" label="@role.Name" />
<input abp-id-name="@Model.Roles[i].Name" asp-for="@role.Name" />
}
</abp-tab>
</abp-tabs>

@ -20,25 +20,28 @@
<abp-tabs name="PermissionsTabs" tab-style="PillVertical" vertical-header-size="_4" class="custom-scroll-container">
@for (var i = 0; i < Model.Groups.Count; i++)
{
<abp-tab title="@Model.Groups[i].DisplayName" name="v-pills-tab-@Model.Groups[i].GetNormalizedGroupName()">
<h4>@Model.Groups[i].DisplayName</h4>
var group = Model.Groups[i];
<abp-tab title="@group.DisplayName" name="v-pills-tab-@group.GetNormalizedGroupName()">
<h4>@group.DisplayName</h4>
<hr class="mt-2 mb-3"/>
<div class="custom-scroll-content">
<div class="pl-1 pt-1">
<abp-input asp-for="@Model.Groups[i].IsAllPermissionsGranted" name="SelectAllInThisTab"
id="SelectAllInThisTab-@Model.Groups[i].GetNormalizedGroupName()"
data-tab-id="v-pills-tab-@Model.Groups[i].GetNormalizedGroupName()"
<abp-input asp-for="@group.IsAllPermissionsGranted" name="SelectAllInThisTab"
id="SelectAllInThisTab-@group.GetNormalizedGroupName()"
data-tab-id="v-pills-tab-@group.GetNormalizedGroupName()"
label="@L["SelectAllInThisTab"].Value"/>
<hr class="mb-3"/>
@for (var j = 0; j < Model.Groups[i].Permissions.Count; j++)
@for (var j = 0; j < group.Permissions.Count; j++)
{
<abp-input asp-for="@Model.Groups[i].Permissions[j].IsGranted"
label="@Model.Groups[i].Permissions[j].GetShownName(Model.ProviderName)"
disabled="@Model.Groups[i].Permissions[j].IsDisabled(Model.ProviderName)"
group-data-permission-name="@Model.Groups[i].Permissions[j].Name"
group-data-parent-name="@(Model.Groups[i].Permissions[j].ParentName ?? "")"
group-style="margin-left: @(Model.Groups[i].Permissions[j].Depth * 20)px"/>
<input asp-for="@Model.Groups[i].Permissions[j].Name"/>
var permission = group.Permissions[j];
<abp-input asp-for="@permission.IsGranted"
abp-id-name="@Model.Groups[i].Permissions[j].IsGranted"
label="@permission.GetShownName(Model.ProviderName)"
disabled="@permission.IsDisabled(Model.ProviderName)"
group-data-permission-name="@permission.Name"
group-data-parent-name="@(permission.ParentName ?? "")"
group-style="margin-left: @(permission.Depth * 20)px"/>
<input asp-for="@permission.Name" abp-id-name="@Model.Groups[i].Permissions[j].Name"/>
}
</div>
</div>

@ -77,6 +77,7 @@ namespace MyCompanyName.MyProjectName
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));
});
}

@ -1,7 +1,8 @@
{
"App": {
"SelfUrl": "https://localhost:44305",
"CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307"
"CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307",
"RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307"
},
"ConnectionStrings": {
"Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true"

@ -112,6 +112,7 @@ namespace MyCompanyName.MyProjectName
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));
});
Configure<AbpBackgroundJobOptions>(options =>

@ -1,7 +1,8 @@
{
"App": {
"SelfUrl": "https://localhost:44301",
"CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307,https://localhost:44300"
"CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307,https://localhost:44300",
"RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307"
},
"ConnectionStrings": {
"Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true"

@ -33,7 +33,7 @@ namespace AbpPerfTest.WithAbp
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto;
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
});
}

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using AbpPerfTest.WithAbp.Dtos;
using AbpPerfTest.WithAbp.Entities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories;
namespace AbpPerfTest.WithAbp.Controllers
@ -22,7 +23,7 @@ namespace AbpPerfTest.WithAbp.Controllers
[HttpGet]
public async Task<List<BookDto>> GetListAsync()
{
var books = await _bookRepository.GetPagedListAsync(0, 10, "Id");
var books = await _bookRepository.OrderBy(x => x.Id).Take(10).ToListAsync();
return books
.Select(b => new BookDto

@ -26,15 +26,54 @@
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Books" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create a book" enabled="true">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{&#xd;
&quot;name&quot;: &quot;${__RandomString(5, abcdefghijklmnopqrstuvwxyz)}&quot;,&#xd;
&quot;price&quot;: ${__Random(1,999)},&#xd;
&quot;isAvailable&quot;: true&#xd;
}&#xd;
</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5001</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Get book id" enabled="true">
<stringProp name="JSONPostProcessor.referenceNames">BookId</stringProp>
<stringProp name="JSONPostProcessor.jsonPathExprs">$</stringProp>
<stringProp name="JSONPostProcessor.match_numbers"></stringProp>
<stringProp name="Scope.variable"></stringProp>
</JSONPostProcessor>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get book by id" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5001</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books</stringProp>
<stringProp name="HTTPSampler.path">api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -45,16 +84,16 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Book" enabled="true">
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update a book" enabled="false">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{&#xd;
&quot;name&quot;: &quot;My book&quot;,&#xd;
&quot;price&quot;: &quot;33&quot;,&#xd;
&quot;isAvailable&quot;: &quot;true&quot;&#xd;
&quot;name&quot;: &quot;${__RandomString(5, abcdefghijklmnopqrstuvwxyz)}&quot;,&#xd;
&quot;price&quot;: ${__Random(1,999)},&#xd;
&quot;isAvailable&quot;: true&#xd;
}&#xd;
</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
@ -65,8 +104,27 @@
<stringProp name="HTTPSampler.port">5001</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<stringProp name="HTTPSampler.path">/api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">PUT</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get books" enabled="false">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5001</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
@ -76,6 +134,26 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Delete a book" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5001</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">DELETE</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
@ -113,7 +191,6 @@
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">

@ -26,15 +26,54 @@
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Books" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create a book" enabled="true">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{&#xd;
&quot;name&quot;: &quot;${__RandomString(5, abcdefghijklmnopqrstuvwxyz)}&quot;,&#xd;
&quot;price&quot;: ${__Random(1,999)},&#xd;
&quot;isAvailable&quot;: true&#xd;
}&#xd;
</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5003</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Get book id" enabled="true">
<stringProp name="JSONPostProcessor.referenceNames">BookId</stringProp>
<stringProp name="JSONPostProcessor.jsonPathExprs">$</stringProp>
<stringProp name="JSONPostProcessor.match_numbers"></stringProp>
<stringProp name="Scope.variable"></stringProp>
</JSONPostProcessor>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get book by id" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5003</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books</stringProp>
<stringProp name="HTTPSampler.path">api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
@ -45,15 +84,15 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Book" enabled="true">
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update a book" enabled="false">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{&#xd;
&quot;name&quot;: &quot;My book&quot;,&#xd;
&quot;price&quot;: 33,&#xd;
&quot;name&quot;: &quot;${__RandomString(5, abcdefghijklmnopqrstuvwxyz)}&quot;,&#xd;
&quot;price&quot;: ${__Random(1,999)},&#xd;
&quot;isAvailable&quot;: true&#xd;
}&#xd;
</stringProp>
@ -65,8 +104,8 @@
<stringProp name="HTTPSampler.port">5003</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<stringProp name="HTTPSampler.path">/api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">PUT</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
@ -76,6 +115,45 @@
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get books" enabled="false">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5003</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Delete a book" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5003</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">DELETE</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
@ -113,7 +191,6 @@
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">

Loading…
Cancel
Save