Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Adding AddPhone and DeletePhone Methods

We are adding two more methods to IPersonAppService interface as shown
below:

```csharp
Task DeletePhone(EntityDto<long> input);
Task<PhoneInPersonListDto> AddPhone(AddPhoneInput input);
```

We could create a new, separated IPhoneAppService. It's your choice.
But, we can consider Person as an aggregate and add phone related
methods here. AddPhoneInput DTO is shown below:

```csharp
public class AddPhoneInput
{
[Range(1, int.MaxValue)]
public int PersonId { get; set; }

[Required]
public PhoneType Type { get; set; }

[Required]
[MaxLength(PhoneConsts.MaxNumberLength)]
public string Number { get; set; }
}
```

We used PhoneConsts.MaxNumberLength for Number field. You should create
this consts in **.Core.Shared**.

```csharp
public class PhoneConsts
{
public const int MaxNumberLength = 16;
}
```

Now, we can implement these methods:

```csharp
private readonly IRepository<Person> _personRepository;
private readonly IRepository<Phone, long> _phoneRepository;

public PersonAppService(IRepository<Person> personRepository, IRepository<Phone, long> phoneRepository)
{
_personRepository = personRepository;
_phoneRepository = phoneRepository;
}

[AbpAuthorize(AppPermissions.Pages_Administration_PhoneBook_DeletePhone)]
public async Task DeletePhone(EntityDto<long> input)
{
await _phoneRepository.DeleteAsync(input.Id);
}

[AbpAuthorize(AppPermissions.Pages_Administration_PhoneBook_AddPhone)]
public async Task<PhoneInPersonListDto> AddPhone(AddPhoneInput input)
{
var person = _personRepository.Get(input.PersonId);
await _personRepository.EnsureCollectionLoadedAsync(person, p => p.Phones);

var phone = ObjectMapper.Map<Phone>(input);
person.Phones.Add(phone);

//Get auto increment Id of the new Phone by saving to database
await CurrentUnitOfWork.SaveChangesAsync();

return ObjectMapper.Map<PhoneInPersonListDto>(phone);
}
```

Then we add configuration for AutoMapper into CustomDtoMapper.cs like below:

```csharp
configuration.CreateMap<AddPhoneInput, Phone>();
```

A permission should have a unique name. We define permission names as constant strings in **AppPermissions** class. It's a simple constant string:

```csharp
public const string Pages_Administration_PhoneBook_DeletePhone = "Pages.Administration.DeletePhone";
public const string Pages_Administration_PhoneBook_AddPhone = "Pages.Administration.AddPhone";
```

Go to **AppAuthorizationProvider** class in the server side and add a new permission as shown below (you can add just below the dashboard permission):

```csharp
phoneBook.CreateChildPermission(AppPermissions.Pages_Administration_PhoneBook_DeletePhone, L("DeletePhone"));
phoneBook.CreateChildPermission(AppPermissions.Pages_Administration_PhoneBook_AddPhone, L("AddPhone"));
```

**DeletePhone** method is simple. It only deletes phone with given id.

**AddPhone** method **gets** the person from database and add new phone
to person.Phones collection. Then is **save changes**. Saving changes
causes inserting new added phone to database and get its **Id**.
Because, we are returning a DTO that contains newly created phone
informations including Id. So, it should be assigned before mapping in
the last line. (Notice that; normally it's not needed to call
CurrentUnitOfWork.SaveChangesAsync. It's automatically called at the end
of the method. We called it in the method since we need to save entity
and get its Id immediately. See [UOW
document](https://aspnetboilerplate.com/Pages/Documents/Unit-Of-Work#DocAutoSaveChanges)
for more.)

There may be different approaches for AddPhone method. You can directly
work with a **phone repository** to insert new phone. They all have
different pros and cons. It's your choice.

## Next

- [Edit Mode for Phone Numbers](Developing-Step-By-Step-React-Edit-Mode-Phone-Numbers)
74 changes: 74 additions & 0 deletions docs/en/Developing-Step-By-Step-React-Adding-New-Menu-Item.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Adding a New Menu Item

Let's begin from UI and create a new page named "**Phone book**".

## Defining a Menu Item

Open **src\\lib\\navigation\\appNavigation.tsx** in the client side which defines menu items in the application. Create new menu item as shown below (You can add it right after the dashboard menu item).

```typescript
export const buildRawMenu = (): AppMenuItem[] => [
{
id: "PhoneBook",
title: L("PhoneBook"),
icon: "book",
route: "/app/admin/phonebook",
},
];
```

**PhoneBook** is the menu name , permisson(will set it later), **book** is just an arbitrary icon class (from [this set](http://keenthemes.com/metronic/preview/?page=components/icons/flaticon&demo=default)) and **/phonebook** is the React route.

If you run the application, you can see a new menu item on the left menu, but it won't work (it redirects to default route) if you click to the menu item, since we haven't defined the React route yet.

## Localize Menu Item Display Name

Localization strings are defined in **XML** files in **.Core** project in server side as shown below:

<img src="images/localization-files-6.png" alt="Localization files" class="img-thumbnail" />

Open PhoneBookDemo.xml (the **default**, **English** localization dictionary) and add the following line:

```xml
<text name="PhoneBook">Phone Book</text>
<text name="PhoneBooksHeaderInfo">Phone Book Details</text>
```

If we don't define "PhoneBook"s value for other localization dictionaries, default value is shown in all languages. For example, we can define it also for Turkish in `PhoneBookDmo-tr.xml` file:

```xml
<text name="PhoneBook">Telefon Rehberi</text>
<text name="PhoneBooksHeaderInfo">Telefon Rehberi Detayları</text>
```

Note: Any change in server side (including change localization texts) requires recycle of the server application. We suggest to use Ctrl+F5 if you don't need to debugging for a faster startup. In that case, it's
enough to make a re-build to recycle the application.

## React Route

React has a powerful URL routing system. ASP.NET Zero has defined routes in a few places (for modularity, see [main menu & layout](Features-React-Main-Menu-Layout.md)). We want to add phone book page to the admin component. So, open **src\\routes\\AppRouter.tsx** in the client side and add a new route just below to the dashboard:

```typescript
const AppRouter = () => {
return (
<Suspense fallback={<LoadingSpinner />}>
<BrowserRouter>
<Routes>
<Route path="/app" element={<ProtectedRoute />}>
<Route element={<AppLayout />}>
{/* Other Routes */}
<Route path="admin/phonebook" element={<PhoneBookPage />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
</Suspense>
);
};
```

We get an error since we haven't defined PhonebookPage yet. Also, we ignored permission for now (will implement later).

## Next

- [Creating the PhoneBook Component](Developing-Step-By-Step-React-Creating-PhoneBook-Component)
8 changes: 8 additions & 0 deletions docs/en/Developing-Step-By-Step-React-Adding-Phone-Numbers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Adding Phone Numbers

Until now, we have not even mentioned phone numbers. It's time to
extend our domain to support **multiple phone numbers** for a person.

## Next

- [Creating Phone Entity](Developing-Step-By-Step-React-Creating-Phone-Entity)
155 changes: 155 additions & 0 deletions docs/en/Developing-Step-By-Step-React-Authorization-PhoneBook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Authorization For Phone Book

At this point, anyone can enter phone book page since no authorization
defined. We will define two permission:

- A permission to **enter phone book page**.
- A permission to **create new person** (which is a child permission
of first one, as naturally).

## Permission for Entering Phone Book Page

### Define the permission

A permission should have a unique name. We define permission names as constant strings in **AppPermissions** class. It's a simple constant string:

```csharp
public const string Pages_Administration_PhoneBook = "Pages.Administration.PhoneBook";
```

Go to **AppAuthorizationProvider** class in the server side and add a new permission as shown below (you can add just below the dashboard permission):

```csharp
pages.CreateChildPermission(AppPermissions.Pages_Administration_PhoneBook, L("PhoneBook"));
```

Unique name of this permission is "**Pages.Administration.PhoneBook**". While you can set any string (as long as it's unique), it's suggested to use that convention. A permission can have a localizable display name: "**PhoneBook**" here. (See "Adding a New Page" section for more about localization, since it's very similar). Lastly, we set this as a **administration** level permission.

### Add AbpAuthorize attribute

**AbpAuthorize** attribute can be used as **class level** or **method level** to protect an application service or service method from unauthorized users. Since all server side code is located in `PersonAppService` class, we can declare a class level attribute as shown below:

```csharp
[AbpAuthorize(AppPermissions.Pages_Administration_PhoneBook)]
public class PersonAppService : PhoneBookAppServiceBase, IPersonAppService
{
//...
}
```

Admin role has every static permission by default but those permissions can be reversible on user interface for this role. Go to Roles page, edit role named "admin", go to Permissions tab and revoke "Phone Book" permission and save.

Now, let's try to enter Phone Book page by clicking the menu item without required permission:

<img src="images/phonebook-permission-error.png" alt="Permission error" class="img-thumbnail" width="505" height="412" />

We get an error message. This exception is thrown when any method of `PersonAppService` is called without required permission.

### Hide Unauthorized Menu Item

While user can not enter to the page, the menu item still there! We should also **hide** the Phone book **menu item**. It's easy, open **src/lib/navigation/appNavigation.tsx** and add change PhoneBook menu definition as shown below:

```typescript
{
id: "PhoneBook",
title: L("PhoneBook"),
permissionName: "Pages.Administration.PhoneBook",
icon: "book",
route: "/app/admin/phonebook",
},
```

### Grant permission

So, how we can enter the page now? Simple, go to **Role Management** page and edit **admin** role:

<img src="images/role-permissions-with-phonebook-react.png" alt="Role permissions" class="img-thumbnail" />

We see that a **new permission** named "**Phone book**" added to **permissions** tab. So, we can check it and save the role. After saving, we need to **refresh** the whole page to refresh permissions for the current user. We could also grant this permission to a specific user. Now, we can enter the Phone book page again.

## Permission for Create New Person

While a permission for a page is useful and probably always needed, we may want to define additional permissions to perform some **specific actions** on a page, like creating a new person.

### Define the Permission

First permission was defined before. In the second line, we are creating a child permission of first one. Remember to create a constant in `AppPermissions` class:

```csharp
public const string Pages_Administration_PhoneBook_CreatePerson = "Pages.Administration.PhoneBook.CreatePerson";
```

Defining a permission is similar (in the `AppAuthorizationProvider` class):

```csharp
var phoneBook = pages.CreateChildPermission(AppPermissions.Pages_Administration_PhoneBook, L("PhoneBook"));
phoneBook.CreateChildPermission(AppPermissions.Pages_Administration_PhoneBook_CreatePerson, L("CreateNewPerson"));
```

### Add AbpAuthorize Attribute

This time, we're declaring **AbpAuthorize** attribute just for **CreatePerson** method:

```csharp
[AbpAuthorize(AppPermissions.Pages_Administration_PhoneBook_CreatePerson)]
public async Task CreatePerson(CreatePersonInput input)
{
//...
}
```

### Hide Unauthorized Button

If we run the application and try to create a person, we get an authorization error after clicking the save button. But, it's good to **completely hide Create New Person button** if we don't have the permission. It's very simple:

Open the **index.tsx** view and add the permission **Pages.Administration.PhoneBook.CreatePerson** condition as shown below:

```typescript
import { usePermissions } from "@/hooks/usePermissions";
import {
PersonListDto,
PersonServiceProxy,
useServiceProxy,
} from "@/api/service-proxy-factory";
import CreatePersonModal from "./CreatePersonModal";

const PhoneBookPage: React.FC = () => {
const { isGranted } = usePermissions();
// Other codes

return (
<>
{/* Other Codes */}
<div className="card-toolbar ms-auto">
{isGranted("Pages.Administration.PhoneBook.CreatePerson") && (
<button
className="btn btn-primary"
type="button"
onClick={handleCreatePerson}
>
<i className="fa fa-plus"></i> {L("CreateNewPerson")}
</button>
)}
</div>
{/* Other Codes */}
</>
);
};

export default PhoneBookPage;
```


In this way, the "Create New Person" button is not rendered in server and user can not see this button.

### Grant permission

To see the button again, we can go to role or user manager and grant related permission as shown below:

<img src="images/user-permissions-phonebook-react.png" alt="Role specific permissions" class="img-thumbnail" />

As shown above, **Create new person** permission is a child permission of the **Phone book**. Remember to refresh page to get permissions updated.

## Next

- [Deleting a Person](Developing-Step-By-Step-React-Deleting-Person)
Loading