How to use ReactJS.NET with multiple server-side bundles - reactjs.net

I use ReactJS.NET with my own server-side bundle that has ReactJS embeded. It is registered as shown below:
using React;
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(My.Little.Website.ReactConfig), "Configure")]
namespace My.Little.Website
{
public static class ReactConfig
{
public static void Configure()
{
ReactSiteConfiguration.Configuration
.SetReuseJavaScriptEngines(true)
.SetLoadBabel(false)
.SetLoadReact(false) // react is included in the server-side bundle
.AddScriptWithoutTransform("~/assets/react/server.bundle.js");
}
}
}
But now I want to use multiple server bundles because multiple "applications" are deployed on the same website and need to use their own configuration. So, for example, I want to use a ReactSiteConfiguration with its own settings per area. Any ideas on how to accomplish this?
In the sourcecode of ReactJS.NET I see that the function AddScriptWithoutTransform() can be called multiple times:
public IReactSiteConfiguration AddScriptWithoutTransform(string filename)
{
_scriptFilesWithoutTransform.Add(filename);
return this;
}
So I could do:
.AddScriptWithoutTransform("~/assets/react/area1/server.bundle.js")
.AddScriptWithoutTransform("~/assets/react/area2/server.bundle.js")
But how can I select per area to only use the area-specific bundle?

Related

Setup OWIN dynamically (by domain)

We are trying to setup a white label on our project that uses OWIN(includes FB, google, Live logins). Is there a way to setup their API credential dynamically, say if they changes the domain the settings will change.
I think owin loads earlier than MVC? Is there a way that we can load it on Global.asax(Request)?
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
UPDATE:
In other words a single Application will host many domains and sub domains(white labeling).
I have been googling on the same things today. Then I found a nice OWIN sample at http://aspnet.codeplex.com/SourceControl/latest#Samples/Katana/BranchingPipelines/BranchingPipelines.sln that explained the branching capabilities of OWIN. If I understand this sample correctly, you should be able to configure different OWIN stacks depending on request parameters such as host header, cookie, path or whatever using the app.Map() or app.MapWhen() methods.
Let's say you have 2 different DNS domains representing 2 customers with different login configs, you can initialize OWIN to use different configs depending on the value of the host header:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapWhen(ctx => ctx.Request.Headers.Get("Host").Equals("customer1.cloudservice.net"), app2 =>
{
app2.UseWsFederationAuthentication(...);
});
app.MapWhen(ctx => ctx.Request.Headers.Get("Host").Equals("customer2.cloudservice.net"), app2 =>
{
app2.UseGoogleAuthentication(...);
});
}
}
I just went through an exercise in trying to do exactly this. Unfortunately, There is no way to directly inject middleware into a Katana-based host after startup. The reason for this is because in order for middleware to actually be used, it has to be composed together into the application delegate. Katana's implementation does this by calling IAppBuilder.Build(typeof(AppFunc)), where AppFunc is a using alias of the specified type for the application delegate: Func<IDictionary<string,object>, Task> when initialization is complete. Here's the key line at the bottom of .Initialize:
AppFunc = (AppFunc)builder.Build(typeof(AppFunc));
The only opportunity you have to configure middleware is before this, during the configuration step in the startup class you write or by web.config.
Just to be very clear about what won't work, I was experimenting with something like this:
public class Startup
{
public void Configuration(IAppBuilder app)
{
HomeController.Initialized += () => ConfigureGoogle(app);
}
private void ConfigureGoogle(IAppBuilder app)
{
app.UseGoogleAuthentication(/* stuff */);
}
}
public class HomeController : Controller
{
public event EventHandler Initialized;
[Route("/setup/submit"), AcceptVerbs(HttpVerbs.Post)]
public ActionResult SetupSubmit()
{
/* ... */
Initialized();
}
}
This doesn't throw exceptions, and there are no obvious signs of errors - but it doesn't work because the application delegate has already been composed by this point. Katana doesn't give you any APIs for re-building the application delegate (and I'm not sure that would be a good idea anyway - there would be countless bugs that could be created by such a mechanism; for example, how should the server handle an in-flight request when the application delegate is recomposed after initialization?).
What's your alternative? #DavidFahlander's approach is going to be the right one, but you still need to be careful if you want to achieve dynamism. Look at what .MapWhen does:
// put middleware in pipeline before creating branch
var options = new MapWhenOptions { Predicate = predicate };
IAppBuilder result = app.Use<MapWhenMiddleware>(options);
// create branch and assign to options
IAppBuilder branch = app.New();
configuration(branch);
options.Branch = (AppFunc)branch.Build(typeof(AppFunc));
First, notice that this calls app.Use with a MapWhenMiddleware type. This means you are facing the same limitations as before - it all has to be done up front. The branched middleware also will be baked in before initialization is complete: see the last line: branch.Build.
Your only hope of dynamism here is to use the predicates in a way that achieves your goal. This doesn't get you 100% of the way there, but it gets pretty darn close:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapWhen(ctx => ClientHasWsFederationConfigured() && ctx.Request.Headers.Get("Host").Equals("customer1.cloudservice.net"), app2 =>
{
app2.UseWsFederationAuthentication(...);
});
app.MapWhen(ctx => ClientHasGoogleAuthConfigured() && ctx.Request.Headers.Get("Host").Equals("customer2.cloudservice.net"), app2 =>
{
app2.UseGoogleAuthentication(...);
});
}
}
The limitations here will be these:
You have to configure all your supported authentication types up-front. you can't add a new one while the app is running.
ClientHasXXXConfigured will be run on every request. This may or may not be acceptable on a performance basis, depending on what you do.
Given the information you put in the question, I think these tradeoffs are probably ok as long as you're careful about what ClientHasXXXConfigured (or it's equivalent) does.
I am working on a Microsoft.Owin.Security design change that will allow for multi-tenancy in AuthenticationOptions (e.g. GoogleAuthenticationOptions) to support the ability for a developer to inject their owin implementations.
Here is my proposal to the Katana project team:
https://katanaproject.codeplex.com/discussions/561673
I also have a working implementation that sits on top of the existing Microsoft.Owin.Security infrastructure and does not benefit from my design change suggestion. It requires rolling your own version of the Auth Middleware (copy paste existing) but it's a viable workaround until I can get my design change implemented in Microsoft.
https://github.com/kingdango/Owin.OAuth.Multitenant
(This is rough, I just got it up this morning)
Ultimately I don't think it makes sense to develop multiple Owin pipelines for each tenant just to support multi-tenancy. The right solution is to have middleware that is extensible and that's what I'm proposing.
I've managed to get this working. The problem is the order of execution of MapWhen doesn't work exactly how one thinks it does.
The key thing to remember is the configuration (i.e. the second parameter of MapWhen) gets executed and cached on app startup. For this reason it is important to think carefully about how many configurations you need and run a seperate 'app.MapWhen' for each unique configuration. If you are using multiple domains and each use the same configuration then you don't need to do this, however if each configuration is unique per domain, you will need to run a MapWhen for each one. In my case I found it easier to put these in a foreach block as I needed to give each domain a unique configuration as OpenIDConnect requires a unique AppID for each one.
The first parameter of MapWhen is a function that returns a conditional. If it evaluates to true it will return the corresponding configuration from a cache as it will already be generated at this point. No new configurations will be generated if this always returned false previously and suddenly returns true. It is important to note that since this conditional function gets executed per request it must be kept as fast and lightweight as possible.
var domains = new string[] { "abc.com", "def.com" };
var host = HttpContext.Current.Request.ServerVariables["HTTP_HOST"]?.ToLower();
foreach (string domain in domains)
{
if (!ShouldEnableForDomain(domain) continue;
app.MapWhen(
context => host == domain, //if true a config will be used from the cache
config =>
{
//This executes once on app startup (per domain) and will be cached - it is not executed in the context of a request
Trace.WriteLine(String.Format("Setting up configuration: {0}", domain));
config.UseOpenIdConnectAuthentication(GetOpenIdOptions(domain));
}
);
}

Grouping bundles with fallback CDN

I wish to provide a fallback URL for my kendo UI references
I've followed the post of Hanselmann post and it works for a single file. I was wondering if it's possible to create a single bundle that groups the tree files
and performs the callback on the 3 files inside my local folder
Scott suggests
public static void RegisterBundles(BundleCollection bundles)
{
bundles.UseCdn = true;
BundleTable.EnableOptimizations = true; //force optimization while debugging
var jquery = new ScriptBundle("~/bundles/jquery", "//ajax.aspnetcdn.com/ajax/jquery/jquery-2.0.0.min.js").Include(
"~/Scripts/jquery-{version}.js");
jquery.CdnFallbackExpression = "window.jQuery";
bundles.Add(jquery);
//...
}
But this relies only on a single file... I need to do it for the 3 kendo file I need (kendo.all.min.js,kendo.aspnetmvc.min.js,cultures/kendo.culture.it-IT.min.js)
Do I need to create 3 bundles?
Thanks

MachineKeyDataProtector - Invalid link when confirmation email sent through background job

I've been pulling my hair out over this. Anytime a user registration email is sent out via my windows service (background task), I get an "Invalid link".
My setup
I'm using Hangfire as a windows service on our development server. This is where the problematic GenerateEmailConfirmationToken call is happening. It's in a completely different context, outside of the ASP.NET pipeline. So I have setup machineKey values to correspond with that in the web.config of the MVC application:
In the app.config of the Windows Service Console project, which transforms to MyApp.exe.config, I have a machineKey element
In the MVC 5 project - I have a machineKey element that matches the MyApp.exe.config machineKey element.
I've verified that BOTH of these have the same machine key element data.
The Problem
When I generate a user using the ASP.NET MVC context and pipeline (IE without going through the Hangfire Background job processing), the link works fine.
When I use the background job processor, I always get invalid link. I'm all out of ideas here.
Why is this happening? Is it because the token is being generated in a different thread? How do I get around this?
Relevant code for the various projects
IoC Bootstrapping
Gets called by both applications (Windows Service and MVC Web App)
container.Register<IUserTokenProvider<AppUser, int>>(() => DataProtector.TokenProvider, defaultAppLifeStyle);
DataProtector.cs
public class DataProtector
{
public static IDataProtectionProvider DataProtectionProvider { get; set; }
public static DataProtectorTokenProvider<AppUser, int> TokenProvider { get; set; }
static DataProtector()
{
DataProtectionProvider = new MachineKeyProtectionProvider();
TokenProvider = new DataProtectorTokenProvider<AppUser, int>(DataProtectionProvider.Create("Confirmation", "ResetPassword"));
}
}
Things I've Tried
Using a DpapiDataProtectionProvider
Custom MachineKeyProtectionProvider from Generating reset password token does not work in Azure Website
The MachineKeyProtectionProvider.cs code is exactly as the linked post above.
I've also tried other purposes like "YourMom" and "AllYourTokensAreBelongToMe" to no avail. Single purposes, multiple purposes - it doesn't matter - none work.
I'm also calling HttpUtility.UrlEncode(code) on the code that gets generated in both places (Controller and Background Job).
Solution
igor got it right, except it was not a code issue. It was because of a rogue service picking up the job, which had a different machine key. I had been staring at the problem so long that I did not see a second service running.
As I understand your problem there are 2 possible places where failure could occur.
1. MachineKey
It could be that the MachineKey itself is not producing a consistent value between your 2 applications. This can happen if your machineKey in the .config file is not the same in both applications (I did read that you checked it but a simple type-o, added space, added to the wrong parent element, etc. could lead to this behavior.). This can be easily tested to rule it out as a point of failure. Also the behavior might be different depending on the referenced .net framework, MachineKey.Protect
The configuration settings that are required for the MachineKeyCompatibilityMode.Framework45 option are required for this method even if the MachineKeySection.CompatibilityMode property is not set to the Framework45 option.
I created a random key pair for testing and using this key I generated a test value I assigned to variable validValue below in the code. If you copy/paste the following section into your web.config and app.config the Unprotect of that keyvalue will work.
web.config / app.config
<system.web>
<httpRuntime targetFramework="4.6.1"/>
<machineKey decryption="AES" decryptionKey="9ADCFD68D2089D79A941F9B8D06170E4F6C96E9CE996449C931F7976EF3DD209" validation="HMACSHA256" validationKey="98D92CC1E5688DB544A1A5EF98474F3758C6819A93CC97E8684FFC7ED163C445852628E36465DB4E93BB1F8E12D69D0A99ED55639938B259D0216BD2DF4F9E73" />
</system.web>
Service Application Test
class Program
{
static void Main(string[] args)
{
// should evaluate to SomeTestString
const string validValue = "03AD03E75A76CF13FDDA57425E9D362BA0FF852C4A052FD94F641B73CEBD3AC8B2F253BB45550379E44A4938371264BFA590F9E68E59DB57A9A4EB5B8B1CCC59";
var unprotected2 = MachineWrapper.Unprotect(validValue);
}
}
Mvc Controller (or Web Api controller) Test
public class WebTestController : Controller
{
// GET: WebTest
public ActionResult Index()
{
// should evaluate to SomeTestString
const string validValue = "03AD03E75A76CF13FDDA57425E9D362BA0FF852C4A052FD94F641B73CEBD3AC8B2F253BB45550379E44A4938371264BFA590F9E68E59DB57A9A4EB5B8B1CCC59";
var unprotected2 = MachineWrapper.Unprotect(validValue);
return View(unprotected2);
}
}
Common Code
using System;
using System.Linq;
using System.Text;
using System.Web.Security;
namespace Common
{
public class MachineWrapper
{
public static string Protect()
{
var testData = "SomeTestString";
return BytesToString(MachineKey.Protect(System.Text.Encoding.UTF8.GetBytes(testData), "PasswordSafe"));
}
public static string Unprotect(string data)
{
var bytes = StringToBytes(data);
var result = MachineKey.Unprotect(bytes, "PasswordSafe");
return System.Text.Encoding.UTF8.GetString(result);
}
public static byte[] StringToBytes(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
public static string BytesToString(byte[] bytes)
{
var hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.AppendFormat("{0:x2}", b);
return hex.ToString().ToUpper();
}
}
}
If this passes both Console and the Web Application will get the same value and not throw a CryptographicException message Error occurred during a cryptographic operation. If you want to test with your own keys just run Protect from the common MachineWrapper class and record the value and re-execute for both apps.
2. UserManager uses Wrong Type
I would start with the previous section BUT the other failure point is that your custom machine key provider is not being used by the Microsoft.AspNet.Identity.UserManager. So here are some questions/action items that can help you figure out why this is happening:
Is container.Register the Unity IoC framework or are you using another framework?
Are you sure that your Di framework is also injecting that instance in the Microsoft.AspNet.Identity.UserManager in both the Service application as well as the Web application?
Have put a break point in public byte[] Protect of your MachineKeyDataProtector class to see if this is called in both the Service application as well as the Web application?
From examples I have seen so far (including the one you posted with the custom MachineKey solution) you need to manually bootstrap the type during application startup but then again I have not ever tried to hook into the Identity framework to replace this component using DI.
If you look at the default Visual Studio template code that is provided when you create a new MVC application the code file App_Start\IdentityConfig.cs would be the place to add this new provider.
Method:
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
Replace
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
With this
var provider = new MachineKeyProtectionProvider();
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("ResetPasswordPurpose"));
And this has to be configured for both applications if you are not using a common library where this is configured.

Enabling bundling and minification on 3rd party libraries

I am including a big javascript application into our MVC-based solution. However, the application includes a lot of files due to which I would like to enable bundling and minification on it. In fact, I would like to enable bundling on all 3rd party javascript and CSS files while keeping the files we develop ourselves unminified and unbundled. Until release, of course.
There is way to enable optimizations globally:
public static void RegisterBundles(BundleCollection bundles)
{
Bundle ckScripts = new ScriptBundle("~/scripts/ckeditor")
.IncludeDirectory("~/Areas/CMS/Editor", "*.js", true);
bundles.Add(ckScripts);
BundleTable.EnableOptimizations = true;
}
However, this happens in the top BundleTable level enabling optimizations on all the bundles within the bundle table.
I would need to have something like this:
public static void RegisterBundles(BundleCollection bundles)
{
Bundle ckScripts = new ScriptBundle("~/scripts/ckeditor")
.IncludeDirectory("~/Areas/CMS/Editor", "*.js", true)
.EnableOptimizations();
bundles.Add(ckScripts);
}
Which would effectively enable optimizations only for that particular bundle.
I know, there is currently no Bundle.EnableOptimizations() method and creating such given the fact that the optimization happens in the BundleTable level, which is inherently global by design, creating such method, would prove very difficult.
So, here I in loss of ideas where to look into.
Questions:
Is there an alternative framework somewhere that would support this
Is there a contrib project somewhere that would provide this
Have you encountered such need, possibly have a solution
Provided that there's no existing solution, please post an idea how to start unfolding this challenge.
From what I know, BundleTable is a singleton. Which means there can be only one instance. I had an idea of creating another bundle table but got lost when I started figuring out how to make MVC use it.
Another starting point would be to code a custom renderer. One that mimics the behavior of System.Web.Optimization.Scripts.Render(), but again, I'm getting lost with it trying to figure out in which state the BundleTable comes into picture.
UPDATE
Seems like I can create a new BundleContext and BundleResponse by using a HtmlHelper.
public static IHtmlString RenderBundled<TSource>(this HtmlHelper<TSource> helper, string bundlePath)
{
// Find the bundle in question
var bundle = BundleTable.Bundles.FirstOrDefault(b => b.Path == bundlePath);
// No bundle found, return
if (bundle == null) return MvcHtmlString.Create(String.Empty);
// Add the bundle found into a new collection
BundleCollection coll = new BundleCollection {bundle};
// Create a new BundleContext
BundleContext ctx = new BundleContext(helper.ViewContext.HttpContext, coll, "~/bundles");
// Enable optimizations
ctx.EnableOptimizations = true;
// Create the response (this contains bundled & minified script/styles from bundle)
BundleResponse response = bundle.GenerateBundleResponse(ctx);
// Render the content based on ContentType
if (response.ContentType == "text/css")
return RenderStyle(response.Content);// returns <style>bundled content</style>
if (response.ContentType == "text/javascript")
return RenderScript(response.Content); // returns <script>bundled content</script>
// In any other case return "nothing"
return MvcHtmlString.Create(String.Empty);
}
This probably is not the best approach. Overhead from creating BundleContext on every page request and adding the script/styles payload into the page output without the caching abilities. But it's a start. One thing I noticed was that the bundled content will actually be cached in HttpContext.Cache. So, theoretically I could just put the bundle path into script src or style href and somehow then handle the request from server side.

Can the ServiceStack config setting “WebHostPhysicalPath” be used for relative paths?

Hello ServiceStack aficionados!
I would like to host static XML files through the ServiceStack service; however, I can't seem to get the configuration right and only receive 404 errors. Feels like I tried all sorts of path/url combinations.
Can the WebHostPhysicalPath be defined as a relative path? Is there another setting that must be enabled? I was concerned that maybe the XML extension is conflicting with the format conversion stuff.
Also, can I host Razor cshtml files this way too?
Any comments on this approach?
thanks!
You can return a static file from a service like so:
[Route("/myFile/")]
public class GetMyFile
{
}
public class HelloService : Service
{
public HttpResult Any(GetMyFile request)
{
return new HttpResult(new FileInfo("~/myfile.xml"), asAttachment:true) { ContentType = "text/xml" };
}
}
As for razor: http://razor.servicestack.net/

Resources