T4MVC not generating an action - asp.net-mvc

I suspected there was some hidden magic somewhere that stopped what looks like actual method calls all over the place in T4MVC. Then I had a view fail to compile, and the stackTrace went into my actual method.
[Authorize]
public string Apply(string shortName)
{
if (shortName.IsNullOrEmpty())
return "Failed alliance name was not transmitted";
if (Request.IsAuthenticated == false || User == null || User.Identity == null)
return "Apply authentication failed";
Models.Persistence.AlliancePersistance.Apply(User.Identity.Name, shortName);
return "Applied";
}
So this method isn't generating in the template after all.
<%=Ajax.ActionLink("Apply", "Apply", new RouteValueDictionary() { { "shortName", item.Shortname } }, new AjaxOptions() { UpdateTargetId = "masterstatus" })%>
<%=Html.ActionLink("Apply",MVC.Alliance.Apply(item.Shortname),new AjaxOptions() { UpdateTargetId = "masterstatus" }) %>
The second method threw an exception on compile because the method Apply in my controller has an [Authorize] attribute so that if someone that isn't logged on clicks this, they get redirected to login, then right back to this page. There they can click on apply again, this time being logged in.
And yes I realize one is Ajax.ActionLink while the other is Html.ActionLink I did try them both with the T4MVC version.

Update: I see the problem. T4MVC only supports actions that return ActionResult, so it's not processing this particular action that returns string. You can fix this by changing it as follows:
[Authorize]
public ActionResult Apply(string shortName) {
if (shortName.IsNullOrEmpty())
return Content("Failed alliance name was not transmitted");
if (Request.IsAuthenticated == false || User == null || User.Identity == null)
return Content("Apply authentication failed");
Models.Persistence.AlliancePersistance.Apply(User.Identity.Name, shortName);
return Content("Applied");
}
Note how it returns an ActionResult, and calls 'return Content("...")' instead of directly returning strings.
Could you give more details on the compile exception that you are getting? I assume this is something you see in the browser and not in VS? Could you include the full text of the error?
Generally, the T4MVC calls through the MVC prefix are never supposed to call the actual action method. Instead, they call an overridden method in a derived class. Look for a generated file called something like AllianceController.generated.cs (under T4MVC.tt). You should see an overridden 'Apply' method in there, which just does what T4MVC needs.

Related

How to catch postpack result to Web Api

I know I have done some basic mistake, but I can't find solution by myself,
I have next form at ASP MVC 4
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "frmMy", data_bind = "submit: onSubmit", action = "/api/MyService/SaveUpload", enctype = "multipart/form-data" }))
{
<button type="submit" class="btn btn-default">Save</button>
}
and web api method
[HttpPost]
[AllowAnonymous]
public async Task<HttpResponseMessage> SaveUpload()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/");
var provider = new MultipartFormDataStreamProvider(root);
try
{
await Request.Content.ReadAsMultipartAsync(provider);
...
return new HttpResponseMessage(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
but after /api/MyService/SaveUpload call my page is make redirect to web api method.
How to prevent this behavior and catch method result on page.
This is happening because you are not telling the Beginform method what to do when the call is successful or unsuccessful.
There are a few things you can do to correct this.
Instead of directly calling the api from view, You can make a call to an action method of MVC controller that will in turn call the api. On a successful return, your action method can decide what you want to do with the view, as in redirect to a different view/action or show some kind of notification.
If you don't want to add another layer, you can use AJAX.BeginForm instead (I am not sure if html.beginform allows success handlers) and then you'll have to write a javascript success/error handler function (a normal js function) and pass that to Ajax.Beginform in ajaxoptions. So that it will execute that handler function based on what your API returns (success /error). In that javascript handler function, you can write script to show a success method to the user or whatever next logical step for your application is. More on Ajax.BeginForm ajax optionshere.

ASP.NET MVC AJAX onError handling

I am trying to delete item from table. There is Ajax link for it.
#Ajax.ActionLink("Delete", "DeleteConfirm", new { id = Model.ID }, new AjaxOptions {
HttpMethod = "POST", UpdateTargetId = "TableID", OnSuccess = "CloseDialog", OnFailure = "AlerDialog"
})
It calls DeleteConfirm method from controller with POST method. I made simple controller which should do something so ActionLink should catch error and run OnFailure function (to show alert dialog).
Controller:
public ActionResult DeleteConfirm(int id)
{
// code here
}
What to return from controller method so OnFailure function invokes?
OnError is fired when error happens on serverside. By error, I mean exception, and I think you can't pass exception message on clientside except of 500 Server error.
I think that good aproach is to have some CustomResponse class that your action will return.
In your case, something like:
Class DeletionResponse
{
public bool IsDeletionSuccesfull {get; set; }
public string Message {get; set;}
}
In DeleteConfirm action you create new response, which maybe needs to inheriteActionResult class(I'm not sure because I'm new to MVC). If some error ocures while deleting, set DeletionSuccesfull to false, and Message to message of exception, or some custom message.
On client side, the point is to examine success in OnSuccess handler, and then decide what to do.
Something like:
function handleResponse(deletionResponse){
if(deletionResponse.d.IsDeletionSuccesfull){
CloseDialog();
}
else{
AlertDialog(deletionResponse.d.Message);
}
}
The OnFailure will fire based off the status code of the result, so something like this would give the desired effect.
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError, "Reason for failure");
Also, not sure if it's related but shouldn't your OnFailure text be "AlertDialog" instead of "AlerDialog"?
EDIT: In your controller action you should be able to test that the request is being made via Ajax by using this Extension method MVC provides Request.IsAjaxRequest(). Note that there is no true way to check if a request is an Ajax request on the server, this method is utilizing the presence of a custom header jQuery sets for all ajax requests it makes, in other words don't use Request.IsAjaxRequest() in business logic.
Source for IsAjaxRequest() method
namespace System.Web.Mvc
{
public static class AjaxRequestExtensions
{
public static bool IsAjaxRequest(this HttpRequestBase request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
return (request["X-Requested-With"] == "XMLHttpRequest") || ((request.Headers != null) && (request.Headers["X-Requested-With"] == "XMLHttpRequest"));
}
}
}
how about throwing an exception?
public ActionResult DeleteConfirm(int id)
{
try
{
//make your call to the database here
return View();
}
catch (ExceptionType1 ex)
{
//log the details of your error for support purposes, alerting tracing etc.
ex.Message = "Nice message for user"
throw ex;
}
catch (ExceptionType2 ex)
{
//log the details of your error for support purposes, alerting tracing etc.
ex.Message = "Another nice message for user"
throw ex;
}
}
Your ajax call would then know it was a failure, and run the correct function.

Why does the previous page link appears in my address bar in MVC3 Razor application?

I am using MVC3/Razor. But I don't understand why the previous page url appears when i redirecToAction.
Scenario: Once the user clicks on Signup from home page like "Account/Signup", the he would get RedirectToAction("AcceptCondition") so once the use accepts the condition and post then the user would get RedirectToAction("Signup") where i will check if he is coming from "AcceptCondition" page then he can proceed otherwise user will go back to "AcceptCondition" page. So when I redirect user from Signup page to AcceptCondition page the url in the address bar appears like "http://localhost:55104/Account/Signup" instead of "http://localhost:55104/Account/AcceptCondition"
I know that i can send user directly to "Account/AcceptCondition" when he click on the signup but I just followed upper scenario.
public ActionResult Signup(string ru)
{
if ((ru == "/Account/AcceptCondition") || (ru == "/Account/Signup"))
{
return View();
}
else
{
return RedirectToAction("AcceptCondition");
}
}
[HttpGet]
public ActionResult AcceptCondition()
{
return View();
}
[HttpPost]
public ActionResult AcceptCondition(AcceptConditionViewModel acceptCondiViewModel)
{
if (acceptCondiViewModel.TermAndCondi == true)
{
return RedirectToAction("Signup",
"Account",
new { ru = "/Account/AcceptCondition" });
}
else
{
return View();
}
}
Signup GET checks refers, if not AcceptCondition, then redirects to AcceptCondition
IF it is AcceptCondition, then render the normal signup view.
AcceptCondition GET renders the AcceptCondition view
AcceptCondition POST does RedirectToAction("SIGNUP")
Signup POST creates the user (Assuming) then goes?
Sounds like the referrer check isn't right so /account/signup is rendered again?
Whats your code look like.
It appears that you are passing a parameter of 'returnUrl' in your first function, but reference it as 'ru' in both the function body and in your RedrectToAction call in the third function. Be sure you are staying consistent with your parameter names as this could be a cause of faulty logic and bad/not working redirects.

Should RenderAction be used with forms?

My setup:
Have a view for a route like: /Pages/Details/2
The page details view has <% Html.RenderAction("CreatePageComment", "Comments"); %> to render a comment form
Comment form posts to Comments/CreatePageComment
/Comments/CreatePageComment returns RedirectToAction when a comment is created successfully
This all works nicely
My question:
If there is a validation error, how should I return to /Pages/Detail/1 and show the error in the comment form?
If I use RedirectToAction, it seems validation is tricky; should I even be using the Post-Redirect-Get pattern for validation errors, instead of just returning?
If I return View() it kicks me back to showing the CreateComment.aspx view (with validation, but just a form on a white page), not the /Pages/Details/2 route that called the RenderAction.
If the PRG pattern should be used, then I think I just need to learn how to do validation while using PRG. If not — and to me this seems better handled by returning View() — then I don't know how to get the user returned to the initial view, showing the form errors, while using RenderAction.
This feels like the game where you tap your head and rub your belly at the same time. I wasn't good at that one either. I'm new at MVC, so that's likely the problem here.
I believe the answer is to use TempData, for example:
In my view (/Steps/Details) I have:
<!-- List comments -->
<% Html.RenderAction("List", "Comments", new { id = Model.Step.Id }); %>
<!-- Create new comment -->
<% Html.RenderAction("Create", "Comments", new { id = Model.Step.Id }); %>
In my comments controller I have my POST method:
// POST: /Comments/Create
[HttpPost]
public ActionResult Create([Bind(Exclude = "Id, Timestamp, ByUserId, ForUserId")]Comment commentToCreate)
{
if (ModelState.IsValid)
{
//Insert functionality here
return RedirectToAction("Details", "Steps", new { id = commentToCreate.StepId });
}
//If validation error
else
{
//Store modelstate from tempdata
TempData.Add("ModelState", ModelState);
//Redirect to action (this is hardcoded for now)
return RedirectToAction("Details", "Steps", new { id = commentToCreate.StepId });
}
}
Also in the comments controller is my GET method:
//
// GET: /Comments/Create
public ActionResult Create(int id)
{
if (TempData.ContainsKey("ModelState"))
{
ModelStateDictionary externalModelState = (ModelStateDictionary)TempData["ModelState"];
foreach (KeyValuePair<string, ModelState> valuePair in externalModelState)
{
ModelState.Add(valuePair.Key, valuePair.Value);
}
}
return View(new Comment { StepId = id });
}
This works great for me, but I'd appreciate feedback on whether this is a good practice, etc.
Also, I noticed that MvcContrib has a ModelStateToTempData decoration that appears to do this, but in a cleaner way. I'm going to try that next.

ASP.NET MVC Ajax post to Action requiring authentication returns login view when user session has timed out

I am using the Ajax.BeginForm to create a form the will do an ajax postback to a certain controller action and then the response view is inserted into the UpdateTargetId.
using (Ajax.BeginForm("Save", null,
new { userId = Model.UserId },
new AjaxOptions { UpdateTargetId = "UserForm" },
new { name = "SaveForm", id = "SaveForm" }))
{
[HTML SAVE BUTTON]
}
Everything works great except when the Users session has timed out and then they are redirected back to the login page. The login page then gets returned from the Ajax call because of the Authorize attribute and the html gets loaded into the UpdateTargetId and therefore I end up with the login page html within the user profile page (at the Target Id). My controller action looks like this:
[Authorize]
public ActionResult Save(Int32 UserId)
{
//code to save user
return View("UserInfoControl", m);
}
How can I solve this problem?
UPDATE (2011-10-20):
Found this post from Phil Haack about this exact issue - http://haacked.com/archive/2011/10/04/prevent-forms-authentication-login-page-redirect-when-you-donrsquot-want.aspx. I have not gotten a chance to digest or implement his solution yet.
I think that you can handle the authorization issue from inside the action.
First remove [Authorize], the use this code:
public ActionResult Save(Int32 UserId)
{
if (!User.Identity.IsAuthenticated)
{
throw new Exception();
}
//code to save user
return View("UserInfoControl", m);
}
Then you can put a OnFailure condition in your AjaxOptions and redirect your page or something.

Resources