Hi I have an MVC app that I used to insert update invoices:
public class Invoice : IEntity, IValidatableObject
{
public virtual int InvoiceId { get; set; }
[Required(ErrorMessage = "Invoice Number is a required field.")]
[Column(TypeName = "varchar")]
[StringLength(20)]
[Display(Name = "Invoice Number:")]
public virtual string InvoiceNumber { get; set; }
[Required(ErrorMessage = "Organisation is a required field.")]
[Display(Name = "Organisation:")]
public int OrganisationId { get; set; }
...
}
The problem is I have a requirement "The combination of organisation and invoice number must be unique.
So this has been set up by the DBA in the database. So if I try to do this it will return an exception.
Is there a way instead of displaying the exception to catch it and add a custom error message to the validation summary?
You could catch the exception and add the error to the model state so that your client code handles all errors the same way. Something like:
ModelState.AddModelError("InvoiceNumber", "The combination of organisation and invoice number must be unique.");
you can achieve this by putting remote attribute on both properties and in AdditionalField parameter you pass the name of other property like
[Remote("IsUnique","home",AdditionalFields = "OrganisationID",ErrorMessage = "abc")]
virtual string InvoiceNumber { get; set; }
[Required(ErrorMessage = "Organisation is a required field.")]
[Display(Name = "Organisation:")]
[Remote("IsUnique","home",AdditionalFields = "InvoiceNumber",ErrorMessage = "abc")]
public int OrganisationId { get; set; }
and you can write InUnique method in home controller (for instance) like
public JsonResult IsUnique(string InvoiceNumber, int? OrganisationID)
{
if(InvoiceNumber == null || !Organisation.HasValue)
{
return Json({valid = true});//null check is not job of this attribute
}
else
{
bool result = CheckDbForUniqueness(InvoiceNumber, OrganisationID.Value);
return Json({valid = result});
}
}
This method will be invoked when you change value of either inputs on the form and take the other value as parameter. if either value is null it would return true and null checking will be handled by Required attributes.
Without getting fancy...you could catch it in the controller, then display the message in the view via a member of the model or ViewBag/ViewData.
You can also write a custom validator that checks this for you (even with some client-side validation as well). I wrote one that checked a database for whether a username existed or not, a similar check could be done for this, without relying on exceptions (although you still would want to handle it I wouldn't recommend using exceptions for a normal course of action).
Related
I started working on my first serious MVC project for school unfortunately without defining the data annotations to the model first (so did not set "required" annotation, limit to size of attribute names etc.). I work with a viewmodel, and after adding the annotations the model is causing my ViewModel state to get invalid when posting the form.
It seems like it's the email required that is causing the issue. It is not used on viewmodel and in the form and it seems the viewmodel expects it will get it. Is there a way the form to stop demanding this field by setting some limitation in viewmodel (or controller). I would really prefer not to change the structure of the application (if I start from the scratch I would probably do this a bit different, but not much time is left to finalize the project)
Customer (Model)
public Class Customer(){
public int Id { get; set; }
[Required(ErrorMessage = "Required")]
[StringLength(25, ErrorMessage = "Message"]
public string Name { get; set; }
public string Logo { get; set; }
//[Required(ErrorMessage = "Email required")]
//[Display(Name = "E-mail")]
//[RegularExpression(xxxx, ErrorMessage = "not correct")]
public string Email { get; set; }
public int UserId { get; set; }
}
ViewModel
public class CustomerEditViewModel
{
public Customer Customer { get; set; }
[FileTypes("jpg,jpeg,png")]
[FileSize(1024 * 1024, ErrorMessage = "Max x bytes")]
public HttpPostedFileBase File { get; set; }
}
You can remove errors from the modelstate in your controller, e.g.
this.ModelState[key].Errors.Clear();
where key is the bit to be cleared, so if it's email it's most likely -
this.ModelState["Customer.Email"].Errors.Clear();
I have made a remote validation in my project, to avoid duplicate entries in DB. My model class is like this
public class Supplier
{
public int SupplierId { get; set; }
public string SupplierName { get; set; }
[Required, DisplayName("Supplier Code")]
[Remote("ViCodeExists", "Supplier", "Vi Code is already exists.", AdditionalFields = "SupplierId")]
public string SupplierCode { get; set; }
}
And inside my SupplierController I have the function like this
public JsonResult ViCodeExists(string SupplierCode, int SupplierId = 0)
{
var user = _db.Suppliers.Where(x => x.SupplierCode == SupplierCode.Trim() && x.SupplierId != SupplierId);
return !user.Any() ?
Json(true, JsonRequestBehavior.AllowGet) :
Json(string.Format("{0} is already exists.", SupplierCode),
JsonRequestBehavior.AllowGet);
}
In my create View
#Html.TextBoxFor(model => model.SupplierCode)
#Html.ValidationMessageFor(model => model.SupplierCode)
Everything looks okay to me, but this validation does not works. I have tried adding breakpoint inside controller, But it never get hit. Can any one point out What I am doing wrong here?
Note: I have same type of validation in some other controllers in the
same project and they all work well. Issue is with this one only.
You using the overload of RemoteAttribute that accepts 3 string parameters where the 3rd parameter is the area name (not an error message).
Change the attribute to
[Remote("ViCodeExists", "Supplier", ErrorMessage = "Vi Code is already exists.", AdditionalFields = "SupplierId")]
public string SupplierCode { get; set; }
Note your overriding the error message in the methods return statement anyway, so you can probably omit it and just use
[Remote("ViCodeExists", "Supplier", AdditionalFields = "SupplierId")]
public string SupplierCode { get; set; }
Here is my ListView Model which moreorless corresponds with a datbase table I have built called Comment.
public int EntryId { get; set; }
public DateTime Posted { get; set; }
public string AuthorIp { get; set; }
[Required(ErrorMessage = " A Name is required *")]
[DisplayFormat(ConvertEmptyStringToNull = false)]
[StringLength(160, MinimumLength = 2, ErrorMessage = "Must be between 2 & 160 characters in length.")]
public string AuthorName { get; set; }
[Required(ErrorMessage = "Email address required *")]
[DisplayFormat(ConvertEmptyStringToNull = false)]
[StringLength(160, MinimumLength = 2, ErrorMessage = "Must be between 2 & 160 characters in length *")]
[EmailValidation(ErrorMessage = "Must be valid email *")]
public string AuthorEmail { get; set; }
[Required(ErrorMessage = " A Message is required *")]
[DisplayFormat(ConvertEmptyStringToNull = false)]
[StringLength(4000, MinimumLength = 2, ErrorMessage = "Must be between 2 & 4000 characters in length *")]
public string Body { get; set; }
public ListView(IBlogRepository blogRepository)
{
Posts = blogRepository.Posts();
}
public ListView(){ }`
I need to get some of the properties into my Comment table. Iam using or at least attempting to use the IRepository Framework. It goes like this...
public interface IBlogRepository : IDisposable
{
IList<Entry> Posts();
void InsertComment(Comment comment);
void Save();
}
Here is my inherit class...
public class BlogRepository : IBlogRepository, IDisposable
{
private BlogDataDataContext _dataContext;
public BlogRepository() { _dataContext = new BlogDataDataContext(); }
// #region IBlogRepository Members
public void InsertComment(Comment comment)
{
_dataContext.Comments.InsertOnSubmit(comment);
}
public void Save()
{
_dataContext.SubmitChanges();
}
So I call above InsertComment from my BlogController like this.
[HttpPost]
public ActionResult BlogPost(ListView pModel)
{
pModel.Posted = DateTime.Now;
_repository.InsertComment( // Cannot pass pModel as is not Comment type.
return RedirectToAction("BlogPost");
}
So my problem is that my ListView pModel is passed in but its not of type Comment so I cant Insert it properly. I need my ListView model because it contains extra validation rules and a couple of constructors. Anyone have an idea of where I am going wrong.
Do I need to create a Model which directly mirrors the datbase table I am asdding too. Then where do I move my Constructors and other validation rules? It feels like I need two models. One on top of the other. I'm so close to understanding this now.
Thanks in advance.
If you use ASP.NET MVC your model should be an exact "copy" of your table.
If you want some other informations to pass to your view you can use viewmodels.
In your example why don't you do something like:
[HttpPost]
public ActionResult BlogPost(ListView pModel)
{
pModel.Posted = DateTime.Now;
foreach (Comment comment in ListView.Posts)
{
_repository.InsertComment(comment);
}
return RedirectToAction("BlogPost");
}
I think your approach looks good overall, but you'll need some way of obtaining that current Comment object to pass into your repository. Whether you create another object to house both objects, or refactor your code, I would recommend looking at the following article:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
You could use AutoMapper to map properties from your ViewModel to your Comments business model in your controller:
var comment = Mapper.Map<ListView, Comment>(pModel);
_repository.InsertComment(comment);
This is common practice when working with ViewModels or DTO's.
I am trying to get security settings from the database for objects in a model. I would like to enable / disable / hide controls on my Rendered View depending upon the security settings for a user logged in.
This is what I have got so far:
public class RestrictedObjectsViewModel
{
[SecureObject(ObjectId = 1)]
[Display(Name = "Name")]
public string Name { get; set; }
[SecureObject(ObjectId = 2)]
[Display(Name = "Address")]
public string Address { get; set; }
[SecureObject(ObjectId = 3)]
[Display(Name = "Phone Number")]
public string PhoneNumber { get; set; }
}
Using this approach, I would be querying the database for each object being rendered. Is it possible to query the database just once for the entire objects in the model to get a list of permissions for objects? How would I set that?
UPDATE:
Ok, let me go a bit into detail.
In my code when I set the following attribute to an object, i have programmed my HTML to hide the associated table row for the rendered object:
[SecureObject(IsInvisible = true)]
The above code works correctly in my tests. However, when i try to do the following:
public class RestrictedObjectsViewModel
{
[SecureObject(IsInvisible = ObjectId3Invisible)]
[Display(Name = "Phone Number")]
public string PhoneNumber { get; set; }
public RestrictedObjectsViewModel(bool setPermissions = false)
{
if (setPermissions)
{
ObjectId3Invisible = true;
}
}
public bool ObjectId3Invisible = false;
}
I get an error message saying "An object reference is required for the non-static field, method, or property 'MyProject.Models.RestrictedObjectsViewModel.ObjectId3Invisible'"
Here is the controller:
public ActionResult RestrictedObjects()
{
return View(new Models.RestrictedObjectsViewModel(true));
}
If i change the ObjectId3Invisible to static, i will not be able to change the value to true or false during runtime.
Any suggestions?
I am using entity framework 4.3 in my MVC 3 application, when trying to update the entity(creating and deleting works fine) I am getting this error:
Store update, insert, or delete statement affected an unexpected number of rows (0)
When I got into debug mode I saw that on the [HttpPost] method no feed Id was supplied:
public ActionResult Edit(Feed feed)
{
if (ModelState.IsValid)
{
db.Entry(feed).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.FolderId = new SelectList(db.Folders, "FolderId", "Name", feed.FolderId);
return View(feed);
}
although in the normal Get method the id in being passed. those are my entities
feed:
public class Feed
{
[ScaffoldColumn(false)]
public int FeedId { get; set; }
[StringLength(150, ErrorMessage = "The {0} must be less then {1} charecters")]
public string Title { get; set; }
[ScaffoldColumn(false)]
public string Description { get; set; }
[Required(ErrorMessage = "you must enter a valid link")]
[StringLength(500, ErrorMessage = "The {0} must be less then {1} characters long.")]
public string LinkUrl { get; set; }
[ScaffoldColumn(false)]
public DateTime PublishDate { get; set; }
[ScaffoldColumn(false)]
public string Image { get; set; }
[ForeignKey("Folder")]
[Required(ErrorMessage="you must choose a folder")]
public int FolderId { get; set; }
public virtual Folder Folder { get; set; }
public Feed()
{
PublishDate = new DateTime(2012, 1, 1);
}
}
folder:
public class Folder
{
public int FolderId { get; set; }
[Required(ErrorMessage = "you must enter a folder name")]
[StringLength(150, ErrorMessage = "the {0} must be less then {1} charecters")]
public string Name { get; set; }
}
I have looked for a solution but none of them worked, like trying the refresh method, which doesn't exist in DbContext or defining a [Key] property above the FeedId and FolderId.
You shouldn't be manually maintaining the entity state - change tracking should be performed by the context.
You're using a view model and assuming it should be attached to the database.
You need to do something like..,
Feed DbFeed = DBSet.Where(f => f.id = feed.Id);
DbFeed.Property = NewValue;
db.SaveChanges();
(excuse possibly incorrect syntax - I work in VB by default)
That is, get a new instance of the Feed object from the DB Context, then perform changes on the object you're given.
This is because the context doesn't actually give you a plain Feed object, but rather an anonymous type which wraps it and has the same properties. The wrapper overrides your methods and detects property changes which is how it maintains state.
The feed object you get back from your view doesn't contain this wrapper, hence the problems
Entity Framework tracks objects, the feed you get from your view is untracked. The pattern for this situation is to fetch the object you want to update from the db, then call UpdateModel which will apply the changes from your untracked entity to your tracked entity, which you can then save..
if (ModelState.IsValid)
{
var trackedEntity = db.Find(feed.Id)
UpdateModel(trackedEntity);
db.SaveChanges();
return RedirectToAction("Index");
}
apparently but putting the [ScaffoldColumn(false)] attribute in my model it didn't create it in my view and there for the id was not passed.
I added #Html.HiddenFor(model => model.FeedId) to my model and that took care of the issue.
Apparently you were having concurrency problems.
Your update state should be running as:
UPDATE tableA SET colA = 'value' WHERE colX1 = 'compare1' AND colX2 = 'compare2';
This colXn could be your primary keys etc, or could be every column you are using. If you are not handling concurrency, if someone gets the data as the same time as you, alter and save it before you, your WHERE statement will never match, since the record information you are updating already has new information.