📜 ⬆️ ⬇️

ASP.NET MVC, WebApi, SignalR and UnityContainer

It is known that all good Jedi use dependency injection ( translation ) in their projects; this increases the concentration of midichlorians in the blood and the testability of the code in the application. In this article I want to consider some aspects of using UnityContainer in an ASP.NET application, namely, the use of dependency injection through controller constructors in ASP.NET MVC and WebApi and hubs in SignalR. An example application is present.

Dependency Injection - Golf analogy


Theory


Suppose that we have an open universal repository interface:
')
public interface IRepository<T> : IDisposable { IEnumerable<string> GetData(); } 

And we really want to get an instance of this repository in the controller, but with a specific type already. I chose this example specifically, since this principle is often used when working with EntityFramework. In addition, it allows you to demonstrate the difficulties that can be encountered when trying to get this type in the controller. Consider the possible options.

Using the Service Locator

In this case, we simply store the container instance in a static field and, if necessary, obtain the required objects from it.

  public static class Global { public static readonly IUnityContainer ServiceLocator = new UnityContainer(); } 

We register a specific class somewhere in Global.asax when loading the application.

 Global.ServiceLocator.RegisterType(typeof(IRepository<>), typeof(Repository<>)); 

We get the repository in the controller.

  public ActionResult Index() { var repository = Global.ServiceLocator.Resolve<IRepository<int>>(); var data = repository.GetData(); return View(data); } 

Everything works just fine, and the amount of code is minimal. The problem is that now our controller depends on Global.ServiceLocator . This, of course, is not as scary as dependence on a particular class, but it can also decently poison life during testing, especially if tests are run in parallel in several threads, and each test wants to use its own repository implementation.

Using Dependency Resolver

ASP.NET MVC, WebApi and SignalR provide excellent opportunities for expansion, in particular, the possibility of replacing the standard DependencyResolvers used by the infrastructure to resolve dependencies. About how to do this, you can read the following articles:


In all cases, we need to create our own class that implements the necessary IDependencyResolver and register it with the appropriate infrastructure. I want to note that in each case, these interfaces have the same name, but are in different namespaces and have slightly different methods. But they have one thing in common: if the requested type cannot be resolved, then they return null . Or they are trying to find this type in DefaultDependencyResolver , in the case of SignalR, since it is in it that all components of its infrastructure are registered. Obviously, this behavior is fundamentally different from what the IUnitiContainer.Resolve() method IUnitiContainer.Resolve() , which in this case simply throws an exception.

It would seem, what's the problem? Use the IUnityContainer.IsRegistered() extension IUnityContainer.IsRegistered() and, if the type is not registered, return 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); } 

The above example is taken from the Unity.MVC3 implementation. His only problem is that in our example it will not work.

System.ArgumentException: The supplied type IRepository`1 doesn’t have the same type of argument as the target type Repository`1.

Well, let's try using the try/catch .

  public static T TryResolve<T>(this IUnityContainer container) { try { return container.Resolve<T>(); } catch (Exception) { return default(T); } } 

Even if we consider that managing the flow of execution through exceptions is a bad practice, this method has some performance problems.

Time comparsion with 100,000 iterations.
  1. Elapsed (Resolve when registered): 00.38940s
  2. Elapsed (TryResolve when registered): 00.37679s
  3. Elapsed (TryResolve when does't registered): 00.00393s
  4. Elapsed (Resolve with IsRegistered when registered): 01.00460s
  5. Elapsed (Resolve with IsRegistered when does't registered): 00.72297s
  6. Elapsed (Resolve with Exception when registered): 00.32938s
  7. Elapsed (Resolve with Exception when does't registered): 05.60332s

The first line shows the result of the test using a simple call to the Resolve method without any checks. In the second and third - the TryResolve method, which will be TryResolve below. In the fourth and fifth - check through IsRegistered() . In the last two, using a try/catch . It can be seen that the execution time in the third and seventh lines differs 1500 times. This, of course, may be imperceptible in a real application, but still, not pleasant.

In the depths of the UnityContainer method, the TryResolve method refers to the UnityContainer.registeredNames.registeredKeys field of type 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, , , .

Source: https://habr.com/ru/post/175161/


All Articles