How to Create a Friendly URL in a CS File - c#

I know how to create a url by using html.actionlink in the aspx file. But if I want to create the same url in a code behind file how would I do that?

The code behind idea for views in MVC was removed coz it didn't really seem to fit the MVC paradigm. Maybe you should consider creating your own Html Helpers instead. Doing this, extending existing actions like Html.ActionLink() is easy (and heaps of fun).
This example shows how i created a helper to tweak my login/logout links. Some ppl might argue whether this is a good use for a helper but it works for me:
/// <summary>
/// For the global MasterPage's footer
/// </summary>
/// <returns></returns>
public static string FooterEditLink(this HtmlHelper helper,
System.Security.Principal.IIdentity user, string loginText, string logoutText)
{
if (user.IsAuthenticated)
return System.Web.Mvc.Html.LinkExtensions.ActionLink(helper, logoutText, "Logout", "Account",
new { returnurl = helper.ViewContext.HttpContext.Request.Url.AbsolutePath }, null);
else
return System.Web.Mvc.Html.LinkExtensions.ActionLink(helper, loginText, "Login", "Account",
new { returnurl = helper.ViewContext.HttpContext.Request.Url.AbsolutePath }, null);
}
..and this is how i use it in the view (partial view to be exact):
<% =Html.FooterEditLink(HttpContext.Current.User.Identity, "Edit", "Logout (" + HttpContext.Current.User.Identity.Name + ")")%>

Take a look at this post by Scott Mitchell
http://scottonwriting.net/sowblog/posts/14011.aspx
(Since you say 'html.actionlink' which is an instance of the UrlHelper class I am assuming you are in a context where you don't have access to an instance of the UrlHelper class)

Related

AspMvcAction annotation with default controller

I am using jquery autocomplete, the source is provided with jquery ajax, calling my AutocompleteController. The AutocompleteController has different Actions, like GetCustomers, GetItems, GetCountries, ...
This is my UrlHelper extension method:
public static string Autocomplete(this UrlHelper url, [AspMvcAction] string actionName, [AspMvcController] string controller = "Autocomplete")
{
return url.Action(actionName, controller, new { area = "" });
}
And in Razor:
#Url.Autocomplete("GetCountries", "Autocomplete")
Because of the [AspMvcAction] and [AspMvcController] the intellisense is working without problems. But the controller parameter in the #Url.Autocomplete is redundant, so I would rather have:
#Url.Autocomplete("GetCountries")
But in this case ReSharper searches the action (GetCountries in this case) in the controller of the current view, that's why it cannot find it (Cannot resolve action 'GetCountries').
Any ideas how to convince ReSharper to look in the Autocomplete controller?
PS - the problem is only with intellisense, the code works in both cases.
Currently this is not supported.
https://resharper-support.jetbrains.com/hc/en-us/community/posts/360008302300-AspMvcAction-annotation-with-default-controller

ViewDataDictionary vs anonymous object in ASP.net MVC 5

I am a beginner and I am going through some tutorials in my MVC. So, I came across two scenarios.
Scenario 1.
I had to pass some data to my view on post and then send that data as hidden field. Here is the code.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ForgotPassword(ForgotPasswordMV viewModel)
{
if (ModelState.IsValid)
{
return RedirectToAction("VerifyToken", new { emailId = viewModel.EmailId });
}
^^ USING ANONYMOUS OBJECTS
return View();
}
public ActionResult VerifyToken(string emailId = null)
{
VerifyTokenMV viewModel = new VerifyTokenMV
{
EmailId = emailId
};
return View(viewModel);
}
VerifyToken View
#using (#Html.BeginForm("VerifyToken", "Security"))
{
#Html.HiddenFor(m => m.EmailId)
<button class="btn btn-primary">Continue</button>
}
Works Perfectly fine. I am able to receive values of EmailId. So far so good.
Scenario 2.
Needed to open a partial view from Main view, here is the snippet.
Main cshtml file
<div class="abc">
#Html.Partial("../Widget/Customize", Model.Unit, new ViewDataDictionary() { { "ElementName", "UnitWidget" } })
</div>
partial cshtml file
#{
string custWidgetElementName = ViewBag.ElementName;
}
// some html code below
Observation:
In scenario 2 why have I used ViewDataDictionary. Although both example works perfectly fine. But is there any reason that I had to use ViewDataDictionary. In scenraio 1 can we use ViewDataDictionary? If Yes, then which one is optimum solution.
Question: When I need to pass values shall I use new {key : value} or use ViewDataDictionary or there is no corelation? Instead of ViewDataDictionary can I use anonymous object in Senario 2
Your two scenarios are totally different. They are not doing the same thing.
In scenario 1 when using this line:
return RedirectToAction("VerifyToken", new { emailId = viewModel.EmailId });
A new URL is genrated and sent back to the client (the browser) with HTTP Code 301 or 302. When received the browser will re-contact your application wiht the received URL. With that URL, your application will execute the associated action. In your case, the client's browser will call VerifyToken action with the emailId parameter setted when you call RedirectionToAction into ForgotPassword action. So using RedirectionToAction method is just telling that method to generate a new URL with parameter defined in the anonymous type.
In scenario 2 is completely different to scenario 1. With this line:
#Html.Partial("../Widget/Customize", Model.Unit, new ViewDataDictionary() { { "ElementName", "UnitWidget" } })
You're telling your view to inject the partial view which path is ../Widget/Customize. Because that partial view the strongly typed, you passed Model.Unit as an second parameter. You use also a third parameter new ViewDataDictionary() { { "ElementName", "UnitWidget" } } because your partial seems to internally need to access to the dynamic property ViewBag or dictionary property ViewData of your view.
Conclusion:
In scenario 1 you are just telling the client's browser to go to the new URL you have generated after requesting ForgetPassword URL. We just call that a rediretion.
In scenario 2, you're just rendering a partial view into a view. The client's broswer doesn't know anything what's going on with partial views they don't know if they exist.

MVC Back Button Having Problems

I want to have a "Back" button that returns a user back to a page with a grid/table on it, but I'm having some trouble. The point of the back button is to allow my user's grid settings to be persisted between viewing records. The link that the Back button points to depends on what URL the user comes from. The url is set in the controller method. When the Url looks like the following...
http://localhost:49933/OpenOrder
... there is very little problem getting the back button to work. However, when the URL looks like this...
http://localhost:49933/OpenOrder?grid-sort=&grid-page=1&grid-pageSize=1000&grid-group=&grid-filter=CustomerName~contains~'TEST'
... sometimes the "Back" button won't work at all. I'm using a Telerik Kendo MVC grid, and this is what happens to the URL when a user filters the page.
I have noticed that the following code in the view works consistently on all links when there is no form tag (e.g. #Html.BeginForm())...
View
<a href='#Url.Content(#ViewBag.PreviousReferrer)' style="display:none;">
<input type="button" value="Back" />
</a>
But this doesn't work well with Views that do have the form tag. All I get is a Back button that I can click, but does absolutely nothing. It doesn't even show up in Fiddler.
Other things that I've tried include...
<input type="button" value="Back" onclick="#("window.location.href='" + #Url.Content(ViewBag.PreviousReferrer) + "'");" />
<input type="button" value="Back2" onclick="window.location.href = '#Url.Content(#ViewBag.PreviousReferrer)'" />
<input type="button" value="Back3" onclick="javascript:window.location=('#Url.Content(#ViewBag.PreviousReferrer)')" />
As stated previously, the ViewBag.PreviousReferrer property is set in the controller. Depending on whether a user is getting or posting, it will use either the Request.PreviousReferrer, or a session variable that I set. I won't include all my controller code since the rest of the pages work fine, but here's what it looks like when I set the ViewBag.PreviousReferrer in the gets and posts. At least one GET will come before any posts, so the session variable will always be set to something.
Controller
GET
string urlReferrer = Request.UrlReferrer == null ? string.Empty : Request.UrlReferrer.AbsoluteUri.ToUpper();
if (!string.IsNullOrEmpty(urlReferrer) && urlReferrer.Contains("OPENORDER"))
{
ViewBag.PreviousReferrer = Request.UrlReferrer.AbsoluteUri;
Session["OpenOrderPreviousReferrer"] = Request.UrlReferrer.AbsoluteUri;
}
else
{
ViewBag.PreviousReferrer = "~/OpenOrder";
}
POST
ViewBag.PreviousReferrer = Session["OpenOrderPreviousReferrer"] ?? "~/OpenOrder";
Ideally, this is what I want: One style of markup to go to the link in the PreviousReferrer REGARDLESS of whether or not the button is inside or outside of a form tag.
I think the problem might have something to do with HTML escape sequences, but I don't know if that's the technical term for it.
I had to create button links, that did a browser back "a lot" in one project, so created this HtmlHelper extension:
public static class ActionLinkButtonHelper
{
/// <summary>
/// Add a back button that generates a Javascript browser-back
/// </summary>
/// <param name="htmlHelper">HtmlHelper we are extending</param>
/// <param name="buttonText">Text to display on back button - defaults to "Back"</param>
/// <param name="actionName">Name of action to execute on click</param>
/// <param name="controller">Name of optional controller to send action to</param>
/// <returns></returns>
public static MvcHtmlString BackButton(this HtmlHelper htmlHelper, string buttonText="Back", string actionName="index", string controller=null, object routeValuesObject = null)
{
// Note: "Index is provided as a default
return ActionLinkButton(htmlHelper, buttonText, actionName, controller, routeValuesObject, new { onclick = "history.go(-1);return false;" });
}
}
and use it in the page like this:
#Html.BackButton()
or this:
#Html.BackButton("Button text", "action", "controller", new {id=routevalues})
It generates a button with a small snippet of JS that causes the browser to go back, so an actual link is not required unless JavaScript is disabled.
Supporting method (ActionLink as a button):
public static MvcHtmlString ActionLinkButton(this HtmlHelper htmlHelper, string buttonText, string actionName, string controllerName, object routeValuesObject = null, object htmlAttributes = null)
{
// For testing - create links instead of buttons
//return System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, buttonText, actionName, controllerName, routeValues, htmlAttributes);
if (string.IsNullOrEmpty(controllerName))
{
controllerName = HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
}
RouteValueDictionary routeValuesDictionary = new RouteValueDictionary(routeValuesObject);
RouteValueDictionary htmlAttr = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
TagBuilder tb = new TagBuilder("input");
tb.MergeAttributes(htmlAttr, false);
string href = UrlHelper.GenerateUrl("default", actionName, controllerName, routeValuesDictionary, RouteTable.Routes, htmlHelper.ViewContext.RequestContext, false);
tb.MergeAttribute("type", "button");
tb.MergeAttribute("value", buttonText);
if (!tb.Attributes.ContainsKey("onclick"))
{
tb.MergeAttribute("onclick", "location.href=\'" + href + "\';return false;");
}
return new MvcHtmlString(tb.ToString(TagRenderMode.Normal).Replace("'", "\'").Replace(" "," "));
}
Views/Web.config change:
To make the above available to Intellisense/auto-complete in Views you need to add a new entry in the Views/web.config file <namespaces> like this:
<namespaces>
... [SNIP] ...
<add namespace="My.Lib.Name" />
</namespaces>
Where My.Lib.Name is the namespace where you have placed the ActionLinkButtonHelper shown above.

ASP.NET MVC - HTML.BeginForm and SSL

I am encountering an issue with what should be a simple logon form in ASP.NET MVC 2. Essentially my form looks a little something like this:
using (Html.BeginForm("LogOn", "Account", new { area = "Buyers" }, FormMethod.Post, new { ID = "buyersLogOnForm" }))
I have a RequiresHTTPS filter on the LogOn Action method but when it executes I receive the following message
The requested resource can only be
accessed via SSL
At this point the only solution that worked was to pass in an extra action htmlattribute as follows:
var actionURL = "https://" + Request.Url.Host + Request.Url.PathAndQuery;
using (Html.BeginForm("LogOn", "Account", new { area = "Buyers" }, FormMethod.Post, new { ID = "buyersLogOnForm", #action = actionURL }))
While this works I wonder a) why i am seeing this issue in the first place and b) if there is a more straightforward way of posting to https from a http page?
[Edit]
I should have stated that the logon dropdown will be available on many public pages. I do not want all of my pages to be HTTPS. For instance, my hope page - which ANYONE can see - should not be HTTPS based. Essentially I need to specify the protocol in my form but have no idea how to do that, or if it is possible.
I would appreciate any advice/suggestions.
Thanks in advance
JP
You could use
<form action =" <%= Url.Action(
"action",
"controller",
ViewContext.RouteData.Values,
"https"
) %>" method="post" >
Use the [RequireHttps] attribute on both the action that renders the form and the one you are posting to.
Update: Review the comments below about the security vulnerabilities of this approach before considering the use of this code.
I found that a hybrid of JP and Malcolm's code examples worked.
using (Html.BeginForm("Login", "Account", FormMethod.Post, new { #action = Url.Action("Login","Account",ViewContext.RouteData.Values,"https") }))
Still felt a bit hacky though so I created a custom BeginForm helper. The custom helper is cleaner and does not require https when running locally.
public static MvcForm BeginFormHttps(this HtmlHelper htmlHelper, string actionName, string controllerName)
{
TagBuilder form = new TagBuilder("form");
UrlHelper Url = new UrlHelper(htmlHelper.ViewContext.RequestContext);
//convert to https when deployed
string protocol = htmlHelper.ViewContext.HttpContext.Request.IsLocal == true? "http" : "https";
string formAction = Url.Action(actionName,controllerName,htmlHelper.ViewContext.RouteData.Values,protocol);
form.MergeAttribute("action", formAction);
FormMethod method = FormMethod.Post;
form.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
htmlHelper.ViewContext.Writer.Write(form.ToString(TagRenderMode.StartTag));
MvcForm mvcForm = new MvcForm(htmlHelper.ViewContext);
return mvcForm;
}
Example usage:
#using (Html.BeginFormHttps("Login", "Account"))

ASP.NET MVC Beta Routes, Controller Actions, Parameters and ActionsLinks...putting it all together

I'm having some trouble with ASP.NET MVC Beta, and the idea of making routes, controller actions, parameters on those controller actions and Html.ActionLinks all work together. I have an application that I'm working on where I have a model object called a Plot, and a corresponding PlotController. When a user creates a new Plot object, a URL friendly name gets generated (i.e.). I would then like to generate a "List" of the Plots that belong to the user, each of which would be a link that would navigate the user to a view of the details of that Plot. I want the URL for that link to look something like this: http://myapp.com/plot/my-plot-name. I've attempted to make that happen with the code below, but it doesn't seem to be working, and I can't seem to find any good samples that show how to make all of this work together.
My Route definition:
routes.MapRoute( "PlotByName", "plot/{name}", new { controller = "Plot", action = "ViewDetails" } );
My ControllerAction:
[Authorize]
public ActionResult ViewDetails( string plotName )
{
ViewData["SelectedPlot"] = from p in CurrentUser.Plots where p.UrlFriendlyName == plotName select p;
return View();
}
As for the ActionLink, I'm not really sure what that would look like to generate the appropriate URL.
Any assistance would be greatly appreciated.
The answer is pretty simple: You have to supply enough values in your "ActionLink" that will fulfill your Route. Example:
<%= Html.ActionLink("Click Here", "ViewDetails", "Plot", new { name="my-plot-name" }, null)%>
If you leave out the "name=" part of the ActionLink method, then the RouteEngine won't see this link as being good enough to "match"... so then it would go to the default route.
This code above will make the URL look the way you want it.
How about this code-fix? (Note the name = null, appened to the end of the 4th line....)
routes.MapRoute(
"PlotByName",
"plot/{name}",
new { controller = "Plot", action = "ViewDetails", name = null }
);
and this should be renamed.. (notice plotName is renamed to name)
public ActionResult ViewDetails(string name ) { ... }
does that help?

Categories