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());
}
Related
Is there a way the tell the ActivatorUtilities.CreateInstance<T>(IServiceProvider serviceProvider); method to try to use other constructors if the first one can't be constructed?
I have a class with multiple constructors:
public ViewModelB(SomeDependency someDependency): this one only takes SomeDependency which is registered in a DI container
public ViewModelB(SomeDependency someDependency, GetUserRequest request): this one takes SomeDependency which is registered in a DI container and a GetUserRequest which has to be passed in manually
And I'm trying to activate them and resolve dependencies like so:
IServiceProvider serviceProvider; //this gets passed from somewhere
Guid userId; //this gets passed manually by the caller
//works
var instanceAWithoutParams = ActivatorUtilities.CreateInstance<ViewModelA>(serviceProvider);
//works
var instanceAWithParams = ActivatorUtilities.CreateInstance<ViewModelA>(serviceProvider, new[] { new GetUserRequest { UserId = userId } });
//does NOT work, it tries to use the first constructor and fails
var instanceBWithoutParams = ActivatorUtilities.CreateInstance<ViewModelB>(serviceProvider);
//works
var instanceBWithParams = ActivatorUtilities.CreateInstance<ViewModelB>(serviceProvider,, new[] { new GetUserRequest { UserId = userId } });
The activation of instanceBWithoutParams fails because it can't resolve the request parameter. It tries to use the first constructor and doesn't check other ones when the activation fails.
Here's what the services look like, they're the same with one difference: the order of the constructors.
public class ViewModelA
{
private readonly SomeDependency _someDependency;
private readonly GetUserRequest? _request;
public ViewModelA(SomeDependency someDependency)
{
_someDependency = someDependency;
}
public ViewModelA(SomeDependency someDependency, GetUserRequest request)
{
_someDependency = someDependency;
_request = request;
}
}
public class ViewModelB
{
private readonly SomeDependency _someDependency;
private readonly GetUserRequest? _request;
public ViewModelB(SomeDependency someDependency, GetUserRequest request)
{
_someDependency = someDependency;
_request = request;
}
public ViewModelB(SomeDependency someDependency)
{
_someDependency = someDependency;
}
}
public class GetUserRequest
{
public Guid UserId { get; set; }
}
Thanks.
I struggled with the same issue. Eventually I came up with this solution:
I would use something like a factory which is able to construct ServiceB by calling a method.
For example:
var serviceBFactory = ActivatorUtilities.CreateInstance<ServiceBFactory>(serviceProvider);
var instanceBWithoutParams = serviceBFactory.CreateServiceB();
var instanceBWithParams = serviceBFactory.CreateServiceB(new Request());
This way you keep you DI clean. But this means that the ServiceBFactory need to know which services need to be injected in a ServiceB. (so that will be a tight coupling) They come as a package.
I've chosen to re-design the view models instead of trying to pass optional parameters next to services from DI (thanks to Steven for the helpful articles: 1 and 2).
There also seems to be no way of making the ActivatorUtilities.CreateInstance<T>(IServiceProvider serviceProvider); method try other constructors after one fails, so here's what my edited solution looks like.
I've moved the initialization of the optional parameter out of the constructor, that way I only have one constructor that only takes injectables. The parameter is then passed separately via the TakeParameter method. The only downside I can think of is that the parameter can no longer be readonly and I can live with that.
My custom activator utility:
public interface IAcceptParameter<T>
{
void TakeParameter(T parameter);
}
public static class CustomActivator
{
public static T CreateInstance<T>()
{
return ActivatorUtilities.CreateInstance<T>(_serviceProvider);
}
public static T CreateInstanceWithParam<T, K>(K parameter) where T : IAcceptParameter<K>
{
var instance = ActivatorUtilities.CreateInstance<T>(_serviceProvider);
instance.TakeParameter(parameter);
return instance;
}
}
Changed view model
public class SomeViewModel : IAcceptParameter<Guid>
{
private readonly SomeDependency _someDependency;
private Guid? _userId;
public SomeViewModel(SomeDependency someDependency)
{
_someDependency = someDependency;
}
public void TakeParameter(Guid parameter){
_userId = parameter;
}
}
How I use it
var instanceWithoutParam = CustomActivator.CreateInstance<SomeViewModel>(serviceProvider);
Guid userId;
var instanceWithParam = CustomActivator.CreateInstanceWithParam<SomeViewModel, Guid>(serviceProvider, userId);
Let say you have a class like this:
public class a
{
public string p { get; set; }
public a()
{
p = "default constructor";
}
public a(string pv)
{
p = pv;
}
}
You can use .GetConstructor method to use a specific constructor:
public class Program
{
static void Main(string[] args)
{
var c = typeof(a).GetConstructor(new Type[] { typeof(string) });
if (c != null)
{
var myA = (a)c.Invoke(new object[] { "new value" });
Console.WriteLine($"Value of p is {myA.p}");
}
}
}
Question
How do I define an incoming Type T constraint that will allow me to call a static method on the class (of type T) to get the intended IndexModel object for passing to Mongo?
Background
I'm currently trying to write a Mongo Provider class that will allow me to ensure my particular database and collection are present before doing any operations with them, since there is a potential that the container or server it resides in could be destroyed and recreated at any time, and I'd prefer to have a safe way in code to ensure that the external dependency is there (instance is beyond my control, so I have to trust that something is there).
One of the things I'm trying to do, since I've managed to do what I stated above for Database and Collection instantiation, is to also generate indexes. My idea was to have a static method on the classes that would return their specific definition of an index model. This way, each class would be responsible for their own Mongo indexes, rather than some convoluted switch-case statement in my Provider based on the incoming type of T.
My first idea was to have an interface that shared this method, but Interfaces don't allow you to declare a static method. Similarly, I tried an Abstract Base-class and found that the static implementation would call the base class that defined the method, rather than any overrides in an inheritor.
Sample Code
public class MyClass
{
public DateTime DateValue { get; set; }
public int GroupId { get; set; }
public string DataType { get; set; }
public static IEnumerable<CreateIndexModel<MyClass>> GetIndexModel(IndexKeysDefinitionBuilder<MyClass> builder)
{
yield return new CreateIndexModel<MyClass>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
}
Edit
I guess I should probably include a shell of my Mongo Provider class. See below:
Edit #2 due to questions about how this hasn't solved my problem, I'm updating the MongoProvider to have the problematic code. Note: Once this method is included, the class will no longer compile, since it isn't possible given what I've done thus far.
public class MongoProvider
{
private readonly IMongoClient _client;
private MongoPrivder(ILookup<string, string> lookup, IMongoClient client)
{
_client = client;
foreach(var database in lookup)
foreach(var collection in database)
Initialize(database.Key, collection);
}
public MongoProvider(IConfiguration config) :this(config.GetMongoObjects(), config.GetMongoClient())
{}
public MongoProvider(IConfiguration config, IMongoClient client) : this(config.GetMongoObjects(), client)
{}
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
// The Problem
private void InitializeIndex<T>(string database, string collection)
{
IEnumerable<CreateIndexModel<T>> models;
switch (T)
{
case MyClass:
model = MyClass.GetIndexModel();
break;
default:
break;
}
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
}
Edit #3
As a stop-gap, I've gone ahead and done something terrible (not sure if it's going to work yet), and I'll supply the example so you can know my best solution thus far.
public static class Extensions
{
#region Object Methods
public static T TryCallMethod<T>(this object obj, string methodName, params object[] args) where T : class
{
var method = obj.GetType().GetMethod(methodName);
if (method != null)
{
return method.Invoke(obj, args) as T;
}
return default;
}
#endregion
}
This allows me to do the following (inside of MongoProvider)
private async void InitializeIndex<T>(string database, string collection) where T : new()
{
var models = new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel");
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
Since it doesn't look like I'm going to get an answer to this, I figured I would provide my solution for future searches of this question. Basically, I added an extension method to the base object class, and used reflection to determine if the method I was looking for was there. From there, I returned a value of true or false, depending on if the method was found, and output the return value to a parameter, in the traditional TryGet pattern.
Note to Future Readers
I do not recommend this approach. This is just how I solved my problem for accessing a method on a type of T. Ideally, an instance method would be implemented, and a signature defined in a common Interface, but that wasn't going to work for my use case.
My Answer
public static class Extensions
{
#region Object Methods
public static bool TryCallMethod<T>(this object obj, string methodName, out T result, params object[] args) where T : class
{
result = null;
var method = obj.GetType().GetMethod(methodName);
if (method == null)
return false;
result = method.Invoke(obj, args) as T;
return true;
}
#endregion
}
My data class looks like this (obfuscated from actual usage)
[BsonDiscriminator("data")]
public class DataClass
{
#region Private Fields
private const string MongoCollectionName = "Data";
#endregion
#region Public Properties
public string CollectionName => MongoCollectionName;
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("date_value")]
public DateTime DateValue { get; set; }
[BsonElement("group_id")]
public int GroupId { get; set; }
[BsonElement("data_type")]
public string DataType { get; set; }
[BsonElement("summary_count")]
public long SummaryCount { get; set; }
[BsonElement("flagged_count")]
public long FlaggedCount { get; set; }
[BsonElement("error_count")]
public long ErrorCount { get; set; }
#endregion
#region Constructor
public DataClass()
{
}
public DataClass(int groupId, string dataType = null, long summaryCount = 0, long flaggedCount = 0, long errorCount = 0)
{
Id = ObjectId.GenerateNewId();
DateValue = DateTime.UtcNow;
GroupId = groupId;
DocCount = summaryCount;
DataType = dataType ?? "default_name";
FlaggedCount = flaggedCount;
ErrorCount = errorCount;
}
#endregion
#region Public Methods
public static IEnumerable<CreateIndexModel<AuditEntry>> GetIndexModel(IndexKeysDefinitionBuilder<AuditEntry> builder)
{
yield return new CreateIndexModel<AuditEntry>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
#endregion
}
I would then call the method in the following fashion, inside my MongoProvider class. The ellipses are present to identify that more code exists within the class.
public class MongoProvider : IMongoProvider
{
#region Private Fields
private readonly IMongoClient _client;
#endregion
#region Constructor
...
#endregion
#region Private Methods
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
private async Task InitializeIndex<T>(string database, string collection) where T : new()
{
if(new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel", out var models, new IndexKeysDefinitionBuilder<T>()))
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
private static void ValidateOptions<T>(ref FindOptions<T, T> options)
{
if(options != null)
return;
options = new FindOptions<T, T>
{
AllowPartialResults = null,
BatchSize = null,
Collation = null,
Comment = "AspNetWebService",
CursorType = CursorType.NonTailable,
MaxAwaitTime = TimeSpan.FromSeconds(10),
MaxTime = TimeSpan.FromSeconds(10),
Modifiers = null,
NoCursorTimeout = false,
OplogReplay = null
};
}
private static FilterDefinition<T> GetFilterDefinition<T>(Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders)
{
if(builders.Length == 0)
builders = new Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] {b => b.Empty};
return new FilterDefinitionBuilder<T>()
.And(builders
.Select(b => b(new FilterDefinitionBuilder<T>()))
);
}
#endregion
#region Public Methods
public async Task<IReadOnlyCollection<T>> SelectManyAsync<T>(string database, string collection, FindOptions<T, T> options = null, params Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders) where T : new()
{
ValidateOptions(ref options);
await InitializeIndex<T>(database, collection);
var filter = GetFilterDefinition(builders);
var find = await _client.GetDatabase(database)
.GetCollection<T>(collection)
.FindAsync(filter, options);
return await find.ToListAsync();
}
...
#endregion
}
In my asp.net core 2 application, I have a static class which handles some interactions with the session. For example :
public static class BookManager
{
private const string Key = "Books";
public static HttpContextAccessor ContextAccessor => new HttpContextAccessor();
public static void AddBooksToContainer(IEnumerable<BookViewModel> books)
{
ContextAccessor.HttpContext.Session.Set(Key, books);
}
public static IEnumerable<BookViewModel> GetBooks()
{
return ContextAccessor.HttpContext.Session.Get<IEnumerable<BookViewModel>>(Key);
}
}
I have also added some extension methods to the session object :
public static class SessionExtensions
{
public static void Set<T>(this ISession session, string key, IEnumerable<T> value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default(T) :
JsonConvert.DeserializeObject<T>(value);
}
}
Update: This is how i try to mock :
var bookList = new List<BookViewModel>()
{
new BookViewModel {Author = "test", Id = Guid.NewGuid(), InStock = 1, Price = 1000, Title = "tes"}
};
var httpContextAccessor = new Mock<IHttpContextAccessor>().SetupAllProperties();
httpContextAccessor.Setup(x => x.HttpContext.Session.Get<IEnumerable<BookViewModel>>("test")).Returns(bookList);
And the error i get :
System.NotSupportedException : Invalid setup on an extension method: x =>
x.HttpContext.Session.Get<IEnumerable`1>("test")
The problem is I have tried many ways to mock session and its extension methods but I got no result yet. many ways work in .netframework and not core.
I use xunit for testing and Moq library to mock the objects. Please tell me how can I mock session and its extension methods. Appreciate it.
The problem is not related to .NET Core, is a limitation of the mocking library, you can't mock extension methods with Moq.
This might help: Mocking Extension Methods with Moq
Depend on abstractions and not concretions. Create a Factory method that will provide you with the IHttpContextAccessor abstraction.
public static class BookManager {
private const string Key = "Books";
public static Func<IHttpContextAccessor> ContextAccessor = () => new HttpContextAccessor();
public static void AddBooksToContainer(IEnumerable<BookViewModel> books) {
ContextAccessor().HttpContext.Session.Set(Key, books);
}
public static IEnumerable<BookViewModel> GetBooks() {
return ContextAccessor().HttpContext.Session.Get<IEnumerable<BookViewModel>>(Key);
}
}
This will allow you to replace the abstraction when testing with a mock.
//Arrange
var mock = mock.Of<IHttpContextAccessor>();
BookManager.ContextAccessor = () => {
return mock.Object;
};
//...
Better yet if this manager only has need of the session then do not expose more than it needs.
public static class BookManager {
private const string Key = "Books";
public static Func<ISession> Session = () => new HttpContextAccessor().HttpContext.Session;
public static void AddBooksToContainer(IEnumerable<BookViewModel> books) {
Session().Set(Key, books);
}
public static IEnumerable<BookViewModel> GetBooks() {
return Session().Get<IEnumerable<BookViewModel>>(Key);
}
}
So now only the explicit dependencies need to be mocked for tests.
According to ISession Extensions source code GetString and SetString are also extension methods
You will need to mock ISession.TryGetValue and ISession.Set
//Arrange
var mock = Mock.Of<ISession>();
BookManager.Session = () => {
return mock;
};
var value = Encoding.UTF8.GetBytes("[{some books json strings here}]");
Mock.Get(mock).Setup(_ => _.TryGetValue("Books", out value))
.Returns(true);
//Act
var books = BookManager.GetBooks();
//...
I am calling methods on a remote system. The remote system implements an interface that both systems have a copy of (via shared nuget repository). At the moment i am sending the requests like this:
var oldRequest = new FooRequest("GetEmployeeById", new object[] { "myPartner", 42, DateTime.Now.AddDays(-1) });
Here is the interface:
public class FooResponse<T> { }
public interface IFooController
{
FooResponse<string> GetEmployeeById(string partnerName, int employeeId, DateTime? ifModifiedSince);
}
As you can image, sometimes programmers passes arguments in the wrong order to the array in the constructor, and things start to fail. To resolve this I have created the following code to have intellisense support when creating the FooRequest:
public static FooRequest Create<T>(Func<FooResponse<T>> func)
{
return new FooRequest(null, null); // Here goes some magic reflection stuff instead of null.
}
It is now possible to create a FooRequest like this:
public static IFooController iFooController => (IFooController)new object();
public static FooRequest CreateRequest<T>(Func<FooResponse<T>> func)
{
return FooRequest.Create(func);
}
var newRequest = CreateRequest(() => iFooController.GetEmployeeById("myPartner", 42, DateTime.Now.AddDays(-1)));
My question then is: How will i be able to get the name of the method and the value of the parameters in the FooRequest.Create-method?
I have exhausted both my reflection and google-skills trying to find the values, but no luck so far.
Complete compiling code can be found here if someone wants to give it a shot: http://ideone.com/ovWseI
Here is a sketch of how you can do this with expressions:
public class Test {
public static IFooController iFooController => (IFooController) new object();
public static FooRequest CreateRequest<T>(Expression<Func<FooResponse<T>>> func) {
return FooRequest.Create(func);
}
public static void Main() {
var newRequest = CreateRequest(() => iFooController.GetEmployeeById("myPartner", 42, DateTime.Now.AddDays(-1)));
Console.ReadKey();
}
}
public class FooRequest {
public static FooRequest Create<T>(Expression<Func<FooResponse<T>>> func) {
var call = (MethodCallExpression) func.Body;
var arguments = new List<object>();
foreach (var arg in call.Arguments) {
var constant = arg as ConstantExpression;
if (constant != null) {
arguments.Add(constant.Value);
}
else {
var evaled = Expression.Lambda(arg).Compile().DynamicInvoke();
arguments.Add(evaled);
}
}
return new FooRequest(call.Method.Name, arguments.ToArray());
}
public FooRequest(string function, object[] data = null) {
//SendRequestToServiceBus(function, data);
Console.Write($"Function name: {function}");
}
}
public class FooResponse<T> {
}
public interface IFooController {
FooResponse<string> GetEmployeeById(string partnerName, int employeeId, DateTime? ifModifiedSince);
}
I have a ASP MVC 4 app that uses Structuremap. I'm trying to add logging to my application via Structuremap interception.
In a Registry, I scan a specific assembly in order to register all of it's types with the default convention:
public class ServicesRegistry : Registry
{
public ServicesRegistry()
{
Scan(x =>
{
x.AssemblyContainingType<MyMarkerService>();
x.WithDefaultConventions();
});
}
}
The interceptor:
public class LogInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var watch = Stopwatch.StartNew();
invocation.Proceed();
watch.Stop();//log the time
}
}
I can add the interceptor for one specific plugin type like this:
var proxyGenerator = new ProxyGenerator();
container.Configure(x => x.For<IServiceA>().Use<ServiceA>().DecorateWith(instance => proxyGenerator.CreateInterfaceProxyWithTarget(instance, new LogInterceptor())));
but I want to make structuremap create logging proxies for all the types that were scanned in the registry.
Is there a way to achieve this?
It doesn't look like there's an easy extension point for this, but I got it working with a fairly decent solution using a custom convention. In order to help you understand the decisions I made I'll walk you through a few steps (skipping the many, many missteps I made on my way).
First lets look at the DefaultConvention which you are already using.
DefaultConvention:
public class DefaultConventionScanner : ConfigurableRegistrationConvention
{
public override void Process(Type type, Registry registry)
{
if (!TypeExtensions.IsConcrete(type))
return;
Type pluginType = this.FindPluginType(type);
if (pluginType == null || !TypeExtensions.HasConstructors(type))
return;
registry.AddType(pluginType, type);
this.ConfigureFamily(registry.For(pluginType, (ILifecycle)null));
}
public virtual Type FindPluginType(Type concreteType)
{
string interfaceName = "I" + concreteType.Name;
return Enumerable.FirstOrDefault<Type>((IEnumerable<Type>)concreteType.GetInterfaces(), (Func<Type, bool>)(t => t.Name == interfaceName));
}
}
Pretty simple, we get the type and interface pairs and check to make sure they have a constructor, if they do we register them. It would be nice to just modify this so that it calls DecorateWith, but you can only call that on For<>().Use<>(), not For().Use().
Next lets look at what DecorateWith does:
public T DecorateWith(Expression<Func<TPluginType, TPluginType>> handler)
{
this.AddInterceptor((IInterceptor) new FuncInterceptor<TPluginType>(handler, (string) null));
return this.thisInstance;
}
So this creates a FuncInterceptor and registers it. I spent a fair bit of time trying to create one of these dynamically with reflection before deciding it would just be easier to make a new class:
public class ProxyFuncInterceptor<T> : FuncInterceptor<T> where T : class
{
public ProxyFuncInterceptor() : base(x => MakeProxy(x), "")
{
}
protected ProxyFuncInterceptor(Expression<Func<T, T>> expression, string description = null)
: base(expression, description)
{
}
protected ProxyFuncInterceptor(Expression<Func<IContext, T, T>> expression, string description = null)
: base(expression, description)
{
}
private static T MakeProxy(T instance)
{
var proxyGenerator = new ProxyGenerator();
return proxyGenerator.CreateInterfaceProxyWithTarget(instance, new LogInterceptor());
}
}
This class just makes it easier to work with when we have the type as a variable.
Finally I've made my own Convention based on the Default convention.
public class DefaultConventionWithProxyScanner : ConfigurableRegistrationConvention
{
public override void Process(Type type, Registry registry)
{
if (!type.IsConcrete())
return;
var pluginType = this.FindPluginType(type);
if (pluginType == null || !type.HasConstructors())
return;
registry.AddType(pluginType, type);
var policy = CreatePolicy(pluginType);
registry.Policies.Interceptors(policy);
ConfigureFamily(registry.For(pluginType));
}
public virtual Type FindPluginType(Type concreteType)
{
var interfaceName = "I" + concreteType.Name;
return concreteType.GetInterfaces().FirstOrDefault(t => t.Name == interfaceName);
}
public IInterceptorPolicy CreatePolicy(Type pluginType)
{
var genericPolicyType = typeof(InterceptorPolicy<>);
var policyType = genericPolicyType.MakeGenericType(pluginType);
return (IInterceptorPolicy)Activator.CreateInstance(policyType, new object[]{CreateInterceptor(pluginType), null});
}
public IInterceptor CreateInterceptor(Type pluginType)
{
var genericInterceptorType = typeof(ProxyFuncInterceptor<>);
var specificInterceptor = genericInterceptorType.MakeGenericType(pluginType);
return (IInterceptor)Activator.CreateInstance(specificInterceptor);
}
}
Its almost exactly the same with one addition, I create an interceptor and interceptorType for each type we register. I then register that policy.
Finally, a few unit tests to prove it works:
[TestFixture]
public class Try4
{
[Test]
public void Can_create_interceptor()
{
var type = typeof (IServiceA);
Assert.NotNull(new DefaultConventionWithProxyScanner().CreateInterceptor(type));
}
[Test]
public void Can_create_policy()
{
var type = typeof (IServiceA);
Assert.NotNull(new DefaultConventionWithProxyScanner().CreatePolicy(type));
}
[Test]
public void Can_register_normally()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.WithDefaultConventions();
}));
var serviceA = container.GetInstance<IServiceA>();
Assert.IsFalse(ProxyUtil.IsProxy(serviceA));
Console.WriteLine(serviceA.GetType());
}
[Test]
public void Can_register_proxy_for_all()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.Convention<DefaultConventionWithProxyScanner>();
}));
var serviceA = container.GetInstance<IServiceA>();
Assert.IsTrue(ProxyUtil.IsProxy(serviceA));
Console.WriteLine(serviceA.GetType());
}
[Test]
public void Make_sure_I_wait()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.Convention<DefaultConventionWithProxyScanner>();
}));
var serviceA = container.GetInstance<IServiceA>();
serviceA.Wait();
}
}
}
public interface IServiceA
{
void Wait();
}
public class ServiceA : IServiceA
{
public void Wait()
{
Thread.Sleep(1000);
}
}
public interface IServiceB
{
}
public class ServiceB : IServiceB
{
}
There's definitely room for some clean up here (caching, make it DRY, more tests, make it easier to configure) but it works for what you need and is a pretty reasonable way of doing it.
Please ask if you have any other questions about it.