I am using a helper in my controllers and in my views that I have found somewhere on the internet. The helper is called like this in my controller "Url.SiteRoot();"
How can I get my controller to not throw an Exception whenever the helper is called? I am using MVCContrib and moq for my unit tests.
I am thinking of implementing some kind of a check in the helper but it feel like the MVCContrib framework or the moq should be able to handle this so that I don't need to add Exception code in my helpers just to be able to pass the unit tests.
You can see the Helper code here:-
namespace System.Web.Mvc {
public static class UrlHelpers {
public static string SiteRoot(HttpContextBase context) {
return SiteRoot(context, true);
}
public static string SiteRoot(HttpContextBase context, bool usePort) {
var Port = context.Request.ServerVariables["SERVER_PORT"];
if (usePort) {
if (Port == null || Port == "80" || Port == "443")
Port = "";
else
Port = ":" + Port;
}
var Protocol = context.Request.ServerVariables["SERVER_PORT_SECURE"];
if (Protocol == null || Protocol == "0")
Protocol = "http://";
else
Protocol = "https://";
var appPath = context.Request.ApplicationPath;
if (appPath == "/")
appPath = "";
var sOut = Protocol + context.Request.ServerVariables["SERVER_NAME"] + Port + appPath;
return sOut;
}
public static string SiteRoot(this UrlHelper url) {
return SiteRoot(url.RequestContext.HttpContext);
}
public static string SiteRoot(this ViewPage pg) {
return SiteRoot(pg.ViewContext.HttpContext);
}
public static string SiteRoot(this ViewUserControl pg) {
var vpage = pg.Page as ViewPage;
return SiteRoot(vpage.ViewContext.HttpContext);
}
public static string SiteRoot(this ViewMasterPage pg) {
return SiteRoot(pg.ViewContext.HttpContext);
}
public static string GetReturnUrl(HttpContextBase context) {
var returnUrl = "";
if (context.Request.QueryString["ReturnUrl"] != null) {
returnUrl = context.Request.QueryString["ReturnUrl"];
}
return returnUrl;
}
public static string GetReturnUrl(this UrlHelper helper) {
return GetReturnUrl(helper.RequestContext.HttpContext);
}
public static string GetReturnUrl(this ViewPage pg) {
return GetReturnUrl(pg.ViewContext.HttpContext);
}
public static string GetReturnUrl(this ViewMasterPage pg) {
return GetReturnUrl(pg.Page as ViewPage);
}
public static string GetReturnUrl(this ViewUserControl pg) {
return GetReturnUrl(pg.Page as ViewPage);
}
}
}
As #Jeremy Frey writes you're getting the exceptions because you're failing to stub/fake some essential parts of the HttpContext.
How about using:
Request.Url.GetLeftPart(System.UriPartial.Authority)
instead of trying to build the logic for building the url yourself? If I remember correctly it should pick up the protocol and port correctly, as well as any virtual directory, site, etc.
You've probably realized that the reason you're getting exceptions from your extension methods is due to unimplemented properties or methods on the mocked objects, e.g. Request on HttpContextBase, or RequestContext on UrlHelper.
Take a look at some of the strategies posted here for ideas on how to mock the extension method calls. I personally prefer this strategy, which would have you refactoring your extension methods to bet swappable at runtime.
For example, instead of:
public static class UrlHelperExtensions
{
public static string GetReturnUrl(this UrlHelper helper)
{
return // your implementation of GetReturnUrl here
}
}
You'd have:
public interface IUrlHelperExtensions
{
string GetReturnUrl(UrlHelper helper);
}
public static class UrlHelperExtensions
{
public static IUrlHelperExtensions Extensions(this UrlHelper target)
{
return UrlHelperExtensionFactory(target);
}
static UrlExtensions
{
UrlHelperExtensionFactory = () => new DefaultUrlHelperExtensionStrategy();
}
public static Func UrlHelperExtensionFactory { get; set; }
}
public DefaultUrlHelperExtensionStrategy : IUrlHelperExtensions
{
public string GetReturnUrl(UrlHelper helper)
{
return // your implementation of GetReturnUrl here
}
}
You'd need to change the way you'd call your extension methods, from urlHelper.GetReturnUrl() to urlHelper.Extensions().GetReturnUrl(), and during unit testing, you can set UrlHelperExtensions.UrlHelperExtensionFactory to a mocked object, but this way, you can control the extension methods' behavior at test time.
That code looks a little complicated. I think this does the same thing and would be much easier to test. (I'm not sure why it needs to be though.)
public string FullApplicationPath(HttpRequestBase request)
{
var path = request.Url.AbsoluteUri.Replace(request.Url.AbsolutePath,string.Empty);
if (!string.IsNullOrEmpty(request.Url.Query))
{
path = path.Replace(request.Url.Query, string.Empty);
}
return path + request.ApplicationPath;
}
Related
public static class ApplicationUtils
{
public static bool IsCurrentUserAManager()
{
var username = WindowsIdentity.GetCurrent().Name;
bool inAdmin;
if (username == "AdminUser") {
inAdmin = true;
} else {
inAdmin = false;
}
return inAdmin;
}
}
Above is some code that is used to test if the currently logged in user is an Admin, I want to unit test this section by passing in a different username and test if the result is correct.
I have heard that dependency injection would be the best way to do this. But I have no idea how to dependency inject into a static class and a static method.
Can anyone help me fill out the TestMethod below in order to pass in a username and test the method?
(Not using enterprise)
[TestMethod]
public void IsCurrentUserAManagerTestIsAdmin()
{
}
Refactor your class a little to take an identity as a parameter.
public static class ApplicationUtils
{
public static bool IsUserAManager(IIdentity identity)
{
if (identity == null)
throw new NullReferenceException("identity");
return identity.Name == "AdminUser";
}
}
And Your Test Class using Moq
[TestMethod]
public void IsUserAManagerTestIsAdminReturnsFalse()
{
var mockedIdentity = new Moq.Mock<IIdentity>();
mockedIdentity.Setup(x => x.Name).Returns("notanadmin");
var result = ApplicationUtils.IsUserAManager(mockedIdentity.Object);
Assert.IsFalse(result);
}
[TestMethod]
public void IsUserAManagerTestIsAdminReturnsTrue()
{
var mockedIdentity = new Moq.Mock<IIdentity>();
mockedIdentity.Setup(x => x.Name).Returns("AdminUser");
var result = ApplicationUtils.IsUserAManager(mockedIdentity.Object);
Assert.IsTrue(result);
}
One should try to avoid coupling code to static classes as they are difficult to test.
That said, with your current code as is, it can be refactored to allow certain separations of concerns and a more fluent API via extension methods.
public static class ApplicationUtils {
public static Func<IIdentity> userFactory = () => WindowsIdentity.GetCurrent();
public static IIdentity CurrentUser { get { return userFactory(); } }
public static bool IsManager(this IIdentity identity) {
return identity != null && string.Compare(identity.Name, "AdminUser", true) == 0;
}
public static bool IsAuthenticated(this IIdentity identity) {
return identity != null && identity.IsAuthenticated;
}
}
The following test is used as an example to demonstrate how the above is used.
Moq was used as mocking framework
[TestMethod]
public void IsManager_Should_Return_True_For_AdminUser() {
//Arrange
var name = "AdminUser";
var identity = Mock.Of<IIdentity>(_ => _.Name == name);
ApplicationUtils.userFactory = () => identity;
//Act
var actual = ApplicationUtils.CurrentUser.IsManager();
//Assert
Assert.IsTrue(actual);
}
That done I would now like to suggest you refactor your code to make it more SOLID.
Abstract the functionality of getting the current user out into a service provider.
public interface IIdentityProvider {
IIdentity CurrentUser { get; }
}
Pretty simple with an even simpler implementation.
public class DefaultIdentityProvider : IIdentityProvider {
public IIdentity CurrentUser {
get { return WindowsIdentity.GetCurrent(); }
}
}
If using DI you can now inject the provider into dependent classes that have need to access the current user.
This allows the code to more flexible and maintainable as mocks/stubs of the provider and user can be used for isolated unit tests. The extension methods remain the same as they have very simple concerns.
Here is a simple example of a test for the extension method from earlier.
[TestMethod]
public void IsManager_Should_Return_True_For_AdminUser() {
//Arrange
var name = "AdminUser";
var identity = Mock.Of<IIdentity>(_ => _.Name == name);
var provider = Mock.Of<IIdentityProvider>(_ => _.CurrentUser == identity);
//Act
var actual = provider.CurrentUser.IsManager();
//Assert
Assert.IsTrue(actual);
}
Purely for demonstrative purposes, the test for the IsManager extension method only really needs an IIdentity to be exercised.
Assert.IsTrue(Mock.Of<IIdentity>(_ => _.Name == "AdminUser").IsManager());
When your code is difficult to test, changing the code is a viable option!
In this case, consider having the IsCurrentUserAManager receive the username as an input parameter (and rename it to IsUserAManager to reflect the change in behavior). It would look something like this:
public static class ApplicationUtils
{
public static bool IsUserAManager(string username)
{
bool inAdmin;
if (username == "AdminUser") {
inAdmin = true;
} else {
inAdmin = false;
}
return inAdmin;
}
}
This will allow you to send in different values for testing different scenarios. If however the entire class as it is cannot appear in the UT (due to environment constraints on initialization, for example), consider having just the business logic exported to a seperate non static class and write your UT for that.
public static class ApplicationUtils
{
// this is not testable, because I dont use DI
public static bool IsCurrentUserAManager() => TestableMethod(WindowsIdentity.GetCurrent().Name);
// This is testable because it contains logics (if-else)
public static bool TestableMethod(string username) => username == "AdminUser";
}
[TestMethod]
public void IsCurrentUserAManagerTestIsAdmin()
{
Assert.IsTrue(ApplicationUtils.TestableMethod("AdminUser"));
Assert.IsFalse(ApplicationUtils.TestableMethod("adminuser"));
}
Consider the following controller:
public class SubmissionController : Controller
{
public SubmissionController()
{ }
public IActionResult Post()
{
RecurringJob.AddOrUpdate(() => InitiateSubmission(), Cron.Minutely);
return Ok("Periodic submission triggered");
}
}
Does Hangfire offer an abstraction inject a dependency for RecurringJob class? I have done some research and the only available abstraction is IBackgroundJobClient, which does not have the option to schedule a recurring job.
I need to verify that the job has been added in a unit test.
If you check the source code of RecurringJob class, you will see that its static methods result in call to RecurringJobManager class:
public static class RecurringJob
{
private static readonly Lazy<RecurringJobManager> Instance = new Lazy<RecurringJobManager>(
() => new RecurringJobManager());
// ...
public static void AddOrUpdate(
Expression<Action> methodCall,
string cronExpression,
TimeZoneInfo timeZone = null,
string queue = EnqueuedState.DefaultQueue)
{
var job = Job.FromExpression(methodCall);
var id = GetRecurringJobId(job);
Instance.Value.AddOrUpdate(id, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue);
}
// ...
}
RecurringJobManager implements IRecurringJobManager interface which you could use for dependency injection and mock in UT.
However RecurringJob has internal logic for getting a job from lambda and building a job id:
var job = Job.FromExpression(methodCall);
var id = GetRecurringJobId(job);
Job.FromExpression() is a public method that you can safely use. However GetRecurringJobId is a private method defined as following:
private static string GetRecurringJobId(Job job)
{
return $"{job.Type.ToGenericTypeString()}.{job.Method.Name}";
}
GetRecurringJobId basically returns name of job method in form of SubmissionController.InitiateSubmission. It's based on internal class TypeExtensions with extension methods for Type. You can't use this class directly since it is internal, so you should duplicate that logic.
If you follow this approach your final solution would be:
TypeExtensions (copied from Hangfire sources):
static class TypeExtensions
{
public static string ToGenericTypeString(this Type type)
{
if (!type.GetTypeInfo().IsGenericType)
{
return type.GetFullNameWithoutNamespace()
.ReplacePlusWithDotInNestedTypeName();
}
return type.GetGenericTypeDefinition()
.GetFullNameWithoutNamespace()
.ReplacePlusWithDotInNestedTypeName()
.ReplaceGenericParametersInGenericTypeName(type);
}
private static string GetFullNameWithoutNamespace(this Type type)
{
if (type.IsGenericParameter)
{
return type.Name;
}
const int dotLength = 1;
// ReSharper disable once PossibleNullReferenceException
return !String.IsNullOrEmpty(type.Namespace)
? type.FullName.Substring(type.Namespace.Length + dotLength)
: type.FullName;
}
private static string ReplacePlusWithDotInNestedTypeName(this string typeName)
{
return typeName.Replace('+', '.');
}
private static string ReplaceGenericParametersInGenericTypeName(this string typeName, Type type)
{
var genericArguments = type.GetTypeInfo().GetAllGenericArguments();
const string regexForGenericArguments = #"`[1-9]\d*";
var rgx = new Regex(regexForGenericArguments);
typeName = rgx.Replace(typeName, match =>
{
var currentGenericArgumentNumbers = int.Parse(match.Value.Substring(1));
var currentArguments = string.Join(",", genericArguments.Take(currentGenericArgumentNumbers).Select(ToGenericTypeString));
genericArguments = genericArguments.Skip(currentGenericArgumentNumbers).ToArray();
return string.Concat("<", currentArguments, ">");
});
return typeName;
}
public static Type[] GetAllGenericArguments(this TypeInfo type)
{
return type.GenericTypeArguments.Length > 0 ? type.GenericTypeArguments : type.GenericTypeParameters;
}
}
RecurringJobManagerExtensions:
public static class RecurringJobManagerExtensions
{
public static void AddOrUpdate(this IRecurringJobManager manager, Expression<Action> methodCall, Func<string> cronExpression, TimeZoneInfo timeZone = null, string queue = EnqueuedState.DefaultQueue)
{
var job = Job.FromExpression(methodCall);
var id = $"{job.Type.ToGenericTypeString()}.{job.Method.Name}";
manager.AddOrUpdate(id, job, cronExpression(), timeZone ?? TimeZoneInfo.Utc, queue);
}
}
Controller with injected IRecurringJobManager:
public class SubmissionController : Controller
{
private readonly IRecurringJobManager recurringJobManager;
public SubmissionController(IRecurringJobManager recurringJobManager)
{
this.recurringJobManager = recurringJobManager;
}
public IActionResult Post()
{
recurringJobManager.AddOrUpdate(() => InitiateSubmission(), Cron.Minutely);
return Ok("Periodic submission triggered");
}
public void InitiateSubmission()
{
// ...
}
}
Well, this approach will work, but I'm not a fan of it. It's based on some internal Hangfire stuff that could be changed in the future.
That's why I suggest to use another approach. You could add new facade interface (e.g. IRecurringJobFacade) which will mimic methods from RecurringJob that you are going to use. Implementation of this interface will just call corresponding RecurringJob methods. Then you inject this IRecurringJobFacade into the controller and could easily mock it in UT. Here is a sample:
IRecurringJobFacade:
public interface IRecurringJobFacade
{
void AddOrUpdate(Expression<Action> methodCall, Func<string> cronExpression);
// Mimic other methods from RecurringJob that you are going to use.
// ...
}
RecurringJobFacade:
public class RecurringJobFacade : IRecurringJobFacade
{
public void AddOrUpdate(Expression<Action> methodCall, Func<string> cronExpression)
{
RecurringJob.AddOrUpdate(methodCall, cronExpression);
}
}
Controller with injected IRecurringJobFacade:
public class SubmissionController : Controller
{
private readonly IRecurringJobFacade recurringJobFacade;
public SubmissionController(IRecurringJobFacade recurringJobFacade)
{
this.recurringJobFacade = recurringJobFacade;
}
public IActionResult Post()
{
recurringJobFacade.AddOrUpdate(() => InitiateSubmission(), Cron.Minutely);
return Ok("Periodic submission triggered");
}
public void InitiateSubmission()
{
// ...
}
}
As you see this approach is much simpler and most importantly it's much more reliable, since it does not dig into Hangfire internals and just calls RecurringJob methods as usual.
Such facade interface is often used when code could not be mocked directly (static methods or classes not based on interfaces). Some other examples that I have used in my practice: mock of System.IO.File, DateTime.Now, System.Timers.Timer, etc.
I had a similar case with: RecurringJob.RemoveIfExists. I try this (I see the original code in github and setup my mock's):
private void SetupHangfire()
{
Mock<JobStorage> _jobStorageMock = new Mock<JobStorage>();
Mock<IStorageConnection> _storageConnectionMock = new Mock<IStorageConnection>();
Mock<IWriteOnlyTransaction> _transactionConnectionMock = new Mock<IWriteOnlyTransaction>();
JobStorage.Current = _jobStorageMock.Object;
_jobStorageMock
.Setup(y => y.GetConnection())
.Returns(_storageConnectionMock.Object);
_storageConnectionMock
.Setup(y => y.AcquireDistributedLock(It.IsAny<string>(), It.IsAny<TimeSpan>()))
.Returns(_transactionConnectionMock.Object);
_storageConnectionMock
.Setup(y => y.CreateWriteTransaction())
.Returns(_transactionConnectionMock.Object);
_transactionConnectionMock
.Setup(y => y.RemoveHash(It.IsAny<string>()));
_transactionConnectionMock
.Setup(y => y.RemoveFromSet(It.IsAny<string>(), It.IsAny<string>()));
_transactionConnectionMock
.Setup(y => y.Commit());
}
I have an IDisposable HTML helper to create a specific html structure that I need very often in my app. I use it with razor and its works perfectly
#using (Html.SlidePanel("settings-button"))
{
<span>panel content</span>
}
I have a html helper component based on the structure and I want to use my SlidePanel inside.
public class MyComponent : IHtmlString
{
private readonly HtmlHelper html;
public MyComponent(HtmlHelper html)
{
this.html = html;
}
public override string ToString()
{
return Render();
}
public string ToHtmlString()
{
return ToString();
}
private string Render()
{
// I want to use my SlidePanel at this place
string renderHtml = "<div>component html</div>";
return renderHtml;
}
}
public static class MyComponentHtmlHelper
{
public static MyComponent MyComponent(this HtmlHelper html)
{
return new MyComponent(html);
}
}
How can I achieve this ?
Thanks
You'll need to intercept the string that SlidePanel would normally be sending to the general output. Something like this should work:
var originalWriter = html.ViewContext.Writer;
using (var stringWriter = new StringWriter())
{
html.ViewContext.Writer = stringWriter;
using (html.SlidePanel())
{
stringWriter.Write("<div>component html</div>");
}
html.ViewContext.Writer = originalWriter;
return stringWriter.ToString();
}
So here you go, example HERE
public static class DisposableExtensions
{
public static IDisposable DisposableDiv(this HtmlHelper htmlHelper)
{
return new DisposableHelper(
() => htmlHelper.BeginDiv(),
() => htmlHelper.EndDiv());
}
public static void BeginDiv(this HtmlHelper htmlHelper)
{
htmlHelper.ViewContext.Writer.Write("<div>");
}
public static void EndDiv(this HtmlHelper htmlHelper)
{
htmlHelper.ViewContext.Writer.Write("</div>");
}
}
}
As you can see in example "Hello Stranger" placed in additional div
I find a solution thanks to Baximilian and StriplingWarrior answers
Yes, I need to intercept the string that SlidePanel would normally be sending to the general output. So I can't use the ViewContext.Writer.
And yes, I need to send methods a parameters to my SlidePanel constructor.
So the solution is to add a second constructor to SlidePanel class :
public string HtmlString;
public SlidePanel(Func<string> begin, Func<string> content, Func<string> end)
{
this.HtmlString = string.Format("{0}{1}{2}", begin(), content(), end());
}
And add public helpers inside the SlidePanel Component class
public static string BeginSlidePanel(this HtmlHelper html)
{
return BeginHtml();
}
public static string ContentSlidePanel(this HtmlHelper html, string htmlString)
{
return htmlString;
}
public static string EndSlidePanel(this HtmlHelper html)
{
return EndHtml();
}
And then, I can use my SlidePanel like this :
new SlidePanel(
() => html.BeginSlidePanel(),
() => html.ContentSlidePanel(GetMyHtmlContent()),
() => html.EndSlidePanel()).HtmlString
Thanks a lot !
I have a class that does some retrieving of contents, and it has a method that requires some inputs (filters) before retrieving it. One of the "input" calls another method, which basically returning an int, how do I mock it using MOQ? Here's an example:
namespace MyNamespace
{
public class ConfigMetaDataColumns : MyModel
{
public int FieldID { get { return ValueInt("FieldID"); } }
public int OrderId { get { return ValueInt("OrderId"); } }
public string Label { get { return ValueString("Label"); } }
public string FieldName { get { return ValueString("FieldName"); } }
public int IsReadOnly { get { return ValueInt("IsReadOnly"); } }
}
public class GetDataClass
{
protected OpenSQLAccessLayer m_WITObject;
// Input Properties
public string GroupID;
public string PageName;
// Output Properties
/// <summary>
/// Get Config meta data
/// </summary>
/// <returns></returns>
public IEnumerable<ConfigMetaDataColumns> GetConfigMetaData()
{
var requester = new ListRequester<OpenSQL, ConfigMetaDataColumns>(m_WITObject, "Result[0].RowCount", "Result[0].Row[{0}].");
return requester.Items;
}
public void InitRequest()
{
User user = (User)HttpContext.Current.User;
m_WITObject = user.NewService<OpenSQLAccessLayer>();
m_WITObject.SetInput("MultipleResultSets", 1);
m_WITObject.SetInput("ClientID", Utils.GetClientID());
m_WITObject.SetInput("GroupID", GroupID);
m_WITObject.SetInput("PageName", PageName);
m_WITObject.Retrieve();
}
}
}
This is the "GetClientID()" method:
public static int GetClientID()
{
User oUser = (User)HttpContext.Current.User;
int nClientID;
string sClientID = string.Empty;
if (String.IsNullOrEmpty(oUser.Session("clientid")))
{
Client oClient = new Client();
}
oUser = (User)HttpContext.Current.User;
sClientID = oUser.Session("clientid");
//If we couldn't retrieve it, throw exception
if ( string.IsNullOrEmpty(sClientID) || !int.TryParse(sClientID, out nClientID))
{
throw new Exception("No clientid found in user session, client not authenticated, please login from main page");
}
return nClientID;
}
I'm just looking for a way for me to pass in a hard-coded value for the ClientID, and use this to do some unit testing with the GetDataClass class.
Thanks.
You cannot mock a static method. You should use some means of dependency injection. Say you make your GetClientId method part of an interface called IUtils like so:
public interface IUtils
{
int GetClientId();
}
And you have your concrete class Utils implemented as above, but without the method being static (and implementing the interface of course).
You now inject an implementation of your interface into the GetDataClass
class by changing its constructor, like so:
public class GetDataClass
{
private readonly IUtils utils;
public GetDataClass(IUtils utils)
{
this.utils = utils;
}
//SNIP
}
In the InitRequest method you change the call Utils.GetClientID() to this.utils.GetClientId().
You are now ready to instantiate your GetDataClass class with a mock, like so:
var utilsMock = new Mock<IUtils>();
utilsMock.Setup(u => u.GetClientId()).Returns(42);
var getDataClass = new GetDataClass(utilsMock.Object);
getDataClass.InitRequest();
And that's it.
I'm trying to secure my MVC routes from a set of users that meet a set of criteria. Since MVC seems to use attributes quite a bit and Steven Sanderson uses one for security extensibility in his pro MVC book I started heading down this route, but I'd like to define the rule contextually based on the action I am applying it to.
Some actions are for employees only, some aren't.
Some actions are for company1 only, some aren't.
So I was thinking this type of usage...
[DisableAccess(BlockUsersWhere = u => u.Company != "Acme")]
public ActionResult AcmeOnlyAction()
{
...
}
[DisableAccess(BlockUsersWhere = u => u.IsEmployee == false)]
public ActionResult EmployeeOnlyAction()
{
...
}
Looks pretty clean to me and is really pretty easy to implement, but I get the following compiler error:
'BlockUsersWhere' is not a valid named attribute argument because it is not a valid attribute parameter type
Apparently you can not use a Func as an attribute argument. Any other suggestions to get around this issue or something else that provides the simple usage we've come to love in our MVC projects?
Necros' suggestion would work, however you would have to invoke his SecurityGuard helper in the body of every action method.
If you would still like to go with the declarative attribute-based approach (which has the advantage that you can apply the attribute to the whole Controller) you could write your own AuthorizeAttribute
public class CustomAuthorizeAttribute : AuthorizeAttribute {
public bool EmployeeOnly { get; set; }
private string _company;
public string Company {
get { return _company; }
set { _company = value; }
}
protected override bool AuthorizeCore(HttpContextBase httpContext) {
return base.AuthorizeCore(httpContext) && MyAuthorizationCheck(httpContext);
}
private bool MyAuthorizationCheck(HttpContextBase httpContext) {
IPrincipal user = httpContext.User;
if (EmployeeOnly && !VerifyUserIsEmployee(user)) {
return false;
}
if (!String.IsNullOrEmpty(Company) && !VerifyUserIsInCompany(user)) {
return false;
}
return true;
}
private bool VerifyUserIsInCompany(IPrincipal user) {
// your check here
}
private bool VerifyUserIsEmployee(IPrincipal user) {
// your check here
}
}
Then you would use it as follows
[CustomAuthorize(Company = "Acme")]
public ActionResult AcmeOnlyAction()
{
...
}
[CustomAuthorize(EmployeeOnly = true)]
public ActionResult EmployeeOnlyAction()
{
...
}
Since you can only use constants, types or array initializers in attribute parameters, they probably won't do, or at least the won't be as flexible.
Alternatively, you could use something similar I came up with when solving this problem.
This is the API:
public static class SecurityGuard
{
private const string ExceptionText = "Permission denied.";
public static bool Require(Action<ISecurityExpression> action)
{
var expression = new SecurityExpressionBuilder();
action.Invoke(expression);
return expression.Eval();
}
public static bool RequireOne(Action<ISecurityExpression> action)
{
var expression = new SecurityExpressionBuilder();
action.Invoke(expression);
return expression.EvalAny();
}
public static void ExcpetionIf(Action<ISecurityExpression> action)
{
var expression = new SecurityExpressionBuilder();
action.Invoke(expression);
if(expression.Eval())
{
throw new SecurityException(ExceptionText);
}
}
}
public interface ISecurityExpression
{
ISecurityExpression UserWorksForCompany(string company);
ISecurityExpression IsTrue(bool expression);
}
Then create an expression builder:
public class SecurityExpressionBuilder : ISecurityExpression
{
private readonly List<SecurityExpression> _expressions;
public SecurityExpressionBuilder()
{
_expressions = new List<SecurityExpression>();
}
public ISecurityExpression UserWorksForCompany(string company)
{
var expression = new CompanySecurityExpression(company);
_expressions.Add(expression);
return this;
}
public ISecurityExpression IsTrue(bool expr)
{
var expression = new BooleanSecurityExpression(expr);
_expressions.Add(expression);
return this;
}
public bool Eval()
{
return _expressions.All(e => e.Eval());
}
public bool EvalAny()
{
return _expressions.Any(e => e.Eval());
}
}
Implement the security expressions:
internal abstract class SecurityExpression
{
public abstract bool Eval();
}
internal class BooleanSecurityExpression : SecurityExpression
{
private readonly bool _result;
public BooleanSecurityExpression(bool expression)
{
_result = expression;
}
public override bool Eval()
{
return _result;
}
}
internal class CompanySecurityExpression : SecurityExpression
{
private readonly string _company;
public CompanySecurityExpression(string company)
{
_company = company;
}
public override bool Eval()
{
return (WhereverYouGetUser).Company == company;
}
}
You can add as many custom expressions as you need. The infrastructure is a bit complicated, but then usage is really simple:
public ActionResult AcmeOnlyAction()
{
SecurityGuard.ExceptionIf(s => s.UserWorksForCompany("Acme"));
}
You can also chain the expression, and use it as a condition in view fro example (using SecurityGuard.Require()).
Sry for long post, hope this helps.