Showing posts with label code first migration. Show all posts
Showing posts with label code first migration. Show all posts

3.13.2013

ModelState.IsValid always returning False for RegularExpression ValidationAttribute for a File Upload in MVC 4

I posted a question in stackoverflow and ended up answering it on my own. Some people gave me clues along the way so I'm thankful. I just want to do a recap of the solution that way I have it in my notes...

I was having issues with model validation using RegularExpression as indicated in my class below, the property AttachmentTrace is a file attachment that is uploaded during form post.
public class Certificate {
    [Required]
    // TODO:  Wow looks like there's a problem with using regex in MVC 4, this does not work!
    [RegularExpression(@"^.*\.(xlsx|xls|XLSX|XLS)$", ErrorMessage = "Only Excel files (*.xls, *.xlsx) files are accepted")]
    public string AttachmentTrace { get; set; }
}
All the while I thought there's something wrong with my Regex. But after looking closely on this issue, I found out that what is being validated on the server side is the string "System.Web.HttpPostedFileWrapper", and not the actual filename. That's the reason why ModelState.IsValid returns a false everytime, no matter how right the regex is. But I cannot simply switch the property type of string to HttpPostedFileBase in my model, because I'm using EF code-first migration, which will result into an unpleasant error message when adding a migration.

So the solution to this is to employ a ViewModel, instead of the Entity directly:
public class CertificateViewModel {
    // .. other properties
    [Required]
    [FileTypes("xls,xlsx")]
    public HttpPostedFileBase AttachmentTrace { get; set; }
}
The next step is to create a custom ValidationAttribute for the FileTypes:
public class FileTypesAttribute : ValidationAttribute {
    private readonly List _types;

    public FileTypesAttribute(string types) {
        _types = types.Split(',').ToList();
    }

    public override bool IsValid(object value) {
        if (value == null) return true;
        var postedFile = value as HttpPostedFileBase;
        var fileExt = System.IO.Path.GetExtension(postedFile.FileName).Substring(1);
        return _types.Contains(fileExt, StringComparer.OrdinalIgnoreCase);
    }

    public override string FormatErrorMessage(string name) {
        return string.Format("Invalid file type. Only {0} are supported.", String.Join(", ", _types));
    }
}
In the controller Action, use the ViewModel instead of the Entity, then map the ViewModel back to the Entity via AutoMapper:
public ActionResult Create(CertificateViewModel certificate, HttpPostedFileBase attachmentTrace, HttpPostedFileBase attachmentEmail) {
        if (ModelState.IsValid) {
            // Let's use AutoMapper to map the ViewModel back to our Certificate Entity
            // We also need to create a converter for type HttpPostedFileBase -> string
            Mapper.CreateMap().ConvertUsing(new HttpPostedFileBaseTypeConverter());
            Mapper.CreateMap();
            Certificate myCert = Mapper.Map(certificate);
            // other code ...
        }
        return View(myCert);
    }
For the AutoMapper, create a TypeConverter for HttpPostedFileBase:
public class HttpPostedFileBaseTypeConverter : ITypeConverter {

    public string Convert(ResolutionContext context) {
        var fileBase = context.SourceValue as HttpPostedFileBase;
        if (fileBase != null) {
            return fileBase.FileName;
        }
        return null;
    }
}
I know, it's a lot of work, but this will ensure the file extension validation will be done correctly. But wait, this is not yet complete, I need to also make sure that the client side validation will work for this and that will be the topic of my next post.

3.08.2013

EF Code First Migrations - How To Sync To Production DB

It's very simple to push out the changes in the database across any db that would need the changes, be it your test, staging or production servers. All you have to do is to create a SQL script via Update-Database with a -Script flag:
PM>Update-Database -Script -SourceMigration: $InitialDatabase -TargetMigration: AddPostAbstract
Because of the -Script switch, Code First Migrations will run the migration pipeline but instead of actually applying the changes it will write them out to a .sql file for you. Neat.

Complete write up from Microsoft is here.

If you are doing this for the first time, and you need all the changes so far, you just need to do this:
PM>Update-Database -Script -SourceMigration: $InitialDatabase
Here's a sample PM console session:
PM> Update-Database -Script -SourceMigration: $InitialDatabase
Applying code-based migrations: [201301240307501_Initial, 201302020143144_ModifiedEmailTableAndAddedSignatureTable, 201302020149599_AddedCreatorToSignatureRelation, 201302020158207_ChangeSignatureIdToNullable, 201302050122407_AddResultingImageFileToSignatureTable, 201302050154113_AddLabelToSignatureTable, 201302052134222_ChangedAddressToHave3LineItems, 201302090226121_ChangedSignatureFileToRequired, 201302121909589_AddedSalesItemClass, 201302192149411_AddedCustomerClass, 201302210141465_AddedLocationInIamProfileClass, 201302260305398_UpdatedCertificateClass].
Applying code-based migration: 201301240307501_Initial.
Applying code-based migration: 201302020143144_ModifiedEmailTableAndAddedSignatureTable.
Applying code-based migration: 201302020149599_AddedCreatorToSignatureRelation.
Applying code-based migration: 201302020158207_ChangeSignatureIdToNullable.
Applying code-based migration: 201302050122407_AddResultingImageFileToSignatureTable.
Applying code-based migration: 201302050154113_AddLabelToSignatureTable.
Applying code-based migration: 201302052134222_ChangedAddressToHave3LineItems.
Applying code-based migration: 201302090226121_ChangedSignatureFileToRequired.
Applying code-based migration: 201302121909589_AddedSalesItemClass.
Applying code-based migration: 201302192149411_AddedCustomerClass.
Applying code-based migration: 201302210141465_AddedLocationInIamProfileClass.
Applying code-based migration: 201302260305398_UpdatedCertificateClass.
Once the script is generated, Visual Studio opens the script for you to save. What I do is just simply copy and paste the resulting sql script to SQL Server Management Studio, then execute it there against the database. Done!

There's a little hickup:
I got this error message, which means that the script generator is unable to remove duplicate variable declarations in the sql. The solution is to simply delete such duplicate declarations.

Msg 134, Level 15, State 1, Line 101 The variable name '@var0' has already been declared. Variable names must be unique within a query batch or stored procedure.

12.13.2012

EF Code First Migrations Error on Foreign Key Constraints

Got this error message: Introducing FOREIGN KEY constraint 'FK_dbo.Emails_dbo.Certificates_CertificateId' on table 'Emails' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

SOLUTION: Change the Up Method of the migration on the ForeignKey field as follows:
.ForeignKey("dbo.Creators", t => t.SenderId, cascadeDelete: false)
.ForeignKey("dbo.Certificates", t => t.CertificateId, cascadeDelete: false)
Change cascadeDelete to false.

11.16.2012

Set Up Code First Migrations for EF in ASP.NET MVC 4

First open Package Manager Console: Tools > Library Package Manager > Package Manager Console

1.  Go to the project, either the Web Application or if a separate project for Model, then enable the migration as follows:
PM> Enable-Migrations -ContextTypeName ProductCoC.Model.DataContext
This creates a Migration folder within the project root and creates the initial creation classes as well as the Configuration.cs.  Open Configuration.cs and add your seed.

2.  Initialize
PM> Add-Migration Initial
3.  Now, let's update the database:
PM> update-database
4.  If adding a property to the model classes, change the model class then do an add as follows, any arbitrary name will do:
PM> add-migration AddSoldToPartyAndShipToParty
5.  Now do a database update:
PM> update-database