I am attempting to create a "registry" so that just creating a new class implementing IServiceCollectionInitializer or IApplicationBuilderInitializer allows it to be loaded. Instead of having a giant start up class the registry would add those automatically.
My problem is I dont know how to make the app either use a new application builder or retrieve the the one given automatically without getting it from startup.
public class ServiceCollectionInitializerRegistry
{
private readonly IList<IServiceCollectionInitializer> _serviceCollectionInitializers;
private readonly IServiceCollection _serviceCollection;
private readonly IServiceProvider _serviceProvider;
public ServiceCollectionInitializerRegistry(IServiceCollection serviceCollection)
{
_serviceCollectionInitializers = new List<IServiceCollectionInitializer>();
_serviceCollection = serviceCollection;
_serviceProvider = serviceCollection.BuildServiceProvider();
}
public ServiceCollectionInitializerRegistry WithInitializers(
params IServiceCollectionInitializer[] initializers)
{
if (!initializers.Any())
{
return this;
}
foreach (var initializer in initializers)
{
_serviceCollectionInitializers.Add(initializer);
}
return this;
}
public ServiceCollectionInitializerRegistry WithAssemblyInitializers()
{
var assembly = Assembly.GetEntryAssembly();
var initializerTypes =
assembly.GetTypes()
.Where(type => typeof(IServiceCollectionInitializer).IsAssignableFrom(type));
if (!initializerTypes.Any())
{
return this;
}
foreach (var type in initializerTypes)
{
_serviceCollectionInitializers.Add((IServiceCollectionInitializer)Activator.CreateInstance(type));
}
return this;
}
public void Build()
{
var configuration = _serviceProvider.GetRequiredService<IConfiguration>();
foreach (var serviceCollectionInitializer in _serviceCollectionInitializers)
{
var logger = _serviceProvider.GetRequiredService<ILoggerProvider>()
.CreateLogger(serviceCollectionInitializer.GetType().AssemblyQualifiedName);
serviceCollectionInitializer
.Initialize(
configuration,
_serviceCollection, logger);
}
}
}
public class ExceptionHandlingInitializer : IServiceCollectionInitializer, IApplicationBuilderInitializer
{
public void Initialize(IConfiguration configuration, IServiceCollection services, ILogger logger)
{
services.AddSingleton<IExceptionMapper, ExceptionMapper>();
services.AddSingleton<IExceptionHandler, ExceptionHandler>();
}
public void Initialize(IConfiguration configuration, IApplicationBuilder builder, ILoggerFactory loggerFactory)
{
builder.UseExceptionHandlerMiddleware();
}
}
I'm creating a custom model binder for a view model, implementing IModelBinder
I have a lot of properties in my view model, the majority of which do not need any custom binding. Rather than explicitly set all of the property values on my model individually from the ModelBindingContext, I would to be able to get the framework to bind the model for me, then I would carry out any custom binding:
public class ApplicationViewModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// get .net core to bind values on model
// Cary out any customization of the models properties
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
return Task.CompletedTask;
}
}
Basically I want to carry out the default model binding, then apply custom binding, similar to the approach taken in this SO post but for .NET Core, not framework.
I assumed applying the default binding would be straight forward, but haven't been able to find out how to do so. I believe the solution would involve ComplexTypeModelBinder and ComplexTypeModelBinderProvider classes, but can't seem to find out how to go about it.
I know I could just make any changes when the POST request hits my controller method, but this seem the wrong place and wrong time to do so.
For custom ComplexTypeModelBinder, you could inherit from ComplexTypeModelBinder.
Model
public class BinderModel
{
public int Id { get; set; }
public string Name { get; set; }
public string BinderValue { get; set; }
}
Controller Action
[HttpPost]
public void Post([FromForm]BinderModel value)
{
}
CustomBinder
public class CustomBinder : ComplexTypeModelBinder
{
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
public CustomBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders)
: base(propertyBinders)
{
_propertyBinders = propertyBinders;
}
protected override Task BindProperty(ModelBindingContext bindingContext)
{
if (bindingContext.FieldName == "BinderValue")
{
bindingContext.Result = ModelBindingResult.Success("BinderValueTest");
return Task.CompletedTask;
}
else
{
return base.BindProperty(bindingContext);
}
}
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
CustomBinderProvider
public class CustomBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
//var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
//return new ComplexTypeModelBinder(propertyBinders, loggerFactory);
return new CustomBinder(propertyBinders);
}
return null;
}
}
Inject provider
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.ModelBinderProviders.Insert(0, new CustomBinderProvider());
});
}
ComplexTypeModelBinder has unfortunately been deprecated in .Net 5.0, and it's counterpart, ComplexObjectModelBinder, is sealed, so you can't extend from it.
But, you can work around that. ComplexObjectModelBinderProvider is public, and you can use that to create a ComplexObjectModelBinder. Thus, if you make your own custom IModelBinderProvider, you can have the constructor accept a ComplexObjectModelBinderProvider argument, and make use of that to make a ComplexObjectModelBinder. Then, you can pass that to your custom IModelBinder, where it'll happily do its custom work before falling back to the ComplexObjectModelBinder you supplied.
Here's an example. First, your IModelBinder. This example shows that you can use DI if you want to. (In this example, say we needed a DbContext.)
public class MyCustomModelBinder : IModelBinder
{
private readonly IModelBinder _defaultBinder;
private readonly DbContext _dbContext;
public MyCustomModelBinder(IModelBinder defaultBinder, DbContext dbContext)
{
_defaultBinder = defaultBinder;
_dbContext = dbContext;
}
public override Task BindModelAsync(ModelBindingContext bindingContext)
{
// -do custom work here-
return _defaultBinder.BindModelAsync(bindingContext);
}
}
However, in order to use DI on your custom model binder, you'll need a helper class. The problem is, when IModelBinderProvider is called, it won't have access to all the services in a typical request, like your DbContext for example. But this class will help:
internal class DIModelBinder : IModelBinder
{
private readonly IModelBinder _rootBinder;
private readonly ObjectFactory _factory;
public DIModelBinder(Type binderType, IModelBinder rootBinder)
{
if (!typeof(IModelBinder).IsAssignableFrom(binderType))
{
throw new ArgumentException($"Your binderType must derive from IModelBinder.");
}
_factory = ActivatorUtilities.CreateFactory(binderType, new[] { typeof(IModelBinder) });
_rootBinder = rootBinder;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var requestServices = bindingContext.HttpContext.RequestServices;
var binder = (IModelBinder)_factory(requestServices, new[] { _rootBinder });
return binder.BindModelAsync(bindingContext);
}
}
Now you're ready to write the custom IModelBinderProvider:
public class MyCustomModelBinderProvider : IModelBinderProvider
{
private readonly IModelBinderProvider _rootProvider;
public MyCustomModelBinderProvider(IModelBinderProvider rootProvider)
{
_rootProvider = rootProvider;
}
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(MyModel))
{
var rootBinder = _rootProvider.GetBinder(context)
?? throw new InvalidOperationException($"Root {_rootProvider.GetType()} did not provide an IModelBinder for MyModel.");
return new DIModelBinder(typeof(MyCustomModelBinder), rootBinder);
}
return null;
}
}
Finally, in your startup file where you configure services, you can grab the ComplexObjectModelBinderProvider instance, use that to create a new instance of your MyCustomModelBinderProvider, and insert that into the ModelBinderProviders.
services.AddMvc(options =>
{
var fallbackProvider = options.ModelBinderProviders
.First(p => p is ComplexObjectModelBinderProvider);
var myProvider = new MyCustomModelBinderProvider(fallbackProvider);
options.ModelBinderProviders.Insert(0, myProvider);
})
At present, I am writing unit tests for my controller. Below is the structure of my code in the project.
MyController Class
public class MyController : Controller
{
private readonly MyRepository _myRepository;
public MyController()
: this(new MyRepository())
{
}
[HttpGet]
public ActionResult Index()
{
var items = _myRepository.GetAllItems();
if (items.Count() == 0)
return View("EmptyItems");
else
{
return View("List", items);
}
}
}
MyRepository Class
public class MyRepository : IDisposable, IMyRepository
{
private readonly MyDbContext _dbcontext;
private readonly ISecurityService _securityService;
public TodoListItemsRepository() : this(new MyDbContext(), new SecurityService())
{
}
public TodoListItemsRepository(MyDbContext context, ISecurityService securityService)
{
_dbcontext = context;
_securityService = securityService;
}
public IEnumerable<MyModel> GetAllItems()
{
var userid = _securityService.GetUser();
var todoList = _dbcontext.MyList.Where(e => e.UserId == userid);
return todoList;
}
//Other Methods etc...
......
}
SecurityService class
public class SecurityService : ISecurityService
{
public int GetUser()
{
return (int)Membership.GetUser().ProviderUserKey;
}
}
Here all methods inside my repository depends on GetUser method. Hence, I have initialized it inside the constructor. The repository class is initialized from the controller constructor.
My issue is - I couldn't unit the Index action unless I need to initialize dbcontext and the securityservice. Could someone please advise me if I am doing the right thing or any changes required in the structure of my code so that I can unit test my application ? I am new to MVC. So, any suggestions would be much appreciated.
Why I am not able to inject the SetterProperty via StructureMap to an MVC ActionFilter?
public class LockProjectFilter : ActionFilterAttribute
{
[SetterProperty]
public ISecurityService SecurityService { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var loggedinStaffId = SecurityService.GetLoggedInStaffId();
if (loggedinStaffId == 1)
throw new ArgumentNullException();
base.OnActionExecuting(filterContext);
}
}
public static IContainer Initialize()
{
ObjectFactory.Initialize(x =>
{
x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.AssemblyContainingType<ISecurityService>();
});
x.SetAllProperties(p => p.OfType<ISecurityService>());
//x.ForConcreteType<LockProjectFilter>().Configure
// .Setter(c => c.SecurityService).IsTheDefault();
});
return ObjectFactory.Container;
}
You need to utilize the 'BuildUp' method off the ObjectFactory.
http://docs.structuremap.net/ConstructorAndSetterInjection.htm#section4
[Test]
public void create_a_setter_rule_and_see_it_applied_in_BuildUp_through_ObjectFactory()
{
var theGateway = new DefaultGateway();
ObjectFactory.Initialize(x =>
{
x.ForRequestedType<IGateway>().TheDefault.IsThis(theGateway);
// First we create a new Setter Injection Policy that
// forces StructureMap to inject all public properties
// where the PropertyType is IGateway
x.SetAllProperties(y =>
{
y.OfType<IGateway>();
});
});
// Create an instance of BuildUpTarget1
var target = new BuildUpTarget1();
// Now, call BuildUp() on target, and
// we should see the Gateway property assigned
ObjectFactory.BuildUp(target);
target.Gateway.ShouldBeTheSameAs(theGateway);
}
Then you can create a new FilterAttributeFilterProvider like this:
public class DependencyResolverFilterProvider : FilterAttributeFilterProvider
{
public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(controllerContext, actionDescriptor);
foreach (var filter in filters)
{
//DI via Setter Injection
DependencyResolver.BuildUp(filter.Instance);
}
return filters;
}
}
Then finally add your custom filter provider to the .net pipeline.
private static void RegisterProviderAndFilters()
{
var oldProvider = FilterProviders.Providers.Single(f => f is FilterAttributeFilterProvider);
FilterProviders.Providers.Remove(oldProvider);
FilterProviders.Providers.Add(new DependencyResolverFilterProvider());
RegisterGlobalFilters(GlobalFilters.Filters);
}
Hope this helps!
wm
A bit of history first. I created a EF Code First Library that contains POCO Objects as my Models, a generic DataProvider that inherits from DbContext, generic Repostory that implements the generic DataProvider, and a generic Service that implements the repository. I have used this library successfully in WPF (MVVM), ASP.Net, Window Forms, and ASP MVC applications.
For this discussion I will reference the Company Model
From the top down, I create a Service class called CompanyService that inherits from a base Service Class. The CompanyService class contains all of the business logic for the Company Model. This class uses the Repository class to perform the CRUD operations. The Repository then encapsulates all the DataProvider class operations.
I have done some research on using EF with WCFDataService, but I can't get my head around how to implement my library with it, particulary when it comes to overriding the CreateDataSource() Method.
It may be that I should just use a WCF Service instead, maybe I'm not understanding the purpose of the WCFDataService.
I have listed partial code for the classes involved:
public class CompanyService : ServiceBase<Company> ,ICompanyService
{
public Company GetCompanyByFolderId(string eFolderId)
{
return (Company)GetModelByFolderId(eFolderId);
}
}
public abstract class ServiceBase<TModel> : IService<TModel> where TModel : class, IModel
{
private IDataProvider _dataProvider;
public IDataProvider DataProvider
{
get
{
if (_dataProvider == null)
{
string connectionStringName = Properties.Settings.Default.DataProvider;
bool enableLazyLoading = true;
_dataProvider = new DataProvider(connectionStringName, enableLazyLoading);
}
return _dataProvider;
}
set
{
_dataProvider = value;
}
}
private IRepository<TModel> _repository;
public IRepository<TModel> Repository
{
get
{
if (_repository == null)
{
_repository = new Repository<TModel>(DataProvider);
}
return _repository;
}
set
{
_repository = value;
}
}
public TModel GetModelByFolderId(String folderId)
{
return GetTable().FirstOrDefault(o => o.EFolderid == folderId);
}
public virtual IQueryable<TModel> GetTable()
{
return Repository.GetTable();
}
}
public class Repository<TModel> : IRepository<TModel> where TModel : class, IModel
{
private IDataProvider _dataProvider;
public Repository(IDataProvider dataProvider)
{
_dataProvider = dataProvider;
}
private IDbSet<TModel> DbSet
{
get
{
return _dataProvider.Set<TModel>();
}
}
public IQueryable<TModel> GetTable()
{
return _dataProvider.GetTable<TModel>();
}
}
public class DataProvider : DbContext, IDataProvider
{
public DataProvider()
{
}
public DataProvider(string connectionStringName, bool enableLazyLoading = true)
: base(connectionStringName)
{
Configuration.LazyLoadingEnabled = enableLazyLoading;
//Configuration.ProxyCreationEnabled = false;
}
public new IDbSet<TModel> Set<TModel>() where TModel : class
{
return base.Set<TModel>();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new CompanyMapping());
base.OnModelCreating(modelBuilder);
}
public IQueryable<TModel> GetTable<TModel>() where TModel : class
{
return Set<TModel>().AsQueryable();
}
}
Then my Test looks something like this:
[TestClass()]
public class CompanyServiceTest
{
[TestMethod()]
public void GetCompanies()
{
CompanyService target = new CompanyService();
IQueryable<Company> companies = target.GetTable();
Assert.IsNotNull(companies);
}
[TestMethod()]
public void GetCompanyByFolderId()
{
CompanyService target = new CompanyService();
Company company = target.GetCompanyByFolderId("0000000000000000000000000172403");
Assert.IsNotNull(company);
}
}