AspMvcAction annotation with default controller - c#

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

Related

How to deduplicate an automatic "Canonical Link" redirector in ASP.NET Core 3.1 C#?

For every web page within my ASP.NET Core 3.1 C# application, I want to automatically generate an canonical link for SEO purposes.
Currently (POC phase), I have the following snippet in every controller action function:
Uri actualUrl = new Uri($"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}{HttpContext.Request.Path}{HttpContext.Request.QueryString}");
RouteValueDictionary values = RouteData.Values;
values.Remove("controller");
values.Remove("action");
foreach (var q in HttpContext.Request.Query)
values.Add(q.Key, q.Value);
// Further route/query parameter "fixes" here.
Uri canonicalUrl = new Uri(Url.ActionLink(values: values));
if (!canonicalUrl.Equals(actualUrl))
return RedirectPermanentPreserveMethod(canonicalUrl.ToString());
This snippet first builds a Uri with the current actual URL. Then it may "fixes" some important route/query parameters (as shown below). Finally it compares the actual uri with the desired uri, and redirects to the desired uri, when the actual uri is different compared to the desired uri (case sensitive).
RouteData.Values["subpage"] = "Digital-Contents";
This process enables the web application to generate the correct canonical url ( http://example.com/MyController/MyAction/Digital-Contents ) for the following sample urls.
http://example.com/mycontroller/myaction/digital-contents
http://example.com/Mycontroller/Myaction/Digital-contents
http://example.com/myconTROLLer/myACTion/digital-Contents
However, the POC is a massive duplication of code, and thus not desirable itself.
My first thought was to use a middleware. However, with an middleware, the action controller cannot "fix" route/query parameters, which are out-of-scope of the regular routing construct (like the "id" route parameter which is shown in most ASP.NET examples). E.g. ActionLink is capable of producing the correct case sensitive url slugs for controller and action, but cannot process ("fix") other route/query parameters.
My second thought was to use a generic class, but there I lose the context.
The best solution would be a single (void) function call, which can be placed before the actual action heavy processing (inside the action controller before processing data and generating output).
How to deduplicate this "automatic canonical redirector" code?
This may not be the best solution, i just modified the case based on the code you provided:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CanonicalUrlAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
Uri actualUrl = new Uri($"{httpContext.Request.Scheme}://{httpContext.Request.Host}{httpContext.Request.Path}{httpContext.Request.QueryString}");
RouteValueDictionary values = filterContext.RouteData.Values;
values.Remove("controller");
values.Remove("action");
foreach (var q in httpContext.Request.Query)
values.Add(q.Key, q.Value);
// Further route/query parameter "fixes" here.
Uri canonicalUrl = new Uri(new UrlHelper(filterContext).ActionLink(values));
if (!canonicalUrl.Equals(actualUrl))
filterContext.Result = new LocalRedirectResult(canonicalUrl.ToString());
}
}
Usage
[CanonicalUrl]
public class HomeController : Controller {
}
If you're using names from view models to generate urls like example.com/some-category/some-product then i would use the helper in this Link to generate a slug in kebab case based on the model name (in my case its saved to db on model creation)
then with a custom route :
endpoints.MapControllerRoute(
name: "category",
pattern: "{Category}/{Product}",
defaults: new { controller = "Product", action = "Index" });
This pattern omits the action and controller names from route (which i prefer)
and gives you something like this example.com/some-category/some-product and in your action you just compare the model's slug with the route segment that is provided by the user (using the route contraint) like this:
public async Task<IActionResult> Index([FromRoute,Required] Category,[FromRoute,Required] Product)
and you do a route redirect on mismatch like this:
return RedirectToRoutePermanent("category", new { Product = Product.Slug, Category = Product.Category.Slug });
Hope this helps.

.NetCore 2.x on using {*catchAll} routing URL.Action always returns null

I am using this route to map all routes which were not found:
routes.MapRoute(
name: "NotFound",
template: "{*url}",
defaults: new { controller = "Home", action = "NotFound" }
);
The problem I encountered is that #Url.Action() always returns null on this route.
Could someone explain why is this happening and what could be alternatives to this?
you have to add the below code before the app.UseMvc(...[Routing] ...), make sure you use it in the right order because the order really matters, the pipeline in asp.net core is in reverse order meaning that if you add A before B then B will be called before A, you can read more here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1#order
app.UseStatusCodePagesWithReExecute("/error/{0}");
and in the controllers consider an ErrorController which contains different error codes here we just consider 404 and 500 errors in ErrorController, we must have the corresponding views for each error code (404, 500, Unknown)
public class ErrorController : ControllerBase
{
[Route("error/{code:int}")]
public ActionResult Error(int code)
{
switch (code)
{
case 404: return View("404");
case 500: return View("500");
default: return View("Unknown");
}
}
}
for more concise description please check Microsoft documentation here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-2.1
I suppose you call #Url.Action() either with route parameters (e.g. controller = "Home", action = "NotFound") or with route name - that makes no difference - to generate a URL to your not found page. Well, you said the URL can be any thing which effects in in-bound routing and it's just fine. But, when you are trying to generate URLs from a route (out-bound routing), the route hasn't any template to generate a URL. In route's perspective, it can be any URL. So, null is any URL too. So, the null will be returned.
After playing around, I found a "cheat" way to do it and it kinda works. If I get NotFound page then I redirect back to the same page if Url.Action() == null
if(this._urlService.Action() == null) //same urlHelper action, only has default Home page values passed into method
{
var query = string.Empty;
if(Request.QueryString.HasValue)
{
query = Request.QueryString.Value;
}
var path = "/Home/NotFound" + query;
path = string.Concat("/", this._userIdentity.CurrentLanguage.ToString().ToLower(), path);
return base.Redirect(path);
}
It could be because I use /{culture}/{Controller}/{Action} as my main route. Creating other test project, where my main route is default /{Controller}/{Action} has no problem at all finding the Url.Action() on NotFound page

How to get Uri of view returned by action method?

I need to serialize some object into xml, which has string property "Url" - url of page returned by some action method (in asp.net mvc 3 app I want to implement custom rss).
I guess to call action method I just need to instantiate the controller which this method belongs to :)
MyController c = new MyContrroller();
c.MyActionMethod();
But how can I get the url of the page returned by this action method??
Edit 1: As the #SLaks answered we can use Url.Action() to get action method url, but how can I pass this url to the xml file?? If I just assign the result of Url.Action() to the link it will display the string: MyController/MyAction.
Pages returned by action methods do not have URLs.
Instead, the action methods themselves have URLs that come from the routing engine.
You can get the URL to an action by calling Url.Action(actionName, controllerName).
I think there is no built in way to do what You need. You have to do it manually. For example like below:
public static class UrlExtension
{
public static string ToAbsoluteUrl(this string relativeUrl, HttpContext httpContext)
{
string http = "http" + (httpContext.Request.IsSecureConnection ? "s" : string.Empty);
string host = httpContext.Request.Url.Host;
string port = httpContext.Request.Url.Port == 80 ? string.Empty : string.Format(":{0}", httpContext.Request.Url.Port);
return string.Format("{0}://{1}{2}{3}", http, host, port, relativeUrl);
}
}
Example:
Index
TestAction
Render result:
Index
TestAction

Nested Html.Action calls in Razor

EDIT: Obviously this is a vastly simplified version of my site and if I make a test app, with this pattern it works fine. In our real app, we are using T4MVC and this is all within an area. I'm guessing one of these factors is causing my issue...
EDIT2: All the default routes are defined and if I navigate directly to /AreaName/ControllerName/SubChild?val=123 it renders.
I have a peculiar problem with Mvc and am hoping someone can help...
I have a controller with the following action methods
public ActionResult Index()
{
return View(GetModel());
}
public ActionResult Result Child(string blah)
{
return View(GetModel(blah));
}
public ActionResult Result SubChild(int val)
{
return View(GetModel(val));
}
I then have 3 razor views.
Index.cshtml
<div>
#Html.Action("Child", new { blah = "raaa"})
</div>
Child.cshtml
<div>
#*ERROR HERE*#
#Html.Action("SubChild", new { val = 123})
</div>
SubChild.cshtml
<h1>#Model.val</h1>
When I navigate to / I get an exception thrown saying that
"No route in the route table matches the supplied values." on the Html.Action calling the SubChild Action.
This is all within the same area and the same controller. If I change the markup and use Html.Partial for the call to the Child view (and construct the model and pass it in the view), it renders fine. The issue comes when I call Html.Action within a view that's already being rendered using Html.Action.
I've tried fully qualifying the action using
/area/controller/action, specifying the controller in the Html.Action call, passing the area as a parameter in the route values and combinations of all of these.
Does anyone have any ideas what this might be? I'm assuming that you can call Html.Action in Views that are being rendered using it, I guess I might be wrong...
Well, out of the box MVC 3 has the default route parameter named id. Your SubChild action has a parameter named val, so that is probably the issue.
Either rename the parameter in the Action to id, or add a new route
routes.MapRoute(
"SubChild",
"{controller}/SubChild/{val}",
new
{
controller = "ControllerName",
action = "SubChild",
val = UrlParameter.Optional
}
);
Are your parameters really named blahand val? Because normally the first parameter is always called id. Check the method RegisterRoutes(RouteCollection routes) in your global.asax.cs. There must be something like
routes.MapRoute("Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }); // Parameter defaults
That indicates how your parameters have to be named.
I think your Actions have to be like this:
public ActionResult Index()
{
return View(GetModel());
}
public ActionResult Result Child(string id)
{
return View(GetModel(id));
}
public ActionResult Result SubChild(int id)
{
return View(GetModel(id));
}
Then the code in your views has to be:
Index.cshtml
<div>
#Html.Action("Child", new { id = "raaa"})
</div>
Child.cshtml
<div>
#Html.Action("SubChild", new { id = 123})
</div>
It appears the problem is to do with our areas and routing setup.
On the 2nd pass, we are losing the reference to the area in the routevaluedictionary and as such it can't find the correct route. Where we are registering the area, we need to register the correct route.
Thanks for the help with this, I've upvoted the other answers as I think they may help someone else in the future.

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