i am working with ASP.Net web API's.I have created a folder named api in controllers folder and then created an Api controller in that api folder.
In the admin controller i have simply placed the following code to check wheather the api is working or not.
public class AdminController : ApiController
{
DBEntities _context;
public AdminController()
{
_context = new DBEntities();
}
[HttpGet]
public IEnumerable<string> GetUsers()
{
return new string[] { "Muhammad","Ali"};
}
}
Then from browser i am calling http://localhost:57368/api/admin but it gives me "The resource cannot be found" with http 404 error code.It should atleast return JSON result but gives this error instead.
Any help is highly appreciated.
And when i go to network tab in chrome it shows the following details of the request:
protected void Application_Start()
{
GlobalConfiguration.Configure(config =>
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
});
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Your global.asax.cs should look like this
[RoutePrefix("api/admin")]
public class AdminController : ApiController
{
DBEntities _context;
public AdminController()
{
_context = new DBEntities();
}
[Route("GetUsers")]
[HttpGet]
public IEnumerable<string> GetUsers()
{
return new string[] { "Muhammad","Ali"};
}
}
URL: http://localhost:57368/api/admin/GetUsers
you should use 'RoutePrefix' and 'Route' attributes. API should know how to navigate the incoming request to an action. this attributes help you. don't forget add this namespace ' using System.Web.Http'.
using System.Web.Http
[RoutePrefix("api/admin")]
public class AdminController : ApiController
{
DBEntities _context;
public AdminController()
{
_context = new DBEntities();
}
[HttpGet]
[Route("GetUsers")]
public IEnumerable<string> GetUsers()
{
return new string[] { "Muhammad","Ali"};
}
}
In the Global.asax i registered Api using GlobalConfiguration.Configure(WebApiConfig.Register)
but issue was that i placed it at the bottom due to which MVC took precedence and when i called api/admin it gave error saying no resourse was found beacuse MVC was in action.
I added this GlobalConfiguration.Configure(WebApiConfig.Register) at the top and in ApplicationStart() in Gloabl.asax and it worked fine.
As a homework I have to do a simple URL shortener, where I can add full link to list, which is processed by Hashids.net library, and I get short version of an URL.
I've got something like this now, but I got stuck on redirecting it back to full link.
I would like to add a new controller, which will take the responsibility of redirecting short URL to full URL. After clicking short URL it should go to localhost:xxxx/ShortenedUrl and then redirect to full link. Any tips how can I create this?
I was trying to do it by #Html.ActionLink(#item.ShortenedLink, "Index", "Redirect") and return Redirect(fullLink) in Redirect controller but it didn't work as I expect.
And one more question about routes, how can I achieve that after clicking short URL it will give me localhost:XXXX/ShortenedURL (i.e. localhost:XXXX/FSIAOFJO2#). Now I've got
#Html.DisplayFor(model => item.ShortenedLink)
and
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Link}/{action=Index}");
});
but it gives me localhost:XXXX/Link/ShortenedURL, so I would like to omit this Link in URL.
View (part with Short URL):
<td>#Html.ActionLink(item.ShortenedLink,"GoToFull","Redirect", new { target = "_blank" }))</td>
Link controller:
public class LinkController : Controller
{
private ILinksRepository _repository;
public LinkController(ILinksRepository linksRepository)
{
_repository = linksRepository;
}
[HttpGet]
public IActionResult Index()
{
var links = _repository.GetLinks();
return View(links);
}
[HttpPost]
public IActionResult Create(Link link)
{
_repository.AddLink(link);
return Redirect("Index");
}
[HttpGet]
public IActionResult Delete(Link link)
{
_repository.DeleteLink(link);
return Redirect("Index");
}
}
Redirect controller which I am trying to do:
private ILinksRepository _repository;
public RedirectController(ILinksRepository linksRepository)
{
_repository = linksRepository;
}
public IActionResult GoToFull()
{
var links = _repository.GetLinks();
return Redirect(links[0].FullLink);
}
Is there a better way to get access to links list in Redirect Controller?
This is my suggestion, trigger the link via AJAX, here is working example:
This is the HTML element binded through model:
#Html.ActionLink(Model.ShortenedLink, "", "", null,
new { onclick = "fncTrigger('" + "http://www.google.com" + "');" })
This is the javascript ajax code:
function fncTrigger(id) {
$.ajax({
url: '#Url.Action("TestDirect", "Home")',
type: "GET",
data: { id: id },
success: function (e) {
},
error: function (err) {
alert(err);
},
});
}
Then on your controller to receive the ajax click:
public ActionResult TestDirect(string id)
{
return JavaScript("window.location = '" + id + "'");
}
Basically what I am doing here is that, after I click the link, it will call the TestDirect action, then redirect it to using the passed url parameter. You can do the conversion inside this action.
To create dynamic data-driven URLs, you need to create a custom IRouter. Here is how it can be done:
CachedRoute<TPrimaryKey>
This is a reusable generic class that maps a set of dynamically provided URLs to a single action method. You can inject an ICachedRouteDataProvider<TPrimaryKey> to provide the data (a URL to primary key mapping).
The data is cached to prevent multiple simultaneous requests from overloading the database (routes run on every request). The default cache time is for 15 minutes, but you can adjust as necessary for your requirements.
If you want it to act "immediate", you could build a more advanced cache that is updated just after a successful database update of one of the records. That is, the same action method would update both the database and the cache.
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class CachedRoute<TPrimaryKey> : IRouter
{
private readonly string _controller;
private readonly string _action;
private readonly ICachedRouteDataProvider<TPrimaryKey> _dataProvider;
private readonly IMemoryCache _cache;
private readonly IRouter _target;
private readonly string _cacheKey;
private object _lock = new object();
public CachedRoute(
string controller,
string action,
ICachedRouteDataProvider<TPrimaryKey> dataProvider,
IMemoryCache cache,
IRouter target)
{
if (string.IsNullOrWhiteSpace(controller))
throw new ArgumentNullException("controller");
if (string.IsNullOrWhiteSpace(action))
throw new ArgumentNullException("action");
if (dataProvider == null)
throw new ArgumentNullException("dataProvider");
if (cache == null)
throw new ArgumentNullException("cache");
if (target == null)
throw new ArgumentNullException("target");
_controller = controller;
_action = action;
_dataProvider = dataProvider;
_cache = cache;
_target = target;
// Set Defaults
CacheTimeoutInSeconds = 900;
_cacheKey = "__" + this.GetType().Name + "_GetPageList_" + _controller + "_" + _action;
}
public int CacheTimeoutInSeconds { get; set; }
public async Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
{
// Trim the leading slash
requestPath = requestPath.Substring(1);
}
// Get the page id that matches.
TPrimaryKey id;
//If this returns false, that means the URI did not match
if (!GetPageList().TryGetValue(requestPath, out id))
{
return;
}
//Invoke MVC controller/action
var routeData = context.RouteData;
// TODO: You might want to use the page object (from the database) to
// get both the controller and action, and possibly even an area.
// Alternatively, you could create a route for each table and hard-code
// this information.
routeData.Values["controller"] = _controller;
routeData.Values["action"] = _action;
// This will be the primary key of the database row.
// It might be an integer or a GUID.
routeData.Values["id"] = id;
await _target.RouteAsync(context);
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
VirtualPathData result = null;
string virtualPath;
if (TryFindMatch(GetPageList(), context.Values, out virtualPath))
{
result = new VirtualPathData(this, virtualPath);
}
return result;
}
private bool TryFindMatch(IDictionary<string, TPrimaryKey> pages, IDictionary<string, object> values, out string virtualPath)
{
virtualPath = string.Empty;
TPrimaryKey id;
object idObj;
object controller;
object action;
if (!values.TryGetValue("id", out idObj))
{
return false;
}
id = SafeConvert<TPrimaryKey>(idObj);
values.TryGetValue("controller", out controller);
values.TryGetValue("action", out action);
// The logic here should be the inverse of the logic in
// RouteAsync(). So, we match the same controller, action, and id.
// If we had additional route values there, we would take them all
// into consideration during this step.
if (action.Equals(_action) && controller.Equals(_controller))
{
// The 'OrDefault' case returns the default value of the type you're
// iterating over. For value types, it will be a new instance of that type.
// Since KeyValuePair<TKey, TValue> is a value type (i.e. a struct),
// the 'OrDefault' case will not result in a null-reference exception.
// Since TKey here is string, the .Key of that new instance will be null.
virtualPath = pages.FirstOrDefault(x => x.Value.Equals(id)).Key;
if (!string.IsNullOrEmpty(virtualPath))
{
return true;
}
}
return false;
}
private IDictionary<string, TPrimaryKey> GetPageList()
{
IDictionary<string, TPrimaryKey> pages;
if (!_cache.TryGetValue(_cacheKey, out pages))
{
// Only allow one thread to poplate the data
lock (_lock)
{
if (!_cache.TryGetValue(_cacheKey, out pages))
{
pages = _dataProvider.GetPageToIdMap();
_cache.Set(_cacheKey, pages,
new MemoryCacheEntryOptions()
{
Priority = CacheItemPriority.NeverRemove,
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(this.CacheTimeoutInSeconds)
});
}
}
}
return pages;
}
private static T SafeConvert<T>(object obj)
{
if (typeof(T).Equals(typeof(Guid)))
{
if (obj.GetType() == typeof(string))
{
return (T)(object)new Guid(obj.ToString());
}
return (T)(object)Guid.Empty;
}
return (T)Convert.ChangeType(obj, typeof(T));
}
}
LinkCachedRouteDataProvider
Here we have a simple service that retrieves the data from the database and loads it into a Dictionary. The most complicated part is the scope that needs to be setup in order to use DbContext from within the service.
public interface ICachedRouteDataProvider<TPrimaryKey>
{
IDictionary<string, TPrimaryKey> GetPageToIdMap();
}
public class LinkCachedRouteDataProvider : ICachedRouteDataProvider<int>
{
private readonly IServiceProvider serviceProvider;
public LinkCachedRouteDataProvider(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider
?? throw new ArgumentNullException(nameof(serviceProvider));
}
public IDictionary<string, int> GetPageToIdMap()
{
using (var scope = serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<ApplicationDbContext>();
return (from link in dbContext.Links
select new KeyValuePair<string, int>(
link.ShortenedLink.Trim('/'),
link.Id)
).ToDictionary(pair => pair.Key, pair => pair.Value);
}
}
}
RedirectController
Our redirect controller accepts the primary key as an id parameter and then looks up the database record to get the URL to redirect to.
public class RedirectController
{
private readonly ApplicationDbContext dbContext;
public RedirectController(ApplicationDbContext dbContext)
{
this.dbContext = dbContext
?? throw new ArgumentNullException(nameof(dbContext));
}
public IActionResult GoToFull(int id)
{
var link = dbContext.Links.FirstOrDefault(x => x.Id == id);
return new RedirectResult(link.FullLink);
}
}
In a production scenario, you would probably want to make this a permanent redirect return new RedirectResult(link.FullLink, true), but those are automatically cached by browsers which makes testing difficult.
Startup.cs
We setup the DbContext, the memory cache, and the LinkCachedRouteDataProvider in our DI container for use later.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
services.AddMemoryCache();
services.AddSingleton<LinkCachedRouteDataProvider>();
}
And then we setup our routing using the CachedRoute<TPrimaryKey>, providing all dependencies.
app.UseMvc(routes =>
{
routes.Routes.Add(new CachedRoute<int>(
controller: "Redirect",
action: "GoToFull",
dataProvider: app.ApplicationServices.GetService<LinkCachedRouteDataProvider>(),
cache: app.ApplicationServices.GetService<IMemoryCache>(),
target: routes.DefaultHandler)
// Set to 60 seconds of caching to make DB updates refresh quicker
{ CacheTimeoutInSeconds = 60 });
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
To build these short URLs on the user interface, you can use tag helpers (or HTML helpers) the same way you would with any other route:
<a asp-area="" asp-controller="Redirect" asp-action="GoToFull" asp-route-id="1">
#Url.Action("GoToFull", "Redirect", new { id = 1 })
</a>
Which is generated as:
/M81J1w0A
You can of course use a model to pass the id parameter into your view when it is generated.
<a asp-area="" asp-controller="Redirect" asp-action="GoToFull" asp-route-id="#Model.Id">
#Url.Action("GoToFull", "Redirect", new { id = Model.Id })
</a>
I have made a Demo on GitHub. If you enter the short URLs into the browser, they will be redirected to the long URLs.
M81J1w0A -> https://maps.google.com/
r33NW8K -> https://stackoverflow.com/
I didn't create any of the views to update the URLs in the database, but that type of thing is covered in several tutorials such as Get started with ASP.NET Core MVC and Entity Framework Core using Visual Studio, and it doesn't look like you are having issues with that part.
References:
Get started with ASP.NET Core MVC and Entity Framework Core using Visual Studio
Change route collection of MVC6 after startup
MVC Routing template to represent infinite self-referential hierarchical category structure
Imlementing a Custom IRouter in ASP.NET 5 (vNext) MVC 6
I have ASP.net Web Api project and I decided that it was time to support versioning. I am using official Microsoft Nuget to support versioning (more info here), and I decided to version by namespace (as exampled here).
Unfortunately I cannot get code to work. If I call my method like this:
http://localhost:7291/api/Saved/GetNumberOfSavedWorkoutsForUser?api-version=2.0
I get error:
Multiple types were found that match the controller named 'Saved'. This can happen if the route that services this request ('api/{controller}/{action}/{id}') found multiple controllers defined with the same name but differing namespaces, which is not supported.
And if I call it like this: http://localhost:7291/v2/Saved/GetNumberOfSavedWorkoutsForUser
I get error 404:
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
I am not sure what I am doing wrong. Here is my code:
Startup.cs
public void Configuration(IAppBuilder app)
{
var configuration = new HttpConfiguration();
var httpServer = new HttpServer(configuration);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
configuration.AddApiVersioning(o =>
{
o.AssumeDefaultVersionWhenUnspecified = true;
o.ReportApiVersions = true;
o.DefaultApiVersion = ApiVersion.Default;
});
configuration.Routes.MapHttpRoute(
"VersionedUrl",
"v{apiVersion}/{controller}/{action}/{id}",
defaults: null,
constraints: new { apiVersion = new ApiVersionRouteConstraint() });
configuration.Routes.MapHttpRoute(
"VersionedQueryString",
"api/{controller}/{action}/{id}",
defaults: null);
app.UseWebApi(httpServer);
ConfigureAuth(app);
}
Saved Controller (v1)
namespace Master.Infrastructure.Api.Controllers
{
[Authorize]
[RoutePrefix("api/Saved")]
[ApiVersion("1.0")]
public class SavedController : ApiController
{
private readonly IUserService _userService;
public SavedController(IUserService userService)
{
_userService = userService;
}
[HttpGet]
[ActionName("GetNumberOfSavedWorkouts")]
public async Task<NumberOfSavedWorkouts> GetNumberOfSavedWorkouts()
{
var numOfSavedWorkouts = new NumberOfSavedWorkouts
{
CurrentNumberOfSavedWorkouts =
await _userService.GetNumberOfSavedWorkoutsForUserAsync(User.Identity.GetUserId())
};
return numOfSavedWorkouts;
}
}
}
Saved Controller (v2)
namespace Master.Infrastructure.Api.V2.Controllers
{
[Authorize]
[ApiVersion("2.0")]
[RoutePrefix("v{version:apiVersion}/Saved")]
public class SavedController : ApiController
{
private readonly ISavedWorkoutService _savedWorkoutService;
public SavedController(ISavedWorkoutService savedWorkoutService)
{
_savedWorkoutService = savedWorkoutService;
}
[ActionName("GetNumberOfSavedWorkoutsForUser")]
public async Task<IHttpActionResult> GetNumberOfSavedWorkoutsForUser()
{
var cnt = await _savedWorkoutService.CountNumberOfSavedWorkoutsForUser(User.Identity.GetUserId());
return Ok(cnt);
}
}
}
Your routes are incorrect. I strongly discourage you from mixing routing styles unless you really need to. It can be very difficult to troubleshoot.
There are several things going on here:
You have configurations to version both by query string and URL segment, which one do you want? I would choose only one. The default and my recommendation would be to use the query string method.
Your convention-based route is different from the attribute-base route
Since you have RoutePrefixAttribute defined, it appears you prefer the attribute-routing style. I would remove all convention-based routes (ex: configuration.Routes.MapHttpRoute).
In your convention, the route template:
v{apiVersion}/{controller}/{action}/{id}
but in your attribute it's:
api/Saved
Neither of these will match your expected routes:
http://localhost:7291/api/Saved/GetNumberOfSavedWorkoutsForUser
http://localhost:7291/v2/Saved/GetNumberOfSavedWorkoutsForUser
For the query string method using route attributes, things should look like:
configuration.AddApiVersioning(o => o.ReportApiVersions = true);
namespace Master.Infrastructure.Api.Controllers
{
[Authorize]
[ApiVersion("1.0")]
[RoutePrefix("api/Saved")]
public class SavedController : ApiController
{
private readonly IUserService _userService;
public SavedController(IUserService userService) => _userService = userService;
[HttpGet]
[Route("GetNumberOfSavedWorkouts")]
public async Task<IHttpActionResult> GetNumberOfSavedWorkouts()
{
var userId = User.Identity.GetUserId();
var count = await _userService.GetNumberOfSavedWorkoutsForUserAsync(userId);
return Ok(new NumberOfSavedWorkouts(){ CurrentNumberOfSavedWorkouts = count });
}
}
}
namespace Master.Infrastructure.Api.V2.Controllers
{
[Authorize]
[ApiVersion("2.0")]
[RoutePrefix("api/Saved")]
public class SavedController : ApiController
{
private readonly ISavedWorkoutService _savedWorkoutService;
public SavedController(ISavedWorkoutService savedWorkoutService) => _savedWorkoutService = savedWorkoutService;
[HttpGet]
[Route("GetNumberOfSavedWorkoutsForUser")]
public async Task<IHttpActionResult> GetNumberOfSavedWorkoutsForUser()
{
var userId = User.Identity.GetUserId();
var count = await _savedWorkoutService.CountNumberOfSavedWorkoutsForUser(userId);
return Ok(count);
}
}
}
The following should then work:
http://localhost:7291/api/Saved/GetNumberOfSavedWorkouts?api-version=1.0
http://localhost:7291/api/Saved/GetNumberOfSavedWorkoutsForUser?api-version=2.0
I hope that help.s
I'm using NInject in my ASP.NET MVC application and I'm not 100% sure how the singleton is working when creating my Object context.
My Question is:
Using the code below will there be one
ObjectContext per user session or will
there be one that is share for the
entire application? I want each user
to have only one context at one time,
but each user must have their own
instance.
Is InRequestScope() something I should be considering?
I also do the same thing with a WCF service and I assume the answer will be same for both.
My Global.asax:
public class MvcApplication : NinjectHttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Change", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected override void OnApplicationStarted()
{
// Ninject Code
base.OnApplicationStarted();
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
protected override IKernel CreateKernel()
{
var modules = new INinjectModule[] { new ContextModule() };
return new StandardKernel(modules);
}
public class ContextModule : NinjectModule
{
public override void Load()
{
Bind<ObjectContext>().To<ChangeRoutingEntities>().InSingletonScope();
Bind<IObjectContext>().To<ObjectContextAdapter>();
Bind<IUnitOfWork>().To<UnitOfWork>();
}
}
}
ISingletonScope is an application wide scope.
InRequestScope is only for the current request.
You need a session scope. See http://iridescence.no/post/Session-Scoped-Bindings-With-Ninject-2.aspx for a way to implement this type of scope.
public static class NinjectSessionScopingExtention
{
public static void InSessionScope<T>(this IBindingInSyntax<T> parent)
{
parent.InScope(SessionScopeCallback);
}
private const string _sessionKey = "Ninject Session Scope Sync Root";
private static object SessionScopeCallback(IContext context)
{
if (HttpContext.Current.Session[_sessionKey] == null)
{
HttpContext.Current.Session[_sessionKey] = new object();
}
return HttpContext.Current.Session[_sessionKey];
}
}
I've just started playing with IoC containers and therefore chosed Ninject.
After several hours of sweat and tears I still cant figure out how to setup my MVC3 application with Ninject.
So far I have:
Global.asax.cs
public class MvcApplication : Ninject.Web.Mvc.NinjectHttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
DependencyResolver.SetResolver(new MyDependencyResolver(CreateKernel()));
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
protected override IKernel CreateKernel()
{
var modules = new [] { new ServiceModule() };
return new StandardKernel(modules);
}
}
ServiceModule.cs
internal class ServiceModule : NinjectModule
{
public override void Load()
{
Bind<IGreetingService>().To<GreetingService>();
}
}
MyDependencyResolver.cs
public class MyDependencyResolver : IDependencyResolver
{
private IKernel kernel;
public MyDependencyResolver(IKernel kernel)
{
this.kernel = kernel;
}
public object GetService(System.Type serviceType)
{
return kernel.TryGet(serviceType);
}
public System.Collections.Generic.IEnumerable<object> GetServices(System.Type serviceType)
{
return kernel.GetAll(serviceType);
}
}
GreetingService.cs
public interface IGreetingService
{
string Hello();
}
public class GreetingService : IGreetingService
{
public string Hello()
{
return "Hello from GreetingService";
}
}
TestController.cs
public class TestController : Controller
{
private readonly IGreetingService service;
public TestController(IGreetingService service)
{
this.service = service;
}
public ActionResult Index()
{
return View("Index", service.Hello());
}
}
Each time I try to load the Index view it either just throws a overflow exception or a HTTP 404 error - If I remove all the Ninject code it works perfectly, whats wrong?
You are mixing an own dependency resolver with the MVC extension. I'd suggest either going with your own dependency resolver or with using the MVC extension but not both. When using the MVC extension you have to use OnApplicationStarted instead of Application_Start.
See http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/ and have a look at the SampleApplication that comes with the source code of the MVC extension https://github.com/ninject/ninject.web.mvc.
Also the fix is not used anymore when you use the current version for the build server: http://teamcity.codebetter.com
UPDATE: The Ninject.MVC3 package continues to be updated and works OOTB against MVC4 RTM (and RC). See this page in the wiki for details.