Template method for MVC Controller - asp.net-mvc

I have a lot of controllers in an application.
Controllers called from UI with JS/ajax.
Almost all of them use similar "template". Please see the code sample.
Check income model for null.
Check model is valid.
Try catch with action.
Difference only in error messages, names and which injected helper used.
Is it possible to reduce repeat code?
Some kind of DRY?
How to achieve this? Template method, delegates, use MVC specific things?
[HttpPost]
public async Task<IActionResult> TestMethod([FromBody] TestModel model)
{
if (null == model)
{
return StatusCode((int)HttpStatusCode.BadRequest, "Empty model provided");
}
if (false == ModelState.IsValid)
{
var message = "Not all parameters provided correctly.";
_logger.WriteWithCallerAndMethodName(LogLevel.Debug, nameof(TestController), nameof(TestMethod), message);
return StatusCode((int)HttpStatusCode.BadRequest, message);
}
try
{
var result = await _testHelper.CreateDataAsync(model);
return Ok(result);
// return PartialView("_TestPart", result);
}
catch (Exception ex)
{
var message = $"Can't retrieve data. Error: ({ex.Message})";
_logger.WriteWithCallerAndMethodName(ex, nameof(TestController), nameof(TestMethod), message);
return StatusCode((int)HttpStatusCode.InternalServerError, message);
}
}

Related

Return to original view from MVC action filter

Im working on a asp.net core website and im trying to make som global validation exception handling using Filters. The backend can at random places throw fluentapi ValidationException and I want to catch these and show the error messages to the user. This filter only cares about ValidationExceptions. All other exceptions will be handled later..
Instead of using a try/catch in every post action in all my controllers, I want to use a filter that catches only ValidationExceptions, add the errors to the ModelState and then return to the original view with the updated ModelState.
I have tried many things but every time I just get a blank page after the filter finishes. I can easily set a new RedirectToRouteResult witht the controller and action from the context. But then I dont have the ModelState and values the user entered..
public class PostExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.Exception is FluentValidation.ValidationException)
{
var ex = context.Exception as FluentValidation.ValidationException;
context.Exception = null;
context.HttpContext.Response.StatusCode = 200;
context.ExceptionHandled = true;
foreach (var item in ex.Errors.ToList())
{
context.ModelState.AddModelError(item.PropertyName, item.ErrorMessage);
}
// Done with the stuff I want.
// Now please go back to the original view with the updated modelstate and values
}
else if (context.Exception is UnauthorizedAccessException)
{
// Do something else...
}
else
{
// Do something else...
}
base.OnException(context);
}
}
You cannot access the particlar Model(related to Action Method) in Exception Filters. So you have to handle the error at Controller level if you want to add Errors to model.
try
{
//Do something
}
Catch(Exception e)
{
ModelState.AddModelError(string key, string errorMessage);
Return View(model)
}
The error message will present itself in the <%: Html.ValidationSummary() %> in your View
Without try-catch blocks you won't know if exception occured in Action Method, So that you can add Custom Errors to Model.

MVC Return view from another method

i am unable to end response in case of some condition
eg below (in Upload Action Method), if Logerror method invoked i just want to return view(browser) without further action. i.e return from Upload Action Method.
Plase find modified question what i am trying to achive,
In case of error i want to return view by stopping all further opeartion
public ActionResult Index()
{
return View();
}
public ActionResult Upload()
{
int i=1;
DoSomethingFirst();
//if LogError i dont want execute code below, rather it should end responce
//should not reach here
string s="This should not be executed in case of LogError()";
return View("Index");
}
public void DoSomethingFirst()
{
try{
DoSomethingSecond();
}
catch(exception ex)
{
LogError();
}
}
public void DoSomethingSecond()
{
try{
DoSomethingThird();
}
catch(exception ex)
{
LogError();
}
}
public void DoSomethingThird()
{
try{
DoSomethingother();
}
catch(exception ex)
{
LogError();
}
}
private LogError()
{
Viewbag.Error="Error details";
return View("Index");
}
This doesn't return a result from the current method:
DoSomething();
But this does:
return DoSomething();
If you want to end execution of the current method, you need to do something which exits the method. Basically, either return from the method or throw an exception. Since DoSomething returns a result, presumably you want to return that result. So simply add a return statement when invoking the method.
i tried wit RedirectToAction("Index");
Same issue. You'd need to return the result:
return RedirectToAction("Index");
Edit: Based on your edit to the question, the overall concept still remains. Focusing on this part of your code here:
var s = DoSomethingFirst();
//if true i dont want execute code below, rather it should end responce
//should not reach here
In order to exit the method, any method in C#, you need to either return or throw. So the first question is... Which do you want to do here? If you want to return a redirect, for example, then return a redirect:
return RedirectToAction("SomeAction");
If you want to return the default view, return that:
return View();
If you want to throw an exception:
throw SomeException("Some Message");
The choice is yours. You just need to define:
What you want this method to return or throw under this condition.
How will you know the condition.
For that second point, your code comment says:
//if true ...
Does this mean DoSomethingFirst() returns a bool indicating success or failure? Then that would be a simple if statement:
if (!DoSomethingFirst())
return View();
Another Edit: Based on your comment below:
Inside LogError mehod called by any child method in action method, i want to update view with error message and end the operation without further operation
How will your Update method know that something it invoked internally called LogError()? What information does DoSomethingFirst() return to indicate this fact? Currently it doesn't. Your various DoSomething methods are all swallowing exceptions, which means they are internally handling exceptions so that consuming code doesn't know about them.
If you want consuming code to know about an exception, re-throw that exception. For example:
public void DoSomethingFirst()
{
try
{
DoSomethingSecond();
}
catch(exception ex)
{
LogError();
throw; // <-- this will re-throw ex without modifying it
}
}
This returns information from DoSomethingFirst(), specifically the fact that an error occurred. Your consuming code can then check for that error:
try
{
DoSomethingFirst();
}
catch (Exception ex)
{
// You should *probably* do something with ex too. So far all of your "logging" has been ignoring the actual error.
return View();
}
Regardless of the structure you build, the basics don't change. In order for consuming code to know something about the code it invokes, that invoked code has to expose that information. In order to end execution of a method, you have to either return or throw. Don't hide exceptions from consuming code if you want consuming code to respond to those exceptions.

Why would an MVC Post (ViewModel) not return updated form using Async?

Here's the Controller:
[HttpPost]
public async Task<ActionResult> Index(EmailService em)
{
if (ModelState.IsValid)
{
await em.Send();
}
return View(em);
}
Here's the ViewModel Send; where "this" is an instance of the EmailService class.
public async Task<EmailService> Send()
{
msg = new MailMessage(From, To);
msg.Body = Body;
msg.Subject = Subject;
SetHost();
try
{
await Server.SendMailAsync(msg);
status = Status.Success;
Sent = DateTime.Now;
return this;
}
catch (Exception iox)
{
status = Status.Failed;
IOX = iox;
return this;
}
}
I set a breakpoint here in the controller and saw the status updated correctly, meaning the data was "on it's way to the view as it should have been": "em" did have the data in it! On this statement.
return View(em);
But the view remains in same state just prior to post? Notice the time stamp and the field below it?
Time to debug the packets, by pressing F12 on the browser on post with a break point set on entry to the controller so it won't respond... This is the inbound data:
To:somebody#email.com
From:somebody#email.com
Subject:This is a test
Body:This is only a test
Sent:1/1/0001 12:00:00 AM
status:Initialized
This was value of "em" on way out setting break on controller's return View(em):
To:somebody#email.com
From:somebody#email.com
Subject:This is a test
Body:"This is only a test" string
Sent:{11/24/2014 6:48:49 PM}
status:Success
Watching the 200 response from browser F12 Network side showed this "old" form data!
To:somebody#email.com
From:somebody#email.com
Subject:This is a test
Body:This is only a test
Sent:1/1/0001 12:00:00 AM
status:Initialized
Any help would be appreciated, it looks like MVC pulled the wrong copy to return after the Asynch controller method returned!
Because when you return a view, the html helpers set the value of the controls from the values in ModelState, not the value in the model. The reason for that behavior is explained in this answer
In order to render the updated Sent and Status properties, you will need to clear ModelState for those properties.
[HttpPost]
public async Task<ActionResult> Index(EmailService em)
{
if (ModelState.IsValid)
{
ModelState.Clear(); // or `ModelState.Remove("Sent"); ModelState.Remove("Status")`
em = await em.Send();
}
return View(em);
}

repeat common error info logic in ActionFilterAttribute

I'm implementing a web API using the REST for ASP.NET MVC framework (MVC 2). I want to encapsulate this code, ideally in an ActionFilterAttribute (?), so that I can decorate specific actions that always perform this same logic:
if (!ModelState.IsValid) {
return View(
new GenericResultModel(){ HasError=True, ErrorMessage="Model is invalid."});
}
I really don't want to have to copy and paste this boilerplate code into every controller action where I need to do this.
In this web API scenario, I need to be able to do something like this so that the caller can get a result back in JSON or POX form and see that there is an error. In an ASPX view, obviously I wouldn't need something like this as the validation controls would take care of notifying the user of a problem. But I do not have an ASPX view - I am only returning JSON or POX data serialized from my model.
I've started with this code in an ActionFilter but am not sure what to do next (or if it is even the right starting point):
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
bool result = filterContext.Controller.ViewData.ModelState.IsValid;
if (!result)
{
GenericResultModel m = new GenericResultModel() { HasError = true };
// return View(m)
// ?????
}
base.OnActionExecuting(filterContext);
}
How do I accomplish this?
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Notice that the controller action hasn't been called yet, so
// don't expect ModelState.IsValid=false here if you have
// ModelState.AddModelError inside your controller action
// (you shouldn't be doing validation in your controller action anyway)
bool result = filterContext.Controller.ViewData.ModelState.IsValid;
if (!result)
{
// the model that resulted from model binding is not valid
// => prepare a ViewResult using the model to return
var result = new ViewResult();
result.ViewData.Model = new GenericResultModel() { HasError = true };
filterContext.Result = result;
}
else
{
// call the action method only if the model is valid after binding
base.OnActionExecuting(filterContext);
}
}

Implementing IDataErrorInfo with SubSonic 2.2

I am moving 1 project, just the data tier, the project is using MVC 1.0 and acess mdb :S
Now I am moving to SubSonic + Sql server and all is fine, except when I try to implement to my class IDataErrorInfo for validation messages, I get always 2 times every error message
I have a table class generated by subsonic:MyTable, then I extend it.
public partial class myTable : IDataErrorInfo{
public string this[string columnName]{
get{
switch (columnName.ToUpperInvariant()){
case "MYFIELD":
if (string.IsNullOrEmpty(myField)){
return "Incorrect MyField";
}
break;
case "ANOTHER":
if (string.IsNullOrEmpty(myField)){
return "Incorrect Another";
}
break;
}
return "";
}
}
public string Error{
get{
return "";
}
}
}
In My Controller I add to my post action this code:
public class mycontroller...{
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult myAction(int id, MyTable data)
{
try
{
UpdateModel(data, new[] { "MyField","Another" });
data.Save();
return RedirectToAction("Admin");
}
catch (Exception ex)
{
//ViewData["Error"] = ex.Message;
return View(data);
}
}
My view have a summary generated as Html.ValidationSummary("Attention:")
When I get invalid data My summary get 2 times the error as it:
Attention:
Incorrect MyField
Incorrect MyField
Incorrect Another
Incorrect Another
I don't want to rewrite the validation form, here is a lot of views (about 130). I think the problem is in some place in subsonic, but I can't get where :S, Please help me :)
Best regards and thanks in advance.
no way to catch this error :(
Which version of SubSonic are you using? IIRC, Save() in v2.0.3 could call the validation method twice.

Resources