I posted a more detailed version of this question on code review exchange, check this link. I later got my implementation reviewed by one of my colleagues and he suggested a different approach to solve the problem. So this question is to help me find out, which of the two approaches should be picked and why?
If this is not suitable for StackOverflow and should still be in CodeReviewExchange, please let me know and I will move it there.
Also I want to say this in the beginning itself because I was criticized for this in my earlier post. The actual code is different than what I have provided here, its more complex so I had to simplify it to ask just what I want to figure out i.e. inheritance vs composition, which is better here?
Here is my set of classes:
public abstract class ConditionBuilder<TContext> : IConditionBuilder where TContext : FieldSearchContext
{
public virtual bool QuotedValues { get; set; } = true;
public abstract string OperatorSymbol { get; }
public string BuildCondition(SearchCondition searchCondition)
{
var conditionBuilder = new StringBuilder();
var context = searchCondition.GetContext<TContext>();
conditionBuilder.Append(context.FieldId);
conditionBuilder.Append(OperatorSymbol);
conditionBuilder.Append(GetValue(context));
return conditionBuilder.ToString();
}
public abstract bool CanHandle(FilterAction filterAction);
public abstract object GetValue(TContext context);
}
public class TextLikeConditionBuilder : ConditionBuilder<TextContext>
{
public override string OperatorSymbol => " LIKE ";
public override bool CanHandle(FilterAction action) => action == FilterAction.TextLike;
public override object GetValue(TextContext context)
{
if (context.Text == null)
{
return null;
}
return string.Concat("%", context.Text, "%");
}
}
public class TextEqualsConditionBuilder : ConditionBuilder<TextContext>
{
public override string OperatorSymbol => " = ";
public override bool CanHandle(FilterAction action) => action == FilterAction.TextEqual;
public override object GetValue(TextContext context)
{
return context.Text;
}
}
In their default implementation the classes build a WHERE clause for SQL. These classes are consumed by other developers and can be overridden to build WHERE clause that is not necessarily meant for Database for example one can override protected virtual StringBuilder GetCondition(SearchFilterCondition filterCondition, TContext context) to build a QUERY for Elasticsearch also.
Coming back to the topic, in one particular instance where these classes are used with their default behavior, I wanted to delimit the FieldId so as to ensure that it works when FieldId contains spaces and special characters. This is how I implemented the solution:
public interface IDelimitedIdentifier
{
string Delimit(string input);
}
internal class SqlServerDelimitedIdentifier : IDelimitedIdentifier
{
public string Delimit(string input)
{
return "[" + input.Replace("]", "]]") + "]";
}
}
internal class OracleDelimitedIdentifier : IDelimitedIdentifier
{
public string Delimit(string input)
{
return "\"" + input + "\"";
}
}
public abstract class ConditionBuilder<TContext> : IConditionBuilder where TContext : FieldSearchContext
{
public virtual bool QuotedValues { get; set; } = true;
public abstract string OperatorSymbol { get; }
public string BuildCondition(SearchCondition searchCondition)
{
var conditionBuilder = new StringBuilder();
var context = searchCondition.GetContext<TContext>();
conditionBuilder.Append(SanitizeFieldId(context.FieldId));
conditionBuilder.Append(OperatorSymbol);
conditionBuilder.Append(GetValue(context));
return conditionBuilder.ToString();
}
public abstract bool CanHandle(FilterAction filterAction);
public abstract object GetValue(TContext context);
protected virtual string SanitizeFieldId(string fieldId)
{
return _delimitedIdentifier.Delimit(fieldId);
}
}
public class SanitizedFieldConditionBuilder<TContext> : ConditionBuilder<TContext> where TContext : FieldSearchContextBase
{
private readonly ConditionBuilder<TContext> _baseConditionBuilder;
private readonly IDelimitedIdentifier _delimitedIdentifier;
public SanitizedFieldConditionBuilder(ConditionBuilder<TContext> baseConditionBuilder, IDelimitedIdentifier delimitedIdentifier)
{
QuotedValues = false;
_baseConditionBuilder = baseConditionBuilder;
_delimitedIdentifier = delimitedIdentifier;
}
public override string OperatorSymbol => _baseConditionBuilder.OperatorSymbol;
public override bool CanHandle(SearchFilterAction action) => _baseConditionBuilder.CanHandle(action);
public override object GetValue(TContext context) => _baseConditionBuilder.GetValue(context);
protected override string SanitizeFieldId(string fieldId)
{
return _delimitedIdentifier.Delimit(fieldId);
}
}
public static class ConditionBuilderExtensions
{
public static SanitizedFieldConditionBuiler<TContext> SanitizeField<TContext>(this ConditionBuilder<TContext> source, IColumnSanitizer columnSanitizer) where TContext : FieldSearchContext
{
return new SanitizedFieldConditionBuiler<TContext>(source, columnSanitizer);
}
public static ParameterizedConditionBuilder<TContext> WithParameters<TContext>(this ConditionBuilder<TContext> source,
ParameterCollection parameterCollection, bool parameterizeNullValues = false) where TContext : FieldSearchContext
{
return new ParameterizedConditionBuilder<TContext>(source, parameterCollection, parameterizeNullValues);
}
}
The classes can be instantiated as shown below:
class Program
{
static void Main(string[] args)
{
var conditionBuilders = new List<IConditionBuilder>()
{
new TextEqualsConditionBuilder().SanitizeField(new SqlServerDelimitedIdentifier()),
new TextLikeConditionBuilder().SanitizeField(new SqlServerDelimitedIdentifier())
};
}
}
My colleague suggested to use composition instead of extension. Here is how the classes would look like as per his recommendation.
public abstract class ConditionBuilder<TContext> : IConditionBuilder where TContext : FieldSearchContext
{
private readonly IDelimitedIdentifier DelimitedIdentifier;
public virtual bool QuotedValues { get; set; } = true;
public abstract string OperatorSymbol { get; }
protected ConditionBuilder(IDelimitedIdentifier delimitedIdentifier)
{
DelimitedIdentifier = delimitedIdentifier;
}
public string BuildCondition(SearchCondition searchCondition)
{
var conditionBuilder = new StringBuilder();
var context = searchCondition.GetContext<TContext>();
conditionBuilder.Append(DelimitedIdentifier.Delimit(context.FieldId));
conditionBuilder.Append(OperatorSymbol);
conditionBuilder.Append(GetValue(context));
return conditionBuilder.ToString();
}
public abstract bool CanHandle(FilterAction filterAction);
public abstract object GetValue(TContext context);
}
public class TextLikeConditionBuilder : ConditionBuilder<TextContext>
{
public TextLikeConditionBuilder(IDelimitedIdentifier delimitedIdentifier) : base(delimitedIdentifier)
{
}
public override string OperatorSymbol => " LIKE ";
public override bool CanHandle(FilterAction action) => action == FilterAction.TextLike;
public override object GetValue(TextContext context)
{
if (context.Text == null)
{
return null;
}
return string.Concat("%", context.Text, "%");
}
}
public class TextEqualsConditionBuilder : ConditionBuilder<TextContext>
{
public TextEqualsConditionBuilder(IDelimitedIdentifier delimitedIdentifier) : base(delimitedIdentifier)
{
}
public override string OperatorSymbol => " = ";
public override bool CanHandle(FilterAction action) => action == FilterAction.TextEqual;
public override object GetValue(TextContext context)
{
if (context.Text == null)
{
return null;
}
return context.Text;
}
}
Here are my arguments for why it shouldn't be done as per what my colleague recommends doing:
IDelimitedIdentifier is very specific to database, why should it be moved inside the base class when the functionality can be supported using extension.
Extension reduces the number of changes otherwise adding that constructor would mean changing all derived classes and let me tell you that there are 15 odd deriving classes
Isn't adding a constructor against Open-Closed principle?
EDIT: Since so many people are concerned about the SQL Injection issue here, rather than answering what I asked, I am adding this additional code that takes care of it. I have also updated the ConditionBuilderExtensions class defined above.
public class ParameterizedConditionBuilder<TContext> : ConditionBuilder<TContext>
where TContext : FieldSearchContext
{
private readonly ConditionBuilder<TContext> baseConditionBuilder;
private readonly ParameterCollection parameterCollection;
private readonly bool parameterizeNullValues;
public override bool QuotedValues { get; set; } = false;
public ParameterizedConditionBuilder(ConditionBuilder<TContext> baseConditionBuilder,
ParameterCollection parameterCollection, bool parameterizeNullValues = false)
{
this.baseConditionBuilder = baseConditionBuilder;
this.parameterCollection = parameterCollection;
this.parameterizeNullValues = parameterizeNullValues;
}
public override bool CanHandle(FilterAction action) => baseConditionBuilder.CanHandle(action);
public override string OperatorSymbol => baseConditionBuilder.OperatorSymbol;
public override object GetValue(TContext context)
{
object val = baseConditionBuilder.GetValue(context);
if (val == null && !parameterizeNullValues)
{
return null;
}
string p = parameterCollection.AddParameter(val);
return p;
}
}
Here is how I build my conditions and then use them in DBCommand. Its immune to SQL Injection
private static DbCommand CreateDbCommand(Database database, MergeOperation mergeOperation, IList<SearchCondition> searchCondition)
{
var whereConditions = new List<string>();
ParameterCollection paramCollection = new ParameterCollection("{{{0}}}");
var conditionBuilders = new List<IConditionBuilder>()
{
new TextEqualsConditionBuilder().WithParameters(paramCollection).SanitizeField(new SqlSanitizer()),
new TextLikeConditionBuilder().WithParameters(paramCollection).SanitizeField(new SqlSanitizer())
};
foreach (var condition in searchCondition)
{
var context = condition.GetContext<FieldSearchContext>();
var conditionBuilder = conditionBuilders.FirstOrDefault(u => u.CanHandle(condition.FilterAction));
whereConditions.Add(conditionBuilder.BuildCondition(condition));
}
SqlBuilder sqlBuilder = new SqlBuilder();
sqlBuilder.SELECT("Id");
sqlBuilder.FROM("Students");
if (whereConditions.Count > 0)
{
sqlBuilder.WHERE(string.Join(Convert.ToString(" " + mergeOperation + " "), whereConditions), paramCollection.GetParameters().Select(pair => pair.Value).ToArray());
}
DbExtensions.Database sqlBuilderDb = new DbExtensions.Database(database.CreateConnection());
IDbCommand sqlBuilderCommand = sqlBuilderDb.CreateCommand(sqlBuilder);
DbCommand databaseCommand = database.GetSqlStringCommand(sqlBuilderCommand.CommandText);
if (sqlBuilderCommand.Parameters != null)
{
foreach (IDataParameter dataParameter in sqlBuilderCommand.Parameters)
{
database.AddInParameter(databaseCommand, dataParameter.ParameterName, dataParameter.DbType, dataParameter.Value);
}
}
return databaseCommand;
}
SqlBuilder is from a nuget package DBExtensions. I use Microsoft.Enterprise.Library.Data for database access.
EDIT 2: Removed the code that was subjected to SQL Injection
Related
I used this accepted answer to create an Enum of Guids.
public enum AccessRoles
{
[EnumGuid("2ED3164-BB48-499B-86C4-A2B1114BF1")]
SysAdmin =1,
[EnumGuid("A5690E7-1111-4AFB-B44D-1DF3AD66D435")]
Admin = 2,
[EnumGuid("30558C7-66D9-4189-9BD9-2B87D11190")]
OrgAdmin = 3,
}
class EnumGuid : Attribute
{
public Guid Guid;
public EnumGuid(string guid)
{
Guid = new Guid(guid);
}
}
I try check if a Guid is part of an enum, it throws an exception System.InvalidOperationException even though userId = 2ED3164-BB48-499B-86C4-A2B1114BF1 is a valid guid.
if(Enum.IsDefined(typeof(AccessRoles), userId))
{
}
I tried converting it to string and checking, but that time it does not throw an error but does not go inside the if loop.
if(Enum.IsDefined(typeof(AccessRoles), userId.ToString().ToUpper()))
{
}
So how do I fix it? Or is there a better way? I want to avoid the multiple if statements or a case statement and so what to use it as enums so they are reusable.
I would replace your enum with an immutable struct, and add a static class to hold all possible roles in the application:
public struct AccessRole
{
public AccessRole(Guid guid, int number, string name) : this()
{
Uuid = guid;
Number = number;
Name = name;
}
public Guid Uuid {get;}
public int Number {get;}
public string Name {get;}
}
Then you can add a static class for AccessRoles:
public static class AccessRoles
{
private static List<AccessRole> _roles;
static AccessRoles()
{
_roles = new List<AccessRole>();
// Here I populate it hard coded for the sample,
// but it should be populated from your database or config file
_roles.Add(new AccessRole(new Guid("2ED3164-BB48-499B-86C4-A2B1114BF1"), 1, "SysAdmin"));
_roles.Add(new AccessRole(new Guid("A5690E7-1111-4AFB-B44D-1DF3AD66D435"), 2, "Admin"));
_roles.Add(new AccessRole(new Guid("30558C7-66D9-4189-9BD9-2B87D11190"), 3, "OrgAdmin"));
}
public static AccessRole GetRole(Guid uuid)
{
return _roles.Find(r => r.Uuid == uuid);
}
public static AccessRole GetRole(int number)
{
return _roles.Find(r => r.Number == number);
}
public static AccessRole GetRole(string name)
{
return _roles.Find(r => r.Name == name);
}
}
Now all you have to do is change the way the _roles list is populated in the static constructor to either a database of a configuration file, and you're good to go.
Note that the AccessRoles provides static methods to get a search for a role by either property. It can be replaced with a single method that will get a predicate, but I think that this way it's more readable.
I would suggest a complete different approach, when working with fixed user roles.
Using an Enumeration you can achieve same and much more:
public abstract class UserRoleType : Enumeration<UserRoleType>
{
protected UserRoleType(int value, string displayName)
: base(value, displayName)
{}
public static readonly UserRoleType Unknown = new UnknownRoleType();
public static readonly UserRoleType Administrator = new AdministratorRoleType();
public static readonly UserRoleType System = new SystemRoleType();
public static readonly UserRoleType Moderator = new ModeratorRoleType();
public virtual bool CanCreateUser => false;
public virtual bool CanBlockUser => false;
public virtual bool CanResetUserPassword => false;
}
public sealed class UnknownRoleType : UserRoleType
{
public UnknownRoleType()
: base(0, "Unknown")
{ }
}
public sealed class AdministratorRoleType : UserRoleType
{
public AdministratorRoleType()
: base(10, "Administrator")
{}
public override bool CanCreateUser => true;
public override bool CanBlockUser => true;
public override bool CanResetUserPassword => true;
}
public sealed class SystemRoleType : UserRoleType
{
public SystemRoleType()
: base(20, "System")
{ }
public override bool CanBlockUser => true;
public override bool CanResetUserPassword => true;
}
public sealed class ModeratorRoleType : UserRoleType
{
public ModeratorRoleType()
: base(40, "Moderator")
{ }
public override bool CanBlockUser => true;
}
By setting abstract/virtual properties on the abstract UserRoleType, you system only have operate on the abstract class.
When your user context is being initialized (on login), you simply find the user role by
var roleTypeValueFromDatabase = 10;
var roleType = UserRoleType.FromValueOrDefault(roleTypeValueFromDatabase, UserRoleType.Unknown);
if (roleType.CanCreateUser)
{
// create user..
}
// Find roles with specific rights
var rolesThatCanResetPassword = UserRoleType.GetAll().Where(urt => urt.CanResetUserPassword);
About the Enumeration class, there are several implementation of them on github/nuget.
Mine is for .Net core v2 - https://github.com/microknights/Collections
with Nuget: Install-Package MicroKnights.Collections
Headspring - https://github.com/HeadspringLabs/Enumeration
source files only.
public enum AccessRoles
{
SysAdmin = 1,
Admin = 2,
OrgAdmin = 3
}
public class Attributes
{
public static Dictionary<int, Guid> Attribute = new Dictionary<int, Guid>()
{
{(int)AccessRoles.SysAdmin, Guid.Parse("6D18698C-04EC-4E50-84DB-BE513D5875AC")},
{(int)AccessRoles.Admin, Guid.Parse("32E86718-7034-4640-9076-A60B9B6CA51A")},
{(int)AccessRoles.OrgAdmin, Guid.Parse("2CA39E37-8AEA-463F-AE14-E9D92AC5FB5E")}
};
}
Console.WriteLine(Attributes.Attribute[(int)AccessRoles.SysAdmin]);
Console.WriteLine(Attributes.Attribute[(int)AccessRoles.Admin]);
Console.WriteLine(Attributes.Attribute[(int)AccessRoles.OrgAdmin]);
Maybe passing the Guid values with System.ComponentModel.AmbientValueAttribute like so :
using System.ComponentModel;
public enum AccessRoles
{
[AmbientValue(typeof(Guid), "749e73c0-ba25-4f69-9f81-ec21d9942e52")]
SysAdmin = 1,
[AmbientValue(typeof(Guid), "39cc7e3d-db5f-4619-a577-e24cb89de5a7")]
Admin = 2,
[AmbientValue(typeof(Guid), "93902f8d-46d3-4b43-b684-b0ee66bbf7de")]
OrgAdmin = 3,
}
With an extension to obtain the AmbientValue:
using System.Reflection;
public static class EnumExtensions
{
public static object GetAmbientValue(this Enum enumVal)
{
Type type = enumVal.GetType();
MemberInfo[] memInfo = type.GetMember(enumVal.ToString());
object[] attributes = memInfo[0].GetCustomAttributes(typeof(AmbientValueAttribute), false);
if (attributes == null || attributes.Length == 0)
return default;
return ((AmbientValueAttribute)attributes[0]).Value;
}
}
And finally obtaining the Guid value like so :
var valGuid = (Guid)AccessRoles.SysAdmin.GetAmbientValue();
We are using HttpSessionStateBase to store messages in a set up similar to this working example:
public class HttpSessionMessageDisplayFetch : IMessageDisplayFetch
{
protected HttpSessionStateBase _session;
private IList<ICoreMessage> messages
{
get
{
if (_session[EchoCoreConstants.MESSAGE_KEY] == null)
_session[EchoCoreConstants.MESSAGE_KEY] = new List<ICoreMessage>();
return _session[EchoCoreConstants.MESSAGE_KEY] as IList<ICoreMessage>;
}
}
public HttpSessionMessageDisplayFetch()
{
if (HttpContext.Current != null)
_session = new HttpSessionStateWrapper(HttpContext.Current.Session);
}
public void AddMessage(ICoreMessage message)
{
if (message != null)
messages.Add(message);
}
public IEnumerable<IResultPresentation> FlushMessagesAsPresentations(IResultFormatter formatter)
{
var mToReturn = messages.Select(m => m.GetPresentation(formatter)).ToList();
messages.Clear();
return mToReturn;
}
}
When we pass in a QualityExplicitlySetMessage (which inherits from ICoreMessage, see below) it is saved correctly to messages.
This is how the object looks after being inserted into the messages list, at the end of AddMessage(ICoreMessage message) above.
But when we come to access it after changing controllers the inherited member's properties are null, which causes a variety of null reference exceptions.
This is how the object now looks after we call FlushMessagesAsPresentations. I've commented out var mToReturn... as this tries to access one of these null ref properties.
I'd like to ask the following:
Why is the HttpSessionStateBase failing to capture these values taken
by the inherited type?
Is this an issue in saving to the HttpSession or in retrieving?
Is this anything to do with, as I suspect, inheritance?
Or is the fact I'm potentially calling a new controller that dependency injects the HttpSessionMessageDisplayFetch causing an issue?
I'm a first-time poster so please let me know if I'm making any kind of faux pas - Super keen to learn! Any input is very welcome.
Some potentially useful code snippets:
QualityExplicitlySetMessage
public class QualityExplicitlySetMessage : QualityChangeMessage
{
public QualityExplicitlySetMessage(IQPossession before, IQPossession after, IQEffect qEffect)
: base(before, after, qEffect)
{
IsSetToExactly = true;
}
}
QualityChangeMessage - Working example
public abstract class QualityChangeMessage : CoreMessage, IQualityChangeMessage
{
protected PossessionChange Change;
public PossessionChange GetPossessionChange()
{
return Change;
}
protected QualityChangeMessage(IQPossession before, IQPossession after, IQEffect qEffect)
{
Change = new PossessionChange(before, after, qEffect);
StoreQualityInfo(qEffect.AssociatedQuality);
}
public override IResultPresentation GetPresentation(IResultFormatter formatter)
{
return formatter.GetQualityResult(this);
}
#region IQualityChangeMessage implementation
public int LevelBefore
{
get { return Change.Before.Level; }
}
//... And so on with values dependent on the Change property.
}
CoreMessage - Working example
public abstract class CoreMessage : ICoreMessage
{
public string MessageType
{
get { return GetType().ToString(); }
}
public string ImageTooltip
{
get { return _imagetooltip; }
set { _imagetooltip = value; }
}
public string Image
{
get { return _image; }
set { _image = value; }
}
public int? RelevantQualityId { get; set; }
protected void StoreQualityInfo(Quality q)
{
PyramidNumberIncreaseLimit = q.PyramidNumberIncreaseLimit;
RelevantQualityId = q.Id;
RelevantQualityName = q.Name;
ImageTooltip = "<strong>" + q.Name + "</strong><br/>" + q.Description + "<br>" +
q.EnhancementsDescription;
Image = q.Image;
}
public virtual IResultPresentation GetPresentation(IResultFormatter formatter)
{
return formatter.GetResult(this);
}
}
UserController - Working example.
public partial class UserController : Controller
{
private readonly IMessageDisplayFetch _messageDisplayFetch;
public UserController(IMessageDisplayFetch messageDisplayFetch)
{
_messageDisplayFetch = messageDisplayFetch;
}
public virtual ActionResult MessagesForStoryletWindow()
{
var activeChar = _us.CurrentCharacter();
IEnumerable<IResultPresentation> messages;
messages = _messageDisplayFetch.FlushMessagesAsPresentations(_storyFormatter);
var vd = new MessagesViewData(messages)
{
Character = new CharacterViewData(activeChar),
};
return View(Views.Messages, vd);
}
}
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.
I have an interface
using ClassAbstractFactory;
public interface IPlugin
{
AbstractFactory GetFactory();
}
and an AbstractFactory
public abstract class AbstractFactory
{
public abstract AbstractCake CreateCake();
public abstract AbstractBox CreateBox();
}
public abstract class AbstractCake
{
public abstract void Interact(AbstractBox box);
}
public abstract class AbstractBox
{
}
and I have .dll that inherit AbstractCake
public class ChocolateCake : AbstractCake
{
private bool _isPacked;
private bool _isDecorated;
private string _nameOfCake;
public ChocolateCake()
{
_isPacked = false;
_isDecorated = false;
_nameOfCake = "Шоколадный";
}
public bool IsPacked
{
get { return _isPacked; }
}
public bool IsDecorated
{
get { return _isDecorated; }
}
public string NameOfCake { get; set; }
public override void Interact(AbstractBox box)
{
_isPacked = true;
}
}
I load dll like this:
public IPlugin LoadAssembly(string assemblyPath)
{
Assembly ptrAssembly = Assembly.LoadFile(assemblyPath);
foreach (Type item in ptrAssembly.GetTypes())
{
if (!item.IsClass) continue;
if (item.GetInterfaces().Contains(typeof(IPlugin)))
{
return (IPlugin)Activator.CreateInstance(item);
}
}
throw new Exception("Invalid DLL, Interface not found!");
}
List<IPlugin> list = new List<IPlugin>();
foreach (var assemblyPath in GetPathsListToDll())
{
list.Add(LoadAssembly(assemblyPath));
}
How can I acess to attributes in my ChocolateCake,to use them like
foreach (var str in list)
{
Boolean a = str.GetFactory().GetCake().CreateCake().IsPacked;
}
or like this
string a = str.GetFactory().GetCake().CreateCake().NameOfCake;
or like this
str.GetFactory().GetCake().CreateCake().NameOfCake("Something");
or like this
str.GetFactory().GetCake().CreateCake().IsDecorated(true);
The problem here is that the AbstractFactory has a method that returns AbstractCake, and AbstractCake itself has no properties at all. As it stands, you would need to downcast the Cake (direct, or with the as keyword) to a ChocolateCake prior to accessing any of its properties, which is really messy:
string a = (ChocolateCake)(str.GetFactory().CreateCake()).NameOfCake;
Here are some considerations:
Move the properties which are common to all types of cake into AbstractCake, e.g. NameOfCake, IsPacked and IsDecorated
Given that the AbstractFactory and AbstractCake classes do not have any implementation at all, consider changing these to interfaces instead of abstract classes, i.e. ICakeFactory and ICake. Concrete implementations will be ChocolateCakeFactory and ChocolateCake as before.
Consumers of the factory and the cake should now only access what is exposed on the interfaces (ICakeFactory, ICake and IBox), and not need to do any down casting or make any assumptions about the actual concrete type of Cake etc.
i.e.
public interface ICake
{
void Interact(IBox box);
bool IsPacked { get; }
bool IsDecorated { get; }
string NameOfCake { get; set; }
}
public class ChocolateCake : ICake
{
private bool _isPacked;
private bool _isDecorated;
private string _nameOfCake;
public ChocolateCake() // ctor is not on the interface and is implementation detail
{
_isPacked = false;
_isDecorated = false;
_nameOfCake = "Шоколадный";
}
public void Interact(IBox box) {...}
public bool IsPacked { get { return _isPacked; } }
public bool IsDecorated { get { return _isDecorated; } }
// ...
}
public interface ICakeFactory
{
ICake CreateCake();
IBox CreateBox();
}
public class ChocolateCakeFactory : ICakeFactory
{
public ICake CreateCake() {return new ChocolateCake();}
public IBox CreateBox() {return new ChocolateCakeBox();}
}
Re : Usage
It is highly unlikely that you would ever do this:
string a = str.GetFactory().GetCake().CreateCake().NameOfCake;
str.GetFactory().GetCake().CreateCake().NameOfCake = "Something"; // Prop setter
as this would create a new cake instance each time (and discard the instance). How about:
class Bakery
{
private readonly ICakeFactory _cakeFactory;
public Bakery(ICakeFactory cakeFactory)
{
Contract.Requires(cakeFactory != null);
cakeFactory = _cakeFactory;
}
bool BakeStuff()
{
var cake = _cakeFactory.CreateCake();
cake.NameOfCake = "StackOverflow";
return cake.IsDecorated && cake.IsPacked;
}
}
Edit, Re Raise change Events
This involves implementing INotifyPropertyChanged
public interface ICake : INotifyPropertyChanged
Which you can then raise on your mutable properties, e.g.
public string NameOfCake
{
get { return _nameOfCake} ;
set {
var propChanged = PropertyChanged;
if (propChanged != null && value != _nameOfCake)
{
propChanged(this, new PropertyChangedEventArgs("NameOfCake"));
}
_nameOfCake = value;
}
}
And subscribe like so
var cake = new ChocolateCake();
cake.PropertyChanged += (sender, eventArgs)
=> Console.WriteLine("Property {0} has changed", eventArgs.PropertyName);
Would this work?
public abstract class AbstractFactory
{
public abstract TCake CreateCake<TCake>() where TCake : AbstractCake, new();
public abstract AbstractBox CreateBox();
}
...
var cake = str.GetFactory().CreateCake<ChocolateCake>();
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);
}
}
}