it-swarm.com.de

MVC-Modellvalidierung aus der Datenbank

Ich habe ein sehr einfaches Modell, das von der Datenbank validiert werden muss

public class UserAddress
{
    public string CityCode {get;set;}
}

CityCode kann Werte haben, die nur in meiner Datenbanktabelle verfügbar sind.

Ich weiß, ich kann so etwas machen.

[HttpPost]
public ActionResult Address(UserAddress model)
{
    var connection = ; // create connection
    var cityRepository = new CityRepository(connection);

    if (!cityRepository.IsValidCityCode(model.CityCode))
    {
        // Added Model error
    }
}

Dies scheint sehr WET zu sein, da ich dieses Modell bei vielen Platzierungen verwenden muss und die gleiche Logik hinzufügen, dass jeder Ort MVC-Architektur nicht richtig zu verwenden scheint.

Also, was ist das beste Muster, um das Modell aus der Datenbank zu validieren? 

ANMERKUNG: Die meisten Überprüfungen bestehen aus der Suche nach einzelnen Feldern aus der Datenbank. Andere Überprüfungen können eine Kombination von Feldern enthalten. Aber im Moment bin ich mit der Validierung der Suche nach einem einzelnen Feld zufrieden, solange dies DRY ist und nicht zu viel Reflexion verwendet, ist es akzeptabel.

KEINE CLIENTENSEITIGE VALIDIERUNG: Für jeden, der in Bezug auf die clientseitige Validierung antwortet, brauche ich keine solche Validierung. Die meisten meiner Validierungen sind serverseitig und ich muss dasselbe tun, bitte Antworten Sie nicht mit clientseitigen Validierungsmethoden.


P.S. Wenn mir jemand einen Hinweis geben kann, wie eine auf Attributen basierende Validierung von Database durchgeführt wird, ist dies äußerst hilfreich.

22
PaRiMaL RaJ

Bitte überprüfen Sie dasEDITaus der Mitte dieser Antwort, um eine ausführlichere und allgemeinere Lösung zu finden.


Im Folgenden finden Sie meine Lösung für eine einfache, auf Attributen basierende Validierung. Ein Attribut erstellen - 

public class Unique : ValidationAttribute
{
    public Type ObjectType { get; private set; }
    public Unique(Type type)
    {
        ObjectType = type;
    }
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (ObjectType == typeof(Email))
        {
            // Here goes the code for creating DbContext, For testing I created List<string>
            // DbContext db = new DbContext();
            var emails = new List<string>();
            emails.Add("[email protected]");
            emails.Add("[email protected]");

            var email = emails.FirstOrDefault(u => u.Contains(((Email)value).EmailId));

            if (String.IsNullOrEmpty(email))
                return ValidationResult.Success;
            else
                return new ValidationResult("Mail already exists");
        }

        return new ValidationResult("Generic Validation Fail");
    }
}

Ich habe ein einfaches Modell zum Testen erstellt - 

public class Person
{
    [Required]
    [Unique(typeof(Email))]
    public Email PersonEmail { get; set; }
    [Required]
    public GenderType Gender { get; set; }
}

public class Email
{
    public string EmailId { get; set; }
}

Dann habe ich folgende Ansicht erstellt - 

@model WebApplication1.Controllers.Person
@using WebApplication1.Controllers;

<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

@using (Html.BeginForm("CreatePersonPost", "Sale"))
{
    @Html.EditorFor(m => m.PersonEmail)

    @Html.RadioButtonFor(m => m.Gender, GenderType.Male) @GenderType.Male.ToString()
    @Html.RadioButtonFor(m => m.Gender, GenderType.Female) @GenderType.Female.ToString()
    @Html.ValidationMessageFor(m => m.Gender)

    <input type="submit" value="click" />
}

Wenn ich nun dieselbe E-Mail - [email protected] eingebe und auf die Schaltfläche Senden klicke, kann ich Fehler in meiner POST-Aktion erhalten, wie unten gezeigt. 

enter image description here


EDITHier geht eine allgemeinere und detailliertere Antwort.


IValidatorCommand erstellen 

public interface IValidatorCommand
{
    object Input { get; set; }
    CustomValidationResult Execute();
}

public class CustomValidationResult
{
    public bool IsValid { get; set; }
    public string ErrorMessage { get; set; }
}

Nehmen wir an, wir haben unsere Repository und UnitOfWork folgendermaßen definiert:

public interface IRepository<TEntity> where TEntity : class
{
    List<TEntity> GetAll();
    TEntity FindById(object id);
    TEntity FindByName(object name);
}

public interface IUnitOfWork
{
    void Dispose();
    void Save();
    IRepository<TEntity> Repository<TEntity>() where TEntity : class;
} 

Jetzt können wir unseren eigenen Validator Commands erstellen. 

public interface IUniqueEmailCommand : IValidatorCommand { }

public interface IEmailFormatCommand : IValidatorCommand { }

public class UniqueEmail : IUniqueEmailCommand
{
    private readonly IUnitOfWork _unitOfWork;
    public UniqueEmail(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
    public object Input { get; set; }

    public CustomValidationResult Execute()
    {
        // Access Repository from Unit Of work here and perform your validation based on Input
        return new CustomValidationResult { IsValid = false, ErrorMessage = "Email not unique" };
    }
}

public class EmailFormat : IEmailFormatCommand
{
    private readonly IUnitOfWork _unitOfWork;
    public EmailFormat(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
    public object Input { get; set; }

    public CustomValidationResult Execute()
    {
        // Access Repository from Unit Of work here and perform your validation based on Input
        return new CustomValidationResult { IsValid = false, ErrorMessage = "Email format not matched" };
    }
}

Erstellen Sie unseren Validator Factory, der uns einen bestimmten Befehl gibt, der auf Typ basiert.

public interface IValidatorFactory
{
    Dictionary<Type,IValidatorCommand> Commands { get; }
}

public class ValidatorFactory : IValidatorFactory
{
    private static Dictionary<Type,IValidatorCommand> _commands = new Dictionary<Type, IValidatorCommand>();

    public ValidatorFactory() { }        

    public Dictionary<Type, IValidatorCommand> Commands
    {
        get
        {
            return _commands;
        }
    }

    private static void LoadCommand()
    {
        // Here we need to use little Dependency Injection principles and
        // populate our implementations from a XML File dynamically
        // at runtime. For demo, I am passing null in place of UnitOfWork
        _commands.Add(typeof(IUniqueEmailCommand), new UniqueEmail(null));
        _commands.Add(typeof(IEmailFormatCommand), new EmailFormat(null));
    }

    public static IValidatorCommand GetCommand(Type validatetype)
    {
        if (_commands.Count == 0)
            LoadCommand();            

        var command = _commands.FirstOrDefault(p => p.Key == validatetype);
        return command.Value ?? null;
    }
}

Und das renovierte Validierungsattribut - 

public class MyValidateAttribute : ValidationAttribute
{
    public Type ValidateType { get; private set; }
    private IValidatorCommand _command;
    public MyValidateAttribute(Type type)
    {
        ValidateType = type;            
    }
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        _command = ValidatorFactory.GetCommand(ValidateType);
        _command.Input = value;
        var result = _command.Execute();

        if (result.IsValid)
            return ValidationResult.Success;
        else
            return new ValidationResult(result.ErrorMessage);
    }
}

Schließlich können wir unser Attribut wie folgt verwenden: 

public class Person
{
    [Required]
    [MyValidate(typeof(IUniqueEmailCommand))]
    public string Email { get; set; }
    [Required]
    public GenderType Gender { get; set; }
}

Ausgabe wie folgt - 

enter image description here


EDITDetaillierte Erklärung, um diese Lösung allgemeiner zu machen.


Nehmen wir an, ich habe eine Eigenschaft Email, in der ich folgende Überprüfungen durchführen muss - 

  1. Format
  2. Länge
  3. Einzigartig

In diesem Fall können wir IEmailCommand erstellen, das von IValidatorCommand geerbt wurde. Dann erben Sie IEmailFormatCommand, IEmailLengthCommand und IEmailUniqueCommand von IEmailCommand.

Unsere ValidatorFactory enthält den Pool aller drei Befehlsimplementierungen in Dictionary<Type, IValidatorCommand> Commands.

Anstatt unsere Email-Eigenschaft mit drei Befehlen zu dekorieren, können wir sie mit IEmailCommand verzieren.

In diesem Fall muss unsere ValidatorFactory.GetCommand()-Methode geändert werden. Anstatt jedes Mal einen Befehl zurückzugeben, sollten alle übereinstimmenden Befehle für einen bestimmten Typ zurückgegeben werden. Im Grunde sollte seine Signatur List<IValidatorCommand> GetCommand(Type validatetype) sein.

Da wir nun alle mit einer Eigenschaft verknüpften Befehle abrufen können, können wir die Befehle in einer Schleife ausführen und die Validierungsergebnisse in unserer ValidatorAttribute abrufen.

27
ramiramilu

Ich hätte RemoteValidation verwendet. Dies ist am einfachsten für Szenarien wie Validierungen gegen Datenbanken.

Verschönern Sie Ihr Eigentum mit Remote-Attribut - 

[Remote("IsCityCodeValid","controller")]
public string CityCode { get; set; }

Jetzt ist "IsCityCodeValid" eine Aktionsmethode, die JsonResult zurückgibt und den zu validierenden Eigenschaftsnamen als Parameter verwendet, und "Controller" ist der Name des Controllers, in den Ihre Methode eingefügt wird. Stellen Sie sicher, dass der Parametername mit dem Eigenschaftennamen übereinstimmt.

Führen Sie Ihre Überprüfungen in der Methode durch, und geben Sie für den Fall, dass sie gültig ist, json true und false zurück. Einfach und schnell!

    public JsonResult IsCityCodeValid(string CityCode)
    {
        //Do you DB validations here
        if (!cityRepository.IsValidCityCode(cityCode))
        {
            //Invalid
           return Json(false, JsonRequestBehavior.AllowGet);
        }
        else
        {            
            //Valid
            return Json(true, JsonRequestBehavior.AllowGet);
        }
    }

Und du bist fertig !! Das MVC-Framework kümmert sich um den Rest.

Und natürlich können Sie abhängig von Ihren Anforderungen verschiedene Überladungen von Remote-Attributen verwenden. Sie können auch andere abhängige Eigenschaften einschließen, eine benutzerdefinierte Fehlermeldung definieren usw. Sie können sogar die Modellklasse als Parameter an die Json-Ergebnisaktionsmethode übergeben MSDN Ref.

2
Yogi

Ich habe das in der Vergangenheit gemacht und es hat für mich funktioniert:

public interface IValidation
{
    void AddError(string key, string errorMessage);
    bool IsValid { get; }
}

public class MVCValidation : IValidation
{
    private ModelStateDictionary _modelStateDictionary;

    public MVCValidation(ModelStateDictionary modelStateDictionary)
    {
        _modelStateDictionary = modelStateDictionary;
    }
    public void AddError(string key, string errorMessage)
    {
        _modelStateDictionary.AddModelError(key, errorMessage);
    }
    public bool IsValid
    {
        get
        {
            return _modelStateDictionary.IsValid;
        }
    }
}

Auf Ihrer Business-Layer-Ebene sollten Sie Folgendes tun:

public class UserBLL
{
    private IValidation _validator;
    private CityRepository _cityRepository;
    public class UserBLL(IValidation validator, CityRepository cityRep)
    {
        _validator = validator;
        _cityRepository = cityRep;
    }
    //other stuff...
    public bool IsCityCodeValid(CityCode cityCode)
    {
        if (!cityRepository.IsValidCityCode(model.CityCode))
        {
            _validator.AddError("Error", "Message.");
        }
        return _validator.IsValid;
    }
}

Nun können Sie auf Controller-Ebene Ihren bevorzugten IoC registrieren und den this.ModelState in Ihre UserBLL-Instanz eintragen:

public class MyController
{
    private UserBLL _userBll;
    public MyController(UserBLL userBll)
    {
        _userBll = userBll;
    }
    [HttpPost]
    public ActionResult Address(UserAddress model)
    {
        if(userBll.IsCityCodeValid(model.CityCode))
        {
            //do whatever
        }
        return View();//modelState already has errors in it so it will display in the view
    }
}
1
SOfanatic

Ich denke, Sie sollten eine benutzerdefinierte Validierung verwenden

public class UserAddress
{
    [CustomValidation(typeof(UserAddress), "ValidateCityCode")]
    public string CityCode {get;set;}
}
public static ValidationResult ValidateCityCode(string pNewName, ValidationContext pValidationContext)
{
    bool IsNotValid = true // should implement here the database validation logic
    if (IsNotValid)
        return new ValidationResult("CityCode not recognized", new List<string> { "CityCode" });
    return ValidationResult.Success;
}
1
liviu mamelluc

Ich würde eine sehr einfache Lösung für die serverseitige Validierung von Feldern anbieten, für die nur Werte in der Datenbank vorhanden sind. Zunächst benötigen wir ein Validierungsattribut:

public class ExistAttribute : ValidationAttribute
{
    //we can inject another error message or use one from resources
    //aint doing it here to keep it simple
    private const string DefaultErrorMessage = "The value has invalid value";

    //use it for validation purpose
    private readonly ExistRepository _repository;

    private readonly string _tableName;
    private readonly string _field;

    /// <summary>
    /// constructor
    /// </summary>
    /// <param name="tableName">Lookup table</param>
    /// <param name="field">Lookup field</param>
    public ExistAttribute(string tableName, string field) : this(tableName, field, DependencyResolver.Current.GetService<ExistRepository>())
    {
    }

    /// <summary>
    /// same thing
    /// </summary>
    /// <param name="tableName"></param>
    /// <param name="field"></param>
    /// <param name="repository">but we also inject validation repository here</param>
    public ExistAttribute(string tableName, string field, ExistRepository repository) : base(DefaultErrorMessage)
    {
        _tableName = tableName;
        _field = field;
        _repository = repository;

    }

    /// <summary>
    /// checking for existing object
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public override bool IsValid(object value)
    {
        return _repository.Exists(_tableName, _field, value);
    }
}

Das Validierungs-Repository selbst sieht ebenfalls ziemlich einfach aus:

public class ExistRepository : Repository
{
    public ExistRepository(string connectionString) : base(connectionString)
    {
    }

    public bool Exists(string tableName, string fieldName, object value)
    {
        //just check if value exists
        var query = string.Format("SELECT TOP 1 1 FROM {0} l WHERE {1} = @value", tableName, fieldName);
        var parameters = new DynamicParameters();
        parameters.Add("@value", value);

        //i use dapper here, and "GetConnection" is inherited from base repository
        var result = GetConnection(c => c.ExecuteScalar<int>(query, parameters, commandType: CommandType.Text)) > 0;
        return result;
    }
}

Hier ist Basis Repository Klasse:

public class Repository
{
    private readonly string _connectionString;

    public Repository(string connectionString)
    {
        _connectionString = connectionString;
    }

    protected T GetConnection<T>(Func<IDbConnection, T> getData)
    {
        var connectionString = _connectionString;
        using (var connection = new SqlConnection(connectionString))
        {
            connection.Open();
            return getData(connection);
        }
    }
}

Jetzt müssen Sie Ihre Felder mit ExistAttribute markieren und dabei Tabellennamen und Feldnamen für die Suche angeben:

public class UserAddress
{
    [Exist("dbo.Cities", "city_id")]
    public int CityCode { get; set; }

    [Exist("dbo.Countries", "country_id")]
    public int CountryCode { get; set; }
}

Controller-Aktion:

    [HttpPost]
    public ActionResult UserAddress(UserAddress model)
    {
        if (ModelState.IsValid) //you'll get false here if CityCode or ContryCode don't exist in Db
        {
            //do stuff
        }
        return View("UserAddress", model);
    }
1
Sergio

Hier ist mein Versuch - 

Um zu ermitteln, welche Validierung wir für die Eigenschaft durchführen müssen, können wir ein Enum als Bezeichner haben.

public enum ValidationType
{
    City,
    //Add more for different validations
}

Definieren Sie als Nächstes unser benutzerdefiniertes Validierungsattribut wie folgt: Der Aufzählungstyp ist als Attributparameter deklariert. 

public class ValidateLookupAttribute : ValidationAttribute
{
    //Use this to identify what validation needs to be performed
    public ValidationType ValidationType { get; private set; }
    public ValidateLookupAttribute(ValidationType validationType)
    {
        ValidationType = validationType;
    }
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        //Use the validation factory to get the validator associated
        //with the validator type
        ValidatorFactory validatorFactory = new ValidatorFactory();
        var Validator = validatorFactory.GetValidator(ValidationType);

        //Execute the validator
        bool isValid = Validator.Validate(value);

        //Validation is successful, return ValidationResult.Succes
        if (isValid)
            return ValidationResult.Success;

        else //Return validation error
            return new ValidationResult(Validator.ErrorMessage);
    }
}

Wenn Sie weitere Überprüfungen hinzufügen möchten, muss die Attributklasse nicht geändert werden.

Und jetzt dekorieren Sie einfach Ihre Immobilie mit diesem Attribut

    [ValidateLookup(ValidationType.City)]
    public int CityId { get; set; }

Hier sind die anderen Verbindungsteile der Lösung:

Validator-Schnittstelle. Alle Prüfer implementieren diese Schnittstelle. Es gibt nur eine Methode zum Überprüfen des eingehenden Objekts und eine spezifische Fehlernachricht, wenn die Überprüfung fehlschlägt.

public interface IValidator
{
    bool Validate(object value);

    string ErrorMessage { get; set; }
}

CityValidator-Klasse (Natürlich können Sie diese Klasse durch Verwendung von DI usw. verbessern, es dient nur zu Referenzzwecken).

 public class CityValidator : IValidator
{
    public bool Validate(object value)
    {
        //Validate your city here
        var connection = ; // create connection
        var cityRepository = new CityRepository(connection);

        if (!cityRepository.IsValidCityCode((int)value))
        {
            // Added Model error
            this.ErrorMessage = "City already exists";
        }
        return true;
    }

    public ErrorMessage { get; set; }
}

Validator Factory, dies ist für die Bereitstellung des korrekten Validators verantwortlich, der dem Validierungstyp zugeordnet ist

public class ValidatorFactory
{
    private Dictionary<ValidationType, IValidator> validators = new Dictionary<ValidationType, IValidator>();

    public ValidatorFactory()
    {
        validators.Add(ValidationType.City, new CityValidator());
    }
    public IValidator GetValidator(ValidationType validationType)
    {
        return this.validators[validationType];
    }
}

Abhängig von der Gestaltung Ihres Systems und den Konventionen kann die tatsächliche Implementierung geringfügig variieren. Auf hohem Niveau sollte sie jedoch die Probleme gut angehen. hoffentlich hilft das

0
Yogi
  • Hi .. Ich denke, das ist nützlich für Ihre Frage. 
  • Ich benutze diese Methode für
  • aufruf einer einzelnen Funktion an verschiedenen Orten. Ich werde im Folgenden ausführlich erklären.

Im Modell:

public class UserAddress
{
    public string CityCode {get;set;}
}

In Controller: Erstellen Sie zunächst eine einzelne Funktion für die Validierung für eine einzelne Verbindung

 public dynamic GetCity(string cityCode)
        {
           var connection = ; // create connection
           var cityRepository = new CityRepository(connection);

           if (!cityRepository.IsValidCityCode(model.CityCode))
           {
               // Added Model error
           }
           return(error);
        }

Funktionsaufruf von einem anderen Controller aus:

var error = controllername.GetCity(citycode);

Andere Methode für viele Verbindungen

 public dynamic GetCity(string cityCode,string connection)
            {

               var cityRepository = new CityRepository(connection);

               if (!cityRepository.IsValidCityCode(model.CityCode))
               {
                   // Added Model error
               }
               return(error);
            }

Funktionsaufruf von einem anderen Controller aus:

var error = controllername.GetCity(citycode,connection);      
0

wenn Sie wirklich von der Datenbank aus validieren möchten, können Sie hier einige Techniken befolgen. __ 

public int StudentID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
public string FirstName { get; set; }
public Nullable<System.DateTime> EnrollmentDate { get; set; }
[StringLength(50)]
 public string MiddleName { get; set; }

hier ist die Stringlänge definiert, d. h. 50 und datetime kann null sein usw EF-Datenbank zuerst mit ASP.NET-MVC: Datenvalidierung verbessern

0
rohit poudel