using guid in ActionLink - asp.net-mvc

I search a lot,but didn't find any solution. I have a product table and here ProductID is guid. And in index.cshtml I have a edit link
#Html.ActionLink("Edit","Edit","Admin",new{id=i.ProductID})
When I click link, url like it
http://localhost:5546/Admin/Edit?Length=5
And I get following error
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Guid' for method 'System.Web.Mvc.ActionResult Edit(System.Guid)' in 'RiotBooks.Controllers.AdminController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
How to I solve it?

You're using the wrong overload of ActionLink.
The one you're calling there is this one:
public static MvcHtmlString ActionLink(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
Object routeValues,
Object htmlAttributes
)
but you really want to be calling this one:
public static MvcHtmlString ActionLink(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName,
Object routeValues,
Object htmlAttributes
)
So just change your call to
#Html.ActionLink("Edit","Edit","Admin",new{id=i.ProductID}, null)
and you'll be good.
For the record, the tip off to me was that ?Length=5 comes from the fact that the string "Admin" being passed into the routeValues parameter has only one property on it (Length) and the length of that string is 5.

Related

How to pass route attribute but not pass the htmlattribute in ActionLink in ASP.Net MVC 5

I just want to know why don't we have an overload for Action link like below.
#Html.ActionLink(displayName, actionName,controllerName,routeValue)
#Html.ActionLink(#item.Resume.ResumeName, "Details","Resume", new { id=item.Id})
When I do something like below everything works fine
#Html.ActionLink(displayName, actionName,controllerName,routeValue,"")
#Html.ActionLink(#item.Resume.ResumeName, "Details","Resume", new { id=item.Id},"")
I checked the overload and with four parameter the only overload available is
#Html.ActionLink(displayName, actionName,controllerName, htmlattribute)
So, I am a beginner and just wondering is there any particular reason for not having an overload with routevalue 4th argument(last argument). So basically with current framework, if I need to send a routevalue its mandatory for me to set 5th parameter as well (empty string).
The ActionLink() methods allow you to add route values and/or html attributes, both of which accept object as its arguments.
If the signature was just
#Html.ActionLink(string displayName, string actionName, string controllerName, object routeValues)
then if you were to pass new { id = 1 } as an argument to the 4th parameter, there would be no way to distinguish if you wanted it to be added as a route value or a html attribute.
Note that there is no overload with #Html.ActionLink(string displayName, string actionName, string controllerName, object htmlAttributes) as you claim. The overload is actually
#Html.ActionLink(string displayName, string actionName, string controllerName, object routeValues, object htmlAttributes)
or
#Html.ActionLink(string displayName, string actionName, object routeValues, object htmlAttributes)
and to add just route values, you typically pass null to the last parameter.

pass values from other properties on my model to custom htmlhelper

I am trying to create a custom html helper that will actually have several html elements in it including an input type of file and input type of button.
I have that working fine, but I'd also like to include a few hidden inputs and populate the values of those with values from a few of the properties of my model. Is it possible to fetch the values of a few properties from my model in a custom #Html.MyCustomHelperFor(m => m.somefield)
my method signature looks like this:
public static MvcHtmlString MyCustomHelperFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
also, a bit off topic, would I be better suited just writing this up in a partial view?
please advise.
You can access the full model from ViewData
MyCustomHelperFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
MyModelClass model = htmlHelper.ViewData as MyModelClass;
string otherProperty = model.OtherProperty;
However this is not very flexible since it will only work for one model type (unless you do conditional checks (if (htmlHelper.ViewData is MyModelClass1) {..} else if (htmlHelper.ViewData is MyModelClass2) {..} etc.
I suggest you would be better of creating a partial view or custom display and editor templates.

ASP.NET MVC - pass htmlAttributes as parameter

I want to pass htmlAttributes as parameter to my HtmlHelper similar as it created in
Html.ActionLink("linktext", "Home", null, new{width="100px"})
How to pass this new{width="100px"} to my method
public static string SelectCategoryAdminWithAllItem(this HtmlHelper htmlHelper, string name, **???**)
{ }
and how to parse it?
Thanks
Always try to look at sources when interested with this kind of questions. From the implementation of HtmlHelper.TextBox
public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, object htmlAttributes)
{
return htmlHelper.TextBox(name, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
as you see, type of parameter is object as you cant use anonymous types as parameters to methods, and object is choice. And when parsing it, you can use HtmlHelper.AnonymousObjectToHtmlAttributes Method
I looked through the source for MVC2 when trying to figure this one out. In MVC2 they used an overload of RouteValueDictionary in System.Web.Routing to turn an object to a dictionary rather than having a helper method available like in MVC3.
public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, object htmlAttributes)
{
return htmlHelper.TextBox(name, value, new RouteValueDictionary(htmlAttributes));
}
A bit counter intuitive but that's the standard in 2.
Edit: Updated tags to include mvc2

ASP.Net MVC Html.HiddenFor with wrong value

I'm using MVC 3 in my project, and I'm seeing a very strange behavior.
I'm trying to create a hidden field for a particular value on my Model, the problem is that for some reason the value set on the field does not correspond to the value in the Model.
e.g.
I have this code, just as a test:
<%:Html.Hidden("Step2", Model.Step) %>
<%:Html.HiddenFor(m => m.Step) %>
I would think that both hidden fields would have the same value.
What I do is, set the value to 1 the first time I display the View, and then after the submission I increase the value of the Model field by 1.
So, the first time I render the page both controls have the value 1, but the second time the values rendered are these:
<input id="Step2" name="Step2" type="hidden" value="2" />
<input id="Step" name="Step" type="hidden" value="1" />
As you can see, the first value is correct, but the second value seems to be the same as the first time I display the View.
What am I missing? Are the *For Html helpers caching the values in some way? If so, how can I disable this caching?.
Thanks for your help.
That's normal and it is how HTML helpers work. They first use the value of the POST request and after that the value in the model. This means that even if you modify the value of the model in your controller action if there is the same variable in the POST request your modification will be ignored and the POSTed value will be used.
One possible workaround is to remove this value from the model state in the controller action which is trying to modify the value:
// remove the Step variable from the model state
// if you want the changes in the model to be
// taken into account
ModelState.Remove("Step");
model.Step = 2;
Another possibility is to write a custom HTML helper which will always use the value of the model and ignore POST values.
And yet another possibility:
<input type="hidden" name="Step" value="<%: Model.Step %>" />
I encountered the same problem when writing a Wizard that shows different parts of a larger model at every step.
Data and/or Errors from "Step 1" would become mixed up with "Step 2", etc, until I finally realized that ModelState was to 'blame'.
This was my simple solution:
if (oldPageIndex != newPageIndex)
{
ModelState.Clear(); // <-- solution
}
return View(model[newPageIndex]);
This code will not work
// remove the Step variable from the model state
// if you want the changes in the model to be
// taken into account
ModelState.Remove("Step");
model.Step = 2;
...because HiddenFor always (!) reads from ModelState not the model itself. And if it doesn't find the "Step" key it will produce the default for that variable type which will be 0 in this case
Here is the solution. I wrote it for myself but don't mind sharing it cause I see many people are struggling with this naughty HiddenFor helper.
public static class CustomExtensions
{
public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
ReplacePropertyState(htmlHelper, expression);
return htmlHelper.HiddenFor(expression);
}
public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
ReplacePropertyState(htmlHelper, expression);
return htmlHelper.HiddenFor(expression, htmlAttributes);
}
public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
{
ReplacePropertyState(htmlHelper, expression);
return htmlHelper.HiddenFor(expression, htmlAttributes);
}
private static void ReplacePropertyState<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
string text = ExpressionHelper.GetExpressionText(expression);
string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(text);
ModelStateDictionary modelState = htmlHelper.ViewContext.ViewData.ModelState;
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (modelState.ContainsKey(fullName))
{
ValueProviderResult currentValue = modelState[fullName].Value;
modelState[fullName].Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), currentValue.Culture);
}
else
{
modelState[fullName] = new ModelState
{
Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), CultureInfo.CurrentUICulture)
};
}
}
}
Then you just use it as usual from within you view:
#Html.HiddenFor2(m => m.Id)
It worth to mention it works with collections too.
I am too struggling with the same situation I think, where I use the same model state between calls, and when I alter a model property on backend. Though, it does not matter for me, if I use textboxfor or hiddenfor.
I just bypass the situation by using page scripts to store the model value as a js variable, because I need the hiddenfield for that purpose in the beginning.
Not sure if this helps but just consider..

Anonymous type as object, how can I access a property?

I am adding some functionality to the HtmlHelper-class. Basically I want to automatically disable links on a web page based on user privileges e t c.
So I have this function:
public static string ActionLinkWithPrivileges(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues)
{
return LinkExtensions.ActionLink(htmlHelper, linkText, actionName, routeValues);
}
The problem here is the routeValues-argument. Its usually created as an anonymous type so I dont know what to cast it to. This anonymous type often has a property named "id" but just writing routeValue.id gives me a compiler error.
Any help would be appreciated!
This should work :
RouteValueDictionary routeVals = new RouteValueDictionary(routeValues);
var value = routeVals["key"];
//RouteValueDictionary is under System.Web.Routing
either implement an interface or use reflection to get the PropertyInfo and then itterate through the property collection to get the right one.
you would of course need to tell the method the name of the property to get unless it's of a particular type.

Resources