My view actionlink does not send a parameter - asp.net-mvc

Hi i have this actionlink in my "cursos" index view(wich redirects to another controller)
#Html.ActionLink("Ver Alumnos", "Index", "Usuarios", new { id= item.idCurso}, null)
in my controller i got this
public ActionResult Index(int? idCursos)
{
var usuarios = db.Usuario.ToList();
if(!(idCursos == null))
{
var query = from Usuario in db.Usuario
from Relaciones in db.Relaciones
where Relaciones.Cursos.idCurso == idCursos
select new Usuario
{
Nombre = Usuario.Nombre,
Apellido = Usuario.Apellido,
DNI = Usuario.DNI,
};
usuarios = query.ToList();
}
the problem is that it never enter that if beacuse the idCursos is always null, even though my link route in the browser looks like this
http://localhost:54680/Usuarios/Index/3
Thanks

The action parameter name must match the parameter passed to the actionlink, so in your case
public ActionResult Index(int? id)
should take care of it
Update: I just noticed your URL, it is likely that your routing is configured to map to "id" parameter by default (if you haven't changed the default VS template).So even though the above correction works ... it is because the framework is expecting to map to the parameter "id" as configured in the routing.

Related

MVC Attribute routing with Url.Action not resolving route

I cannot get #Url.Action to resolve to the url I am expecting based on the attribute route I have applied:
My action (SearchController but with [RoutePrefix("add")])
[Route("{searchTerm}/page/{page?}", Name = "NamedSearch")]
[Route("~/add")]
public ActionResult Index(string searchTerm = "", int page = 1)
{
...
}
Call to Url.Action
#Url.Action("Index", new { controller = "Search", searchTerm = "replaceMe", page = 1 })
This results in a url of
/add?searchTerm=replaceMe&page=1
I would expect
/add/replaceMe/page/1
If I type the url manually then it resolves to the correct action with the correct parameters. Why doesn't #Url.Action resolve the correct url?
Since you have a name for your pretty route definition, you may use the RouteUrl method.
#Url.RouteUrl("NamedSearch", new { searchTerm = "replaceMe", page = 1})
And since you need add in the url, you should update your route definition to include that in the url pattern.
[Route("~/add")]
[Route("~/add/{searchTerm?}/page/{page?}", Name = "NamedSearch")]
public ActionResult Index(string searchTerm = "", int page = 1)
{
// to do : return something
}
Routes are order sensitive. However, attributes are not. In fact, when using 2 Route attributes on a single action like this you may find that it works on some compiles and not on others because Reflection does not guarantee an order when analyzing custom attributes.
To ensure your routes are entered into the route table in the correct order, you need to add the Order property to each attribute.
[Route("{searchTerm}/page/{page?}", Name = "NamedSearch", Order = 1)]
[Route("~/add", Order = 2)]
public ActionResult Index(string searchTerm = "", int page = 1)
{
return View();
}
After you fix the ordering problem, the URL resolves the way you expect.
#Url.Action("Index", new { controller = "Search", searchTerm = "replaceMe", page = 1 })
// Returns "/add/replaceMe/page/1"
To return full URL use this
#Url.Action("Index", new { controller = "Search", searchTerm = "replaceMe", page = 1}, protocol: Request.Url.Scheme)
// Returns "http://yourdomain.com/add/replaceMe/page/1"
Hope this helps someone.

Reference DropDownList selected value from enclosing Form

I'm just getting started with MVC5 (from WebForms), and dropdownlist bindings are giving me some fits.
I'd like to get this working using a GET request back to the page, with a selected value parameter. I'm hopeful that I can specify the route arguments in the form itself, so I'd like to reference the DDL's SelectedValue.
<p>
#using (Html.BeginForm("Index", "Profile", FormMethod.Get, new { id = WHATDOIPUTHERE} )) {
#Html.AntiForgeryToken()
#Html.DropDownList("ApplicationID", new SelectList(ViewBag.ApplicationList, "ApplicationID", "ApplicationName", ViewBag.SelectedApplicationId), new {onchange = "this.form.submit();"})
}
</p>
I can make it work with a POST form, but that requires a second controller method so I end up with
public ActionResult Index(long? id) {
ConfigManager config = new ConfigManager();
//handle application. default to the first application returned if none is supplied.
ViewBag.ApplicationList = config.GetApplications().ToList();
if (id != null) {
ViewBag.SelectedApplicationId = (long)id;
}
else {
ViewBag.SelectedApplicationId = ViewBag.ApplicationList[0].ApplicationID; //just a safe default, if no param provided.
}
//handle profile list.
List<ProfileViewModel> ps = new List<ProfileViewModel>();
ps = (from p in config.GetProfilesByApp((long)ViewBag.SelectedApplicationId) select new ProfileViewModel(p)).ToList();
return View(ps);
}
//POST: Profile
//read the form post result, and recall Index, passing in the ID.
[HttpPost]
public ActionResult index(FormCollection collection) {
return RedirectToAction("Index", "Profile", new {id = collection["ApplicationId"]});
}
It would be really nice to get rid of the POST method, since this View only ever lists child entities.
What do you think?
You can update your GET action method parameter name to be same as your dropdown name.
I also made some small changes to avoid possible null reference exceptions.
public ActionResult Index(long? ApplicationID) {
var config = new ConfigManager();
var applicationList = config.GetApplications().ToList();
ViewBag.ApplicationList = applicationList ;
if (ApplicationID!= null) {
ViewBag.SelectedApplicationId = ApplicationID.Value;
}
else
{
if(applicationList.Any())
{
ViewBag.SelectedApplicationId = applicationList[0].ApplicationID;
}
}
var ps = new List<ProfileViewModel>();
ps = (from p in config.GetProfilesByApp((long)ViewBag.SelectedApplicationId)
select new ProfileViewModel(p)).ToList();
return View(ps);
}

Pretty URL ASP.NET MVC

How can I get pretty urls like localhost:8888/News/Example-post instead of localhost:8888/Home/Details/2
My HomeController has the following for the Details method
public ActionResult Details(int id)
{
var ArticleToView = (from m in _db.ArticleSet where m.storyId == id select m).First();
return View(ArticleToView);
As the ASP.NET routing system is somewhat complicated, there are many ways to accomplish what you describe.
First of all, do you just want to have a pretty URL for the Details method? If so, you might consider renaming HomeController to NewsController or moving the Details method into a new NewsController class - that will automatically form the /News part of the URL. If you don't want a /Details part, you might rename your Details method Index, as that will be automatically called by /News. Finally, you need to change your int id parameter into string name.
If you want many custom URLs, you're going to have to define your own routes. Here are two ways of doing this:
1.
The easiest way I've found is to use an ASP.NET MVC Attribute-Based Route Mapper. That way, all you have to do is add an attribute on each method you want a pretty URL for and specify what URL you want.
First, you must follow a few steps to set up the attribute-based route mapping system, as outlined on that link.
After completing those steps, you must change your method to look like this:
[Url("News/{name}")]
public ActionResult Details(string name)
{
var ArticleToView = (from m in _db.ArticleSet where m.storyName == name select m).First();
return View(ArticleToView);
}
2.
Alternatively, you can define your custom routes manually in Global.asax.cs. In your RegisterRoutes method, you can add the following in the middle:
routes.MapRoute(
"NewsDetails",
"News/{name}",
new { controller = "News", action = "Details", name = "" }
);
What I do on my sites is that I check the URL against either the Page Title or Page Stub in cases where the page titles could have the same name for instance if you have a site that posts a "Picture of the Week" you may want to use a stub instead of title as you'll have multiples named the same thing.
URLs look like this: http://mySite.com/Page/Verse-of-the-Week
Global.asax contains this:
routes.MapRoute("Pages", "{controller}/{pageID}", new { controller = "Page", action = "Index", pageID = "Home" });
PageController is this:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index(string pageID)
{
if (pageID == null)
{
pageID = pageRepository.HomeOrLowest();
}
var p = pageRepository.ByStub(pageID);
if (p == null) { return RedirectToAction("NotFound", "Error"); }
return View(p);
}
The repository looks like this:
private static Func<mvCmsContext, string, Page> _byStub =
CompiledQuery.Compile((mvCmsContext context, string pageTitle) =>
(from p in context.Pages
where p.pageTitle.Replace(" ", "-") == pageTitle
select p).SingleOrDefault());
public Page ByStub(string pageTitle)
{
return _byStub(context, pageTitle);
}
I hope that helps.
Edit to add duplicate handling:
private static Func<mvCmsContext, string, int> _pageExists =
CompiledQuery.Compile((mvCmsContext context, string pageTitle) =>
(from p in context.Pages
where p.pageTitle.Replace(" ", "-") == pageTitle
select p).Count());
public bool PageExists(string pageTitle)
{
return Convert.ToBoolean(_pageExists(context, pageTitle));
}
Validates like this:
IValidationErrors errors = new ValidationErrors();
if (CreateOrEdit == "Create")
{
if (pageRepository.PageExists(model.pageTitle) && !String.IsNullOrEmpty(model.pageTitle))
errors.Add("pageTitle", "A page with this title already exists. Please edit it and try again.");
}
Please check out this package I've created: https://www.nuget.org/packages/LowercaseDashedRoute/
And read the one-line configuration here: https://github.com/AtaS/lowercase-dashed-route

Asp.net MVC ModelState.Clear

Can anyone give me a succinct definition of the role of ModelState in Asp.net MVC (or a link to one). In particular I need to know in what situations it is necessary or desirable to call ModelState.Clear().
Bit open ended huh... sorry, I think it might help if tell you what I'm acutally doing:
I have an Action of Edit on a Controller called "Page". When I first see the form to change the Page's details everything loads up fine (binding to a "MyCmsPage" object). Then I click a button that generates a value for one of the MyCmsPage object's fields (MyCmsPage.SeoTitle). It generates fine and updates the object and I then return the action result with the newly modified page object and expect the relevant textbox (rendered using <%= Html.TextBox("seoTitle", page.SeoTitle)%>) to be updated ... but alas it displays the value from the old model that was loaded.
I've worked around it by using ModelState.Clear() but I need to know why / how it has worked so I'm not just doing it blindly.
PageController:
[AcceptVerbs("POST")]
public ActionResult Edit(MyCmsPage page, string submitButton)
{
// add the seoTitle to the current page object
page.GenerateSeoTitle();
// why must I do this?
ModelState.Clear();
// return the modified page object
return View(page);
}
Aspx:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyCmsPage>" %>
....
<div class="c">
<label for="seoTitle">
Seo Title</label>
<%= Html.TextBox("seoTitle", page.SeoTitle)%>
<input type="submit" value="Generate Seo Title" name="submitButton" />
</div>
I think is a bug in MVC. I struggled with this issue for hours today.
Given this:
public ViewResult SomeAction(SomeModel model)
{
model.SomeString = "some value";
return View(model);
}
The view renders with the original model, ignoring the changes. So I thought, maybe it does not like me using the same model, so I tried like this:
public ViewResult SomeAction(SomeModel model)
{
var newModel = new SomeModel { SomeString = "some value" };
return View(newModel);
}
And still the view renders with the original model. What's odd is, when I put a breakpoint in the view and examine the model, it has the changed value. But the response stream has the old values.
Eventually I discovered the same work around that you did:
public ViewResult SomeAction(SomeModel model)
{
var newModel = new SomeModel { SomeString = "some value" };
ModelState.Clear();
return View(newModel);
}
Works as expected.
I don't think this is a "feature," is it?
Update:
This is not a bug.
Please stop returning View() from a POST action. Use PRG instead and redirect to a GET if the action is a success.
If you are returning a View() from a POST action, do it for form validation, and do it the way MVC is designed using the built in helpers. If you do it this way then you shouldn't need to use .Clear()
If you're using this action to return ajax for a SPA, use a web api controller and forget about ModelState since you shouldn't be using it anyway.
Old answer:
ModelState in MVC is used primarily to describe the state of a model object largely with relation to whether that object is valid or not. This tutorial should explain a lot.
Generally you should not need to clear the ModelState as it is maintained by the MVC engine for you. Clearing it manually might cause undesired results when trying to adhere to MVC validation best practises.
It seems that you are trying to set a default value for the title. This should be done when the model object is instantiated (domain layer somewhere or in the object itself - parameterless ctor), on the get action such that it goes down to the page the 1st time or completely on the client (via ajax or something) so that it appears as if the user entered it and it comes back with the posted forms collection. Some how your approach of adding this value on the receiving of a forms collection (in the POST action // Edit) is causing this bizarre behaviour that might result in a .Clear() appearing to work for you. Trust me - you don't want to be using the clear. Try one of the other ideas.
If you want to clear a value for an individual field then I found the following technique useful.
ModelState.SetModelValue("Key", new ValueProviderResult(null, string.Empty, CultureInfo.InvariantCulture));
Note:
Change "Key" to the name of the field that you want to reset.
Well lots of us seem to have been bitten by this, and although the reason this happens makes sense I needed a way to ensure that the value on my Model was shown, and not ModelState.
Some have suggested ModelState.Remove(string key), but it's not obvious what key should be, especially for nested models. Here are a couple methods I came up with to assist with this.
The RemoveStateFor method will take a ModelStateDictionary, a Model, and an expression for the desired property, and remove it. HiddenForModel can be used in your View to create a hidden input field using only the value from the Model, by first removing its ModelState entry. (This could easily be expanded for the other helper extension methods).
/// <summary>
/// Returns a hidden input field for the specified property. The corresponding value will first be removed from
/// the ModelState to ensure that the current Model value is shown.
/// </summary>
public static MvcHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression)
{
RemoveStateFor(helper.ViewData.ModelState, helper.ViewData.Model, expression);
return helper.HiddenFor(expression);
}
/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model. Call this when changing
/// Model values on the server after a postback, to prevent ModelState entries from taking precedence.
/// </summary>
public static void RemoveStateFor<TModel, TProperty>(this ModelStateDictionary modelState, TModel model,
Expression<Func<TModel, TProperty>> expression)
{
var key = ExpressionHelper.GetExpressionText(expression);
modelState.Remove(key);
}
Call from a controller like this:
ModelState.RemoveStateFor(model, m => m.MySubProperty.MySubValue);
or from a view like this:
#Html.HiddenForModel(m => m.MySubProperty.MySubValue)
It uses System.Web.Mvc.ExpressionHelper to get the name of the ModelState property.
Well the ModelState basically holds the current State of the model in terms of validation, it holds
ModelErrorCollection: Represent the errors when the model try to bind the values.
ex.
TryUpdateModel();
UpdateModel();
or like a parameter in the ActionResult
public ActionResult Create(Person person)
ValueProviderResult: Hold the details about the attempted bind to the model.
ex. AttemptedValue, Culture, RawValue.
Clear() method must be use with caution because it can lead to unspected results. And you will lose some nice properties of the ModelState like AttemptedValue, this is used by MVC in the background to repopulate the form values in case of error.
ModelState["a"].Value.AttemptedValue
I had an instance where I wanted to update the model of a sumitted form, and did not want to 'Redirect To Action' for performanace reason. Previous values of hidden fields were being retained on my updated model - causing allsorts of issues!.
A few lines of code soon identified the elements within ModelState that I wanted to remove (after validation), so the new values were used in the form:-
while (ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")).Value != null)
{
ModelState.Remove(ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")));
}
I wanted to update or reset a value if it didn't quite validate, and ran into this problem.
The easy answer, ModelState.Remove, is.. problematic.. because if you are using helpers you don't really know the name (unless you stick by the naming convention). Unless perhaps you create a function that both your custom helper and your controller can use to get a name.
This feature should have been implemented as an option on the helper, where by default is does not do this, but if you wanted the unaccepted input to redisplay you could just say so.
But at least I understand the issue now ;).
Got it in the end. My Custom ModelBinder which was not being registered and does this :
var mymsPage = new MyCmsPage();
NameValueCollection frm = controllerContext.HttpContext.Request.Form;
myCmsPage.SeoTitle = (!String.IsNullOrEmpty(frm["seoTitle"])) ? frm["seoTitle"] : null;
So something that the default model binding was doing must have been causing the problem. Not sure what, but my problem is at least fixed now that my custom model binder is being registered.
Generally, when you find yourself fighting against a framework standard practices, it is time to reconsider your approach. In this case, the behavior of ModelState. For instance, when you don't want model state after a POST, consider a redirect to the get.
[HttpPost]
public ActionResult Edit(MyCmsPage page, string submitButton)
{
if (ModelState.IsValid) {
SomeRepository.SaveChanges(page);
return RedirectToAction("GenerateSeoTitle",new { page.Id });
}
return View(page);
}
public ActionResult GenerateSeoTitle(int id) {
var page = SomeRepository.Find(id);
page.GenerateSeoTitle();
return View("Edit",page);
}
EDITED to answer culture comment:
Here is what I use to handle a multi-cultural MVC application. First the route handler subclasses:
public class SingleCultureMvcRouteHandler : MvcRouteHandler {
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var culture = requestContext.RouteData.Values["culture"].ToString();
if (string.IsNullOrWhiteSpace(culture))
{
culture = "en";
}
var ci = new CultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
return base.GetHttpHandler(requestContext);
}
}
public class MultiCultureMvcRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var culture = requestContext.RouteData.Values["culture"].ToString();
if (string.IsNullOrWhiteSpace(culture))
{
culture = "en";
}
var ci = new CultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
return base.GetHttpHandler(requestContext);
}
}
public class CultureConstraint : IRouteConstraint
{
private string[] _values;
public CultureConstraint(params string[] values)
{
this._values = values;
}
public bool Match(HttpContextBase httpContext,Route route,string parameterName,
RouteValueDictionary values, RouteDirection routeDirection)
{
// Get the value called "parameterName" from the
// RouteValueDictionary called "value"
string value = values[parameterName].ToString();
// Return true is the list of allowed values contains
// this value.
return _values.Contains(value);
}
}
public enum Culture
{
es = 2,
en = 1
}
And here is how I wire up the routes. After creating the routes, I prepend my subagent (example.com/subagent1, example.com/subagent2, etc) then the culture code. If all you need is the culture, simply remove the subagent from the route handlers and routes.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("Content/{*pathInfo}");
routes.IgnoreRoute("Cache/{*pathInfo}");
routes.IgnoreRoute("Scripts/{pathInfo}.js");
routes.IgnoreRoute("favicon.ico");
routes.IgnoreRoute("apple-touch-icon.png");
routes.IgnoreRoute("apple-touch-icon-precomposed.png");
/* Dynamically generated robots.txt */
routes.MapRoute(
"Robots.txt", "robots.txt",
new { controller = "Robots", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"Sitemap", // Route name
"{subagent}/sitemap.xml", // URL with parameters
new { subagent = "aq", controller = "Default", action = "Sitemap"}, new[] { "aq3.Controllers" } // Parameter defaults
);
routes.MapRoute(
"Rss Feed", // Route name
"{subagent}/rss", // URL with parameters
new { subagent = "aq", controller = "Default", action = "RSS"}, new[] { "aq3.Controllers" } // Parameter defaults
);
/* remap wordpress tags to mvc blog posts */
routes.MapRoute(
"Tag", "tag/{title}",
new { subagent = "aq", controller = "Default", action = "ThreeOhOne", id = UrlParameter.Optional}, new[] { "aq3.Controllers" }
).RouteHandler = new MultiCultureMvcRouteHandler(); ;
routes.MapRoute(
"Custom Errors", "Error/{*errorType}",
new { controller = "Error", action = "Index", id = UrlParameter.Optional}, new[] { "aq3.Controllers" }
);
/* dynamic images not loaded from content folder */
routes.MapRoute(
"Stock Images",
"{subagent}/Images/{*filename}",
new { subagent = "aq", controller = "Image", action = "Show", id = UrlParameter.Optional, culture = "en"}, new[] { "aq3.Controllers" }
);
/* localized routes follow */
routes.MapRoute(
"Localized Images",
"Images/{*filename}",
new { subagent = "aq", controller = "Image", action = "Show", id = UrlParameter.Optional}, new[] { "aq3.Controllers" }
).RouteHandler = new MultiCultureMvcRouteHandler();
routes.MapRoute(
"Blog Posts",
"Blog/{*postname}",
new { subagent = "aq", controller = "Blog", action = "Index", id = UrlParameter.Optional}, new[] { "aq3.Controllers" }
).RouteHandler = new MultiCultureMvcRouteHandler();
routes.MapRoute(
"Office Posts",
"Office/{*address}",
new { subagent = "aq", controller = "Offices", action = "Address", id = UrlParameter.Optional }, new[] { "aq3.Controllers" }
).RouteHandler = new MultiCultureMvcRouteHandler();
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { subagent = "aq", controller = "Home", action = "Index", id = UrlParameter.Optional }, new[] { "aq3.Controllers" } // Parameter defaults
).RouteHandler = new MultiCultureMvcRouteHandler();
foreach (System.Web.Routing.Route r in routes)
{
if (r.RouteHandler is MultiCultureMvcRouteHandler)
{
r.Url = "{subagent}/{culture}/" + r.Url;
//Adding default culture
if (r.Defaults == null)
{
r.Defaults = new RouteValueDictionary();
}
r.Defaults.Add("culture", Culture.en.ToString());
//Adding constraint for culture param
if (r.Constraints == null)
{
r.Constraints = new RouteValueDictionary();
}
r.Constraints.Add("culture", new CultureConstraint(Culture.en.ToString(), Culture.es.ToString()));
}
}
}
Well, this seemed to work on my Razor Page and never even did a round trip to the .cs file.
This is old html way. It might be useful.
<input type="reset" value="Reset">

GetVirtualPath method in mvc

Can anyone explain how the virtual path is being calculating?
According to the RouteData.Values or according to the url pattern?
I'm trying to remove some routedata values but still the virtual path is not changes.
I have a problem that the virtual path return with redundant slash at the beginning of the URL like : /he/controller/action the slash before culture is redundant...
I'm using custom routes like the following
routes.Add("Default",
new CustomRoute("{culture}/{controller}/{action}/{id}",
new
{
controller = "Desktop",
action = "Index",
culture = "he-IL",
guid = "",
id = UrlParameter.Optional
}));
routes.Add("Wizard_" + wizard,
new CustomRoute("{guid}/{culture}/" + wizardName + "/{action}/{id}",
new
{
controller = wizard,
action = "Index",
culture = "he-IL",
guid = "",
id = UrlParameter.Optional
}));
the problem is when using Url.Action(action, controller) method and the action is in the wizard controller, so the URL for the action is wizard format like {guid}/{culture}/" + wizard + "/{action}/{id}
bu the guid value is empty and the returned URL is //he-il/controller/action
instead of /he-il/controller/action
The CustomRoute class:
public class CustomRoute : Route
{
private List<string> _wizards;
public CustomRoute(string uri, object defaults)
: base(uri, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
_wizards = new List<string>();
FillWizards(ref _wizards);
DataTokens = new RouteValueDictionary();
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
bool hasGuid = httpContext.Request.RequestContext.RouteData != null
&& httpContext.Request.RequestContext.RouteData.Values != null
&& httpContext.Request.RequestContext.RouteData.Values.ContainsKey("guid")
&& !httpContext.Request.RequestContext.RouteData.Values["guid"].ToString().Equals(Guid.Empty);
var routeData = base.GetRouteData(httpContext);
if (routeData == null)
return null;
bool isWizard = _wizards.Contains(routeData.Values["controller"].ToString());
Debug.WriteLine("Controller: " + routeData.Values["controller"] + " action: " + routeData.Values["action"] + " Is wizard: " + isWizard + " has guid: " + hasGuid);
if (isWizard && !hasGuid)
{
if (string.IsNullOrEmpty(routeData.Values["guid"].ToString()))
{
routeData.Values["guid"] = Guid.NewGuid().ToString("N");
}
}
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData path;
path = base.GetVirtualPath(requestContext, values);
return path;
}
private void FillWizards(ref List<string> items)
{
var _configuration = ObjectFactory.GetInstance<IConfiguration>();
List<string> wizards = _configuration.GetParamValue<string>("SessionUniqueWizards", "").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
items = wizards;
}
}
The reason why your guid parameter is missing is because
There is no guid parameter in your call to Url.Action(action, controller).
There is (apparently) no guid parameter in the current request. That is, the current route that is being hit has no guid route value.
You have specified the default value for the guid (guid = ""). Since empty string is what you specified as the default, empty string is what you are getting by default.
For the URL to build correctly, the guid has to come from somewhere. MVC always passes matching route values from the current request when building outgoing URLs, but since not all of your URLs have a guid you need to specify it for the pages where it does not exist in the context:
Url.Action("Index", "Search", new { guid = "a565f84f9152495792d433f5bd26000f")
This is the normal way to do it. Typically, if you are building the link for a CRUD operation you are doing so within a list of entities.
For example, for a Product entity, you would normally have links for Edit Product and Delete Product for each Product in a list that would look something like this:
<tr>
<td>model.ProductName</td>
<td>#Html.ActionLink("Product", "Edit", new { guid = model.ProductId })</td>
<td>#Html.ActionLink("Product", "Delete", new { guid = model.ProductId })</td>
</tr>
There would also typically be a link to add a new entity that has no identifier.
#Html.ActionLink("Product", "Add")
But it is unclear from your example how a "wizard" would be created. Creating a new identifier should normally be a function of the Add method, not that of a route.
But there seems to be an issue where you are randomly generating a GUID within your route, so it is unclear how you expect this value to be maintained from one request to the next.

Resources