MVC 6 Custom Model Binder with Dependency Injection - c#

Right now my ViewModel looks like this:
public class MyViewModel
{
private readonly IMyService myService;
public ClaimantSearchViewModel(IMyService myService)
{
this.myService = myService;
}
}
My Controller that consumes this ViewModel looks like this:
public class MyController : Controller
{
private readonly IMyService myService;
public HomeController(IMyService myService)
{
this.myService = myService;
}
public IActionResult Index()
{
var model = new MyViewModel(myService);
return View(model);
}
[HttpPost]
public async Task<IActionResult> Find()
{
var model = new MyViewModel(myService);
await TryUpdateModelAsync(model);
return View("Index", model);
}
}
What I need is my Controller to look like is this:
public class MyController : Controller
{
private readonly IServiceProvider servicePovider;
public MyController(IServiceProvider servicePovider)
{
this.servicePovider = servicePovider;
}
public IActionResult Index()
{
var model = servicePovider.GetService(typeof(MyViewModel));
return View(model);
}
[HttpPost]
public IActionResult Index(MyViewModel model)
{
return View(model);
}
}
Right now, calling the first Index method works fine (with
builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource(x => x.Name.Contains("ViewModel")));
in my Startup class) but doing the POST to Index(MyViewModel model) gives you a No parameterless constructor defined for this object exception. I realize that a custom model binder that can use my DI will be the most likely solution... but I'm not able to find any help on how to even get started here. Please help me with this, especially for Autofac in MVC 6.

We got the answer here: https://github.com/aspnet/Mvc/issues/4167
And the answer is to use: [FromServices]
My Model ends up looking like this:
public class MyViewModel
{
[FromServices]
public IMyService myService { get; set; }
public ClaimantSearchViewModel(IMyService myService)
{
this.myService = myService;
}
}
Although it's sad to make that property public, it's much less sad than having to use a custom model binder.
Also, supposedly you should be able to pass [FromServices] as part of the param in the Action method, it does resolve the class, but that breaks the model binding... ie none of my properties got mapped. It looks like this: (but again, THIS DOES NOT WORK so use the above example)
public class MyController : Controller
{
... same as in OP
[HttpPost]
public IActionResult Index([FromServices]MyViewModel model)
{
return View(model);
}
}
UPDATE 1
After working with the [FromServices] attribute we decided that property injection in all of our ViewModels was not the way we wanted to go, especially when thinking about long term maintenance with testing. SO we decided to remove the [FromServices] attributes and got our custom model binder working:
public class IoCModelBinder : IModelBinder
{
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
var serviceProvider = bindingContext.OperationBindingContext.HttpContext.RequestServices;
var model = serviceProvider.GetService(bindingContext.ModelType);
bindingContext.Model = model;
var binder = new GenericModelBinder();
return binder.BindModelAsync(bindingContext);
}
}
It's registered like this in the Startup ConfigureServices method:
services.AddMvc().AddMvcOptions(options =>
{
options.ModelBinders.Clear();
options.ModelBinders.Add(new IoCModelBinder());
});
And that's it. (Not even sure that options.ModelBinders.Clear(); is needed.)
UPDATE 2
After going through various iterations of getting this to work (with help https://github.com/aspnet/Mvc/issues/4196), here is the final result:
public class IoCModelBinder : IModelBinder
{
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{ // For reference: https://github.com/aspnet/Mvc/issues/4196
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
if (bindingContext.Model == null && // This binder only constructs viewmodels, avoid infinite recursion.
(
(bindingContext.ModelType.Namespace.StartsWith("OUR.SOLUTION.Web.ViewModels") && bindingContext.ModelType.IsClass)
||
(bindingContext.ModelType.IsInterface)
)
)
{
var serviceProvider = bindingContext.OperationBindingContext.HttpContext.RequestServices;
var model = serviceProvider.GetRequiredService(bindingContext.ModelType);
// Call model binding recursively to set properties
bindingContext.Model = model;
var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext);
bindingContext.ValidationState[model] = new ValidationStateEntry() { SuppressValidation = true };
return result;
}
return await ModelBindingResult.NoResultAsync;
}
}
You'd obviously want to replace OUR.SOLUTION... with whatever the namespace is for your ViewModels Our registration:
services.AddMvc().AddMvcOptions(options =>
{
options.ModelBinders.Insert(0, new IoCModelBinder());
});
UPDATE 3:
This is the latest iteration of the Model Binder and its Provider that works with ASP.NET Core 2.X:
public class IocModelBinder : ComplexTypeModelBinder
{
public IocModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory) : base(propertyBinders, loggerFactory)
{
}
protected override object CreateModel(ModelBindingContext bindingContext)
{
object model = bindingContext.HttpContext.RequestServices.GetService(bindingContext.ModelType) ?? base.CreateModel(bindingContext);
if (bindingContext.HttpContext.Request.Method == "GET")
bindingContext.ValidationState[model] = new ValidationStateEntry { SuppressValidation = true };
return model;
}
}
public class IocModelBinderProvider : IModelBinderProvider
{
private readonly ILoggerFactory loggerFactory;
public IocModelBinderProvider(ILoggerFactory loggerFactory)
{
this.loggerFactory = loggerFactory;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType) return null;
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
foreach (ModelMetadata property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
return new IocModelBinder(propertyBinders, loggerFactory);
}
}
Then in Startup:
services.AddMvc(options =>
{
// add IoC model binder.
IModelBinderProvider complexBinder = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(ComplexTypeModelBinderProvider));
int complexBinderIndex = options.ModelBinderProviders.IndexOf(complexBinder);
options.ModelBinderProviders.RemoveAt(complexBinderIndex);
options.ModelBinderProviders.Insert(complexBinderIndex, new IocModelBinderProvider(loggerFactory));

This question is tagged with ASP.NET Core, so here's our solution for dotnet core 3.1.
Outline of our solution: TheProject needs to make ICustomerService available to an object created automatically in the request pipeline. Classes that need this are tagged with an interface, IUsesCustomerService. This interface is then checked by the Binder on object creation, and special case is handled.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
namespace TheProject.Infrastructure.DependencyInjection
{
/// <summary>
/// This is a simple pass through class to the binder class.
/// It gathers some information from the context and passes it along.
/// </summary>
public class TheProjectModelBinderProvider : IModelBinderProvider
{
public TheProjectModelBinderProvider()
{
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
ILoggerFactory ilogger;
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// The Binder that gets returned is a <ComplexTypeModelBinder>, but I'm
// not sure what side effects returning early here might cause.
if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType)
{
return null;
}
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
foreach (ModelMetadata property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
ilogger = (ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory));
return new TheProjectModelBinder(propertyBinders, ilogger);
}
}
/// <summary>
/// Custom model binder.
/// Allows interception of endpoint method to adjust object construction
/// (allows automatically setting properties on an object that ASP.NET creates for the endpoint).
/// Here this is used to make sure the <see cref="ICustomerService"/> is set correctly.
/// </summary>
public class TheProjectModelBinder : ComplexTypeModelBinder
{
public TheProjectModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory)
: base(propertyBinders, loggerFactory)
{
}
/// <summary>
/// Method to construct an object. This normally calls the default constructor.
/// This method does not set property values, setting those are handled elsewhere in the pipeline,
/// with the exception of any special properties handled here.
/// </summary>
/// <param name="bindingContext">Context.</param>
/// <returns>Newly created object.</returns>
protected override object CreateModel(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var customerService = (ICustomerService)bindingContext.HttpContext.RequestServices.GetService(typeof(ICustomerService));
bool setcustomerService = false;
object model;
if (typeof(IUsesCustomerService).IsAssignableFrom(bindingContext.ModelType))
{
setcustomerService = true;
}
// I think you can also just call Activator.CreateInstance here.
// The end result is an object that's constructed, but no properties are set yet.
model = base.CreateModel(bindingContext);
if (setcustomerService)
{
((IUsesCustomerService)model).SetcustomerService(customerService);
}
return model;
}
}
}
Then in the startup code, make sure to set AddMvcOptions.
public void ConfigureServices(IServiceCollection services)
{
// ...
// asp.net core 3.1 MVC setup
services.AddControllersWithViews()
.AddApplicationPart(assembly)
.AddRazorRuntimeCompilation()
.AddMvcOptions(options =>
{
IModelBinderProvider complexBinder = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(ComplexTypeModelBinderProvider));
int complexBinderIndex = options.ModelBinderProviders.IndexOf(complexBinder);
options.ModelBinderProviders.RemoveAt(complexBinderIndex);
options.ModelBinderProviders.Insert(complexBinderIndex, new Infrastructure.DependencyInjection.TheProjectModelBinderProvider());
});
}

Related

Can I inject a dependency into a model object created in a post method with .Net Core WebApi?

If in my Startup I have registered my dependency:
services.AddScoped<IService, SomeService>();
Is there a way for this to be injected into my model object at instantiation via a post method?
[HttpPost]
public void Post([FromBody] Model myModel) { ... }
And my model class looks something like this.
public class Model {
...
public Model(IService service) { ... }
...
}
If you wan to inject something into the model you can go with custom model binding. Inside ModelBinder you get access to context with all your registered services and you can call your model's constructor with any arguments that you need.
In general models should not keep reference to services that are using them. So please think it through, if you really want to go this way.
You can create a custom model binder for this purpose. We can use ComplexTypeModelBinder as base for this
public class ServiceModelBinder : ComplexTypeModelBinder
{
public ServiceModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory)
: base(propertyBinders, loggerFactory)
{
}
public ServiceModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory, bool allowValidatingTopLevelNodes) : base(propertyBinders, loggerFactory, allowValidatingTopLevelNodes)
{
}
protected override object CreateModel(ModelBindingContext bindingContext)
{
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
//if type satisfies default binder requirements just use it
if (!modelTypeInfo.IsAbstract && modelTypeInfo.GetConstructor(Type.EmptyTypes) != null)
{
return base.CreateModel(bindingContext);
}
var constructors = modelTypeInfo.GetConstructors();
if (constructors.Length > 1)
{
throw new InvalidOperationException("Model should contain one constructor only");
}
var constructor = constructors.First();
var parameters = constructor.GetParameters();
//require that all parameters are marked with [FromServices]
//this step is completely optional
if (!parameters.Any(p => p.IsDefined(typeof(FromServicesAttribute))))
{
throw new InvalidOperationException("Mark all parameters with [FromServices]");
}
//build arguments from registered services
var arguments = parameters
.Select(p => bindingContext.ActionContext.HttpContext.RequestServices.GetService(p.ParameterType))
.ToArray();
return constructor.Invoke(arguments);
}
}
Then add a model binder provider (copied from ASP.NET Core sources)
public class ServiceModelBinderProvider : 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 ServiceModelBinder(
propertyBinders,
loggerFactory,
allowValidatingTopLevelNodes: true);
}
return null;
}
}
And register it in startup
services
.AddControllersWithViews()
.AddMvcOptions(options =>
{
options.ModelBinderProviders.Insert(0, new ServiceModelBinderProvider());
})

.Net Core Api cannot access original value on model validation failures

I cannot access the original value that didn't pass the model validation. I would suspect AttemptedValue and/or RawValue in ModelStateEntry to contain the original value, however both properties are null.
For clarification, I wrote a minimalistic api, to showcase the issue.
The model to validate:
public class User
{
[EmailAddress]
public string Email { get; set; }
}
The controller:
[ApiController]
[Route("test")]
public class TestController : ControllerBase
{
[HttpPost]
[ValidationFilter()]
public string Test([FromBody] User user)
{
return user.Email;
}
}
The validation filter:
public class ValidationFilterAttribute : ActionFilterAttribute, IOrderedFilter
{
public int Order { get; } = int.MinValue;
override public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
ModelStateEntry entry = context.ModelState.ElementAt(0).Value;
var attemptedVal = entry.AttemptedValue;
var rawVal = entry.RawValue;
context.Result = new OkObjectResult(rawVal);
}
}
}
When I call the test method with this model:
{
"email": "No email here ;)"
}
The ValidationFilterAttribute code is called as expected, however the ModelStateEntry does not contain the original value. Both AttemptedValue and RawValue are null:
Visual Studio debugging screenshot
As far as I know, for model binding, the filter will calls context.ModelState.SetModelValue to set value for RawValue and AttemptedValue.
But the SystemTextJsonInputFormatter doesn't set it to solve this issue, I suggest you could try to build custom extension method and try again.
More details, you could refer to below codes:
Create a new ModelStateJsonInputFormatter class:
public class ModelStateJsonInputFormatter : SystemTextJsonInputFormatter
{
public ModelStateJsonInputFormatter(ILogger<ModelStateJsonInputFormatter> logger, JsonOptions options) :
base(options ,logger)
{
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var result = await base.ReadRequestBodyAsync(context);
foreach (var property in context.ModelType.GetProperties())
{
var propValue = property.GetValue(result.Model, null);
var propAttemptValue = property.GetValue(result.Model, null)?.ToString();
context.ModelState.SetModelValue(property.Name, propValue, propAttemptValue);
}
return result;
}
}
Reigster it in startup.cs:
services.AddControllersWithViews(options => {
var serviceProvider = services.BuildServiceProvider();
var modelStateJsonInputFormatter = new ModelStateJsonInputFormatter(
serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<ModelStateJsonInputFormatter>(),
serviceProvider.GetRequiredService<IOptions<JsonOptions>>().Value);
options.InputFormatters.Insert(0, modelStateJsonInputFormatter);
});
Result:

asp.net core custom attribute to get object from filter

I am having some ActionFilters in my asp.net core mvc application, which validate some input data. For example, the client sends a userId inside the header, the filter loads that user from a repository, and validates, if the user exists, is active, has a license, and so on.
This filter is attached to a Controller method. The Controller method also needs to collect the same user object. Because of performance, I want to pass that user object, collected inside the filter, to the controller, so the controller does not need to load the same user object again. I know there are ways to do so, like mentioned here.
Because of clean code, I wonder if this would be possible, coding an attribute which defines what to retrieve, like the [FromBody] attribute does, for instance.
I could imagine this attribute named [FromFilter("User")], which takes a parameter to specify the key inside the HttpContext.Items
A basic implementation could be something like this:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromFilterAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
/// <inheritdoc />
public BindingSource BindingSource => BindingSource.Custom;
/// <inheritdoc />
public string Name { get; set; }
}
Neither do I know if this would a be a good idea, nor how to implement such a feature. Hopefully someone can me point into the right direction
As far as I know, we couldn't directly pass the object from filter to action.
In my opinion, the best solution is creating a custom model binding and then find the user from the repository and pass the user to the action.
Since the model binding is triggered before the filter, you could get the custom model binding result from the ActionExecutingContext.
Order of execution:
UserModelBinder --> OnActionExecuting --> Index action
More details about to do it, you could refer to below codes:
Custom model binding:
public class UserModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var model = new UserModel()
{
id = 1,
name = "test"
};
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Controller action and OnActionExecuting method:
OnActionExecuting:
public override void OnActionExecuting(ActionExecutingContext context)
{
//ActionArguments["user"] is the parameter name of the action parameter
var user = context.ActionArguments["user"] as UserModel;
// Do something before the action executes.
base.OnActionExecuting(context);
}
Action method:
public async Task<IActionResult> Index([ModelBinder(BinderType = typeof(UserModelBinder))] UserModel user)
{
int i =0;
return View();
}
Result:
Filter onexecuting:
Action parameter:
You can use HttpContext.Items for this and create HttpContextItemsModelBinder which will bind model from HttpContext.Items
public class HttpContextItemsModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var items = bindingContext.HttpContext.Items;
string name = bindingContext.BinderModelName ?? bindingContext.FieldName;
bindingContext.Result = items.TryGetValue(name, out object item)
? ModelBindingResult.Success(item)
: ModelBindingResult.Failed();
return Task.CompletedTask;
}
}
Create and register model binder provider
public static class CustomBindingSources
{
public static BindingSource HttpContextItems { get; } = new BindingSource("HttpContextItems", "HttpContext Items", true, true);
}
public class HttpContextItemsModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.BindingInfo.BindingSource == CustomBindingSources.HttpContextItems)
{
return new HttpContextItemsModelBinder();
}
return null;
}
}
In Startup.cs
services
.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new HttpContextItemsModelBinderProvider());
//...
})
Create an attribute which will set correct BindingSource to use HttpContextItemsModelBinder
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromHttpContextItemsAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
public string Name { get; set; }
public BindingSource BindingSource => CustomBindingSources.HttpContextItems;
public FromHttpContextItemsAttribute(string name)
{
Name = name;
}
public FromHttpContextItemsAttribute() { }
}
Usage:
//in controller
[HttpGet]
[ValidateUserFilter]
public IActionResult TestHttpContextItems([FromHttpContextItems("UserItem")]UserItemModel model)
{
return Ok(model);
}
//your action filter
public class ValidateUserFilterAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//...
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var model = new UserItemModel
{
Id = 45,
Name = "Some user name"
};
context.HttpContext.Items["UserItem"] = model;
}
}
Important note
Pay attention that I save user model to HttpContext.Items during OnAuthorization and not OnActionExecuting because model binding happens before any action filters run, so HttpContext.Items won't contain user and model binding will fail. You might need to adjust filter code to your needs and to make the solution work as expected.
Usage without specifying item name. Parameter name in action method should match key ("userModel") used to store value in HttpContext.Items:
//in controller
[HttpGet]
[ValidateUserFilter]
public IActionResult TestHttpContextItems([FromHttpContextItems]UserItemModel userModel)
{
return Ok(userModel);
}
//action filter
public class ValidateUserFilterAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//...
}
public void OnAuthorization(AuthorizationFilterContext context)
{
//...
context.HttpContext.Items["userModel"] = model;
}
}

FluentValidation with Mediatr and Unity

I'm trying to use FluentValidation in a WebApi project (not asp.net Core).
I have the following code:
public static class UnityConfig
{
public static void RegisterComponents(UnityContainer container)
{
// Register validators
RegisterValidators(container);
// Mediatr
container.RegisterType<IMediator, Mediator>();
container.RegisterTypes(AllClasses.FromAssemblies(true, Assembly.GetExecutingAssembly()), WithMappings.FromAllInterfaces, GetName, GetLifetimeManager);
container.RegisterInstance<SingleInstanceFactory>(t => container.Resolve(t));
container.RegisterInstance<MultiInstanceFactory>(t => container.ResolveAll(t));
// Automapper profiles
var profileTypes = typeof(BaseProfile).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(BaseProfile)));
var config = new MapperConfiguration(cfg => new MapperConfiguration(x =>
{
foreach (var type in profileTypes)
{
var profile = (BaseProfile)Activator.CreateInstance(type);
cfg.AddProfile(profile);
}
}));
container.RegisterInstance<IConfigurationProvider>(config);
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
static LifetimeManager GetLifetimeManager(Type type)
{
return IsNotificationHandler(type) ? new ContainerControlledLifetimeManager() : null;
}
static string GetName(Type type)
{
return IsNotificationHandler(type) ? string.Format("HandlerFor" + type.Name) : string.Empty;
}
private static void RegisterValidators(IUnityContainer container)
{
var validators = AssemblyScanner.FindValidatorsInAssembly(Assembly.GetExecutingAssembly());
validators.ForEach(validator => container.RegisterType(validator.InterfaceType, validator.ValidatorType));
}
}
I'm scanning the assemblies and registrering the validators, of which there's only one right now, it sits here: (don't mind the weird validations, I'm trying to have it fail)
public class Query : IRequest<Result>
{
public Guid? Id { get; set; }
}
public class QueryValidator : AbstractValidator<Query>
{
public QueryValidator()
{
RuleFor(q => q.Id).Empty();
RuleFor(q => q.Id).Equal(Guid.NewGuid());
}
}
My Application_start looks like this:
protected void Application_Start()
{
var container = new UnityContainer();
UnityConfig.RegisterComponents(container);
GlobalConfiguration.Configure(WebApiConfig.Register);
var factory = new UnityValidatorFactory2(GlobalConfiguration.Configuration);
FluentValidationModelValidatorProvider.Configure(GlobalConfiguration.Configuration, x => x.ValidatorFactory = factory);
}
And I have the following validatorFactory:
public class UnityValidatorFactory2 : ValidatorFactoryBase
{
private readonly HttpConfiguration _configuration;
public UnityValidatorFactory2(HttpConfiguration configuration)
{
_configuration = configuration;
}
public override IValidator CreateInstance(Type validatorType)
{
var validator = _configuration.DependencyResolver.GetService(validatorType) as IValidator;
return validator;
}
}
Now; when I call the action on the controller, 'CreateInstance' tries to resolve a validatorType of the type:
IValidator<Guid>
instead of:
IValidator<Query>
and of course finds nothing, this means that my validations does not run.
Does anyone have an ideas as to why this is? it seems faily straight forward, so I have trouble seeing what goes wrong.
After having slept on it, I found the answer myself.
I was posting a Guid to my controller instead of the model I was trying to validate (which only contains a guid)
After posting the right model, it now validates correctly.

An entity object cannot be referenced by multiple instances of IEntityChangeTracker error

I understand there are a lot of duplicates to this question, but I couldn't find one that fits my scenario.
So I am using the ASP.NET MVC 4 + Entity Framework + Ninject using repository pattern (I see many mentions of repository + unit of work pattern? That could be a potential fix to my problem but I don't know how to implement it).
When I try to add a new post, I get "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" error on the following line of code
context.Posts.Add(post);
Here is my full implementation:
Concrete repository
public class EFBlogRepository : IBlogRepository
{
private readonly EFDbContext context;
public EFBlogRepository(EFDbContext dbcontext)
{
context = dbcontext;
}
//add post
public int AddPost(Post post)
{
context.Posts.Add(post);
context.SaveChanges();
return post.PostID;
}
public Category Category(int id)
{
return context.Categories.FirstOrDefault(c => c.CategoryID == id);
}
public Tag Tag(int id)
{
return context.Tags.FirstOrDefault(t => t.TagID == id);
}
}
Interface
public interface IBlogRepository
{
int AddPost(Post post);
Category Category(int id);
Tag Tag(int id);
}
My controller
public class AdminController : Controller
{
private IBlogRepository repository;
public AdminController(IBlogRepository repo)
{
repository = repo;
}
[HttpPost]
public ContentResult AddPost(Post post)
{
string json;
ModelState.Clear();
if (TryValidateModel(post))
{
var id = repository.AddPost(post);
json = JsonConvert.SerializeObject(new
{
id = id,
success = true,
message = "Post added successfully."
});
}
else
{
json = JsonConvert.SerializeObject(new
{
id = 0,
success = false,
message = "Failed to add the post."
});
}
return Content(json, "application/json");
}
}
I don't think any of the above are the root of the problem, I think the problem is in my custom model binder
public class PostModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var post = (Post)base.BindModel(controllerContext, bindingContext);
var repository = DependencyResolver.Current.GetService<IBlogRepository>();
if (post.Category != null)
post.Category = repository.Category(post.Category.CategoryID);
var tags = bindingContext.ValueProvider.GetValue("Tags").AttemptedValue.Split(',');
if (tags.Length > 0)
{
post.Tags = new List<Tag>();
foreach (var tag in tags)
{
post.Tags.Add(repository.Tag(int.Parse(tag.Trim())));
}
}
return post;
}
}
and my global.asax.cs
ModelBinders.Binders.Add(typeof(Post), new PostModelBinder());
This is my Ninject dependency resolver
public class NinjectDependencyResolver: IDependencyResolver
{
private IKernel kernel;
public NinjectDependencyResolver()
{
kernel = new StandardKernel();
AddBindings();
}
public object GetService(Type serviceType)
{
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return kernel.GetAll(serviceType);
}
private void AddBindings()
{
kernel.Bind<IBlogRepository>().To<EFBlogRepository>();
kernel.Bind<IAuthProvider>().To<FormsAuthProvider>();
}
}
You should bind your context in ninject bindings as InRequestScope
kernel.Bind<EFDbContext >().To<EFDbContext >().InRequestScope();
As the error says, one entity cannot be bound to more than one EF context. It seems that you are retrieving the entity from one context and then adding it to a different one. Using the line above you are telling Ninject to use the same context instance to serve all dependencies in the same HTTP request.
Two repositories are being created. One in the controller IBlogRepository repository and the other in the model binder var repository = DependencyResolver.Current.GetService<IBlogRepository>(). Before the fix each repository have a new instance of the context, causing the error. After the fix, both repositories will share the same instance of the context.

Categories

Resources