MVC routing priority for shared views - c#

My solution has shared views with similar names in different folder locations
~\Views\Shared\Discount.ascx
~\Views\Dashboard\Shared\Discount.ascx
I'm extending WebFormViewEngine to define a view engine for routing
public class AreaViewEngine : WebFormViewEngine
{
public AreaViewEngine() : base()
{
ViewLocationFormats = new[] {
"~/Views/Shared/{0}.ascx",
"~/Views/Dashboard/Shared/{0}.ascx"
};
MasterLocationFormats = new[] {
"~/Shared/{0}.master"
};
PartialViewLocationFormats = ViewLocationFormats;
}
}
This is causing issues for views with similar names. I want to set higher priority to ~/Views/Dashboard/Shared/{0}.ascx if the URL contains /Dashboard/
Anyone knows how to do it? or is aware of a better way to handle this situation?

You could override the FindView method in your custom ViewEngine class, and look for a match containing the URL fragment before falling back to the default behavior.
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
var fragment = ... // access URL via controllerContext
var preferred = ViewLocationFormats.Where(x => x.Contains(fragment)).ToList();
// return retult from preferred or fall back to base.FindView
}
I'm not a big fan of this approach, though. If you need a view to be "overridable", you could simply make its output conditional or dependent upon model values, such as by adding a DiscountView property to your view model and then using the value of this property to decide what your view should emit.
If all you desire is "clever naming clash handling" then I'd recommend using T4MVC instead. It allows you to reference your views specifically and without magic strings, resolving any ambiguities that multiple views of the same name might otherwise cause.

Related

Where is the ViewResult returned by ASP.NET Core Controller consumed?

In the definition of the ASP.NET Core Controller class, it defines the View() method as returning a ViewResult object.
namespace Microsoft.AspNetCore.Mvc
{
//...
public abstract class Controller : ControllerBase, IActionFilter, IFilterMetadata, IAsyncActionFilter, IDisposable
{
//...
public virtual ViewResult View();
//...
}
}
Where is it in the framework that invokes a controller method such as below and consumes the ViewResult returned by the call to the View() method?
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
The entire process is quite involved, and is managed internally by the ResourceInvoker class, but the main piece of plumbing responsible for handling a ViewResult is the ViewExecutor class (source code).
The primary entry point of the ViewExecutor is the ExecuteAsync() method, which accepts the ActionContext and your ViewResult. It then locates the appropriate view using the registered IViewEngine (typically the RazorViewEngine) to identify the corresponding view and return a ViewEngineResult instance. That ViewEngineResult is then used to execute and render the view.
Extending ViewExecutor
If you need to customize the logic, you can implement your own view executor class by implementing the IActionResultExecutor<> interface and then registering it as a singleton using ASP.NET Core's dependency injection container. When doing so, I recommend referencing the out-of-the-box ViewResultExecutor's source code.
This can be useful if you want to implement custom logic for locating the view. For instance, perhaps you want to account for state or context data such as route data, request headers, cookies, session, &c. when locating the view.
So, as a really simple example, let us say you want to allow a view to be optionally selected using a query string value (e.g., ?View=MyView). In that case, you might create a QueryStringViewResultExecutor. Here's a basic proof-of-concept:
public class QueryStringViewResultExecutor : ViewExecutor, IActionResultExecutor<ViewResult>
{
public QueryStringViewResultExecutor(
IOptions<MvcViewOptions> viewOptions,
IHttpResponseStreamWriterFactory writerFactory,
ICompositeViewEngine viewEngine,
ITempDataDictionaryFactory tempDataFactory,
DiagnosticListener diagnosticListener,
IModelMetadataProvider modelMetadataProvider
) : base(
viewOptions, writerFactory, viewEngine, tempDataFactory, diagnosticListener, modelMetadataProvider
)
{
}
public async Task ExecuteAsync(ActionContext context, ViewResult result)
{
var viewEngineResult = FindView(context, result); // See helper method below
viewEngineResult.EnsureSuccessful(originalLocations: null);
var view = viewEngineResult.View;
using (view as IDisposable)
{
await ExecuteAsync(
context,
view,
result.ViewData,
result.TempData,
result.ViewName,
result.StatusCode
).ConfigureAwait(false);
}
}
private ViewEngineResult FindView(ActionContext actionContext, ViewResult viewResult)
{
// Define variables
var view = (ViewEngineResult?)null;
var viewEngine = viewResult.ViewEngine?? ViewEngine;
var searchedPaths = new List<string>();
var requestContext = actionContext.HttpContext.Request;
// If defined, attempt to locate view based on query string variable
if (requestContext.Query.ContainsKey("View"))
{
var queryStringValue = requestContext.Query["View"].First<string>();
if (queryStringValue is not null)
{
view = viewEngine.FindView(actionContext, queryStringValue, isMainPage: true);
searchedPaths = searchedPaths.Union(view.SearchedLocations?? Array.Empty<string>()).ToList();
}
}
// If no view is found, fall back to the view defined on the viewResult
if (!view?.Success?? true)
{
view = viewEngine.FindView(actionContext, viewResult.ViewName, isMainPage: true);
searchedPaths = searchedPaths.Union(view.SearchedLocations ?? Array.Empty<string>()).ToList();
}
// Return view for processing by the razor engine
if (view is not null and { Success: true }) {
return view;
}
return ViewEngineResult.NotFound(viewResult.ViewName, searchedPaths);
}
}
You would then register this in your Startup.ConfigureServices() method as:
services.Services.TryAddSingleton<IActionResultExecutor<ViewResult>, QueryStringViewResultExecutor>();
Disclaimer: You may need to unregister the out-of-the-box ViewExecutor in order to avoid a conflict. Typically, however, you are registering an IActionResultExecutor<> with a custom ViewResult and, thus, that isn't necessary; see below.
Extending ViewResult
Often, you will want to pair this with a custom ViewResult. Why is this useful? Usually because you need to pass additional data to your ViewResultExecutor from your Controller.
So, as a contrived example, imagine that you have themes, and views can optionally be customized based on that theme. You might then add a Theme property to your ViewResult, thus allowing the ViewResultExecutor to first look for a view based on the theme, and otherwise fallback to the non-themed version.
public class ThemedViewResult : ViewResult
{
public string Theme { get; set; }
public override async Task ExecuteResultAsync(ActionContext context)
{
var executor = context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<ThemedViewResult>>();
await executor.ExecuteAsync(context, this).ConfigureAwait(false);
}
}
Even if you don't need a custom ViewResult, it's worth pausing for a second to evaluate this code. The underlying ViewResult class implements IActionResult, which exposes a single method, ExecuteResultAsync(). This is called by the ResourceInvoker class mentioned at the top. This in turn locates the registered IActionResultExecutor<>—i.e., the type of component we created and registered in the previous section—and calls its ExecuteAsync() method.
Note: This is a contrived example because often a theme would be accessible by other context data, such as the RouteData, a custom ClaimsPrincipal, or an ISession implementation. But you can imagine other times where this information would be request-specific, and best relayed from the Controller via the ViewResult. For instance, perhaps a CMS where the theme can be selected on a per page basis.
As it's unclear whether you actually need to extend this functionality or are just curious how everything fits together, the above examples are only meant as a proof-of-concept; they aren't tested. Still, they should give you a basic idea of where and how the ViewResult is handled, as well as options for extending that behavior should you need.

Disable model validation for single .NET Core API action

I have an API controller for performing autosaves on an application I am developing. It uses the same viewmodel as the view, which has a number of required fields. The autosave controller may need to save a model that is not considered valid if the user has not completed the form when it is saved. By default, an .NET Core controller declared with the [ApiController] attribute will automatically force validation. I know I can disable this like so in Startup.cs:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
But this will apply to all API controllers in the project. Is it possible to disable this default validation for only one controller or action? Everything I've found so far has directed me to use the code above, but that doesn't accomplish what I'm looking for.
You can override the default InvalidModelStateResponseFactory:
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory =
AllowingServerSideValidationToBeDisabledInvalidModelStateResponseFactoryHelper.InvalidModelStateResponseFactory;
});
The InvalidModelStateResponseFactory below checks for OptionalValidationAttribute on a controller action, and searches for a form/query parameter flag which controls the validation is enabled/disabled:
// Code taken from https://github.com/dotnet/aspnetcore/blob/5747cb36f2040d12e75c4b5b3f49580ef7aac5fa/src/Mvc/Mvc.Core/src/DependencyInjection/ApiBehaviorOptionsSetup.cs#L23
// and is modified to optionally disable validation for controller action methods decorated with OptionalValidationAttribute
public static class AllowingServerSideValidationToBeDisabledInvalidModelStateResponseFactoryHelper
{
public static Func<ActionContext, IActionResult> InvalidModelStateResponseFactory => actionContext =>
{
var shouldEnableDataValidationarameterName = ((OptionalValidationAttribute)((ControllerActionDescriptor)actionContext.ActionDescriptor)
.MethodInfo.GetCustomAttributes(typeof(OptionalValidationAttribute), true)
.SingleOrDefault())?.ShouldEnableDataValidationParameterName;
var isValidationEnabled = true;
if (shouldEnableDataValidationarameterName != null)
{
var httpContextRequest = actionContext.HttpContext.Request;
var shouldEnableDataValidationValue = httpContextRequest.Form[shouldEnableDataValidationarameterName]
.Union(httpContextRequest.Query[shouldEnableDataValidationarameterName]).FirstOrDefault();
isValidationEnabled = shouldEnableDataValidationValue?.ToLower() == bool.TrueString.ToLower();
}
if (!isValidationEnabled)
{
return null;
}
var problemDetailsFactory = actionContext.HttpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = problemDetailsFactory.CreateValidationProblemDetails(actionContext.HttpContext, actionContext.ModelState);
ObjectResult result;
if (problemDetails.Status == 400)
{
// For compatibility with 2.x, continue producing BadRequestObjectResult instances if the status code is 400.
result = new BadRequestObjectResult(problemDetails);
}
else
{
result = new ObjectResult(problemDetails)
{
StatusCode = problemDetails.Status,
};
}
result.ContentTypes.Add("application/problem+json");
result.ContentTypes.Add("application/problem+xml");
return result;
};
}
The OptionalValidationAttribute:
[AttributeUsage(AttributeTargets.Method)]
public class OptionalValidationAttribute : Attribute
{
public OptionalValidationAttribute(string shouldEnableDataValidationParameterName)
{
ShouldEnableDataValidationParameterName = shouldEnableDataValidationParameterName;
}
public string ShouldEnableDataValidationParameterName { get; }
}
Example usage on an controller action:
[HttpPost]
[OptionalValidation(shouldEnableDataValidationParameterName: nameof(shouldEnableDataValidation))] // C# 11 needed to use nameof for a method parameter
public async Task<IActionResult> Update(
[FromForm] int id,
[FromForm] string name,
[FromForm] bool shouldEnableDataValidation
)
{
...
}
I would suggest you to approach this differently: Disabling the model validation will mean that there isn’t any validation for this action; not now, not later. Just because you do not require validation right now that doesn’t mean that you won’t need some kind of validation later on.
If you used some custom handling to disable the validation altogether for that action, then all you are doing is creating an actual exception into your application which will make it more complex. Developers looking at this later might not expect this behavior and could spend a good amount of time trying to figure out why the validation isn’t running when it’s working for every other action.
So instead, consider just duplicating the model so that each action has its own model: Action A has the original model with the validation attributes, requiring the values to be filled. And action B has a copy of that model without any validation attributes.
While this may seem wasteful, this does give you a few more benefits:
If you later require validation for some fields on action B, you could just add some validation attributes back. You didn’t have to disable the automatic validation completely, so individual validation attributes will just continue to work if you add them to the model.
Having separate models allows both actions to evolve independently. There’s already a good indicator that the actions do two different things: One requires the values, the other doesn’t. So it’s not unlikely that the models might need to diverge further in the future. For example, you might want to add a property to only one model but not the other.
As already mentioned above, you can stick to the default behavior and keep a consistent development experience.
Similar to Poke's answer, I would recommend using a different model for the action you want not to be validated. Instead of creating a copy of the model, however, I would just derive from the validated model and add the [ValidateNever] attribute, e.g.
[ValidateNever]
public class MyUnvalidatedModel : MyValidatedModel {
}
This will allow you to avoid a lot of duplication, while still giving you an unvalidated version of your model.

Dynamic RazorViewEngine for relating controllers dedicated to partials

I'm working on what should be a minor RazorViewEngine Extension, but having some difficulty with the second part of my approach.
Basically I have some controllers that are dedicated to some partial views, and would like to be able to place them in sub directories. Of course placing the models and controllers in sub directories is easy enough, but the views need to match as well. That's where it gets tricky.
Below you'll see my engine, and what I'm missing is how to get the name of the master controller. It seems like maybe I should be able to extend ControllerContext somehow?
In the FileExists override you can see I hard coded in a value to test, and that much works. But I don't just want administration. An example of the controller relationship - I have an administration controller, which loads a few partials, each with their own dedicated controller, for example User. So when I'm loading views for User, I'd like it to go to Views/Administration/User instead of Views/User.
public class PartialsViewEngine : RazorViewEngine
{
private static string[] NewPartialViewFormats = new[] {
"~/Views/%1/{1}/{0}.cshtml",
"~/Views/{1}/Partials/{0}.cshtml",
"~/Views/Shared/Partials/{0}.cshtml"
};
public PartialsViewEngine()
{
base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(NewPartialViewFormats).ToArray();
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
string firstController = '????. //Trying to figure how to get this
var path = partialPath.Replace("%1", firstcontroller);
return base.CreatePartialView(controllerContext, path));
}
protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
{
var path = partialPath.Replace("%1", "Administration");
return base.FileExists(controllerContext, path);
}
}
How would I, with the least overhead possible to the controllers, obtain that value?
--- Update 1 ---
I noticed with this configuration resharper doesn't recognize the file paths even though it executes.

System.NullReferenceException creating viewModel

So, I'm trying to find the Umbraco node (as iPublishedContent), and pass it to the viewModel (as Ш've hijacked a route). So i put this in my controller:
private AddCouponCodesViewModel viewModel;
public AddCouponCodesController(){
//Get iPublished content
IPublishedContent content = Umbraco.TypedContent(1225);
//Pass to viewModel
viewModel = new AddCouponCodesViewModel(content);
RouteData.DataTokens["umbraco"] = content;
}
public ActionResult Index()
{
//return view etc
}
But I'm getting
Exception Details: System.NullReferenceException:
Object reference not set to an instance of an object.
here:
Source Error(AddCouponCodesViewModel.cs):
Line 20:
Line 21: }
Line 22: public AddCouponCodesViewModel(IPublishedContent content)
Line 23: : base(content)
Line 24: {
AddCouponCodeRenderModel.cs:
public class AddCouponCodesViewModel : RenderModel
{
public string test { get; set; }
public List<string> tables { get; set; }
public List<string> errors { get; set; }
public AddCouponCodesViewModel(IPublishedContent content, CultureInfo culture) : base(content, culture)
{
}
public AddCouponCodesViewModel(IPublishedContent content)
: base(content)
{
}
And this is the Global.asax
public class Global : UmbracoApplication
{
protected override void OnApplicationStarted(object sender, EventArgs e)
{
base.OnApplicationStarted(sender, e);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//AreaRegistration.RegisterAllAreas();
//WebApiConfig.Register(GlobalConfiguration.Configuration);
//FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
//RouteConfig.RegisterRoutes(RouteTable.Routes);
base.OnApplicationStarting(sender, e);
RouteTable.Routes.MapRoute(
"AddCouponCodes", // Route name
"Admin/{controller}/{action}/{id}", // URL with parameters
new { controller = "AddCouponCodes", action = "Index", id = "" } // Parameter defaults
);
}
}
The content is published (I've checked and double checked), and the node ID is correct.
What I'm basically trying to do here, is to get the route example.com/Admin/{controller}/{action}/{parameter}
To be routed, but having problems connecting it with the umbracoNode (And class RenderModel requires a iPublishContent object as a parameter, but I'm in no luck when trying to pass it anything)
Could someone please help me here, been stuck way too many hours on this :-(
To clarify, if you are hijacking a route, it means that you are overriding the way Umbraco passes it's RenderModel to one of it's published pages. You can either do this globally by overriding the main RenderMvcController, or you can override on a DocumentType-specific basis. So for example, if I have a Homepage doc type, I could create:
public HomepageController : RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
// Create your new renderModel here, inheriting
// from RenderModel
return CurrentTemplate(renderModel);
}
}
This would route all calls to the homepage through this one action. For this, you don't need to define any new routes in the route table. And you should override the render model in the action not in the constructor.
Your question is slightly confusing and it's not entirely clear what you are trying to achieve because:
You have defined a route, and
In your constructor you are calling Umbraco.TypedContent(1225) to retrieve a specific published node
So ... if the admin page you are trying to route has itself been published by Umbraco (and it doesn't sound like it has), the just create a new controller with the name of the page's document type and override the render model in the way described above.
However ... if your admin page hasn't been published by Umbraco and you just want the admin page to access node data, then you have a couple of options:
Create a surface controller, inheriting from SurfaceController. This will give you access to the Umbraco context et al; or
Create a standard controller (preferrably in an Area) and inject the ContentCache using something like Autofac
E.g.:
builder.RegisterControllers(typeof (AdminController).Assembly)
.WithParameter("contentCache", UmbracoContext.Current.ContentCache);
Create a standard controller (preferrably in an Area) and access the node using Umbraco's ContentService API, i.e. new Umbraco.Core.Services.ContentService().GetById(1225)
The difference between the last two approaches is that:
Injecting the ContentCache provides you readonly but very quick access to the published content.
Accessing the ContentService provides you read/write access to the nodes themselves but at the expense of speed as you are querying the database directly.
It depends on what your requirement is.
Either way, it is well worth taking time to read through the documentation for hijacking Umbraco routes, and at least trying to understand what is going on.
Well, I can tell you that your view isn't getting fed anything for the Razor markup because your Index method doesn't feed it anything. That's one problem. I can also tell you, that in your AddCouponCodesViewModel, you'll need an empty constructor, so that the razor syntax can just create an instance, and then populate it to match your submitted object to the view.
Modify your ViewController :
public ActionResult Index()
{
return View(viewModel);
}
Modify your AddCouponCodesViewModel to add an Empty constructor:
public AddCouponCodesViewModel()
{
}
Create a paramaterless constructor on your view model like this:
public AddCouponCodesViewModel():
this(new UmbracoHelper(UmbracoContext.Current).
TypedContent(UmbracoContext.Current.PageId))
{
}
This will get the contexts your other constructors are looking for.
After you've created a class with specific constructors, the compiler stops generating a parameterless one by default. Since you need a parameterless constructor, this is how to get one and still pass in the Umbraco contextual info your viewmodel needs

Creating an "Ambient Context" (UserContext) for an ASP.NET application using a static factory Func<T>

I have found out that I need the current logged in user data in nearly every class (controllers, view, HTML helpers, services and so on). So I thought about to create an "Ambient Context" instead of injecting an IUserService or the User directly.
My approach looks something like that.
public class Bootstrapper
{
public void Boot()
{
var container = new Container();
// the call to IUserService.GetUser is cached per Http request
// by using a dynamic proxy caching mechanism, that also handles cases where we want to
// invalidate a cache within an Http request
UserContext.ConfigureUser = container.GetInstance<IUserService>().GetUser;
}
}
public interface IUserService
{
User GetUser();
}
public class User
{
string Name { get; set; }
}
public class UserContext : AbstractFactoryBase<User>
{
public static Func<User> ConfigureUser = NotConfigured;
public static User ActiveUser { get { return ConfigureUser(); } }
}
public class AbstractFactoryBase<T>
{
protected static T NotConfigured()
{
throw new Exception(String.Format("{0} is not configured", typeof(T).Name));
}
}
Example usage:
public class Controller
{
public ActionResult Index()
{
var activeUser = UserContext.ActiveUser;
return View();
}
}
Is my approach correct or do I missing something? Do you have better solutions in mind?
UPDATE:
More Detail of the User class:
public class User
{
string Name { get; set; }
bool IsSuperUser { get; set;}
IEnumerable<AzManOperation> Operations { get; set}
}
In Controllers we need to check if an User is a SuperUser to only provide the SuperUser some extra functionality.
public class BaseController : Controller
{
private readonly IUserService _userService;
BaseControler(IUserService userService)
{
_userService = userService
}
public User ActiveUser
{
get { return _userService.GetUser(); }
}
}
In Views we check Operations to only show an edit or delete button if the user has the right to do so. A view never uses the DependencyResolver, but ViewBag or ViewModel. My idea here is to implementing a custom ViewBasePage and providing an ActiveUser property, so that Views have an easy accesss.
In HtmlHelpers we render controls depending on IsSuperUser and Operations (passing in the User object or using DependencyResolver).
In Service Classes we need those properties too. For instance to decide if a basket is valid or not (check if the User is allowed to buy articles that are not in a standard list). So the Service class depends on IUserService and calling GetUser().
In Action Filters to force the user to change his password (only if it is not a SuperUser and User.ForcePasswordChange is true). Here we use the DependencyResolver.
My wish is to have a more easily way to get the User object, instead of using DependencyResolver.Current.GetService().GetUser() or using things like ViewBag.ActiveUser = User.
The User object is an object that is almost everywhere needed to check permissions or the like.
In Views we check Operations to only show an edit or delete button if the user has the right to do so.
The view should not do this check. The Controller should return a view model to the view that contains boolean properties that state whether those buttons should be visible. Returning a bool with IsSuperUser already moves to much knownledge into the view. The view shouldn't know that it should show a certain button for a super user: that's up to the controller. The view should only be told what to display.
If almost all views have this code, there are ways to extract repetitive parts out of your views, for instance with partial views. If you're finding yourself repeating those properties over many view models, perhaps you should define an envelope view model (a generic view model that wraps the specific model as T). A controller can create its view model, while you create a service or cross-cutting concern that wraps it in your envelope.
In Service Classes we need those properties too. For instance to decide if a basket is valid or not
In this case you are talking about validation, which is a cross-cutting concern. You should use decorators to add this behavior instead.
This is MVC, right?
You're reinventing the wheel.
Add this method to your Global.asax.cs:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var ticket = FormsAuthentication.Decrypt(authCookie.Value);
var user = ticket.Name;
var identity = new GenericIdentity(user, "Forms");
var principal = new GenericPrincipal(identity, null);
Context.User = principal;
}
}
This example shows forms authentication which you can strip if you're using another mechanism. The key is these three lines:
var identity = new GenericIdentity(user, "Forms");
var principal = new GenericPrincipal(identity, null);
Context.User = principal;
GenericIdentity and GenericPrincipal can be replaced with anything you want as long as they implement the (trivial) IIdentity and IPrincipal interfaces. You can create your own implementations of these classes with whatever extra properties you need.
You can then access the authenticated user from all the things you listed - controllers, views, etc. - via HttpContext.Current.User (which is static).
If you created your own implementation of IPrincipal you can just cast that reference to your custom type.
You'll note that IPrincipal has a method called IsInRole, so you'd say:
if (HttpContext.Current.User.IsInRole("SuperUser"))
TL;DR - you are overengineering something ASP.NET has already solved, and I'd have an aneurysm if I saw the types you're proposing in a production application.
I think the easiest and maintainable solution is to create a static class CurrentUserProvider which has only one method Get(HttpContextBase) that returns the current user, behind the scene you can use the DependencyResolver to get the service that actually returns the user. Then where you need the CurrentUser you can call CurrentUserProvider.Get(context) and do whatever custom logic you need to perform.
The other solution that you are trying to do is injecting the service in the base controller constructor which is okay if you have handful of controllers, it would become an issue if you have quite a number of controllers and not all of the controllers requires that service. Writing tests for those controller would be such pain in the neck, because you have to create stubs/mocks for that service for all your controller tests. Maybe you can use property injection instead of constructor to address it.
You can use the same property injection for Filters too.
Now, the remaining two are the view and the helper. For View you can create special base class that inherits from WebViewPage/ViewPage and use the IViewActivator to inject the service and the same applies for the helpers, create helpers that inherits from system helpers and use those in your base controllers and views.
I think the second approach is bit cumbersome and it does not add that much value to do all those custom things.
So my suggestion is to go with the first.

Categories

Resources