Update:
The issue I had is model validation, but I found FluentValidation already taking care of this when the model passed to Action, so my question is not completely correct and probably misleading. I will close it (if find how to do it).
I have the following code:
[HttpPost]
public async Task<IActionResult> Create(CreateAccountViewModel model)
{
try
{
**await Mediator.Send(_mapper.Map<CreateAccountCommand>(model));
return RedirectToAction("Index");**
}
catch (ValidationException vldEx)
{
foreach (var vldError in vldEx.Errors)
{
ModelState.AddModelError(vldError.PropertyName, vldError.ErrorMessage);
}
return View(model);
}
}
The text inside the try is different for different actions of a controller, but everything else is the same. Is it possible to extract common code into function in the base class?
I tried using Func, but can't' figure out the right way to do it.
I am looking for something like this:
[HttpPost]
public async Task<IActionResult> Create(CreateAccountViewModel model)
{
FunctionWithCommonCode(x =>
{
await Mediator.Send(_mapper.Map<CreateAccountCommand>(model));
return RedirectToAction("Index");
});
}
No need for delegates or anything complicated just creates a base method that you call from each action and pass in the string you need, for example:
private async Task<IActionResult> CreateBase(CreateAccountViewModel model, string action)
{
try
{
await Mediator.Send(_mapper.Map<CreateAccountCommand>(model));
return RedirectToAction(action);
}
catch (ValidationException vldEx)
{
foreach (var vldError in vldEx.Errors)
{
ModelState.AddModelError(vldError.PropertyName, vldError.ErrorMessage);
}
return View(model);
}
}
And an action method would look like this:
[HttpPost]
public async Task<IActionResult> Create(CreateAccountViewModel model)
{
return await CreateBase(model, "Index");
}
I can't get this to compile - there are way too many dependencies on code that you did not provide. If you can provide Minimal, Complete and Verifiable Example (https://stackoverflow.com/help/minimal-reproducible-example) with enough stubbed out classes to get your example code to compile (without dependencies on code you don't provide), then I'll update my answer.
But, if I'm understanding your question, what you need to do is create a method that does everything except your **star-ed** code, and pass the star-ed code in as a parameter with the appropriate delegate type. Sorry, but because I couldn't get things to compile, I'm not sure I have the asyncs and awaits in exactly the right place. But, this should give you a head start.
So, you start with a worker function that provides all the boiler-plate:
private async Task<IActionResult> WebWorker<TModel> (TModel model, Func<TModel, Task<IActionResult>> workToDo) {
try {
return await workToDo(TModel model);
} catch (ValidationException vldEx) {
foreach (var vldError in vldEx.Errors) {
ModelState.AddModelError(vldError.PropertyName, vldError.ErrorMessage);
}
return View(model);
}
}
That function takes in your mode and a delegate of the appropriate Func<> type.
Then you call it something like this:
[HttpPost]
public async Task<IActionResult> Create(CreateAccountViewModel model) {
return await WebWorker<CreateAccountViewModel>(model,
m => {
Mediator.Send(_mapper.Map<CreateAccountCommand>(m));
return RedirectToAction("Index");
});
}
Note that the second parameter to the WebWorker function is a Lambda whose signature matches that of the Func declared as the second parameter to that function.
Related
This function gives an error:
[HttpPost]
public Task<IActionResult> Something (string value)
{
Student student = new Student();
if (value == "LOW")
{
return NotFound();
}
return Ok();
}
This function works fine:
[HttpPost]
public async Task<IActionResult> Something (string value)
{
Student student = new Student();
if (value == "LOW")
{
return NotFound();
}
return Ok();
}
When I am trying to return NotFound(); or return Ok(); when I am not including keyword asnyc before Task as shown above, it gives me error. However, when i added the async keyword in front of Task, it does not shows any error in the compiler. Can you tell me what is causing this?
The async keyword tells the compiler that while the method returns a Task, the code within the method is expected to run in an asynchronous manner and internally await something. The compiler will look for one or more awaits and modify the structure of the method to wrap functionality around tasks and continuations of tasks. So ultimately the compiler will wrap the returned result in a Task<T> for you.
Basically, your logic can focus on the value you want to return, rather than focus on the structure of asynchronicity. The async and await keywords exist to make that part simpler.
So both of these methods actually have a problem:
The first method is not async, so the compiler isn't expecting to modify it in any way. Instead, the compiler is expecting it to return what it says it will return, which is a Task<IActionResult>. It doesn't return that, so the compiler produces an error.
The second method is async, but nowhere in that method do you await anything. The compiler can make this work, but produces a warning that nothing is being awaited in this async method.
So both are wrong. Since your method doesn't do anything asynchronous, don't make it async:
[HttpPost]
public IActionResult Something(string value)
{
Student student = new Student();
if (value == "LOW")
{
return NotFound();
}
return Ok();
}
You only need to make the method async (and wrap a Task<T> around the return type) if the method internally needs to await something.
If you do not want async...then your (first) code should be:
public IActionResult Something (string value)
(no Task)
I've 7 actions in my controllers. I've refactored them an was able to isolate the common portion.
public IActionResult Get()
{
var response = CommonCode();
}
public IActionResult Get(guid id)
{
var response = CommonCode();
}
public IActionResult Post(ViewModel vm)
{
var response = CommonCode();
}
This is where I refactored the common code.
provate IActionResult CommonCode()
{
if(userHasNoPermission())
return Forbid();
if(IdProvidedDoesntExist())
return BadRequest();
//...
}
When I look inside the response, I see only one method: ExecuteResultAsync().
Is there a way to retrieve the HTTP code that I sent inside the helper method?
I'd like for instance to stop the processing if it's 500, retrieve the message to add to the ModelState* if it's 400, but proceed if it's OK.
There are a few "filthy" ways you can do it without a case statement. The status code is actually in the result but IActionResult and ActionResult hate being cast to a common type.
This example takes a IActionResult from any common result and rips the status code out using reflection. If you don't mind doing it that way it saves requiring the case statement to pattern match. The same can be done for the content.
public static HttpStatusCode GetHttpStatusCode(IActionResult functionResult)
{
try
{
return (HttpStatusCode)functionResult
.GetType()
.GetProperty("StatusCode")
.GetValue(functionResult, null);
}
catch
{
return HttpStatusCode.InternalServerError;
}
}
The return of CommonCode is simply some type of IActionResult. The "result" is not actually a "response". That comes later when the action is fully returned back into the request pipeline (which has not occurred yet when you're calling the method directly in code). As result, concepts like HTTP status code aren't even relevant yet. If you return something like StatusCodeResult, that's technically only a suggested status code. If there's an exception later in the request pipeline or some piece of middleware explicitly changes the status code for some reason, it will be different.
Long and short, you're trying to conflate two unrelated things together. I think you simply want to know what happened in CommonCode, and think the HTTP status is the best way to determine that. In actuality, you'd be better served by returning a tuple or doing something like pattern matching:
With a tuple, you can essentially return more than one thing from your CommonCode method. For example, you could do something like:
private (int, IActionResult) CommonCode()
{
if(userHasNoPermission())
return (403, Forbid());
if(IdProvidedDoesntExist())
return (400, BadRequest());
//...
}
Then:
public IActionResult Get()
{
(var status, var response) = CommonCode();
// branch on `status`
}
Or, with pattern matching:
public IActionResult Get()
{
var response = CommonCode();
switch (response)
{
case ForbidResult forbid:
// do something for forbidden
break;
case BadRequestResult badRequest:
// do something for bad request
break;
}
}
I had a similar problem returning Ok() and NotFound(). I
was able to get the status code using the IStatusCodeActionResult interface.
((IStatusCodeActionResult)response).StatusCode;
You could return the statuscode itself from the private method. for example
private IActionResult CommonCode() {
if(userHasNoPermission())
return StatusCode(401);
if(IdProvidedDoesntExist())
return StatusCode(400);
//... }
then in your method just check for the status code
public IActionResult Post(ViewModel vm)
{
var response = CommonCode();
if (this.HttpContext.Response.StatusCode == 418)
Console.WriteLine("I'm a teapot")
else {
return response;
}
}
I am using MVC 4, and I have the following:
[HttpGet]
public ActionResult SomeForm(modelType model = null)
{
if(model != null)
return View(model);
return View(getModelFromSomewhere());
}
[HttpPost]
public ActionResult SomeForm(modelType model)
{
if(isValid())
doSomething();
else
return SomeForm(model) // Line in Question
}
However, obviously, I am getting an ambiguous method error on "Line in Question". I'm wondering if anyone has an elegant solution to be able to specify to return specifically the [Get] method of the same name?
Thank you!
You can't have methods with the same signature as you've pointed out already. In C# it also means you can't distinguish functions by just return type - so you must use different names if parameters are same (again default values are ignored when matching of signatures).
If you want separate GET and POST handler - use different names of methods and ActionNameAttribute to name the action:
[HttpGet]
[AciontName("SomeForm")]
public ActionResult SomeFormGet(modelType model = null) ...
[HttpPost]
[AciontName("SomeForm")]
public ActionResult SomeFormPost(modelType model) ...
make it compile...
[HttpPost]
public ActionResult SomeForm(modelType model, FormCollection fc)
{
if(isValid())
doSomething();
else
return SomeForm(model) // Line in Question
}
If you are using http get method you are waiting that browser will send you serialized model as a string query. For example, you are waiting url like
http://example.com?name=Andrew&type=Worker&field1=param1&field2=param2&....
It is common practice to use only id in your get method, so you can do it like this:
[HttpGet]
public ActionResult SomeForm(int id)
{
var model = FindModelById(id);
if(model != null)
return View(model);
return View(getModelFromSomewhere());
}
If you are looking for an elegant solution, it will be more elegant in architecture
I have an Asynchronous controller implementation as follows,
public Task<ActionResult> UpdateUser(ProfileModel model)
{
return Task.Factory.StartNew(showMethod).ContinueWith(
t =>
{
return RedirectToAction("ViewUser","UserProfile");
});
}
However I am unable to redirect to the action as I am keep on getting the error,
Cannot implicitly convert type, System.Threading.Taska.Task<Sytem.Web.Mvc.RedirectToRouteResult> to System.Threading.Taska.Task<Sytem.Web.Mvc.ActionResult>
However I really want to redirect to the mentioned Action, how can I do that.
For people who come here looking for an answer, the newer versions of .NET make things simpler. Use the keyword async in the definition of the method and you can clear up the body.
public async Task<ActionResult> UpdateUser(ProfileModel model)
{
return RedirectToAction("ViewUser","UserProfile");
}
You need to change the return type of UpdateUser action from Task<ActionResult> to Task<RedirectToRouteResult>
public Task<RedirectToRouteResult> UpdateUser(ProfileModel model)
{
return Task.Factory.StartNew(showMethod).ContinueWith(
t => {
return RedirectToAction("ViewUser","UserProfile");
});
}
Or you could explicitly set the generic type argument of ContinueWith method with ActionResult, like this:
public Task<ActionResult> UpdateUser(ProfileModel model)
{
return Task.Factory.StartNew(showMethod).ContinueWith<ActionResult>(
t => {
return RedirectToAction("ViewUser","UserProfile");
});
}
Use this example:
public async Task<ActionResult> Login(LoginModel model) {
//You would do some async work here like I was doing.
return RedirectToAction("Action","Controller");//The action must be async as well
}
public async Task<ActionResult> Action() {//This must be an async task
return View();
}
I have some server side code that runs some rules and than will redirect to differing controllers and actions depending on the outcome of the rules.
Whats the best way to represent a Controller and Action combo without doing the RedirectToAction("Action","Controller"); because I dont actually want to issue the redirect right after the method executes.
so i want to do something like
public SomeObject ApplyRules(){
if(somecondition){
return(Action1,Controller1);
else if(someotherCondition){
return(Action2,Controller2);
}
}
I can create a class that has two string properties (Controller, and Action) but I have to think there is some built in class that I should be using to represent this.
You can use RedirectToRouteResult type and pass it around. You don't have to create new objects or tuples.
[NonAction]
private RedirectToRouteResult ApplyRules(){
if(condition1){
return RedirectToAction("Action1");
}
else if(condition2){
return RedirectToAction("Action2");
}
else return RedirectToAction("Action3")
}
public ActionResult MyAction()
{
RedirectToRouteResult result = ApplyRules();
// As long as you don't return your "result" from THIS METHOD
// redirect won't happen:
// return result;
return View();
}
As long as you don't return RedirectResult from your ACTION method, you can use it as return parameter of your ApplyRules() or any other method.
If your ApplyRules() method is outside of Controller, you cannot use RedirectToAction helper. In that case you can generate RedirectToRouteResult like this:
var routeValues = new System.Web.Routing.RouteValueDictionary();
routeValues.Add("controller", "Account");
routeValues.Add("action", "Register");
var result = new RedirectToRouteResult(routeValues);
I don't normally use tuples, but this could be a situation where it makes sense:
public Tuple<string,string> ApplyRules(){
if(somecondition)
{
return(new Tuple<string, string>("Action1","Controller1"));
}
else if(someotherCondition)
{
return(return(new Tuple<string, string>("Action2","Controller2")););
}
}
Learn more about tuples at this msdn post.
You would of course do your redirect, something like this:
public void DoMyRedirect(Tuple<string,string> route)
{
return RedirectToAction(route.Key1, route.Key2);
}