|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@ -0,0 +1,324 @@ |
|||
# Using Vue components in a Razor Pages ABP Application |
|||
|
|||
In modern web development, integrating dynamic front-end frameworks with server-side technologies has become increasingly essential for creating responsive and interactive applications. This article explores how to effectively use Vue components within Razor Pages in an ABP Framework application. We will delve into the process of consuming endpoints through ABP Client Proxies, leveraging ABP's powerful localization features to enhance user experience, and implementing ABP permissions to ensure secure access control. By the end of this guide, you will have a comprehensive understanding of how to seamlessly blend Vue.js with Razor Pages, empowering you to build robust and user-friendly applications. |
|||
|
|||
This article won't use any SPA approach. The goal of this article is to use Razor Pages with simple Vue components to eliminate jQuery while developing MVC application. |
|||
|
|||
> **🎉 Also video version is available!** |
|||
> |
|||
> [Watch on YouTube Now!](https://youtu.be/sZ8iSMovHZs?si=GynuJjsLEI1p2g6w) |
|||
|
|||
## Creating the Solution |
|||
|
|||
Let's create a simple TODO list application to demonstrate how to use Vue components in Razor Pages. I'll build a really simple backend without a connection to a database for demonstration purposes. We will focus on the frontend part. |
|||
|
|||
- Creating a solution with ABP CLI: |
|||
|
|||
```bash |
|||
abp new MyTodoApp -t app-nolayers -csf |
|||
``` |
|||
|
|||
## Configure Vue |
|||
|
|||
We need to add the `@abp/vue` package to the project to use Vue components. |
|||
|
|||
```bash |
|||
npm install @abp/vue |
|||
``` |
|||
|
|||
- Install client libraries by using ABP CLI: |
|||
|
|||
```bash |
|||
abp install-libs |
|||
``` |
|||
|
|||
As a last step, we need to configure our bundle in the `ConfigureBundles` method in the `MyTodoAppModule.cs` file: |
|||
|
|||
```csharp |
|||
private void ConfigureBundles() |
|||
{ |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
// ... |
|||
|
|||
options.ScriptBundles.Configure( |
|||
// Or BasicThemeBundles.Scripts.Global |
|||
// Or LeptonXLiteThemeBundles.Scripts.Global |
|||
// 👇 Depends on the theme you are using |
|||
LeptonXThemeBundles.Scripts.Global, |
|||
bundle => |
|||
{ |
|||
bundle.AddFiles("/global-scripts.js"); |
|||
// 👇 Make sure to add this line |
|||
bundle.AddContributors(typeof(VueScriptContributor)); |
|||
} |
|||
); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
> If your IDE doesn't recognize the namespace of the `VueScriptContributor`, you can add it manually: |
|||
> |
|||
> ```csharp |
|||
> using Volo.Abp.AspNetCore.Mvc.UI.Packages.Vue; |
|||
> ``` |
|||
|
|||
Now we're ready to use Vue components in our Razor Pages. |
|||
|
|||
## Creating a Vue Component |
|||
|
|||
Let's create a simple Vue component to display the TODO list. |
|||
|
|||
### Passing a simple message to the component |
|||
|
|||
- Remove existing HTML codes in `Index.cshtml` and replace with the following code: |
|||
|
|||
```html |
|||
<div id="vue-app"> |
|||
<message-component :message="'Welcome, @CurrentUser.UserName !'"></todo-component> |
|||
</div> |
|||
``` |
|||
|
|||
- Navigate to the `Index.cshtml.js` file and add the following code: |
|||
```js |
|||
Vue.component('message-component', { |
|||
template: '<div>Hello, {{ message }}</div>', |
|||
props: ['message'] |
|||
}); |
|||
|
|||
new Vue({ |
|||
el: '#vue-app' |
|||
}); |
|||
``` |
|||
|
|||
Run the application and you should see the following output: |
|||
|
|||
 |
|||
|
|||
> _Hard refresh might be required to see the component since we added a new vue js file to the bundle._ |
|||
> |
|||
> If still you can't see the component, please check the browser console for any errors. |
|||
|
|||
### Interacting with the component |
|||
|
|||
Let's add a button to the component to interact with the component. |
|||
|
|||
- Add another component in the `Index.cshtml` file: |
|||
|
|||
```html |
|||
<div id="vue-app"> |
|||
<message-component :message="'Welcome, @CurrentUser.UserName !'"></message-component> |
|||
<counter-component></counter-component> |
|||
</div> |
|||
``` |
|||
|
|||
```js |
|||
Vue.component('counter-component', { |
|||
template:` |
|||
<div class="card"> |
|||
<div class="card-body"> |
|||
<p>Count: {{ count }}</p> |
|||
<button class="btn btn-primary" @click="increment">Increment</button> |
|||
</div> |
|||
</div> |
|||
`, |
|||
data: function () { |
|||
return { |
|||
count: 0 |
|||
}; |
|||
}, |
|||
methods: { |
|||
increment: function () { |
|||
this.count++; |
|||
} |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
> _Do not replicate `new Vue({})` code block in the file. It's already in the `Index.cshtml.js` file. Keep it at the bottom of the file as it is._ |
|||
|
|||
Run the application and you should see the following output: |
|||
|
|||
 |
|||
|
|||
|
|||
## Using ABP Client Proxy, Authorization and Localization |
|||
|
|||
|
|||
### Building the backend |
|||
Before we go, let's build our backend to use in the component. |
|||
|
|||
- Creating a simple Application Service: |
|||
|
|||
```csharp |
|||
public class TodoAppService : MyTodoAppAppService, ITodoAppService |
|||
{ |
|||
public static List<TodoItem> Items { get; } = new List<TodoItem>(); |
|||
|
|||
[Authorize("Todo.Create")] |
|||
public async Task<TodoItem> AddTodoItemAsync(TodoItem input) |
|||
{ |
|||
Items.Add(input); |
|||
return input; |
|||
} |
|||
|
|||
[Authorize("Todo")] |
|||
public async Task<List<TodoItem>> GetAllAsync() |
|||
{ |
|||
await Task.Delay(1500); |
|||
return Items; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
- `TodoItem.cs` |
|||
|
|||
```csharp |
|||
public class TodoItem |
|||
{ |
|||
public string Description { get; set; } |
|||
public bool IsDone { get; set; } |
|||
} |
|||
``` |
|||
|
|||
- `ITodoAppService.cs` |
|||
|
|||
```csharp |
|||
public interface ITodoAppService |
|||
{ |
|||
Task<List<TodoItem>> GetAllAsync(); |
|||
Task<TodoItem> AddTodoItemAsync(TodoItem input); |
|||
} |
|||
``` |
|||
|
|||
- Run the application and if you can see the following client proxy in the browser console, you're ready to go: |
|||
|
|||
 |
|||
|
|||
> [!NOTE] |
|||
> If you can't see the client proxy in the browser console, please check the [Dynamic JavaScript Proxies](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/dynamic-javascript-proxies) to learn how to enable it. |
|||
|
|||
- Add a new permission in the `MyTodoAppPermissionDefinitionProvider.cs` file: |
|||
```csharp |
|||
public override void Define(IPermissionDefinitionContext context) |
|||
{ |
|||
var myGroup = context.AddGroup(MyTodoAppPermissions.GroupName); |
|||
|
|||
var todo = myGroup.AddPermission("Todo"); |
|||
todo.AddChild("Todo.Create"); |
|||
} |
|||
``` |
|||
> _I go without localization or constants for simplicity._ |
|||
|
|||
- Add a localization key in the `en.json` file: |
|||
|
|||
```json |
|||
{ |
|||
"TodoItems": "Todo Items Localized" |
|||
} |
|||
``` |
|||
|
|||
### Building the Vue Component: Using ABP Localization, Authorization and Client Proxy |
|||
|
|||
Since the component it directly loaded into the page, we can access the `abp` object on the page. |
|||
|
|||
So we can use: |
|||
|
|||
- `abp.localization.localize()` to localize a string. |
|||
- `abp.auth.isGranted()` to check the authorization. |
|||
- `myTodoApp.todo.getAll()` and `myTodoApp.todo.addTodoItem` to call the Application Service. |
|||
|
|||
inside **Vue Component** code. |
|||
|
|||
- Let's add another component named `todo-component` and usee all the **ABP Features** in it. |
|||
|
|||
```html |
|||
<div id="vue-app"> |
|||
<!-- ... --> |
|||
<todo-component></todo-component> |
|||
</div> |
|||
``` |
|||
|
|||
- Implement the `todo-component` in `Index.cshtml.js` file: |
|||
|
|||
```js |
|||
Vue.component('todo-component', { |
|||
template: ` |
|||
<div class="card" v-if="abp.auth.isGranted('Todo')"> |
|||
<div class="card-header border-bottom"> |
|||
<h3>{{ abp.localization.localize('TodoItems') }}</h3> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div v-if="isBusy" class="w-100 text-center"> |
|||
<div class="spinner-border" role="status"> |
|||
<span class="visually-hidden">Loading...</span> |
|||
</div> |
|||
</div> |
|||
<ul v-else-if="todos.length > 0" class="list-group"> |
|||
<li class="list-group-item" v-for="item in todos" :key="item.description"> |
|||
<input class="form-check-input" type="checkbox" v-model="item.isDone"> |
|||
<label class="form-check-label">{{ item.description }}</label> |
|||
</li> |
|||
</ul> |
|||
<p v-else>No todos yet</p> |
|||
</div> |
|||
<div v-if="abp.auth.isGranted('Todo.Create')" class="card-footer d-flex flex-column gap-2 border-top pt-2"> |
|||
<input class="form-control" type="text" v-model="newTodo.description" placeholder="Add a new todo"> |
|||
<div class="form-check"> |
|||
<input class="form-check-input" type="checkbox" v-model="newTodo.isDone" id="isDone"> |
|||
<label class="form-check-label" for="isDone">Is Done</label> |
|||
</div> |
|||
|
|||
<button class="btn btn-primary" @click="addTodo">Add</button> |
|||
</div> |
|||
</div> |
|||
`, |
|||
data: function () { |
|||
return { |
|||
newTodo: { |
|||
description: '', |
|||
isDone: false |
|||
}, |
|||
isBusy: false, |
|||
todos: [] |
|||
}; |
|||
}, |
|||
methods: { |
|||
addTodo() { |
|||
myTodoApp.todo.addTodoItem(this.newTodo); |
|||
this.newTodo = { description: '', isDone: false }; |
|||
this.todos.push(this.newTodo); |
|||
|
|||
// Preferrable, you can load entire list of todos again. |
|||
// this.loadTodos(); |
|||
}, |
|||
async loadTodos() { |
|||
if (!abp.auth.isGranted('Todo')) { |
|||
return; |
|||
} |
|||
this.isBusy = true; |
|||
this.todos = await myTodoApp.todo.getAll(); |
|||
this.isBusy = false; |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.loadTodos(); |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
And see the result: |
|||
|
|||
 |
|||
|
|||
|
|||
Since we use `abp.auth.isGranted()` to check the authorization, we can see the component only if we have the permission. |
|||
|
|||
Whenever you remove `Todo.Create` permission, you can see the component is not rendered. |
|||
|
|||
 |
|||
|
|||
|
|||
You won't see the card footer: |
|||
|
|||
 |
|||
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 337 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 212 KiB |
@ -0,0 +1,248 @@ |
|||
# Understanding the Embedded Files in ABP Framework |
|||
|
|||
Embedded Files functionality in .NET applications allows external files (like configuration files, images, etc.) to be directly embedded into assemblies (.exe or .dll). This simplifies deployment, prevents file loss or tampering, improves security and performance, and reduces path and dependency management issues. Through embedded resources, programs can access these files more conveniently without additional file operations. |
|||
|
|||
## Embedding Files in Your Project |
|||
|
|||
We embed `Volo\Abp\MyModule\Localization\*.json` files into the assembly in our `MyModule.csproj`. |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net9.0</TargetFramework> |
|||
<OutputType>Exe</OutputType> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" /> |
|||
<PackageReference Include="Volo.Abp.VirtualFileSystem" Version="9.0.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="Volo\Abp\MyModule\Localization\*.json" /> |
|||
<EmbeddedResource Include="Volo\Abp\MyModule\Localization\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
``` |
|||
|
|||
If we check the `en.json` file in our IDE, we'll see it's embedded in the assembly. |
|||
|
|||
 |
|||
|
|||
When we decompile the built `MyModule.dll` file, we can also see the `en.json` file. |
|||
|
|||
 |
|||
|
|||
## Accessing Embedded Files in Code |
|||
|
|||
```csharp |
|||
public class Program |
|||
{ |
|||
public static async Task<int> Main(string[] args) |
|||
{ |
|||
var embeddedFiles = typeof(Program).Assembly.GetManifestResourceNames(); |
|||
foreach (var embeddedFile in embeddedFiles) |
|||
{ |
|||
Console.WriteLine(embeddedFile); |
|||
var fileStream = typeof(Program).Assembly.GetManifestResourceStream(embeddedFile); |
|||
if (fileStream != null) |
|||
{ |
|||
using var reader = new System.IO.StreamReader(fileStream); |
|||
var content = await reader.ReadToEndAsync(); |
|||
Console.WriteLine(content); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
This code will output the embedded file names and their contents. |
|||
|
|||
``` |
|||
MyModule.Volo.Abp.MyModule.Localization.en.json |
|||
|
|||
{ |
|||
"key":"value" |
|||
} |
|||
``` |
|||
|
|||
## Integrating with ABP Virtual File System |
|||
|
|||
The ABP Virtual File System makes it possible to manage files that don't physically exist on the file system (disk). It's mainly used to embed (js, css, image..) files into assemblies and use them like physical files at runtime. |
|||
|
|||
The following code shows how to add embedded files from the current application assembly to the ABP virtual file system: |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpVirtualFileSystemModule))] |
|||
public class MyModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<MyModule>(); |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
ABP creates an `AbpEmbeddedFileProvider` to access the embedded files. |
|||
|
|||
The full name of `en.json` is `MyModule.Volo.Abp.MyModule.Localization.en.json`. Without directory information, ABP uses `.` to split and assume directory information. This creates the following directory structure in the virtual file system: |
|||
|
|||
``` |
|||
[Dir] [/MyModule] |
|||
[Dir] [/MyModule/Volo] |
|||
[Dir] [/MyModule/Volo/Abp] |
|||
[Dir] [/MyModule/Volo/Abp/MyModule] |
|||
[Dir] [/MyModule/Volo/Abp/MyModule/Localization] |
|||
[File] [/MyModule/Volo/Abp/MyModule/Localization/en.json] |
|||
``` |
|||
|
|||
Now you can inject `IVirtualFileProvider` to access embedded files using the directory/file structure above. |
|||
|
|||
## Manifest Embedded File Provider |
|||
|
|||
You might have noticed that using `.` to split and assume directory information can cause confusion if filenames contain dots. |
|||
|
|||
For example, if your filename is `zh.hans.json`, ABP will generate the following directory structure, which isn't what we want: |
|||
|
|||
``` |
|||
[Dir] [/MyModule] |
|||
[Dir] [/MyModule/Volo] |
|||
[Dir] [/MyModule/Volo/Abp] |
|||
[Dir] [/MyModule/Volo/Abp/MyModule] |
|||
[Dir] [/MyModule/Volo/Abp/MyModule/Localization] |
|||
[Dir] [/MyModule/Volo/Abp/MyModule/Localization/zh] |
|||
[File] [/MyModule/Volo/Abp/MyModule/Localization/zh/hans.json] |
|||
``` |
|||
|
|||
Microsoft provides the `Microsoft.Extensions.FileProviders.Manifest` library to solve this problem. |
|||
|
|||
We need to add this package dependency and set `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>` in our project: |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net9.0</TargetFramework> |
|||
<OutputType>Exe</OutputType> |
|||
<Nullable>enable</Nullable> |
|||
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" /> |
|||
<PackageReference Include="Volo.Abp.VirtualFileSystem" Version="9.0.0" /> |
|||
<PackageReference Include="Microsoft.Extensions.FileProviders.Manifest" Version="9.0.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="Volo\Abp\MyModule\Localization\*.json" /> |
|||
<EmbeddedResource Include="Volo\Abp\MyModule\Localization\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
``` |
|||
|
|||
After rebuilding the project, when we decompile `MyModule.dll`, we'll see an additional `Microsoft.Extensions.FileProviders.Embedded.Manifest.xml` file. |
|||
|
|||
 |
|||
|
|||
This manifest file stores all the directory and file information of embedded resources. When ABP finds this file, it will use `ManifestEmbeddedFileProvider` instead of `AbpEmbeddedFileProvider` to access embedded files: |
|||
|
|||
```xml |
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?> |
|||
<Manifest> |
|||
<ManifestVersion>1.0</ManifestVersion> |
|||
<FileSystem> |
|||
<File Name="Microsoft.Extensions.FileProviders.Embedded.Manifest.xml"> |
|||
<ResourcePath>Microsoft.Extensions.FileProviders.Embedded.Manifest.xml</ResourcePath> |
|||
</File> |
|||
<Directory Name="Volo"> |
|||
<Directory Name="Abp"> |
|||
<Directory Name="MyModule"> |
|||
<Directory Name="Localization"> |
|||
<File Name="zh.hans.json"> |
|||
<ResourcePath>MyModule.Volo.Abp.MyModule.Localization.zh.hans.json</ResourcePath> |
|||
</File> |
|||
</Directory> |
|||
</Directory> |
|||
</Directory> |
|||
</Directory> |
|||
</FileSystem> |
|||
</Manifest> |
|||
``` |
|||
|
|||
## Parameters of AddEmbedded Method |
|||
|
|||
The `AddEmbedded` method can take two parameters: |
|||
|
|||
### baseNamespace |
|||
|
|||
This may only be needed if you haven't used the `Manifest Embedded File Provider` and your project's `root namespace` isn't empty. In this case, set your root namespace here. |
|||
|
|||
The `root namespace` is your project's name by default. You can change it or set it to empty in the `csproj` file. |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<RootNamespace>MyModule</RootNamespace> |
|||
</PropertyGroup> |
|||
</Project> |
|||
``` |
|||
|
|||
```xml |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<RootNamespace></RootNamespace> |
|||
</PropertyGroup> |
|||
</Project> |
|||
``` |
|||
|
|||
```csharp |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<MyModule>(baseNamespace: "MyModule"); |
|||
}); |
|||
``` |
|||
|
|||
``` |
|||
[Dir] [/Volo] |
|||
[Dir] [/Volo/Abp] |
|||
[Dir] [/Volo/Abp/MyModule] |
|||
[Dir] [/Volo/Abp/MyModule/Localization] |
|||
[File] [/Volo/Abp/MyModule/Localization/en.json] |
|||
``` |
|||
|
|||
### baseFolder |
|||
|
|||
If you don't want to expose all embedded files in the project, but only want to expose a specific folder (and sub folders/files), you can set the base folder relative to your project root folder. |
|||
|
|||
> baseFolder is only effective when using `Manifest Embedded File Provider`. |
|||
|
|||
You can set the `baseFolder` parameter to `/Volo/Abp/MyModule`, resulting in this directory structure: |
|||
|
|||
```csharp |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<MyModule>(baseFolder: "/Volo/Abp/MyModule"); |
|||
}); |
|||
``` |
|||
|
|||
``` |
|||
[Dir] [Localization] |
|||
[File] [Localization/en.json] |
|||
``` |
|||
|
|||
## Summary |
|||
|
|||
We recommend using the `Manifest Embedded File Provider` in your projects and libraries. Hope this article has been helpful. |
|||
|
|||
## References |
|||
|
|||
[ABP Virtual File System](https://abp.io/docs/latest/framework/infrastructure/virtual-file-system) |
|||
|
|||
[Manifest Embedded File Provider](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/file-providers#manifest-embedded-file-provider) |
|||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 485 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 190 KiB |
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net9.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<WarningsAsErrors>Nullable</WarningsAsErrors> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Minify\Volo.Abp.Minify.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions\Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,15 @@ |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.Minify; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Bundling; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAspNetCoreMvcUiBundlingAbstractionsModule), |
|||
typeof(AbpMinifyModule), |
|||
typeof(AbpVirtualFileSystemModule) |
|||
)] |
|||
public class AbpAspNetCoreBundlingModule : AbpModule |
|||
{ |
|||
} |
|||
@ -1,9 +1,10 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
namespace Volo.Abp.AspNetCore.Bundling; |
|||
|
|||
public class BundleCache : IBundleCache, ISingletonDependency |
|||
{ |
|||
@ -1,7 +1,8 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
namespace Volo.Abp.AspNetCore.Bundling; |
|||
|
|||
public class BundleCacheItem |
|||
{ |
|||
@ -0,0 +1,248 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Bundling.Scripts; |
|||
using Volo.Abp.AspNetCore.Bundling.Styles; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Bundling; |
|||
|
|||
public abstract class BundleManagerBase : IBundleManager |
|||
{ |
|||
public ILogger<BundleManagerBase> Logger { get; set; } |
|||
|
|||
protected readonly AbpBundlingOptions Options; |
|||
protected readonly AbpBundleContributorOptions ContributorOptions; |
|||
protected readonly IScriptBundler ScriptBundler; |
|||
protected readonly IStyleBundler StyleBundler; |
|||
protected readonly IServiceProvider ServiceProvider; |
|||
protected readonly IDynamicFileProvider DynamicFileProvider; |
|||
protected readonly IBundleCache BundleCache; |
|||
|
|||
public BundleManagerBase( |
|||
IOptions<AbpBundlingOptions> options, |
|||
IOptions<AbpBundleContributorOptions> contributorOptions, |
|||
IScriptBundler scriptBundler, |
|||
IStyleBundler styleBundler, |
|||
IServiceProvider serviceProvider, |
|||
IDynamicFileProvider dynamicFileProvider, |
|||
IBundleCache bundleCache) |
|||
{ |
|||
Options = options.Value; |
|||
ContributorOptions = contributorOptions.Value; |
|||
ScriptBundler = scriptBundler; |
|||
ServiceProvider = serviceProvider; |
|||
DynamicFileProvider = dynamicFileProvider; |
|||
BundleCache = bundleCache; |
|||
StyleBundler = styleBundler; |
|||
|
|||
Logger = NullLogger<BundleManagerBase>.Instance; |
|||
} |
|||
|
|||
public virtual async Task<IReadOnlyList<BundleFile>> GetStyleBundleFilesAsync(string bundleName) |
|||
{ |
|||
return await GetBundleFilesAsync(Options.StyleBundles, bundleName, StyleBundler); |
|||
} |
|||
|
|||
public virtual async Task<IReadOnlyList<BundleFile>> GetScriptBundleFilesAsync(string bundleName) |
|||
{ |
|||
return await GetBundleFilesAsync(Options.ScriptBundles, bundleName, ScriptBundler); |
|||
} |
|||
|
|||
protected virtual async Task<IReadOnlyList<BundleFile>> GetBundleFilesAsync(BundleConfigurationCollection bundles, |
|||
string bundleName, IBundler bundler) |
|||
{ |
|||
var files = new List<BundleFile>(); |
|||
|
|||
var contributors = GetContributors(bundles, bundleName); |
|||
var bundleFiles = await GetBundleFilesAsync(contributors); |
|||
var dynamicResources = await GetDynamicResourcesAsync(contributors); |
|||
|
|||
if (!IsBundlingEnabled()) |
|||
{ |
|||
return bundleFiles.Union(dynamicResources).ToImmutableList(); |
|||
} |
|||
|
|||
var localBundleFiles = new List<string>(); |
|||
foreach (var bundleFile in bundleFiles) |
|||
{ |
|||
if (!bundleFile.IsExternalFile) |
|||
{ |
|||
localBundleFiles.Add(bundleFile.FileName); |
|||
} |
|||
else |
|||
{ |
|||
if (localBundleFiles.Count != 0) |
|||
{ |
|||
files.AddRange(AddToBundleCache(bundleName, bundler, localBundleFiles).Files); |
|||
localBundleFiles.Clear(); |
|||
} |
|||
|
|||
files.Add(bundleFile); |
|||
} |
|||
} |
|||
|
|||
if (localBundleFiles.Count != 0) |
|||
{ |
|||
files.AddRange(AddToBundleCache(bundleName, bundler, localBundleFiles).Files); |
|||
} |
|||
|
|||
return files.Union(dynamicResources).ToImmutableList(); |
|||
} |
|||
|
|||
private BundleCacheItem AddToBundleCache(string bundleName, IBundler bundler, List<string> bundleFiles) |
|||
{ |
|||
var bundleRelativePath = |
|||
Options.BundleFolderName.EnsureEndsWith('/') + |
|||
bundleName + "." + bundleFiles.JoinAsString("|").ToMd5() + "." + bundler.FileExtension; |
|||
|
|||
return BundleCache.GetOrAdd(bundleRelativePath, () => |
|||
{ |
|||
var cacheValue = new BundleCacheItem( |
|||
new List<BundleFile> { new BundleFile("/" + bundleRelativePath) } |
|||
); |
|||
|
|||
WatchChanges(cacheValue, bundleFiles, bundleRelativePath); |
|||
|
|||
var bundleResult = bundler.Bundle( |
|||
new BundlerContext( |
|||
bundleRelativePath, |
|||
bundleFiles, |
|||
IsMinficationEnabled() |
|||
) |
|||
); |
|||
|
|||
SaveBundleResult(bundleRelativePath, bundleResult); |
|||
|
|||
return cacheValue; |
|||
}); |
|||
} |
|||
|
|||
private void WatchChanges(BundleCacheItem cacheValue, List<string> files, string bundleRelativePath) |
|||
{ |
|||
lock (cacheValue.WatchDisposeHandles) |
|||
{ |
|||
foreach (var file in files) |
|||
{ |
|||
var watchDisposeHandle = GetFileProvider().Watch(file).RegisterChangeCallback(_ => |
|||
{ |
|||
lock (cacheValue.WatchDisposeHandles) |
|||
{ |
|||
cacheValue.WatchDisposeHandles.ForEach(h => h.Dispose()); |
|||
cacheValue.WatchDisposeHandles.Clear(); |
|||
} |
|||
|
|||
BundleCache.Remove(bundleRelativePath); |
|||
DynamicFileProvider.Delete("/wwwroot/" + bundleRelativePath); //TODO: get rid of wwwroot!
|
|||
}, null); |
|||
|
|||
cacheValue.WatchDisposeHandles.Add(watchDisposeHandle); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected virtual void SaveBundleResult(string bundleRelativePath, BundleResult bundleResult) |
|||
{ |
|||
var fileName = bundleRelativePath.Substring(bundleRelativePath.IndexOf('/') + 1); |
|||
|
|||
DynamicFileProvider.AddOrUpdate( |
|||
new InMemoryFileInfo( |
|||
"/wwwroot/" + bundleRelativePath, //TODO: get rid of wwwroot!
|
|||
Encoding.UTF8.GetBytes(bundleResult.Content), |
|||
fileName |
|||
) |
|||
); |
|||
} |
|||
|
|||
public abstract bool IsBundlingEnabled(); |
|||
|
|||
|
|||
protected abstract bool IsMinficationEnabled(); |
|||
|
|||
protected virtual async Task<List<BundleFile>> GetBundleFilesAsync(List<IBundleContributor> contributors) |
|||
{ |
|||
var context = CreateBundleConfigurationContext(); |
|||
|
|||
foreach (var contributor in contributors) |
|||
{ |
|||
await contributor.PreConfigureBundleAsync(context); |
|||
} |
|||
|
|||
foreach (var contributor in contributors) |
|||
{ |
|||
await contributor.ConfigureBundleAsync(context); |
|||
} |
|||
|
|||
foreach (var contributor in contributors) |
|||
{ |
|||
await contributor.PostConfigureBundleAsync(context); |
|||
} |
|||
|
|||
return context.Files; |
|||
} |
|||
|
|||
protected virtual async Task<List<BundleFile>> GetDynamicResourcesAsync(List<IBundleContributor> contributors) |
|||
{ |
|||
var context = CreateBundleConfigurationContext(); |
|||
|
|||
foreach (var contributor in contributors) |
|||
{ |
|||
await contributor.ConfigureDynamicResourcesAsync(context); |
|||
} |
|||
|
|||
return context.Files; |
|||
} |
|||
|
|||
protected virtual BundleConfigurationContext CreateBundleConfigurationContext() |
|||
{ |
|||
return new BundleConfigurationContext(ServiceProvider, GetFileProvider(), |
|||
Options.Parameters); |
|||
} |
|||
|
|||
protected abstract IFileProvider GetFileProvider(); |
|||
|
|||
protected virtual List<IBundleContributor> GetContributors(BundleConfigurationCollection bundles, string bundleName) |
|||
{ |
|||
var contributors = new List<IBundleContributor>(); |
|||
|
|||
AddContributorsWithBaseBundles(contributors, bundles, bundleName); |
|||
|
|||
for (var i = 0; i < contributors.Count; ++i) |
|||
{ |
|||
var extensions = ContributorOptions.Extensions(contributors[i].GetType()).GetAll(); |
|||
if (extensions.Count > 0) |
|||
{ |
|||
contributors.InsertRange(i + 1, extensions); |
|||
i += extensions.Count; |
|||
} |
|||
} |
|||
|
|||
return contributors; |
|||
} |
|||
|
|||
protected virtual void AddContributorsWithBaseBundles(List<IBundleContributor> contributors, |
|||
BundleConfigurationCollection bundles, string bundleName) |
|||
{ |
|||
var bundleConfiguration = bundles.Get(bundleName); |
|||
|
|||
foreach (var baseBundleName in bundleConfiguration.BaseBundles) |
|||
{ |
|||
AddContributorsWithBaseBundles(contributors, bundles, baseBundleName); //Recursive call
|
|||
} |
|||
|
|||
var selfContributors = bundleConfiguration.Contributors.GetAll(); |
|||
|
|||
if (selfContributors.Any()) |
|||
{ |
|||
contributors.AddRange(selfContributors); |
|||
} |
|||
} |
|||
} |
|||
@ -1,4 +1,4 @@ |
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
namespace Volo.Abp.AspNetCore.Bundling; |
|||
|
|||
public class BundleResult |
|||
{ |
|||
@ -1,6 +1,6 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
namespace Volo.Abp.AspNetCore.Bundling; |
|||
|
|||
public class BundlerContext : IBundlerContext |
|||
{ |
|||
@ -1,6 +1,6 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
namespace Volo.Abp.AspNetCore.Bundling; |
|||
|
|||
public interface IBundleCache |
|||
{ |
|||
@ -1,7 +1,8 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
namespace Volo.Abp.AspNetCore.Bundling; |
|||
|
|||
public interface IBundleManager |
|||
{ |
|||
@ -1,4 +1,6 @@ |
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Bundling; |
|||
|
|||
public interface IBundler |
|||
{ |
|||
@ -1,6 +1,6 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
namespace Volo.Abp.AspNetCore.Bundling; |
|||
|
|||
public interface IBundlerContext |
|||
{ |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.AspNetCore.Bundling.Scripts; |
|||
|
|||
public interface IScriptBundler : IBundler |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.AspNetCore.Bundling.Styles; |
|||
|
|||
public interface IStyleBundler : IBundler |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
using System.Text; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.Bundling.Styles; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Threading; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAspNetCoreComponentsMauiBlazorModule), |
|||
typeof(AbpAspNetCoreBundlingModule) |
|||
)] |
|||
public class AbpAspNetCoreComponentsMauiBlazorBundlingModule : AbpModule |
|||
{ |
|||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); |
|||
} |
|||
|
|||
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) |
|||
{ |
|||
await InitialGlobalAssetsAsync(context); |
|||
} |
|||
|
|||
protected virtual async Task InitialGlobalAssetsAsync(ApplicationInitializationContext context) |
|||
{ |
|||
var bundlingOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpBundlingOptions>>().Value; |
|||
var logger = context.ServiceProvider.GetRequiredService<ILogger<AbpAspNetCoreComponentsMauiBlazorBundlingModule>>(); |
|||
if (!bundlingOptions.GlobalAssets.Enabled) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var bundleManager = context.ServiceProvider.GetRequiredService<BundleManager>(); |
|||
var mauiBlazorContentFileProvider = context.ServiceProvider.GetRequiredService<IMauiBlazorContentFileProvider>(); |
|||
var dynamicFileProvider = context.ServiceProvider.GetRequiredService<IDynamicFileProvider>(); |
|||
if (!bundlingOptions.GlobalAssets.GlobalStyleBundleName.IsNullOrWhiteSpace()) |
|||
{ |
|||
var styleFiles = await bundleManager.GetStyleBundleFilesAsync(bundlingOptions.GlobalAssets.GlobalStyleBundleName); |
|||
var styles = string.Empty; |
|||
foreach (var file in styleFiles) |
|||
{ |
|||
var fileInfo = mauiBlazorContentFileProvider.GetFileInfo(file.FileName); |
|||
if (!fileInfo.Exists) |
|||
{ |
|||
logger.LogError($"Could not find the file: {file.FileName}"); |
|||
continue; |
|||
} |
|||
|
|||
var fileContent = await fileInfo.ReadAsStringAsync(); |
|||
if (!bundleManager.IsBundlingEnabled()) |
|||
{ |
|||
fileContent = CssRelativePath.Adjust(fileContent, |
|||
file.FileName, |
|||
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")); |
|||
|
|||
styles += $"/*{file.FileName}*/{Environment.NewLine}{fileContent}{Environment.NewLine}{Environment.NewLine}"; |
|||
} |
|||
else |
|||
{ |
|||
styles += $"{fileContent}{Environment.NewLine}{Environment.NewLine}"; |
|||
} |
|||
} |
|||
|
|||
dynamicFileProvider.AddOrUpdate( |
|||
new InMemoryFileInfo("/wwwroot/" + bundlingOptions.GlobalAssets.CssFileName, |
|||
Encoding.UTF8.GetBytes(styles), |
|||
bundlingOptions.GlobalAssets.CssFileName)); |
|||
} |
|||
|
|||
if (!bundlingOptions.GlobalAssets.GlobalScriptBundleName.IsNullOrWhiteSpace()) |
|||
{ |
|||
var scriptFiles = await bundleManager.GetScriptBundleFilesAsync(bundlingOptions.GlobalAssets.GlobalScriptBundleName); |
|||
var scripts = string.Empty; |
|||
foreach (var file in scriptFiles) |
|||
{ |
|||
var fileInfo = mauiBlazorContentFileProvider.GetFileInfo(file.FileName); |
|||
if (!fileInfo.Exists) |
|||
{ |
|||
logger.LogError($"Could not find the file: {file.FileName}"); |
|||
continue; |
|||
} |
|||
|
|||
var fileContent = await fileInfo.ReadAsStringAsync(); |
|||
if (!bundleManager.IsBundlingEnabled()) |
|||
{ |
|||
scripts += $"{fileContent.EnsureEndsWith(';')}{Environment.NewLine}{Environment.NewLine}"; |
|||
} |
|||
else |
|||
{ |
|||
scripts += $"//{file.FileName}{Environment.NewLine}{fileContent.EnsureEndsWith(';')}{Environment.NewLine}{Environment.NewLine}"; |
|||
} |
|||
} |
|||
|
|||
dynamicFileProvider.AddOrUpdate( |
|||
new InMemoryFileInfo("/wwwroot/" + bundlingOptions.GlobalAssets.JavaScriptFileName, |
|||
Encoding.UTF8.GetBytes(scripts), |
|||
bundlingOptions.GlobalAssets.JavaScriptFileName)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using Microsoft.AspNetCore.Components.WebView.Maui; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling; |
|||
|
|||
public class AbpBlazorWebView : BlazorWebView |
|||
{ |
|||
public override IFileProvider CreateFileProvider(string contentRootDir) |
|||
{ |
|||
return new CompositeFileProvider(Handler!.GetRequiredService<IMauiBlazorContentFileProvider>(), base.CreateFileProvider(contentRootDir)); |
|||
} |
|||
} |
|||
@ -0,0 +1,125 @@ |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Bundling; |
|||
using Volo.Abp.AspNetCore.Bundling.Scripts; |
|||
using Volo.Abp.AspNetCore.Bundling.Styles; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling; |
|||
|
|||
public class BundleManager : BundleManagerBase, ITransientDependency |
|||
{ |
|||
protected IMauiBlazorContentFileProvider MauiBlazorContentFileProvider { get; } |
|||
|
|||
public BundleManager( |
|||
IOptions<AbpBundlingOptions> options, |
|||
IOptions<AbpBundleContributorOptions> contributorOptions, |
|||
IScriptBundler scriptBundler, |
|||
IStyleBundler styleBundler, |
|||
IServiceProvider serviceProvider, |
|||
IDynamicFileProvider dynamicFileProvider, |
|||
IBundleCache bundleCache, |
|||
IMauiBlazorContentFileProvider mauiBlazorContentFileProvider) : base( |
|||
options, |
|||
contributorOptions, |
|||
scriptBundler, |
|||
styleBundler, |
|||
serviceProvider, |
|||
dynamicFileProvider, |
|||
bundleCache) |
|||
{ |
|||
MauiBlazorContentFileProvider = mauiBlazorContentFileProvider; |
|||
} |
|||
|
|||
public override bool IsBundlingEnabled() |
|||
{ |
|||
switch (Options.Mode) |
|||
{ |
|||
case BundlingMode.None: |
|||
return false; |
|||
case BundlingMode.Bundle: |
|||
case BundlingMode.BundleAndMinify: |
|||
return true; |
|||
case BundlingMode.Auto: |
|||
return !IsDebug(); |
|||
default: |
|||
throw new AbpException($"Unhandled {nameof(BundlingMode)}: {Options.Mode}"); |
|||
} |
|||
} |
|||
|
|||
protected async override Task<List<BundleFile>> GetBundleFilesAsync(List<IBundleContributor> contributors) |
|||
{ |
|||
var files = await base.GetBundleFilesAsync(contributors); |
|||
|
|||
foreach (var file in files) |
|||
{ |
|||
await CopyFileToAppDataDirectoryAsync(file); |
|||
} |
|||
|
|||
return files; |
|||
} |
|||
|
|||
protected virtual async Task CopyFileToAppDataDirectoryAsync(BundleFile file) |
|||
{ |
|||
if (file.IsExternalFile) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var fileName = Path.Combine("wwwroot", file.FileName); |
|||
if(MauiBlazorContentFileProvider.GetFileInfo(fileName).Exists) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
await using var inputStream = await FileSystem.Current.OpenAppPackageFileAsync(fileName); |
|||
var targetFile = Path.Combine(FileSystem.Current.AppDataDirectory, fileName); |
|||
var fileDirectory = Path.GetDirectoryName(targetFile)!; |
|||
if (!Path.Exists(fileDirectory)) |
|||
{ |
|||
Directory.CreateDirectory(fileDirectory); |
|||
} |
|||
await using var outputStream = File.Create(targetFile); |
|||
await inputStream.CopyToAsync(outputStream); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.LogError($"Could not copy the file to the app data directory: {fileName}", e); |
|||
} |
|||
} |
|||
|
|||
protected override bool IsMinficationEnabled() |
|||
{ |
|||
switch (Options.Mode) |
|||
{ |
|||
case BundlingMode.None: |
|||
case BundlingMode.Bundle: |
|||
return false; |
|||
case BundlingMode.BundleAndMinify: |
|||
return true; |
|||
case BundlingMode.Auto: |
|||
return !IsDebug(); |
|||
default: |
|||
throw new AbpException($"Unhandled {nameof(BundlingMode)}: {Options.Mode}"); |
|||
} |
|||
} |
|||
|
|||
protected virtual bool IsDebug() |
|||
{ |
|||
#if DEBUG
|
|||
return true; |
|||
#else
|
|||
retur false; |
|||
#endif
|
|||
} |
|||
|
|||
protected override IFileProvider GetFileProvider() |
|||
{ |
|||
return MauiBlazorContentFileProvider; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Microsoft.Extensions.FileProviders; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling; |
|||
|
|||
public interface IMauiBlazorContentFileProvider : IFileProvider |
|||
{ |
|||
string ContentRootPath { get; } |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.Minify; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling; |
|||
|
|||
public abstract class MauiBlazorBundlerBase : BundlerBase |
|||
{ |
|||
protected IMauiBlazorContentFileProvider MauiBlazorContentFileProvider { get; } |
|||
|
|||
public MauiBlazorBundlerBase( |
|||
IMauiBlazorContentFileProvider mauiBlazorContentFileProvider, |
|||
IMinifier minifier, |
|||
IOptions<AbpBundlingOptions> bundlingOptions) : base(minifier, |
|||
bundlingOptions) |
|||
{ |
|||
MauiBlazorContentFileProvider = mauiBlazorContentFileProvider; |
|||
} |
|||
|
|||
protected override IFileInfo FindFileInfo(string file) |
|||
{ |
|||
return MauiBlazorContentFileProvider.GetFileInfo(file); |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Microsoft.Extensions.Primitives; |
|||
using Microsoft.Maui.Controls.PlatformConfiguration; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling; |
|||
|
|||
public class MauiBlazorContentFileProvider : IMauiBlazorContentFileProvider, ISingletonDependency |
|||
{ |
|||
private readonly IVirtualFileProvider _virtualFileProvider; |
|||
private readonly IFileProvider _fileProvider; |
|||
private string _rootPath = "/wwwroot"; |
|||
|
|||
public MauiBlazorContentFileProvider(IVirtualFileProvider virtualFileProvider) |
|||
{ |
|||
_virtualFileProvider = virtualFileProvider; |
|||
_fileProvider = CreateFileProvider(); |
|||
} |
|||
|
|||
public string ContentRootPath => FileSystem.Current.AppDataDirectory; |
|||
|
|||
public IFileInfo GetFileInfo(string subpath) |
|||
{ |
|||
if (string.IsNullOrEmpty(subpath)) |
|||
{ |
|||
return new NotFoundFileInfo(subpath); |
|||
} |
|||
|
|||
var fileInfo = _fileProvider.GetFileInfo(subpath); |
|||
return fileInfo.Exists ? fileInfo : _fileProvider.GetFileInfo( _rootPath + subpath.EnsureStartsWith('/')); |
|||
} |
|||
|
|||
public IDirectoryContents GetDirectoryContents(string subpath) |
|||
{ |
|||
if (string.IsNullOrEmpty(subpath)) |
|||
{ |
|||
return NotFoundDirectoryContents.Singleton; |
|||
} |
|||
|
|||
var directory = _fileProvider.GetDirectoryContents(subpath); |
|||
return directory.Exists ? directory : _fileProvider.GetDirectoryContents( _rootPath + subpath.EnsureStartsWith('/')); |
|||
} |
|||
|
|||
public IChangeToken Watch(string filter) |
|||
{ |
|||
return new CompositeChangeToken( |
|||
[ |
|||
_fileProvider.Watch(_rootPath + filter), |
|||
_fileProvider.Watch(filter) |
|||
] |
|||
); |
|||
} |
|||
|
|||
protected virtual IFileProvider CreateFileProvider() |
|||
{ |
|||
var assetsDirectory = Path.Combine(ContentRootPath, _rootPath.TrimStart('/')); |
|||
if (!Path.Exists(assetsDirectory)) |
|||
{ |
|||
Directory.CreateDirectory(assetsDirectory); |
|||
} |
|||
|
|||
return new CompositeFileProvider(new PhysicalFileProvider(assetsDirectory), _virtualFileProvider); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Bundling; |
|||
using Volo.Abp.AspNetCore.Bundling.Scripts; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.Minify.Scripts; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.Scripts; |
|||
|
|||
public class ScriptBundler : MauiBlazorBundlerBase, IScriptBundler |
|||
{ |
|||
public override string FileExtension => "js"; |
|||
|
|||
public ScriptBundler( |
|||
IMauiBlazorContentFileProvider mauiBlazorContentFileProvider, |
|||
IJavascriptMinifier minifier, |
|||
IOptions<AbpBundlingOptions> bundlingOptions) |
|||
: base( |
|||
mauiBlazorContentFileProvider, |
|||
minifier, |
|||
bundlingOptions) |
|||
{ |
|||
} |
|||
|
|||
protected override string ProcessBeforeAddingToTheBundle(IBundlerContext context, string filePath, string fileContent) |
|||
{ |
|||
return fileContent.EnsureEndsWith(';') + Environment.NewLine; |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Bundling; |
|||
using Volo.Abp.AspNetCore.Bundling.Styles; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.Bundling.Styles; |
|||
using Volo.Abp.Minify.Styles; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.Styles; |
|||
|
|||
public class StyleBundler : MauiBlazorBundlerBase, IStyleBundler |
|||
{ |
|||
private readonly IMauiBlazorContentFileProvider _mauiBlazorContentFileProvider; |
|||
public override string FileExtension => "css"; |
|||
|
|||
public StyleBundler( |
|||
IMauiBlazorContentFileProvider mauiBlazorContentFileProvider, |
|||
ICssMinifier minifier, |
|||
IOptions<AbpBundlingOptions> bundlingOptions) |
|||
: base( |
|||
mauiBlazorContentFileProvider, |
|||
minifier, |
|||
bundlingOptions) |
|||
{ |
|||
_mauiBlazorContentFileProvider = mauiBlazorContentFileProvider; |
|||
} |
|||
|
|||
public string GetAbsolutePath(string relativePath) |
|||
{ |
|||
return Path.Combine(_mauiBlazorContentFileProvider.ContentRootPath, "wwwroot", relativePath.RemovePreFix("/")).Replace("file://", ""); |
|||
} |
|||
|
|||
protected override string ProcessBeforeAddingToTheBundle(IBundlerContext context, string filePath, string fileContent) |
|||
{ |
|||
return CssRelativePath.Adjust( |
|||
fileContent, |
|||
GetAbsolutePath(filePath), |
|||
GetAbsolutePath(context.BundleRelativePath) |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
|
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> |
|||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks> |
|||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> |
|||
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> --> |
|||
<UseMaui>true</UseMaui> |
|||
<SingleProject>true</SingleProject> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
|
|||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion> |
|||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion> |
|||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion> |
|||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion> |
|||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion> |
|||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" /> |
|||
<PackageReference Include="Microsoft.Maui.Controls"/> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.AspNetCore.Bundling\Volo.Abp.AspNetCore.Bundling.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.AspNetCore.Components.MauiBlazor\Volo.Abp.AspNetCore.Components.MauiBlazor.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<Folder Include="Platforms\Android\" /> |
|||
<Folder Include="Platforms\iOS\" /> |
|||
<Folder Include="Platforms\MacCatalyst\" /> |
|||
<Folder Include="Platforms\Windows\" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,25 @@ |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.Extensions.FileProviders; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Bundling; |
|||
using Volo.Abp.Minify; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
public abstract class MvcUiBundlerBase : BundlerBase |
|||
{ |
|||
protected IWebHostEnvironment WebHostingEnvironment { get; } |
|||
|
|||
protected MvcUiBundlerBase( |
|||
IWebHostEnvironment webHostingEnvironment, |
|||
IMinifier minifier, |
|||
IOptions<AbpBundlingOptions> bundlingOptions) : base(minifier, bundlingOptions) |
|||
{ |
|||
WebHostingEnvironment = webHostingEnvironment; |
|||
} |
|||
|
|||
protected override IFileInfo FindFileInfo(string file) |
|||
{ |
|||
return WebHostingEnvironment.WebRootFileProvider.GetFileInfo(file); |
|||
} |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.Scripts; |
|||
|
|||
public interface IScriptBundler : IBundler |
|||
{ |
|||
|
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.Styles; |
|||
|
|||
public interface IStyleBundler : IBundler |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
# Installation Notes for Account Module |
|||
|
|||
Account module implements the basic authentication features like login, register, forgot password and account management. |
|||
|
|||
This module is based on [Microsoft's Identity library](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0&tabs=visual-studio) and the [Identity Module](https://docs.abp.io/en/abp/latest/modules/identity). It has [IdentityServer](https://docs.abp.io/en/abp/latest/modules/identity-server) integration (based on the [IdentityServer Module](https://docs.abp.io/en/abp/latest/modules/identity-server)) and [OpenIddict](https://github.com/openiddict) integration (based on the [Openiddict Module](https://docs.abp.io/en/abp/latest/modules/openiddict)) to provide single sign-on, access control and other advanced authentication features. |
|||
|
|||
## Documentation |
|||
|
|||
For detailed information and usage instructions, please visit the [Account Module documentation](https://abp.io/docs/latest/Modules/Account). |
|||
@ -0,0 +1,9 @@ |
|||
# Installation Notes for Audit Logging Module |
|||
|
|||
The ABP Audit Logging module provides automatic audit logging for web requests, service methods, and entity changes. It helps you track user activities and changes in your application. |
|||
|
|||
This module is part of the ABP Framework and provides comprehensive audit logging capabilities including entity history tracking, exception logging, and user activity monitoring. |
|||
|
|||
## Documentation |
|||
|
|||
For detailed information and usage instructions, please visit the [Audit Logging documentation](https://abp.io/docs/latest/framework/infrastructure/audit-logging). |
|||
@ -0,0 +1,12 @@ |
|||
# Installation Notes for Background Jobs Module |
|||
|
|||
Background jobs are used to queue some tasks to be executed in the background. You may need background jobs for several reasons. Here are some examples: |
|||
|
|||
- To perform **long-running tasks** without having the users wait. For example, a user presses a 'report' button to start a long-running reporting job. You add this job to the **queue** and send the report's result to your user via email when it's completed. |
|||
- To create **re-trying** and **persistent tasks** to **guarantee** that a code will be **successfully executed**. For example, you can send emails in a background job to overcome **temporary failures** and **guarantee** that it eventually will be sent. That way users do not wait while sending emails. |
|||
|
|||
Background jobs are **persistent** that means they will be **re-tried** and **executed** later even if your application crashes. |
|||
|
|||
## Documentation |
|||
|
|||
For detailed information and usage instructions, please visit the [Background Jobs documentation](https://abp.io/docs/latest/framework/infrastructure/background-jobs). |
|||
@ -0,0 +1,13 @@ |
|||
# Installation Notes for Basic Theme Module (MVC) |
|||
|
|||
The Basic Theme is a theme implementation for the ASP.NET Core MVC / Razor Pages UI. It is a minimalist theme that doesn't add any styling on top of the plain [Bootstrap](https://getbootstrap.com/). You can take the Basic Theme as the base theme and build your own theme or styling on top of it. See the Customization section. |
|||
|
|||
The Basic Theme has RTL (Right-to-Left language) support. |
|||
|
|||
If you are looking for a professional, enterprise ready theme, you can check the [Lepton Theme](https://abp.io/themes), which is a part of the ABP. |
|||
|
|||
See the [Theming document](https://github.com/abpframework/abp/blob/rel-9.1/docs/en/framework/ui/mvc-razor-pages/theming.md) to learn about themes. |
|||
|
|||
## Documentation |
|||
|
|||
For detailed information and usage instructions, please visit the [Basic Theme documentation](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/basic-theme). |
|||
@ -0,0 +1,7 @@ |
|||
# Installation Notes for Basic Theme Module (Blazor) |
|||
|
|||
The Basic Theme is a theme implementation for the Blazor UI. It is a minimalist theme that doesn't add any styling on top of the plain [Bootstrap](https://getbootstrap.com/). You can take the Basic Theme as the base theme and build your own theme or styling on top of it. See the Customization section. |
|||
|
|||
## Documentation |
|||
|
|||
For detailed information and usage instructions, please visit the [Blazor UI Basic Theme documentation](https://abp.io/docs/latest/framework/ui/blazor/basic-theme?UI=BlazorServer). |
|||
@ -0,0 +1,16 @@ |
|||
# Installation Notes for Blob Storing Database Module |
|||
|
|||
It is typical to store file contents in an application and read these file contents on need. Not only files, but you may also need to save various types of large binary objects, a.k.a. [BLOBs](https://en.wikipedia.org/wiki/Binary_large_object), into a storage. For example, you may want to save user profile pictures. |
|||
|
|||
A BLOB is a typically byte array. There are various places to store a BLOB item; storing in the local file system, in a shared database or on the [Azure BLOB storage](https://azure.microsoft.com/en-us/products/storage/blobs/) can be options. |
|||
|
|||
The ABP provides an abstraction to work with BLOBs and provides some pre-built storage providers that you can easily integrate to. Having such an abstraction has some benefits; |
|||
|
|||
You can easily integrate to your favorite BLOB storage provides with a few lines of configuration. |
|||
You can then easily change your BLOB storage without changing your application code. |
|||
If you want to create reusable application modules, you don't need to make assumption about how the BLOBs are stored. |
|||
ABP BLOB Storage system is also compatible to other ABP features like multi-tenancy. |
|||
|
|||
## Documentation |
|||
|
|||
For detailed information and usage instructions, please visit the [BLOB Storing documentation](https://abp.io/docs/latest/framework/infrastructure/blob-storing). |
|||
@ -0,0 +1,28 @@ |
|||
# Installation Notes for Blogging Module |
|||
|
|||
The ABP Blogging module provides a simple blogging system for ABP applications. It allows you to create and manage blogs, posts, tags, and comments. The module includes both a public interface for readers and an admin interface for content management. |
|||
|
|||
Key features of the Blogging module: |
|||
- Multiple blog support |
|||
- Post management with rich text editing |
|||
- Commenting functionality |
|||
- Social media sharing |
|||
- Admin interface for content management |
|||
|
|||
## Required Configurations |
|||
|
|||
The Blogging module requires **permission** settings to be configured after installation. Ensure that the necessary roles have the appropriate access rights for managing blogs, posts, comments and others. |
|||
|
|||
### Update Database |
|||
|
|||
The Blogging module requires database migrations to be applied. Following installation, you must update the database to create the necessary tables. |
|||
|
|||
### Permissions |
|||
|
|||
Enable the following permissions for the roles that require access to the Blogging module: |
|||
|
|||
 |
|||
|
|||
## Documentation |
|||
|
|||
For detailed information and usage instructions, please visit the [Blogging Module documentation](https://abp.io/docs/latest/Modules/Cms-Kit/Blogging). |
|||
|
After Width: | Height: | Size: 36 KiB |
@ -0,0 +1,48 @@ |
|||
# Installation Notes for CMS Kit Module |
|||
|
|||
The ABP CMS Kit module provides a set of reusable Content Management System (CMS) features for your ABP-based applications. It offers ready-to-use UI components and APIs for common content management requirements. |
|||
|
|||
This module is part of the ABP Framework and provides features like comments, ratings, tags, blogs, and more to help you build content-rich applications. |
|||
|
|||
## Required Configurations |
|||
|
|||
The CmsKit module requires **permission** settings to be configured after installation. Ensure that the necessary roles have the appropriate access rights for managing blogs, posts, comments and others. |
|||
|
|||
### Enable CmsKit |
|||
|
|||
To enable the CmsKit module, add the following line to the `GlobalFeatureConfigurator` class of your module: |
|||
|
|||
```csharp |
|||
public static void Configure() |
|||
{ |
|||
OneTimeRunner.Run(() => |
|||
{ |
|||
/* You can configure (enable/disable) global features of the used modules here. |
|||
* Please refer to the documentation to learn more about the Global Features System: |
|||
* https://docs.abp.io/en/abp/latest/Global-Features |
|||
*/ |
|||
|
|||
GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit => |
|||
{ |
|||
cmsKit.EnableAll(); |
|||
// or |
|||
// cmsKit.Tags.Enable(); |
|||
// cmsKit.Comments.Enable(); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
### Database Migrations |
|||
|
|||
The CmsKit module requires database migrations to be applied. After enable **CmsKit**, Add a new migration and update the database to create the necessary tables. |
|||
|
|||
### Permissions |
|||
|
|||
Enable the following permissions for the roles that require access to the CmsKit module: |
|||
|
|||
 |
|||
|
|||
## Documentation |
|||
|
|||
For detailed information and usage instructions, please visit the [CMS Kit documentation](https://abp.io/docs/latest/modules/cms-kit). |
|||
|
After Width: | Height: | Size: 45 KiB |
@ -0,0 +1,31 @@ |
|||
# Installation Notes for Docs Module |
|||
|
|||
The ABP Docs module provides a complete documentation system for ABP applications. It allows you to create, manage, and publish documentation from various sources like GitHub, GitLab, or local file system. The module includes both a public interface for readers and an admin interface for documentation management. |
|||
|
|||
Key features of the Docs module: |
|||
- Multiple documentation projects support |
|||
- Version control integration |
|||
- Markdown support |
|||
- Navigation generation |
|||
- Full-text search |
|||
- Multi-language support |
|||
- Admin interface for documentation management |
|||
|
|||
|
|||
## Required Configurations |
|||
|
|||
The Docs module requires **permission** settings to be configured after installation and database update. |
|||
|
|||
### Update Database |
|||
|
|||
The Docs module requires database migrations to be applied. Following installation, you must update the database to create the necessary tables. |
|||
|
|||
### Permissions |
|||
|
|||
Enable the following permissions for the roles that require access to the Docs module: |
|||
|
|||
 |
|||
|
|||
## Documentation |
|||
|
|||
For detailed information and usage instructions, please visit the [Docs Module documentation](https://abp.io/docs/latest/Modules/Docs). |
|||
|
After Width: | Height: | Size: 36 KiB |
@ -0,0 +1,24 @@ |
|||
# Installation Notes for Feature Management Module |
|||
|
|||
The Feature Management module provides a way to define and manage features in an ABP application. Features are used to enable or disable specific functionalities of an application based on different conditions, such as tenant subscription levels or user preferences. |
|||
|
|||
Key capabilities of the Feature Management module: |
|||
- Define features with different value types (boolean, numeric, etc.) |
|||
- Group features by providers (tenant, edition, etc.) |
|||
- Manage feature values through a user interface |
|||
- Check feature status in your application code |
|||
|
|||
## NuGet Packages |
|||
|
|||
The following NuGet packages are required for the Feature Management module: |
|||
- `Volo.Abp.FeatureManagement.Application` |
|||
- `Volo.Abp.FeatureManagement.HttpApi` |
|||
- `Volo.Abp.FeatureManagement.EntityFrameworkCore` (for EF Core) |
|||
- `Volo.Abp.FeatureManagement.MongoDB` (for MongoDB) |
|||
- `Volo.Abp.FeatureManagement.Web` (for MVC UI) |
|||
- `Volo.Abp.FeatureManagement.Blazor.Server` (for Blazor Server UI) |
|||
- `Volo.Abp.FeatureManagement.Blazor.WebAssembly` (for Blazor WebAssembly UI) |
|||
|
|||
## Documentation |
|||
|
|||
For detailed information and usage instructions, please visit the [Feature Management Module documentation](https://abp.io/docs/latest/Modules/Feature-Management). |
|||