diff --git a/common.props b/common.props index 1c201c731c..48e20853bf 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 0.3.3 + 0.3.4 $(NoWarn);CS1591 http://www.aspnetboilerplate.com/images/abp_nupkg.png http://abp.io diff --git a/docs/Tutorials/AspNetCore-Mvc/Part-I.md b/docs/Tutorials/AspNetCore-Mvc/Part-I.md index 5ba2cce338..290c0a558e 100644 --- a/docs/Tutorials/AspNetCore-Mvc/Part-I.md +++ b/docs/Tutorials/AspNetCore-Mvc/Part-I.md @@ -2,9 +2,9 @@ ### About the Tutorial -In this tutorial series, you will build an application that is used to manage a list of books & their authors. **Entity Framework Core** (EF Core) will be used as the ORM provider (as it comes pre-configured with the startup template). +In this tutorial series, you will build an application that is used to manage a list of books & their authors. **Entity Framework Core** (EF Core) will be used as the ORM provider (as it comes pre-configured with the [startup template](https://abp.io/Templates)). -This is the second part of the tutorial series. See all parts: +This is the first part of the tutorial series. See all parts: - **Part I: Create the project and a book list page (this tutorial)** - [Part II: Create, Update and Delete books](Part-II.md) @@ -30,12 +30,12 @@ Define [entities](../../Entities.md) in the **domain layer** (`Acme.BookStore.Do using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Entities.Auditing; namespace Acme.BookStore { [Table("Books")] - public class Book : AggregateRoot + public class Book : AuditedAggregateRoot { [Required] [StringLength(128)] @@ -50,8 +50,10 @@ namespace Acme.BookStore } ```` -* ABP has two fundamental base classes for entities: `AggregateRoot` and `Entity`. **Aggregate Roots** are one of the concepts of the **Domain Driven Design (DDD)**. See [entity document](../../Entities.md) for details and best practices. -* Used **data annotation attributes** in this code. You could use EF Core's [fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling) instead. +* ABP has two fundamental base classes for entities: `AggregateRoot` and `Entity`. **Aggregate Root** is one of the **Domain Driven Design (DDD)** concepts. See [entity document](../../Entities.md) for details and best practices. +* `Book` entity inherits `AuditedAggregateRoot` which adds some auditing properties (`CreationTime`, `CreatorId`, `LastModificationTime`... etc.) on top of the `AggregateRoot` class. +* `Guid` is the **primary key type** of the `Book` entity. +* Used **data annotation attributes** in this code for EF Core mappings. You could use EF Core's [fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling) instead. #### BookType Enum @@ -117,15 +119,14 @@ Create a DTO class named `BookDto` into the `Acme.BookStore.Application` project ````C# using System; -using System.ComponentModel.DataAnnotations; using Volo.Abp.Application.Dtos; +using Volo.Abp.AutoMapper; namespace Acme.BookStore { - public class BookDto : EntityDto + [AutoMapFrom(typeof(Book))] + public class BookDto : AuditedEntityDto { - [Required] - [StringLength(128)] public string Name { get; set; } public BookType Type { get; set; } @@ -138,27 +139,73 @@ namespace Acme.BookStore ```` * **DTO** classes are used to transfer data between the presentation layer and the application layer. See the [Data Transfer Objects document](../../Data-Transfer-Objects.md) for details. +* `BookDto` is used to transfer a book data to the presentation layer to show a book information on the UI. +* `BookDto` is derived from the `AuditedEntityDto` which has audit properties just like the `Book` defined above. +* `[AutoMapFrom(typeof(Book))]` is used to create AutoMapper mapping from the `Book` class to the `BookDto` class. Thus, you can automatically convert `Book` objects to `BookDto` objects (instead of manually copy all properties). + +#### CreateUpdateDto + +Create a DTO class named `CreateUpdateDto` into the `Acme.BookStore.Application` project: + +````c# +using System; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.AutoMapper; + +namespace Acme.BookStore +{ + [AutoMapTo(typeof(Book))] + public class CreateUpdateBookDto + { + [Required] + [StringLength(128)] + [Display(Name = "Name")] + public string Name { get; set; } + + [Display(Name = "Type")] + public BookType Type { get; set; } = BookType.Undefined; + + [Display(Name = "PublishDate")] + public DateTime PublishDate { get; set; } + + [Display(Name = "Price")] + public float Price { get; set; } + } +} +```` + +* This DTO class is used to get book information from the user interface to create or update a book. +* It defines data annotation attributes (like `[Required]`) to define validations for the properties. +* Each property has a `[Display]` property which sets the label on UI forms for the related inputs. It's also integrated to the localization system. The same DTO will be used as View Model. That's why it defines that attribute. You may find incorrect to use DTOs as View Models. You could use a separated view model class, but we thought it's practical and makes the sample project less complex. #### IBookAppService -First, define an interface named `IBookAppService` for the book application service: +Define an interface named `IBookAppService` for the book application service: ````C# using System; +using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; namespace Acme.BookStore { - public interface IBookAppService : IAsyncCrudAppService + public interface IBookAppService : + IAsyncCrudAppService< //Defines CRUD methods + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting on getting a list of books + CreateUpdateBookDto, //Used to create a new book + CreateUpdateBookDto> //Used to update a book { } } + ```` * Defining interfaces for application services is not required by the framework. However, it's suggested as a good practice. -* `IAsyncCrudAppService` defines common CRUD methods: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` and `DeleteAsync`. It's not required to extend it. Instead you could inherit from the empty `IApplicationService` interface and define your own methods. -* There are some variations of the `IAsyncCrudAppService`. In this sample, the first generic parameter, `BookDto`, is the DTO used for service methods and the second parameter, `Guid`, is the type of the primary key of the entity. +* `IAsyncCrudAppService` defines common **CRUD** methods: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` and `DeleteAsync`. It's not required to extend it. Instead you could inherit from the empty `IApplicationService` interface and define your own methods. +* There are some variations of the `IAsyncCrudAppService` where you can use a single DTO or separated DTOs for each method. #### BookAppService @@ -166,12 +213,16 @@ Implement the `IBookAppService` as named `BookAppService`: ````C# using System; +using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; namespace Acme.BookStore { - public class BookAppService : AsyncCrudAppService, IBookAppService + public class BookAppService : + AsyncCrudAppService, + IBookAppService { public BookAppService(IRepository repository) : base(repository) @@ -182,9 +233,9 @@ namespace Acme.BookStore } ```` -* `BookAppService` is derived from `AsyncCrudAppService` which implements all CRUD methods defined above. -* `BookAppService` injects `IRepository` which is the default repository created for the `Book` entity. See the [repository document](../../Repositories.md). -* `BookAppService` uses `IObjectMapper` to convert `Book` objects to `BookDto` objects and vice verse. Startup template uses the [AutoMapper](http://automapper.org/) library as the mapping provider. Since you haven't defined any mapping configuration, AutoMapper's [inline mapping](http://automapper.readthedocs.io/en/latest/Inline-Mapping.html) feature is used to automatically configure the mapping. This works fine if both classes have identical properties, but may cause to problems if they not. See the [AutoMapper integration document](../../AutoMapper-Integration.md) for details. +* `BookAppService` is derived from `AsyncCrudAppService<...>` which implements all CRUD methods defined above. +* `BookAppService` injects `IRepository` which is the default repository created for the `Book` entity. ABP automatically creates repositories for each aggregate root (or entity). See the [repository document](../../Repositories.md). +* `BookAppService` uses `IObjectMapper` to convert `Book` objects to `BookDto` objects and `CreateUpdateBookDto` objects to `Book` objects. Startup template uses the [AutoMapper](http://automapper.org/) library as the mapping provider. You defined mappings using the `AutoMapFrom` and `AutoMapTo` attributes above. See the [AutoMapper integration document](../../AutoMapper-Integration.md) for details. ### Auto API Controllers @@ -198,7 +249,7 @@ The startup template is configured to run the [swagger UI](https://swagger.io/to ![bookstore-swagger](../../images/bookstore-swagger.png) -You will see some built-in service endpoints as well as the `Book` service and its REST-style service endpoints. +You will see some built-in service endpoints as well as the `Book` service and its REST-style endpoints. ### Dynamic JavaScript Proxies @@ -229,14 +280,7 @@ You can see the **book list** returned from the server. Let's **create a new book** using the `create` function: ````js -acme.bookStore.book.create({ - name: 'Foundation', - type: 7, - publishDate: '1951-05-24', - price: 21.5 -}).done(function (result) { - console.log('successfully created the book with id: ' + result.id); -}); +acme.bookStore.book.create({ name: 'Foundation', type: 7, publishDate: '1951-05-24', price: 21.5 }).done(function (result) { console.log('successfully created the book with id: ' + result.id); }); ```` You should see a message in the console something like that: @@ -245,7 +289,7 @@ You should see a message in the console something like that: successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7 ```` -Check the `books` table in the database to see the new book row. You can also try `get`, `update` and `delete` functions. +Check the `books` table in the database to see the new book row. You can try `get`, `update` and `delete` functions too. ### Create the Books Page @@ -266,7 +310,7 @@ Open the `Index.cshtml` and change the content as shown below:

Books

```` -* This page inherits from the `BookStorePageBase` class which comes with the startup template and provides some shared properties/methods used by all pages. +* This page **inherits** from the `BookStorePageBase` class which comes with the startup template and provides some shared properties/methods used by all pages. #### Add Books Page to the Main Menu @@ -336,6 +380,7 @@ Change the `Pages/Books/Index.cshtml` as following: @L["Type"] @L["PublishDate"] @L["Price"] + @L["CreationTime"] @@ -370,11 +415,15 @@ $(function() { }, { targets: 2, - data: "price" + data: "publishDate" }, { targets: 3, - data: "publishDate" + data: "price" + }, + { + targets: 4, + data: "creationTime" } ] }); diff --git a/docs/Tutorials/AspNetCore-Mvc/Part-II.md b/docs/Tutorials/AspNetCore-Mvc/Part-II.md index 0791fef352..422e226d15 100644 --- a/docs/Tutorials/AspNetCore-Mvc/Part-II.md +++ b/docs/Tutorials/AspNetCore-Mvc/Part-II.md @@ -29,18 +29,16 @@ Create a new razor page, named `CreateModal.cshtml` under the `Pages/Books` fold Open the `CreateModal.cshtml.cs` file (`CreateModalModel` class) and replace with the following code: ````C# -using System; -using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; namespace Acme.BookStore.Pages.Books { - public class CreateModalModel : AbpPageModel + public class CreateModalModel : BookStorePageModelBase { [BindProperty] - public CreateBookViewModel Book { get; set; } + public CreateUpdateBookDto Book { get; set; } private readonly IBookAppService _bookAppService; @@ -51,75 +49,16 @@ namespace Acme.BookStore.Pages.Books public async Task OnPostAsync() { - ValidateModel(); - - var bookDto = ObjectMapper.Map(Book); - await _bookAppService.CreateAsync(bookDto); - + await _bookAppService.CreateAsync(Book); return NoContent(); } - - public class CreateBookViewModel - { - [Required] - [StringLength(128)] - [Display(Name = "Name")] - public string Name { get; set; } - - [Display(Name = "Type")] - public BookType Type { get; set; } = BookType.Undefined; - - [Display(Name = "PublishDate")] - public DateTime PublishDate { get; set; } - - [Display(Name = "Price")] - public float Price { get; set; } - } } } ```` -* `CreateBookViewModel` is a nested class that will be used to create and post the form. - * Each property has a `[Display]` property which sets the label on the form for the related input. It's also integrated to the localization system. - * Each property has data annotations for validation which is used for validation in the client side and the server side and automatically localized. +* This class is derived from the `BookStorePageModelBase` instead of standard `PageModel`. `BookStorePageModelBase` inherits the `PageModel` and adds some common properties/methods those can be used in your page model classes. * `[BindProperty]` attribute on the `Book` property binds post request data to this property. - -##### AutoMapper Configuration - -`OnPostAsync` method maps `CreateBookViewModel` object to `BookDto` object (which is accepted by the `BookAppService.CreateAsync` method). - -Open the `BookStoreWebAutoMapperProfile` class and add the mapping: - -````C# -using Acme.BookStore.Pages.Books; -using AutoMapper; -using Volo.Abp.AutoMapper; - -namespace Acme.BookStore -{ - public class BookStoreWebAutoMapperProfile : Profile - { - public BookStoreWebAutoMapperProfile() - { - CreateMap() - .Ignore(x => x.Id); - } - } -} - -```` - -Thus, AutoMapper will create the mapping configuration between classes and will ignore the `Id` property of the `BookDto` class (to satisfy the configuration validation - see below). - -##### AutoMapper Configuration Validation - -AutoMapper has a [configuration validation feature](http://automapper.readthedocs.io/en/latest/Configuration-validation.html) which is not enabled for the startup template by default. If you want to perform validation, go to the `BookStoreWebModule` class, find the `ConfigureAutoMapper` method and change the `AddProfile` line as shown below: - -````c# -options.AddProfile(validate: true); -```` - -It's up to you to use validation or not. It can prevent mapping mistakes, but comes with a cost of configuration. See [AutoMapper's documentation](http://automapper.readthedocs.io/en/latest/Configuration-validation.html) to fully understand it. +* This class simply injects the `IBookAppService` in its constructor and calls the `CreateAsync` method in the `OnPostAsync` handler. ##### CreateModal.cshtml @@ -139,8 +78,7 @@ Open the `CreateModal.cshtml` file and paste the code below: - - + ```` @@ -195,4 +133,237 @@ Now, you can **run the application** and add new books using the new modal form. ### Updating An Existing Book -TODO... \ No newline at end of file +Create a new razor page, named `EditModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project: + +![bookstore-add-edit-dialog](../../images/bookstore-add-edit-dialog.png) + +#### EditModal.cshtml.cs + +Open the `EditModal.cshtml.cs` file (`EditModalModel` class) and replace with the following code: + +````C# +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Acme.BookStore.Pages.Books +{ + public class EditModalModel : BookStorePageModelBase + { + [HiddenInput] + [BindProperty(SupportsGet = true)] + public Guid Id { get; set; } + + [BindProperty] + public CreateUpdateBookDto Book { get; set; } + + private readonly IBookAppService _bookAppService; + + public EditModalModel(IBookAppService bookAppService) + { + _bookAppService = bookAppService; + } + + public async Task OnGetAsync() + { + var bookDto = await _bookAppService.GetAsync(Id); + Book = ObjectMapper.Map(bookDto); + } + + public async Task OnPostAsync() + { + await _bookAppService.UpdateAsync(Id, Book); + return NoContent(); + } + } +} +```` + +* `HiddenInput` and `BindProperty` are standard ASP.NET Core MVC attributes. Used `SupportsGet` to be able to get Id value from query string parameter of the request. +* Mapped `BookDto` (received from the `BookAppService.GetAsync`) to `CreateUpdateBookDto` in the `GetAsync` method. +* The `OnPostAsync` simply uses `BookAppService.UpdateAsync` to update the entity. + +#### CreateUpdateBookDto + +In order to perform `BookDto` to `CreateUpdateBookDto` object mapping, change the `CreateUpdateBookDto` class as shown below: + +````C# +using System; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.AutoMapper; + +namespace Acme.BookStore +{ + [AutoMapTo(typeof(Book))] + [AutoMapFrom(typeof(BookDto))] + public class CreateUpdateBookDto + { + [Required] + [StringLength(128)] + [Display(Name = "Name")] + public string Name { get; set; } + + [Display(Name = "Type")] + public BookType Type { get; set; } = BookType.Undefined; + + [Display(Name = "PublishDate")] + public DateTime PublishDate { get; set; } + + [Display(Name = "Price")] + public float Price { get; set; } + } +} +```` + +* Just added the `[AutoMapFrom(typeof(BookDto))]` attribute to create the mapping. + +#### EditModal.cshtml + +Replace `EditModal.cshtml` content with the following content: + +````html +@page +@inherits Acme.BookStore.Pages.BookStorePageBase +@using Acme.BookStore.Pages.Books +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model EditModalModel +@{ + Layout = null; +} + + + + + + + + + + +```` + +This page is very similar to the `CreateModal.cshtml` except; + +* It includes an `abp-input` for the `Id` property to store id of the editing book. +* It uses `Books/EditModal` as the post URL and *Update* text as the modal header. + +#### Add "Actions" Dropdown to the Table + +We will add a dropdown button ("Actions") for each row of the table. The final UI looks like this: + +![bookstore-books-table-actions](../../images/bookstore-books-table-actions.png) + +Open the `Pages/Books/Index.cshtml` page and change the table section as shown below: + +````html + + + + @L["Actions"] + @L["Name"] + @L["Type"] + @L["PublishDate"] + @L["Price"] + @L["CreationTime"] + + + +```` + +* Just added a new `th` tag for the "Actions" button. + +Open the `wwwroot/pages/books/index.js` and replace the content as below: + +````js +$(function () { + + var l = abp.localization.getResource('BookStore'); + + var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); + var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); + + var dataTable = $('#BooksTable').DataTable({ + order: [[1, "asc"]], + ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), + columnDefs: [ + { + targets: 0, + data: null, + orderable: false, + autoWidth: false, + defaultContent: '', + rowAction: { + text: ' ' + l('Actions') + + ' ', + items: + [ + { + text: l('Edit'), + visible: function () { return true; }, + action: function (data) { + editModal.open({ + id: data.record.id + }); + } + }, + { + text: l('Delete'), + visible: function () { return true; }, + confirmMessage: function (data) { + return l('BookDeletionConfirmationMessage', + data.record.name); + }, + action: function (data) { + acme.bookStore.book + .delete(data.record.id) + .then(function () { + abp.notify.info(l('SuccessfullyDeleted')); + dataTable.ajax.reload(); + }); + } + } + ] + } + }, + { + targets: 1, + data: "name" + }, + { + targets: 2, + data: "type" + }, + { + targets: 3, + data: "publishDate" + }, + { + targets: 4, + data: "price" + }, + { + targets: 5, + data: "creationTime" + } + ] + }); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + editModal.onResult(function () { + dataTable.ajax.reload(); + }); + + $('#NewBookButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); +}); +```` + +* Added a new `ModalManager` named `editModal` to open the edit modal dialog. +* Added a new column at the beginning of the `columnDefs` section. This column is used for the "Actions" dropdown button. +* "Edit" action simply calls `editModal.open` to open the edit dialog. +* Also added a "Delete" button to delete the book. \ No newline at end of file diff --git a/docs/images/bookstore-add-edit-dialog.png b/docs/images/bookstore-add-edit-dialog.png new file mode 100644 index 0000000000..b28d8736dd Binary files /dev/null and b/docs/images/bookstore-add-edit-dialog.png differ diff --git a/docs/images/bookstore-book-list.png b/docs/images/bookstore-book-list.png index 34cec48c66..f531e6f457 100644 Binary files a/docs/images/bookstore-book-list.png and b/docs/images/bookstore-book-list.png differ diff --git a/docs/images/bookstore-books-table-actions.png b/docs/images/bookstore-books-table-actions.png new file mode 100644 index 0000000000..bc1d4e3f6f Binary files /dev/null and b/docs/images/bookstore-books-table-actions.png differ diff --git a/docs/images/bookstore-books-table.png b/docs/images/bookstore-books-table.png index 0493791918..3cc8d7ca18 100644 Binary files a/docs/images/bookstore-books-table.png and b/docs/images/bookstore-books-table.png differ diff --git a/docs/images/bookstore-new-book-button.png b/docs/images/bookstore-new-book-button.png index 6f1cb321bf..dfd4b5d8aa 100644 Binary files a/docs/images/bookstore-new-book-button.png and b/docs/images/bookstore-new-book-button.png differ diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index 169fb7f9e9..e692aae557 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -56,6 +56,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form return GetContent(context, output, label, inputHtml, validation, isCheckbox); } + protected virtual string GetValidationAsHtml(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag) { if (inputTag.Attributes.Any(a => a.Name.ToLowerInvariant() == "type" && a.Value.ToString().ToLowerInvariant() == "hidden")) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tooltip/AbpTooltipTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tooltip/AbpTooltipTagHelperService.cs index 9c01dab35f..006ccbc856 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tooltip/AbpTooltipTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tooltip/AbpTooltipTagHelperService.cs @@ -18,7 +18,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tooltip protected virtual void SetDataPlacement(TagHelperContext context, TagHelperOutput output) { - output.Attributes.Add("data-placement", GetDirectory().ToString().ToLowerInvariant()); + var directory = GetDirectory() != TooltipDirectory.Default ? GetDirectory() : TooltipDirectory.Bottom; + output.Attributes.Add("data-placement", directory.ToString().ToLowerInvariant()); } protected virtual void SetTooltipTitle(TagHelperContext context, TagHelperOutput output) @@ -62,7 +63,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tooltip return TooltipDirectory.Left; } - return TooltipDirectory.Bottom; + return TooltipDirectory.Default; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs index b6ce0ba270..d4fd3cadd7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/AbpAspNetCoreMvcUIBasicThemeModule.cs @@ -1,6 +1,9 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Toolbars; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; using Volo.Abp.AspNetCore.Mvc.UI.Theming; using Volo.Abp.Modularity; @@ -34,7 +37,26 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic { options.Contributors.Add(new BasicThemeMainTopToolbarContributor()); }); - + + services.Configure(options => + { + options + .StyleBundles + .Add(BasicThemeBundles.Styles.Global, bundle => + { + bundle + .AddBaseBundles(StandardBundles.Styles.Global) + .AddContributors(new BasicThemeGlobalStyleContributor()); + }); + + options + .ScriptBundles + .Add(BasicThemeBundles.Scripts.Global, bundle => + { + bundle.AddBaseBundles(StandardBundles.Scripts.Global); + }); + }); + services.AddAssemblyOf(); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeBundles.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeBundles.cs new file mode 100644 index 0000000000..1ac98f41d2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeBundles.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling +{ + public static class BasicThemeBundles + { + public static class Styles + { + public const string Global = "Basic.Global"; + } + + public static class Scripts + { + public const string Global = "Basic.Global"; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeGlobalStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeGlobalStyleContributor.cs new file mode 100644 index 0000000000..bf60b4ccd0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Bundling/BasicThemeGlobalStyleContributor.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling +{ + public class BasicThemeGlobalStyleContributor : BundleContributor + { + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.Add("/themes/basic/layout.css"); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml index 9c91553b33..1dc80a0de0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml @@ -1,7 +1,7 @@ @using Volo.Abp.AspNetCore.Mvc.AntiForgery +@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.MainNavbar @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.PageAlerts -@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components @inject IAbpAntiForgeryManager AbpAntiForgeryManager @inject IBrandingProvider BrandingProvider @@ -20,9 +20,7 @@ @(ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title) - - - + @RenderSection("styles", false) @@ -35,7 +33,7 @@ @RenderBody() - + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml index f284d78de3..10f996a484 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml @@ -1,6 +1,6 @@ @using Volo.Abp.AspNetCore.Mvc.AntiForgery +@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.PageAlerts -@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components @inject IAbpAntiForgeryManager AbpAntiForgeryManager @inject IBrandingProvider BrandingProvider @@ -18,9 +18,7 @@ @(ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title) - - - + @RenderSection("styles", false) @@ -31,7 +29,7 @@ @RenderBody() - + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj index 742418a028..efdf54dd79 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj @@ -21,6 +21,14 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/views/shared/_AppLayout.css b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css similarity index 100% rename from framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/views/shared/_AppLayout.css rename to framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs index 52660c52f0..3402b92e99 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs @@ -19,7 +19,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling { public override void ConfigureBundle(BundleConfigurationContext context) { - + context.Files.AddRange(new[] + { + "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-styles.css" + }); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj index b09b95f613..424f80287a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj @@ -21,6 +21,17 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/compilerconfig.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/compilerconfig.json index ee76e9b485..b6108a03fa 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/compilerconfig.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/compilerconfig.json @@ -1,3 +1,6 @@ [ - + { + "outputFile": "wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-styles.css", + "inputFile": "wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-styles.scss" + } ] \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index eeb3116e51..b358f1065e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js @@ -2,11 +2,47 @@ (function ($) { - /************************************************************************ - * RECORD-ACTIONS extension for datatables * + * RECORD-ACTIONS extension for datatables + --------------------------------------------------------------- + * USAGE: element (creates the JQuery element and puts in the specified target) + { + targets: 0, + rowAction: + { + element: $("