My main ASP.NET MVC composite application uses a global Unity container to register types. The app sets the controller factory up to use this global container. I would like to refactor this such that each one of my portable areas leverages it's own child Unity container, so that different areas can implement interfaces in varying ways. It seems like I would need to have a different ControllerFactory per area. How would I accomplish that, given that the following sets the factory for all?
ControllerBuilder.Current
.SetControllerFactory(/* controller factory with global unity container */);
You can use a MasterControllerFactory that contains all the IControllerFactory implementations for each area, and knows which factory can build which RequestContext. This actually allows you to select a different ControllerFactory for any variation, not just by area. Here's how it works:
All of the area controller factory implementations must implement IFilteredControllerFactory instead of IControllerFactory. Here it is:
public interface IFilteredControllerFactory:IControllerFactory
{
bool CanHandle(RequestContext requestContext);
}
An example of an implementation that filters based on the area name looks like this:
public class Area51ControllerFactory:IFilteredControllerFactory
{
public bool CanHandle(RequestContext requestContext)
{
return requestContext.RouteData.DataTokens["area"].ToString().ToLowerInvariant() == "area51";
}
public IController CreateController(RequestContext requestContext, string controllerName)
{
// create a controller...
}
public void ReleaseController(IController controller)
{
// release the controller...
}
}
Then you need the MasterControllerFactory, which looks like this:
public class MasterControllerFactory : DefaultControllerFactory
{
private readonly List<IFilteredControllerFactory> _slaveFactories;
public MasterControllerFactory()
{
_slaveFactories = new List<IFilteredControllerFactory>();
}
public void RegisterFactory(IFilteredControllerFactory slaveFactory)
{
if(slaveFactory!=null && !_slaveFactories.Contains(slaveFactory))
{
_slaveFactories.Add(slaveFactory);
}
}
public override IController CreateController(RequestContext requestContext, string controllerName)
{
var factory = _slaveFactories.FirstOrDefault(x => x.CanHandle(requestContext));
if(factory!=null)
{
return factory.CreateController(requestContext, controllerName);
}
return base.CreateController(requestContext, controllerName);
}
public override void ReleaseController(IController controller)
{
bool released = false;
if (controller is Controller)
{
var requestContext = ((Controller) controller).ControllerContext.RequestContext;
var factory = _slaveFactories.FirstOrDefault(x => x.CanHandle(requestContext));
if (factory != null)
{
factory.ReleaseController(controller);
released = true;
}
}
if(!released)base.ReleaseController(controller);
}
}
In the Application_Start of your global.asax you still need to set everything up, but that's easy.
var masterControllerFactory = new MasterControllerFactory();
masterControllerFactory.Register(new Area51ControllerFactory());
ControllerBuilder.Current.SetControllerFactory(masterControllerFactory);
Obviously, you can tweak this a number of ways to work best with your coding style and application architecture.
I don't think you can have different factory per area, but you can in your single factory, put the controller into different container, based on the area. I think but not sure, that you can find your area in CreateController method of your factory by querying this:
requestContext.RouteData.DataTokens["area"];
where requestContext is passed to CreateController as a parameter.
Related
I have a custom ASP.NET MVC controller that retrieves operations from the user service. I want to pass the operations property to the scenario service using dependency injection.
public abstract class BaseController : Controller {
protected IUserService userService;
public OperationWrapper operations { get; private set; }
public BaseController(IUserService userService) {
this.userService = userService;
this.operations = userService.GetOperations(HttpContext.Current.User.Identity.Name);
}
}
public abstract class ScenarioController : BaseController {
protected IScenarioService scenarioService;
public ScenarioController(IScenarioService scenarioService, IUserService userService)
: base(userService) {
this.scenarioService = scenarioService;
}
}
public class ScenarioService : IScenarioService {
private OperationWrapper operations;
public ScenarioService(OperationWrapper operations) {
this.repo = repo;
this.operations = operations;
}
}
Here is my Windsor installer.
public class Installer : IWindsorInstaller {
public void Install(IWindsorContainer container, IConfigurationStore store) {
container.Register(Classes.FromThisAssembly()
.BasedOn<IController>());
container.Register(Classes.FromThisAssembly()
.Where(x => x.Name.EndsWith("Service"))
.WithService.DefaultInterfaces()
.LifestyleTransient());
}
}
I pretty sure I've done something similar with Ninject a couple of years back. What do I need to add to the installer in order to make this work? Is it even possible?
There are a few of options here:
1. Use LifeStylePerWebRequest() and UsingFactoryMethod()
First, you could register an OperationWrapper as LifestylePerWebRequest() and inject it into both the BaseController and ScenarioService. Windsor will let you register the dependency with a factory method for creating it, which can in turn call other services which have been registered.
container.Register(Component.For<OperationWrapper>()
.LifestylePerWebRequest()
.UsingFactoryMethod(kernel =>
{
var userService = kernel.Resolve<IUserService>();
try
{
return userService.GetOperations(
HttpContext.Current.User.Identity.Name);
}
finally
{
kernel.ReleaseComponent(userService);
}
}));
So, every time Windsor is asked for an OperationWrapper, it will run that call against an instance if IUserService, giving it the Name of the current User. By binding the lifestyle to LifestylePerWebRequest(), you can verify that each request will get its own instance of the OperationWrapper and it won't bleed across requests.
(The only edge case you'd run into is one where a user becomes authenticated mid-request and the OperationWrapper needs to be adjusted as a result. If that's a normal-path use case, this may need some re-thinking.)
Then, modify your base controller to take that registered object in as a dependency:
public abstract class BaseController : Controller {
protected IUserService userService;
protected OperationWrapper operations;
public BaseController(IUserService userService, OperationWrapper operations) {
this.userService = userService;
this.operations = operations;
}
}
2. Use Method Injection
It looks like OperationWrapper is some sort of context object, and those can sometimes be injected into the method instead of into the constructor.
For instance, if your method was:
int GetTransactionId() { /* use OperationWrapper property */ }
You could just modify the signature to look like:
int GetTransactionId(OperationWrapper operations) { /* use arg */ }
In this situation, it makes sense to use it if a small-ish subset of your service's methods use that dependency. If the majority (or totality) of methods need it, then you should probably go a different route.
3. Don't use DI for OperationWrapper at all
In situations where you have a highly-stateful contextual object (which it seems like your OperationWrapper is), it frequently just makes sense to have a property whose value gets passed around. Since the object is based on some current thread state and is accessible from everywhere in any subclassed Controller, it may be right to just keep the pattern you have.
If you can't answer the question "What am I unable to do with OperationWrapper now that DI is going to solve for me?" with anything but "use the pattern/container," this may be the option for this particular situation.
You should set dependency resolver in Application_Start method of global.asax
System.Web.MVC.DependencyResolver.SetResolver(your windsor resolver)
Create a class that inherits from DefaultControllerFactory. Something like this will do:
public class WindsorControllerFactory : DefaultControllerFactory
{
public WindsorControllerFactory(IKernel kernel)
{
_kernel = kernel;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(
404,
String.Format(
CultureInfo.CurrentCulture,
"The controller for path '{0}' was not found or does not implement IController.",
requestContext.HttpContext.Request.Path
)
);
}
return (IController)_kernel.Resolve(controllerType);
}
public override void ReleaseController(IController controller)
{
Kernel.ReleaseComponent(controller);
}
private readonly IKernel _kernel;
private IKernel Kernel
{
get { return _kernel; }
}
}
In the Application_Start method of your MvcApplication class add the following:
var container = new WindsorContainer();
container.Install(FromAssembly.This());
ControllerBuilder.Current.SetControllerFactory(
new WindsorControllerFactory(container.Kernel)
);
This should work with your existing installer and get you to the point where Windsor will start resolving your dependencies for you. You might have to fill-in a few gaps, but you'll get the point.
I've borrowed heavily from: https://github.com/castleproject/Windsor/blob/master/docs/mvc-tutorial-intro.md
Be wary of using IDependencyResolver as it doesn't make provision for releasing what's resolved.
If I want to implement a Dependency Injection for ASP.NET MVC controllers, I can use IControllerFactory interface to create a factory class and then register it in Global.asax.cs, like:
ControllerBuilder.Current.SetControllerFactory(controllerFactory)
(reference: http://www.dotnetcurry.com/ShowArticle.aspx?ID=786)
Now my question is:
How could I set a factory for an IHttpControllerActivator derived class?
Do we have something in ASP.NET MVC 4.0 like:
HttpControllerBuilder.Current.SetApiControllerActivator(httpControllerActivator)
?
Update:
I want to add my current code, it might be helpful to understand the case:
My customized HttpControllerActivator:
public class MyCustomApiActivator : DefaultHttpControllerActivator
//or IHttpControllerActivator ?
{
private readonly Dictionary<string, Func<HttpRequestMessage, IHttpController>> _apiMap;
public MyCustomApiActivator(IMyRepository repository)
{
if (repository == null)
{
throw new ArgumentException("repository");
}
_apiMap = new Dictionary<string, Func<HttpRequestMessage, IHttpController>>();
controllerMap["Home"] = context => new HomeController();
_apiMap["MyCustomApi"] = context => new MyCustomApiController(repository);
}
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
if(_apiMap.ContainsKey(controllerType.Name))
return _apiMap[controllerType.Name](request);
return null;
}
}
My composition root:
public class CompositionRoot
{
private readonly IHttpControllerActivator _apiControllerActivator;
public CompositionRoot()
{
_apiControllerActivator = CompositionRoot.CreateHttpControllerActivator();
}
public IHttpControllerActivator ApiControllerActivator
{
get { return _apiControllerActivator; }
}
private static IHttpControllerActivator CreateHttpControllerActivator()
{
string defaultRepositoryTypeName = ConfigurationManager.AppSettings["DefaultRepositoryTypeName"];
var defaultRepositoryType = Type.GetType(defaultRepositoryTypeName, true);
var defaultRepository = (IMyRepository)Activator.CreateInstance(defaultRepositoryType);
var apiController = new MyCustomApiActivator(defaultRepository);
return apiController;
}
}
Finally this is inside my Global.asax.cs, where I need a trick:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
var root = new CompositionRoot();
//The following line raise this compile-time error:
// cannot convert from 'System.Web.Http.Dispatcher.IHttpControllerActivator'
// to 'System.Web.Mvc.IControllerFactory'
ControllerBuilder.Current.SetControllerFactory(root.ApiControllerActivator);
}
Mark Seemann, author of Dependency Injection in .NET, has a short series on DI with ASP.NET WebAPI.
He places the composition root inside an IHttpControllerActivator
Dependency Injection and Lifetime Management with ASP.NET Web API
Dependency Injection in ASP.NET Web API with Castle Windsor
Maybe that helps.
Update
GlobalConfiguration.Configuration.Services.Replace(
typeof(IHttpControllerActivator),
new PoorMansCompositionRoot());
Registers your custom HttpControllerActivator globally.
In ASP.NET Web API HttpControllerDispatcher is responsible for constructing controllers. It works with the DependecyResolver by default but if you want to extend its functionality, you need to override its SendAsync method and register it again.
Example:
public class MyCustomDispatcher : HttpControllerDispatcher {
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken) {
// Do your stuff here
// According to your requirements, either run its default functionality
// or return your own stuff
return base.SendAsync(request, cancellationToken);
}
}
Note:
However, I wasn't able to find an elegant way to replace the default one
globally. You can replace the default one per-route easily by
attaching this custom dispatcher to a route but there seems to be no
way to do this globally. I opened up a discussion on that at the
project site: http://aspnetwebstack.codeplex.com/discussions/400366
I need to setup a policy in base controller that applies to all controller instance, like below:
public class BaseController : Controller
{
private IPolicy Policy;
public BaseController()
{
this.Policy= new Policy(HttpContext);
}
}
Within the Policy class, I need to do something like:
this.httpContextBase.User.
Questions: (Update)
What is the better way to design the BaseController in terms of using HttpContext and Unit test.
What is the correct way to unit test HttpContext?
Absolutely no way. You are using the HttpContext inside the constructor of a controller when this context is still not initialized. Not only that this code cannot be tested but when you run the application it will also crash with NRE. You should never use any HttpContext related stuff in a constructor of a controller.
One possibility is to refactor your code and perform this inside the Initialize method:
public class BaseController : Controller
{
private IPolicy Policy;
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
this.Policy = new Policy(HttpContext);
}
}
This being said, that's not the approach I would recommend. I would recommend you using dependency injection instead of service location which is considered by many as an anti-pattern.
So:
public abstract class BaseController : Controller
{
protected IPolicy Policy { get; private set; }
protected BaseController(IPolicy policy)
{
Policy = policy;
}
}
Now, all that's left is to configure your favourite Dependency Injection framework to inject the correct instance into the constructor. For example with Ninject.Mvc3 this is achieved with a single line of code:
kernel.Bind<IPolicy>().To<Policy>();
Now you can feel more than free to mock this IPolicy in your unit test without even caring about any HttpContext.
For example let's suppose that you have the following controller that you want to unit test:
public class FooController : BaseController
{
public FooController(IPolicy policy): base(policy)
{ }
[Authorize]
public ActionResult Index()
{
Policy.DoSomething();
return View();
}
}
Now, all that you need to do is pick up your favorite mock framework (Rhino Mocks in my case) and do the mocking:
[TestMethod]
public void Index_Action_Should_DoSomething_With_The_Policy()
{
// arrange
var policyStub = MockRepository.GenerateStub<IPolicy>();
var sut = new FooController(policyStub);
// act
var actual = sut.Index();
// assert
Assert.IsInstanceOfType(actual, typeof(ViewResult));
policyStub.AssertWasCalled(x => x.DoSomething());
}
In the past I've stuck common properties, such as the current user, onto ViewData/ViewBag in a global fashion by having all Controllers inherit from a common base controller.
This allowed my to use IoC on the base controller and not just reach out into global shared for such data.
I'm wondering if there is an alternate way of inserting this kind of code into the MVC pipeline?
The best way is using the ActionFilterAttribute. I'll show you how to use it in .Net Core and .Net Framework.
.Net Core 2.1 & 3.1
public class ViewBagActionFilter : ActionFilterAttribute
{
public ViewBagActionFilter(IOptions<Settings> settings){
//DI will inject what you need here
}
public override void OnResultExecuting(ResultExecutingContext context)
{
// for razor pages
if (context.Controller is PageModel)
{
var controller = context.Controller as PageModel;
controller.ViewData.Add("Avatar", $"~/avatar/empty.png");
// or
controller.ViewBag.Avatar = $"~/avatar/empty.png";
//also you have access to the httpcontext & route in controller.HttpContext & controller.RouteData
}
// for Razor Views
if (context.Controller is Controller)
{
var controller = context.Controller as Controller;
controller.ViewData.Add("Avatar", $"~/avatar/empty.png");
// or
controller.ViewBag.Avatar = $"~/avatar/empty.png";
//also you have access to the httpcontext & route in controller.HttpContext & controller.RouteData
}
base.OnResultExecuting(context);
}
}
Then you need to register this in your startup.cs.
.Net Core 3.1
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options => {
options.Filters.Add<Components.ViewBagActionFilter>();
});
}
.Net Core 2.1
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add<Configs.ViewBagActionFilter>();
});
}
Then you can use it in all views and pages
#ViewData["Avatar"]
#ViewBag.Avatar
.Net Framework (ASP.NET MVC .Net Framework)
public class UserProfilePictureActionFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.Controller.ViewBag.IsAuthenticated = MembershipService.IsAuthenticated;
filterContext.Controller.ViewBag.IsAdmin = MembershipService.IsAdmin;
var userProfile = MembershipService.GetCurrentUserProfile();
if (userProfile != null)
{
filterContext.Controller.ViewBag.Avatar = userProfile.Picture;
}
}
}
register your custom class in the global. asax (Application_Start)
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalFilters.Filters.Add(new UserProfilePictureActionFilter(), 0);
}
Then you can use it in all views
#ViewBag.IsAdmin
#ViewBag.IsAuthenticated
#ViewBag.Avatar
Also there is another way
Creating an extension method on HtmlHelper
[Extension()]
public string MyTest(System.Web.Mvc.HtmlHelper htmlHelper)
{
return "This is a test";
}
Then you can use it in all views
#Html.MyTest()
Since ViewBag properties are, by definition, tied to the view presentation and any light view logic that may be necessary, I'd create a base WebViewPage and set the properties on page initialization. It's very similar to the concept of a base controller for repeated logic and common functionality, but for your views:
public abstract class ApplicationViewPage<T> : WebViewPage<T>
{
protected override void InitializePage()
{
SetViewBagDefaultProperties();
base.InitializePage();
}
private void SetViewBagDefaultProperties()
{
ViewBag.GlobalProperty = "MyValue";
}
}
And then in \Views\Web.config, set the pageBaseType property:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="MyNamespace.ApplicationViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
</system.web.webPages.razor>
Un-tried by me, but you might look at registering your views and then setting the view data during the activation process.
Because views are registered on-the-fly, the registration syntax doesn't help you with connecting to the Activated event, so you'd need to set it up in a Module:
class SetViewBagItemsModule : Module
{
protected override void AttachToComponentRegistration(
IComponentRegistration registration,
IComponentRegistry registry)
{
if (typeof(WebViewPage).IsAssignableFrom(registration.Activator.LimitType))
{
registration.Activated += (s, e) => {
((WebViewPage)e.Instance).ViewBag.Global = "global";
};
}
}
}
This might be one of those "only tool's a hammer"-type suggestions from me; there may be simpler MVC-enabled ways to get at it.
Edit: Alternate, less code approach - just attach to the Controller
public class SetViewBagItemsModule: Module
{
protected override void AttachToComponentRegistration(IComponentRegistry cr,
IComponentRegistration reg)
{
Type limitType = reg.Activator.LimitType;
if (typeof(Controller).IsAssignableFrom(limitType))
{
registration.Activated += (s, e) =>
{
dynamic viewBag = ((Controller)e.Instance).ViewBag;
viewBag.Config = e.Context.Resolve<Config>();
viewBag.Identity = e.Context.Resolve<IIdentity>();
};
}
}
}
Edit 2: Another approach that works directly from the controller registration code:
builder.RegisterControllers(asm)
.OnActivated(e => {
dynamic viewBag = ((Controller)e.Instance).ViewBag;
viewBag.Config = e.Context.Resolve<Config>();
viewBag.Identity = e.Context.Resolve<IIdentity>();
});
Brandon's post is right on the money. As a matter of fact, I would take this a step further and say that you should just add your common objects as properties of the base WebViewPage so you don't have to cast items from the ViewBag in every single View. I do my CurrentUser setup this way.
You could use a custom ActionResult:
public class GlobalView : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
context.Controller.ViewData["Global"] = "global";
}
}
Or even a ActionFilter:
public class GlobalView : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = new ViewResult() {ViewData = new ViewDataDictionary()};
base.OnActionExecuting(filterContext);
}
}
Had an MVC 2 project open but both techniques still apply with minor changes.
You don't have to mess with actions or change the model, just use a base controller and cast the existing controller from the layout viewcontext.
Create a base controller with the desired common data (title/page/location etc) and action initialization...
public abstract class _BaseController:Controller {
public Int32 MyCommonValue { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
MyCommonValue = 12345;
base.OnActionExecuting(filterContext);
}
}
Make sure every controller uses the base controller...
public class UserController:_BaseController {...
Cast the existing base controller from the view context in your _Layout.cshml page...
#{
var myController = (_BaseController)ViewContext.Controller;
}
Now you can refer to values in your base controller from your layout page.
#myController.MyCommonValue
If you want compile time checking and intellisense for the properties in your views then the ViewBag isn't the way to go.
Consider a BaseViewModel class and have your other view models inherit from this class, eg:
Base ViewModel
public class BaseViewModel
{
public bool IsAdmin { get; set; }
public BaseViewModel(IUserService userService)
{
IsAdmin = userService.IsAdmin;
}
}
View specific ViewModel
public class WidgetViewModel : BaseViewModel
{
public string WidgetName { get; set;}
}
Now view code can access the property directly in the view
<p>Is Admin: #Model.IsAdmin</p>
I have found the following approach to be the most efficient and gives excellent control utilizing the _ViewStart.chtml file and conditional statements when necessary:
_ViewStart:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
var CurrentView = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue.ToString();
if (CurrentView == "ViewA" || CurrentView == "ViewB" || CurrentView == "ViewC")
{
PageData["Profile"] = db.GetUserAccessProfile();
}
}
ViewA:
#{
var UserProfile= PageData["Profile"] as List<string>;
}
Note:
PageData will work perfectly in Views; however, in the case of a
PartialView, it will need to be passed from the View to
the child Partial.
I implemented the ActionFilterAttribute solution from #Mohammad Karimi. It worked well as I had the same scenario as the OP. I needed to add data to every view. The action filter attribute was executed for every Razor page request, but it was also called for every web API controller request.
Razor Pages offers a page filter attribute to avoid unnecessary execution of the action filter when a web API controller request is made.
Razor Page filters IPageFilter and IAsyncPageFilter allow Razor Pages to run code before and after a Razor Page handler is run.
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
namespace MyProject
{
// learn.microsoft.com/en-us/aspnet/core/razor-pages/filter?view=aspnetcore-6.0
// "The following code implements the synchronous IPageFilter"
// Enable the page filter using 'services.AddRazorPages().AddMvcOptions( ... )
// in the 'ConfigureServices()' startup method.
public class ViewDataPageFilter : IPageFilter
{
private readonly IConfiguration _config;
public ViewDataPageFilter(IConfiguration config)
{
_config = config;
}
// "Called after a handler method has been selected,
// but before model binding occurs."
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
}
// "Called before the handler method executes,
// after model binding is complete."
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
PageModel page = context.HandlerInstance as PageModel;
if (page == null) { return; }
page.ViewData["cdn"] = _config["cdn:url"];
}
// "Called after the handler method executes,
// before the action result."
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
}
}
}
As per the sample in the filter methods for Razor Pages documentation, the page filter is enabled by:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new ViewDataPageFilter(Configuration));
});
}
I am starting to dabble with ASP.Net MVC. One question I have is on best practices for protecting user data. For example in the scenario of Sales people, they should only be able to view their own data.
e.g.
SalesData/Edit/14
It is very easy to change the "14" to view other data which they may/or may not have access to.
At this point, I am thinking in my controllers to check for who is logged in, and checking if they have access to the "id" that is getting requested. The problem I see with this, is that this will be application wide, and I am looking for best practices on how to approach this. Should I be looking at CustomControllers? Filters? or what? Any articles/references for how to tackle this would be appreciated.
Set up your methods for retrieving data from your database repository in such a way that you can pass the UserID of the currently logged in person as a parameter. You can then use a permissions table to filter the data to only that data for which the user has access.
The permissions table would have two fields: UserID and ContentID. Once this is set up, it's fairly straightforward to set up CRUD screens so that someone with administrative privileges can set content permissions.
The problem I see with this, is that
this will be application wide,
Then you need common service that handles it. Suprisingly, I would call it IAuthorisationService.
and I
am looking for best practices on how
to approach this. Should I be looking
at CustomControllers? Filters? or
what?
Whichever way you choose you should use common IAuthorisationService above.
From my experience I can tell that it is easier to inject the service into controller and use it on every action:
/* Interfaces */
public interface IAuthorisationService {
bool CanEdit(YourItem item);
}
public interface ICurrentUserProvider {
YourUserEntity GetCurrentUser();
}
/* Implementations */
public class HttpUserProvider : ICurrentUserProvider {
public YourUserEntity GetCurrentUser() {
return HttpContext.Current.User.Principal as YourUserEntity;
}
}
public calss RolesAuthorisationService : IAuthorisationService {
ICurrentUserProvider userProvider
public RolesAuthorisationService(ICurrentUserProvider userProvider) {
this.userProvider = userProvider;
}
public bool CanEdit(YourItem item) {
var u = userProvider.GetCurrentUser();
if (u == null)
return false;
return item.Owner == u && u.IsInRole("EditYourItem");
}
}
/* Controller */
public class MyController: Controller {
IAuthorisationService authorisation;
public MyController(IAuthorisationService authorisation) {
this.authorisation = authorisation;
}
public ActionResult Edit(int id) {
var item = GetTheItembyIdSomehow();
if (!authorisation.CanEdit(item))
return new HttpUnauthorizedResult();
// Can do this
}
}
Then you can use ControllerFactory to inject the required dependencies automatically into the controllers:
class DependencyInjectionContainer : WindsorContainer {
public DependencyInjectionContainer() {
RegisterDependencies();
}
private void RegisterDependencies() {
// Services
Register(
AllTypes.Of<IDiscoverableService>()
.FromAssembly(typeof(IDiscoverableService).Assembly)
.Configure(c => c.LifeStyle.Transient)
.WithService.FromInterface()
);
// Controllers
Register(
AllTypes.Of<IController>()
.FromAssembly(typeof(DependencyInjectionContainer).Assembly)
.Configure(c => c.LifeStyle.Transient)
);
}
}
class WindsorControllerFactory : DefaultControllerFactory, IDisposable {
private readonly IWindsorContainer container;
public WindsorControllerFactory() {
container = new DependencyInjectionContainer();
}
protected override IController GetControllerInstance(Type controllerType) {
if (controllerType == null)
return base.GetControllerInstance(controllerType);
return (IController) container.Resolve(controllerType);
}
public void Dispose() {
container.Dispose();
}
}
I use IPrincipal and Authorize(Roles='...') attribute to limit access to actions. IPrincipal is then injected into service layer and user IIdentity is used to filter data.
Example: Users create tasks. Every user can see his tasks. GetTask(int taskId) method first filters by CreatedBy field using identifier from IIdentity and then takes task with specified id. If user doesn't have access to data, method will not return any rows.