PageRenderTime 59ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/ReactiveUI/RxApp.cs

https://github.com/bsiegel/ReactiveUI
C# | 507 lines | 341 code | 71 blank | 95 comment | 48 complexity | ea38d6c5f4cfe760b8f7260118056176 MD5 | raw file
Possible License(s): Apache-2.0, CC-BY-SA-3.0, LGPL-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Reactive;
  6. using System.Reactive.Concurrency;
  7. using System.Diagnostics.Contracts;
  8. using System.Linq;
  9. using System.Linq.Expressions;
  10. using System.Reactive.Linq;
  11. using System.Reactive.Subjects;
  12. using System.Reflection;
  13. using System.Runtime.InteropServices;
  14. using System.Text.RegularExpressions;
  15. using System.Threading;
  16. using System.Threading.Tasks;
  17. #if SILVERLIGHT
  18. using System.Windows;
  19. #endif
  20. #if WINRT
  21. using Windows.ApplicationModel;
  22. using System.Reactive.Windows.Foundation;
  23. using System.Reactive.Threading.Tasks;
  24. using Windows.ApplicationModel.Store;
  25. #endif
  26. namespace ReactiveUI
  27. {
  28. /*
  29. * N.B. Why we have this evil global class
  30. *
  31. * In a WPF or Silverlight application, most commands must have the Dispatcher
  32. * scheduler set, because notifications will end up being run on another thread;
  33. * this happens most often in a CanExecute observable. Unfortunately, in a Unit
  34. * Test framework, while the MS Test Unit runner will *set* the Dispatcher (so
  35. * we can't even use the lack of its presence to determine whether we're in a
  36. * test runner or not), none of the items queued to it will ever be executed
  37. * during the unit test.
  38. *
  39. * Initially, I tried to plumb the ability to set the scheduler throughout the
  40. * classes, but when you start building applications on top of that, having to
  41. * have *every single* class have a default Scheduler property is really
  42. * irritating, with either default making life difficult.
  43. */
  44. public static class RxApp
  45. {
  46. static RxApp()
  47. {
  48. // Default name for the field backing the "Foo" property => "_Foo"
  49. // This is used for ReactiveObject's RaiseAndSetIfChanged mixin
  50. GetFieldNameForPropertyNameFunc = new Func<string,string>(x => "_" + x);
  51. #if WP7
  52. TaskpoolScheduler = new EventLoopScheduler();
  53. #elif WP8
  54. //TaskpoolScheduler = Scheduler.TaskPool;
  55. TaskpoolScheduler = Scheduler.ThreadPool;
  56. #elif SILVERLIGHT || DOTNETISOLDANDSAD
  57. TaskpoolScheduler = Scheduler.ThreadPool;
  58. #elif WINRT
  59. TaskpoolScheduler = System.Reactive.Concurrency.ThreadPoolScheduler.Default;
  60. #else
  61. TaskpoolScheduler = Scheduler.TaskPool;
  62. #endif
  63. DefaultExceptionHandler = Observer.Create<Exception>(ex => {
  64. // NB: If you're seeing this, it means that an
  65. // ObservableAsPropertyHelper or the CanExecute of a
  66. // ReactiveCommand ended in an OnError. Instead of silently
  67. // breaking, ReactiveUI will halt here if a debugger is attached.
  68. if (Debugger.IsAttached) {
  69. Debugger.Break();
  70. }
  71. RxApp.DeferredScheduler.Schedule(() => {
  72. throw new Exception(
  73. "An OnError occurred on an object (usually ObservableAsPropertyHelper) that would break a binding or command. To prevent this, Subscribe to the ThrownExceptions property of your objects",
  74. ex);
  75. });
  76. });
  77. MessageBus = new MessageBus();
  78. LoggerFactory = t => new DebugLogger();
  79. RxApp.Register(typeof(INPCObservableForProperty), typeof(ICreatesObservableForProperty));
  80. RxApp.Register(typeof(IRNPCObservableForProperty), typeof(ICreatesObservableForProperty));
  81. RxApp.Register(typeof(POCOObservableForProperty), typeof(ICreatesObservableForProperty));
  82. RxApp.Register(typeof(PropertyBinderImplementation), typeof(IPropertyBinderImplementation));
  83. RxApp.Register(typeof(NullDefaultPropertyBindingProvider), typeof(IDefaultPropertyBindingProvider));
  84. RxApp.Register(typeof(EqualityTypeConverter), typeof(IBindingTypeConverter));
  85. RxApp.Register(typeof(StringConverter), typeof(IBindingTypeConverter));
  86. #if !SILVERLIGHT && !WINRT
  87. RxApp.Register(typeof(ComponentModelTypeConverter), typeof(IBindingTypeConverter));
  88. #endif
  89. var namespaces = attemptToEarlyLoadReactiveUIDLLs();
  90. namespaces.ForEach(ns => {
  91. #if WINRT
  92. var assm = typeof (RxApp).GetTypeInfo().Assembly;
  93. #else
  94. var assm = Assembly.GetExecutingAssembly();
  95. #endif
  96. var fullName = typeof (RxApp).AssemblyQualifiedName;
  97. var targetType = ns + ".ServiceLocationRegistration";
  98. fullName = fullName.Replace("ReactiveUI.RxApp", targetType);
  99. fullName = fullName.Replace(assm.FullName, assm.FullName.Replace("ReactiveUI", ns));
  100. var registerTypeClass = Reflection.ReallyFindType(fullName, false);
  101. if (registerTypeClass != null) {
  102. var registerer = (IWantsToRegisterStuff) Activator.CreateInstance(registerTypeClass);
  103. registerer.Register();
  104. }
  105. });
  106. if (InUnitTestRunner()) {
  107. LogHost.Default.Warn("*** Detected Unit Test Runner, setting Scheduler to Immediate ***");
  108. LogHost.Default.Warn("If we are not actually in a test runner, please file a bug\n");
  109. DeferredScheduler = Scheduler.Immediate;
  110. } else {
  111. LogHost.Default.Info("Initializing to normal mode");
  112. }
  113. if (DeferredScheduler == null) {
  114. LogHost.Default.Error("*** ReactiveUI.Xaml DLL reference not added - using Event Loop *** ");
  115. LogHost.Default.Error("Add a reference to ReactiveUI.Xaml if you're using WPF / SL5 / WP7 / WinRT");
  116. LogHost.Default.Error("or consider explicitly setting RxApp.DeferredScheduler if not");
  117. RxApp.DeferredScheduler = new EventLoopScheduler();
  118. }
  119. }
  120. [ThreadStatic] static IScheduler _UnitTestDeferredScheduler;
  121. static IScheduler _DeferredScheduler;
  122. /// <summary>
  123. /// DeferredScheduler is the scheduler used to schedule work items that
  124. /// should be run "on the UI thread". In normal mode, this will be
  125. /// DispatcherScheduler, and in Unit Test mode this will be Immediate,
  126. /// to simplify writing common unit tests.
  127. /// </summary>
  128. public static IScheduler DeferredScheduler {
  129. get { return _UnitTestDeferredScheduler ?? _DeferredScheduler; }
  130. set {
  131. // N.B. The ThreadStatic dance here is for the unit test case -
  132. // often, each test will override DeferredScheduler with their
  133. // own TestScheduler, and if this wasn't ThreadStatic, they would
  134. // stomp on each other, causing test cases to randomly fail,
  135. // then pass when you rerun them.
  136. if (InUnitTestRunner()) {
  137. _UnitTestDeferredScheduler = value;
  138. _DeferredScheduler = _DeferredScheduler ?? value;
  139. } else {
  140. _DeferredScheduler = value;
  141. }
  142. }
  143. }
  144. [ThreadStatic] static IScheduler _UnitTestTaskpoolScheduler;
  145. static IScheduler _TaskpoolScheduler;
  146. /// <summary>
  147. /// TaskpoolScheduler is the scheduler used to schedule work items to
  148. /// run in a background thread. In both modes, this will run on the TPL
  149. /// Task Pool (or the normal Threadpool on Silverlight).
  150. /// </summary>
  151. public static IScheduler TaskpoolScheduler {
  152. get { return _UnitTestTaskpoolScheduler ?? _TaskpoolScheduler; }
  153. set {
  154. if (InUnitTestRunner()) {
  155. _UnitTestTaskpoolScheduler = value;
  156. _TaskpoolScheduler = _TaskpoolScheduler ?? value;
  157. } else {
  158. _TaskpoolScheduler = value;
  159. }
  160. }
  161. }
  162. static Func<Type, IRxUILogger> _LoggerFactory;
  163. static internal readonly Subject<Unit> _LoggerFactoryChanged = new Subject<Unit>();
  164. /// <summary>
  165. /// Set this property to implement a custom logger provider - the
  166. /// string parameter is the 'prefix' (usually the class name of the log
  167. /// entry)
  168. /// </summary>
  169. public static Func<Type, IRxUILogger> LoggerFactory {
  170. get { return _LoggerFactory; }
  171. set { _LoggerFactory = value; _LoggerFactoryChanged.OnNext(Unit.Default); }
  172. }
  173. [ThreadStatic] static IMessageBus _UnitTestMessageBus;
  174. static IMessageBus _MessageBus;
  175. /// <summary>
  176. /// Set this property to implement a custom MessageBus for
  177. /// MessageBus.Current.
  178. /// </summary>
  179. public static IMessageBus MessageBus {
  180. get { return _UnitTestMessageBus ?? _MessageBus; }
  181. set {
  182. if (InUnitTestRunner()) {
  183. _UnitTestMessageBus = value;
  184. _MessageBus = _MessageBus ?? value;
  185. } else {
  186. _MessageBus = value;
  187. }
  188. }
  189. }
  190. /// <summary>
  191. /// Set this property to override the default field naming convention
  192. /// of "_PropertyName" with a custom one.
  193. /// </summary>
  194. public static Func<string, string> GetFieldNameForPropertyNameFunc { get; set; }
  195. /// <summary>
  196. /// This method allows you to override the return value of
  197. /// RxApp.InUnitTestRunner - a null value means that InUnitTestRunner
  198. /// will determine this using its normal logic.
  199. /// </summary>
  200. public static bool? InUnitTestRunnerOverride { get; set; }
  201. /// <summary>
  202. /// This Observer is signalled whenever an object that has a
  203. /// ThrownExceptions property doesn't Subscribe to that Observable. Use
  204. /// Observer.Create to set up what will happen - the default is to crash
  205. /// the application with an error message.
  206. /// </summary>
  207. public static IObserver<Exception> DefaultExceptionHandler { get; set; }
  208. static bool? _inUnitTestRunner;
  209. /// <summary>
  210. /// InUnitTestRunner attempts to determine heuristically if the current
  211. /// application is running in a unit test framework.
  212. /// </summary>
  213. /// <returns>True if we have determined that a unit test framework is
  214. /// currently running.</returns>
  215. public static bool InUnitTestRunner()
  216. {
  217. if (InUnitTestRunnerOverride.HasValue) {
  218. return InUnitTestRunnerOverride.Value;
  219. }
  220. if (_inUnitTestRunner.HasValue) return _inUnitTestRunner.Value;
  221. // NB: This is in a separate static ctor to avoid a deadlock on
  222. // the static ctor lock when blocking on async methods
  223. _inUnitTestRunner = RealUnitTestDetector.InUnitTestRunner();
  224. return _inUnitTestRunner.Value;
  225. }
  226. /// <summary>
  227. /// GetFieldNameForProperty returns the corresponding backing field name
  228. /// for a given property name, using the convention specified in
  229. /// GetFieldNameForPropertyNameFunc.
  230. /// </summary>
  231. /// <param name="propertyName">The name of the property whose backing
  232. /// field needs to be found.</param>
  233. /// <returns>The backing field name.</returns>
  234. public static string GetFieldNameForProperty(string propertyName)
  235. {
  236. return GetFieldNameForPropertyNameFunc(propertyName);
  237. }
  238. //
  239. // Service Location
  240. //
  241. static Func<Type, string, object> _getService;
  242. static Func<Type, string, IEnumerable<object>> _getAllServices;
  243. static Action<Type, Type, string> _register;
  244. public static T GetService<T>(string key = null)
  245. {
  246. return (T)GetService(typeof(T), key);
  247. }
  248. public static object GetService(Type type, string key = null)
  249. {
  250. lock (_preregisteredTypes) {
  251. if (_preregisteredTypes.Count == 0) goto callSl;
  252. var k = Tuple.Create(type, key);
  253. if (!_preregisteredTypes.ContainsKey(k)) goto callSl;
  254. return Activator.CreateInstance(_preregisteredTypes[k].First());
  255. }
  256. callSl:
  257. var getService = _getService ??
  258. ((_, __) => { throw new Exception("You need to call RxApp.ConfigureServiceLocator to set up service location"); });
  259. return getService(type, key);
  260. }
  261. public static IEnumerable<T> GetAllServices<T>(string key = null)
  262. {
  263. return GetAllServices(typeof(T), key).Cast<T>().ToArray();
  264. }
  265. public static IEnumerable<object> GetAllServices(Type type, string key = null)
  266. {
  267. lock (_preregisteredTypes) {
  268. if (_preregisteredTypes.Count == 0) goto callSl;
  269. var k = Tuple.Create(type, key);
  270. if (!_preregisteredTypes.ContainsKey(k)) goto callSl;
  271. return _preregisteredTypes[k].Select(Activator.CreateInstance).ToArray();
  272. }
  273. callSl:
  274. var getAllServices = _getAllServices ??
  275. ((_,__) => { throw new Exception("You need to call RxApp.ConfigureServiceLocator to set up service location"); });
  276. return getAllServices(type, key).ToArray();
  277. }
  278. static readonly Dictionary<Tuple<Type, string>, List<Type>> _preregisteredTypes = new Dictionary<Tuple<Type, string>, List<Type>>();
  279. public static void Register(Type concreteType, Type interfaceType, string key = null)
  280. {
  281. // NB: This allows ReactiveUI itself (as well as other libraries)
  282. // to register types before the actual service locator is set up,
  283. // or to serve as an ultra-crappy service locator if the app doesn't
  284. // use service location
  285. lock (_preregisteredTypes) {
  286. if (_register == null) {
  287. var k = Tuple.Create(interfaceType, key);
  288. if (!_preregisteredTypes.ContainsKey(k)) _preregisteredTypes[k] = new List<Type>();
  289. _preregisteredTypes[k].Add(concreteType);
  290. } else {
  291. _register(concreteType, interfaceType, key);
  292. }
  293. }
  294. }
  295. public static void ConfigureServiceLocator(
  296. Func<Type, string, object> getService,
  297. Func<Type, string, IEnumerable<object>> getAllServices,
  298. Action<Type, Type, string> register)
  299. {
  300. if (getService == null || getAllServices == null || register == null) {
  301. throw new ArgumentException("Both getService and getAllServices must be implemented");
  302. }
  303. _getService = getService;
  304. _getAllServices = getAllServices;
  305. _register = register;
  306. // Empty out the types that were registered before service location
  307. // was set up.
  308. lock (_preregisteredTypes) {
  309. _preregisteredTypes.Keys
  310. .SelectMany(x => _preregisteredTypes[x]
  311. .Select(v => Tuple.Create(v, x.Item1, x.Item2)))
  312. .ForEach(x => _register(x.Item1, x.Item2, x.Item3));
  313. }
  314. }
  315. public static bool IsServiceLocationConfigured()
  316. {
  317. return _getService != null && _getAllServices != null;
  318. }
  319. static IEnumerable<string> attemptToEarlyLoadReactiveUIDLLs()
  320. {
  321. var guiLibs = new[] {
  322. "ReactiveUI.Xaml",
  323. "ReactiveUI.Routing",
  324. "ReactiveUI.Gtk",
  325. "ReactiveUI.Cocoa",
  326. "ReactiveUI.Android",
  327. "ReactiveUI.NLog",
  328. };
  329. #if WINRT || SILVERLIGHT
  330. // NB: WinRT hates your Freedom
  331. return new[] {"ReactiveUI.Xaml", "ReactiveUI.Routing"};
  332. #else
  333. var name = Assembly.GetExecutingAssembly().GetName();
  334. var suffix = getArchSuffixForPath(Assembly.GetExecutingAssembly().Location);
  335. return guiLibs.SelectMany(x => {
  336. var fullName = String.Format("{0}{1}, Version={2}, Culture=neutral, PublicKeyToken=null", x, suffix, name.Version.ToString());
  337. var assemblyLocation = Assembly.GetExecutingAssembly().Location;
  338. if (String.IsNullOrEmpty(assemblyLocation))
  339. return Enumerable.Empty<string>();
  340. var path = Path.Combine(Path.GetDirectoryName(assemblyLocation), x + suffix + ".dll");
  341. if (!File.Exists(path) && !RxApp.InUnitTestRunner()) {
  342. LogHost.Default.Debug("Couldn't find {0}", path);
  343. return Enumerable.Empty<string>();
  344. }
  345. try {
  346. Assembly.Load(fullName);
  347. return new[] {x};
  348. } catch (Exception ex) {
  349. LogHost.Default.DebugException("Couldn't load " + x, ex);
  350. return Enumerable.Empty<string>();
  351. }
  352. });
  353. #endif
  354. }
  355. static string getArchSuffixForPath(string path)
  356. {
  357. var re = new Regex(@"(_[A-Za-z0-9]+)\.");
  358. var m = re.Match(Path.GetFileName(path));
  359. return m.Success ? m.Groups[1].Value : "";
  360. }
  361. }
  362. public class NullDefaultPropertyBindingProvider : IDefaultPropertyBindingProvider
  363. {
  364. public Tuple<string, int> GetPropertyForControl(object control)
  365. {
  366. return null;
  367. }
  368. }
  369. internal static class RealUnitTestDetector
  370. {
  371. public static bool InUnitTestRunner()
  372. {
  373. // XXX: This is hacky and evil, but I can't think of any better way
  374. // to do this
  375. string[] testAssemblies = new[] {
  376. "CSUNIT",
  377. "NUNIT",
  378. "XUNIT",
  379. "MBUNIT",
  380. "TESTDRIVEN",
  381. "QUALITYTOOLS.TIPS.UNITTEST.ADAPTER",
  382. "QUALITYTOOLS.UNITTESTING.SILVERLIGHT",
  383. "PEX",
  384. "MSBUILD",
  385. "NBEHAVE",
  386. "TESTPLATFORM",
  387. };
  388. string[] designEnvironments = new[] {
  389. "BLEND.EXE",
  390. "MONODEVELOP",
  391. "SHARPDEVELOP.EXE",
  392. };
  393. #if SILVERLIGHT
  394. // NB: Deployment.Current.Parts throws an exception when accessed in Blend
  395. try {
  396. var ret = Deployment.Current.Parts.Any(x =>
  397. testAssemblies.Any(name => x.Source.ToUpperInvariant().Contains(name)));
  398. if (ret) {
  399. return ret;
  400. }
  401. } catch(Exception) {
  402. return true;
  403. }
  404. try {
  405. if (Application.Current.RootVisual != null && System.ComponentModel.DesignerProperties.GetIsInDesignMode(Application.Current.RootVisual)) {
  406. return false;
  407. }
  408. } catch {
  409. return true;
  410. }
  411. return false;
  412. #elif WINRT
  413. if (DesignMode.DesignModeEnabled) return true;
  414. var depPackages = Package.Current.Dependencies.Select(x => x.Id.FullName);
  415. if (depPackages.Any(x => testAssemblies.Any(name => x.ToUpperInvariant().Contains(name)))) return true;
  416. var fileTask = Task.Factory.StartNew(async () =>
  417. {
  418. var files = await Package.Current.InstalledLocation.GetFilesAsync();
  419. return files.Select(x => x.Path).ToArray();
  420. }, TaskCreationOptions.HideScheduler).Unwrap();
  421. return fileTask.Result.Any(x => testAssemblies.Any(name => x.ToUpperInvariant().Contains(name)));
  422. #else
  423. // Try to detect whether we're in design mode - bonus points,
  424. // without access to any WPF references :-/
  425. var entry = Assembly.GetEntryAssembly();
  426. var exeName = (entry != null ? entry.Location.ToUpperInvariant() : "");
  427. if (designEnvironments.Any(x => x.Contains(exeName))) {
  428. return true;
  429. }
  430. return AppDomain.CurrentDomain.GetAssemblies().Any(x =>
  431. testAssemblies.Any(name => x.FullName.ToUpperInvariant().Contains(name)));
  432. #endif
  433. }
  434. }
  435. }
  436. // vim: tw=120 ts=4 sw=4 et :