Cannot get MVC 5 Attribute Routing to work - c#

I am working on an MVC5 application with C# back-end. I am attempting to get change /Controller/Action?parameter=value show as /Controller/Action/Value.
Currently I have an action link on a page setup in hopes to be able to view the user's profile info:
#{ var test = new RouteValueDictionary {{"username", "testman"}}; }
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-md-2" style="border-right:1px solid #EEE; min-height: 250px;">
Sidebar 1
</div>
<div class="col-md-8">
test link
</div>
<div class="col-md-2" style="border-left:1px solid #EEE; min-height: 250px;">
Sidebar 2
</div>
</div>
And here is the Action:
[RequireRequestValue(new[] { "username" })]
[Route("Test/{username}")]
public ActionResult Test(string username)
{
var route = RouteData.Route;
try
{
ViewBag.Page = 9;
ViewBag.Title = "Testing";
ViewBag.Message = username;
return View();
}
catch (Exception ex)
{
var error = new HandleErrorInfo(ex, "Home", "Test");
ViewBag.errorMessage = ex.Message;
return View("Error", error);
}
}
I have added routes.MapMvcAttributeRoutes(); to my RouteConfig.cs file.
If I do not add the [Route("Test/{username}")] Attribute to my action, the link renders like this: Home/Test?username=testman. If I add the Attribute the link renders to my liking: Home/Test/testman.
Unfortunately, I get this server 500 error when I click on the link, and this is where I have been stuck:
No matching action was found on controller '<project>.Controllers.HomeController'.
This can happen when a controller uses RouteAttribute for routing, but no action on
that controller matches the request.
For fun I even tried creating an explicit ActionResult to see if I could get something different, but no luck there.
I am obviously missing something...can anyone help me get on the right track?
Thanks!

Related

Can I show the Error Handling in same page ? ASP.NET Core

I can control the errors with Error Handling but show a different page.
Example:
But this is not my page, I want this error in there in this layout:
And this is my code:
Startup.cs:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseStatusCodePagesWithReExecute("/Error/{0}");
}
ErrorController.cs;
[Route("Error/{statusCode}")]
public IActionResult HttpStatusCodeHandler(int statusCode)
{
var statusCodeResult = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
switch (statusCode)
{
case 404:
ViewBag.ErrorMessage = "Sorry, the page you requested could not be found";
ViewBag.Path = statusCodeResult.OriginalPath;
ViewBag.QS = statusCodeResult.OriginalQueryString;
//ViewBag.StatusCode = statusCode;
break;
}
return View("Index");
}
[Route("Error")]
[AllowAnonymous]
public IActionResult Error()
{
var exceptionDetails = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
ViewBag.ExceptionPath = exceptionDetails.Path;
ViewBag.ExceptionMessage = exceptionDetails.Error.Message;
ViewBag.StackTrace = exceptionDetails.Error.StackTrace;
return View("Error");
}
Error.cshtml;
<h3>
Exception Details:
</h3>
<div class="alert alert-danger">
<h5>Exception Path</h5>
<hr />
<p>#ViewBag.ExceptionPath</p>
</div>
<div class="alert alert-danger">
<h5>Exception Message</h5>
<hr />
<p>#ViewBag.ExceptionMessage</p>
</div>
<div class="alert alert-danger">
<h5>Exception Stack Trace</h5>
<hr />
<p>#ViewBag.ExceptionStackTrace</p>
</div>
How can I show errors in different layouts?
Well yes, you can use your layout page if you want by adding this to the Error page
#{
Layout = "~/Views/Shared/_YourLayoutPage.cshtml";
}
There is something to consider though - what if the error occurs within your layout page?
You can also catch your errors and display them on the page itself (during execution), but you'll need to catch the errors you might expect and display those. The error you provide as example is not an error you expect, so this error does belong to it's specific error page

Redirecting parent page from Html.renderAction child without using Ajax, Java, Jquery or such

I have a problem where I have a form in a Html.RenderAction and after submitting the form I have to reload the parent but I keep getting "Child actions can not perform redirect actions". So how can I solve it without Ajax etc.
In my parent I have:
#{
var UserReviewExist = Model.Reviews.FirstOrDefault(x => x.AspNetUser.UserName == Name.AspNetUser.UserName);
}
#{if (UserReviewExist == null)
{
Html.RenderAction("ReviewCreate", "Reviews", new { BookID = Model.Id });
}
}
My RenderAction View contains this:
#model Trigger_Happy_Bunnies.Models.Review
#{
Layout = null;
}
#{
if (true)
{
Trigger_Happy_Bunnies.Models.Review newReview = new Trigger_Happy_Bunnies.Models.Review();
<div style="border:1px black">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
and ends with
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
</div>
}
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
And lastly I have this in my controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ReviewCreate([Bind(Include = "Id,BookId,UserId,Text,Title,Rating,IsActive,IsReported,ReportedBy,ReportReason,ModifiedDate,ModifiedBy,CreatedDate")] Review review)
{
if (ModelState.IsValid)
{
db.Reviews.Add(review);
db.SaveChanges();
return View("~/Views/Reviews/ReviewCreate.cshtml");
}
ViewBag.UserId = new SelectList(db.AspNetUsers, "Id", "Email", review.UserId);
ViewBag.BookId = new SelectList(db.Books, "Id", "UserId", review.BookId);
return PartialView();
}
So how can I update the parent view when submitting the form?
I'm not sure what your issue is here. A child action merely dumps its response into the view. So at the end of the day, whether you used a child action, a partial or just plopped the code right in the view, you just have a one HTML document that includes a form.
Calling Html.BeginForm with no parameters says basically that it should use the current action, but even in the context of child action, that's still going to be the main action being rendered. So, your form will post to that main action, not your child action.
That's how it should be. You cannot post to a child action, because that makes no sense in the context of a web page. Technically, you can as long as it's not marked as [ChildActionOnly], but the entire page will change to the partial view that's returned as the response, sans layout. If you want to replace just the area that was rendered via the child action, you must submit an AJAX request that returns the partial response and manually replace the appropriate node in the DOM with that.
In other words, that's why a child action can't redirect. It's not a true action and it hasn't been routed to. It's not rendered until the response preparation phase, and by that point, there's already data in the response, preventing any changes, like a redirect. If you need to redirect after the post of the form, you should have that already in place, just make sure your main action has a version that handles post, and redirect from there.

Issue sending data to PartialView C# MVC

I'm trying to build an inbox that is very similar to facebooks message inbox, where you have a list of conversations(I only want a list of a message title) and when you click the conversation or message title in my situation, I want the whole message to be rendered next to it in a partial view.
Here's my Inbox view:
#model BlocketProject.Models.ViewModels.ProfilePageViewModel
#{
ViewBag.Title = "Inbox";
}
<h2>Dina meddelanden:</h2><br />
<div class="left">
<table id="messageTable">
#foreach (var message in Model.UserMessages)
{
<tr>
<td>
<button type="submit" class="messageButton">
#if (message.Unread == true)
{
<h4 style="font-weight:bold;">#message.MessageTitle</h4>
}
else if (message.Unread == false)
{
<h4>#message.MessageTitle</h4>
}
</button>
</td>
</tr>
}
</table>
</div>
<div class="right">
#Html.Partial("ReadMessage")
</div>
When I click this message-element that is a button, I want to pass that messageId to the PartialView ReadMessage:
#model BlocketProject.Models.DbClasses.DbMessages
<h2>#Model.MessageTitle</h2><br />
<p>#Model.MessageText</p>
and the controller looks like this:
[HttpPost]
public ActionResult Inbox()
{
var allMessages = ConnectionHelper.GetAllMessages(ConnectionHelper.GetUserByEmail(User.Identity.Name).UserId);
var model = new ProfilePageViewModel();
model.UserMessages = allMessages;
return View("Inbox", model);
}
[HttpPost]
public ActionResult ReadMessage(int messageId)
{
var model = ConnectionHelper.GetMessageByMessageId(messageId);
return PartialView("ReadMessage", model);
}
I've tried passing the messageId through a post as you can see in my controller, but then the partialView is returned as a new page and I simply want to render it in the Inbox view.
Any ideas?
EDIT:
Jonesy's answer fixed my problem when I edited it like this:
Controller:
public ActionResult ReadMessage(int messageId)
{
var model = ConnectionHelper.GetMessageByMessageId(messageId);
return PartialView("ReadMessage", model);
}
View:
<div class="left">
<table id="messageTable">
#foreach (var message in Model.UserMessages)
{
<tr>
<td>
#using (Ajax.BeginForm("ReadMessage", new { #messageId = message.MessageId }, new AjaxOptions { UpdateTargetId = "showMessage" }, FormMethod.Post))
{
<button type="submit" class="messageButton">
#if (message.Unread == true)
{
<h4 style="font-weight:bold;">#message.MessageTitle</h4>
}
else if (message.Unread == false)
{
<h4>#message.MessageTitle</h4>
}
</button>
}
</td>
</tr>
}
</table>
</div>
<div class="right" id="showMessage">
#Html.Partial("ReadMessage", new BlocketProject.Models.DbClasses.DbMessages())
</div>
Razor is run on the server, before the page is rendered. Once the page is on the client, and they can click a message, the concept of the PartialView is gone - it's all just one HTML page.
The easiest way for you to do this is to use an Ajax.BeginForm where your button is, and on click, update an element with a partial view retrieved from the server. Something like:
#using(Ajax.BeginForm("ReadMessage", "Messages", new AjaxOptions() { UpdateTargetId = "showMessage" })) {
//...
}
//...
<div class="right" id="showMessage">
//ReadMessage partial rendered on button click
</div>
A little method that could help you :
protected ActionResult View(string viewName, object model)
{
if (ControllerContext.IsChildAction)
return PartialView(viewName, model);
else if (Request.IsAjaxRequest())
return PartialView(viewName, model);
else
return View(viewName, model);
}
This will return a PartialView when you call you action via #Html.RenderAction() or call the action via Ajax (jQuery).
You can then use jQuery to prevent the form to be posted and post it with Ajax or when you click on a message, you can also use jQuery to get the result from the action and insert it in your DOM.

MVC 4 - LogOff controller action giving 404 not found

I'm just wrapping up a college project, I'm not sure if I've been staring at my computer for too long and am missing something obvious, but when I try to log a user out, I'm getting a 404 not found for the URL /Account/LogOff.
I have a navbar that shows Log in/Log out depending on whether a user is, logged in, or, logged out:
<div class="nav-collapse collapse">
<ul class="nav pull-right">
<li class="dropdown" id="dropdown-login-div">
#if (!Request.IsAuthenticated)
{
<a class="dropdown-toggle" href="#" data-toggle="dropdown">Sign In <strong class="caret"></strong></a>
}
else
{
#Html.ActionLink("Log Off", "LogOff", "Account")
}
<div class="dropdown-menu" id="dropdown-login">
#Html.Partial("~/Views/Account/_LoginPartial.cshtml", new ViewDataDictionary<LetLord.Models.LoginModel>())
</div>
</li>
</ul>
</div>
In my Account controller the default LogOff action that comes with the Internet template:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
WebSecurity.Logout();
return View("Index");
}
Could someone tell me why this happening - before I throw my laptop against the wall. Cheers.
You use a link (<a/> tag) to log off which results in HTTP GET request when user clicks on it, but your action is constrained to serve POST request only (because it is decorated with [HttpPost] attribute).
You either need to put your link into a form and generate POST request, or remove [HttpPost] and [ValidateAntiForgeryToken] (credits to GalacticCowboy) from your action.
Since logout modifies server state, I wouldnt remove [HttpPost] and [ValidateAntiForgeryToken]
Instead I will replace the link (anchor tag) with the following
#using (Html.BeginForm("Log Out", "Account", FormMethod.Post,
new { id = "logoutForm" }))
{
#Html.AntiForgeryToken()
Log Out
}
I ran into this issue on a legacy app. The way that I fixed it was to detect when the supplied return Url was '/Account/LogOff' and act accordingly.
From the 'AccountController.cs' file, 'Login' method:
if (returnUrl == "/Account/LogOff")
{
return this.RedirectToLocal(null);
}
else
{
return this.RedirectToLocal(returnUrl);
}

MVC project, Error because model not passed to view?

The Scenario is this...I goto the login screen on my web app. I login with wrong username/password. I get an error on the line in my HTML for tab-6 #HTML.Partial("../../Account/Register") because the RegisterModel is not passed in(Exact error below). Well I'm not doing anything with the Register screen, just logging in.
However, I think the problem is because the Register section is always loaded. So I'm thinking, I don't need to pass in both LogOnModel and RegisterModel, to fix the problem, but maybe should not load the Register page until it is specifically called, I think? Is that the correct thing to do and how would I load the Register page only when it is called/clicked. I'm using JQuery UI tabs and asp.net mvc.
ERROR:
The model item passed into the dictionary is of type 'Portal.Web.Models.LogOnModel', but this dictionary requires a model item of type 'Portal.Web.Models.RegisterModel'.
Thanks a lot!
<div id="tabs" class="ui-tabs ui-widget ui-widget-content ui-corner-all" style=" position:relative; border:0px;" >
<ul class="ui-tabs-nav">
<li><a href="#tabs-1" >Home</a></li>
<li><a href="#tabs-2" >Statistics</a></li>
<li><a href="#tabs-3" >Topo Maps</a></li>
<li><a href="#tabs-4" >FAQs</a></li>
<li style="display:none;">Login</li>
<li style="display:none;">SignUp</li>
</ul>
<div id="tabs-1" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Home/Home") </div>
<div id="tabs-2" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Statistics/Statistics")</div>
<div id="tabs-3" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Maps/Maps")</div>
<div id="tabs-4" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Home/FAQs")</div>
<div id="tabs-5" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Account/LogOn")</div>
<div id="tabs-6" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Account/Register")</div>
</div>
Here is my AccountController with action method
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
return View(model);
}
As Requested Here is the additional code:
public ActionResult LogOn()
{
return View();
}
Double-check that you've set your partial view to inherit from the appropriate model. At the top of your partial view code, you should have a line like this.
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Portal.Web.Models.LogOnModel>" %>

Categories