i am working on an asp.net mvc-5 web application with entity framework-6.and i have mapped my DB tables using entity framework which generates a .edmx file containing a DBContext class. and currently i have two controller classes one for managing servers and the other for managing vms. When add/edit Server or VMs I want to check if their ip & mac addresses already exists. currently i am going these checks on the action method itself as follow:-
public class Server : Controller{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ServerJoin sj)
{
bool ITipunique = repository.ISITIPUnique(vmj.NetworkInfo2.FirstOrDefault().IPADDRESS);
bool ITmacunique = repository.ISITMACUnique(vmj.NetworkInfo2.FirstOrDefault().MACADDRESS);
bool Tipunique = repository.ISTIPUnique(vmj.NetworkInfo2.FirstOrDefault().IPADDRESS);
bool Tmacunique = repository.ISTMACUnique(vmj.NetworkInfo2.FirstOrDefault().MACADDRESS);
try
{
if ((sj.IsIPUnique == true) && (!ITipunique || !Tipunique))
{
ModelState.AddModelError("NetworkInfo2[0].IPAddress", "Error occurred. The Same IP is already assigned.");
}
if ((sj.IsMACUnique == true) && (!ITmacunique || !Tmacunique))
{
ModelState.AddModelError("NetworkInfo2[0].MACAddress", "Error occurred. The Same MAC Address is already assigned.");
}
&
public class VM : Controlelr {
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(VMJoin vmj)
{
bool ITipunique = repository.ISITIPUnique(vmj.NetworkInfo2.FirstOrDefault().IPADDRESS);
bool ITmacunique = repository.ISITMACUnique(vmj.NetworkInfo2.FirstOrDefault().MACADDRESS);
bool Tipunique = repository.ISTIPUnique(vmj.NetworkInfo2.FirstOrDefault().IPADDRESS);
bool Tmacunique = repository.ISTMACUnique(vmj.NetworkInfo2.FirstOrDefault().MACADDRESS);
try
{
if ((vmj.IsIPUnique == true) && (!ITipunique || !Tipunique))
{
ModelState.AddModelError("NetworkInfo2[0].IPAddress", "Error occurred. The Same IP is already assigned.");
}
if ((vmj.IsMACUnique == true) && (!ITmacunique || !Tmacunique))
{
ModelState.AddModelError("NetworkInfo2[0].MACAddress", "Error occurred. The Same MAC Address is already assigned.");
}
if (!repository.IshypervisorServers(vmj.VirtualMachine.ServerID))
{
ModelState.AddModelError("VirtualMachine.ServerID", "Error occurred. Please select a valid hypervisor server. ");
}
this approach is working well, but i am facing a problem is that i have to repeat these validations on all the related action methods mainly (add & edit) and inside other controller classes (server, vms, storage device, etc...) so is there a way to manage the shared validation in a better way , which facilitate re-use and maintainability ?
EDIT
ServerJoin is as follow:-
public class ServerJoin : IValidatableObject
{
public Server Server { get; set; }
public Resource Resource { get; set; }
public Technology Technology { get; set; }
public SDOrganization Site { get; set; }
public SDOrganization Customer { get; set; }
public NetworkInfo NetworkInfo { get; set; }
public ICollection<NetworkInfo> NetworkInfo2 { get; set; }
[Display(Name="Unique")]
public bool IsMACUnique { get; set; }
[Display(Name = "Unique")]
public bool IsIPUnique { get; set; }
public Nullable<double> SPEED { get; set; }
public Nullable<Int64> PROCESSORCOUNT { get; set; }
[Display(Name = "IP Unique")]
public bool IsTIPUnique { get; set; }
[Display(Name = "MAC Unique")]
public bool IsTMACUnique { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Server != null)
{
if (Server.RackUnitID != null && Server.RackID == null)
{
yield return new ValidationResult("Please select a Rack, or remove the current Rack Unit", new[] { "Server.RackUnitID" });
}
if (Server.RackUnitIDTo != null && Server.RackUnitID == null)
{
yield return new ValidationResult("Please select a Rack From Value", new[] { "Server.RackUnitID" });
}
if (Server.RackUnitIDTo != null && Server.RackUnitID != null && Server.RackUnitID > Server.RackUnitIDTo)
{
yield return new ValidationResult("Rack Unit From must be less than or equal Rack Unit To", new[] { "Server.RackUnitID" });
}
}
&
public class VMJoin
{
public VirtualMachine VirtualMachine { get; set; }
public Resource Resource { get; set; }
public Technology Technology { get; set; }
public SDOrganization Site { get; set; }
public SDOrganization Customer { get; set; }
public NetworkInfo NetworkInfo { get; set; }
public ICollection<NetworkInfo> NetworkInfo2 { get; set; }
public ICollection<TechnologyIP> TechnologyIP { get; set; }
[Display(Name = "Unique")]
public bool IsMACUnique { get; set; }
[Display(Name = "Unique")]
public bool IsIPUnique { get; set; }
public Nullable<double> SPEED { get; set; }
public TechnologyIP TechnologyIP2 { get; set; }
[Display(Name = "IP Unique")]
public bool IsTIPUnique { get; set; }
[Display(Name = "MAC Unique")]
public bool IsTMACUnique { get; set; }
}
}
Start by creating a base class for your models with the common properties which will also reduce the code within the concrete classes.
public class BaseModel
{
public bool IsIPUnique { get; set; }
public bool IsMACUnique { get; set; }
.... // other common properties
}
public class ServerJoin : BaseModel
{
.... // properties specific to ServerJoin
}
public class VMJoin : BaseModel
{
.... // properties specific to VMJoin
}
and create a base controller for the common validation code
public class BaseController : Controller
{
public void Validate(BaseModel model)
{
bool ITipunique = repository.ISITIPUnique(vmj.NetworkInfo2.FirstOrDefault().IPADDRESS);
....
if ((model.IsIPUnique == true) && (!ITipunique || !Tipunique))
{
ModelState.AddModelError("NetworkInfo2[0].IPAddress", "Error occurred. The Same IP is already assigned.");
}
if ((model.IsMACUnique == true) && (!ITmacunique || !Tmacunique))
{
ModelState.AddModelError("NetworkInfo2[0].MACAddress", "Error occurred. The Same MAC Address is already assigned.");
}
.... // other common validation
}
}
and then on the specific controllers
public class ServerController : BaseController
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ServerJoin sj)
{
Validate(sj); // adds the ModelStateErrors
if (!ModelState.IsValid)
{
return View(sj);
}
....
}
}
public class VMController : BaseController
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(VMJoin vmj)
{
Validate(vmj); // adds the ModelStateErrors
if (!ModelState.IsValid)
{
return View(vmj);
}
....
}
}
If VMJoin
and ServerJoin
have the same interface you can just create extension method with ModelState
as a second parameter.
Update Here is example of extension method
public static void TestMethod<T>(this T context, ModelStateDictionary modelsState) where T : YourDbBaseClass
{
//check context
//add errors if exist
modelsState.AddModelError("Error", "Big Error");
}
//Usage
TestMethod<YourDbContext>(ModelState);
In my opinion the cleanest way to do this is using custom validation data annotation attribute.
You simply create custom attribute
public class IPUniqueValidator : ValidationAttribute
{
public string ShouldCheck { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var IP = value.ToString();
var isValid = false;
// Using reflection to get the other property value
var shouldCheckPropert = validationContext.ObjectInstance.GetType().GetProperty(this.ShouldCheck);
var shouldCheckPropertValue = (bool)shouldCheckPropert.GetValue(validationContext.ObjectInstance, null);
if (shouldCheckPropertValue)
{
// do validation code...
}
if (isValid)
{
return ValidationResult.Success;
}
else
{
return new ValidationResult("Error occurred. The Same IP is already assigned.");
}
}
else
{
return new ValidationResult("" + validationContext.DisplayName + " is required");
}
}
}
Mark your models with new attribute:
public class VMJoin
{
[IPUniqueValidator(ShouldCheck = "ShouldCheck")]
public string IpAddress { get; set; }
public bool ShouldCheck { get; set; }
}
public class ServerJoin
{
[IPUniqueValidator(ShouldCheck = "ShouldCheck")]
public string IpAddress { get; set; }
public bool ShouldCheck { get; set; }
}
And it left to you only add check for validation state. The framework will do all work for you.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ServerJoin sj)
{
if (ModelState.IsValid)
{
// do staff
}
}
I would go with a custom ActionFilter
. Check out how to access ModelState
from an action filter here:
How do I access the ModelState from an ActionFilter?
You can also setup dependency injection within the custom action filter to receive the required repositories, further increasing testability/maintainability.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.