Creating WebAPI models using dependency injection - c#

In my controller I have the action:
public void Post(NewCustomerModel model)
{
model.Save();
}
and the model:
public class NewCustomerModel
{
private readonly CustomerRepository repository;
public NewCustomerModel(CustomerRepository repository)
{
this.repository = repository;
}
public string Name { get; set; }
public void Save()
{
var customer = new Customer(Name);
repository.Save(customer);
}
}
I want this model to be instantiated using the IoC-container that I have configured and the Name property should be set by reading the values from the request.
I managed to get the IoC-container to instantiate the model by creating a custom IModelBinder:
public class MyCustomModelBinder : IModelBinder
{
private readonly IComponentContext componentContext;
public EntityModelBinder(IComponentContext componentContext)
{
this.componentContext = componentContext;
this.modelType = modelType;
}
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
object model = componentContext.Resolve(bindingContext.ModelType);
bindingContext.Model = model;
return true;
}
}
This works fine, the repository is being injected into the model. After this I expect the WebAPI framework to take over and set the public properties, but it doesn't: the Name property is null. If I'm not using this model binder and use the default WebAPI framework, the Name property is set, so there is nothing wrong with the request-to-model mapping.
How can I make this work?

Related

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;
}
}

How should I get the Request in ApiController constructor?

Task
I have a DataMapper class that I use to map data into custom representations for my web api's mobile client.
public class DataMapper
{
public static string Role { get; set; }
public static RoleReturnModel Create(IdentityRole appRole)
{
return new RoleReturnModel
{
Id = appRole.Id,
Name = appRole.Name
};
}
public static CountryReturnModel Create(Country country)
{
return new CountryReturnModel
{
Id = country.Id,
Name = country.Name,
CityList = country.Cities.Select(city => DataMapper.Create(city))
};
}
public static CityReturnModel Create(City city)
{
return new CityReturnModel
{
Id = city.Id,
Name = city.Name,
};
}
}
The first property as you can see is called Role. I need to populate that with whichever role is accessing my web method. This is so because at times I need conditional mapping to return role specific data representations to the client.
Problem
I thought the best place to do DataMapper.Role = CurrentRole would be in the constructor of my ApiController
public class BaseApiController : ApiController
{
private ModelFactory _modelFactory;
private ApplicationUserManager _AppUserManager = null;
private ApplicationRoleManager _AppRoleManager = null;
protected BaseApiController()
{
//Request is null here
DataMapper.Role = Request.GetOwinContext().GetUserManager<ApplicationRoleManager>().FindById(User.Identity.GetUserId()).Name;
}
This however doesn't work . The Request object is null in the constructor. It only gets filled in my actual web method
public class UsersController : BaseApiController
{
IUserRepository UserRepository;
public UsersController() // will use ninject for constructor injection
{
UserRepository = new UserRepository();
}
[Route("profile")]
public IHttpActionResult GetUser()
{
//Request is available here
}
I am a webapi noobie. Need pointers to this problem.
The request is not available as yet in the constructor. You can only access it in an action/method after the controller has already been initialized.
public class BaseApiController : ApiController {
private ModelFactory _modelFactory;
private ApplicationUserManager _AppUserManager = null;
private ApplicationRoleManager _AppRoleManager = null;
protected string GetRole() {
return Request.GetOwinContext()
.GetUserManager<ApplicationRoleManager>()
.FindById(User.Identity.GetUserId()).Name;
}
And accessed
public class UsersController : BaseApiController {
IUserRepository UserRepository;
public UsersController() // will use ninject for constructor injection
{
UserRepository = new UserRepository();
}
[Route("profile")]
public IHttpActionResult GetUser()
{
//Request is available here
var role = GetRole();
}
Or consider extracting that out into an extension method so that it can be reused
var role = this.GetUserRole();
Where
public static string GetUserRole(this ApiController controller) {
var request = controller.Request;
var user = controller.User
return request.GetOwinContext()
.GetUserManager<ApplicationRoleManager>()
.FindById(user.Identity.GetUserId()).Name;
}

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.

How to get controller's ModelState in service for validation

I am exactly at the situation where this person (Controller ModelState with ModelStateWrappper) is except that I am using Castle Windsor DI. I am trying to validate my model's business logic side in my service before I save the model pass it to the data store. if the validation fails I want to get the model state errors and display in my view.
(I am following this article as a guide to implement validation from service: http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validating-with-a-service-layer-cs)
Below is my sample code,
//My controller code
public class TimeEntryController : TempoBaseController
{
public TimeEntryController(TimeService service, UserService userService)
{
_service = service;
_userService = userService;
}
[Authorize(Roles = "Worker")]
public ActionResult Index()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(EntryLogDto entry)
{
if(_service.AddEntryLog(entry))
{
return ViewSuccess();
}else
{
return ViewFailue();
}
}
}
//My Service class
public class TimeService : GenericService
{
public TimeService(IRepository repository, IValidationDictionary validationDictionary, UserManager<ApplicationUser> userManager)
: base(repository, validationDictionary, userManager)
{
}
public bool AddEntryLog(EntryLogDto log)
{
if (!ValidateEntryLog(log))
{
//return false;
}
else
{
//insert entry into database and return true
}
}
protected bool ValidateEntryLog(EntryLogDto log)
{
//Check if the entry overlaps with any other entries
bool res = _repository.Find<EntryLogDto>(//my linq logic);
if (res)
{
_validationDictionary.IsValid = true;
}else
{
_validatonDictionary.AddError("Entry", "Entry Overlaps.");
_validationDictionary.IsValid = false;
}
return _validationDictionary.IsValid;
}
}
//Validation class
public class TempoValidation : IValidationDictionary
{
private ModelStateDictionary _modelState;
public TempoValidation(ModelStateDictionary modelState) // Issue is how am I gona give it this as the ModelStateDictiona ry is controller specific
{
_modelState = modelState;
}
public void AddError(string key, string error)
{
_modelState.AddModelError(key, error);
}
public bool IsValid
{
get { return _modelState.IsValid; }
}
}
//Global.asax castle compnonent registration method
container
.Register(Component
.For<Tempo.Model.Configuration.TempoDbContext>()
.LifestylePerWebRequest()
.DependsOn(new { connectionString }))
.Register(Component
.For<DbRepositories.ITempoDataContextFactory>()
.AsFactory())
.Register(Component
.For<IRepository>()
.ImplementedBy<Tempo.Repositories.EntityFrameworkRepository.Repository>())
.Register(Component
.For<TimeService>().LifestyleTransient())
I am injecting IValidationDictionary in my service class where I set the model state depending on the validation result. Is there a way I can pass in the model state of the controller when I use it? I don't know how to approach this, I have many controllers and I don't know how I will/when will I pass the respective controller's model state (I would like to do that by DI if its possible )... I don't know if castle can create a separate instance of TempoValidation class for each controller??
I know that this is impossible to do this by default, but you can use Fluent Validation to achieve this.
Example:
ViewModel
[Validator(typeof(VmSysTestValidator))]
public class VmSysTestModel
{
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
Fluent validation implementation :
public class VmSysTestValidator : AbstractValidator<VmSysTestModel>
{
public VmSysTestValidator()
{
RuleFor(x => x.FirstName).NotNull().WithMessage("First name is required");
RuleFor(x => x.LastName).NotNull().WithMessage("Last Name is required");
}
}
Controller or business logic side :
[HttpPost]
public ActionResult TestPost(VmSysTestModel obj)
{
//Start Validation From start to end you can call this code everywhere you want , this will work like server side valdiatin
//Instead of ModelState.IsValid you will call your fluent validator
var testValidator = new VmSysTestValidator();
var validationResult = testValidator.Validate(obj);
if (validationResult.IsValid)
{
}
else
{
}
//End valdiation
}

Bind Controller Context with Ninject

I have a service class that is required to create a pdf, which needs the ControllerContext injected, in order to render a the html => pdf.
This service is called through a middle tier, that has no reference to the web/mvc project. which is fine, since ninject does all the required injection of services etc.
This is what the Service looks like (simplified for these purposes)
public class PdfCreatorService : AbstractUrlBasedPdfCreatorService
{
[Inject]
public ControllerContext ControllerContext { get; set; }
public override byte[] CreateReport(int reportId)
{
var result = new PdfController().CreateReport(reportId);
using (var it = new ResponseCapture(ControllerContext.RequestContext))
{
result.ExecuteResult(ControllerContext);
return it.ReadAllContents();
}
}
}
Here is the simplified call stack:
Web.HomeController.SendEmailWithPdf(int id) calls:
MiddleTier.BusinessLogic.SendEmailWithPdf(int id) calls:
Web.Services.PdfCreatorService.CreateReport(int id)
Ninject is reaching PdfCreatorService, no problem, but: I need to somehow forward the ControllerContext from the HomeController (through the middle tier) to the PdfCreatorService.
Whilst the middle tier cannot have any reference to the ControllerContext.
I've looked at Providers, Factories, Resolver, etc.
But couldn't find the right solution.
Any help is appreciated!
Cheers
OK. I've come up with a solution that I'm happy with.
Here is how I'm retrieving the ControllerContext:
public class PdfCreatorService
{
[Inject]
public ControllerContextProvider contextProvider { get; set; }
[Inject]
public PdfController pdfController { get; set; }
public override byte[] CreateReport(int reportId)
{
var context = contextProvider.GetControllerContext();
using (var stream = new ResponseCapture(context.RequestContext))
{
// Setup Controller
var routeData = new RouteData(context.RouteData.Route, context.RouteData.RouteHandler);
routeData.Values.Add("action", "CreateReport");
routeData.Values.Add("controller", "Pdf");
routeData.Values.Add("id", reportId);
var pdfContext = new ControllerContext(context.HttpContext, routeData, pdfController);
// Execute Controller
var result = pdfController.CreateReport(reportId);
result.ExecuteResult(pdfContext);
return stream.ReadAllContents();
}
}
}
This is how to set the context in the original controller:
public abstract class HomeController : Controller
{
[Inject]
public ControllerContextProvider ControllerContextProvider { get; set; }
protected override IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state)
{
ControllerContextProvider.GetControllerContext = () => ControllerContext;
return base.BeginExecute(requestContext, callback, state);
}
}
This is what the provider class looks like:
public class ControllerContextProvider
{
public Func<ControllerContext> GetControllerContext { get; set; }
}
This is how I'm binding it:
public class PortalNinjectModule : NinjectModule
{
public override void Load()
{
Bind<ControllerContextProvider>().ToSelf().InRequestScope();
}
}
Still interested to see whether anyone has a more elegant solution.
You could create a Custom Controller factory.
public class MyControllerFactory : DefaultControllerFactory
{
public override IController CreateController(RequestContext requestContext, string controllerName)
{
var controller = base.CreateController(requestContext, controllerName);
HttpContext.Current.Request["controllerInstance"] = controller;
return controller;
}
}
You need to register this controller in your global.asax:
ControllerBuilder.Current.SetControllerFactory(typeof(MyControllerFactory));
After that you can configure Ninject to resolve the ControllerContext from the request:
kernel.Bind<ControllerContext>().ToMethod(ctx => ((Controller)HttpContext.Current.Request["controllerInstance"]).ControllerContext);

Categories

Resources