diff --git a/Oqtane.Client/Resources/UI/ModuleInstance.resx b/Oqtane.Client/Resources/UI/ModuleInstance.resx
index fc0994f2..4c4a4c63 100644
--- a/Oqtane.Client/Resources/UI/ModuleInstance.resx
+++ b/Oqtane.Client/Resources/UI/ModuleInstance.resx
@@ -124,6 +124,9 @@
Module Type Is Invalid For {0}
- An Unexpected Error Has Occurred
+ An Unexpected Error Has Occurred
+
+
+ Missing service(s): {0}. Please make sure they have been registered correctly.
\ No newline at end of file
diff --git a/Oqtane.Client/UI/RenderModeBoundary.razor b/Oqtane.Client/UI/RenderModeBoundary.razor
index abf96087..b6b5e0af 100644
--- a/Oqtane.Client/UI/RenderModeBoundary.razor
+++ b/Oqtane.Client/UI/RenderModeBoundary.razor
@@ -1,7 +1,11 @@
@namespace Oqtane.UI
+@using System.Reflection
+@using Module = Oqtane.Models.Module
+@inject IServiceProvider ServiceProvider
@inject SiteState ComponentSiteState
@inject IStringLocalizer Localizer
@inject ILogService LoggingService
+@inject NavigationManager NavigationManager
@inherits ErrorBoundary
@@ -67,37 +71,50 @@
{
if (ShouldRender())
{
- if (!string.IsNullOrEmpty(ModuleState.ModuleType))
- {
- ModuleType = Type.GetType(ModuleState.ModuleType);
- if (ModuleType != null)
- {
- // repopulate the SiteState service based on the values passed in the SiteState parameter (this is how state is marshalled across the render mode boundary)
- ComponentSiteState.Hydrate(SiteState);
-
- DynamicComponent = builder =>
- {
- builder.OpenComponent(0, ModuleType);
- builder.AddAttribute(1, "RenderModeBoundary", this);
- builder.CloseComponent();
- };
- }
- else
- {
- // module does not exist with typename specified
- _messageContent = string.Format(Localizer["Error.Module.InvalidName"], Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0));
- _messageType = MessageType.Error;
- _messagePosition = "top";
- _messageStyle = MessageStyle.Alert;
- }
- }
- else
+ if (string.IsNullOrEmpty(ModuleState.ModuleType))
{
_messageContent = string.Format(Localizer["Error.Module.InvalidType"], ModuleState.ModuleDefinitionName);
_messageType = MessageType.Error;
_messagePosition = "top";
_messageStyle = MessageStyle.Alert;
+
+ return;
}
+
+ ModuleType = Type.GetType(ModuleState.ModuleType);
+ var moduleName = Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0);
+ if (ModuleType == null)
+ {
+ // module does not exist with typename specified
+ _messageContent = string.Format(Localizer["Error.Module.InvalidName"], moduleName);
+ _messageType = MessageType.Error;
+ _messagePosition = "top";
+ _messageStyle = MessageStyle.Alert;
+
+ return;
+ }
+
+ //only validate the services injection in development environment
+ if (NavigationManager.BaseUri.Contains("localhost:") && !ValidateModuleTypeInjectedServices(ModuleType, out IList missingServices))
+ {
+ // module type is not valid for instantiation
+ _messageContent = string.Format(Localizer["Error.Module.InvalidInjectedServices"], string.Join(",", missingServices));
+ _messageType = MessageType.Error;
+ _messagePosition = "top";
+ _messageStyle = MessageStyle.Alert;
+
+ return;
+ }
+
+ // repopulate the SiteState service based on the values passed in the SiteState parameter (this is how state is marshalled across the render mode boundary)
+ ComponentSiteState.Hydrate(SiteState);
+
+ DynamicComponent = builder =>
+ {
+ builder.OpenComponent(0, ModuleType);
+ builder.AddAttribute(1, "RenderModeBoundary", this);
+ builder.CloseComponent();
+ };
}
}
@@ -165,4 +182,26 @@
_error = "";
base.Recover();
}
+
+ private bool ValidateModuleTypeInjectedServices(Type moduleType, out IList missingServices)
+ {
+ missingServices = new List();
+
+ var properties = Utilities.GetPropertiesIncludingInherited(moduleType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ foreach(var property in properties)
+ {
+ var injectAttribute = property.GetCustomAttribute(typeof(InjectAttribute));
+ if (injectAttribute != null)
+ {
+ var serviceType = property.PropertyType;
+ var service = ServiceProvider.GetService(serviceType);
+ if (serviceType != null && service == null)
+ {
+ missingServices.Add(Utilities.GetTypeNameLastSegment(serviceType.FullName, 0));
+ }
+ }
+ }
+
+ return !missingServices.Any();
+ }
}
\ No newline at end of file
diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs
index 44cccffc..45888f0c 100644
--- a/Oqtane.Shared/Shared/Utilities.cs
+++ b/Oqtane.Shared/Shared/Utilities.cs
@@ -4,6 +4,8 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Reflection;
+using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
@@ -750,6 +752,75 @@ namespace Oqtane.Shared
}
}
+ public static IEnumerable GetPropertiesIncludingInherited(Type type, BindingFlags bindingFlags)
+ {
+ var dictionary = new Dictionary(StringComparer.Ordinal);
+
+ var currentType = type;
+ while (currentType != null)
+ {
+ var properties = currentType.GetProperties(bindingFlags | BindingFlags.DeclaredOnly);
+ foreach (var property in properties)
+ {
+ if (!dictionary.TryGetValue(property.Name, out var others))
+ {
+ dictionary.Add(property.Name, property);
+ }
+ else if (!IsInheritedProperty(property, others))
+ {
+ List many;
+ if (others is PropertyInfo single)
+ {
+ many = new List { single };
+ dictionary[property.Name] = many;
+ }
+ else
+ {
+ many = (List)others;
+ }
+ many.Add(property);
+ }
+ }
+
+ currentType = currentType.BaseType;
+ }
+
+ foreach (var item in dictionary)
+ {
+ if (item.Value is PropertyInfo property)
+ {
+ yield return property;
+ continue;
+ }
+
+ var list = (List)item.Value;
+ var count = list.Count;
+ for (var i = 0; i < count; i++)
+ {
+ yield return list[i];
+ }
+ }
+ }
+
+ private static bool IsInheritedProperty(PropertyInfo property, object others)
+ {
+ if (others is PropertyInfo single)
+ {
+ return single.GetMethod?.GetBaseDefinition() == property.GetMethod?.GetBaseDefinition();
+ }
+
+ var many = (List)others;
+ foreach (var other in CollectionsMarshal.AsSpan(many))
+ {
+ if (other.GetMethod?.GetBaseDefinition() == property.GetMethod?.GetBaseDefinition())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
[Obsolete("ContentUrl(Alias alias, int fileId) is deprecated. Use FileUrl(Alias alias, int fileId) instead.", false)]
public static string ContentUrl(Alias alias, int fileId)
{