Merge branch 'master' into dev

pull/1672/head
Halil İbrahim Kalkan 6 years ago
commit 3ed2e7ef2f

@ -1,6 +1,6 @@
# ABP v0.19 Release with Angular UI Option
# ABP v0.19 Release With New Angular UI
ABP v0.19 has been released with [~90 issues](https://github.com/abpframework/abp/milestone/17?closed=1) resolved and [600+ commits](https://github.com/abpframework/abp/compare/0.18.1...0.19.0) pushed.
ABP v0.19 has been released with [90 issues](https://github.com/abpframework/abp/milestone/17?closed=1) resolved and [650+ commits](https://github.com/abpframework/abp/compare/0.18.1...0.19.0) pushed.
## New Features

@ -6,7 +6,7 @@ Explore the left navigation menu to deep dive in the documentation.
## Project Status
ABP is the **next generation** of the open source [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework. It's currently in early preview stage and not ready to use in production. The documentation is still in progress and it is far from complete.
ABP is the **next generation** of the open source [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework. It's currently in preview stage and not ready to use in production. The documentation is still in progress and it is far from complete.
For short-term and production level applications, it's suggested to use [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework which has rich feature set, mature, actively maintained and up-to-date.
@ -14,7 +14,8 @@ For short-term and production level applications, it's suggested to use [ASP.NET
Easiest way to start a new project with ABP is to use the startup templates:
* [ASP.NET Core MVC Template](Getting-Started-AspNetCore-MVC-Template.md)
* [ASP.NET Core MVC (Razor Pages) UI Template](Getting-Started-AspNetCore-MVC-Template.md)
* [Angular UI Template](Getting-Started-Angular-Template.md)
If you want to start from scratch (with an empty project) then manually install the ABP Framework and use the following tutorials:

@ -5,7 +5,8 @@
This template provides a layered application structure based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices. This document explains the solution structure and projects in details. If you want to start quickly, follow the guides below:
* See [Getting Started With the ASP.NET Core MVC Template](../Getting-Started-AspNetCore-MVC-Template.md) to create a new solution and run it for this template (uses MVC as the UI framework and Entity Framework Core as the database provider).
* See the [ASP.NET Core MVC Tutorial](../Tutorials/AspNetCore-Mvc/Part-I.md) to learn how to develop applications using this template (uses MVC as the UI framework and Entity Framework Core as the database provider).
* See the [ASP.NET Core MVC Application Development Tutorial](../Tutorials/AspNetCore-Mvc/Part-I.md) to learn how to develop applications using this template (uses MVC as the UI framework and Entity Framework Core as the database provider).
* See the [Angular Application Development Tutorial](../Tutorials/Angular/Part-I.md) to learn how to develop applications using this template (uses Angular as the UI framework and MongoDB as the database provider).
## How to Start With?

@ -20,7 +20,7 @@ Create a new project named `Acme.BookStore` by selecting the Angular as the UI f
This is how the layered solution structure looks after it's created:
![bookstore-backend-solution](images\bookstore-backend-solution-v2.png)
![bookstore-backend-solution](images/bookstore-backend-solution-v2.png)
> You can see the [Application template document](../../Startup-Templates/Application.md) to understand the solution structure in details. However, you will understand the basics with this tutorial.
@ -342,7 +342,7 @@ Run `yarn start`, wait Angular to run the application and open `http://localhost
Open the `app-routing.module.ts` and replace `books` as shown below:
```typescript
```js
import { ApplicationLayoutComponent } from '@abp/ng.theme.basic';-
//...
@ -380,7 +380,7 @@ yarn ng generate component books/book-list
Import the `SharedModule` to the `BooksModule` to reuse some components and services defined in:
```typescript
```js
import { SharedModule } from '../shared/shared.module';
@NgModule({
@ -395,7 +395,7 @@ export class BooksModule {}
Then, update the `routes` in the `books-routing.module.ts` to add the new book-list component:
```typescript
```js
import { BookListComponent } from './book-list/book-list.component';
const routes: Routes = [
@ -425,7 +425,7 @@ yarn ng generate ngxs-schematic:state books
This command creates several new files and edits `app.modules.ts` to import the `NgxsModule` with the new state:
```typescript
```js
// app.module.ts
import { BooksState } from './store/states/books.state';
@ -446,7 +446,7 @@ First, create data types to map data returning from the backend (you can check s
Modify the `books.ts` as shown below:
```typescript
```js
export namespace Books {
export interface State {
books: Response;
@ -497,7 +497,7 @@ yarn ng generate service books/shared/books
Modify `books.service.ts` as shown below:
```typescript
```js
import { Injectable } from '@angular/core';
import { RestService } from '@abp/ng.core';
import { Books } from '../../store/models';
@ -522,7 +522,7 @@ Added the `get` method to get the list of books by performing an HTTP request to
Replace `books.actions.ts` content as shown below:
```typescript
```js
export class GetBooks {
static readonly type = '[Books] Get';
}
@ -532,7 +532,7 @@ export class GetBooks {
Open the `books.state.ts` and change the file as shown below:
```typescript
```js
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks } from '../actions/books.actions';
import { Books } from '../models/books';
@ -572,7 +572,7 @@ Added the `GetBooks` action that uses the `BookService` defined above to get the
Modify the `book-list.component.ts` as shown below:
```typescript
```js
import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';

@ -18,7 +18,7 @@ In this section, you will learn how to create a new modal dialog form to create
Create an interface, named `CreateUpdateBookInput` in the `books.ts` as shown below:
```typescript
```js
export namespace Books {
//...
export interface CreateUpdateBookInput {
@ -36,7 +36,7 @@ export namespace Books {
Open the `books.service.ts` and add a new method, named `create` to perform an HTTP POST request to the server:
```typescript
```js
create(createBookInput: Books.CreateUpdateBookInput): Observable<Books.Book> {
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'POST',
@ -52,7 +52,7 @@ create(createBookInput: Books.CreateUpdateBookInput): Observable<Books.Book> {
Add the `CreateUpdateBook` action to the `books.actions.ts` as shown below:
```typescript
```js
import { Books } from '../models';
export class CreateUpdateBook {
@ -63,7 +63,7 @@ export class CreateUpdateBook {
Open `books.state.ts` and define the `save` method that will listen to a `CreateUpdateBook` action to create a book:
```typescript
```js
import { ... , CreateUpdateBook } from '../actions/books.actions';
import { ... , switchMap } from 'rxjs/operators';
//...
@ -118,7 +118,7 @@ Add a button, labeled `New book` to show the modal:
Open the `book-list.component.ts` and add `isModalOpen` variable and `createBook` method to show/hide the modal.
```typescript
```js
isModalOpen = false;
//...
@ -136,7 +136,7 @@ createBook() {
Add a `form` variable and inject a `FormBuilder` service to the `book-list.component.ts` as shown below (remember add the import statement).
```typescript
```js
import { FormGroup, FormBuilder } from '@angular/forms';
form: FormGroup;
@ -151,7 +151,7 @@ constructor(
Add the `buildForm` method to create book form.
```typescript
```js
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
@ -167,7 +167,7 @@ buildForm() {
Modify the `createBook` method as shown below:
```typescript
```js
createBook() {
this.buildForm();
this.isModalOpen = true;
@ -220,7 +220,7 @@ Open `book-list.component.html` and add the form in the body template of the mod
Open the `book-list.component.ts` and then create an array, named `bookTypeArr`:
```typescript
```js
//...
form: FormGroup;
@ -241,7 +241,7 @@ This array was used in the previous form template (in the `ngFor` loop).
You need to import `NgbDatepickerModule` to the `books.module.ts`:
```typescript
```js
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
@ -253,9 +253,9 @@ import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
export class BooksModule {}
```
Then open the `book-list.component.html` and add `providers` as shown below:
Then open the `book-list.component.ts` and add `providers` as shown below:
```typescript
```js
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@Component({
@ -292,7 +292,7 @@ This adds a save button to the bottom area of the modal:
Then define a `save` method in the `BookListComponent`:
```typescript
```js
save() {
if (this.form.invalid) {
return;
@ -311,7 +311,7 @@ save() {
Open the `books.service.ts` and then add the `getById` and `update` methods.
```typescript
```js
getById(id: string): Observable<Books.Book> {
return this.restService.request<void, Books.Book>({
method: 'GET',
@ -332,7 +332,7 @@ update(updateBookInput: Books.CreateUpdateBookInput, id: string): Observable<Boo
Open the `books.actins.ts` and add `id` parameter to the `CreateUpdateBook` action:
```typescript
```js
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
constructor(public payload: Books.CreateUpdateBookInput, public id?: string) {}
@ -341,7 +341,7 @@ export class CreateUpdateBook {
Open `books.state.ts` and modify the `save` method as show below:
```typescript
```js
@Action(CreateUpdateBook)
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
let request;
@ -360,7 +360,7 @@ save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
Inject `BooksService` dependency by adding it to the `book-list.component.ts` constructor and add a variable named `selectedBook`.
```typescript
```js
import { BooksService } from '../shared/books.service';
//...
selectedBook = {} as Books.Book;
@ -373,7 +373,7 @@ constructor(
`booksService` is used to get the editing book to prepare the form. Modify the `buildForm` method to reuse the same form while editing a book.
```typescript
```js
buildForm() {
this.form = this.fb.group({
name: [this.selectedBook.name || '', Validators.required],
@ -386,7 +386,7 @@ buildForm() {
Add the `editBook` method as shown below:
```typescript
```js
editBook(id: string) {
this.booksService.getById(id).subscribe(book => {
this.selectedBook = book;
@ -400,7 +400,7 @@ Added `editBook` method to get the editing book, build the form and show the mod
Now, add the `selectedBook` definition to `createBook` method to reuse the same form while creating a new book:
```typescript
```js
createBook() {
this.selectedBook = {} as Books.Book;
//...
@ -409,7 +409,7 @@ Now, add the `selectedBook` definition to `createBook` method to reuse the same
Modify the `save` method to pass the id of the selected book as shown below:
```typescript
```js
save() {
if (this.form.invalid) {
return;
@ -489,7 +489,7 @@ Update the modal header to change the title based on the current operation:
Open `books.service.ts` and add a `delete` method to delete a book with the `id` by performing an HTTP request to the related endpoint:
```typescript
```js
delete(id: string): Observable<void> {
return this.restService.request<void, void>({
method: 'DELETE',
@ -502,7 +502,7 @@ delete(id: string): Observable<void> {
Add an action named `DeleteBook` to `books.actions.ts`:
```typescript
```js
export class DeleteBook {
static readonly type = '[Books] Delete';
constructor(public id: string) {}
@ -511,7 +511,7 @@ export class DeleteBook {
Open the `books.state.ts` and add the `delete` method that will listen to the `DeleteBook` action to delete a book:
```typescript
```js
import { ... , DeleteBook } from '../actions/books.actions';
//...
@Action(DeleteBook)
@ -544,7 +544,7 @@ The final actions dropdown UI looks like below:
Open `book-list.component.ts` and inject the `ConfirmationService`.
```typescript
```js
import { ConfirmationService } from '@abp/ng.theme.shared';
//...
constructor(
@ -557,7 +557,7 @@ constructor(
Add a delete method to the `BookListComponent`:
```typescript
```js
import { ... , DeleteBook } from '../../store/actions';
import { ... , Toaster } from '@abp/ng.theme.shared';
//...

@ -7,8 +7,12 @@
"text": "From Startup Templates",
"items": [
{
"text": "ASP.NET Core MVC",
"text": "Application with MVC (Razor Pages) UI",
"path": "Getting-Started-AspNetCore-MVC-Template.md"
},
{
"text": "Application with Angular UI",
"path": "Getting-Started-Angular-Template.md"
}
]
},
@ -34,8 +38,12 @@
"text": "Application Development",
"items": [
{
"text": "With ASP.NET Core MVC",
"text": "With ASP.NET Core MVC UI",
"path": "Tutorials/AspNetCore-Mvc/Part-I.md"
},
{
"text": "With Angular UI",
"path": "Tutorials/Angular/Part-I.md"
}
]
}
@ -264,6 +272,20 @@
}
]
},
{
"text": "Startup Templates",
"path": "Startup-Templates/Index.md",
"items": [
{
"text": "Application",
"path": "Startup-Templates/Application.md"
},
{
"text": "Module",
"path": "Startup-Templates/Application.md"
}
]
},
{
"text": "Samples",
"items": [

@ -22,7 +22,7 @@ namespace Volo.Blogging
options.Resources
.Add<BloggingResource>("en")
.AddBaseTypes(typeof(AbpValidationResource))
.AddVirtualJson("/Localization/Resources");
.AddVirtualJson("Volo/Blogging/Localization/Resources");
});
}
}

@ -8,19 +8,19 @@ namespace VoloDocs.Web.Pages
{
public class IndexModel : PageModel
{
private readonly DocsUrlOptions _urlOptions;
private readonly DocsUiOptions _urlUiOptions;
public IndexModel(IOptions<DocsUrlOptions> urlOptions)
public IndexModel(IOptions<DocsUiOptions> urlOptions)
{
_urlOptions = urlOptions.Value;
_urlUiOptions = urlOptions.Value;
}
public IActionResult OnGet()
{
//TODO: Create HomeController & Index instead of Page. Otherwise, we have an empty Index.cshtml file.
if (!_urlOptions.RoutePrefix.IsNullOrWhiteSpace())
if (!_urlUiOptions.RoutePrefix.IsNullOrWhiteSpace())
{
return Redirect("." + _urlOptions.RoutePrefix);
return Redirect("." + _urlUiOptions.RoutePrefix);
}
return Page();

@ -62,7 +62,7 @@ namespace VoloDocs.Web
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
Configure<DocsUrlOptions>(options =>
Configure<DocsUiOptions>(options =>
{
options.RoutePrefix = null;
});

@ -13,7 +13,7 @@ namespace Volo.Docs.Areas.Documents.TagHelpers
[HtmlTargetElement("ul", Attributes = "root-node")]
public class TreeTagHelper : TagHelper
{
private readonly DocsUrlOptions _urlOptions;
private readonly DocsUiOptions _uiOptions;
private const string LiItemTemplateWithLink = @"<li class='{0}'><span class='plus-icon'><i class='fa fa-{1}'></i></span>{2}{3}</li>";
@ -41,9 +41,9 @@ namespace Volo.Docs.Areas.Documents.TagHelpers
[HtmlAttributeName("language")]
public string LanguageCode { get; set; }
public TreeTagHelper(IOptions<DocsUrlOptions> urlOptions)
public TreeTagHelper(IOptions<DocsUiOptions> urlOptions)
{
_urlOptions = urlOptions.Value;
_uiOptions = urlOptions.Value;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
@ -142,7 +142,7 @@ namespace Volo.Docs.Areas.Documents.TagHelpers
return "javascript:;";
}
var prefix = _urlOptions.RoutePrefix;
var prefix = _uiOptions.RoutePrefix;
return prefix + LanguageCode + "/" + ProjectName + "/" + Version + "/" + pathWithoutFileExtension;
}

@ -2,7 +2,7 @@
namespace Volo.Docs
{
public class DocsUrlOptions
public class DocsUiOptions
{
private string _routePrefix = "documents";
@ -15,6 +15,12 @@ namespace Volo.Docs
set => _routePrefix = value;
}
/// <summary>
/// Allows user to see a combobox in user interface for swapping across projects
/// Default value: True;
/// </summary>
public bool ShowProjectsCombobox = true;
private string GetFormattedRoutePrefix()
{
if (string.IsNullOrWhiteSpace(_routePrefix))

@ -41,11 +41,11 @@ namespace Volo.Docs
Configure<RazorPagesOptions>(options =>
{
var urlOptions = context.Services
.GetRequiredServiceLazy<IOptions<DocsUrlOptions>>()
var docsOptions = context.Services
.GetRequiredServiceLazy<IOptions<DocsUiOptions>>()
.Value.Value;
var routePrefix = urlOptions.RoutePrefix;
var routePrefix = docsOptions.RoutePrefix;
options.Conventions.AddPageRoute("/Documents/Project/Index", routePrefix + "{projectName}");
options.Conventions.AddPageRoute("/Documents/Project/Index", routePrefix + "{languageCode}/{projectName}");

@ -14,13 +14,13 @@ namespace Volo.Docs.Markdown
public const string Type = "md";
private readonly IMarkdownConverter _markdownConverter;
private readonly DocsUrlOptions _urlOptions;
private readonly DocsUiOptions _uiOptions;
public MarkdownDocumentToHtmlConverter(IMarkdownConverter markdownConverter,
IOptions<DocsUrlOptions> urlOptions)
IOptions<DocsUiOptions> urlOptions)
{
_markdownConverter = markdownConverter;
_urlOptions = urlOptions.Value;
_uiOptions = urlOptions.Value;
}
private const string MdLinkFormat = "[{0}]({1}{2}/{3}/{4}{5}/{6})";
@ -73,7 +73,7 @@ namespace Volo.Docs.Markdown
return string.Format(
MdLinkFormat,
displayText,
_urlOptions.RoutePrefix,
_uiOptions.RoutePrefix,
languageCode,
projectShortName,
version,
@ -101,7 +101,7 @@ namespace Volo.Docs.Markdown
return string.Format(
MdLinkFormat,
displayText,
_urlOptions.RoutePrefix,
_uiOptions.RoutePrefix,
languageCode,
projectShortName,
version,

@ -15,19 +15,19 @@ namespace Volo.Docs.Pages.Documents
public IReadOnlyList<ProjectDto> Projects { get; set; }
private readonly IProjectAppService _projectAppService;
private readonly DocsUrlOptions _urlOptions;
private readonly DocsUiOptions _uiOptions;
public IndexModel(
IProjectAppService projectAppService,
IOptions<DocsUrlOptions> urlOptions)
IOptions<DocsUiOptions> urlOptions)
{
_projectAppService = projectAppService;
_urlOptions = urlOptions.Value;
_uiOptions = urlOptions.Value;
}
public async Task<IActionResult> OnGetAsync()
{
DocumentsUrlPrefix = _urlOptions.RoutePrefix;
DocumentsUrlPrefix = _uiOptions.RoutePrefix;
var listResult = await _projectAppService.GetListAsync();

@ -76,7 +76,7 @@
<div class="docs-tree-list">
@if (Model.ProjectSelectItems.Count > 1)
@if (Model.ShowProjectsCombobox && Model.ProjectSelectItems.Count > 1)
{
<div class="docs-version">
<div class="version-select">

@ -52,28 +52,31 @@ namespace Volo.Docs.Pages.Documents.Project
public string DocumentsUrlPrefix { get; set; }
public bool ShowProjectsCombobox { get; set; }
public bool DocumentLanguageIsDifferent { get; set; }
private readonly IDocumentAppService _documentAppService;
private readonly IDocumentToHtmlConverterFactory _documentToHtmlConverterFactory;
private readonly IProjectAppService _projectAppService;
private readonly DocsUrlOptions _options;
private readonly DocsUiOptions _uiOptions;
public IndexModel(
IDocumentAppService documentAppService,
IDocumentToHtmlConverterFactory documentToHtmlConverterFactory,
IProjectAppService projectAppService,
IOptions<DocsUrlOptions> options)
IOptions<DocsUiOptions> options)
{
_documentAppService = documentAppService;
_documentToHtmlConverterFactory = documentToHtmlConverterFactory;
_projectAppService = projectAppService;
_options = options.Value;
_uiOptions = options.Value;
}
public async Task<IActionResult> OnGetAsync()
{
DocumentsUrlPrefix = _options.RoutePrefix;
DocumentsUrlPrefix = _uiOptions.RoutePrefix;
ShowProjectsCombobox = _uiOptions.ShowProjectsCombobox;
if (IsDocumentCultureDifferentThanCurrent())
{
@ -81,7 +84,12 @@ namespace Volo.Docs.Pages.Documents.Project
}
await SetProjectAsync();
await SetProjectsAsync();
if (ShowProjectsCombobox)
{
await SetProjectsAsync();
}
await SetVersionAsync();
await SetLanguageList();
@ -179,7 +187,7 @@ namespace Volo.Docs.Pages.Documents.Project
ProjectSelectItems = projects.Items.Select(p => new SelectListItem
{
Text = p.Name,
Value = p.Id != Project.Id ? "/documents/" + LanguageCode + "/" + p.ShortName + "/" + DocsAppConsts.Latest : null,
Value = p.Id != Project.Id ? "/" + DocumentsUrlPrefix + LanguageCode + "/" + p.ShortName + "/" + DocsAppConsts.Latest : null,
Selected = p.Id == Project.Id
}).ToList();
}

Loading…
Cancel
Save