How can I pass the exception thorwn by the action in MVCContrib.FluentController CheckValidCall(action)?
[ExportModelStateToTempData]
public ActionResult Index(int itemId, int page)
{
return CheckValidCall(() => MyService.GetResults(itemId, page))
.Valid(x => View(x))
.Invalid(() => RedirectToAction(RestfulAction.Index));
}
When GetResults() throws exception I want to display it in the view. I've tired ModelState
<%if (ViewData.ModelState.ContainsKey("_FORM")) {%>
<div class="notificationError">
<%= ViewData.ModelState["_FORM"].Errors.FirstOrDefault().ErrorMessage %>
</div>
<%}%>
but the ModelState is valid and contains no errors. Is there any way to access the exception message without wrapping service method in try-catch block? If it helps here is my unit test to check ModelState which fails as TestController.ModelState.IsValid is true:
[Fact]
public void ServiceExceptionIsConvertedToModelStateErrorInFluentController()
{
// Set up
MockService.Setup(x => x.GetResults(It.IsAny<int>(), It.IsAny<int>()))
.Throws(new InvalidOperationException("Mocked Service Exception"));
// Excercise
Assert.Throws<InvalidOperationException>(() => TestController.GetResults(1, 1));
// Verify
Assert.False(TestController.ModelState.IsValid);
Assert.True(TestController.ModelState["_FORM"].Errors.Count > 0);
}
I've manage to pass exception into ModelState by overriding MvcContrib.FluentController.AbsteactFluentController.ExecuteCheckValidCall(Func action):
protected override object ExecuteCheckValidCall(Func<object> action)
{
try
{
return base.ExecuteCheckValidCall(action);
}
catch (Exception exception)
{
ModelState.AddModelError("_Exception", exception);
return null;
}
}
Which is called by CheckValidCall. However the method is described as "public for testing purposes only and shouldn't be used" the alternative way of doing it is to override MvcContrib.FluentController.AbstractFluentController.CheckValidCall().
Related
I have a simple form with a textbox (and a model editor I want to render in specific cases)
#using (Html.BeginForm("Import", "Flow"))
{
#Html.TextBoxFor(model => model.IsConfirmed)
#if (Model.IsConfirmed)
{
#Html.EditorFor(m => m.Preview)
}
}
The model used in this view is the following
public class ImportViewModel
{
public Boolean IsConfirmed { get; set; }
public PreviewViewModel Preview { get; set; }
public ImportViewModel()
{
this.IsConfirmed = false;
}
}
The form posts on the following controller
public class FlowController
{
[HttpPost]
public ActionResult Import(ImportViewModel model)
{
try
{
if (ModelState.IsValid)
{
if (model.IsConfirmed)
{
// do something else
}
else
{
model.Preview = Preview(model.strCA, model.SelectedAccount);
model.IsConfirmed = true;
return View(model);
}
}
}
catch (Exception ex)
{
throw new Exception("arf", ex);
}
return RedirectToAction("Index", "Home");
}
}
On first load, the textbox contains "false"
When posted, the property IsConfirmed of the model is set to "true" and this model is passed to the same view.
I expect the textbox to be "true" but it is still "false"... moreover the Preview property is correctly rendered, so it means Model.IsConfirmed is indeed true...
Am I missing something ?
Thanks
Make sure you remove the value from the ModelState if you intend to modify it:
ModelState.Remove("IsConfirmed");
model.IsConfirmed = true;
The reason you need to do that is because, by design, all Html helpers (such as TextBoxFor) will first look for a value in the ModelState when binding and only not found they will use the value on your model. And since there's a value with the same name in the ModelState (coming from the POST request), that's what's being used.
I have a view that design view like :
Here i impose some entry validation when click save button I want to display me error message in my expecting display region. How can it possible?
My Controller Action is :
[HttpPost]
public ActionResult Save(COA_ChartsOfAccount oCOA_ChartsOfAccount)
{
try
{
if (this.ValidateInput(oCOA_ChartsOfAccount))
{
COA_ChartsOfAccount oParent = new COA_ChartsOfAccount();
oParent = oParent.Get(oCOA_ChartsOfAccount.ParentHeadID);
if (oCOA_ChartsOfAccount.IsChild)
{
oCOA_ChartsOfAccount.ParentHeadID = oParent.AccountHeadID;
}
else
{
oCOA_ChartsOfAccount.ParentHeadID = oParent.ParentHeadID;
}
oCOA_ChartsOfAccount = oCOA_ChartsOfAccount.Save();
return RedirectToAction("RefreshList");
}
return View(oCOA_ChartsOfAccount);
}
catch (Exception ex)
{
return View(oCOA_ChartsOfAccount);
}
}
Note : I want to make common partial view for error message display. (Like exception error message, validation message, all kind of user notification message)
With your current set up
To display an error message
In your controller:
catch (Exception ex)
{
TempData["message"] = "Custom Error Messge";
return View(oCOA_ChartsOfAccount);
}
In your view:
<div style="color: red;font-weight:900;">#TempData["message"]</div>
In my MVC3 application I have the model ( not important properties deleted ):
public class AccountViewModel
{
[StringLength(65)]
public string Property1 { get; set; }
[StringLength(65)]
public string Property2 { get; set; }
}
The problem is when an action is submited validation attribute called twice, and I can get 4 errors in summary, instead of 2:
'Property1' length must be less than 65 characters
'Property1' length must be less than 65 characters
'Property2' length must be less than 65 characters
'Property2' length must be less than 65 characters
I dont use Validate method in my controller's code. The problem appears also with my custom attributes, but its not happens with Required attribute. Also I have to note that ctor of the custom attributes also called twice
My action
[HttpPost]
public ActionResult CreateOrEdit(AccountViewModel model) {
if (!ModelState.IsValid) {
return View("Edit", model);
}
try {
_accountService.InsertOrUpdate(model);
}
catch (Exception ee) {
ModelState.AddModelError("", ee.Message);
return View("Edit", model);
}
return RedirectToAction("Index");
}
On View I render my errors using:
#{
var errors = ViewData.ModelState.Errors();
<div class="alert alert-block alert-error #(errors.Count == 0 ? "hide" : "")" >
<h4 class="alert-heading"> You got an error!</h4>
<ul>
#foreach (var error in errors) {
<li>#error</li>
}
</ul>
</div>
}
And I double re-check once more that ViewData.ModelState already contains errors twice.
The problem was in integrating Ninject. If you use Ninject.MVC package ( I use version 3 ) it registers his own ModelValidationProvider while initializing and removes the old one:
In Ninject.Web.Mvc.MvcModule
this.Kernel.Bind<ModelValidatorProvider>().To<NinjectDataAnnotationsModelValidatorProvider>();
In Ninject.Web.Mvc.NinjectMvcHttpApplicationPlugin:
public void Start()
{
ModelValidatorProviders.Providers.Remove(ModelValidatorProviders.Providers.OfType<DataAnnotationsModelValidatorProvider>().Single());
DependencyResolver.SetResolver(this.CreateDependencyResolver());
RemoveDefaultAttributeFilterProvider();
}
So, rather than creating my own implementation of IDependencyResolver ( Ninject Kernel wrapper ) I followed this tutorial
or
you should remove Ninject.MVC package and remove its binaries from the bin folder.
This issue is caused by improper use of NInject.
To solve this issue In NinjectWebCommon class CreateKernel() method add kernel.Rebind<ModelValidatorProvider>().To<NinjectDefaultModelValidatorProvider>();
Here is the code sample.
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
try
{
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
kernel.Rebind<ModelValidatorProvider>().To<NinjectDefaultModelValidatorProvider>();
return kernel;
}
catch
{
kernel.Dispose();
throw;
}
}
Problem: ModelState is not valid, complaining that there is no Event.
I've tried associating it and done a TryUpdateModel
Inspecting raceViewModel it does look fine.
[HttpPost]
public ActionResult Create(RaceViewModel raceViewModel, Guid idEvent)
{
Event _event = uow.Events.Single(e => e.Id == idEvent);
raceViewModel.RaceInVM.EventU = _event;
TryUpdateModel<RaceViewModel>(raceViewModel);
if (!ModelState.IsValid)
{
SetupDropDownsStronglyTyped(raceViewModel);
return View(raceViewModel);
}
I think that your idEvent parameter may not be what you expect it to be. The uow.Events.Single call then fails and throws an exception (there is no Event with the provided Id).
I am having the following code, However the errors causght not being displayed. What is wrong ?
public ActionResult DeleteRateGroup(int id)
{
try
{
RateGroup.Load(id).Delete();
RateGroupListModel list = new RateGroupListModel();
return GetIndexView(list);
}
catch (Exception e)
{
RateGroupListModel model = new RateGroupListModel();
if (e.InnerException != null)
{
if (e.InnerException.Message.Contains("REFERENCE constraint"))
ModelState.AddModelError("Error", "The user has related information and cannot be deleted.");
}
else
{
ModelState.AddModelError("Error", e.Message);
}
return RedirectToAction("RateGroup", model);
}
}
#model MvcUI.Models.RateGroupListModel
#{
View.Title = "RateGroup";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Rate Group</h2>
#Html.ValidationSummary()
#using (Html.BeginForm())
private ActionResult GetIndexView(RateGroupListModel model)
{
return View("RateGroup", model);
}
public ActionResult RateGroup(RateGroupListModel model)
{
return GetIndexView(model);
}
It looks like you're setting the ModelState error, then redirecting to another action. I'm pretty sure the ModelState gets lost when you do that.
Typically, you'd just render the RateGroup view directly from the DeleteRateGroup action, without the redirect, passing in your model if needed, like this:
return View("RateGroup", model);
If you want the ModelState to come along to the second action with you, take a look at MvcContrib's ModelStateToTempDataAttribute. Here's the attribute's description, from the MvcContrib source code's comments:
When a RedirectToRouteResult is returned from an action, anything in the ViewData.ModelState dictionary will be copied into TempData. When a ViewResultBase is returned from an action, any ModelState entries that were previously copied to TempData will be copied back to the ModelState dictionary.