I'm using Autofac with ASP.NET MVC and am wondering if my view model setup in the web project is a good approach or not. In the past I only used constructer injection at the Controller level but thought it would be interesting to see if everything could be injected via Autofac.
Let's say I have a view model that looks this:
public class CollegeViewModel : BaseViewModel
{
private CollegeModel _model { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public CollegeViewModel(ICsnCache cache, CollegeModel model)
{
this._cache = cache;
this._model = model;
}
public void Populate() { /* TODO: */ }
public void Update() { /* TODO: */ }
}
Cache is to access code sets (i.e. for drop down lists)
The model has methods that are called from the view model (e.g. Save())
This is where things get a bit strange. I found myself having to create a custom action invoker which derives from System.Web.Mvc.Async.AsyncControllerActionInvoker. Autofac has one of these already Autofac.Integration.Mvc.ExtensibleActionInvoker. The problem I found with the one built into Autofac is that it stopped the default model binding. i.e. it succeeded in injecting my dependencies but then the rest of the view model properties were empty even though the POST had valid form data.
This is a combination of Autofac and ASP.NET MVC code (taken out some validation and comments for readability):
public class CustomExtensibleActionInvoker : System.Web.Mvc.Async.AsyncControllerActionInvoker
{
protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
{
object value = null;
try
{
value = base.GetParameterValue(controllerContext, parameterDescriptor);
}
catch (MissingMethodException) { }
if (value == null)
{
// We got nothing from the default model binding, so try to resolve it.
var context = Autofac.Integration.Mvc.AutofacDependencyResolver.Current.RequestLifetimeScope;
value = context.ResolveOptional(parameterDescriptor.ParameterType);
// This is the part I added, which is from the ASP.NET MVC source code
ModelBindingContext bindingContext = new ModelBindingContext()
{
FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => value, parameterDescriptor.ParameterType),
ModelName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName,
ModelState = controllerContext.Controller.ViewData.ModelState,
PropertyFilter = GetPropertyFilter(parameterDescriptor),
ValueProvider = controllerContext.Controller.ValueProvider,
};
value = System.Web.Mvc.ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
}
return value;
}
private Predicate<string> GetPropertyFilter(ParameterDescriptor parameterDescriptor)
{
ParameterBindingInfo bindingInfo = parameterDescriptor.BindingInfo;
return propertyName => IsPropertyAllowed(propertyName, bindingInfo.Include, bindingInfo.Exclude);
}
private bool IsPropertyAllowed(string propertyName, ICollection<string> includeProperties, ICollection<string> excludeProperties)
{
bool includeProperty = (includeProperties == null) || (includeProperties.Count == 0) || includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
bool excludeProperty = (excludeProperties != null) && excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
return includeProperty && !excludeProperty;
}
}
I realise I'm always using the default model binder, but that could be enhanced if I ever needed other model binders.
Could it be a bug in Autofac (the fact model binding doesn't happen but the DI works) or am I misusing the framework?
Note that this does work, but since it's early days I'm worried I might be adding complexity and maybe should just handle some dependency injection myself.
Edit (tweaked Ruskin's code to suit my app):
public class MyCustomModelBinder : DefaultModelBinder
{
/// <summary>
/// If the model type is registered in our Autofac configuration,
/// use that, otherwise let MVC create the model as per usual
/// </summary>
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var item = DependencyResolver.Current.GetService(modelType);
if (item != null)
{
return item;
}
return base.CreateModel(controllerContext, bindingContext, modelType);
}
}
Global.asax.cs:
protected void Application_Start()
{
// removed other code for readability
System.Web.Mvc.ModelBinders.Binders.DefaultBinder = new MyCustomModelBinder();
}
To answer your question (I think that's your question):
The problem I found with the one built into Autofac is that it stopped the default model binding. i.e. it succeeded in injecting my dependencies but then the rest of the view model properties were empty even though the POST had valid form data.
I don't think it's a bug in Autofac, I believe the model resolution happens well before the MVC has bound it's properties into the view model, so when are you expecting the properties to be present in the view model?
I had the exact same issue: have a a read of this question
Edit:
This is your autofac registration where builder is your ContainerBuilder...
var types = LoadMyViewModels(); // Do some reflection to get all your viewmodels
foreach (var type in types)
{
Type modelBinderType = typeof(MyCustomModelBinder<>);
Type[] typeArgs = { modelType };
Type genType = modelBinderType.MakeGenericType(typeArgs);
object viewmodel = Activator.CreateInstance(genType);
ModelBinders.Binders.Add(modelType, (IModelBinder)viewmodel);
var registeredType = builder.RegisterType(modelType).AsSelf();
}
CustomModelBinder
[ModelBinderType]
public class MyCustomModelBinder<T> : DefaultModelBinder where T : class
{
[NotNull]
protected override object CreateModel([NotNull] ControllerContext controllerContext, [NotNull] ModelBindingContext bindingContext, [NotNull] Type modelType)
{
var item = DependencyResolver.Current.GetService<T>();
if (item != null)
{
return item;
}
throw new ArgumentException(string.Format("model type {0} is not registered", modelType.Name));
}
}
Related
I have multiple Object with different structure, but each time i make a save on these objects, i want to carry out common control actions because these objects are in fact "content objects" of a similar process.
imagine in UI user can do tasks, he must enter different data depend on task but we stay in the same "process".
i want to avoid multiple HttPut Route, so i want something like this :
[HttpPut("{processId}/content")]
public ActionResult SaveContent(IContent content)
{
//Do something with the processID like security controll
//Now i can do the different save
}
i want to have an instance of my specific object, i can determine type with a property "ContentType"
i would like to have a custombinder to do something like this :
if(content.GetContent==ContentA)
{
return ContentA with all properties binded from body
}
else if(content.GetContent==ContentB)
{
return ContentB with all properties binded from body
}
i dont want to map properties manually, i want to work as if I had put "ContentA" or "ContentB" in [FromBody] parameter.
i just want to avoid this:
[HttpPut("{processId}/content-a")]
public ActionResult SaveContentA(ContentA contentA)
{
//Do something with the processID like security control
//Now i can save contentA
}
[HttpPut("{processId}/content-b")]
public ActionResult SaveContentB(ContentB contentb)
{
//Do something with the processID like security control
//Now i can save contentB
}
I can have 20+ different content, that's a lot of different routes (of course I have different services behind that do different actions depending on content A or B but I would like to avoid as many routes).
I looked at the side of the custombinding but I did not manage to do what I wanted.
Thank you.
I'm not so sure if I understand your requirement correctly. From what you shared, the IContent is some kind of a base interface which contains a common set of members (properties, ...) for all the derived types (which should be well-known). So it's actually a scenario of polymorphic model binding and can be implemented by using a customer IModelBinder as demonstrated in a basic example here https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-5.0#polymorphic-model-binding
I've adjusted that example a bit to make it cleaner with more separate responsibility and better naming. The following code is not tested at all. Because the main logic is basically the same as from the example in the given link above:
//the marker interface to indicate the model should be polymorphically bound.
//you can replace this with your IContent
public interface IPolymorphModel { }
//a separate resolver to help resolve actual model type from ModelBindingContext
//(kind of separating responsibility
public interface IPolymorphTypeResolver
{
Type ResolveType(ModelBindingContext modelBindingContext);
}
//a separate types provider to get all derived types from a base type/interface
public interface IPolymorphTypeProvider
{
IEnumerable<Type> GetDerivedTypesFrom<T>();
}
//the custom model binder for polymorphism
public class PolymorphModelBinder : IModelBinder
{
readonly IDictionary<Type, (ModelMetadata ModelMetadata, IModelBinder Binder)> _bindersByType;
readonly IPolymorphTypeResolver _polymorphTypeResolver;
public PolymorphModelBinder(IDictionary<Type, (ModelMetadata,IModelBinder)> bindersByType,
IPolymorphTypeResolver polymorphTypeResolver)
{
_bindersByType = bindersByType;
_polymorphTypeResolver = polymorphTypeResolver;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelType = _polymorphTypeResolver.ResolveType(bindingContext);
if(modelType == null ||
!_bindersByType.TryGetValue(modelType, out var binder))
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
//create new binding context with the concrete/actual model type
var actualBindingContext = DefaultModelBindingContext.CreateBindingContext(bindingContext.ActionContext,
bindingContext.ValueProvider,
binder.ModelMetadata,
null,
bindingContext.ModelName);
await binder.Binder.BindModelAsync(actualBindingContext);
//set back the result to the original bindingContext
bindingContext.Result = actualBindingContext.Result;
if (actualBindingContext.Result.IsModelSet)
{
bindingContext.ValidationState[bindingContext.Result] = new ValidationStateEntry {
Metadata = binder.ModelMetadata
};
}
}
}
//the custom model binder provider for polymorphism
public class PolymorphModelBinderProvider<T> : IModelBinderProvider
{
readonly IPolymorphTypeResolver _polymorphTypeResolver;
readonly IPolymorphTypeProvider _polymorphTypeProvider;
public PolymorphModelBinderProvider(IPolymorphTypeResolver polymorphTypeResolver,
IPolymorphTypeProvider polymorphTypeProvider)
{
_polymorphTypeResolver = polymorphTypeResolver;
_polymorphTypeProvider = polymorphTypeProvider;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (!typeof(T).IsAssignableFrom(context.Metadata.ModelType)) return null;
//prepare the list of the well-known derived types
var derivedTypes = _polymorphTypeProvider.GetDerivedTypesFrom<T>();
var binders = derivedTypes.ToDictionary(e => e,
e =>
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(e);
return (modelMetadata, context.CreateBinder(modelMetadata));
}
);
return new PolymorphModelBinder(binders, _polymorphTypeResolver);
}
}
Here is the default implementation for a IPolymorphTypeResolver which basically depends on an IValueProvider from the ModelBindingContext (which is just like what used in the Microsoft's example):
public class DefaultPolymorphTypeResolver : IPolymorphTypeResolver
{
public Type ResolveType(ModelBindingContext modelBindingContext)
{
var contentTypeValueKey = ModelNames.CreatePropertyModelName(modelBindingContext.ModelName, "ContentType");
var contentType = modelBindingContext.ValueProvider.GetValue(contentTypeValueKey).FirstValue;
switch (contentType)
{
case "whatever ...":
return ...;
//...
default:
return null;
}
}
}
If you store the contentType in header or some other sources, just implement it your own way, here for the content type stored in the request header:
public class DefaultPolymorphTypeResolver : IPolymorphTypeResolver {
public Type ResolveType(ModelBindingContext modelBindingContext) {
var contentType = modelBindingContext.ActionContext.HttpContext.Request.ContentType;
switch (contentType)
{
//...
default:
return null;
}
}
}
Here is the default implementation of IPolymorphTypeProvider:
public class PolymorphContentTypeProvider : IPolymorphTypeProvider
{
public IEnumerable<Type> GetDerivedTypesFrom<T>()
{
return new List<Type> { /* your logic to populate the list ... */ };
}
}
Now we configure it to register everything needed including the PolymorphModelBinderProvider and your specific implementation of IPolymorphTypeResolver and IPolymorphTypeProvider:
//in ConfigureServices
services.AddSingleton<IPolymorphTypeResolver, DefaultPolymorphTypeResolver>();
services.AddSingleton<IPolymorphTypeProvider, PolymorphContentTypeProvider>();
services.AddOptions<MvcOptions>()
.Configure((MvcOptions o,
IPolymorphTypeResolver typeResolver,
IPolymorphTypeProvider typesProvider) =>
{
o.ModelBinderProviders.Insert(0, new PolymorphModelBinderProvider<IPolymorphModel>(typeResolver, typesProvider));
});
I just wasted a lot of hours with trying to get a custom ComplexTypeModelBinder to work. Whatever I did, it never worked. As it turns out, this only works when the data is POSTed as form data; when you post a JSON object (in my case from a Swagger "try out" form) the ComplexTypeModelBinder never invokes the SetProperty method.
I have a lot of models, some more complex than others, and I have annotated some of the properties with a custom attribute. Whenever that property is bound I want it to 'normalized' (apply some 'formatting' to it) so that by the time the model gets validated the validator gets to see the 'normalized' data instead of the user-entered data.
I really, really, want to keep the current modelbinding behavior because that currently works fine but with the one exception that the annotated properties are processed by some code implemented by me. All other properties and behavior should be kept as-is. That is why I hoped to inherit from ComplexTypeModelBinder, but, as it turns out, this doesn't work if data is POSTed as JSON.
My (example) model looks like:
public class MyComplexModel
{
public int Id { get; set; }
public string Name { get; set; }
[FormatNumber(NumberFormat.E164)]
public string PhoneNumber { get; set; }
}
My controller method looks like this:
[HttpPost]
public MyComplexModel Post(MyComplexModel model)
{
return model;
}
My (not working) custom ComplexTypeModelBinder looks like:
public class MyModelBinder : ComplexTypeModelBinder
{
private readonly INumberFormatter _numberformatter;
private static readonly ConcurrentDictionary<Type, Dictionary<string, FormatNumberAttribute>> _formatproperties = new ConcurrentDictionary<Type, Dictionary<string, FormatNumberAttribute>>();
public MyModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, INumberFormatter numberFormatter, ILoggerFactory loggerFactory)
: base(propertyBinders, loggerFactory)
{
_numberformatter = numberFormatter;
}
protected override object CreateModel(ModelBindingContext bindingContext)
{
// Index and cache all properties having the FormatNumber Attribute
_formatproperties.GetOrAdd(bindingContext.ModelType, (t) =>
{
return t.GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(FormatNumberAttribute))).ToDictionary(pi => pi.Name, pi => pi.GetCustomAttribute<FormatNumberAttribute>(), StringComparer.OrdinalIgnoreCase);
});
return base.CreateModel(bindingContext);
}
protected override Task BindProperty(ModelBindingContext bindingContext)
{
return base.BindProperty(bindingContext);
}
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
if (_formatproperties.TryGetValue(bindingContext.ModelType, out var props) && props.TryGetValue(modelName, out var att))
{
// Do our formatting here
var formatted = _numberformatter.FormatNumber(result.Model as string, att.NumberFormat);
base.SetProperty(bindingContext, modelName, propertyMetadata, ModelBindingResult.Success(formatted));
} else
{
// Do nothing
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
}
(The actual MyModelBinder checks for the FormatNumber attribute and handles the property accordingly, but I left it out for brevity since it doesn't really matter).
And my ModelBinderProvider:
public class MyModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var modelType = context.Metadata.ModelType;
if (!typeof(MyComplexModel).IsAssignableFrom(modelType))
return null;
if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType)
return null;
var propertyBinders = context.Metadata.Properties
.ToDictionary(modelProperty => modelProperty, context.CreateBinder);
return new MyModelBinder(
propertyBinders,
(INumberFormatter)context.Services.GetService(typeof(INumberFormatter)),
(ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory))
);
}
}
And ofcourse, I added the provider in the StartUp class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config =>
{
config.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Again, this works fine when data is posted as form-data but not when posted as JSON. What would be the correct way to implement this? I have read somewhere that I shouldn't be looking in the ModelBinding direction but in the "JSON converters" direction but I haven't found anything that actually worked (yet).
Edit: I have created a git repository to demonstrate my problem here. To see my problem, set a breakpoint here in the TestController where the model is returned in the Post method. Start the project; a simple webpage will be shown with two buttons. The left one will post the form data as, well, form-data and you will see the model being returned with a reversed phonenumber (as an example). Click the right button and the data will be posted as a JSON model. Notice the model being returned having a 0 id and null values for both properties.
following code in asp.net web API worked fine but doesn't work in Asp.net core.
Endpoint api/devices?query={"deviceName":"example"}
[HttpGet]
public Device ([FromUri] string deviceName)
{
var device = context.Computers.Where(x => x.deviceName == deviceName);
return device;
}
[FromUri] attribute is not present asp.net core web API, and I tried to use following , but no success.
[HttpGet]
public Device Get([FromQuery] string deviceName)
{
return repo.GetDeviceByName(deviceName);
}
Unfortunately there is no way to bind JSON in a GET query like you have there. What you are looking for is to use a custom model binder to tell ASP.net Core how you want to bind.
First, you want to build your model for your JSON object.
public class MyCustomModel
{
public string DeviceName { get; set; }
}
Next you need to build your model binder. A simple example is given below but you would obviously want other checks around if it can be converted, Try/Catch blocks etc. Essentially a model binder tells ASP.net Core how a model should be bound. You might also run into TypeConverters which are given a type, how can I change this to another type during model binding. For now let's just use modelbinders.
public class MyViewModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var jsonString = bindingContext.ActionContext.HttpContext.Request.Query["query"];
MyCustomModel result = JsonConvert.DeserializeObject<MyCustomModel>(jsonString);
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
So all we are doing is taking the query string and deserializing it to our model.
Next we build a provider. A provider is what tells ASP.net core which modelbinder to use. In our case it's simple, if the model type is our custom type, then use our custom binder.
public class MyViewModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(MyCustomModel))
return new MyViewModelBinder();
return null;
}
}
And the final piece of the puzzle. In our startup.cs, we find where we add MVC services and we insert our model binder to the front of the list. This is important. If we just add our modelbinder to the list, another model binder might think it should be used instead (First in first served), so we might not ever make it to ours. So be sure to insert it at the start.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config => config.ModelBinderProviders.Insert(0, new MyViewModelBinderProvider()));
}
Now we just create an action where we read the data, no attributes required.
[HttpGet]
public void Get(MyCustomModel model)
{
}
Further reading :
http://dotnetcoretutorials.com/2016/12/28/custom-model-binders-asp-net-core/
https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding
I came along this question because I have a similar problem.
In my scenario it was more convinient to implement ModelBinderAttribute:
public class FromUriJsonAttribute : ModelBinderAttribute
{
public FromUriJsonAttribute()
{
BinderType = typeof(JsonBinder);
}
public FromUriJsonAttribute(string paramName)
: this()
{
Name = paramName;
}
}
As in #MindingData answer the binder is needed:
public class JsonBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
try
{
var name = bindingContext.ModelName;
var json = actionContext.Request.GetQueryNameValuePairs().FirstOrDefault(x => x.Key == name).Value;
var targetType = bindingContext.ModelType;
var model = Newtonsoft.Json.JsonConvert.DeserializeObject(json, targetType);
bindingContext.Model = model;
return true;
}
catch
{
}
return false;
}
}
And the usage:
[Route("{id}/items")]
public Item GetItem(string id, [FromUriJson] Filter filter)
{
//logic
}
or:
[Route("{id}/items")]
public Item GetItem(string id, [FromUriJson("queryStringKey")] Filter filter)
{
//logic
}
It can be useful if you want to have Json formatted query string parameter only in some endpoints and/or don't want to mess with default IServiceCollection configuration by inserting a new IModelBinderProvider implementation.
This question is similar to what I want to achieve:
Avoiding null model in ASP.Net Web API when no posted properties match the model
But it's gone un-answered.
I have a route which takes a model that is a GET:
[HttpGet, Route("accounts")]
public AccountListResult Post(AccountListRequest loginRequest)
{
return accountService.GetAccounts(loginRequest);
}
The model is populated with additional data from an action filter.
In this case all that needs to be known is the UserId, which the action filter add's to the model based on cookie/header into passed in with the request.
I want to use all the default model binding in WebAPI but I want to avoid null objects.
I don't believe model binding solves my problem.
How do I replace the behaviour of Web API model binding so that instead of Null I receive a new instance when no parameters are passed in
This is closer to what I want to do except its per type which is tedious.
EDIT: Since the question is for Web API, I am posting the Web API solution also below.
You can do this as below in an Action Filter. The below code works only if your model contains default constructor.
Web API Implementation:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
foreach (var parameter in parameters)
{
object value = null;
if (actionContext.ActionArguments.ContainsKey(parameter.ParameterName))
value = actionContext.ActionArguments[parameter.ParameterName];
if (value != null)
continue;
value = CreateInstance(parameter.ParameterType);
actionContext.ActionArguments[parameter.ParameterName] = value;
}
base.OnActionExecuting(actionContext);
}
protected virtual object CreateInstance(Type type)
{
// Check for existence of default constructor using reflection if needed
// and if performance is not a constraint.
// The below line will fail if the model does not contain a default constructor.
return Activator.CreateInstance(type);
}
MVC Implementation:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var parameters = filterContext.ActionDescriptor.GetParameters();
foreach (var parameter in parameters)
{
if (filterContext.ActionParameters.ContainsKey(parameter.ParameterName))
{
object value = filterContext.ActionParameters[parameter.ParameterName];
if (value == null)
{
// The below line will fail if the model does not contain a default constructor.
value = Activator.CreateInstance(parameter.ParameterType);
filterContext.ActionParameters[parameter.ParameterName] = value;
}
}
}
base.OnActionExecuting(filterContext);
}
#Sarathy's solution works, but doesn't trigger model validation on objects it creates. This can cause situations where an empty model was passed in to an action but ModelState.IsValid still evaluates true.
For my own purposes, it was necessary to trigger re-validation of the model in the event that an empty model object was created:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
foreach (var parameter in parameters)
{
object value = null;
if (actionContext.ActionArguments.ContainsKey(parameter.ParameterName))
value = actionContext.ActionArguments[parameter.ParameterName];
if (value != null)
continue;
value = Activator.CreateInstance(parameter.ParameterType);
actionContext.ActionArguments[parameter.ParameterName] = value;
var bodyModelValidator = actionContext.ControllerContext.Configuration.Services.GetBodyModelValidator();
var metadataProvider = actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider();
bodyModelValidator.Validate(value, value.GetType(), metadataProvider, actionContext, string.Empty);
}
base.OnActionExecuting(actionContext);
}
In short: I am trying to create a custom model binder that will take in the type of user and get their id, then use a service class to retrieve the strongly typed object.
If there is a better way to do this, please let me know.
Elabaration:
I have ninject setup with all my bindings within my DomainService layer, 3 web ui's are hooked up to the domain service layer. Each asp.net mvc app loads the bindings into the kernal.
//my custom model binder
public class UserModelBinder : IModelBinder
{
private IAuthenticationService auth;
public UserModelBinder(IAuthenticationService _auth, EntityName type,
string loggedonuserid)
{
this.auth = _auth;
CurrentUserType = type;
CurrentUserId = loggedonuserid;
}
public EntityName CurrentUserType { get; private set; }
private string CurrentUserId { get; set; }
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
object loggedonuser = null;
if (CurrentUserType == EntityName.Client)
loggedonuser = GetLoggedOnClientUser(CurrentUserId);
else if (CurrentUserType == EntityName.Shop)
loggedonuser = GetLoggedOnShopUser(CurrentUserId);
else
throw new NotImplementedException();
return loggedonuser;
}
public ClientUser GetLoggedOnClientUser(string loggedonuserid)
{
var user = _auth.GetLoggedOnClientUser(loggedonuserid);
if (user == null)
throw new NoAccessException();
return user;
}
public ShopUser GetLoggedOnShopUser(string loggedonuserid)
{
var user = _auth.GetLoggedOnShopUser(loggedonuserid);
if (user == null)
throw new NoAccessException();
return user;
}
}
my Global.aspx.cs
// using NInject to override application started
protected override void OnApplicationStarted()
{
AreaRegistration.RegisterAllAreas();
// hand over control to NInject to register all controllers
RegisterRoutes(RouteTable.Routes);
//how do I instantiate?
ModelBinders.Binders.Add(typeof(object), new
UserModelBinder(null,EntityName.Client, User.Identity.Name));
}
My problem is IAuthentication is a service, it is connected to other things like a repository, how do I actually instantiate this properly? Should I create a new NinjectModule? I am really confused with this so any help is greatly appreciated. I have tried to pass in Container.Get(); - but it is null...
NOTE: the reason why I am creating a modelbinder- all controllers will require the type of user as I my service layer requires which type of user is making a request, most methods in my service layer will have overloads where it will do one thing for a ShopUser or ClientUser or any other user in the system...
EDIT:
I could very easiy within my controller call upon the IAuthenticationService and get the type of user and pass into my domainservice layer to process the relevant tasks but I just want to know how it is possible with the ModelBindings (and if it makes sense to do it that way).
Edit2: Is there a working sample of using a custom Attribute with AOP with the custom attribute calling/binding/getting an instance of ISomethingService?
You can use the Service Locator pattern here. Pass the Ninject Container (IKernel?) to the constructor and resolve the AuthenticationService each time you need to bind something.
A refinement of this could be to have a constructor argument Func where you pass the function to resolve the service. This would be more explicit and removes the dependency on Ninject. Something like this:
public class MyModelBinder : IModelBinder
{
Func<IAuthenticationService> _resolveAuthService;
public MyModelBinder(Func<IAuthenticationService> resolveAuthService)
{
_resolveAuthService = resolveAuthService;
}
public override object Bind(Context c)
{
var authService = _resolveAuthService();
authService.GetSomething();
// etc...
}
}