Passing Func as an attribute parameter to secure MVC routes - c#

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.

Related

How should I return a generic response and response code from all functions in a .NET MVC app?

I want to be able to return a generic response from function calls in the business layer of my MVC application. Most of the time I see an object create function look like this
public int Create(ICNUser item)
{
return this._repository.Create(item);
}
public void Update(ICNUser item)
{
this._repository.Create(item);
}
In this case the _repository is a repository that wraps entity framework.
This works great for a lot of cases but I want more information to be returned and I want to have a success/failure variable and a response code for why this action failed validation. I want to optionally be able to return the inserted object or a selected object.
An example would be a create user function that returns an email can't be blank error and or a user already exists error and based on the error I show the user a different message.
The problem I'm running into is I want to have unit tests cover all of the possible response codes from a function without me having to go look at the code and try to figure out what the possible return values can be. What I'm doing feels like an anti-pattern. Is there a better way to accomplish all of this?
This is what I have now.
public IGenericActionResponse<ICNUser> Create(ICNUser item)
{
return this._repository.Create(item);
}
public IGenericActionResponse Update(ICNUser item)
{
return this._repository.Update(item);
}
Interfaces
namespace Web.ActionResponses
{
public enum ActionResponseCode
{
Success,
RecordNotFound,
InvalidCreateHash,
ExpiredCreateHash,
ExpiredModifyHash,
UnableToCreateRecord,
UnableToUpdateRecord,
UnableToSoftDeleteRecord,
UnableToHardDeleteRecord,
UserAlreadyExists,
EmailCannotBeBlank,
PasswordCannotBeBlank,
PasswordResetHashExpired,
AccountNotActivated,
InvalidEmail,
InvalidPassword,
InvalidPageAction
}
public interface IGenericActionResponse
{
bool RequestSuccessful { get; }
ActionResponseCode ResponseCode { get; }
}
public interface IGenericActionResponse<T>
{
bool RequestSuccessful { get; }
bool RecordIsNull{get;}
ActionResponseCode ResponseCode { get; }
}
}
implementations
namespace Web.ActionResponses
{
public class GenericActionResponse<T> : IGenericActionResponse<T>
{
private bool _requestSuccessful;
private ActionResponseCode _actionResponseCode;
public T Item { get; set; }
public GenericActionResponse(bool success, ActionResponseCode actionResponseCode, T item)
{
this._requestSuccessful = success;
this._actionResponseCode = actionResponseCode;
this.Item = item;
}
public GenericActionResponse(bool success, ActionResponseCode actionResponseCode)
{
this._requestSuccessful = success;
this._actionResponseCode = actionResponseCode;
this.Item = default(T);
}
public bool RecordIsNull
{
get
{
return this.Item == null;
}
}
public bool RequestSuccessful
{
get
{
return this._requestSuccessful;
}
}
public ActionResponseCode ResponseCode
{
get
{
return this._actionResponseCode;
}
}
}
public class GenericActionResponse : IGenericActionResponse
{
private bool _requestSuccessful;
private ActionResponseCode _actionResponseCode;
public GenericActionResponse(bool success, ActionResponseCode actionResponseCode)
{
this._requestSuccessful = success;
this._actionResponseCode = actionResponseCode;
}
public bool RequestSuccessful
{
get
{
return this._requestSuccessful;
}
}
public ActionResponseCode ResponseCode
{
get
{
return this._actionResponseCode;
}
}
}}
MVC app
public ActionResult ValidateResetHash(string passwordResetHash)
{
IGenericActionResponse result = (IGenericActionResponse)this._userManager.IsValidPasswordResetHash(passwordResetHash);
if (result.RequestSuccessful)
{
Models.PasswordChangeModel model = new Models.PasswordChangeModel();
model.PasswordResetHash = passwordResetHash;
return View("~/Areas/Public/Views/ResetPassword/PasswordChangeForm.cshtml", model);
}
else
{
switch (result.ResponseCode)
{
case ActionResponseCode.RecordNotFound:
{
FermataFish.Models.GenericActionModel responseModel = new FermataFish.Models.GenericActionModel(true, "/Login", "Login", "You have submitted an invalid password reset link.", false);
return View("~/Views/Shared/GenericAction.cshtml", responseModel);
}
case ActionResponseCode.PasswordResetHashExpired:
{
FermataFish.Models.GenericActionModel responseModel = new FermataFish.Models.GenericActionModel(true, "/ResetPassword", "Reset Password", "You have submitted an expired password reset link. You must reset your password again to change it.", false);
return View("~/Views/Shared/GenericAction.cshtml", responseModel);
}
default:
{
FermataFish.Models.GenericActionModel responseModel = new FermataFish.Models.GenericActionModel(true, "/", "Home", "An unknown error has occured. The system administrator has been notified. Error code:" + Enum.GetName(typeof(ActionResponseCode), result.ResponseCode), false);
return View("~/Views/Shared/GenericAction.cshtml", responseModel);
}
}
}
}
The switch statement in your ValidateResetHash response is a tad code smelly. This would suggest to me that you may benefit from the use of a subclassable enum. The subclassable enum would map action response codes or types to return views with models. Here is a compiling example of how to use this.
First some class fills I used to get a compiling example:
public class GenericActionModel
{
private bool v1;
private string v2;
private string v3;
private string v4;
private bool v5;
protected GenericActionModel() {}
public GenericActionModel(bool v1, string v2, string v3, string v4, bool v5)
{
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
this.v4 = v4;
this.v5 = v5;
}
}
public class ActionResult
{
private GenericActionModel responseModel;
private string v;
public ActionResult(string v, GenericActionModel responseModel)
{
this.v = v;
this.responseModel = responseModel;
}
}
public class PasswordChangeModel : GenericActionModel
{
public object PasswordResetHash
{
get;
set;
}
}
public interface IUserManager
{
Response IsValidPasswordResetHash(string passwordResetHash);
}
Next some infrastructure(framework) classes (I'm using StringEnum base class from the AtomicStack project for the ResponseEnum base class):
public abstract class Response
{
public abstract string name { get; }
}
public class Response<TResponse> : Response where TResponse : Response<TResponse>
{
private static string _name = typeof(TResponse).Name;
public override string name => _name;
}
// Base ResponseEnum class to be used by more specific enum sets
public abstract class ResponseEnum<TResponseEnum> : StringEnum<TResponseEnum>
where TResponseEnum : ResponseEnum<TResponseEnum>
{
protected ResponseEnum(string responseName) : base(responseName) {}
public abstract ActionResult GenerateView(Response response);
}
Here are some sample responses:
public class HashValidated : Response<HashValidated>
{
public string passwordResetHash;
}
public class InvalidHash : Response<InvalidHash> {}
public class PasswordResetHashExpired : Response<PasswordResetHashExpired> {}
public class Unexpected : Response<Unexpected> {}
A sample subclassable enum mapping the sample responses would look something like this:
public abstract class ValidateHashResponses : ResponseEnum<ValidateHashResponses>
{
public static readonly ValidateHashResponses HashOk = HashValidatedResponse.instance;
public static readonly ValidateHashResponses InvalidHash = InvalidHashResponse.instance;
public static readonly ValidateHashResponses PasswordResetHashExpired = PasswordResetHashExpiredResponse.instance;
public static readonly ValidateHashResponses Default = DefaultResponse.instance;
private ValidateHashResponses(string responseName) : base(responseName) {}
protected abstract class ValidateHashResponse<TValidateHashResponse, TResponse> : ValidateHashResponses
where TValidateHashResponse : ValidateHashResponse<TValidateHashResponse, TResponse>, new()
where TResponse : Response<TResponse>
{
public static TValidateHashResponse instance = new TValidateHashResponse();
private static string name = Response<TResponse>.Name;
protected ValidateHashResponse() : base(name) {}
}
protected class HashValidatedResponse : ValidateHashResponse<HashValidatedResponse, HashValidated>
{
public override ActionResult GenerateView(Response response)
{
PasswordChangeModel model = new PasswordChangeModel();
model.PasswordResetHash = ((HashValidated) response).passwordResetHash;
return new ActionResult("~/Areas/Public/Views/ResetPassword/PasswordChangeForm.cshtml", model);
}
}
protected class InvalidHashResponse : ValidateHashResponse<InvalidHashResponse, InvalidHash>
{
public override ActionResult GenerateView(Response response)
{
GenericActionModel responseModel = new GenericActionModel(true, "/Login", "Login", "You have submitted an invalid password reset link.", false);
return new ActionResult("~/Views/Shared/GenericAction.cshtml", responseModel);
}
}
protected class PasswordResetHashExpiredResponse : ValidateHashResponse<PasswordResetHashExpiredResponse, PasswordResetHashExpired>
{
public override ActionResult GenerateView(Response response)
{
GenericActionModel responseModel = new GenericActionModel(true, "/ResetPassword", "Reset Password", "You have submitted an expired password reset link. You must reset your password again to change it.", false);
return new ActionResult("~/Views/Shared/GenericAction.cshtml", responseModel);
}
}
protected class DefaultResponse : ValidateHashResponses
{
public static DefaultResponse instance = new DefaultResponse();
private DefaultResponse() : base("Default") {}
public override ActionResult GenerateView(Response response)
{
GenericActionModel responseModel = new GenericActionModel(true, "/", "Home", "An unknown error has occured. The system administrator has been notified. Error code:" + response.name, false);
return new ActionResult("~/Views/Shared/GenericAction.cshtml", responseModel);
}
}
}
Implementing the SampleController:
public class SampleController
{
private IUserManager _userManager;
public ActionResult ValidateResetHash(string passwordResetHash)
{
Response result = this._userManager.IsValidPasswordResetHash(passwordResetHash);
var resultType = ValidateHashResponses.TrySelect(result.name,ValidateHashResponses.Default);
return resultType.GenerateView(result);
}
}
Tweak the code above to fit your situation.
If you want to allow others to extend the ValidateHashResponses enum, you can make the constructor protected instead of private. They can then extend ValidateHashResponses and add their own additional enum values.
The point of using the subclassable enum, it to take adavantage of the TrySelect method that resolves responses to a specific enum value. Then we call the GenerateView method on the enum value to generate a view.
Another benefit of the enum is that if you need to have other decisions made based on the enum value, you simply add another abstract method to the enum and all value definitions will be forced to implement the new abstract method, unlike traditional enum/switch statement combinations where new enum values are not required to have cases added and where one may forget to revisit all of the switch statements where the enum was used.
DISCLAIMER:
I'm am the author of the AtomicStack project. Feel free to take the Subclassable enum class code from the project if you feel it would suit your needs.
UPDATE:
If you want to inject the response enum, you should create an IResponseHandler adapter interface with a GenerateViewForResponse type method and provide a concrete implementation that consumes the ValidateHashResponses enum.

How to "send" a method to inside another method?

I have the following:
public class Mail {
public String Obfuscate(String email) {
return email.Replace("#", "at").Replace(".", "dot");
}
}
I am calling the method Obfuscate in a class, as follows:
public class Resolver {
public Data GetData () {
return new Data { Email = new Mail().Obfuscate(myEmail) };
}
public String Translate(string value) { /* Some Code */ }
}
The problem is that Obfuscate does the replacement in English: # > at, . > dot
But in the Resolver class the method Translate does exactly what I need ...
How can I "pass" the Translate method to the Obfuscate method so this one uses it to translate # and . to at and dot in the current language?
So the code line inside Obfuscate:
return email.Replace("#", "at").Replace(".", "dot");
Would be become:
return email.Replace("#", Translate("#")).Replace(".", Translate("."));
Where Translate would be the method that I am "passing" to it.
Than You,
Miguel
Consider a different design:
public interface ITranslator
{
string Translate(string s);
}
public class Obfuscator
{
public Obfuscator(ITranslator translator)
{
this.translator = translator;
}
public string Obfuscate(string email)
{
var at = translator.Translate("at");
var dot = translator.Translate("dot");
return email.Replace("#", at).Replace(".", dot);
}
private ITranslator translator;
}
public class EnglishTranslator : ITranslator
{
public string Translate(string s)
{
return s;
}
}
public class PolishTranslator : ITranslator
{
public PolishTranslator() //or `FileInfo dictionaryFile` parameter perhaps
{
// for simplicity
translations = new Dictionary<string, string>();
translations.Add("at", "malpa");
translations.Add("dot", "kropka");
}
public string Translate(string s)
{
return translations[s];
}
private Dictionary<string, string> translations;
}
However you really should consider using a ResourceManager. Resource related mechanisms are designed to deal with translations.
I think #BartoszKP's answer is the right design decision. For completeness, here's how to do what you asked.
Change Mail to take a Func<string,string>:
public class Mail {
public String Obfuscate(String email, Func<string,string> translate) {
return email.Replace("#", translate("at")).Replace(".", translate("dot"));
}
}
And pass your Translate method to it:
public class Resolver {
public Data GetData () {
return new Data { Email = new Mail().Obfuscate(myEmail, Translate) };
}
public String Translate(string value) { /* Some Code */ }
}

Modify existing WCF communication object

This is how I used to make method calls:
SvcHelper.Using<SomeWebServiceClient>(proxy =>
{
proxy.SomeMethod();
}
public class SvcHelper
{
public static void Using<TClient>(Action<TClient> action) where TClient : ICommunicationObject, IDisposable, new()
{
}
}
This is how I make method calls:
ChannelFactory<ISomethingWebService> cnFactory = new ChannelFactory<ISomethingWebService>("SomethingWebService");
ISomethingWebService client = cnFactory.CreateChannel();
using (new OperationContextScope((IContextChannel)client))
{
client.SomeMethod();
}
My question is: Instead of replacing every instance of my original method call approach; Is there a way to modify my SvcHelper and do the creation of the channel in the SvcHelper constructor and then simply pass the interface like the following:
SvcHelper.Using<ISomethingWebService>(client =>
{
client.SomeMethod();
}
Hope this makes sense and thanks in advance.
First, you don't want to create a new ChannelFactory<T> every call to the Using helper method. They are the most costly thing to construct in the WCF universe. So, at bare minimum, you will want to use a caching approach there.
Second, you don't want to tie yourself to "client" types at all anymore. Just work straight with the service contract interfaces.
Starting from what you've got, here's where I'd go based on how I've done this in the past:
public class SvcHelper
{
private static ConcurrentDictionary<ChannelFactoryCacheKey, ChannelFactory> ChannelFactories = new ConcurrentDictionary<ChannelFactoryCacheKey, ChannelFactory>();
public static void Using<TServiceContract>(Action<TServiceContract> action) where TServiceContract : class
{
SvcHelper.Using<TServiceContract>(action, "*");
}
public static void Using<TServiceContract>(Action<TServiceContract> action, string endpointConfigurationName) where TServiceContract : class
{
ChannelFactoryCacheKey cacheKey = new ChannelFactoryCacheKey(typeof(TServiceContract), endpointConfigurationName);
ChannelFactory<TServiceContract> channelFactory = (ChannelFactory<TServiceContract>)SvcHelper.ChannelFactories.GetOrAdd(
cacheKey,
missingCacheKey => new ChannelFactory<TServiceContract>(missingCacheKey.EndpointConfigurationName));
TServiceContract typedChannel = channelFactory.CreateChannel();
IClientChannel clientChannel = (IClientChannel)typedChannel;
try
{
using(new OperationContextScope((IContextChannel)typedChannel))
{
action(typedChannel);
}
}
finally
{
try
{
clientChannel.Close();
}
catch
{
clientChannel.Abort();
}
}
}
private sealed class ChannelFactoryCacheKey : IEquatable<ChannelFactoryCacheKey>
{
public ChannelFactoryCacheKey(Type channelType, string endpointConfigurationName)
{
this.channelType = channelType;
this.endpointConfigurationName = endpointConfigurationName;
}
private Type channelType;
public Type ChannelType
{
get
{
return this.channelType;
}
}
private string endpointConfigurationName;
public string EndpointConfigurationName
{
get
{
return this.endpointConfigurationName;
}
}
public bool Equals(ChannelFactoryCacheKey compareTo)
{
return object.ReferenceEquals(this, compareTo)
||
(compareTo != null
&&
this.channelType == compareTo.channelType
&&
this.endpointConfigurationName == compareTo.endpointConfigurationName);
}
public override bool Equals(object compareTo)
{
return this.Equals(compareTo as ChannelFactoryCacheKey);
}
public override int GetHashCode()
{
return this.channelType.GetHashCode() ^ this.endpointConfigurationName.GetHashCode();
}
}
}
This should work:
public class SvcHelper
{
public static void Using<TClient>(Action<TClient> action) where TClient : ICommunicationObject, IDisposable
{
ChannelFactory<TClient> cnFactory = new ChannelFactory<TClient>("SomethingWebService");
TClient client = cnFactory.CreateChannel();
using (new OperationContextScope((IContextChannel)client))
{
action(client);
}
}
}

Implementing conditional in a fluent interface

I've been trying to implement a fluent interface for a set of rules in my system. What I am trying to accomplish is this
TicketRules
.RequireValidation()
.When(quartType => quartType == QuartType.Before).TotalMilageIs(64)
.When(quartType => quartType == QuartType.After).TotalMilageIs(128);
However, I have trouble implementing the When conditional how I intended to be. Currently, I need to call When() twice like in this snippet:
rules.When(param => param.Remarque == "Test").TotalMilageIs(100);
rules.When(param => param.Remarque == "Other").TotalMilageIs(50);
var params1 = new AddTicketParameters() { Remarque = "Test" };
var params2 = new AddTicketParameters() { Remarque = "Other" };
rules.ExecuteWith(params1);
Assert.That(ticket.TotalMilage, Is.EqualTo(100));
rules.ExecuteWith(params2);
Assert.That(ticket.TotalMilage, Is.EqualTo(50));
My TicketRules class looks this:
[EditorBrowsable(EditorBrowsableState.Never)]
public class TicketRules : ITicketRule, IHideObjectMembers
{
private Ticket theTicket;
public Ticket Ticket
{
set
{
theTicket = value;
}
}
private List<ITicketRule> allRules = new List<ITicketRule>();
public TicketRules()
{
}
public TicketRules(Ticket ticket)
{
theTicket = ticket;
}
public void Execute()
{
ExecuteWith(null, null);
}
public void ExecuteWith(AddTicketParameters param)
{
ExecuteWith(param, null);
}
public virtual void ExecuteWith(AddTicketParameters param, Ticket outsideTicket)
{
foreach (ITicketRule rule in allRules)
{
rule.ExecuteWith(param, theTicket ?? outsideTicket);
}
}
public TicketRules RequireValidation()
{
CreateModifierRule(ticket => ticket.NeedValidation = true);
return this;
}
public TicketRules TotalMilageIs(int milage)
{
CreateModifierRule(ticket => ticket.TotalMilage = milage);
return this;
}
private void CreateModifierRule(Action<Ticket> function)
{
AddRule(new ModifierTicketRule(function));
}
internal void AddRule(ITicketRule rule)
{
allRules.Add(rule);
}
public WhenClauseTicketRule When(Predicate<AddTicketParameters> predicate)
{
WhenClauseTicketRule whenClause = new WhenClauseTicketRule();
whenClause.Predicate = predicate;
AddRule(whenClause);
return whenClause;
}
public TicketRules UseStandardFormulaForTotalMilageAndTime()
{
AddRule(new StandardFormulaTicketRule());
return this;
}
public TicketRules EnsureMinimumMilageIs(int milage)
{
AddRule(new EnsureMinimumMilageTicketRule(milage));
return this;
}
}
the ITicketRules
internal interface ITicketRule : IHideObjectMembers
{
void ExecuteWith(AddTicketParameters param, Ticket ticket);
}
I also need to support the subclasses of AddTicketParameters in the When clause (I've though maybe using generics for that part). I'm posting here because I'm all confused in my design and the Martin Fowler articles confuse me even more.
This is known as the finishing problem when method chaining
Try this
TicketRules
.RequireValidation()
.When(quartType => quartType == QuartType.Before,
rule => rule.TotalMilageIs(64))
.When(quartType => quartType == QuartType.After,
rule => rule.TotalMilageIs(128));
It looks a little odd at first, but it wraps your conditionals into a different scope so you can conditionally execute them. Think about it like creating your own if block. By closing it, you know when you can "finish" a sub statement.

Better way of doing strongly-typed ASP.NET MVC sessions

I am developing an ASP.NET MVC project and want to use strongly-typed session objects. I have implemented the following Controller-derived class to expose this object:
public class StrongController<_T> : Controller
where _T : new()
{
public _T SessionObject
{
get
{
if (Session[typeof(_T).FullName] == null)
{
_T newsession = new _T();
Session[typeof(_T).FullName] = newsession;
return newsession;
}
else
return (_T)Session[typeof(_T).FullName];
}
}
}
This allows me to define a session object for each controller, which is in line with the concept of controller isolation. Is there a better/more "correct" way, perhaps something that is officially supported by Microsoft?
This way other objects won't have access to this object (e.g. ActionFilter). I do it like this:
public interface IUserDataStorage<T>
{
T Access { get; set; }
}
public class HttpUserDataStorage<T>: IUserDataStorage<T>
where T : class
{
public T Access
{
get { return HttpContext.Current.Session[typeof(T).FullName] as T; }
set { HttpContext.Current.Session[typeof(T).FullName] = value; }
}
}
Then, I can either inject IUserDataStorage into controller's constructor, or use ServiceLocator.Current.GetInstance(typeof(IUserDataStorage<T>)) inside ActionFilter.
public class MyController: Controller
{
// automatically passed by IoC container
public MyController(IUserDataStorage<MyObject> objectData)
{
}
}
Of course for cases when all controllers need this (e.g. ICurrentUser) you may want to use property injection instead.
This might be better for what you want. I would just create an extension method that can access your session. The added benefit to the extension method is that you no longer have to inherit from a controller, or have to inject a dependency that really isn't necessary to begin with.
public static class SessionExtensions {
public static T Get<T>(this HttpSessionBase session, string key) {
var result;
if (session.TryGetValue(key, out result))
{
return (T)result;
}
// or throw an exception, whatever you want.
return default(T);
}
}
public class HomeController : Controller {
public ActionResult Index() {
//....
var candy = Session.Get<Candy>("chocolate");
return View();
}
}
http://codingsmith.co.za/a-better-way-of-working-with-httpcontext-session-in-mvc/ (apologies for the colours on my blog was tooling around with themes and just havent fixed it yet)
public interface ISessionCache
{
T Get<T>(string key);
void Set<T>(string key, T item);
bool contains(string key);
void clearKey(string key);
T singleTon<T>(String key, getStuffAction<T> actionToPerform);
}
public class InMemorySessionCache : BaseSessionCache
{
Dictionary<String, Object> _col;
public InMemorySessionCache()
{
_col = new Dictionary<string, object>();
}
public T Get<T>(string key)
{
return (T)_col[key];
}
public void Set<T>(string key, T item)
{
_col.Add(key, item);
}
public bool contains(string key)
{
if (_col.ContainsKey(key))
{
return true;
}
return false;
}
public void clearKey(string key)
{
if (contains(key))
{
_col.Remove(key);
}
}
}
public class HttpContextSessionCache : BaseSessionCache
{
private readonly HttpContext _context;
public HttpContextSessionCache()
{
_context = HttpContext.Current;
}
public T Get<T>(string key)
{
object value = _context.Session[key];
return value == null ? default(T) : (T)value;
}
public void Set<T>(string key, T item)
{
_context.Session[key] = item;
}
public bool contains(string key)
{
if (_context.Session[key] != null)
{
return true;
}
return false;
}
public void clearKey(string key)
{
_context.Session[key] = null;
}
}
i came up with that a few years ago and it works fine. same basic idea as everyone else i guess, why microsoft dont just implement this as standard eludes me.
I generally use this for a session key and then explicitly add objects as needed. The reason for this is it's a clean way to do it and I find that you want to keep the number of objects in session to a minimum.
This particular approach brings together forms authentication and user session into one place so you can add objects and forget about it. The argument could be made that it is a big verbose, but it does prevent any double up and you shouldn't have too many objects in session.
The following can exist in a core library or wherever you want.
/// <summary>
/// Provides a default pattern to access the current user in the session, identified
/// by forms authentication.
/// </summary>
public abstract class MySession<T> where T : class
{
public const string USERSESSIONKEY = "CurrentUser";
/// <summary>
/// Gets the object associated with the CurrentUser from the session.
/// </summary>
public T CurrentUser
{
get
{
if (HttpContext.Current.Request.IsAuthenticated)
{
if (HttpContext.Current.Session[USERSESSIONKEY] == null)
{
HttpContext.Current.Session[USERSESSIONKEY] = LoadCurrentUser(HttpContext.Current.User.Identity.Name);
}
return HttpContext.Current.Session[USERSESSIONKEY] as T;
}
else
{
return null;
}
}
}
public void LogOutCurrentUser()
{
HttpContext.Current.Session[USERSESSIONKEY] = null;
FormsAuthentication.SignOut();
}
/// <summary>
/// Implement this method to load the user object identified by username.
/// </summary>
/// <param name="username">The username of the object to retrieve.</param>
/// <returns>The user object associated with the username 'username'.</returns>
protected abstract T LoadCurrentUser(string username);
}
}
Then implement this in the following class namespaced to the root of your project (I usually put it in a code folder on mvc projects):
public class CurrentSession : MySession<PublicUser>
{
public static CurrentSession Instance = new CurrentSession();
protected override PublicUser LoadCurrentUser(string username)
{
// This would be a data logic call to load a user's detail from the database
return new PublicUser(username);
}
// Put additional session objects here
public const string SESSIONOBJECT1 = "CurrentObject1";
public const string SESSIONOBJECT2 = "CurrentObject2";
public Object1 CurrentObject1
{
get
{
if (Session[SESSIONOBJECT1] == null)
Session[SESSIONOBJECT1] = new Object1();
return Session[SESSIONOBJECT1] as Object1;
}
set
{
Session[SESSIONOBJECT1] = value;
}
}
public Object2 CurrentObject2
{
get
{
if (Session[SESSIONOBJECT2] == null)
Session[SESSIONOBJECT2] = new Object2();
return Session[SESSIONOBJECT2] as Object2;
}
set
{
Session[SESSIONOBJECT2] = value;
}
}
}
FINALLY
The big advantage of explicitly declaring what you want in session is that you can reference this absolutely anywhere in your mvc application including the views. Just reference it with:
CurrentSession.Instance.Object1
CurrentSession.Instance.CurrentUser
Again a little less generic than other approaches, but really really clear what's going on, no other rigging or dependancy injection and 100% safe to the request context.
On another note, the dicionary approaches are cool, but you still end up with strings all over the place to reference stuff. You could rig it with enums or something, but I prefer the strong typing and set and forget of the above approach.
Yes, it's years after this question was asked and there are other ways to do this... but in case anyone else shows up looking for something that combines the approaches above into an appealing one stop shop (at least one that appealed to my team and I...) Here's what we use.
public enum SessionKey { CurrentUser, CurrentMember, CurrentChart, CurrentAPIToken, MemberBanner }
public static class SessionCache {
public static T Get<T>(this HttpSessionStateBase session, SessionKey key)
{
var value = session[key.ToString()];
return value == null ? default(T) : (T) value;
}
public static void Set<T>(this HttpSessionStateBase session, SessionKey key, T item)
{
session[key.ToString()] = item;
}
public static bool contains(this HttpSessionStateBase session, SessionKey key)
{
if (session[key.ToString()] != null)
return true;
return false;
}
public static void clearKey(this HttpSessionStateBase session, SessionKey key)
{
session[key.ToString()] = null;
}
}
Then in your controllers you can do your thing with your session variables in a more strongly typed way.
// get member
var currentMember = Session.Get<Member>(SessionKey.CurrentMember);
// set member
Session.Set<Member>(SessionKey.CurrentMember, currentMember);
// clear member
Session.ClearKey(SessionKey.CurrentMember);
// get member if in session
if (Session.Contains(SessionKey.CurrentMember))
{
var current = Session.Get<Member>(SessionKey.CurrentMember);
}
Hope this helps someone!

Categories