I'm hoping someone can help me here - I'm trying to set up a basic MVC widget for Sitefinity that will POST to an external URL to log the user in there. The problem is if I use regular HTML to do this, the widget will only work in pure MVC mode thanks to the way WebForms deals with forms.
I googled around a bit and found people explaining that with the regular "BeginForm" you can specify an external action URL thusly:
Html.BeginForm(null, null, FormMethod.Post, new { #action="EXTERNAL_URL" })
Which produces the following opening form tag:
<form action="EXTERNAL_URL" method="post">
This works very nicely, but still outputs a regular HTML form which doesn't work with Sitefinity, so Sitefinity has it's own method to produce forms that will work in Hybrid mode "Html.BeginFormSitefinity". This method has all of the same overrides but doesn't behave in quite the same way - when I attempt to use this code:
Html.BeginFormSitefinity(null, null, FormMethod.Post, new { #action="EXTERNAL_URL" })
This produces the following opening form tag:
<form action="/TestProject/TestPage/Index" method="POST" name="defaultForm" action="EXTERNAL_URL">
As you can see, it's popping a second action attribute in rather than overriding the action attribute as seen in the default MVC method's behaviour.
I'm hoping that someone with a better understanding of how Sitefinity works might be able to provide some advice here?
Looking at the sources of the HybridForm, it cannot be modified so it will take action htmlAttribute into account: http://pastebin.com/5dfQdzs8
So you may create your own form based on this code.
You'll need html helper:
public static MyHybridForm BeginFormSitefinity(this HtmlHelper htmlHelper, string actionName, string formName, FormMethod method, IDictionary<string, object> htmlAttributes)
{
return new MyHybridForm(htmlHelper.ViewContext, actionName, formName, method, (object) htmlAttributes);
}
and override GenerateActionUrl() in your MyHybridForm by using action htmlAttribute into account.
Related
With MVC5 (and older MVC frameworks), I was able to write my form without specifying the controller/action method:
#using (Html.BeginForm())
{
}
This way, I was able to reuse the form with different URL. If the form was called from the route "/books/2/edit", the HTML generated would be:
<form action="/books/2/edit"></form>
And if I call the form using the URL "/books/add", the HTML generated would be:
<form action="/books/add"></form>
How can I do the same with the tag helper syntax? I have tried all kind of syntaxes but it always generate an empty action attribute:
<form></form>
<form asp-route=""></form>
<form asp-controller="" asp-action=""></form>
Result:
<form></form>
<form action></form>
<form action></form>
When using HTML helpers, values that are not supplied explicitly default to the route values that are in the current request. That is the reason why you can specify BeginForm with no parameters.
When using tag helpers, this default logic no longer applies - the values must be provided explicitly. There are no defaults.
Option 1 - form tag
The simplest way to mimic what the HTML helper does with a form tag is:
<form action="#Url.RouteUrl(this.ViewContext.RouteData.Values)" method="post">
</form>
Option 2 - Html.BeginForm
Note that your current syntax is also still valid in ASP.NET Core MVC:
#using (Html.BeginForm())
{
}
But since you had to ask this question, I would say that it is absolutely not clear how the URL is being generated when using this syntax, which means you should probably change to using Url.RouteUrl to make it more readable despite being a bit more to write.
Option 3 - Tag Helper
Here is an example of how you could use a tag helper to achieve this, although it is a bit ugly.
There is a form tag helper attribute asp-all-route-values that allows you to pass all of the route values in a single parameter. However, according to asp-all-route-data must be IDictionary<string,object> or RouteValueDictionary, it is not possible to pass a RouteValueDictionary to this attribute, you would need to convert it to an IDictionary<string, string>. One way to do that is to build an extension method to make the conversion.
public static class RouteValueDictionaryExtensions
{
public static IDictionary<string, string> ToTagHelperValues(this RouteValueDictionary routeValueDictionary)
{
var result = new Dictionary<string, string>();
foreach (var kvp in routeValueDictionary)
{
result.Add(kvp.Key, kvp.Value as string);
}
return result;
}
}
You can then use a tag helper to generate the current URL as follows:
<form asp-all-route-data="#this.ViewContext.RouteData.Values.ToTagHelperValues()">
</form>
Option 4 - No action attribute
It is also possible to use a form tag with no action attribute. If you omit the action attribute, the default behavior in most (if not all) browsers is to use the current URL.
<form method="post">
</form>
WARNING: It is not standards compliant to use this option and technically the behavior of browsers do not have to default to the expected behavior of using the current URL if it is not supplied.
In the end, which method you use is a matter of preference.
I'm trying to create custom Html Helpers in my sample MVC Project.
Here is what i've done so far:
public static MvcHtmlString ImageFor<TModel,TValue>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel,TValue>> expression,
string alternateText,
object htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
string name = ExpressionHelper.GetExpressionText(expression);
TagBuilder tagBuilder = new TagBuilder("img");
tagBuilder.MergeAttribute("src", metadata.Model.ToString());
tagBuilder.MergeAttribute("alt", alternateText);
tagBuilder.MergeAttribute("name", name);
return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.SelfClosing));
}
In my controller I'm passing the image path to a property(imgUrl).
When i try to create a tag for it it doesnot give me the image on browser, instead it is always displaying the alternate text.
Here is the rendered markup for it.
<img name="imageUrl" alt="alernate" src="C:\Users\Administrator\Desktop\down.png"/>
While i've done the loosely typed helper for #Html.Image, it works perfect !.
What has gone wrong with my code.
I've searched alot for creating Strongly Typed Helpers, but none of the tutorials explained me what is going on when we access the strongly typed helpers.
I've also seen posts in SO, when someone post code regarding helpers, its just a piece of code without any explanation.
If anyone has a tutorial for creating strongly typed custom helpers, which gives a breif of please share the link.
Your src attribute points to your local drive. That is not going to work. Your browser displays the alternate text because it can't find the image. You should use the URL of the image instead. For example src="/images/down.png".
Our designers have come up with button styles for an application which require the addition of <span> tags inside the <a> tags of our links.
In ASP.NET we implemented this by adding an App_Browsers entry for Link Buttons.
How would I go about doing this in ASP.NET MVC?
I've contemplated creating my own versions of all of the various HTML helper functions for creating ActionLinks and RouteLinks but this seems to be quite a 'brute force' way of doing things.
Is there a nice elegant way of doing it?
I know we could write some simple jQuery to do it, but we'd rather have the markup coming out of the server correctly in the first place.
Actually I think writing a new helper is exactly the way I would go. Seems to me that that's exactly what they are there for and it makes them very re-usable too.
You could always write one extension method, that takes another one (one of the built-in ones) as an argument, and wrappes the <span> around your link text before calling it. It should be quite easy to do with lambdas...
public static string SpanLink(this HtmlHelper helper,
string linkText, object args, Action<string> action)
where TController : IController
{
action("<span>" + linkText + "</span>", args);
}
And to call it:
<%= Html.SpanLink<HomeController>("link text", (s) => Html.ActionLink<HomeController>(c => c.Index(s));
(This code is typed directly into the answer field of SO - I haven't even checked it to make sure it compiles. So bear with me if it doesn't work on the first try...)
Using Forms Authentication in ASP.NET MVC when trying to log back into a site, it puts a ReturnUrl parameter in the query string. My Logon action method accepts a "returnUrl" string. However it seems that returnUrl string is always null, even when it is clearly in the query string. Any thoughts on why this might be the case or a possible fix?
This tends to happen when you're using one generic logon form, but you're explicitly specifying the Controller and ActionMethod (which is causing a form post, but losing the querystring)
Just to clarify, this is what your code should look like in your BeginForm:
Html.BeginForm("LogOn", "Account", new { ReturnUrl = Request.QueryString["ReturnUrl"] })
EDIT: This is by design as RickAnd mentions in comments below. However it doesn't allow for the UI pattern of being deep in a site, clicking on LogOn, then returning to the page you were previously on, if it allows anonymous users. It's a commonly requested pattern. David Allen's approach to LogOff would also work nicely for a clean redirect at LogOn.
Maybe you don't include the ReturnURL parameter into you login form's action attribute, thus posting to a URL without that parameter?
Basically, The Asp.net MVC has some hidden features. For Example when you pass variable 'id' to controller action, it interprets 'id' as default identifier and puts it on browser query with fore slash.By using another name instead of 'id' we will see '?' rather than fore slash. Because of setting the 'id' name on RegisterRoutes method on global.asax file.
In this Problem you have created a custom data passer to controller by using this code:
using(Html.BeginForm("LogOn", "Account", FormMethod.Post))
{
//form fields
}
So Asp.net MVC ignores other useful data to pass to controller action, and we'll see returnUrl always null.
While, by using this, Asp.net MVC acts Correctly and returnUrl is mounted:
using(Html.BeginForm())
{
//form fields in LogOn View
}
By the way, When we use custom data passer to controller action, must pass another data manually like this:
using(Html.BeginForm("LogOn", "Account", new {ReturnUrl = Request.QueryString["ReturnUrl"] }))
{
//form fields
}
There are two ways I can think of to deal with logon and logoff scenarios.
Dave Beer outlined one way, above.
There is another approach that works in many situations. I used it when I coded the NerdDinner tutorial. The tutorial provides us with a logoff function that logs you off and takes you home. I did not want that. I wanted to return to the page I was on before I logged off. So I modified my Account controller logoff action to look like this
public ActionResult LogOff()
{
FormsService.SignOut();
return Redirect(Request.UrlReferrer.ToString());
}
You can get fancier and pass in a returnUrl and test for it, in case you want to override this behavior. But I don't need that. This achieves the desired result. The Logon can work similarly. Maybe there are ways to use the MVC framework to do this for me, but until I learn them, this is VERY simple and works reliably.
Try the following:
public static MvcForm BeginForm(this HtmlHelper htmlHelper, string id)
{
string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
TagBuilder tagBuilder = new TagBuilder("form");
tagBuilder.MergeAttribute("id", id);
tagBuilder.MergeAttribute("action", formAction);
tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(FormMethod.Post), true);
HttpResponseBase httpResponse = htmlHelper.ViewContext.HttpContext.Response;
httpResponse.Write(tagBuilder.ToString(TagRenderMode.StartTag));
return new MvcForm(htmlHelper.ViewContext.HttpContext.Response);
}
First ensure you have set the login url in the web.config, Next, ensure your Signin Form does not contain anything like action, for example:
View:
If you specify action you will always get null for return url:
Controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult SignIn(string userName, string password, bool? rememberMe, string returnUrl)
{
}
Is there a way to force use of a named route in ASP.NET MVC when using Form.Begin. I'm learning more and more about routing and getting scared that it can be very fragile if you just change the order or whether or not parameters have defaults.
<% Form.Begin(...) %> <!-- no overload for providing a route name --%>
There does not seem to be an overload for a named route for beginning a form so instead the best I could come up with was this :
<form action="/Products/Command/ViewProduct" method="post">
I'm wondering if this missing overload is an oversight (Beta right now), if there is a reason for it or an alternative way to generate the URL.
I tried to use RouteLink and embed it in the Form tag, but RouteLink creates me the full HTML for an <A> tag which is no good.
action="<%= Ajax.RouteLink("Update Status", "product-route-short", new { action = "GetStatus", sku = "" }, new AjaxOptions { UpdateTargetId = "status" })%>"
What alternatives do i have to generate a URL from a named route.
Should I report this missing overload as an issue?
If you need to generate a URL to a named route, you can use Url.RouteUrl(), you just have to put the controller and action into the route values object like so:
<%= Url.RouteUrl("NamedRoute", new { controller="Controller", action="Action", foo="Bar" ... }) %>
As for adding the overload, a lot of these helpers have so many different overloads that they start to conflict and become ambiguous. For example, consider the following overloads
public string BeginForm(string actionName, string controllerName)
public string BeginForm(string actionName)
public string BeginForm(string routeName, string actionName) // Uh oh!
The 1st and 3rd have identical signatures, so they are invalid.
You could always create your own form helper as an extension method to HtmlHelper if you need to use Named Routes often.