Reusing Validation Attributes in MVC5 - c#

I have been doing a lot of research on validation and how it works. I understand that we can use attributes or even make custom attributes which we can throw over our ViewModels to validate that data. While this is all working fine, I am finding myself to be reusing same combination of attributes on multiple ViewModels.
For example, lets take a "Name", in project X a name, whether it is a Movie Name, Book Name, Person First Name, Last Name, etc... it is a name after all and as such I tend to apply 90% of the validation attributes the same. Required, Minimum length 3, Maximum length 50, only letters, spaces, etc... you get the picture.
Now I end up with a variable that has 5+ attributes stacked on it. These are pre-built attributes that I would prefer not to code again as they are already coded for me. So my question is this:
How can I create a CustomValidateName attribute, which will validate for all of those things, provide different error messages based on what is wrong and at the same time, reuse some of the built in attributes in .NET framework so that I am not re-inventing the wheel. The bottom line here is that whenever I have a Name variable, I can now just put this one attribute instead of the normal 5+.

Use Can create custom Validation for your all validation
For Example :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
namespace Custom_DataAnnotation_Attribute.Models
{
public class CustomEmailValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
string email = value.ToString();
if (Regex.IsMatch(email, #"[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", RegexOptions.IgnoreCase))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult("Please Enter a Valid Email.");
}
}
else
{
return new ValidationResult("" + validationContext.DisplayName + " is required");
}
}
Above method generate validation for both required nd Email type
You can Add More validation In this method Using If Else or Switch and revert the custome message
At Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace Custom_DataAnnotation_Attribute.Models
{
public class EmployeeModel
{
public string Name { get; set; }
[CustomEmailValidator]
public string Email { get; set; }
public string Password { get; set; }
public string Mobile { get; set; }
}
}

Related

Internal exception has occured: Type '<>f__AnonymousType2`6[...]' cannot be serialized

I have written an endpoint in ASP.net (which should give back a nice JSON) and the following error puzzles me since via my Swagger interface, everything works fine, but if I call the endpoint directly in a browser with http://localhost:63291/api/AutoUpload/, I receive:
Internal exception has occured: Type '<>f__AnonymousType2`6[System.String,System.DateTime]' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. If the type is a collection, consider marking it with the CollectionDataContractAttribute.
See the Microsoft .NET Framework documentation for other supported types.
This seems strange, because I was thinking that I did indeed implement a parameter-less (default) constructor. My code reads as follows
using AutoMapper;
using myProject.API.Filters;
using myProject.API.Models;
using myProject.Entity.DAL;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Description;
namespace myProject.API.Controllers
{
[UserAuthenticationFilter]
public class AutoUploadController : BaseController
{
public AutoUploadController() {
System.Diagnostics.Debug.WriteLine("Parameterless default constructor only for serialization");
}
// GET: api/AutoUpload
[ResponseType(typeof(IEnumerable<FilesDetailsDto>))]
public IHttpActionResult GetAutoUpload(string OptionString = "status")
{
if (OptionString == "status")
{
var rootLocation = ConfigurationManager.AppSettings["ROOT-FOLDER-LOCATION"];
string[] entries = Directory.GetFiles(rootLocation, "*.csv", SearchOption.AllDirectories);
var convList = entries.Select(x => new
{
FullPath = x,
LastModifed = System.IO.File.GetLastWriteTime(x)
});
return Ok(convList.AsEnumerable());
} // end IF-clause for optional parameter
else
{
return NotFound();
}
}
}
}
The above piece of code uses the following data transfer object (DTO) definition:
using System;
namespace myProject.API.Models
{
public class FilesDetailsDto
{
public string FileName { get; set; }
public DateTime LastModifiedOnFilesystem { get; set; }
}
}
References
Datacontract exception. Cannot be serialized
You are returning collection of anonymous type instead of your actual type defined as attribute which can be the culprit for this error.
You can try to be more explicit when using the Select here something like:
var convList = entries.Select(x => new FilesDetailsDto()
{
FullPath = x,
LastModifiedOnFilesystem = System.IO.File.GetLastWriteTime(x)
});

DataTypeAnnotations MetadataType not working as expected

I try to implement Metadatatype, in order to seperate Validation attributes from my Acquisitiecode class, into the AcquisitiecodeAnnotations class.
Now when I add attributes (like Required, StringLength and so on) to the Acquisitiecode class, validation works as expected. When I move these attributes to the AcquisitiecodeAnnotations class and bind this class using the MetadataType attribute, I does not work.
Please find the code examples below (I've stripped them down for readability). Also, the project is an ASP.NET Core 3.0 web application. All code, including the examples are also running in.NET Core 3.0 projects.
Snippet 1:
using System;
using System.ComponentModel.DataAnnotations;
namespace Shared.Entities
{
[MetadataType(typeof(AcquisitiecodeAnnotations))]
public partial class Acquisitiecode
{ }
public partial class AcquisitiecodeAnnotations
{
[StringLength(4, ErrorMessage = "The value cannot exceed 4 characters. ")]
public string Acquisitiecode1 { get; set; }
}
}
Snippet 2:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Shared.Entities
{
public partial class Acquisitiecode
{
public Acquisitiecode()
{
Lidmaatschap = new HashSet<Lidmaatschap>();
}
public string Acquisitiecode1 { get; set; }
public virtual Lid Lid { get; set; }
public virtual ICollection<Lidmaatschap> Lidmaatschap { get; set; }
}
}
As of October 2020 the current version of Blazor does not support Metadatatype.
For more information please read this issue.

Entity Framework MVC and multiple databases

I've put together an MVC application using a repository pattern with Entity Framework and everything is going smoothly - but I've run into a stopping block and I'm not sure how to proceed.
I have a few dozen databases with the same schema, and I want to be able to choose one or many at runtime. For example, let's say I start with a database of users (not made yet). That user has connection string information associated with them (possibly more than one). Once the user has "logged in", I want the Enumerables I feed to my Views to contain matching data from all of the databases that user has access to.
Here's an example of what I have right now:
Entity:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations.Schema;
namespace Dashboard.Domain.Entities
{
public class Flight
{
public Guid Id { get; set; }
public string CarrierCode { get; set; }
public string FlightNo { get; set; }
public string MarketingCarrierCode { get; set; }
public string MarketingFlightNo { get; set; }
public string Type { get; set; }
public string TailNo { get; set; }
public string OriginIATA { get; set; }
...
}
}
DB Context:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using Dashboard.Domain.Entities;
namespace Dashboard.Domain.Concrete
{
public class EFDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Passenger>().ToTable("PAX");
}
public DbSet<Flight> Flights { get; set; }
public DbSet<Passenger> PAX { get; set; }
public DbSet<Airport> Airports { get; set; }
}
}
Flight repository interface:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dashboard.Domain.Entities;
namespace Dashboard.Domain.Abstract
{
public interface IFlightRepository
{
IQueryable<Flight> Flights { get; }
}
}
EF Flight Repository:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dashboard.Domain.Abstract;
using Dashboard.Domain.Entities;
namespace Dashboard.Domain.Concrete
{
public class EFFlightRepository : IFlightRepository
{
private EFDbContext context = new EFDbContext();
public IQueryable<Flight> Flights
{
get { return context.Flights; }
}
}
}
Controller:
public class FlightController : Controller
{
private IFlightRepository fRepository;
private IPaxRepository pRepository;
private IAirportRepository aRepository;
public int PageSize = 10;
public FlightController(IFlightRepository flightRepository, IPaxRepository paxRepository, IAirportRepository airportRepository)
{
this.fRepository = flightRepository;
this.pRepository = paxRepository;
this.aRepository = airportRepository;
}
public ViewResult List(byte status = 1, int page = 1)
{ ...
I want those repositories to contain all of the data from all of the connection strings specified, but I have no idea where to start. EF is getting my connection string from the web.config, but I need to be able to set it dynamically somehow and I need to put more than one database's data into the repository.
Is this possible? I should mention that the site is READ ONLY, so I won't need to write changes back to the DBs.
UPDATE:
I've changed the code so I can pass a connection string to the constructor of my EF Repository, but when I try to merge the IQueryables from two different contexts, as below:
public class EFFlightRepository : IFlightRepository
{
private EFDbContext context1 = new EFDbContext(connectionstring1);
private EFDbContext context2 = new EFDbContext(connectionstring2);
private IQueryable<Flight> context;
public EFFlightRepository()
{
context = (IQueryable<Flight>)context1.Flights.Union(context2.Flights);
}
public IQueryable<Flight> Flights
{
get { return context;}
}
}
I get this exception:
The specified LINQ expression contains references to queries that are
associated with different contexts.
How can I combine them so I can run my LINQ queries just like it's ONE set of data?
It is difficult to come up with a detailed solution because it really depends on your software design choices, but I think a possible solution consists of the following things:
1) A method / class that creates a collection of DbContext objects using the DbContext constructor with connection string or connection string name (is the same constructor) as Willian Werlang mentioned:
new DbContext("DB1");
2) Your repositories should be able to accept the list of DbContext's rather than a single one. It could e.g. be injected with the constructor of it.
3) The retrieval methods should iterate over the repositories and load (eager load when detaching) the relevant objects.
4) The retrieved objects could be detached from their DbContext using the following code:
dbContext.Entry(entity).State = EntityState.Detached;
This isn't required but might be a consideration since you would return a mix of different data sources.
5) The retrieved/detached objects should be added to a returned List<> or you could yield return the results one by one with IEnumerable<> is return type.
Returning an IQueryable isn't possible in this case but an IEnumerable will do as result.
An example of a simple retrieval method for a flight repository could be something like:
public IEnumerable<Flight> GetFlights() {
// dbContexts is an IEnumerable<DbContext> that was injected in the constructor
foreach (var ctx in dbContexts) {
foreach (var flight in ctx.Flights) {
yield return flight;
}
}
}
You can set multiples databases on your web.config, but with different names, so your DbContext's can receive the name of the database you want as parameter, like:
new DbContext("DB1");
This way you can choose from which database you'll get the data but I don't think you can get data from multiples bases at the same time with only onde dbContext;
My solution was to change my Repository classes to take a connection string parameter, like this:
namespace Dashboard.Domain.Concrete
{
public class EFFlightRepository : IFlightRepository
{
private EFDbContext context;
public IQueryable<Flight> Flights
{
get { return context.Flights;}
}
public EFFlightRepository(string connectionString)
{
context = new EFDbContext(connectionString);
}
}
}
Then create a factory class (using Ninject.Extensions.Factory) to pass the parameter when the repository is being created (How to pass parameters down the dependency chain using Ninject):
namespace Dashboard.Domain.Factories
{
public interface IFlightRepoFactory
{
IFlightRepository CreateRepo(string connectionString);
}
}
I have another Factory class that produces a list of Repositories based on a list of strings (connection strings to feed to the individual repository classes).
namespace Dashboard.Domain.Factories
{
public interface IRepoCollectionFactory
{
IRepositoryCollection CreateCollection(List<string> connectionStrings);
}
}
Then, in my controller class, I iterate through the Collection generated by the Collection Factory, running whatever query needs to be run on each set of repositories, and combine the results.
This ultimately gives me a list that contains all of the data from each query on each repository.
public FlightController(IRepoCollectionFactory repoCollectionFactory)
{
this.repoCollectionFactory = repoCollectionFactory;
this.collection = repoCollectionFactory.CreateCollection(new List<string> {
// each connection string for each database here
});
}
Bindings in Ninject class:
private void AddBindings()
{
ninjectKernel.Bind<IFlightRepoFactory>().ToFactory();
ninjectKernel.Bind<IAirportRepoFactory>().ToFactory();
ninjectKernel.Bind<IPaxRepoFactory>().ToFactory();
ninjectKernel.Bind<IRepoFactory>().ToFactory();
ninjectKernel.Bind<IRepoCollectionFactory>().ToFactory();
ninjectKernel.Bind<IRepositories>().To<EFRepositories>();
ninjectKernel.Bind<IRepositoryCollection>().To<EFRepositoryCollection>();
ninjectKernel.Bind<IFlightRepository>().To<EFFlightRepository>();
ninjectKernel.Bind<IPaxRepository>().To<EFPaxRepository>();
ninjectKernel.Bind<IAirportRepository>().To<EFAirportRepository>();
}

Migrations.cs is not created successfully

I am creating new module called "sms" in orchard cms using webmatrix. I create it successfully but when i generate "migrateions.cs", it doesn't generated successfully.
my sms.cs class in Model is given below
using System.Collections.Generic;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace SMSs.Model{
public class smsrecord:ContentPartRecord
{
public virtual int ID{get;set;}
public virtual string Name{get;set;}
public virtual char is_deleted{get;set;}
}
public class smspart:ContentPart<smsrecord>
{
[Required]
public int ID
{
get{return ID=Record.ID;}
set{Record.ID=value;}
}
public string Name
{
get{return Name=Record.Name;}
set{Record.Name=value;}
}
public char is_deleted
{
get{return is_deleted=Record.is_deleted;}
set{Record.is_deleted=value;}
}
}
and the generated Migrations.cs class is as follow
using System;
using System.Collections.Generic;
using System.Data;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
namespace sms {
public class Migrations : DataMigrationImpl {
public int Create() {
return 1;
}
}
}
the "migrations.cs" is not generated successfully why?? Please help
Class itself is generated properly, although it lacks code for creating appropriate tables because you didn't adhere to naming conventions for your record class.
Data migration code generation requires you to follow several conventions in order for it to work properly. I.e.:
Namespace of a record must end with .Models or .Records
There has to exist a public property named Id
All properties have to be virtual (required by NHibernate anyway)
Class cannot be sealed
Class cannot be abstract
Class has to implement IContent or be a subclass of ContentPartRecord
In your case, the namespace (should end with .Models) and incorrect casing of ID (should be Id) are the culprits.

If I know the length of a string, should I declare it as a char with specific length

I'm preparing an Entity Framework model (CODE FIRST) for my C#.NET project. It dawned on me that I was going to have PageTitles stored as strings which have no length restrictions apart from the max and minimum bits available.
I have assumed that if I know a string will be 255 characters long and never exceed that, I could declare my string as a new char[255].
What are the downsides of using char instead of string.
What are the upsides of using char instead of string.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ContentManagementSystem.Models
{
public class Page
{
int Id { get; set; }
string PageTitle { get; set; }
// This seems wasteful and unclear
char[] PageTitle = new char[255];
// How would i apply { get; set; } to this?
}
}
Is there some way of restricting a strings size?
---------------ANSWERED---------------------
This is now my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
namespace ContentManagementSystem.Models
{
public class Page
{
public int Id { get; set; }
[MaxLength(255)] public string Title { get; set; }
[MaxLength(255)] public string Description { get; set; }
public string Content { get; set; }
}
public class MyDbContext : DbContext
{
public DbSet<Page> Pages { get; set; }
}
}
No, you should not use a char[] when you intend to operate on it as a string. Why? Beacuse string has a ton of useful methods that will be unavailable to you if you use a character array. Performance benefits of a character array, if any, would be exceedingly minimal.
EDIT: As DamienG pointed out, this will only work in case of code first.
Are you looking for this?
[MaxLength(255)]
public string PageTitle { get; set; }
Referenced dll:
Assembly System.ComponentModel.DataAnnotations.dll
Referenced namespace:
namespace System.ComponentModel.DataAnnotations
I wouldn't store strings as chars as you'll be forever cursing as you pass them around to things that want a string.
You can specify a maximum length for a string in Entity Framework using the designer for model-first or using the MaxLength attribute on the property for Code First models.
Use the StringLength attribute to inform the framework about the maximum length. You can continue to use strings instead of character arrays.
using System.ComponentModel.DataAnnotations;
...
[StringLength(255)]
public string PageTitle { get; set; }
Using the StringLength attribute in this case may be preferable to the MaxLength attribute because StringLength can also used by the model validation framework to validate user input.
If basic inconvenience is note enough to convince you not to do so - it is also against API design guidelines for C#/.Net - returning arrays via get/set methods is not recommended due to unclear behavior (is it copy/reference) and potential performance impact due to copying large arrays.
When you re-read your sample code you will already know the answer - it is bad idea to replace string with char[255] in public API because it will be very hard to deal with - you don't know how to set it. Most people will expect "XxxxxTitle" property to be be of any type string.
If you need to put length restriction - just enforce it in set method.

Categories

Resources