理論
オープンでユニバーサルなリポジトリインターフェイスがあるとします。
public interface IRepository<T> : IDisposable { IEnumerable<string> GetData(); }
そして、コントローラでこのリポジトリのインスタンスを取得したいのですが、特定のタイプを使用します。 EntityFrameworkを操作する際に同様の原則がよく使用されるため、この例を意図的に選択しました。 さらに、コントローラーでこのタイプを取得しようとするときに遭遇する可能性のある困難を示すことができます。 可能なオプションを考えてみましょう。
Service Locatorを使用する
この場合、単純にコンテナインスタンスを静的フィールドに格納し、必要に応じてそこから必要なオブジェクトを取得します。
public static class Global { public static readonly IUnityContainer ServiceLocator = new UnityContainer(); }
アプリケーションをロードするときに、Global.asaxのどこかに特定のクラスを登録します。
Global.ServiceLocator.RegisterType(typeof(IRepository<>), typeof(Repository<>));
コントローラーでリポジトリーを取得します。
public ActionResult Index() { var repository = Global.ServiceLocator.Resolve<IRepository<int>>(); var data = repository.GetData(); return View(data); }
すべてがうまく機能し、コードの量は最小限です。 問題は、コントローラーが
Global.ServiceLocator
依存するようになったことです。 もちろん、これは特定のクラスに依存するほど怖いわけではありませんが、特に複数のスレッドで並行してテストが実行され、各テストが独自のリポジトリ実装を使用する場合は、テスト中にかなりの命を奪う可能性があります。
依存関係リゾルバーの使用
ASP.NET MVC、WebApi、およびSignalRは、優れた拡張オプションを提供します。特に、依存関係を解決するためにインフラストラクチャで使用される標準のDependencyResolverを置き換える機能があります。 これを行う方法については、次の記事を読むことができます。
いずれの場合も、必要な
IDependencyResolver
を実装する独自のクラスを作成し、適切なインフラストラクチャに登録する必要があります。 いずれの場合も、これらのインターフェイスは同じ名前ですが、異なる名前空間にあり、メソッドがわずかに異なることに注意してください。 ただし、共通点が1つあり
null
。要求された型を解決できない場合、
null
を返し
null
。 または、SignalRの場合、インフラストラクチャのすべてのコンポーネントが登録されているので、このタイプを
DefaultDependencyResolver
で見つけようとします。 明らかに、この動作は
IUnitiContainer.Resolve()
メソッドの動作とは根本的に異なります。この場合、単純に例外がスローされます。
問題は何ですか? 拡張メソッド
IUnityContainer.IsRegistered()
を使用し、型が登録されていない場合は
null
返し
null
。
public static T TryResolve<T>(this IUnityContainer container) { var isRegistered = true; var typeToCheck = typeof (T); if (typeToCheck.IsInterface || typeToCheck.IsAbstract) { isRegistered = container.IsRegistered(typeToCheck); if (!isRegistered && typeToCheck.IsGenericType) { var openGenericType = typeToCheck.GetGenericTypeDefinition(); isRegistered = container.IsRegistered(openGenericType); } } return isRegistered ? container.Resolve<T>() : default(T); }
上記の例はUnity.MVC3の実装から取られています。 彼の唯一の問題は、この例では機能しないことです。
System.ArgumentException:指定されたIRepository`1型には、ターゲット型Repository`1と同じ数のジェネリック引数がありません。
それでは、
try/catch
を使用して
try/catch
。
public static T TryResolve<T>(this IUnityContainer container) { try { return container.Resolve<T>(); } catch (Exception) { return default(T); } }
例外を介したスレッドフローの管理は悪い習慣であると考えても、この方法にはパフォーマンスの問題がわずかにあります。
100000回の繰り返しによる時間比較。
- 経過(登録時に解決):00.38940s
- 経過(登録時にTryResolve):00.37679s
- 経過(登録されていない場合はTryResolve):00.00393s
- 経過(登録時にIsRegisteredで解決):01.00460s
- 経過(登録されていない場合はIsRegisteredで解決):00.72297s
- 経過(登録時に例外で解決):00.32938s
- 経過(登録されていない場合は例外で解決):05.60332s
最初の行は、チェックなしで
Resolve
メソッドの単純な呼び出しを使用した場合のテスト結果を示しています。 2番目と3番目の
TryResolve
メソッドでは、以下で
TryResolve
ます。 4番目と5番目の
IsRegistered()
によるチェック。 最後の2つでは、
try/catch
を使用し
try/catch
。 3行目と7行目の実行時間が1,500回異なることがわかります。 もちろん、これは実際のアプリケーションでは見えないことがありますが、それでも快適ではありません。
UnityContainerの腸内で、TryResolveメソッドは、
Dictionary<Type, List> . , , null . , TryResolve , , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
UnityContainer.registeredNames.registeredKeys
フィールドにアクセスし
Dictionary<Type, List> . , , null . , TryResolve , , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .
Dictionary<Type, List> . , , null . , TryResolve
, , , , Linq
. , , , , . , , private
internal
. (expression trees) (reflection).
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using Microsoft.Practices.Unity; namespace CommonInfrastructure { public static class UnityExtensions { private static readonly Func<UnityContainer, Dictionary<Type, List<string>>> GetRegisteredKeys; private static readonly ResolverOverride[] EmptyResolverOverrides = new ResolverOverride[0]; static UnityExtensions() { // Get information about fileds. var unityType = typeof (UnityContainer); var registryType = Type.GetType("Microsoft.Practices.Unity.NamedTypesRegistry, " + unityType.Assembly.FullName); const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var registeredNamesInfo = unityType.GetField("registeredNames", flags); var registeredKeysInfo = registryType.GetField("registeredKeys", flags); // Create and compile expression to accessing the field // UnityContainer.registeredNames.registeredKeys of type Dictionary<Type, List<string>> var unityParam = Expression.Parameter(unityType); GetRegisteredKeys = Expression .Lambda<Func<UnityContainer, Dictionary<Type, List<string>>>>( Expression.Field( Expression.Field( unityParam, registeredNamesInfo), registeredKeysInfo), unityParam) .Compile(); } ....
internal
NamedTypesRegistry
, . FieldInfo
. Func<UnityContainer, Dictionary<Type, List>> GetRegisteredKeys = container => container.registeredNames.registeredKeys;.
.... /// <summary> /// Try to resolve an instance of requested type <typeparamref name="T" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static T TryResolve<T>(this IUnityContainer container) where T : class { return (T) TryResolve(container, typeof (T)); } /// <summary> /// Try to resolve an instance of requested <paramref name="type" /> without name. /// If type is interface or abstract class and it is't registered then return null. /// </summary> public static object TryResolve(this IUnityContainer container, Type type) { bool resolve; if (type.IsInterface || type.IsAbstract) { // Get the dictionary with registered types and names. var keys = GetRegisteredKeys((UnityContainer) container); // Check if interface or abstract type is registered in the container. resolve = IsRegistered(type, keys); // If type still is't registered and it's generic type then check if generic type definition is registered. if (!resolve && type.IsGenericType) { var openGenericType = type.GetGenericTypeDefinition(); resolve = IsRegistered(openGenericType, keys); } } else { // If type can be created then resolve it immediately. resolve = true; } // If type is registered then resolve it or return null if nothing was found. return resolve ? container.Resolve(type, null, EmptyResolverOverrides) : null; } private static bool IsRegistered(Type type, Dictionary<Type, List<string>> keys) { List<string> names; if (keys.TryGetValue(type, out names)) { // By default type without name is registered with null string. if (names.Exists(name => ReferenceEquals(name, null))) { return true; } } return false; } } }
, , . , , .
, , UnityContainer , , : Test if classes are registered in unity . , , .
ASP.NET MVC, WebApi SignalR I*Activator
. ASP.NET MVC System.Web.Mvc.IControllerActivator
, WebApi System.Web.Http.Dispatcher.IHttpControllerActivator
, SignalR Microsoft.AspNet.SignalR.Hubs.IHubActivator
. Create
, . .
ASP.NET MVC
using System; using System.Diagnostics; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityControllerActivator : IControllerActivator { private readonly IUnityContainer _container; public UnityControllerActivator(IUnityContainer container) { _container = container; } public IController Create(RequestContext requestContext, Type controllerType) { return (IController) _container.Resolve(controllerType); } } }
WebApi
using System; using System.Diagnostics; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHttpControllerActivator : IHttpControllerActivator { private readonly IUnityContainer _container; public UnityHttpControllerActivator(IUnityContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController) _container.Resolve(controllerType); } } }
SignalR
using System.Diagnostics; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; namespace WebApp.Infrastructure { public sealed class UnityHubActivator : IHubActivator { private readonly IUnityContainer _container; public UnityHubActivator(IUnityContainer container) { Debug.Assert(container != null, "container == null"); _container = container; } public IHub Create(HubDescriptor descriptor) { var hubType = descriptor.HubType; return hubType != null ? _container.Resolve(hubType) as IHub : null; } } }
.
using System.Web.Http; using System.Web.Http.Dispatcher; using System.Web.Mvc; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Microsoft.Practices.Unity; using WebApp.Infrastructure; using WebApp.Models; namespace WebApp.App_Start { public static class ContainerConfig { public static void Config() { var container = new UnityContainer(); MapTypes(container); // Set resolver to MVC. var controllerActivator = new UnityControllerActivator(container); ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(controllerActivator)); // Set resolver to WebApi. var httpControllerActivator = new UnityHttpControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof (IHttpControllerActivator), httpControllerActivator); // Set resolver to SignalR. var hubActivator = new UnityHubActivator(container); GlobalHost.DependencyResolver.Register(typeof (IHubActivator), () => hubActivator); } private static void MapTypes(IUnityContainer container) { container.RegisterType(typeof(IRepository<>), typeof(Repository<>)); } } }
, , Global.asax
. , SignalR .
using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using WebApp.App_Start; namespace WebApp { public class MvcApplication : HttpApplication { protected void Application_Start() { ContainerConfig.Config(); SignalRConfig.Config(RouteTable.Routes); AreaRegistration.RegisterAllAreas(); WebApiConfig.Config(GlobalConfiguration.Configuration); FilterConfig.Config(GlobalFilters.Filters); RouteConfig.Config(RouteTable.Routes); BundleConfig.Config(BundleTable.Bundles); AuthConfig.Config(); } } }
.
, . , GetData()
, , . , .
public sealed class Repository<T> : IRepository<T> { public IEnumerable<string> GetData() { for (int i = 0; i < 10; i++) { yield return string.Format("Data {0} of type {1}", i, typeof (T)); } } public void Dispose() { Debug.WriteLine("Repository<{0}>.Dispose", typeof (T)); } }
ASP.NET MVC WebApi Controllers, . , System.Web.Mvc.AuthorizeAttribute
System.Web.Http.AuthorizeAttribute
. ASP.NET MVC 4 Web Application - Single Page Application, , .
ASP.NET MVC
using System.Web.Mvc; using WebApp.Models; namespace WebApp.Controllers { [Authorize] public sealed class DataController : Controller { private readonly IRepository<int> _repository; public DataController(IRepository<int> repository) { _repository = repository; } // GET: /Data/ public ActionResult Index() { ViewBag.Title = "Data from DataController"; var data = _repository.GetData(); return View(data); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
WebApi
using System.Collections.Generic; using System.Web.Http; using WebApp.Filters; using WebApp.Models; namespace WebApp.Controllers { [Authorize] [ValidateHttpAntiForgeryToken] public sealed class DataWebApiController : ApiController { private readonly IRepository<string> _repository; public DataWebApiController(IRepository<string> repository) { _repository = repository; } // GET /api/DataWebApi/ public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
Hubs . Microsoft.AspNet.SignalR.AuthorizeAttribute
.
SignalR
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using WebApp.Models; namespace WebApp.Hubs { [Authorize] public sealed class DataHub : Hub { private readonly IRepository<double> _repository; public DataHub(IRepository<double> repository) { _repository = repository; } public IEnumerable<string> GetData() { return _repository.GetData(); } protected override void Dispose(bool disposing) { _repository.Dispose(); base.Dispose(disposing); } } }
. , .
TypeScript. TypeScript for Visual Studio 2012 , Web Essentials 2012 . , , Scripts/app
. NuGet : Twitter.Bootstrap , RequireJs , KnockoutJs , jQuery , SignalR , TypeScript Definitions . TypeScript. ReSharper, TypeScript, , "Hide overridden Visual Studio menu items" . AMD .
/scripts/app/app.ts
/// <reference path="../typings/knockout/knockout.d.ts" /> /// <reference path="../typings/jquery/jquery.d.ts" /> /// <reference path="../typings/signalr/signalr.d.ts" /> /** * Helper method to creating ajax requests with anti firgery token. * @param type Request type: get, post, delete, put. * @param url A string containing the URL to which the request is sent. * @param data Data to be sent to the server. Before sending serializes to Json format. * @param dataType The type of data that you're expecting back from the server. Default is Json. */ function ajaxRequest(type: string, url: string, data?: any, dataType: string = "json") { var headers = {}; var antiForgeryToken = $("#antiForgeryToken").val(); if (antiForgeryToken) { headers = { 'RequestVerificationToken': antiForgeryToken } } return $.ajax(url, { headers: headers, dataType: dataType, contentType: "application/json", cache: false, type: type, data: data ? data.toJson() : null }); } interface IDataHub { client: {}; server: { getData(): JQueryDeferred; }; } export class AppViewModel { private dataHub: IDataHub; /** If error occurred this field should contain error message. Otherwise it's empty string. */ errorMessage = ko.observable(""); /** Displays optional text. */ text = ko.observable(""); /** Data received from server. */ data = ko.observableArray([]); /** Create ViewModel and connect to hubs. */ constructor() { this.dataHub = $.connection["dataHub"]; $.connection.hub .start() .fail(e => this.onFail(e)); } /** Receive data from SignalR hub. */ signalrBtnClick() { this.text("SignalR"); this.dataHub.server .getData() .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from WebApi controller. */ apiBtnClick() { this.text("WebApi"); ajaxRequest("get", "/api/dataWebApi") .done(d => this.showData(d)) .fail(e => this.onFail(e)); } /** Receive data from static page controller. */ staticBtnClick() { ajaxRequest("get", "/Data", null, "html") .done(data => { this.data([]); this.errorMessage(""); this.text(data); }) .fail(e => this.onFail(e)); } private onFail(error: string) { this.text("Error!"); this.data([]); this.errorMessage(error); } private showData(data: string[]) { this.data(data); this.errorMessage(""); } }
, , DataHub
. , *BtnClick
, .
<div class="row"> <div class="span2"> <div class="btn-group btn-group-vertical" data-toggle="buttons-radio"> <button class="btn span2" data-bind="click: signalrBtnClick">SignalR</button> <button class="btn span2" data-bind="click: apiBtnClick">WebApi</button> <button class="btn span2" data-bind="click: staticBtnClick">Static page</button> </div> </div> <div class="span5"> <div data-bind="visible: errorMessage() != ''"> <span class="text-error" data-bind="text: errorMessage"></span> </div> <div class="well" data-bind="visible: text() != ''"> <p data-bind="html: text"></p> <ol data-bind="foreach: data"> <li><span data-bind="text: $data"></span></li> </ol> </div> </div> </div>
, - , require.js
. , ~/signalr/hubs
. init.ts
, RequireJs . , RequireJs .
<script src="~/signalr/hubs"> </script> <script type="text/javascript"> var require = { waitSeconds: 15, urlArgs: "bust=" + new Date().getTime() }; </script> <script data-main="/scripts/app/init" type="text/javascript" src="@Scripts.Url("~/Scripts/require.js")"> </script>
init.ts
. TypeScript "Use the AMD module" true "Tools"-"Options"-"Web Essentials"-"TypeScript"-"Compiler flags".
/scripts/app/init.ts
import app = module("app"); $(() => { ko.applyBindings(new app.AppViewModel()); });
export
, , . import
module
, . .
, ASP.NET . , , . TypeScript , NuGet TypeScript Definitions. , , , JetBrains TypeScript ReSharper . SignalR . , WebSockets Windows7, , , .