0

I am learning Dependency Injection. How can I make my "UserCreate" model to use the custom constructor I set when it is being used as a parameter on controller action? I want to pass the UserContext to my UserCreate model.

My action:

[HttpPost]
    public JsonResult Post(UserCreate model)
    {
        var user = _repository.GetByUserName(model.Email);
        if (user != null)
        {
            this.ModelState.AddModelError(nameof(model.Email), "Email already registered!");
        }
        else
        {
            if (ModelState.IsValid)
            {
                var userModel = _mapper.Map<User>(model);
                _repository.Add(userModel);
                _repository.SaveChanges();
                return Json(new { success = "true" });
            }
        }
        return Json(new { success = "false", errors = this.ModelErrors(this.ModelState) });
    }

My Model

public class UserCreate : BaseModel
{
    private readonly IUserRepo repo;

    public UserCreate(UserContext context) : base(context){
        repo = new UserRepository(context);
    }

    public UserCreate():base() { }


    [Required]
    [MaxLength(100)]
    public string Email { get; set; }

    [Required]
    [MaxLength(30)]
    public string Password { get; set; }

    [Required]
    [MaxLength(30)]
    public string FirstName { get; set; }

    [Required]
    [MaxLength(30)]
    public string MiddleName { get; set; }

    [Required]
    [MaxLength(30)]
    public string LastName { get; set; }

    [Required]
    public int Age { get; set; }

    [Required]
    public DateTime Birthday { get; set; }

    [Required]
    [MaxLength(250)]
    public string Adddress { get; set; }

    public DateTime Created { get { return DateTime.Now; } }


}

I've set it on startup:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<UserContext>(opt => opt.UseSqlServer
            (Configuration.GetConnectionString("Dev")));
        services.AddControllers();
        services.AddScoped<IUserRepo, UserRepository>();
        services.AddScoped<ICardRepo, CardRepository>();
        services.AddScoped<IUserContext, UserContext>();
        services.AddScoped<TransactCreate, TransactCreate>();

        services.AddSingleton<UserCreate>(x =>
            new UserCreate(x.GetRequiredService<UserContext>()));

I have set the Addsingleton on startup however when i test my API, public UserCreate():base() { } constructor is called instead of the constructor with UserContext parameter. I am using netcore 3.1

the reason why I want do this is I to move my validations to model and i need to use UserContext from there.

Thanks!

2 Answers2

1

I understand what you are asking, but please understand that your approach to this problem is very flawed. Your view model should absolutely know nothing about your repository.

In MVC, the Controller is responsible for handling HTTP requests (as well as model validation), and delegating actions to the rest of the application. The Model (UserCreate), should be a simple poco that only exists to transfer data from the client back to your controller. The controller should then delegate responsibility to the repository for handling the data.

Your controller should, instead, accept the repository via DI, and then send the UserCreate model through, after validating it. And your model, UserCreate, should 100% have a parameterless constructor, as the ModelBinder is going to build it up from the request.

Jonesopolis
  • 25,034
  • 12
  • 68
  • 112
  • Hi Jones, Thanks for your reply. My controller currently accepts repository via DI and to validate the data. however what I want to achieve is if I have multiple properties that i need to validate from the database, i dont want to write them all in my controller action. Can you recommend the right way to handle custom validations? Thank You! – Nhey Perez Sep 23 '20 at 04:04
0

however what I want to achieve is if I have multiple properties that i need to validate from the database, i dont want to write them all in my controller action. Can you recommend the right way to handle custom validations?

According to your code and the previous discuss, I suppose you want to valid whether the entered value is exist in the database, if the value exist, display the error message, such as "Email already registered". If that is the case, it is better to use the [Remote] attribute:

Code as below:

[Remote(action: "VerifyEmail", controller: "Users")]
public string Email { get; set; }

and

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyEmail(string email)
{
    if (!_userService.VerifyEmail(email))
    {
        return Json($"Email {email} is already in use.");
    }

    return Json(true);
}

Besides, if you want to create custom validation, you can check this thread, then, in the Custom validation IsValid method, you could get the current dbcontext and check whether the entered data is valid or not. Code as below:

code in the model:

[Required(ErrorMessage ="Country is Required")]
public string Country { get; set; }
[RequiredIfHasState("Country", ErrorMessage ="State is Required")]
public string State { get; set; }

code in the custom valiation:

public class RequiredIfHasStateAttribute : ValidationAttribute
{
    private readonly string _comparisonProperty;

    public RequiredIfHasStateAttribute(string comparisonProperty)
    {
        _comparisonProperty = comparisonProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        ErrorMessage = ErrorMessageString;

        //get entered state value
        var stateValue = (string)value;
        var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
        if (property == null)
            throw new ArgumentException("Property with this name not found");
        //get the country value
        var countryValue = (string)property.GetValue(validationContext.ObjectInstance);
        //get the current dbcontext
        var _context = (MvcMovieContext)validationContext.GetService(typeof(MvcMovieContext));
        //query the database and check whether the country has state.
        if (_context.Countries.Where(c => c.CountryCode == countryValue).Select(c => c).FirstOrDefault().HasState)
        {
            if(stateValue == null)
            { 
                //if country has state and the state is null. return error message
                return new ValidationResult(ErrorMessage);
            }
            else
            {
                //if country has state and the state is not found.
                if(!_context.Countries.Where(c => c.CountryCode == countryValue).Any(c => c.States.Any(e => e.StateName == stateValue)))
                {
                    return new ValidationResult("State not found");
                }
            }
        }   
        return ValidationResult.Success;
    }
}
Zhi Lv
  • 18,845
  • 1
  • 19
  • 30