I am using ABP Boilerplate 6.0 and have integrated NRules with our platform.
I am able to get the rule below to work, but the issue is that I cannot use the injected ‘_questionResponseRepository’ within the rule condition, because only after the rule criteria are met, does the resolution of dependencies take place.
I would like to use ‘_questionResponseRepository’ to get from the database a list of keywords and use those words in the Rule match condition
The Calling code
public WasteManagementManager(
IRepository<WasteBirthCertificateBlock, long> wasteBirthCertificateBlockRepository,
IRepository<WasteBirthCertificateChain, long> wasteBirthCertificateChainRepository,
ISession nRuleSession,
ISessionFactory nRuleSessionFactory,
ILogger log
)
{
_wasteBirthCertificateBlockRepository = wasteBirthCertificateBlockRepository;
_wasteBirthCertificateChainRepository = wasteBirthCertificateChainRepository;
_nRuleSession = nRuleSession;
_nRuleSessionFactory = nRuleSessionFactory;
_log = log;
}
public void Trigger()
{==>
When I am in debug, _questionResponseRepository is NOT NUll. I'm trying inject it as a fact but that is not property injection .. I'm just trying one way or the other to get it working
_nRuleSession.Insert(_questionResponseRepository);
_nRuleSession.Fire();
}
The Rule code
namespace SagerSystems.AI.WasteManagements.NRules
{
[Name("Track Explosive Substances Rule")]
public class TrackExplosiveSubstancesRule : Rule
{
private string[] _listOfExplosiveKeyWords = new string[] { "H3O", "N2" };
public IRepository<QuestionResponse, long> _questionResponseRepository { get; set; }
public TrackExplosiveSubstancesRule()
{
**This does NOT work** (the value remains null)
Dependency()
.Resolve(() => _questionResponseRepository);
}
public override void Define()
{
*This does work* but only after the rule fires)
Dependency()
.Resolve(() => _questionResponseRepository);
When()
.Match(() => questionResponseDto, c => CheckKeyWord(c));
Then()
.Do(ctx => Console.WriteLine(“Test Condition Works”))
}
private bool CheckKeyWord(QuestionResponseDto questionResponseDto)
{
==> How do I user ‘questionResponseRepository’
var isKeyWord=
_listOfExplosiveKeyWords.Any(c => questionResponseDto.QuestionText.Contains(c));
return isKeyWord;
}
}
}
There are a few ways to use external information (in this case keywords from the DB) in the rule's match condition in NRules.
Inject the corresponding repository/service into the rule.
There are two ways to inject dependencies into rules. During instantiation of rule classes, or at rule's run time via Dependency.Resolve DSL. Since Dependency.Relsove, as you pointed out, can only be used on the right-hand side of the rule (actions), it's not suitable for this use case. But you can still inject the dependency into the rule during the rule's instantiation.
What you need to do here is to register the rule type itself with the container, implement an IRuleActivator to resolve rules via that container, and set the RuleRepository.RuleActivator when loading the rules. If both the repository and the rule are registered with the same container, the rule will get injected with the dependency (you can use either property or constructor injection, depending on how you registered the types). Then you can just use the dependency in the expressions.
I don't have all your code, so hypothetically, it would look something like below. Here I'm assuming there is a repository of a Keyword entity that can be used to fetch keywords from the DB. I'm also using constructor injection, but the same would work for property injection.
public class RuleActivator : IRuleActivator
{
private readonly IIocResolver _iocResolver;
public RuleActivator(IIocResolver iocResolver)
{
_iocResolver = iocResolver;
}
public IEnumerable<Rule> Activate(Type type)
{
yield return (Rule)_iocResolver.Resolve(type);
}
}
public class RulesEngineModule : AbpModule
{
public override void Initialize()
{
//Find rule classes
var scanner = new RuleTypeScanner();
scanner.AssemblyOf<TrackExplosiveSubstancesRule>();
var ruleTypes = scanner.GetRuleTypes();
//Register rule classes with the container
foreach (var ruleType in ruleTypes)
{
IocManager.Register(ruleType);
}
//Load rules into the repository; use a rule activator to resolve rules via the container
var repository = new RuleRepository {Activator = new RuleActivator(IocManager)};
repository.Load(x => x.From(s => s.Type(ruleTypes)));
//Compile rules into the factory
var factory = repository.Compile();
//Register session factory instance
IocManager.IocContainer.Register(
Component.For<ISessionFactory>().Instance(factory));
//Register session as a delegate that creates a new instance from a factory
IocManager.IocContainer.Register(
Component.For<ISession>().UsingFactoryMethod(k => k.Resolve<ISessionFactory>().CreateSession()).LifestyleTransient());
}
}
[Name("Track Explosive Substances Rule")]
public class TrackExplosiveSubstancesRule : Rule
{
private readonly IRepository<Keyword, long> _keywordRepository;
public TrackExplosiveSubstancesRule(IRepository<Keyword, long> keywordRepository)
{
_keywordRepository = keywordRepository;
}
public override void Define()
{
QuestionResponseDto questionResponseDto = default;
When()
.Match(() => questionResponseDto, c => ContainsKeyword(c));
Then()
.Do(ctx => Console.WriteLine("Test Condition Works"));
}
private bool ContainsKeyword(QuestionResponseDto questionResponseDto)
{
var keywords = _keywordRepository.GetAll().ToList();
var hasKeyWord = keywords.Any(keyword => questionResponseDto.QuestionText.Contains(keyword.Value));
return hasKeyWord;
}
}
Then somewhere in your Trigger method or somewhere in a corresponding controller:
var dto = new QuestionResponseDto(...);
_session.Insert(dto);
_session.Fire();
Retrieve keywords from the repository outside of the rules engine, and insert them into the session as facts. This is actually preferred, because you would have more control over the interactions with the external data. Also, you can put keywords into a data structure with efficient lookup performance (e.g. a trie).
In this case you don't need a rule activator or to register the rules with the container, like with the option #1, since all inputs come to the rule as facts, so there are actually no external dependencies.
For example:
public class KeywordSet
{
private readonly Keyword[] _keywords;
public KeywordSet(IEnumerable<Keyword> keywords)
{
_keywords = keywords.ToArray();
}
public bool ContainsAny(string value)
{
return _keywords.Any(keyword => value.Contains(keyword.Value));
}
}
[Name("Track Explosive Substances Rule")]
public class TrackExplosiveSubstancesRule : Rule
{
public override void Define()
{
KeywordSet keywordSet = default;
QuestionResponseDto questionResponseDto = default;
When()
.Match(() => keywordSet)
.Match(() => questionResponseDto, c => keywordSet.ContainsAny(c.QuestionText));
Then()
.Do(ctx => Console.WriteLine("Test Condition Works"));
}
}
Then somewhere in your Trigger method or somewhere in a corresponding controller:
var keywords = _keywordRepository.GetAll().ToList();
var keywordSet = new KeywordSet(keywords);
_session.Insert(keywordSet);
var dto = new QuestionResponseDto(...);
_session.Insert(dto);
_session.Fire();
You can also insert the repository itself as a fact, and then match it in the rule, but I would not recommend doing this, so I would stick to either the option #1 or #2.
Related
I've been struggling with the issue/behaviour described below for while now and can't seem to figure out what's going on. This is all based on inherited/adapted code, so it's completely possible that I'm missing something fundamental. It's been literally years since I've asked a question on SO, so please bear that in mind if I'm not complying with the current expectations on formatting and content/detail.
I've got a number of domain classes involved in this problem, but at a high level, what I'm trying to do is to count the number of users with a specific role in relation to a group.
The behaviour that I'm seeing when I execute a GraphQL query (See below), is that the attribute in question (capacityUsed) is populated as expected, which makes me believe that the wiring is correct.
However, when I try to access the attribute within a GraphQL mutation, the underlying data to populate the value isn't retrieved, so the default value is returned, which isn't accurate or what I want.
Ultimately what I'm trying to do is to access (and make business decisions based on) the GroupRoleCapacity.CapacityUsed attribute. I'd like to sort out what's wrong with the config/setup for the mutation so that my resolver works as expected in both situations.
I'd appreciate any help or insight into what might be causing this. I've gone through the related HotCholocate and GraphL documentation a number of times and tried just about every iteration of related keywords that I can think of searching for an explanation of what's going on, and haven't been able to find a solution.
Here's how things are currently wired up (pulling out extraneous info/attributes to keep it short):
//GraphQL Entity Configuration
public class GroupRoleCapacityType : ObjectType<GroupRoleCapacity>
{
protected override void Configure(IObjectTypeDescriptor<GroupRoleCapacity> descriptor)
{
descriptor
.Field(grc => grc.Group)
.ResolveWith<Resolvers>(r => r.GetGroup(default!, default!))
.UseDbContext<AppDbContext>()
.Description("This is the Group for this GroupRoleCapacity.");
descriptor
.Field(grc => grc.Role)
.ResolveWith<Resolvers>(r => r.GetRole(default!, default!))
.UseDbContext<AppDbContext>()
.Description("This is the Role for this GroupRoleCapacity.");
descriptor
.Field(grc => grc.CapacityUsed)
.ResolveWith<Resolvers>(r => r.GetCapacityUsed(default!, default!))
.UseDbContext<AppDbContext>()
.Description("This is the number of Users with the indicated Role for this GroupRoleCapacity.");
}
protected class Resolvers
{
public Group GetGroup([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
{
return context.Groups.FirstOrDefault(p => p.Id == GroupRoleCapacity.GroupId);
}
public Role GetRole([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
{
return context.Roles.FirstOrDefault(p => p.Id == GroupRoleCapacity.RoleId);
}
public int GetCapacityUsed([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
{
return context.Users.Count(u => u.GroupId == GroupRoleCapacity.GroupId &&
u.UserRoles.Any(ur => ur.RoleId == GroupRoleCapacity.RoleId));
}
}
}
//GraphQL Service Startup
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) { Configuration = configuration; }
public void ConfigureServices(IServiceCollection services) {
services.AddScoped(p => p.GetRequiredService<IDbContextFactory<AppDbContext>>().CreateDbContext());
services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddType<GroupType>()
.AddType<RoleType>()
.AddType<UserType>()
.AddType<UserRoleType>()
.AddType<GroupRoleCapacityType>()
}
}
Using the query below seems to work as expected and the GroupRoleCapacity.CapacityUsed attribute is populated appropriately when accessing the data via a query
{ groupRoleCapacity { groupId roleId capacityUsed }}
//GraphQL Query
public class Query
{
[UseDbContext(typeof(AppDbContext))]
[GraphQLType(typeof(NonNullType<ListType<NonNullType<GroupRoleCapacities.GroupRoleCapacityType>>>))]
public IQueryable<GroupRoleCapacity> GetGroupRoleCapacity([ScopedService] AppDbContext context)
{
return context.GroupRoleCapacities;
}
}
However, when I use a mutation similar to below, GroupRoleCapacity.CapacityUsed isn't being set correctly, which is what's making me suspect a problem with either the Resolver implementation or the configuration.
mutation { addUserToGroup(input: { userId: 1, groupId: 1, roleId: 1}) { result { userId, groupId, roleId }}}
//GraphQL Mutation
public class Mutation
{
public Mutation(IConfiguration configuration) { }
[UseDbContext(typeof(AppDbContext))]
public async Task<AddUserToGroupPayload> AddUserToGroupAsync(AddUserToGroupInput input,
[ScopedService] AppDbContext context
)
{
int someLimit = 5;
var roleCapacity = context.GroupRoleCapacities.First(grc =>
grc.GroupId == input.GroupId &&
grc.RoleId == input.RoleId);
if(roleCapacity.CapacityUsed > someLimit) {
throw ApplicationException("limit exceeded");
}
//add user to group and save etc
return new AddUserToGroupPayload(result);
}
}
Difference here that Hot Chocolate is used only for GraphQL querying, but your code calls EF Core context explicitly. You need some common part.
What you can do:
Better expose DTO instead of database entity
Write common query which returns this DTO
Feed Hot Chocolate with this query. In this case you do not need resolvers at all (if I understand this library correctly)
Sample query (without DTO, never used Hot Chocolate and not familiar with it's attributes)
public static class MyQueries
{
public static IQueryable<GroupRoleCapacity> GetGroupRoleCapacity(AppDbContext context)
{
return context.GroupRoleCapacities.Select(c => new GroupRoleCapacity
{
GroupId = c.GroupId,
Group = c.Group, // I assume you have defined navigation properties
Role = c.Role,
RoleId = c.RoleId,
CapacityUsed = await context.Users.Count(u => u.GroupId == c.GroupId &&
u.UserRoles.Any(ur => ur.RoleId == c.RoleId))
);
}
}
Query:
//GraphQL Query
public class Query
{
[UseDbContext(typeof(AppDbContext))]
[GraphQLType(typeof(NonNullType<ListType<NonNullType<GroupRoleCapacities.GroupRoleCapacityType>>>))]
public IQueryable<GroupRoleCapacity> GetGroupRoleCapacity([ScopedService] AppDbContext context)
{
return MyQueries.GetGroupRoleCapacity(context);
}
}
Mutation:
//GraphQL Mutation
public class Mutation
{
public Mutation(IConfiguration configuration) { }
[UseDbContext(typeof(AppDbContext))]
public async Task<AddUserToGroupPayload> AddUserToGroupAsync(AddUserToGroupInput input,
[ScopedService] AppDbContext context
)
{
int someLimit = 5;
var roleCapacity = MyQueries.GetGroupRoleCapacity(context)
.FirstAsync(grc =>
grc.GroupId == input.GroupId &&
grc.RoleId == input.RoleId);
if (roleCapacity.CapacityUsed > someLimit)
{
throw ApplicationException("limit exceeded");
}
//add user to group and save etc
return new AddUserToGroupPayload(result);
}
}
I need to change a function that accepts one Expression with one property inside and give it the ability to work with 2 properties at least.
I have the following base class that contains nested ElementHelper class
public class DomainObjectViewModel<TModel> where TModel : DomainObject
{
public class ElementHelper
{
public static void Create<T1>(TModel model, Expression<Func<TModel, T1>> expression)
{
var getPropertyInfo = GetPropertyInfo(expression);
//Do cool stuff
}
private static PropertyInfo GetPropertyInfo<T1>(Expression<Func<TModel, T1>> propertyExpression)
{
return (PropertyInfo)((MemberExpression)propertyExpression.Body).Member;
}
}
}
-ElementHelper class contains a Create function that gets the propertyInfo of the expression and only works if you pass one property in the expression.
Then I have the following inherited class that uses the helper function in the constructor.
public class ProductViewModel : DomainObjectViewModel<ProductEditViewModel>
{
public ProductViewModel(ProductEditViewModel model)
{
//It works for one property in the Expression
ElementHelper.Create(model, x => x.LaunchDate);
//Give the ability to pass multiple paramenters in the expression
ElementHelper.Create(model, x => new { x.LaunchDate, x.ApplyLaunchDateChanges });
}
}
I think I can use NewExpression (new { x.LaunchDate, x.ApplyLaunchDateChanges }) in order to pass it a collection of properties, but I cannot make it work.
Would you use same approach?
How you can split the passed Expression so you can get the propertyinfo of each properties found in the NewExpression object?
Well, since ElementHelper.GetPropertyInfo is your own method, you can decide what is allowed to pass, and then handle it appropriately inside that method.
Currently you handle only MemberExpression, so that's why it works only with single property accessor. If you want to be able to pass new { ... }, you need to add support for NewExpression like this:
private static IEnumerable<PropertyInfo> GetPropertyInfo<T1>(Expression<Func<TModel, T1>> propertyExpression)
{
var memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression != null)
return Enumerable.Repeat((PropertyInfo)memberExpression.Member, 1);
var newExpression = propertyExpression.Body as NewExpression;
if (newExpression != null)
return newExpression.Arguments.Select(item => (PropertyInfo)((MemberExpression)item).Member);
return Enumerable.Empty<PropertyInfo>(); // or throw exception
}
I am exposing my repository operations through web api. Repository has been implemented with Entity framework and Unit Of Work Pattern. I have many instances of the same database. Each one represent the data of a different Client. Now the issue is how can I set the connection string dynamically through each webapi call? Should I get connection string parameter with each call ? Or I should host web Api per client ?
Based on the information provided, I would use the same controller and look up the connection string rather than rather than hosting separate Web API instances for each client. There would be more complexity in hosting multiple instances and given the only difference indicated is the connection string, I do not think the complexity would be justified.
The first thing we will need to do is determine which client is calling in order to get the appropriate connection string. This could be done with tokens, headers, request data, or routing. Routing is simplest and most generally accessible to clients, so I will demonstrate using it; however, carefully consider your requirements in deciding how you will make the determination.
[Route( "{clientId}" )]
public Foo Get( string clientId ) { /* ... */ }
Next we need to get the right DbContext for the client. We want to keep using DI but that is complicated in that we do not know until after the Controller is created what connection string is needed to construct the object. Therefore, we need to inject some form of factory rather than the object itself. In this case we will represent this as a Func<string, IUnitOfWork> with the understanding it takes the 'clientId' as a string and returns an appropriately instantiated IUnitOfWork. We could alternatively use a named interface for this.
[RoutePrefix("foo")]
public class FooController : ApiController
{
private Func<string, IUnitOfWork> unitOfWorkFactory;
public FooController( Func<string, IUnitOfWork> unitOfWorkFactory )
{
this.unitOfWorkFactory = unitOfWorkFactory;
}
[Route( "{clientId}" )]
public Foo Get( string clientId )
{
var unitOfWork = unitOfWorkFactory(clientId);
// ...
}
}
All that remains is configuring our dependency injection container to provide us that Func<string, IUnitOfWork>. This could vary significantly between implementation. The following is one possible way to do it in Autofac.
protected override void Load( ContainerBuilder builder )
{
// It is expected `MyDbContext` has a constructor that takes the connection string as a parameter
// This registration may need to be tweaked depending on what other constructors you have.
builder.Register<MyDbContext>().ForType<DbContext>().InstancePerRequest();
// It is expected `UnitOfWork`'s constructor takes a `DbContext` as a parameter
builder.RegisterType<UnitOfWork>().ForType<IUnitOfWork>().InstancePerRequest();
builder.Register<Func<string, Bar>>(
c =>
{
var dbContextFactory = c.Resolve<Func<string, DbContext>>();
var unitOfWorkFactory = c.Resolve<Func<DbContext, IUnitOfWork>>();
return clientId =>
{
// You may have injected another type to help with this
var connectionString = GetConnectionStringForClient(clientId);
return unitOfWorkFactory(dbContextFactory(connectionString));
};
});
}
Autofac is used since comments indicates Autofac is currently being used, though similar results would be possible with other containers.
With that the controller should be able to be instantiated and the appropriate connection string will be used for each request.
Example registration based on linked project:
builder.Register<Func<string, IEmployeeService>>(
c =>
{
var dbContextFactory = c.Resolve<Func<string, IMainContext>>();
var unitOfWorkFactory = c.Resolve<Func<IMainContext, IUnitOfWork>>();
var repositoryFactory = c.Resolve<Func<IMainContext, IEmployeeRepository>>();
var serviceFactory = c.Resolve<Func<IUnitOfWork, IEmployeeService>>();
return clientId =>
{
// You may have injected another type to help with this
var connectionString = GetConnectionStringForClient(clientId);
IMainContext dbContext = dbContextFactory(connectionString);
IUnitOfWork unitOfWork = unitOfWorkFactory(dbContext);
IEmployeeRepository employeeRepository = repositoryFactory(dbContext);
unitOfWork.employeeRepositoty = employeeRepository;
return serviceFactory(unitOfWork);
};
});
If you find the registration grows too cumbersome because of needing to do a little wiring manually, you probably need to look at updating (or creating a new) container after you have determined the client so that you can rely more on the container.
You can change the connectionstring per DbContext instance
Example:
public class AwesomeContext : DbContext
{
public AwesomeContext (string connectionString)
: base(connectionString)
{
}
public DbSet<AwesomePeople> AwesomePeoples { get; set; }
}
And then use your DbContext like this:
using(AwesomeContext context = new AwesomeContext("newConnectionString"))
{
return context.AwesomePeoples.ToList();
}
Depending on how many ConnectionStrings there are you can make a DB table for the client / constring mapping or save it in the solution (array for example).
If you can't/don't want to change the constructor you can do it later as well
Add this to your DbContext override:
public void SetConnectionString(string connectionString)
{
this.Database.Connection.ConnectionString = connectionString;
}
And call the method before you do any DB operations:
using(AwesomeContext context = new AwesomeContext())
{
context.SetConnectionString(ConfigurationManager.ConnectionStrings["newConnectionString"].ConnectionString)
return context.AwesomePeoples.ToList();
}
here's my controller
[POST("signup")]
public virtual ActionResult Signup(UserRegisterViewModel user)
{
if (ModelState.IsValid)
{
var newUser = Mapper.Map<UserRegisterViewModel, User>(user);
var confirmation = _userService.AddUser(newUser);
if (confirmation.WasSuccessful)
return RedirectToAction(MVC.Home.Index());
else
ModelState.AddModelError("Email", confirmation.Message);
}
return View(user);
}
here's my unit test:
[Test]
public void Signup_Action_When_The_User_Model_Is_Valid_Returns_RedirectToRouteResult()
{
// Arrange
const string expectedRouteName = "~/Views/Home/Index.cshtml";
var registeredUser = new UserRegisterViewModel { Email = "newuser#test.com", Password = "123456789".Hash()};
var confirmation = new ActionConfirmation<User>
{
WasSuccessful = true,
Message = "",
Value = new User()
};
_userService.Setup(r => r.AddUser(new User())).Returns(confirmation);
_accountController = new AccountController(_userService.Object);
// Act
var result = _accountController.Signup(registeredUser) as RedirectToRouteResult;
// Assert
Assert.IsNotNull(result, "Should have returned a RedirectToRouteResult");
Assert.AreEqual(expectedRouteName, result.RouteName, "Route name should be {0}", expectedRouteName);
}
Unit test failed right here.
var result = _accountController.Signup(registeredUser) as RedirectToRouteResult;
when I debug my unit test, I got following error message: "Missing type map configuration or unsupported mapping."
I think its because configuration is in web project, not the unit test project. what should I do to fix it?
You need to have the mapper configured, so in your test class set up, not the per-test setup, call the code to set up the mappings. Note, you'll also probably need to modify your expectation for the user service call as the arguments won't match, i.e, they are different objects. Probably you want a test that checks if the properties of the object match those of the model being passed to the method.
You should really use an interface for the mapping engine so that you can mock it rather than using AutoMapper otherwise it is an integration test not a unit test.
AutoMapper has an interface called IMappingEngine that you can inject into your controller using your IoC container like below (this example is using StructureMap).
class MyRegistry : Registry
{
public MyRegistry()
{
For<IMyRepository>().Use<MyRepository>();
For<ILogger>().Use<Logger>();
Mapper.AddProfile(new AutoMapperProfile());
For<IMappingEngine>().Use(() => Mapper.Engine);
}
}
You will then be able to use dependency injection to inject AutoMapper's mapping engine into your controller, allowing you to reference your mappings like below:
[POST("signup")]
public virtual ActionResult Signup(UserRegisterViewModel user)
{
if (ModelState.IsValid)
{
var newUser = this.mappingEngine.Map<UserRegisterViewModel, User>(user);
var confirmation = _userService.AddUser(newUser);
if (confirmation.WasSuccessful)
return RedirectToAction(MVC.Home.Index());
else
ModelState.AddModelError("Email", confirmation.Message);
}
return View(user);
}
You can read more about this here: How to inject AutoMapper IMappingEngine with StructureMap
Probably it is cool to abstract mapping into MappingEngine.
Sometimes I use following approach to IOC Automapper
In IOC builder:
builder.RegisterInstance(AutoMapperConfiguration.GetAutoMapper()).As<IMapper>();
where GetAutoMapper is:
public class AutoMapperConfiguration
{
public static IMapper GetAutoMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<OrderModelMapperProfile>();
cfg.AddProfile<OtherModelMapperProfile>();
//etc;
});
var mapper = config.CreateMapper();
return mapper;
}
}
And finally in Controller ctor
public MyController(IMapper mapper)
{
_mapper = mapper;
}
I've just started to use AutoMapper in my MVC 3 project and I'm wondering how people here structure their projects when using it. I've created a MapManager which simply has a SetupMaps method that I call in global.asax to create the initial map configurations. I also need to use a ValueResolver for one of my mappings. For me, this particular ValueResolver will be needed in a couple of different places and will simply return a value from Article.GenerateSlug.
So my questions are:
How do you manage the initial creation of all of your maps (Mapper.CreateMap)?
Where do you put the classes for your ValueResolvers in your project? Do you create subfolders under your Model folder or something else entirely?
Thanks for any help.
i won't speak to question 2 as its really personal preference, but for 1 i generally use one or more AutoMapper.Profile to hold all my Mapper.CreateMap for a specific purpose (domaintoviewmodel, etc).
public class ViewModelToDomainAutomapperProfile : Profile
{
public override string ProfileName
{
get
{
return "ViewModelToDomain";
}
}
protected override void Configure()
{
CreateMap<TripRegistrationViewModel, TripRegistration>()
.ForMember(x=>x.PingAttempts, y => y.Ignore())
.ForMember(x=>x.PingResponses, y => y.Ignore());
}
}
then i create a bootstrapper (IInitializer) that configures the Mapper, adding all of my profiles.
public class AutoMapperInitializer : IInitializer
{
public void Execute()
{
Mapper.Initialize(x =>
{
x.AddProfile<DomainToViewModelAutomapperProfile>();
x.AddProfile<ViewModelToDomainAutomapperProfile>();
});
}
}
then in my global.asax i get all instances of IInitializer and loop through them running Execute().
foreach (var initializer in ObjectFactory.GetAllInstances<IInitializer>())
{
initializer.Execute();
}
that's my general strategy.
by request, here is the reflection implementation of the final step.
var iInitializer = typeof(IInitializer);
List<IInitializer> initializers = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => iInitializer.IsAssignableFrom(p) && p.IsClass)
.Select(x => (IInitializer) Activator.CreateInstance(x)).ToList();
foreach (var initializer in initializers)
{
initializer.Execute();
}