Currently running a site with 100+ tenants and have to manually enable all the modules. Is there a way to enable all modules in orchard across all my tenants without having to go to each site and click enable or use manually use the command prompt. I am also having the same issue deploying contents to multiple site. my set up is unique to where i have different database for each tenants.
Below is an example of how I enable/disable features from my modules' migration classes. What you could do is create a command line implementation (inherit from DefaultOrchardCommandHandler) that will fan out your enable/disable module commands to all the tenants.
I'm not sure how the tenant system works in terms fo command line, i think you may have to launch a separate command line bin\orchard.exe session for each tenant. If that's the case you can do the fanning in a script that calls into bin\orchard.exe. If you can do tenant specific commands for different tenants from a single session of bin\orchard.exe, you can do the fanning inside your Commands class.
[UsedImplicitly]
public class Migrations : DataMigrationImpl {
public int UpdateFrom2() {
var features = _moduleService.GetAvailableFeatures().ToDictionary(m=>m.Descriptor.Id, m=>m);
DisableFeature(features, "TinyMce");
EnableFeature(features, "TinyMceDeluxe");
EnableFeature(features, "Contrib.Cache");
EnableFeature(features, "WebAdvanced.Sitemap");
return 3;
}
private void DisableFeature(Dictionary<string, Orchard.Modules.Models.ModuleFeature> features, string featureId) {
if (features.ContainsKey(featureId) && features[featureId].IsEnabled) {
_moduleService.DisableFeatures(new string[] { featureId });
}
}
private void EnableFeature(Dictionary<string, Orchard.Modules.Models.ModuleFeature> features, string featureId) {
if (features.ContainsKey(featureId) && !features[featureId].IsEnabled) {
_moduleService.EnableFeatures(new string[] { featureId });
}
}
}
Related
I am currently experimenting with ASP.NET Core MVC by creating a simple blog with static views. I've created a custom class that implements the IViewLocationExpander in order to enumerate a directory structure to retrieve razor views
public class FolderEnumerationViewExpander : IViewLocationExpander
{
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context,
IEnumerable<string> viewLocations)
{
var locations = viewLocations.ToList();
foreach (var directory in Directory.EnumerateDirectories("Views/Blog", "*", SearchOption.AllDirectories))
{
locations.Add($"/{directory.Replace("\\","/")}" + "/{0}.cshtml");
}
return locations.AsEnumerable();
}
}
I've configured this class to be used within Startup.cs's ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IPostsMetaDataRepositry>(new JsonPostsMetaDataRepository(ConfigurationPath.Combine("posts.json")));
services.Configure<RazorViewEngineOptions>(o => { o.ViewLocationExpanders.Add(new FolderEnumerationViewExpander()); });
services.AddMvc();
}
This seems to work perfectly when I run my application locally using IISExpress however, when I deploy the application to an Azure AppService, only a handful of the directories within ~/Views/Blog are being enumerated when trying to locate views.
I'm fairly certain the issue is one of configuration but I am having a difficult time tracking down what it could be. Any ideas as to why this may be occurring?
I have the full source of this project on GitHub for reference: https://github.com/pstricker/Develothink
I have an ASP.NET Core 2 application hosted on Azure, and I added a new Application Settings MyNewSetting for my App in the Azure Portal.
How do I access that setting from a controller?
My code bellow:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<AppSecrets>(Configuration);
services.AddSingleton<ITableRepositories, TableClientOperationsService>();
//...
My Controller:
public class RecordController : Controller
{
const int MyNewSetting = 7; // this one to replace with Azure Setting one
private readonly ITableRepositories repository;
public RecordController(ITableRepositories rep) {
repository = rep;
}
Here, I need probably to add FromServices injection, but I am not sure if it will work...
EDIT:
Folowing the #dee_zg answer, the following code could probably do the job:
public class RecordController : Controller
{
int MyNewSetting = 7;
private readonly ITableRepositories repository;
public RecordController(ITableRepositories rep) {
repository = rep;
int myInt;
if (int.TryParse(System.Environment.GetEnvironmentVariable("MY_NEW_SETTING"),
out myInt)) {
MyNewSetting = myInt;
};
}
You can choose to either get them from AppSettings["your-key"] collection or as environment variables: Environment.GetEnvironmentVariable("your-key").
From there you can map them to your custom IOptions and inject wherever you need them.
There's quite a few things you can do.
Use Options and configuration objects
The options pattern uses custom options classes to represent a group of related settings. We recommended that you create decoupled classes for each feature within your app.
Use an IOptionsSnapshot.
IOptionsSnapshot supports reloading configuration data when the configuration file has changed. It has minimal overhead. Using IOptionsSnapshot with reloadOnChange: true, the options are bound to Configuration and reloaded when changed.
... (see documentation)
In short, have a look at Configuration in ASP.NET Core, determine the scenario that best fits your needs and have at it!
Hope this helps.
We are moving from an on premise-like application to a multi tenant cloud application.
for my web application we made a very simple interface based on IPlugin, to create a plugin architecture. (customers can have/install different plugins)
public interface IWebPlugin : IPlugin
{
string ContentBaseUrl { set; get; }
}
We have some plugins that would normally be loaded in on startup. Now i'm migrating the code to load at the beginning of a request (the Register function is called on request start), and scope everything inside this request.
It's not ideal but it would bring the least impact on the plugin system for now.
I could scope the Container by making an AppHost child container which would stick to the request:
Container IHasContainer.Container
{
get
{
if (HasStarted)
return ChildContainer;
return base.Container;
}
}
public Container ChildContainer
{
get { return HttpContext.Current.Items.GetOrAdd<Container>("ChildContainer", c => Container.CreateChildContainer()); }
}
problem case
Now im trying to make plugins work that actually add API services.
appHost.Routes.Add<GetTranslations>("/Localizations/translations", ApplyTo.Get);
But this service is unreachable (and not visible in metadata). How do i make it reachable?
I see you execute the following in ServiceController AfterInit. Re-executing this still wouldnt make it work.
//Copied from servicestack repo
public void AfterInit()
{
//Register any routes configured on Metadata.Routes
foreach (var restPath in appHost.RestPaths)
{
RegisterRestPath(restPath);
//Auto add Route Attributes so they're available in T.ToUrl() extension methods
restPath.RequestType
.AddAttributes(new RouteAttribute(restPath.Path, restPath.AllowedVerbs)
{
Priority = restPath.Priority,
Summary = restPath.Summary,
Notes = restPath.Notes,
});
}
//Sync the RestPaths collections
appHost.RestPaths.Clear();
appHost.RestPaths.AddRange(RestPathMap.Values.SelectMany(x => x));
appHost.Metadata.AfterInit();
}
solution directions
Is there a way i could override the route finding? like extending RestHandler.FindMatchingRestPath(httpMethod, pathInfo, out contentType);
Or could i restart the path compilation/caching? (would be enough for now that the service would be reachable tenant wide )
All configuration in ServiceStack should be contained within AppHost.Configure() and remain immutable thereafter. It's not ThreadSafe to modify ServiceStack's Static Configuration at runtime like trying to modify registered routes or Service Metadata which needs to be registered once at StartUp in AppHost.Configure().
It looks as though you'll need to re-architect your solution so all Routes are registered on Startup. If it helps Plugins can implement IPreInitPlugin and IPostInitPlugin interfaces to execute custom logic before and after Plugins are registered. They can also register a appHost.AfterInitCallbacks to register custom logic after ServiceStack's AppHost has been initialized.
Not sure if it's applicable but at runtime you can "hi-jack Requests" in ServiceStack by registering a RawHttpHandler or a PreRequestFilter, e.g:
appHost.RawHttpHandlers.Add(httpReq =>
MyShouldHandleThisRoute(httpReq.PathInfo)
? new CustomActionHandler((req, res) => {
//Handle Route
});
: null);
Simple answer seems to be, no. The framework wasn't build to be a run-time plugable system.
You will have to make this architecture yourself on top of ServiceStack.
Routing solution
To make it route to these run-time loaded services/routes it is needed to make your own implementation.
The ServiceStack.HttpHandlerFactory checks if a route exist (one that is registered on init). so here is where you will have to start extending. The method GetHandlerForPathInfo checks if it can find the (service)route and otherwise return a NotFoundHandler or StaticFileHandler.
My solution consists of the following code:
string contentType;
var restPath = RestHandler.FindMatchingRestPath(httpMethod, pathInfo, out contentType);
//Added part
if (restPath == null)
restPath = AppHost.Instance.FindPluginServiceForRoute(httpMethod, pathInfo);
//End added part
if (restPath != null)
return new RestHandler { RestPath = restPath, RequestName = restPath.RequestType.GetOperationName(), ResponseContentType = contentType };
technically speaking IAppHost.IServiceRoutes should be the one doing the routing. Probably in the future this will be extensible.
Resolving services
The second problem is resolving the services. After the route has been found and the right Message/Dto Type has been resolved. The IAppHost.ServiceController will attempt to find the right service and make it execute the message.
This class also has init functions which are called on startup to reflect all the services in servicestack. I didn't found a work around yet, but ill by working on it to make it possible in ServiceStack coming weeks.
Current version on nuget its not possible to make it work. I added some extensibility in servicestack to make it +- possible.
Ioc Solution out of the box
For ioc ServiceStack.Funq gives us a solution. Funq allows making child containers where you can register your ioc on. On resolve a child container will, if it can't resolve the interface, ask its parent to resolve it.
Container.CreateChildContainer()
Here are my parameters:
Simple NServiceBus Saga implementation using the default builder
In-house ORM on top of SQL Server
Multitenancy - I have two ASP.NET MVC 4 domains running on the same website, each with their own databases
We configure our ORM using a static method like so:
public class EndpointConfig: IConfigureThisEndpoint, IWantCustomInitialization {
public void Init() {
var bus = Configure.With()
.AutofacBuilder()
.UnicastBus().LoadMessageHandlers().DoNotAutoSubscribe()
.XmlSerializer()
.MsmqTransport().IsTransactional(true).PurgeOnStartup(false)
.MsmqSubscriptionStorage()
.Sagas().RavenSagaPersister().InstallRavenIfNeeded()
.UseInMemoryTimeoutPersister()
.CreateBus()
.Start();
SlenderConfiguration.Init(bus);
}
}
public class SlenderCofnigruation {
private static ORMScope scope { get; set; }
public static void Init(IBus bus)
{
ORMConfig.GetScope = () =>
{
var environment = "dev";
if (bus.CurrentMessageContext.Headers.ContainsKey("Environment"))
environment = bus.CurrentMessageContext.Headers["Environment"];
if (scope == null)
scope = new SlenderScope(ConfigurationManager.ConnectionStrings[environment].ConnectionString);
return scope;
};
}
}
This works fine in our single-tenant Beta environment - it's fine for that static scope to get re-used because the environment header is always the same for a given deployment.
It's my understanding that this won't work for the multitenant situation described above, because NServiceBus will reuse threads across messages. The same scope would then be used, causing problems if the message was intended for a different environment.
What I think I want is a single scope per message, but I'm really not sure how to get there.
I've seen Unit Of Work Implementation for RavenDB, and the unit of work implementation in the full duplex sample, but I'm not sure that's the right path.
I've also seen the DependencyLifecycle enum, but I'm not sure how I can use that to resolve the scope given the way I have to set up the GetScope func.
Obviously I have no idea what's going on here. Any suggestions?
If you need to do something on a per-message basis, consider using message mutators (IMutateIncomingMessages) in addition to your unit-of-work management with some thread-static state.
I am wondering why the two methods listed below do not give the same security trimming.
Expected result: Both methods give full access to all content in the current site collection
Actual result: Security trimming is occurring when using Method #1
Method #2 works properly for retrieving content from other webs, but Method #1 does not.
Both methods give access across webs in Anonymous mode, and both work for site admin accounts.
The difference comes for Hierarchy Managers, Approvers and Editors. Method #1 does not give admin access across webs.
Method #1
using (SystemOperation op = new SystemOperation())
{
//Do an operation that requires retrieving across webs
}
public class SystemOperation : IDisposable
{
private WindowsImpersonationContext ctx;
public SystemOperation()
{
if (!WindowsIdentity.GetCurrent().IsSystem)
{
ctx = WindowsIdentity.Impersonate(System.IntPtr.Zero);
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool all)
{
if (ctx != null)
{
ctx.Undo();
}
}
}
Method #2:
Microsoft.Sharepoint.SPSecurity.RunWithElevatedPrivileges(delegate()
{
//Do an operation that requires retrieving across webs
});
RunWithElevatedPrivileges provides two separate privledges. First is that it elevates the Windows identity of the user to the AppPool account, the second is that it also elevates the identity to the SharePoint\System account which is a built in security account that provides full control (in a SharePoint sense). The internal SharePoint account is used when you construct your SP Objects (like SPSite).
So basically it will depend on how you build your code and when you instatiate your objects that affect how the privledges work out.