Hi i'm new to MVC and EF so this may be a really simple question but what is the best way to prevent the user from trying to enter duplicate records?
I have a simple look up table with one column which is the primary key. I'm creating a maintenance screen so admins can add extra items to the look up list. My controller looks like :
public ActionResult Index(string NewRow)
{
try
{
CaseType t = new CaseType { ID = NewRow };
if (ModelState.IsValid)
{
UOW.CaseTypes.Add(t);
UOW.Save();
}
}
catch (Exception ex)
{
ModelState.AddModelError("", ex.Message);
}
return View(UOW.CaseTypes.All());
}
Which stops the duplicate records and stops the yellow screen of death but the message displayed is : "An error occurred while updating the entries. See the inner exception for details." which is no good for the users.
I was just wondering if there is a better way to catch the error.
For show validation error I use something like this:
MainEmail it's property from ViewModel
var mod = ModelState.First(c => c.Key == "MainEmail"); // this
mod.Value.Errors.Add("Row's shouldn't duplicates."); // this
if (ModelState.IsValid)
{
return RedirectToAction("Details");
}
return View(client);
Error will show's in this field in view:
<div class="editor-label">
#Html.LabelFor(model => model.MainEmail)
</div>
And for future, you must hide your error screen! You need to display a custom error page:
If you use asp-mvc-3, add to web.config such string:
<system.web>
<customErrors mode="On" defaultRedirect="~/Error" />
...
And users will have /Shared/Error.cshtml page insted of page with exception message (which can show sequrity data).
ADD
About unique constraint creation was discussed here
Unique Constraint in Entity Framework Code First
than you can check about records duplication with your try code.
In my application I use code first and don't add unique constraint because it's lead to low testability. In that case use simple Find before saving changes.
First aproach is little bit faster.
Approach two is grow up testability.
If you want to get the inner exception you can do like this,
catch (Exception ex)
{
while(ex.InnerException!=null){
ex=ex.InnerException;
}
ModelState.AddModelError("", ex.Message);
}
no too sure about the syntax :)
Related
Hello I am new in mvc so I have a problem during the DB update. I have table in database which column is defined as a unique key that means I don't want to same data in that column but at the time of entering same data my data access layer class generates an exception called DbUpdate exception. I just want to handle this exception by sending a message "Your given data is already exists".. Please help me. Thanx in advance.
Generally, the idea is you want to raise a custom exception which make sense to each layer. The reason for this is because you want to keep your DAL abstract, for example, in your DAL you would catch this particular exception (DbUpdateException) and raise your own custom exception e.g.
try
{
...
myContext.SaveChanges();
}
catch (DbUpdateException ex)
{
throw new DuplicateDataException("Data already exists");
}
Then in your business layer, if you want to further abstract you could throw a domain exception
try
{
dataStore.Save(new Entity { ... });
}
catch (DuplicateDataException ex)
{
throw new DomainException(ex.Message);
}
Then finally in your controller, pass the error to the view
[HttpPost]
public ActionResult SomeAction(SomeModel model)
{
try
{
myDomain.Save(model);
return View("Success", model);
}
catch (DomainException ex)
{
return View("Error", ex.Message);
}
}
The example is completely fictitious of course and may or may not apply to your specific code-base, however, the goal is to demonstrate how the exception will effectively "bubble-up" from your DAL layer back to your UI.
I am placing particular emphasis on using custom exceptions simply because it provides you with a nice clean abstraction.
So I have a method in my service, which I will call from a controller:
public void SendMessage(Message message) {
message.Property = "Random";
try {
// try some insert logic
}
catch (Exception) {
// if it fails undo some stuff
// return the errors
throw;
}
// if there are no errors on the return the operation was a success
// but how do I get the Service generated data?
}
Edit:
So the question isn't really about getting my code to work it's a problem I have with the Repository Pattern whilst using a Service Layer as a 'go between' for communication between the DAL and Presentation
So I have a separate assembly called DataLibrary.
The DataLibrary has my models (Message), my repositories and Services (MessageService)
In my MVC site I would typically have a controller, with CRUD functionality. It would look something like this:
public ActionResult Create(Message message) {
if(ModelState.IsValid) {
db.insert(message);
}
Return View(message);
}
But by using the Repository Pattern, with a service layer for communication I have this instead:
public ActionResult Create(MessageCreateModel message) {
if(ModelState.IsValid) {
MessageService.SendMessage(message.ToDTO());
}
Return View(message);
}
How do I know that the operation was successful or unsuccessful and for what reason?
How do I retrieve the populated data from the service's business logic at the same time as the above?
And how do I achieve both of these two above while sticking as close as possible to MVC design pattern / spearation of concerns for extensibility?
First of all, why do you go through as service if it's just delegating the work to your repository? If you have implemented your repository properly (i.e. being a complete abstraction) there is no need to use the service. Simply call the repository directly from your controller. You can read more about the repository pattern in my blog.
But that doesn't really adress the issue.
So how do you handle errors? When it comes to exceptions: Simply do not catch it ;) Exceptions are after all exceptions and is not something that you typically can handle to deliver the expected result.
As we are talking about the data layer that usually means a stable database engine where it's expected that the read/write will succeed. Hence there is no need for any other error handling than using exceptions.
In ASP.NET MVC you can handle transactions with an attribute and use try/catch to fill the model state as shown here:
[HttpPost, Transactional]
public virtual ActionResult Create(CreateModel model)
{
if (!ModelState.IsValid)
return View(model);
try
{
model.Category = model.Category ?? "Allmänt";
var instruction = new Instruction(CurrentUser);
Mapper.Map(model, instruction);
_repository.Save(instruction);
return RedirectToAction("Details", new {id = instruction.Id});
}
catch (Exception err)
{
// Adds an error to prevent commit.
ModelState.AddModelError("", err.Message);
Logger.Error("Failed to save instruction for app " + CurrentApplication, err);
return View(model);
}
}
You really didn't provide enough information about your architecture is structured to answer this question. However, if you want to get a value back from the SendMessage method, adding a return value instead of void is a good place to start.
I think you should first decide how are you going to design your architecture. Are you go with service oriented, if so your service methods have to be return something for inform controllers. So think your service layer like a border of a country, and the other country's borders are controllers. And you have to let trade these two countries. This can be done with return object which contains return data and also service errors and so on.
If you only want to put your some business logic into your service layer then you probably dont need independent layers. Just some loose coupling is enough for you. So you can return basic clr objects or domain objects or application objects. In a very basic example like this:
//AService service method
public AnEntity ServiceMethod(AFilterViewModel aFilter)
{
//do some validation
try
{
//some transactional operations
}
catch
{
//do some log and rollback it...
throw;
}
var anEntity = _aRepository.GetSomeEntity(x=> x.Something == aFilter.Something);
return anEntity;
}
//controller method
public ActionResult GetSomething(AFilterViewModel aFilter)
{
try
{
var entity = _aService.ServiceMethod(aFilter);
AViewModel model = MapToView(entity);
return View(model);
}
catch
{
return RedirectToAction("Error");
}
}
As you see above the controller and service layer methods can share each others objects. They have boundaries to each other and they coupled. But your architecture decides how much they coupled.
You can also do these mappings one way only. Something like service to Controller only or Controller to service only. If you dont want to use your viewmodel in service layer, you should always do mappings in service layer. Otherwise do your object mapping in controllers. And also dont forgot to put your viewmodel's into another library, its very important. These are something like "value objects".
I have a controller action in my project that has a situation where it needs to display an error message to the user under certain scenarios. This action occurs in a POST:
[HttpPost]
public ActionResult DoSomeAction() {
if( someCondition )
return RedirectToAction("SomeActionError");
return RedirectToAction("Index");
}
public ActionResult SomeActionError() {
return View();
}
Currently I have it set up so that it will redirect to an error controller action. I'm not really fond of this approach because in the URL they see /SomeActionError and it also means that the user can directly navigate to this URL.
Is it a bad design/approach to put some flag in TempData and redirect to another controller that checks for the TempData error flag?
Example:
[HttpPost]
public ActionResult DoSomeAction() {
if( someCondition ) {
TempData["DoSomeActionError"] = true;
}
return RedirectToAction("Index");
}
public ActionResult Index() {
// check for error
if( TempData["DoSomeActionError"] ) {
return View("SomeActionError");
}
}
Is this a bad idea? Is there another approach that does something similar (doesn't allow the user to directly navigate to the error action)? I don't want to return the View on the POST action because I don't want them to refresh and cause another POST.
TempData is not per se a bad concept. TempData is for transporting an information to some consumer that reads that information and the information should vanish after it's been read.
The way your're using TempData is odd. A more elegant implementation for your requirements (you should show an error message) is to implement an equivalent to the rails flash concept and don't redirect to an error page but display an error message in your index view. Something like a red banner that says "The record could not be saved".
This question shows a nice flash implementation including the view stuff (not the accepted answer but the answer by #jim)
using tempdata in mvc is not a good approach.
If i were you i'll do as following:
[HttpPost]
public ActionResult DoSomeAction() {
if( someCondition ) {
return RedirectToAction("Index", new{error=true}
}
return RedirectToAction("Index");
}
public ActionResult Index(bool? error) {
// check for error
if(error?? false ) {
return View("SomeActionError");
}
}
While I don't agree TempData is always bad (I find it great for status messages I absolutely don't want passed on the url such as "record saved", I think in your case there may be a better option.
First you don't want an error page to be accessible - may I ask why?
To do a redirect when an error happens only to redirect again is a bit odd. I would throw the exception and handle that exception by your error view. MVC automatically adds the [HandleError] attribute as a global filter, so throw your exception (a custom type if necessary) and handle it in your error page as you see fit since you can access exception details there and it doesn't require a redirect.
I'm trying to setup xVal with an ASP.NET MVC 2 Preview 1 project. I'm basically following the example at http://blog.codeville.net/2009/01/10/xval-a-validation-framework-for-aspnet-mvc/ to the letter (server-side only, so far).
I have annotated a BlogPost entity, and here is the Post action:
[HttpPost]
public ActionResult Index(BlogPost b)
{
try
{
_blogService.Insert(b);
}
catch (RulesException ex)
{
ex.AddModelStateErrors(ModelState, "");
}
return (View(b));
}
And here's the service method:
public void Insert(BlogPost post)
{
var errors = DataAnnotationsValidationRunner.GetErrors(post);
if(errors.Any())
{
throw new RulesException(errors);
}
_blogRepo.Insert(post);
}
(Note that the DataAnnotationsValidationRunner is verbatim from the example blog post). When I submit a totally invalid BlogPost form, I get this list of validation errors:
A value is required.
Please enter a title
Please enter a posted date
Please enter some content
Please enter a title
Please enter a posted date
Please enter some content
I don't even know what the first message is for, but as you can see, the other errors are appearing twice. What am I doing wrong? Or is this a problem with MVC V2?
Starting in ASP.Net MVC 2 Preview 1 we now get DataAnnotation validation support out of the box, so I guess your issue is that when the ModelBinder logic runs it is applying the DataAnnotation rules:
public ActionResult Index(BlogPost b) //Create BlogPost object and apply rules
and then with your XVal logic you are requesting the check again:
var errors = DataAnnotationsValidationRunner.GetErrors(post);
This is backed up by the fact they are repeated in the same order.
Your code would have worked fine in version 1 of MVC as public ActionResult Index(BlogPost b) would not have run the DataAnnotation rules. I have not read anywhere if it is possible to turn off the new DataAnnotation logic and just use XVal.
There is more information about this on Scott's post able preview 1
To find out what the first item is run debug and check what errors are on the ModelState, as this will tell you what property on the object the errors are related to.
[HttpPost]
public ActionResult Index(BlogPost b)
{
try
{
_blogService.Insert(b); //Add breakpoint here and check ModelState
}
catch (RulesException ex)
{
ex.AddModelStateErrors(ModelState, "");
}
return (View(b));
}
I have a top-level page called ReceiveItem. Within that page, I have a couple different FORMs - only one of which will be filled out and submitted (depending upon how that item is received). This all works quite well from the UI perspective. Here is the general page structure:
<ReceiveItem.aspx>
<ReceiveNewInventory.ascx>
<ReceiveOrderReturn.ascx>
<ReceiveFromLoan.ascx>
Except, I do not know how to properly display validation errors. Here is my controller for one of those forms:
public ActionResult ReceiveNewInventory(
int id,
int vendorId,
int quantity,
decimal cost) {
var db = new Data();
var item = db.ItemSet.First(i => i.Id == id);
var vendor = db.BusinessSet.First(i => i.Id == vendorId);
ValidateCost(cost);
ValidateQuantity(quantity);
if (ModelState.IsValid) {
item.AddNewInventory(vendor, quantity, cost);
TempData["Message"] = "Added " + quantity +
" inventory items to " + item.FullDisplayName;
return RedirectToAction("Index");
}
else {
TempData["Quantity"] = quantity;
TempData["VendorId"] = vendorId;
TempData["Cost"] = cost;
return RedirectToAction("ReceiveItem", new { id });
}
}
I would like to display the model errors that the two validation functions add using the simple Html.ValidationSummary function; but, those errors seem to get lost because I do the RedirectToAction. (My ReceiveNewInventory controller action does not have a view directly associated with it.)
With the one condition that I still want 1 page with multiple FORMs, what can I change about this design so that my validation messages show up on the ReceiveItem page?
You need to put the ModelState into TempData and extract it in your ReceiveItem action method. Alternatively, change the Redirect to a return View()
hth
Dan
Look at NerdDinner and see how they do it. Very neat and you can display a summary at the top of the page as well as text next to each item if you wish.
let me know if you have trouble and I'll post code.
Why do you redirect to ReceiveItem even if you have errors? When you display the validation message, don't you want the user to have the opportunity to fix their mistakes? If so, why not keep them on the RecevieNewInventory page again and return the view?