I need a way to instantiate a class thats supposed to help me with some querystring-building when it comes to my links in the view, and I cant use this methods that i require as static methods since that would cause the querystringbuilder to keep data during the whole life-time of the application (which would cause some serious problems)
So my question to you guys is,
Is it possible to some how be able to instantiate the class/object that i require but not in the actual view itself?
SO to keep the question simple.. is there anyway that I could do something like:
#MyInstantiatedObject.DoStuff()
in my view with out doing this before in my view:
#{
var MyInstantiatedObject = new MyClass()
}
I do get that I some where some how will need to instansiate the object, but my question is if its possible to do it in some other manner (like telling the web.config to handel it..or using some app_code #helper magic or something)
Thanks in advance!
What you are trying to achieve goes against the philosophy of MVC. If you want to keep the query string data between the actions, you can create your custom ActionLink html helper like this:
public static MvcHtmlString ActionLinkWithQueryString(this HtmlHelper htmlHelper,
string linkText, string actionName)
{
var routeValueDictionary = new RouteValueDictionary();
return htmlHelper.ActionLinkWithQueryString(linkText,
actionName, routeValueDictionary);
}
public static MvcHtmlString ActionLinkWithQueryString(this HtmlHelper htmlHelper,
string linkText, string actionName, RouteValueDictionary routeValues)
{
var queryString = HttpContext.Current.Request.QueryString;
if (queryString.Count > 0)
{
foreach (string key in queryString.Keys)
{
routeValues.Add(key, queryString[key]);
}
}
return htmlHelper.ActionLink(linkText, actionName, routeValues);
}
You can also create a custom RedirectToAction method in your Controller or in a Controller Extention like this:
private RedirectToRouteResult RedirectToActionWithQueryString(string actionName)
{
var queryString = Request.QueryString;
var routeValues = new RouteValueDictionary();
foreach (string key in queryString.Keys)
{
routeValues.Add(key, queryString[key]);
}
return RedirectToAction(actionName, routeValues);
}
I need a form on my ASP.NET MVC Razor page. My preference would be to use the following syntax:
#using (Html.BeginForm())
{
}
However, I need several attributes added to the form. So I ended up with something like the following:
#using (Html.BeginForm(null, null, FormMethod.Post, new { name = "value" }))
{
}
However, this has an undesired side effect. If there are query arguments in this page's request, the first form passes them along when the form is submitted. However, the second version does not.
I really don't know why BeginForm() doesn't support attributes, but is there a straight-forward way to add attributes to BeginForm() and still pass along any query arguments when the for is submitted?
EDIT:
After looking into this, it would seem the best solution is something like this:
<form action="#Request.RawUrl" method="post" name="value">
</form>
However, when using this syntax, client-side validation is disabled. It seems there is no good solution to this situation without more complicated and potentially unreliable constructs.
That's indeed true, but I would go with a custom helper in order to preserve the form context inside which is used for client side validation:
public static class FormExtensions
{
private static object _lastFormNumKey = new object();
public static IDisposable BeginForm(this HtmlHelper htmlHelper, object htmlAttributes)
{
string rawUrl = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
return htmlHelper.FormHelper(rawUrl, FormMethod.Post, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
private static int IncrementFormCount(IDictionary items)
{
object obj2 = items[_lastFormNumKey];
int num = (obj2 != null) ? (((int)obj2) + 1) : 0;
items[_lastFormNumKey] = num;
return num;
}
private static string DefaultFormIdGenerator(this HtmlHelper htmlhelper)
{
int num = IncrementFormCount(htmlhelper.ViewContext.HttpContext.Items);
return string.Format(CultureInfo.InvariantCulture, "form{0}", new object[] { num });
}
private static IDisposable FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
{
var builder = new TagBuilder("form");
builder.MergeAttributes<string, object>(htmlAttributes);
builder.MergeAttribute("action", formAction);
builder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
bool flag = htmlHelper.ViewContext.ClientValidationEnabled && !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;
if (flag)
{
builder.GenerateId(htmlHelper.DefaultFormIdGenerator());
}
htmlHelper.ViewContext.Writer.Write(builder.ToString(TagRenderMode.StartTag));
var form = new MvcForm(htmlHelper.ViewContext);
if (flag)
{
htmlHelper.ViewContext.FormContext.FormId = builder.Attributes["id"];
}
return form;
}
}
which could be used like this:
#using (Html.BeginForm(htmlAttributes: new { name = "value" }))
{
...
}
I had a similar problem and here is quick solution (it works with MVC4).
Declare the extension method:
public static MvcForm BeginForm(this HtmlHelper helper, object htmlAttributes)
{
return helper.BeginForm(helper.ViewContext.RouteData.Values["Action"].ToString(),
helper.ViewContext.RouteData.Values["Controller"].ToString(),
FormMethod.Post, htmlAttributes);
}
and use it in your page:
#using (Html.BeginForm(htmlAttributes: new {#class="form-horizontal"}))
{
...
}
Small modification to source code:
http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/Html/FormExtensions.cs
public static MvcForm BeginForm(this HtmlHelper htmlHelper, object htmlAttributes)
{
// generates <form action="{current url}" method="post">...</form>
string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
return FormHelper(htmlHelper, formAction, FormMethod.Post, new RouteValueDictionary(htmlAttributes));
}
private static MvcForm FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
{
TagBuilder tagBuilder = new TagBuilder("form");
tagBuilder.MergeAttributes(htmlAttributes);
// action is implicitly generated, so htmlAttributes take precedence.
tagBuilder.MergeAttribute("action", formAction);
// method is an explicit parameter, so it takes precedence over the htmlAttributes.
tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
bool traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled
&& !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;
if (traditionalJavascriptEnabled)
{
// forms must have an ID for client validation
tagBuilder.GenerateId(htmlHelper.ViewContext.FormIdGenerator());
}
htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
MvcForm theForm = new MvcForm(htmlHelper.ViewContext);
if (traditionalJavascriptEnabled)
{
htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"];
}
return theForm;
}
I have an ASP.NET MVC site that uses strongly typed views. In my case, a controller action could look like this:
public ActionResult List(MyStrongType data)
When submitting the page (view) the response will generate a URL that looks something like this (yes, I know routing could generate a nicer URL):
http://localhost/Ad/List?F.ShowF=0&ALS.CP=30&ALS.L=0&ALS.OB=0&ALS.ST=0&S=&LS.L1=&LS.L2=&CS.C1=32&CS.C2=34&CS.C3=&ALS.ST=0
If I submit the page again, I can see that the data object in the action is set properly (according to the URL)(default binder).
The problem is: Say that I am to add page buttons (to change the page) for a list on my sitepage, the list will be controlled by settings like filter, sortorder, amount of pages per page and so on (controlled by the querystring). First, I need to include all current query parameters in the URL, and then I need to update the page parameter without tampering with the other query parameters. How can I genereate this URL from the view/"HTML helper"?
I could of course manipulate the URL string manually, but this will involve a lot of work and it will be hard to keep up to date if a route is changed, there must be a easier way? Like some kind of querystring collection that can be altered on service side (like ASP.NET Request.QueryString)?
I would hope to not involve the route, but I post the one I got so far anyway:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"TreeEditing",
"{controller}/{action}/{name}/{id}",
new { controller = "MyCategory", action = "Add", name = string.Empty, id = -1 }
);
BestRegards
Edit 1: It's possible to set the query parameters like this (in view):
<%= url.Action(new {controller="search", action="result", query="Beverages", Page=2})%>
But this will only generate a URL like this (with the default route):
/search/result?query=Beverages&page=2
The rest of the parameters will be missing as you can see.
I could of course add every known parameter in this URL action, but if any query parameter is added or changed there will be a lot of work to keep everything up to date.
I have read the article ASP.NET MVC Framework (Part 2): URL Routing, but how do I find an answer to my problem?
It sounds to me like the problem you have is that you want to be able to easily persist query string values from the current request and render them into the URLs of links in your view. One solution would be to create an HtmlHelper method that returns the existing query string with some changes. I created an extension method for the HtmlHelper class that takes an object and merges its property names and values with the query string from the current request, and returns the modified querystring. It looks like this:
public static class StackOverflowExtensions
{
public static string UpdateCurrentQueryString(this HtmlHelper helper, object parameters)
{
var newQueryStringNameValueCollection = new NameValueCollection(HttpContext.Current.Request.QueryString);
foreach (var propertyInfo in parameters.GetType().GetProperties(BindingFlags.Public))
{
newQueryStringNameValueCollection[propertyInfo.Name] = propertyInfo.GetValue(parameters, null).ToString();
}
return ToQueryString(newQueryStringNameValueCollection);
}
private static string ToQueryString(NameValueCollection nvc)
{
return "?" + string.Join("&", Array.ConvertAll(nvc.AllKeys, key => string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(nvc[key]))));
}
}
It will loop through the query string values from the current request and merge in the properties defined on the object you passed in. So your view code might look like this:
<a href='/SomeController/SomeAction<%=Html.GetCurrentQueryStringWithReplacements(new {page = "2", parameter2 = "someValue"})%>'>Some Link</a>
This is basically saying "keep the query string from the current request, but change the page and parameter2 values, or create them if they didn't exist." Note that if your current request has a "page" query string parameter, this method will overwrite the value from the current request with the one you explicitly pass in from the view.
In this case, if your querystring was:
?parameter1=abc&page=1
It would become:
?parameter1=abc&page=2¶meter2=someValue
EDIT:
The above implementation will probably not work with the dictionary lookup of querystring parameter names you described. Here is an implementation and that uses a dictionary instead of an object:
public static string UpdateCurrentQueryString(this HtmlHelper helper, Dictionary<string, string> newParameters)
{
var newQueryStringNameValueCollection = new NameValueCollection(HttpContext.Current.Request.QueryString);
foreach (var parameter in newParameters)
{
newQueryStringNameValueCollection[parameter.Key] = parameter.Value;
}
return ToQueryString(newQueryStringNameValueCollection);
}
Your view would call the function by doing an inline initialization of a dictionary and passing it to the helper function like this:
<a href='/SomeController/SomeAction<%=Html.GetCurrentQueryStringWithReplacements(new Dictionary<string,string>() {
{ QuerystringHandler.Instance.KnownQueryParameters[QuaryParameters.PageNr], "2" },
{ QuerystringHandler.Instance.KnownQueryParameters[QuaryParameters.AnotherParam], "1234" }})%>'>Some Link</a>
I did JUST what you need!
I created an HTML helper for this. The helper takes the same parameters as a normal helper. Yet, it keeps the current values from the URL. I made it both for the URL helper as a ActionLink helper.
This replaces the: Url.Action()
<a href='<%= Html.UrlwParams("TeamStart","Inschrijvingen", new {modID=item.Mod_ID}) %>' title="Selecteer">
<img src="<%= Url.Content("~/img/arrow_right.png") %>" alt="Selecteer" width="16" /></a>
and this replaces the Html.ActionLink()
<%: Html.ActionLinkwParams("Tekst of url", "Action", new {test="yes"}) %>
Here is the helper:
using System;
using System.Web.Mvc;
using System.Web.Routing;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Web.Mvc.Html;
namespace MVC2_NASTEST.Helpers {
public static class ActionLinkwParamsExtensions {
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, string controller, object extraRVs, object htmlAttributes) {
NameValueCollection c = helper.ViewContext.RequestContext.HttpContext.Request.QueryString;
RouteValueDictionary r = new RouteValueDictionary();
foreach (string s in c.AllKeys) {
r.Add(s, c[s]);
}
RouteValueDictionary htmlAtts = new RouteValueDictionary(htmlAttributes);
RouteValueDictionary extra = new RouteValueDictionary(extraRVs);
RouteValueDictionary m = RouteValues.MergeRouteValues(r, extra);
//return System.Web.Mvc.Html.LinkExtensions.ActionLink(helper, linktext, action, controller, m, htmlAtts);
return helper.ActionLink(linktext, action, controller, m, htmlAtts);
}
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action) {
return ActionLinkwParams(helper, linktext, action, null, null, null);
}
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, string controller) {
return ActionLinkwParams(helper, linktext, action, controller, null, null);
}
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, object extraRVs) {
return ActionLinkwParams(helper, linktext, action, null, extraRVs, null);
}
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, string controller, object extraRVs) {
return ActionLinkwParams(helper, linktext, action, controller, extraRVs, null);
}
public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, object extraRVs, object htmlAttributes) {
return ActionLinkwParams(helper, linktext, action, null, extraRVs, htmlAttributes);
}
}
public static class UrlwParamsExtensions {
public static string UrlwParams(this HtmlHelper helper, string action, string controller, object extraRVs) {
NameValueCollection c = helper.ViewContext.RequestContext.HttpContext.Request.QueryString;
RouteValueDictionary r = RouteValues.optionalParamters(c);
RouteValueDictionary extra = new RouteValueDictionary(extraRVs);
RouteValueDictionary m = RouteValues.MergeRouteValues(r, extra);
string s = UrlHelper.GenerateUrl("", action, controller, m, helper.RouteCollection, helper.ViewContext.RequestContext, false);
return s;
}
public static string UrlwParams(this HtmlHelper helper, string action) {
return UrlwParams(helper, action, null, null);
}
public static string UrlwParams(this HtmlHelper helper, string action, string controller) {
return UrlwParams(helper, action, controller, null);
}
public static string UrlwParams(this HtmlHelper helper, string action, object extraRVs) {
return UrlwParams(helper, action, null, extraRVs);
}
}
}
How does it work?
The calls are the same as for the Html.ActionLink() so you simple can replace those.
The method does the following:
It takes all the optional parameters from the current URL and places them in a RouteValueDictionary.
It also places the htmlattributes in a dictionary.
Then it takes the extra routevalues which you specified manually and places those in a RouteValueDictionary as well.
The key then is to merge the ones from the URL and the ones manually specified.
This happens in the RouteValues class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
using System.Collections.Specialized;
using System.Web.Mvc;
namespace MVC2_NASTEST {
public static class RouteValues {
public static RouteValueDictionary optionalParamters() {
return optionalParamters(HttpContext.Current.Request.QueryString);
}
public static RouteValueDictionary optionalParamters(NameValueCollection c) {
RouteValueDictionary r = new RouteValueDictionary();
foreach (string s in c.AllKeys) {
r.Add(s, c[s]);
}
return r;
}
public static RouteValueDictionary MergeRouteValues(this RouteValueDictionary original, RouteValueDictionary newVals) {
// Create a new dictionary containing implicit and auto-generated values
RouteValueDictionary merged = new RouteValueDictionary(original);
foreach (var f in newVals) {
if (merged.ContainsKey(f.Key)) {
merged[f.Key] = f.Value;
} else {
merged.Add(f.Key, f.Value);
}
}
return merged;
}
public static RouteValueDictionary MergeRouteValues(this RouteValueDictionary original, object newVals) {
return MergeRouteValues(original, new RouteValueDictionary(newVals));
}
}
}
This is all pretty straightforward. In the end, the actionlink is made with the merged routevalues. This code also lets you remove values from the URL.
Examples:
Your URL is localhost.com/controller/action?id=10&foo=bar. If in that page you place this code
<%: Html.ActionLinkwParams("Tekst of url", "Action", new {test="yes"}) %>
the URL returned in that element will be localhost.com/controller/action?id=10&foo=bar&test=yes.
If you want to remove an item, you just set the item as an empty string. For example,
<%: Html.ActionLinkwParams("Tekst of url", "Action", new {test="yes", foo=""}) %>
will return the URL in the <a> element: localhost.com/controller/action?id=10&test=yes
I'm guessing this is all you need?
If you have some additional questions, just ask.
Extra:
Sometimes you will want to keep your values inside your action too, when you will redirect to another Action. With my RouteValues class, this can be done very easily:
public ActionResult Action(string something, int? somethingelse) {
return RedirectToAction("index", routeValues.optionalParamters(Request.QueryString));
}
If you still want to add some optional parameters, no problem!
public ActionResult Action(string something, int? somethingelse) {
return RedirectToAction("index", routeValues.optionalParamters(Request.QueryString).MergeRouteValues(new{somethingelse=somethingelse}));
}
I think that covers pretty much everything you'll need.
If you want to set the query string in a link of a view:
Html.ActionLink("LinkName", "Action", "Controller", new { param1 = value1, param2 = value2 }, ...)
If you want to set it in the browser URL after a post back, just call Route* in an action like RouteToAction() and set the parameter key/value you want.
If you use the action public ActionResult List(MyStrongType data), you need to include all page settings (page index, ordering,...) as parameters to 'MyStrongType', and the data object will contain all infomation for the view.
In the view, if you need to generate a URL, using the approach of CallMeLaNN:
Html.ActionLink("LinkName", "Action", "Controller", new { param1 = Model.value1, param2 = Model.param2, ... });. You need to manually set all the parameters here or create a helper to help you fill the URL.
You don't need to care about current parameters included in the address.
You can route:
routes.MapRoute(
"custome",
"{controller}/{action}/",
new { controller = "Home", action = "Index"}
);
to generate all the parameters as a query string.
I want to display images from other sever by using view and controller by asp.net mvc. how can i do? can u tell me detail and give me detail an exmaple? wait to see your answer.
Thanks
Nara
To display image in a view you could use the <img> tag:
<img src="http://someotherserver/path/to/some/image.png" alt="" />
or you could make a little html helper:
public static MvcHtmlString Image(this HtmlHelper helper,
string url,
object htmlAttributes)
{
return Image(helper, url, null, htmlAttributes);
}
public static MvcHtmlString Image(this HtmlHelper helper,
string url,
string altText,
object htmlAttributes)
{
TagBuilder builder = new TagBuilder("image");
var path = url.Split('?');
string pathExtra = "";
if(path.Length >1)
{
pathExtra = "?" + path[1];
}
builder.Attributes.Add("src", VirtualPathUtility.ToAbsolute(path[0]) + pathExtra);
builder.Attributes.Add("alt", altText);
builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
return MvcHtmlString.Create( builder.ToString(TagRenderMode.SelfClosing));
}
typical usage:
<%=Html.Image("~/content/images/ajax-loader.gif", new{style="margin: 0 auto;"})%>
enjoy..
I am writing my own HtmlHelper extenstion for ASP.NET MVC:
public static string CreateDialogLink (this HtmlHelper htmlHelper, string linkText,
string contentPath)
{
// fix up content path if the user supplied a path beginning with '~'
contentPath = Url.Content(contentPath); // doesn't work (see below for why)
// create the link and return it
// .....
};
Where I am having trouble is tryin to access UrlHelper from within my HtmlHelper's definition. The problem is that the way you normally access HtmlHelper (via Html.MethodName(...) ) is via a property on the View. This isn't available to me obviously from with my own extension class.
This is the actual MVC source code for ViewMasterPage (as of Beta) - which defines Html and Url.
public class ViewMasterPage : MasterPage
{
public ViewMasterPage();
public AjaxHelper Ajax { get; }
public HtmlHelper Html { get; }
public object Model { get; }
public TempDataDictionary TempData { get; }
public UrlHelper Url { get; }
public ViewContext ViewContext { get; }
public ViewDataDictionary ViewData { get; }
public HtmlTextWriter Writer { get; }
}
I want to be able to access these properties inside an HtmlHelper.
The best I've come up with is this (insert at beginning of CreateDialogLink method)
HtmlHelper Html = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer);
UrlHelper Url = new UrlHelper(htmlHelper.ViewContext.RequestContext);
Am I missing some other way to access the existing HtmlHelper and UrlHelper instances - or do i really need to create a new one? I'm sure there isn't much overhead but I'd prefer to use the preexisting ones if I can.
Before asking this question I had looked at some of the MVC source code, but evidently I missed this, which is how they do it for the Image helper.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "The return value is not a regular URL since it may contain ~/ ASP.NET-specific characters")]
public static string Image(this HtmlHelper helper, string imageRelativeUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageRelativeUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageRelativeUrl");
}
UrlHelper url = new UrlHelper(helper.ViewContext);
string imageUrl = url.Content(imageRelativeUrl);
return Image(imageUrl, alt, htmlAttributes).ToString(TagRenderMode.SelfClosing);
}
Looks like instantiating a new UrlHelper is the correct approach after all. Thats good enough for me.
Update: RTM code from ASP.NET MVC v1.0 Source Code is slightly different as pointed out in the comments.
File: MVC\src\MvcFutures\Mvc\ImageExtensions.cs
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "The return value is not a regular URL since it may contain ~/ ASP.NET-specific characters")]
public static string Image(this HtmlHelper helper, string imageRelativeUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageRelativeUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageRelativeUrl");
}
UrlHelper url = new UrlHelper(helper.ViewContext.RequestContext);
string imageUrl = url.Content(imageRelativeUrl);
return Image(imageUrl, alt, htmlAttributes).ToString(TagRenderMode.SelfClosing);
}
I faced a similar issue and decided that it would be easier to just call the UrlHelper in the view and pass the output to my HtmlHelper extension. In your case it would look like:
<%= Html.CreateDialogLink( "text", Url.Content( "~/...path.to.content" ) ) %>
If you want to access the extension methods on the existing HtmlHelper that is passed into your class, you should only need to import System.Web.Mvc.Html in your source code file and you will get access to them (that's where the extension classes are defined). If you want a UrlHelper, you'll need to instantiate that as the HtmlHelper you are getting doesn't have a handle for the ViewPage that it's coming from.
If you need to create a UrlHelper in a utility class you can do the following :
string url = "~/content/images/foo.jpg";
var urlHelper = new UrlHelper(new RequestContext(
new HttpContextWrapper(HttpContext.Current),
new RouteData()), RouteTable.Routes);
string absoluteUrl = urlHelper.Content(url);
This allows you to use routing or '~ expansion' away from an MVC context.
Well, you can always pass the instance of the page to the extension method. I think that is a much better way of doing this than creating new instances in your method.
You could also define this method on a class that derives from MasterPage/ViewMasterPage and then derive the page from that. This way, you have access to all the properties of the instance and don't have to pass them around.