Browse Source

Merge branch 'rel-4.0' into dev

pull/6255/head
Halil İbrahim Kalkan 6 years ago
parent
commit
3d52feed3b
  1. 41
      docs/en/Tutorials/Part-10.md
  2. 5
      docs/en/Tutorials/Part-2.md
  3. 449
      docs/en/Tutorials/Part-3.md
  4. 4
      docs/en/Tutorials/Part-4.md
  5. 126
      docs/en/Tutorials/Part-5.md
  6. 17
      docs/en/Tutorials/Part-9.md
  7. BIN
      docs/en/Tutorials/images/blazor-bookstore-book-list.png
  8. BIN
      docs/en/Tutorials/images/blazor-delete-book-action.png
  9. BIN
      docs/en/Tutorials/images/blazor-edit-book-action-2.png
  10. 2
      framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj
  11. 4
      framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs
  12. 4
      modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs
  13. 2
      npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts
  14. 2
      nupkg/common.ps1

41
docs/en/Tutorials/Part-10.md

@ -50,7 +50,7 @@ public Guid AuthorId { get; set; }
{{if DB=="EF"}} {{if DB=="EF"}}
> In this tutorial, we preferred to not add a **navigation property** to the `Author` entity from the `Book` class (like `public Author Author { get; set; }`). This is due to follow the DDD best practices (rule: refer to other aggregates only by id). However, you can add such a navigation property and configure it for the EF Core. In this way, you don't need to write join queries while getting books with their authors (just like we will done below) which makes your application code simpler. > In this tutorial, we preferred to not add a **navigation property** to the `Author` entity from the `Book` class (like `public Author Author { get; set; }`). This is due to follow the DDD best practices (rule: refer to other aggregates only by id). However, you can add such a navigation property and configure it for the EF Core. In this way, you don't need to write join queries while getting books with their authors (like we will done below) which makes your application code simpler.
{{end}} {{end}}
@ -362,7 +362,7 @@ namespace Acme.BookStore.Books
DeletePolicyName = BookStorePermissions.Books.Create; DeletePolicyName = BookStorePermissions.Books.Create;
} }
public async override Task<BookDto> GetAsync(Guid id) public override async Task<BookDto> GetAsync(Guid id)
{ {
await CheckGetPolicyAsync(); await CheckGetPolicyAsync();
@ -384,8 +384,8 @@ namespace Acme.BookStore.Books
return bookDto; return bookDto;
} }
public async override Task<PagedResultDto<BookDto>> public override async Task<PagedResultDto<BookDto>> GetListAsync(
GetListAsync(PagedAndSortedResultRequestDto input) PagedAndSortedResultRequestDto input)
{ {
await CheckGetListPolicyAsync(); await CheckGetListPolicyAsync();
@ -1082,36 +1082,37 @@ Add the following field to the `@code` section of the `Books.razor` file:
IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>(); IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>();
```` ````
And fill it in the `OnInitializedAsync` method, by adding the following code to the end of the method: Override the `OnInitializedAsync` method and adding the following code:
````csharp ````csharp
authorList = (await AppService.GetAuthorLookupAsync()).Items; protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
authorList = (await AppService.GetAuthorLookupAsync()).Items;
}
```` ````
* It is essential to call the `base.OnInitializedAsync()` since `AbpCrudPageBase` has some initialization code to be executed.
The final `@code` block should be the following: The final `@code` block should be the following:
````csharp ````csharp
@code @code
{ {
bool canCreateBook;
bool canEditBook;
bool canDeleteBook;
//ADDED A NEW FIELD //ADDED A NEW FIELD
IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>(); IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>();
protected async override Task OnInitializedAsync() public Books() // Constructor
{ {
await base.OnInitializedAsync(); CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
canCreateBook = await DeletePolicyName = BookStorePermissions.Books.Delete;
AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create); }
canEditBook = await
AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Edit);
canDeleteBook = await
AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Delete);
//GET AUTHORS //GET AUTHORS ON INITIALIZATION
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
authorList = (await AppService.GetAuthorLookupAsync()).Items; authorList = (await AppService.GetAuthorLookupAsync()).Items;
} }
} }

5
docs/en/Tutorials/Part-2.md

@ -590,7 +590,6 @@ Open the `Books.razor` and replace the content as the following:
````xml ````xml
@page "/books" @page "/books"
@using Volo.Abp.Application.Dtos @using Volo.Abp.Application.Dtos
@using Volo.Abp.BlazoriseUI
@using Acme.BookStore.Books @using Acme.BookStore.Books
@using Acme.BookStore.Localization @using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization @using Microsoft.Extensions.Localization
@ -620,14 +619,14 @@ Open the `Books.razor` and replace the content as the following:
</DisplayTemplate> </DisplayTemplate>
</DataGridColumn> </DataGridColumn>
<DataGridColumn TItem="BookDto" <DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.PublishDate)" Field="@nameof(BookDto.PublishDate)"
Caption="@L["PublishDate"]"> Caption="@L["PublishDate"]">
<DisplayTemplate> <DisplayTemplate>
@context.PublishDate.ToShortDateString() @context.PublishDate.ToShortDateString()
</DisplayTemplate> </DisplayTemplate>
</DataGridColumn> </DataGridColumn>
<DataGridColumn TItem="BookDto" <DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Price)" Field="@nameof(BookDto.Price)"
Caption="@L["Price"]"> Caption="@L["Price"]">
</DataGridColumn> </DataGridColumn>
<DataGridColumn TItem="BookDto" <DataGridColumn TItem="BookDto"

449
docs/en/Tutorials/Part-3.md

@ -1171,15 +1171,13 @@ Open the `Books.razor` and replace the `<CardHeader>` section with the following
````xml ````xml
<CardHeader> <CardHeader>
<Row> <Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.Is6"> <Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Books"]</h2> <h2>@L["Books"]</h2>
</Column> </Column>
<Column ColumnSize="ColumnSize.Is6"> <Column ColumnSize="ColumnSize.IsAuto">
<Paragraph Alignment="TextAlignment.Right"> <Button Color="Color.Primary"
<Button Color="Color.Primary" Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
</Paragraph>
</Column> </Column>
</Row> </Row>
</CardHeader> </CardHeader>
@ -1196,48 +1194,67 @@ Now, we can add a modal that will be opened when we click to the button.
Open the `Books.razor` and add the following code to the end of the page: Open the `Books.razor` and add the following code to the end of the page:
````xml ````xml
<Modal @ref="CreateModal"> <Modal @ref="@CreateModal">
<ModalBackdrop /> <ModalBackdrop />
<ModalContent IsCentered="true"> <ModalContent IsCentered="true">
<ModalHeader> <Form>
<ModalTitle>@L["NewBook"]</ModalTitle> <ModalHeader>
<CloseButton Clicked="CloseCreateModalAsync" /> <ModalTitle>@L["NewBook"]</ModalTitle>
</ModalHeader> <CloseButton Clicked="CloseCreateModalAsync"/>
<ModalBody> </ModalHeader>
<Field> <ModalBody>
<FieldLabel>@L["Name"]</FieldLabel> <Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<TextEdit @bind-text="NewEntity.Name" /> <Validation MessageLocalizer="@LH.Localize">
</Field> <Field>
<Field> <FieldLabel>@L["Name"]</FieldLabel>
<FieldLabel>@L["Type"]</FieldLabel> <TextEdit @bind-Text="@NewEntity.Name">
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type"> <Feedback>
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType))) <ValidationError/>
{ </Feedback>
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)"> </TextEdit>
@L[$"Enum:BookType:{bookTypeValue}"] </Field>
</SelectItem> </Validation>
} <Field>
</Select> <FieldLabel>@L["Type"]</FieldLabel>
</Field> <Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
<Field> @foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
<FieldLabel>@L["PublishDate"]</FieldLabel> {
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate" /> <SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
</Field> @L[$"Enum:BookType:{bookTypeValue}"]
<Field> </SelectItem>
<FieldLabel>@L["Price"]</FieldLabel> }
<NumericEdit TValue="float" @bind-Value="NewEntity.Price" /> </Select>
</Field> </Field>
</ModalBody> <Field>
<ModalFooter> <FieldLabel>@L["PublishDate"]</FieldLabel>
<Button Color="Color.Secondary" <DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate"/>
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button> </Field>
<Button Color="Color.Primary" <Field>
Clicked="CreateEntityAsync">@L["Save"]</Button> <FieldLabel>@L["Price"]</FieldLabel>
</ModalFooter> <NumericEdit TValue="float" @bind-Value="NewEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent> </ModalContent>
</Modal> </Modal>
```` ````
This code requires a service; Inject the `AbpBlazorMessageLocalizerHelper<T>` at the top of the file, just before the `@inherits...` line:
````csharp
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
````
* The form implements validation and the `AbpBlazorMessageLocalizerHelper` is used to simply localize the validation messages.
* `CreateModal` object, `CloseCreateModalAsync` and `CreateEntityAsync` method are defined by the base class. See the [Blazorise documentation](https://blazorise.com/docs/) if you want to understand the `Modal` and the other components. * `CreateModal` object, `CloseCreateModalAsync` and `CreateEntityAsync` method are defined by the base class. See the [Blazorise documentation](https://blazorise.com/docs/) if you want to understand the `Modal` and the other components.
That's all. Run the application and try to add a new book: That's all. Run the application and try to add a new book:
@ -1250,78 +1267,81 @@ Editing a books is similar to the creating a new book.
### Actions Dropdown ### Actions Dropdown
Open the `Books.razor` and add the following `DataGridColumn` section inside the `DataGridColumns` as the first item: Open the `Books.razor` and add the following `DataGridEntityActionsColumn` section inside the `DataGridColumns` as the first item:
````xml ````xml
<DataGridColumn Width="150px" <DataGridEntityActionsColumn TItem="BookDto" @ref="@EntityActionsColumn">
TItem="BookDto"
Field="@nameof(BookDto.Id)"
Sortable="false"
Caption="@L["Actions"]">
<DisplayTemplate> <DisplayTemplate>
<Dropdown> <EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
<DropdownToggle Color="Color.Primary"> <EntityAction TItem="BookDto"
@L["Actions"] Text="@L["Edit"]"
</DropdownToggle> Clicked="() => OpenEditModalAsync(context)" />
<DropdownMenu> </EntityActions>
<DropdownItem Clicked="() => OpenEditModalAsync(context)">
@L["Edit"]
</DropdownItem>
</DropdownMenu>
</Dropdown>
</DisplayTemplate> </DisplayTemplate>
</DataGridColumn> </DataGridEntityActionsColumn>
```` ````
* `OpenEditModalAsync` is defined in the base class which takes the entity (book) to edit. * `OpenEditModalAsync` is defined in the base class which takes the entity (book) to edit.
This adds an "Actions" dropdown to all the books inside the `DataGrid` with an `Edit` action: `DataGridEntityActionsColumn` component is used to show an "Actions" dropdown for each row in the `DataGrid`. `DataGridEntityActionsColumn` shows a **single button** instead of a dropdown if there is only one available action inside it:
![blazor-edit-book-action](images/blazor-edit-book-action.png) ![blazor-edit-book-action](images/blazor-edit-book-action-2.png)
### Edit Modal ### Edit Modal
We can now define a modal to edit the book. Add the following code to the end of the `Books.razor` page: We can now define a modal to edit the book. Add the following code to the end of the `Books.razor` page:
````xml ````xml
<Modal @ref="EditModal"> <Modal @ref="@EditModal">
<ModalBackdrop /> <ModalBackdrop />
<ModalContent IsCentered="true"> <ModalContent IsCentered="true">
<ModalHeader> <Form>
<ModalTitle>@EditingEntity.Name</ModalTitle> <ModalHeader>
<CloseButton Clicked="CloseEditModalAsync" /> <ModalTitle>@EditingEntity.Name</ModalTitle>
</ModalHeader> <CloseButton Clicked="CloseEditModalAsync"/>
<ModalBody> </ModalHeader>
<Field> <ModalBody>
<FieldLabel>@L["Name"]</FieldLabel> <Validations @ref="@EditValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<TextEdit @bind-text="EditingEntity.Name" /> <Validation MessageLocalizer="@LH.Localize">
</Field> <Field>
<Field> <FieldLabel>@L["Name"]</FieldLabel>
<FieldLabel>@L["Type"]</FieldLabel> <TextEdit @bind-Text="@EditingEntity.Name">
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type"> <Feedback>
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType))) <ValidationError/>
{ </Feedback>
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)"> </TextEdit>
@L[$"Enum:BookType:{bookTypeValue}"] </Field>
</SelectItem> </Validation>
} <Field>
</Select> <FieldLabel>@L["Type"]</FieldLabel>
</Field> <Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
<Field> @foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
<FieldLabel>@L["PublishDate"]</FieldLabel> {
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate" /> <SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
</Field> @L[$"Enum:BookType:{bookTypeValue}"]
<Field> </SelectItem>
<FieldLabel>@L["Price"]</FieldLabel> }
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price" /> </Select>
</Field> </Field>
</ModalBody> <Field>
<ModalFooter> <FieldLabel>@L["PublishDate"]</FieldLabel>
<Button Color="Color.Secondary" <DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate"/>
Clicked="CloseEditModalAsync">@L["Cancel"]</Button> </Field>
<Button Color="Color.Primary" <Field>
Clicked="UpdateEntityAsync">@L["Save"]</Button> <FieldLabel>@L["Price"]</FieldLabel>
</ModalFooter> <NumericEdit TValue="float" @bind-Value="EditingEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent> </ModalContent>
</Modal> </Modal>
```` ````
@ -1356,17 +1376,26 @@ You can now run the application and try to edit a book.
![blazor-edit-book-modal](images/blazor-edit-book-modal.png) ![blazor-edit-book-modal](images/blazor-edit-book-modal.png)
> Tip: Try to leave the *Name* field empty and submit the form to show the validation error message.
## Deleting a Book ## Deleting a Book
Open the `Books.razor` page and add the following `DropdownItem` under the "Edit" action inside the "Actions" `DropdownMenu`: Open the `Books.razor` page and add the following `EntityAction` under the "Edit" action inside the `EntityActions`:
````xml ````xml
<DropdownItem Clicked="() => DeleteEntityAsync(context)"> <EntityAction TItem="BookDto"
@L["Delete"] Text="@L["Delete"]"
</DropdownItem> Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="@() => GetDeleteConfirmationMessage(context)" />
```` ````
* `DeleteEntityAsync` is defined in the base class. * `DeleteEntityAsync` is defined in the base class that deletes the entity by performing a call to the server.
* `ConfirmationMessage` is a callback to show a confirmation message before executing the action.
* `GetDeleteConfirmationMessage` is defined in the base class. You can override this method (or pass another value to the `ConfirmationMessage` parameter) to customize the localization message.
The "Actions" button becomes a dropdown since it has two actions now:
![blazor-edit-book-action](images/blazor-delete-book-action.png)
Run the application and try to delete a book. Run the application and try to delete a book.
@ -1377,26 +1406,22 @@ Here the complete code to create the book management CRUD page, that has been de
````xml ````xml
@page "/books" @page "/books"
@using Volo.Abp.Application.Dtos @using Volo.Abp.Application.Dtos
@using Volo.Abp.BlazoriseUI
@using Acme.BookStore.Books @using Acme.BookStore.Books
@using Acme.BookStore.Localization @using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization @using Microsoft.Extensions.Localization
@inject IStringLocalizer<BookStoreResource> L @inject IStringLocalizer<BookStoreResource> L
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
@inherits AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto> @inherits AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<Card> <Card>
<CardHeader> <CardHeader>
<Row> <Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.Is6"> <Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Books"]</h2> <h2>@L["Books"]</h2>
</Column> </Column>
<Column ColumnSize="ColumnSize.Is6"> <Column ColumnSize="ColumnSize.IsAuto">
<Paragraph Alignment="TextAlignment.Right">
<Button Color="Color.Primary" <Button Color="Color.Primary"
Clicked="OpenCreateModalAsync"> Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
@L["NewBook"]
</Button>
</Paragraph>
</Column> </Column>
</Row> </Row>
</CardHeader> </CardHeader>
@ -1408,27 +1433,19 @@ Here the complete code to create the book management CRUD page, that has been de
ShowPager="true" ShowPager="true"
PageSize="PageSize"> PageSize="PageSize">
<DataGridColumns> <DataGridColumns>
<DataGridColumn Width="150px" <DataGridEntityActionsColumn TItem="BookDto" @ref="@EntityActionsColumn">
TItem="BookDto"
Field="@nameof(BookDto.Id)"
Sortable="false"
Caption="@L["Actions"]">
<DisplayTemplate> <DisplayTemplate>
<Dropdown> <EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
<DropdownToggle Color="Color.Primary"> <EntityAction TItem="BookDto"
@L["Actions"] Text="@L["Edit"]"
</DropdownToggle> Clicked="() => OpenEditModalAsync(context)" />
<DropdownMenu> <EntityAction TItem="BookDto"
<DropdownItem Clicked="() => OpenEditModalAsync(context)"> Text="@L["Delete"]"
@L["Edit"] Clicked="() => DeleteEntityAsync(context)"
</DropdownItem> ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)" />
<DropdownItem Clicked="() => DeleteEntityAsync(context)"> </EntityActions>
@L["Delete"]
</DropdownItem>
</DropdownMenu>
</Dropdown>
</DisplayTemplate> </DisplayTemplate>
</DataGridColumn> </DataGridEntityActionsColumn>
<DataGridColumn TItem="BookDto" <DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Name)" Field="@nameof(BookDto.Name)"
Caption="@L["Name"]"></DataGridColumn> Caption="@L["Name"]"></DataGridColumn>
@ -1436,7 +1453,7 @@ Here the complete code to create the book management CRUD page, that has been de
Field="@nameof(BookDto.Type)" Field="@nameof(BookDto.Type)"
Caption="@L["Type"]"> Caption="@L["Type"]">
<DisplayTemplate> <DisplayTemplate>
@L[$"Enum:BookType:{(int)context.Type}"] @L[$"Enum:BookType:{(int) context.Type}"]
</DisplayTemplate> </DisplayTemplate>
</DataGridColumn> </DataGridColumn>
<DataGridColumn TItem="BookDto" <DataGridColumn TItem="BookDto"
@ -1462,85 +1479,109 @@ Here the complete code to create the book management CRUD page, that has been de
</CardBody> </CardBody>
</Card> </Card>
<Modal @ref="CreateModal"> <Modal @ref="@CreateModal">
<ModalBackdrop /> <ModalBackdrop />
<ModalContent IsCentered="true"> <ModalContent IsCentered="true">
<ModalHeader> <Form>
<ModalTitle>@L["NewBook"]</ModalTitle> <ModalHeader>
<CloseButton Clicked="CloseCreateModalAsync" /> <ModalTitle>@L["NewBook"]</ModalTitle>
</ModalHeader> <CloseButton Clicked="CloseCreateModalAsync"/>
<ModalBody> </ModalHeader>
<Field> <ModalBody>
<FieldLabel>@L["Name"]</FieldLabel> <Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<TextEdit @bind-text="NewEntity.Name" /> <Validation MessageLocalizer="@LH.Localize">
</Field> <Field>
<Field> <FieldLabel>@L["Name"]</FieldLabel>
<FieldLabel>@L["Type"]</FieldLabel> <TextEdit @bind-Text="@NewEntity.Name">
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type"> <Feedback>
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType))) <ValidationError/>
{ </Feedback>
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)"> </TextEdit>
@L[$"Enum:BookType:{bookTypeValue}"] </Field>
</SelectItem> </Validation>
} <Field>
</Select> <FieldLabel>@L["Type"]</FieldLabel>
</Field> <Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
<Field> @foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
<FieldLabel>@L["PublishDate"]</FieldLabel> {
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate" /> <SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
</Field> @L[$"Enum:BookType:{bookTypeValue}"]
<Field> </SelectItem>
<FieldLabel>@L["Price"]</FieldLabel> }
<NumericEdit TValue="float" @bind-Value="NewEntity.Price" /> </Select>
</Field> </Field>
</ModalBody> <Field>
<ModalFooter> <FieldLabel>@L["PublishDate"]</FieldLabel>
<Button Color="Color.Secondary" <DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate"/>
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button> </Field>
<Button Color="Color.Primary" <Field>
Clicked="CreateEntityAsync">@L["Save"]</Button> <FieldLabel>@L["Price"]</FieldLabel>
</ModalFooter> <NumericEdit TValue="float" @bind-Value="NewEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent> </ModalContent>
</Modal> </Modal>
<Modal @ref="EditModal"> <Modal @ref="@EditModal">
<ModalBackdrop /> <ModalBackdrop />
<ModalContent IsCentered="true"> <ModalContent IsCentered="true">
<ModalHeader> <Form>
<ModalTitle>@EditingEntity.Name</ModalTitle> <ModalHeader>
<CloseButton Clicked="CloseEditModalAsync" /> <ModalTitle>@EditingEntity.Name</ModalTitle>
</ModalHeader> <CloseButton Clicked="CloseEditModalAsync"/>
<ModalBody> </ModalHeader>
<Field> <ModalBody>
<FieldLabel>@L["Name"]</FieldLabel> <Validations @ref="@EditValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<TextEdit @bind-text="EditingEntity.Name" /> <Validation MessageLocalizer="@LH.Localize">
</Field> <Field>
<Field> <FieldLabel>@L["Name"]</FieldLabel>
<FieldLabel>@L["Type"]</FieldLabel> <TextEdit @bind-Text="@EditingEntity.Name">
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type"> <Feedback>
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType))) <ValidationError/>
{ </Feedback>
<SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)"> </TextEdit>
@L[$"Enum:BookType:{bookTypeValue}"] </Field>
</SelectItem> </Validation>
} <Field>
</Select> <FieldLabel>@L["Type"]</FieldLabel>
</Field> <Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
<Field> @foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
<FieldLabel>@L["PublishDate"]</FieldLabel> {
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate" /> <SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
</Field> @L[$"Enum:BookType:{bookTypeValue}"]
<Field> </SelectItem>
<FieldLabel>@L["Price"]</FieldLabel> }
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price" /> </Select>
</Field> </Field>
</ModalBody> <Field>
<ModalFooter> <FieldLabel>@L["PublishDate"]</FieldLabel>
<Button Color="Color.Secondary" <DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate"/>
Clicked="CloseEditModalAsync">@L["Cancel"]</Button> </Field>
<Button Color="Color.Primary" <Field>
Clicked="UpdateEntityAsync">@L["Save"]</Button> <FieldLabel>@L["Price"]</FieldLabel>
</ModalFooter> <NumericEdit TValue="float" @bind-Value="EditingEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent> </ModalContent>
</Modal> </Modal>
```` ````

4
docs/en/Tutorials/Part-4.md

@ -126,7 +126,7 @@ public async Task Should_Create_A_Valid_Book()
{ {
Name = "New test book 42", Name = "New test book 42",
Price = 10, Price = 10,
PublishDate = System.DateTime.Now, PublishDate = DateTime.Now,
Type = BookType.ScienceFiction Type = BookType.ScienceFiction
} }
); );
@ -208,7 +208,7 @@ namespace Acme.BookStore.Books
{ {
Name = "New test book 42", Name = "New test book 42",
Price = 10, Price = 10,
PublishDate = System.DateTime.Now, PublishDate = DateTime.Now,
Type = BookType.ScienceFiction Type = BookType.ScienceFiction
} }
); );

126
docs/en/Tutorials/Part-5.md

@ -70,7 +70,7 @@ namespace Acme.BookStore.Permissions
} }
```` ````
This is a hierarchical way of defining permission names. For example, "create book" permission name was defined as `BookStore.Books.Create`. This is a hierarchical way of defining permission names. For example, "create book" permission name was defined as `BookStore.Books.Create`. ABP doesn't force you to a structure, but we find this way useful.
### Permission Definitions ### Permission Definitions
@ -476,29 +476,29 @@ Adding this attribute prevents to enter this page if the current hasn't logged i
The book management page has a *New Book* button and *Edit* and *Delete* actions for each book. We should hide these buttons/actions if the current user has not granted for the related permissions. The book management page has a *New Book* button and *Edit* and *Delete* actions for each book. We should hide these buttons/actions if the current user has not granted for the related permissions.
#### Get the Permissions On Initialization The base `AbpCrudPageBase` class already has the necessary functionality for these kind of operations.
#### Set the Policy (Permission) Names
Add the following code block to the end of the `Books.razor` file: Add the following code block to the end of the `Books.razor` file:
````csharp ````csharp
@code @code
{ {
bool canCreateBook; public Books() // Constructor
bool canEditBook;
bool canDeleteBook;
protected async override Task OnInitializedAsync()
{ {
await base.OnInitializedAsync(); CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
canCreateBook =await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create); DeletePolicyName = BookStorePermissions.Books.Delete;
canEditBook = await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Edit);
canDeleteBook = await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Delete);
} }
} }
```` ````
We will use these `bool` fields to check the permissions. `AuthorizationService` comes from the base class as an injected property. The base `AbpCrudPageBase` class automatically checks these permissions on the related operations. It also defines the given properties for us if we need to check them manually:
* `HasCreatePermission`: True, if the current user has permission to create the entity.
* `HasUpdatePermission`: True, if the current user has permission to edit/update the entity.
* `HasDeletePermission`: True, if the current user has permission to delete the entity.
> **Blazor Tip**: While adding the C# code into a `@code` block is fine for small code parts, it is suggested to use the code behind approach to develop a more maintainable code base when the code block becomes longer. We will use this approach for the authors part. > **Blazor Tip**: While adding the C# code into a `@code` block is fine for small code parts, it is suggested to use the code behind approach to develop a more maintainable code base when the code block becomes longer. We will use this approach for the authors part.
@ -507,32 +507,31 @@ We will use these `bool` fields to check the permissions. `AuthorizationService`
Wrap the *New Book* button by an `if` block as shown below: Wrap the *New Book* button by an `if` block as shown below:
````xml ````xml
@if (canCreateBook) @if (HasCreatePermission)
{ {
<Button Color="Color.Primary" <Button Color="Color.Primary"
Clicked="OpenCreateModalAsync"> Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
@L["NewBook"]
</Button>
} }
```` ````
#### Hide the Edit/Delete Actions #### Hide the Edit/Delete Actions
As similar to the *New Book* button, we can use `if` blocks to conditionally show/hide the *Edit* and *Delete* actions: `EntityAction` component defines `RequiredPolicy` attribute (parameter) to conditionally show the action based on the user permissions.
Update the `EntityActions` section as shown below:
````xml ````xml
@if (canEditBook) <EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
{ <EntityAction TItem="BookDto"
<DropdownItem Clicked="() => OpenEditModalAsync(context)"> Text="@L["Edit"]"
@L["Edit"] RequiredPolicy="@UpdatePolicyName"
</DropdownItem> Clicked="() => OpenEditModalAsync(context)" />
} <EntityAction TItem="BookDto"
@if (canDeleteBook) Text="@L["Delete"]"
{ RequiredPolicy="@DeletePolicyName"
<DropdownItem Clicked="() => DeleteEntityAsync(context)"> Clicked="() => DeleteEntityAsync(context)"
@L["Delete"] ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)" />
</DropdownItem> </EntityActions>
}
```` ````
#### About the Permission Caching #### About the Permission Caching
@ -587,54 +586,39 @@ if (await context.IsGrantedAsync(BookStorePermissions.Books.Default))
} }
```` ````
You also need to add `async` keyword to the `ConfigureMenuAsync` method and re-arrange the return values. The final `BookStoreMenuContributor` class should be the following: You also need to add `async` keyword to the `ConfigureMenuAsync` method and re-arrange the return value. The final `ConfigureMainMenuAsync` method should be the following:
````csharp ````csharp
using System.Threading.Tasks; private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
using Acme.BookStore.Localization;
using Acme.BookStore.Permissions;
using Volo.Abp.UI.Navigation;
namespace Acme.BookStore.Blazor
{ {
public class BookStoreMenuContributor : IMenuContributor var l = context.GetLocalizer<BookStoreResource>();
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if(context.Menu.DisplayName != StandardMenus.Main)
{
return;
}
var l = context.GetLocalizer<BookStoreResource>(); context.Menu.Items.Insert(
0,
context.Menu.Items.Insert( new ApplicationMenuItem(
0, "BookStore.Home",
new ApplicationMenuItem( l["Menu:Home"],
"BookStore.Home", "/",
l["Menu:Home"], icon: "fas fa-home"
"/", )
icon: "fas fa-home" );
)
);
var bookStoreMenu = new ApplicationMenuItem( var bookStoreMenu = new ApplicationMenuItem(
"BooksStore", "BooksStore",
l["Menu:BookStore"], l["Menu:BookStore"],
icon: "fa fa-book" icon: "fa fa-book"
); );
context.Menu.AddItem(bookStoreMenu); context.Menu.AddItem(bookStoreMenu);
if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) //CHECK the PERMISSION
{ if (await context.IsGrantedAsync(BookStorePermissions.Books.Default))
bookStoreMenu.AddItem(new ApplicationMenuItem( {
"BooksStore.Books", bookStoreMenu.AddItem(new ApplicationMenuItem(
l["Menu:Books"], "BooksStore.Books",
url: "/books" l["Menu:Books"],
)); url: "/books"
} ));
}
} }
} }
```` ````

17
docs/en/Tutorials/Part-9.md

@ -843,15 +843,8 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the `Acme.BookStor
````xml ````xml
@page "/authors" @page "/authors"
@using Acme.BookStore.Authors @using Acme.BookStore.Authors
@using Acme.BookStore.Localization @inherits BookStoreComponentBase
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Extensions.Localization
@using Volo.Abp.ObjectMapping
@inject IAuthorAppService AuthorAppService @inject IAuthorAppService AuthorAppService
@inject IStringLocalizer<BookStoreResource> L
@inject IAuthorizationService AuthorizationService
@inject IUiMessageService UiMessageService
@inject IObjectMapper ObjectMapper
<Card> <Card>
<CardHeader> <CardHeader>
<Row> <Row>
@ -1038,7 +1031,7 @@ namespace Acme.BookStore.Blazor.Pages
EditingAuthor = new UpdateAuthorDto(); EditingAuthor = new UpdateAuthorDto();
} }
protected async override Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await SetPermissionsAsync(); await SetPermissionsAsync();
await GetAuthorsAsync(); await GetAuthorsAsync();
@ -1048,10 +1041,10 @@ namespace Acme.BookStore.Blazor.Pages
{ {
CanCreateAuthor = await AuthorizationService CanCreateAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Create); .IsGrantedAsync(BookStorePermissions.Authors.Create);
CanEditAuthor = await AuthorizationService CanEditAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Edit); .IsGrantedAsync(BookStorePermissions.Authors.Edit);
CanDeleteAuthor = await AuthorizationService CanDeleteAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Delete); .IsGrantedAsync(BookStorePermissions.Authors.Delete);
} }
@ -1105,7 +1098,7 @@ namespace Acme.BookStore.Blazor.Pages
private async Task DeleteAuthorAsync(AuthorDto author) private async Task DeleteAuthorAsync(AuthorDto author)
{ {
var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name]; var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name];
if (!await UiMessageService.ConfirmAsync(confirmMessage)) if (!await Message.Confirm(confirmMessage))
{ {
return; return;
} }

BIN
docs/en/Tutorials/images/blazor-bookstore-book-list.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 60 KiB

BIN
docs/en/Tutorials/images/blazor-delete-book-action.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/en/Tutorials/images/blazor-edit-book-action-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

2
framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj

@ -19,7 +19,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="5.0.0-alpha.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

4
framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs

@ -14,11 +14,11 @@ namespace Volo.Abp.EntityFrameworkCore
{ {
if (context.ExistingConnection != null) if (context.ExistingConnection != null)
{ {
return context.DbContextOptions.UseMySql(context.ExistingConnection, mySQLOptionsAction); return context.DbContextOptions.UseMySql(context.ExistingConnection, ServerVersion.AutoDetect(context.ConnectionString), mySQLOptionsAction);
} }
else else
{ {
return context.DbContextOptions.UseMySql(context.ConnectionString, mySQLOptionsAction); return context.DbContextOptions.UseMySql(context.ConnectionString, ServerVersion.AutoDetect(context.ConnectionString), mySQLOptionsAction);
} }
} }
} }

4
modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs

@ -176,6 +176,10 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore
b.HasKey(x => new {x.ClientId, x.Key, x.Value}); b.HasKey(x => new {x.ClientId, x.Key, x.Value});
b.Property(x => x.Key).HasMaxLength(ClientPropertyConsts.KeyMaxLength).IsRequired(); b.Property(x => x.Key).HasMaxLength(ClientPropertyConsts.KeyMaxLength).IsRequired();
if (IsDatabaseProvider(builder, options, EfCoreDatabaseProvider.MySql))
{
ClientPropertyConsts.ValueMaxLength = 300;
}
b.Property(x => x.Value).HasMaxLength(ClientPropertyConsts.ValueMaxLength).IsRequired(); b.Property(x => x.Value).HasMaxLength(ClientPropertyConsts.ValueMaxLength).IsRequired();
}); });

2
npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts

@ -99,7 +99,7 @@ export class FeatureManagementComponent
Object.keys(this.features).forEach(key => { Object.keys(this.features).forEach(key => {
this.features[key].forEach(feature => { this.features[key].forEach(feature => {
if (feature.value !== feature.initialValue) if (feature.value !== feature.initialValue)
changedFeatures.push({ name: feature.name, value: feature.value }); changedFeatures.push({ name: feature.name, value: `${feature.value}` });
}); });
}); });

2
nupkg/common.ps1

@ -85,7 +85,7 @@ $projects = (
"framework/src/Volo.Abp.Ddd.Domain", "framework/src/Volo.Abp.Ddd.Domain",
"framework/src/Volo.Abp.Emailing", "framework/src/Volo.Abp.Emailing",
"framework/src/Volo.Abp.EntityFrameworkCore", "framework/src/Volo.Abp.EntityFrameworkCore",
# "framework/src/Volo.Abp.EntityFrameworkCore.MySQL", "framework/src/Volo.Abp.EntityFrameworkCore.MySQL",
# "framework/src/Volo.Abp.EntityFrameworkCore.Oracle", # "framework/src/Volo.Abp.EntityFrameworkCore.Oracle",
# "framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart", # "framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart",
"framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql", "framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql",

Loading…
Cancel
Save