mirror of https://github.com/abpframework/abp.git
138 changed files with 5417 additions and 203 deletions
@ -1,3 +1,3 @@ |
|||||
## ABP Tag Helpers |
## ABP Tag Helpers |
||||
|
|
||||
"ABP tag helpers" is not documented yet. You can see a [demo of components](http://bootstrap-taghelpers.abp.io/) for now. |
"ABP tag helpers" documentation is creating now. You can see a [demo of components](http://bootstrap-taghelpers.abp.io/) for now. |
||||
@ -0,0 +1,90 @@ |
|||||
|
# Customizing the Existing Modules |
||||
|
|
||||
|
ABP Framework provides was designed to support to build fully [modular applications](Module-Development-Basics.md) and systems. It also provides some [pre-built application modules](Modules/Index.md) those are **ready to use** in any kind of application. |
||||
|
|
||||
|
For example, you can **re-use** the [Identity Management Module](Modules/Identity.md) to add user, role and permission management to your application. The [application startup template](Startup-Templates/Application.md) already comes with Identity and some other modules **pre-installed**. |
||||
|
|
||||
|
## Re-Using an Application Module |
||||
|
|
||||
|
You have two options to re-use an application module. |
||||
|
|
||||
|
### As Package References |
||||
|
|
||||
|
You can add **NuGet** & **NPM** package references of the related module to your application and configure the module (based on its documentation) to integrate to your application. |
||||
|
|
||||
|
As mentioned before, the [application startup template](Startup-Templates/Application.md) already comes with some **fundamental modules pre-installed**. It uses the modules as NuGet & NPM package references. |
||||
|
|
||||
|
This approach has the following benefits: |
||||
|
|
||||
|
* Your solution will be **clean** and only contains your **own application code**. |
||||
|
* You can **easily upgrade** a module when a new version is available. `abp update` [CLI](CLI.md) command makes it even easier. In this way, you can continue to get **new features and bug fixes**. |
||||
|
|
||||
|
However, there is a drawback: |
||||
|
|
||||
|
* You may not able to **customize** the module source code as it is in your own solution. |
||||
|
|
||||
|
This document explains **how to customize or extend** a depended module without need to change its source code. While it is limited compared to a full source code change opportunity, there are still some good ways to make some customizations. |
||||
|
|
||||
|
If you don't think to make huge changes on the pre-built modules, re-using them as package reference is the recommended way. |
||||
|
|
||||
|
### Including the Source Code |
||||
|
|
||||
|
If you want to make **huge changes** or add **major features** on a pre-built module, but the available extension points are not enough, you can consider to directly work the source code of the depended module. |
||||
|
|
||||
|
In this case, you typically **add the source code** of the module to your solution and **replace package references** by local project references. **[ABP CLI](CLI.md)** automates this process for you. |
||||
|
|
||||
|
#### Separating the Module Solution |
||||
|
|
||||
|
You may prefer to not include the module source code **directly into your solution**. Every module consists of 10+ project files and adding **multiple modules** may impact on the **size** of your solution **load & development time.** Also, you may have different development teams working on different modules, so you don't want to make the module code available to the application development team. |
||||
|
|
||||
|
In any case, you can create a **separate solution** for the desired module and depend on the module as project references out of the solution. We do it like that for the [abp repository](https://github.com/abpframework/abp/). |
||||
|
|
||||
|
> One problem we see is Visual Studio doesn't play nice with this kind of approach (it doesn't support well to have references to local projects out of the solution directory). If you get error while building the application (depends on an external module), run `dotnet restore` in the command line after opening the application's solution in the Visual Studio. |
||||
|
|
||||
|
#### Publishing the Customized Module as Packages |
||||
|
|
||||
|
One alternative scenario could be re-packaging the module source code (as NuGet/NPM packages) and using as package references. You can use a local private NuGet/NPM server for your company. |
||||
|
|
||||
|
## Module Customization / Extending Approaches |
||||
|
|
||||
|
This section suggests some approaches if you decided to use pre-built application modules as NuGet/NPM package references. |
||||
|
|
||||
|
### Extending Entities |
||||
|
|
||||
|
In some cases, you may want to add some additional properties (and database fields) for an entity defined in a depended module. This section will cover some different approaches to make this possible. |
||||
|
|
||||
|
#### Extra Properties |
||||
|
|
||||
|
[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these entities. |
||||
|
|
||||
|
Example: |
||||
|
|
||||
|
````csharp |
||||
|
//SET AN EXTRA PROPERTY |
||||
|
var user = await _identityUserRepository.GetAsync(userId); |
||||
|
user.SetProperty("Title", "My custom title value!"); |
||||
|
await _identityUserRepository.UpdateAsync(user); |
||||
|
|
||||
|
//GET AN EXTRA PROPERTY |
||||
|
var user = await _identityUserRepository.GetAsync(userId); |
||||
|
return user.GetProperty<string>("Title"); |
||||
|
```` |
||||
|
|
||||
|
This approach is very easy to use and available out of the box. No extra code needed. You can store more than one property at the same time by using different property names (like `Title` here). |
||||
|
|
||||
|
Extra properties are stored as a single `JSON` formatted string value in the database for the EF Core. For MongoDB, they are stored as separate fields of the document. |
||||
|
|
||||
|
See the [entities document](Entities.md) for more about the extra properties system. |
||||
|
|
||||
|
> It is possible to perform a **business logic** based on the value of an extra property. You can **override** a service method and get or set the value as shown above. Overriding services will be discussed below. |
||||
|
|
||||
|
#### Creating a New Entity Maps to the Same Database Table/Collection |
||||
|
|
||||
|
While using the extra properties approach is easy to use and suitable for some scenarios, it has some drawbacks described in the [entities document](Entities.md). |
||||
|
|
||||
|
Another approach can be creating your own entity mapped to the same database table (or collection for a MongoDB database). |
||||
|
|
||||
|
`AppUser` entity in the [application startup template](Startup-Templates/Application.md) already implements this approach. [EF Core Migrations document](Entity-Framework-Core-Migrations.md) describes how to implement it and manage database migrations in such a case. It is also possible for MongoDB, while this time you won't deal with the database migration problems. |
||||
|
|
||||
|
#### Creating a New Entity with Its Own Database Table/Collection |
||||
|
|
||||
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 38 KiB |
@ -1,4 +1,7 @@ |
|||||
{ |
{ |
||||
"ConnectionString": "Server=localhost;Database=VoloDocs;Trusted_Connection=True;MultipleActiveResultSets=true", |
"ConnectionString": "Server=localhost;Database=VoloDocs;Trusted_Connection=True;MultipleActiveResultSets=true", |
||||
"LogoUrl": "/assets/images/Logo.png" |
"LogoUrl": "/assets/images/Logo.png", |
||||
|
"ElasticSearch": { |
||||
|
"Url": "http://localhost:9200" |
||||
|
} |
||||
} |
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Docs.Documents |
||||
|
{ |
||||
|
public class DocumentSearchInput |
||||
|
{ |
||||
|
public string Context { get; set; } |
||||
|
|
||||
|
public Guid ProjectId { get; set; } |
||||
|
|
||||
|
public string LanguageCode { get; set; } |
||||
|
|
||||
|
public string Version { get; set; } |
||||
|
|
||||
|
public int? SkipCount { get; set; } |
||||
|
|
||||
|
public int? MaxResultCount { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Volo.Docs.Documents |
||||
|
{ |
||||
|
public class DocumentSearchOutput |
||||
|
{ |
||||
|
public DocumentSearchOutput() |
||||
|
{ |
||||
|
Highlight = new List<string>(); |
||||
|
} |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string FileName { get; set; } |
||||
|
|
||||
|
public string Version { get; set; } |
||||
|
|
||||
|
public string LanguageCode { get; set; } |
||||
|
|
||||
|
public List<string> Highlight { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Docs |
||||
|
{ |
||||
|
public static class DocsDomainErrorCodes |
||||
|
{ |
||||
|
public const string ElasticSearchNotEnabled = "Volo.Docs.Domain:010001"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Nest; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Docs.Documents.FullSearch.Elastic |
||||
|
{ |
||||
|
public class DefaultElasticClientProvider : IElasticClientProvider, ISingletonDependency |
||||
|
{ |
||||
|
protected readonly DocsElasticSearchOptions Options; |
||||
|
protected readonly IConfiguration Configuration; |
||||
|
|
||||
|
public DefaultElasticClientProvider(IOptions<DocsElasticSearchOptions> options, IConfiguration configuration) |
||||
|
{ |
||||
|
Configuration = configuration; |
||||
|
Options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual IElasticClient GetClient() |
||||
|
{ |
||||
|
var node = new Uri(Configuration["ElasticSearch:Url"]); |
||||
|
var settings = new ConnectionSettings(node).DefaultIndex(Options.IndexName); |
||||
|
return new ElasticClient(settings); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace Volo.Docs.Documents.FullSearch.Elastic |
||||
|
{ |
||||
|
public class DocsElasticSearchOptions |
||||
|
{ |
||||
|
public DocsElasticSearchOptions() |
||||
|
{ |
||||
|
Enable = false; |
||||
|
IndexName = "abp_documents"; |
||||
|
} |
||||
|
|
||||
|
public bool Enable { get; set; } |
||||
|
|
||||
|
public string IndexName { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Domain.Entities.Events; |
||||
|
using Volo.Abp.EventBus; |
||||
|
|
||||
|
namespace Volo.Docs.Documents.FullSearch.Elastic |
||||
|
{ |
||||
|
public class DocumentChangedEventHandler : ILocalEventHandler<EntityCreatedEventData<Document>>, |
||||
|
ILocalEventHandler<EntityUpdatedEventData<Document>>, |
||||
|
ILocalEventHandler<EntityDeletedEventData<Document>>, |
||||
|
ITransientDependency |
||||
|
{ |
||||
|
private readonly DocsElasticSearchOptions _options; |
||||
|
private readonly IDocumentFullSearch _documentFullSearch; |
||||
|
|
||||
|
public DocumentChangedEventHandler(IDocumentFullSearch documentFullSearch, IOptions<DocsElasticSearchOptions> options) |
||||
|
{ |
||||
|
_documentFullSearch = documentFullSearch; |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public async Task HandleEventAsync(EntityCreatedEventData<Document> eventData) |
||||
|
{ |
||||
|
await AddOrUpdate(eventData.Entity); |
||||
|
} |
||||
|
|
||||
|
public async Task HandleEventAsync(EntityUpdatedEventData<Document> eventData) |
||||
|
{ |
||||
|
await AddOrUpdate(eventData.Entity); |
||||
|
} |
||||
|
|
||||
|
private async Task AddOrUpdate(Document document) |
||||
|
{ |
||||
|
if (_options.Enable) |
||||
|
{ |
||||
|
await _documentFullSearch.AddOrUpdateAsync(document); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task HandleEventAsync(EntityDeletedEventData<Document> eventData) |
||||
|
{ |
||||
|
if (_options.Enable) |
||||
|
{ |
||||
|
await _documentFullSearch.DeleteAsync(eventData.Entity.Id); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,189 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Elasticsearch.Net; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Nest; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Domain.Services; |
||||
|
|
||||
|
namespace Volo.Docs.Documents.FullSearch.Elastic |
||||
|
{ |
||||
|
public class ElasticDocumentFullSearch : DomainService, IDocumentFullSearch |
||||
|
{ |
||||
|
private readonly IElasticClientProvider _clientProvider; |
||||
|
private readonly DocsElasticSearchOptions _options; |
||||
|
|
||||
|
public ElasticDocumentFullSearch(IElasticClientProvider clientProvider, IOptions<DocsElasticSearchOptions> options) |
||||
|
{ |
||||
|
_clientProvider = clientProvider; |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public async Task CreateIndexIfNeededAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
CheckEsEnabled(); |
||||
|
|
||||
|
var client = _clientProvider.GetClient(); |
||||
|
|
||||
|
var existsResponse = await client.Indices.ExistsAsync(_options.IndexName, ct: cancellationToken); |
||||
|
|
||||
|
HandleError(existsResponse); |
||||
|
|
||||
|
if (!existsResponse.Exists) |
||||
|
{ |
||||
|
HandleError(await client.Indices.CreateAsync(_options.IndexName, c => c |
||||
|
.Map<EsDocument>(m => |
||||
|
{ |
||||
|
return m |
||||
|
.Properties(p => p |
||||
|
.Keyword(x => x.Name(d => d.Id)) |
||||
|
.Keyword(x => x.Name(d => d.ProjectId)) |
||||
|
.Keyword(x => x.Name(d => d.Name)) |
||||
|
.Keyword(x => x.Name(d => d.FileName)) |
||||
|
.Keyword(x => x.Name(d => d.Version)) |
||||
|
.Keyword(x => x.Name(d => d.LanguageCode)) |
||||
|
.Text(x => x.Name(d => d.Content))); |
||||
|
}), cancellationToken)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task AddOrUpdateAsync(Document document, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
CheckEsEnabled(); |
||||
|
|
||||
|
var client = _clientProvider.GetClient(); |
||||
|
|
||||
|
var existsResponse = await client.DocumentExistsAsync<EsDocument>(DocumentPath<EsDocument>.Id(document.Id), |
||||
|
x => x.Index(_options.IndexName), cancellationToken); |
||||
|
|
||||
|
HandleError(existsResponse); |
||||
|
|
||||
|
var esDocument = new EsDocument |
||||
|
{ |
||||
|
Id = document.Id, |
||||
|
ProjectId = document.ProjectId, |
||||
|
Name = document.Name, |
||||
|
FileName = document.FileName, |
||||
|
Content = document.Content, |
||||
|
LanguageCode = document.LanguageCode, |
||||
|
Version = document.Version |
||||
|
}; |
||||
|
|
||||
|
if (!existsResponse.Exists) |
||||
|
{ |
||||
|
HandleError(await client.IndexAsync<EsDocument>(esDocument, |
||||
|
x => x.Id(document.Id).Index(_options.IndexName), cancellationToken)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
HandleError(await client.UpdateAsync<EsDocument>(DocumentPath<EsDocument>.Id(document.Id), |
||||
|
x => x.Doc(esDocument).Index(_options.IndexName), cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
CheckEsEnabled(); |
||||
|
|
||||
|
HandleError(await _clientProvider.GetClient() |
||||
|
.DeleteAsync(DocumentPath<Document>.Id(id), x => x.Index(_options.IndexName), cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<EsDocument>> SearchAsync(string context, Guid projectId, string languageCode, |
||||
|
string version, int? skipCount = null, int? maxResultCount = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
CheckEsEnabled(); |
||||
|
|
||||
|
var request = new SearchRequest |
||||
|
{ |
||||
|
Size = maxResultCount ?? 10, |
||||
|
From = skipCount ?? 0, |
||||
|
Query = new BoolQuery |
||||
|
{ |
||||
|
Must = new QueryContainer[] |
||||
|
{ |
||||
|
new MatchQuery |
||||
|
{ |
||||
|
Field = "content", |
||||
|
Query = context |
||||
|
} |
||||
|
}, |
||||
|
Filter = new QueryContainer[] |
||||
|
{ |
||||
|
new BoolQuery |
||||
|
{ |
||||
|
Must = new QueryContainer[] |
||||
|
{ |
||||
|
new TermQuery |
||||
|
{ |
||||
|
Field = "projectId", |
||||
|
Value = projectId |
||||
|
}, |
||||
|
new TermQuery |
||||
|
{ |
||||
|
Field = "version", |
||||
|
Value = version |
||||
|
}, |
||||
|
new TermQuery |
||||
|
{ |
||||
|
Field = "languageCode", |
||||
|
Value = languageCode |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
Highlight = new Highlight |
||||
|
{ |
||||
|
PreTags = new[] { "<highlight>" }, |
||||
|
PostTags = new[] { "</highlight>" }, |
||||
|
Fields = new Dictionary<Field, IHighlightField> |
||||
|
{ |
||||
|
{ |
||||
|
"content", new HighlightField() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
//var json = _clientProvider.GetClient().RequestResponseSerializer.SerializeToString(request);
|
||||
|
var response = await _clientProvider.GetClient().SearchAsync<EsDocument>(request, cancellationToken); |
||||
|
|
||||
|
HandleError(response); |
||||
|
|
||||
|
var docs = new List<EsDocument>(); |
||||
|
foreach (var hit in response.Hits) |
||||
|
{ |
||||
|
var doc = hit.Source; |
||||
|
if (hit.Highlight.ContainsKey("content")) |
||||
|
{ |
||||
|
doc.Highlight = new List<string>(); |
||||
|
doc.Highlight.AddRange(hit.Highlight["content"]); |
||||
|
} |
||||
|
docs.Add(doc); |
||||
|
} |
||||
|
|
||||
|
return docs; |
||||
|
} |
||||
|
|
||||
|
protected void HandleError(IElasticsearchResponse response) |
||||
|
{ |
||||
|
if (!response.ApiCall.Success) |
||||
|
{ |
||||
|
throw response.ApiCall.OriginalException; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void CheckEsEnabled() |
||||
|
{ |
||||
|
if (!_options.Enable) |
||||
|
{ |
||||
|
throw new BusinessException(DocsDomainErrorCodes.ElasticSearchNotEnabled); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Docs.Documents.FullSearch.Elastic |
||||
|
{ |
||||
|
public class EsDocument |
||||
|
{ |
||||
|
public EsDocument() |
||||
|
{ |
||||
|
Highlight = new List<string>(); |
||||
|
} |
||||
|
|
||||
|
public Guid Id { get; set; } |
||||
|
|
||||
|
public Guid ProjectId { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string FileName { get; set; } |
||||
|
|
||||
|
public string Version { get; set; } |
||||
|
|
||||
|
public string LanguageCode { get; set; } |
||||
|
|
||||
|
public string Content { get; set; } |
||||
|
|
||||
|
public List<string> Highlight { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Docs.Documents.FullSearch.Elastic |
||||
|
{ |
||||
|
public interface IDocumentFullSearch |
||||
|
{ |
||||
|
Task CreateIndexIfNeededAsync(CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task AddOrUpdateAsync(Document document, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<List<EsDocument>> SearchAsync(string context, Guid projectId, string languageCode, |
||||
|
string version, int? skipCount = null, int? maxResultCount = null, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Nest; |
||||
|
|
||||
|
namespace Volo.Docs.Documents.FullSearch.Elastic |
||||
|
{ |
||||
|
public interface IElasticClientProvider |
||||
|
{ |
||||
|
IElasticClient GetClient(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
@page |
||||
|
@using Microsoft.AspNetCore.Mvc.Localization |
||||
|
@using Volo.Abp.AspNetCore.Mvc.UI.Layout |
||||
|
@using Volo.Abp.AspNetCore.Mvc.UI.Theming |
||||
|
@using Volo.Docs.Localization |
||||
|
@using Volo.Docs.Pages.Documents |
||||
|
@inject IHtmlLocalizer<DocsResource> L |
||||
|
@inject IThemeManager ThemeManager |
||||
|
@inject IPageLayout PageLayout |
||||
|
@model SearchModel |
||||
|
@{ |
||||
|
Layout = ThemeManager.CurrentTheme.GetEmptyLayout(); |
||||
|
} |
||||
|
@section styles { |
||||
|
<style> |
||||
|
highlight { |
||||
|
font-weight: bold; |
||||
|
color: red; |
||||
|
font-style: italic; |
||||
|
} |
||||
|
</style> |
||||
|
} |
||||
|
|
||||
|
<div class="container"> |
||||
|
<form method="get" action="/search/@Model.LanguageCode/@Model.ProjectName/@Model.Version/" class="mt-4"> |
||||
|
<input type="text" asp-for="@Model.KeyWord" class="form-control" /> |
||||
|
<button type="submit" class="btn-block btn-primary btn-lg mt-3">Search</button> |
||||
|
</form> |
||||
|
|
||||
|
<div class="my-3 p-3 bg-white rounded"> |
||||
|
<h6 class="border-bottom pb-4 mb-0">Search results</h6> |
||||
|
|
||||
|
@foreach (var docs in Model.SearchOutputs) |
||||
|
{ |
||||
|
<div class="media text-muted pt-3"> |
||||
|
<div class="media-body pb-3 mb-0 small border-bottom"> |
||||
|
<div class="list-group"> |
||||
|
|
||||
|
@functions |
||||
|
{ |
||||
|
string RemoveFileExtensionFromPath(string path) |
||||
|
{ |
||||
|
if (path == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return path.EndsWith("." + @Model.Project.Format) |
||||
|
? path.Left(path.Length - Model.Project.Format.Length - 1) |
||||
|
: path; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
<a href="/@Model.LanguageCode/@Model.ProjectName/@Model.Version/@RemoveFileExtensionFromPath(docs.Name)"> |
||||
|
<h3>@RemoveFileExtensionFromPath(docs.Name)</h3></a> |
||||
|
|
||||
|
@foreach (var highlight in docs.Highlight) |
||||
|
{ |
||||
|
<p class="list-group-item list-group-item-action">@Html.Raw(highlight)</p> |
||||
|
} |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,73 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
using Volo.Docs.Documents; |
||||
|
using Volo.Docs.HtmlConverting; |
||||
|
using Volo.Docs.Models; |
||||
|
using Volo.Docs.Projects; |
||||
|
|
||||
|
namespace Volo.Docs.Pages.Documents |
||||
|
{ |
||||
|
public class SearchModel : PageModel |
||||
|
{ |
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string ProjectName { get; set; } |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string Version { get; set; } |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string LanguageCode { get; set; } |
||||
|
|
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string KeyWord { get; set; } |
||||
|
|
||||
|
public ProjectDto Project { get; set; } |
||||
|
|
||||
|
private readonly IProjectAppService _projectAppService; |
||||
|
private readonly IDocumentAppService _documentAppService; |
||||
|
|
||||
|
public SearchModel(IProjectAppService projectAppService, |
||||
|
IDocumentAppService documentAppService) |
||||
|
{ |
||||
|
_projectAppService = projectAppService; |
||||
|
_documentAppService = documentAppService; |
||||
|
} |
||||
|
|
||||
|
public List<DocumentSearchOutput> SearchOutputs { get; set; } = new List<DocumentSearchOutput>(); |
||||
|
|
||||
|
public async Task<IActionResult> OnGetAsync(string keyword) |
||||
|
{ |
||||
|
if (!await _documentAppService.FullSearchEnabledAsync()) |
||||
|
{ |
||||
|
return RedirectToPage("Index"); |
||||
|
} |
||||
|
|
||||
|
KeyWord = keyword; |
||||
|
|
||||
|
Project = await _projectAppService.GetAsync(ProjectName); |
||||
|
|
||||
|
var output = await _projectAppService.GetVersionsAsync(Project.ShortName); |
||||
|
|
||||
|
var versions = output.Items.ToList(); |
||||
|
|
||||
|
if (versions.Any() && string.Equals(Version, DocsAppConsts.Latest, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
Version = versions.First().Name; |
||||
|
} |
||||
|
|
||||
|
SearchOutputs = await _documentAppService.SearchAsync(new DocumentSearchInput |
||||
|
{ |
||||
|
ProjectId = Project.Id, |
||||
|
Context = KeyWord, |
||||
|
LanguageCode = LanguageCode, |
||||
|
Version = Version |
||||
|
}); |
||||
|
|
||||
|
return Page(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.TenantManagement |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class TenantEto |
||||
|
{ |
||||
|
public Guid Id { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,65 @@ |
|||||
|
using System; |
||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
|
||||
|
namespace AuthServer.Host.Migrations |
||||
|
{ |
||||
|
public partial class Added_Tenant_Management : Migration |
||||
|
{ |
||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.CreateTable( |
||||
|
name: "AbpTenants", |
||||
|
columns: table => new |
||||
|
{ |
||||
|
Id = table.Column<Guid>(nullable: false), |
||||
|
ExtraProperties = table.Column<string>(nullable: true), |
||||
|
ConcurrencyStamp = table.Column<string>(nullable: true), |
||||
|
CreationTime = table.Column<DateTime>(nullable: false), |
||||
|
CreatorId = table.Column<Guid>(nullable: true), |
||||
|
LastModificationTime = table.Column<DateTime>(nullable: true), |
||||
|
LastModifierId = table.Column<Guid>(nullable: true), |
||||
|
IsDeleted = table.Column<bool>(nullable: false, defaultValue: false), |
||||
|
DeleterId = table.Column<Guid>(nullable: true), |
||||
|
DeletionTime = table.Column<DateTime>(nullable: true), |
||||
|
Name = table.Column<string>(maxLength: 64, nullable: false) |
||||
|
}, |
||||
|
constraints: table => |
||||
|
{ |
||||
|
table.PrimaryKey("PK_AbpTenants", x => x.Id); |
||||
|
}); |
||||
|
|
||||
|
migrationBuilder.CreateTable( |
||||
|
name: "AbpTenantConnectionStrings", |
||||
|
columns: table => new |
||||
|
{ |
||||
|
TenantId = table.Column<Guid>(nullable: false), |
||||
|
Name = table.Column<string>(maxLength: 64, nullable: false), |
||||
|
Value = table.Column<string>(maxLength: 1024, nullable: false) |
||||
|
}, |
||||
|
constraints: table => |
||||
|
{ |
||||
|
table.PrimaryKey("PK_AbpTenantConnectionStrings", x => new { x.TenantId, x.Name }); |
||||
|
table.ForeignKey( |
||||
|
name: "FK_AbpTenantConnectionStrings_AbpTenants_TenantId", |
||||
|
column: x => x.TenantId, |
||||
|
principalTable: "AbpTenants", |
||||
|
principalColumn: "Id", |
||||
|
onDelete: ReferentialAction.Cascade); |
||||
|
}); |
||||
|
|
||||
|
migrationBuilder.CreateIndex( |
||||
|
name: "IX_AbpTenants_Name", |
||||
|
table: "AbpTenants", |
||||
|
column: "Name"); |
||||
|
} |
||||
|
|
||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.DropTable( |
||||
|
name: "AbpTenantConnectionStrings"); |
||||
|
|
||||
|
migrationBuilder.DropTable( |
||||
|
name: "AbpTenants"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,37 @@ |
|||||
|
using System; |
||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
|
||||
|
namespace AuthServer.Host.Migrations |
||||
|
{ |
||||
|
public partial class Added_Feature_Management : Migration |
||||
|
{ |
||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.CreateTable( |
||||
|
name: "AbpFeatureValues", |
||||
|
columns: table => new |
||||
|
{ |
||||
|
Id = table.Column<Guid>(nullable: false), |
||||
|
Name = table.Column<string>(maxLength: 128, nullable: false), |
||||
|
Value = table.Column<string>(maxLength: 128, nullable: false), |
||||
|
ProviderName = table.Column<string>(maxLength: 64, nullable: true), |
||||
|
ProviderKey = table.Column<string>(maxLength: 64, nullable: true) |
||||
|
}, |
||||
|
constraints: table => |
||||
|
{ |
||||
|
table.PrimaryKey("PK_AbpFeatureValues", x => x.Id); |
||||
|
}); |
||||
|
|
||||
|
migrationBuilder.CreateIndex( |
||||
|
name: "IX_AbpFeatureValues_Name_ProviderName_ProviderKey", |
||||
|
table: "AbpFeatureValues", |
||||
|
columns: new[] { "Name", "ProviderName", "ProviderKey" }); |
||||
|
} |
||||
|
|
||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.DropTable( |
||||
|
name: "AbpFeatureValues"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.UI.Navigation; |
||||
|
using Volo.Abp.Users; |
||||
|
|
||||
|
namespace BackendAdminApp.Host |
||||
|
{ |
||||
|
public class BackendAdminAppMenuContributor : IMenuContributor |
||||
|
{ |
||||
|
private readonly IConfiguration _configuration; |
||||
|
|
||||
|
public BackendAdminAppMenuContributor(IConfiguration configuration) |
||||
|
{ |
||||
|
_configuration = configuration; |
||||
|
} |
||||
|
|
||||
|
public async Task ConfigureMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
if (context.Menu.Name == StandardMenus.User) |
||||
|
{ |
||||
|
await ConfigureUserMenuAsync(context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Task ConfigureUserMenuAsync(MenuConfigurationContext context) |
||||
|
{ |
||||
|
var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>(); |
||||
|
|
||||
|
var identityServerUrl = _configuration["AuthServer:Authority"] ?? ""; |
||||
|
|
||||
|
if (currentUser.IsAuthenticated) |
||||
|
{ |
||||
|
//TODO: Localize menu items
|
||||
|
context.Menu.AddItem(new ApplicationMenuItem("Account.Manage", "Manage Your Profile", $"{identityServerUrl.EnsureEndsWith('/')}Account/Manage", icon: "fa fa-cog", order: 1000, null, "_blank")); |
||||
|
context.Menu.AddItem(new ApplicationMenuItem("Account.Logout", "Logout", url: "/Account/Logout", icon: "fa fa-power-off", order: int.MaxValue - 1000)); |
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.AspNetCore.Mvc.Authentication; |
||||
|
|
||||
|
namespace BackendAdminApp.Host.Controllers |
||||
|
{ |
||||
|
public class AccountController : ChallengeAccountController |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue