implementation of [OqtaneIgnore] class attribute in controls

This commit is contained in:
Pavel Vesely 2020-05-07 18:51:55 +02:00
parent 6586883979
commit 47f17a589f
31 changed files with 227 additions and 178 deletions

View File

@ -1,6 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@attribute [OqtaneIgnore]
@if (_visible)
{
<div class="app-admin-modal">

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IUserService UserService
@if (_authorized)

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@if (_text != string.Empty)
{

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IFolderService FolderService
@inject IFileService FileService
@inject IJSRuntime JsRuntime

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@if (!string.IsNullOrEmpty(HelpText))
{
@ -41,4 +42,4 @@ else
_openLabel += ">";
}
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@if (!string.IsNullOrEmpty(_message))
{

View File

@ -1,6 +1,8 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@typeparam TableItem
@attribute [OqtaneIgnore]
@typeparam TableItem
<p>
@if(Format == "Table")
@ -209,4 +211,4 @@
UpdateList(_page);
}
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IRoleService RoleService
@inject IUserService UserService

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IJSRuntime JsRuntime
@if (_filemanagervisible)

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
<div class="d-flex">
<div>

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@if (Name == Parent.ActiveTab)
{

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
<CascadingValue Value="this">
<div class="container-fluid">

View File

@ -82,7 +82,6 @@ namespace Oqtane.Services
var result = await response.Content.ReadFromJsonAsync<TResult>();
return result;
}
return default;
}
@ -121,6 +120,8 @@ namespace Oqtane.Services
if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound)
{
//TODO: Log errors here
Console.WriteLine($"Request: {response.RequestMessage.RequestUri}");
Console.WriteLine($"Response status: {response.StatusCode} {response.ReasonPhrase}");
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inherits ThemeControlBase
@attribute [OqtaneIgnore]
@if (BreadCrumbPages.Any())
{

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Themes.Controls
@using Oqtane.Enums
@inherits ThemeControlBase
@inherits ThemeControlBase
@attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IModuleDefinitionService ModuleDefinitionService

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.Controls
@inherits LoginBase
@inherits LoginBase
@attribute [OqtaneIgnore]
<AuthorizeView>
<Authorizing>

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inherits ThemeControlBase
@attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager
@if (PageState.Site.LogoFileId != null)

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.Controls
@inherits MenuBase
@attribute [OqtaneIgnore]
@if (MenuPages.Any())
{
<div class="app-menu">

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.Controls
@inherits MenuBase
@inherits MenuBase
@attribute [OqtaneIgnore]
@if (MenuPages.Any())
{
<div class="app-menu">

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.Controls
@inherits ModuleActionsBase
@inherits ModuleActionsBase
@attribute [OqtaneIgnore]
@if (PageState.EditMode && !PageState.Page.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions))
{

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.Controls
@inherits ContainerBase
@inherits ContainerBase
@attribute [OqtaneIgnore]
@((MarkupString)title)

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inherits ThemeControlBase
@attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager
<AuthorizeView>

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Oqtane.Shared;
// ReSharper disable once CheckNamespace
namespace System.Reflection
@ -31,5 +33,20 @@ namespace System.Reflection
return assembly.GetTypes()
.Where(t => t.GetInterfaces().Contains(interfaceType));
}
public static bool IsOqtaneAssembly(this Assembly assembly)
{
return assembly.FullName != null && (assembly.FullName.Contains("oqtane.", StringComparison.OrdinalIgnoreCase));
}
public static bool IsOqtaneAssembly(this FileInfo fileInfo)
{
return (fileInfo.Name.Contains("oqtane.", StringComparison.OrdinalIgnoreCase));
}
public static IEnumerable<Assembly> GetOqtaneAssemblies(this AppDomain appDomain)
{
return appDomain.GetAssemblies().Where(a => a.IsOqtaneAssembly());
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
@ -16,10 +17,11 @@ namespace Microsoft.Extensions.DependencyInjection
}
// load MVC application parts from module assemblies
foreach (var assembly in OqtaneServiceCollectionExtensions.GetOqtaneModuleAssemblies())
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// check if assembly contains MVC Controllers
if (assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Controller))).ToArray().Length > 0)
if (assembly.GetTypes().Any(t => t.IsSubclassOf(typeof(Controller))))
{
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
foreach (var part in partFactory.GetApplicationParts(assembly))

View File

@ -5,67 +5,35 @@ using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.Hosting;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Modules;
using Oqtane.Shared;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class OqtaneServiceCollectionExtensions
{
private static readonly IList<Assembly> OqtaneModuleAssemblies = new List<Assembly>();
private static Assembly[] Assemblies => AppDomain.CurrentDomain.GetAssemblies();
internal static IEnumerable<Assembly> GetOqtaneModuleAssemblies() => OqtaneModuleAssemblies;
public static IServiceCollection AddOqtaneModules(this IServiceCollection services)
public static IServiceCollection AddOqtaneParts(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
LoadAssemblies("Module");
LoadAssemblies();
services.AddOqtaneServices();
return services;
}
public static IServiceCollection AddOqtaneThemes(this IServiceCollection services)
private static IServiceCollection AddOqtaneServices(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
LoadAssemblies("Theme");
return services;
}
public static IServiceCollection AddOqtaneSiteTemplates(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
LoadAssemblies("SiteTemplate");
return services;
}
public static IServiceCollection AddOqtaneServices(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
// dynamically register module services, contexts, and repository classes
var assemblies = Assemblies.Where(item => item.FullName != null && (item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module."))).ToArray();
var hostedServiceType = typeof(IHostedService);
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// dynamically register module services, contexts, and repository classes
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{
@ -75,22 +43,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
}
return services;
}
public static IServiceCollection AddOqtaneHostedServices(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
// dynamically register hosted services
var hostedServiceType = typeof(IHostedService);
foreach (var assembly in Assemblies)
{
// dynamically register hosted services
var serviceTypes = assembly.GetTypes(hostedServiceType);
foreach (var serviceType in serviceTypes)
{
@ -104,34 +58,50 @@ namespace Microsoft.Extensions.DependencyInjection
return services;
}
private static void LoadAssemblies(string pattern)
private static void LoadAssemblies()
{
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (assemblyPath == null) return;
var assembliesFolder = new DirectoryInfo(assemblyPath);
// iterate through Oqtane assemblies in /bin ( filter is narrow to optimize loading process )
foreach (var dll in assembliesFolder.EnumerateFiles($"*.{pattern}.*.dll"))
foreach (var dll in assembliesFolder.EnumerateFiles($"*.dll", SearchOption.TopDirectoryOnly).Where(f => f.IsOqtaneAssembly()))
{
// check if assembly is already loaded
var assembly = Assemblies.FirstOrDefault(a =>!a.IsDynamic && a.Location == dll.FullName);
if (assembly == null)
AssemblyName assemblyName;
try
{
// load assembly ( and symbols ) from stream to prevent locking files ( as long as dependencies are in /bin they will load as well )
string pdb = dll.FullName.Replace(".dll", ".pdb");
if (File.Exists(pdb))
assemblyName = AssemblyName.GetAssemblyName(dll.FullName);
}
catch
{
Console.WriteLine($"Not Assembly : {dll.Name}");
continue;
}
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
if (!assemblies.Any(a => AssemblyName.ReferenceMatchesDefinition(assemblyName, a.GetName())))
{
try
{
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)), new MemoryStream(File.ReadAllBytes(pdb)));
var pdb = Path.ChangeExtension(dll.FullName, ".pdb");
Assembly assembly = null;
// load assembly ( and symbols ) from stream to prevent locking files ( as long as dependencies are in /bin they will load as well )
if (File.Exists(pdb))
{
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)), new MemoryStream(File.ReadAllBytes(pdb)));
}
else
{
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)));
}
Console.WriteLine($"Loaded : {assemblyName}");
}
else
catch (Exception e)
{
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)));
}
if (pattern == "Module")
{
// build a list of module assemblies
OqtaneModuleAssemblies.Add(assembly);
Console.WriteLine($"Failed : {assemblyName}\n{e}");
}
}
}

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Linq;
namespace Oqtane.Extensions
{
public static class StringExtensions
{
public static bool StartWithAnyOf(this string s, IEnumerable<string> list)
{
if (s == null)
{
return false;
}
return list.Any(f => s.StartsWith(f));
}
}
}

View File

@ -75,6 +75,7 @@ namespace Oqtane.Repository
// get module assemblies
_moduleDefinitions = LoadModuleDefinitionsFromAssemblies();
}
List<ModuleDefinition> moduleDefinitions = _moduleDefinitions;
List<Permission> permissions = new List<Permission>();
@ -94,7 +95,7 @@ namespace Oqtane.Repository
if (moduledef == null)
{
// new module definition
moduledef = new ModuleDefinition { ModuleDefinitionName = moduledefinition.ModuleDefinitionName };
moduledef = new ModuleDefinition {ModuleDefinitionName = moduledefinition.ModuleDefinitionName};
_db.ModuleDefinition.Add(moduledef);
_db.SaveChanges();
if (siteId != -1)
@ -109,18 +110,22 @@ namespace Oqtane.Repository
{
moduledefinition.Name = moduledef.Name;
}
if (!string.IsNullOrEmpty(moduledef.Description))
{
moduledefinition.Description = moduledef.Description;
}
if (!string.IsNullOrEmpty(moduledef.Categories))
{
moduledefinition.Categories = moduledef.Categories;
}
if (!string.IsNullOrEmpty(moduledef.Version))
{
moduledefinition.Version = moduledef.Version;
}
if (siteId != -1)
{
if (permissions.Count == 0)
@ -139,9 +144,11 @@ namespace Oqtane.Repository
}
}
}
// remove module definition from list as it is already synced
moduledefs.Remove(moduledef);
}
moduledefinition.ModuleDefinitionId = moduledef.ModuleDefinitionId;
moduledefinition.SiteId = siteId;
moduledefinition.CreatedBy = moduledef.CreatedBy;
@ -157,6 +164,7 @@ namespace Oqtane.Repository
{
_permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId);
}
_db.ModuleDefinition.Remove(moduledefinition); // delete
_db.SaveChanges();
}
@ -168,12 +176,12 @@ namespace Oqtane.Repository
{
List<ModuleDefinition> moduleDefinitions = new List<ModuleDefinition>();
// iterate through Oqtane module assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module.")).ToArray();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly);
}
return moduleDefinitions;
}
@ -183,84 +191,92 @@ namespace Oqtane.Repository
Type[] modulecontroltypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModuleControl))).ToArray();
foreach (Type modulecontroltype in modulecontroltypes)
{
if (modulecontroltype.Name != "ModuleBase" && !modulecontroltype.Namespace.EndsWith(".Controls"))
{
string[] typename = modulecontroltype.AssemblyQualifiedName?.Split(',').Select(item => item.Trim()).ToArray();
string[] segments = typename[0].Split('.');
Array.Resize(ref segments, segments.Length - 1);
string moduleType = string.Join(".", segments);
string qualifiedModuleType = moduleType + ", " + typename[1];
// Check if type should be ignored
if (modulecontroltype.Name == "ModuleBase"
|| modulecontroltype.IsGenericType
|| Attribute.IsDefined(modulecontroltype, typeof(OqtaneIgnoreAttribute))
) continue;
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1)
string[] typename = modulecontroltype.AssemblyQualifiedName?.Split(',').Select(item => item.Trim()).ToArray();
string[] segments = typename[0].Split('.');
Array.Resize(ref segments, segments.Length - 1);
string moduleType = string.Join(".", segments);
string qualifiedModuleType = moduleType + ", " + typename[1];
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1)
{
// determine if this module implements IModule
Type moduletype = assembly
.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(moduleType))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
if (moduletype != null)
{
// determine if this module implements IModule
Type moduletype = assembly
.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(moduleType))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
if (moduletype != null)
{
// get property values from IModule
var moduleobject = Activator.CreateInstance(moduletype);
moduledefinition = (ModuleDefinition)moduletype.GetProperty("ModuleDefinition").GetValue(moduleobject);
}
else
{
// set default property values
moduledefinition = new ModuleDefinition
{
Name = moduleType.Substring(moduleType.LastIndexOf(".") + 1),
Description = "Manage " + moduleType.Substring(moduleType.LastIndexOf(".") + 1),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
};
}
// set internal properties
moduledefinition.ModuleDefinitionName = qualifiedModuleType;
moduledefinition.Version = ""; // will be populated from database
moduledefinition.ControlTypeTemplate = moduleType + "." + Constants.ActionToken + ", " + typename[1];
moduledefinition.AssemblyName = assembly.GetName().Name;
if (string.IsNullOrEmpty(moduledefinition.Categories))
{
moduledefinition.Categories = "Common";
}
if (moduledefinition.Categories == "Admin")
{
moduledefinition.Permissions = new List<Permission>
{
new Permission(PermissionNames.Utilize, Constants.AdminRole, true)
}.EncodePermissions();
}
else
{
moduledefinition.Permissions = new List<Permission>
{
new Permission(PermissionNames.Utilize, Constants.AdminRole, true),
new Permission(PermissionNames.Utilize, Constants.RegisteredRole, true)
}.EncodePermissions();
}
moduledefinitions.Add(moduledefinition);
index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
// get property values from IModule
var moduleobject = Activator.CreateInstance(moduletype);
moduledefinition = (ModuleDefinition) moduletype.GetProperty("ModuleDefinition").GetValue(moduleobject);
}
moduledefinition = moduledefinitions[index];
// actions
var modulecontrolobject = Activator.CreateInstance(modulecontroltype);
string actions = (string)modulecontroltype.GetProperty("Actions")?.GetValue(modulecontrolobject);
if (!string.IsNullOrEmpty(actions))
else
{
foreach (string action in actions.Split(','))
// set default property values
moduledefinition = new ModuleDefinition
{
moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + typename[1] + ";");
}
Name = moduleType.Substring(moduleType.LastIndexOf(".") + 1),
Description = "Manage " + moduleType.Substring(moduleType.LastIndexOf(".") + 1),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
};
}
moduledefinitions[index] = moduledefinition;
// set internal properties
moduledefinition.ModuleDefinitionName = qualifiedModuleType;
moduledefinition.Version = ""; // will be populated from database
moduledefinition.ControlTypeTemplate = moduleType + "." + Constants.ActionToken + ", " + typename[1];
moduledefinition.AssemblyName = assembly.GetName().Name;
if (string.IsNullOrEmpty(moduledefinition.Categories))
{
moduledefinition.Categories = "Common";
}
if (moduledefinition.Categories == "Admin")
{
moduledefinition.Permissions = new List<Permission>
{
new Permission(PermissionNames.Utilize, Constants.AdminRole, true)
}.EncodePermissions();
}
else
{
moduledefinition.Permissions = new List<Permission>
{
new Permission(PermissionNames.Utilize, Constants.AdminRole, true),
new Permission(PermissionNames.Utilize, Constants.RegisteredRole, true)
}.EncodePermissions();
}
Console.WriteLine($"Registering module: {moduledefinition.ModuleDefinitionName}");
moduledefinitions.Add(moduledefinition);
index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
}
moduledefinition = moduledefinitions[index];
// actions
var modulecontrolobject = Activator.CreateInstance(modulecontroltype);
string actions = (string) modulecontroltype.GetProperty("Actions")?.GetValue(modulecontrolobject);
if (!string.IsNullOrEmpty(actions))
{
foreach (string action in actions.Split(','))
{
moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + typename[1] + ";");
}
}
moduledefinitions[index] = moduledefinition;
}
return moduledefinitions;
}
}
}

View File

@ -22,8 +22,8 @@ namespace Oqtane.Repository
List<SiteTemplate> siteTemplates = new List<SiteTemplate>();
// iterate through Oqtane site template assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".SiteTemplate.")).ToArray();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
siteTemplates = LoadSiteTemplatesFromAssembly(siteTemplates, assembly);

View File

@ -31,8 +31,7 @@ namespace Oqtane.Repository
List<Theme> themes = new List<Theme>();
// iterate through Oqtane theme assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Theme.")).ToArray();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
themes = LoadThemesFromAssembly(themes, assembly);

View File

@ -41,7 +41,7 @@ namespace Oqtane
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddNewtonsoftJson();
services.AddServerSideBlazor();
// setup HttpClient for server side in a client side compatible fashion ( with auth cookie )
@ -188,16 +188,13 @@ namespace Oqtane
services.AddTransient<IUpgradeManager, UpgradeManager>();
// load the external assemblies into the app domain
services.AddOqtaneModules();
services.AddOqtaneThemes();
services.AddOqtaneSiteTemplates();
services.AddOqtaneParts();
services.AddMvc()
.AddOqtaneApplicationParts() // register any Controllers from custom modules
.AddNewtonsoftJson();
services.AddOqtaneServices();
services.AddOqtaneHostedServices();
services.AddSwaggerGen(c =>
{

View File

@ -0,0 +1,9 @@
using System;
namespace Oqtane.Shared
{
[AttributeUsage(AttributeTargets.Class)]
public class OqtaneIgnoreAttribute : Attribute
{
}
}