Best Method to run Subdomains in MVC4 - asp.net-mvc

I have a website that is in a pre-integration phase. In other words, I have ensured that the site runs fine on my local Development Server (VS2012) utilizing the dynamically generated ASP.NET Development Server that runs at the time of debug executions; and I have now created a sub-domain of my domain on the Web Host Server and deployed my site there.
My decision to do this was because I obviously don't want users accessing the site until it has undergone thorough testing on the actual Host. My problem is though most of the site functions without issue (including URL's), there are a few links that produce the HTTP 404 error.
"The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly."
It then references the View/Controller path in the context of the Error that I know exists.
Why it only happens for certain Uri requests and not others is somewhat puzzling to me.
So I am strongly suspecting that it has something to do with the Default Routing Configuration for MVC and I believe if I were to move the site to the main domain, the issue will likely resolve but then again, it would defeat the purpose of setting it up in the SubDomain for testing before public access.
I need some viable options here but don't no where to start.
Should I address the issue from the perspective of the Routing Configuration and create 2 separate Global.asax.cs files? One for the domain and the other for the subdomain testing? And if so, How should I modify the file to accomodate for the Subdomain.
Or is there a more elegant solution for approaching the Integration process?
----------------- UPDATE ---------------------
So I've been troubleshooting the problem and it appears as though the 404 Error is only being generated for a method in my Controller that is returning a string.
I have a function that is being called in my View that looks like this:
<script>
function Subscribe(slvl) {
$.ajax({
type: 'POST',
datatype: "text",
data: { level: slvl },
url: '#Url.Action("Upgrade", "Profile")',
success: function (result) {
if (result) {
window.location = result;
}
},
error: function onError(result) {
alert(result.responseText);
}
});
}
</script>
I cannot post the full details of the Controller but it simply returns a string and whether I were to post the entire method or a simple one, the results would be the same. So for illustration only it looks something like this.
[HttpPost]
public string Upgrade(string level)
{
var uri = "http://www.someUri.com?Upgrade=" + level;
return uri;
}
This code is producing a HTTP 404 Error complaining that the path Profile/Upgrade cannot be found.
But I've found that if I use reference to a different method being called in the same Controller with the only exception being that it returns an ActionResult to a different View, the Error goes away and I'm redirected to the alternate view.
Any Ideas? So maybe it has nothing to do with the Subdomain???

First of all you need to create your own Custom Route
public class SubDomainRoute : Route
{
private readonly string[] namespaces;
public SubDomainRoute(string url, object defaults, string[] namespaces)
: base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
this.namespaces = namespaces;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
if (the subdomain is the expected one, then update your route data)
{
var routeData = base.GetRouteData(httpContext);
if (this.namespaces != null && this.namespaces.Length > 0)
{
routeData.DataTokens["Namespaces"] = this.namespaces;
}
routeData.DataTokens["Area"] = "Your Subdomain Area";
routeData.DataTokens["UseNamespaceFallback"] = bool.FalseString;
return routeData;
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
if (is your expected subdomain)
{
return base.GetVirtualPath(requestContext, values);
}
return null;
}
}
Then you have to use the custom route in the area registration like this:
public override void RegisterArea(AreaRegistrationContext context)
{
context.Routes.Add("YourAreaName_default", new CustomSubDomainRoute(
"{controller}/{action}/{id}",
new { area = "Your Area", controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { typeof(Controllers.HomeController).Namespace } // Namespaces defaults
));
}
And finally, my advise is to use redirect to route, because the routeData Area is lost between the different requests.
Use your global property route name when redirecting.
[Authorize]
public class YourController : Controller
{
protected virtual string RouteName
{
get
{
return "YourRouteName";
}
}
public ActionResult Test(params here)
{
return RedirectToRoute(this.RouteName, new { action = "your action" });
}
}
Ref: http://forums.asp.net/t/1967197.aspx?Subdomain+in+mvc4+can+not+redirect+to+controller+in+area+

Related

Redirect to specified action if requested action was not found

How can I redirect Action which is not found in controller into another action within the same controller? Let's say that file abc.txt is requested via http://localhost:5000/Link/GetFile/abc.txt. My controller correctly serving that file. But now, i need to handle request such as http://localhost:5000/Link/Document/abc. Of course there is no any action matched to Document so I need to invoke function Error within the same controller (including id from original request).
I tried to solve this with StatusCodePagesWithReExecute function but then my File action is not working (each request goes directly to Error function).
I have following controller:
public class LinkController : ControllerBase
{
public IActionResult GetFile(string id)
{
return DownloadFile(id);
}
public IActionResult Error(string id)
{
return File("~/index.html", "text/html");
}
private FileResult DownloadFile(string fileName)
{
IFileProvider provider = new PhysicalFileProvider(#mypath);
IFileInfo fileInfo = provider.GetFileInfo(fileName);
var readStream = fileInfo.CreateReadStream();
return File(readStream, "text/plain");
}
}
and startup configuration:
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "application/octet-stream",
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}"
);
});
Any clues how to solve this problem?
Regards
You can use UseStatusCodePages to achieve a simple redirection whenever there's a 404. Here's what it looks like:
app.UseStatusCodePages(ctx =>
{
if (ctx.HttpContext.Response.StatusCode == 404)
ctx.HttpContext.Response.Redirect("/Path/To/Your/Action");
return Task.CompletedTask;
});
Just add this somewhere above UseMvc.
EDIT:
I´m sorry, my first answer was not correct.
IRouteCollection router = RouteData.Routers.OfType<IRouteCollection>().First();
with this, you can match an url to controller action
Create HttpContext for testing (example with injection)
private readonly IHttpContextFactory _httpContextFactory;
public HomeController(
IHttpContextFactory httpContextFactory)
{
_httpContextFactory = httpContextFactory;
}
Create the context with values
HttpContext context = _httpContextFactory.Create(HttpContext.Features);
context.Request.Path = "/Home/Index";
context.Request.Method = "GET";
Check route
var routeContext = new RouteContext(context);
await router.RouteAsync(routeContext);
bool exists = routeContext.Handler != null;
Further reading: https://joonasw.net/view/find-out-if-url-matches-action

How do I form the URL to call this WebAPI function?

I have this controller in an MVC 5 WebAPI application and I can't seem to figure out how to form the URL to call it. I keep getting a 404. tried .../ssa, /ssa/ssamedians, /ssa/ssamedians?titles=abc... What am I missing?
public class ssaController : ApiController
{
public IHttpActionResult getSsaMedians(string Titles = "")
{
SsaDB db = new SsaDB();
try
{
IEnumerable<Title_Medians> medians = db.getTitleMedians(Titles, null, null);
System.Web.HttpContext.Current.Response.AppendHeader("Access-Control-Allow-Origin", "*");
return Ok(medians);
}
catch
{
return NotFound();
}
}
There are also Actions called getSsaMediansByAaa() and getSsaMediansByBbb(). Once I got rid of the api/ in the routeTemplate, I now get a "Multiple actions were found that match the request".
Open your WebApiConfig.cs file and add this:
config.Routes.MapHttpRoute(
name: "SSATitles",
routeTemplate: "api/{controller}/{action}/{Titles}",
defaults: new { controller = "ssa", action = "getSsaMedians", Titles = UrlParameter.Optional }
);
For more information on webapi routing look here:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
You have an parameter in your method, that means that you need to add an extra link in your url.
Examlple.com/DirToApi/getSsaMedians/YourString
You may also want to check your RouteConfig file in your App_Start

Redirect with ASP.NET MVC MapRoute

On my site, I have moved some images from one folder to another.
Now, when I receive a request for old images '/old_folder/images/*' I want to make a permanent redirect to new folder with these images '/new_folder/images/*'
For example:
/old_folder/images/image1.png => /new_folder/images/image1.png
/old_folder/images/image2.jpg => /new_folder/images/image2.jpg
I have added a simple redirect controller
public class RedirectController : Controller
{
public ActionResult Index(string path)
{
return RedirectPermanent(path);
}
}
Now I need to setup proper routing, but I don't know how to pass the path part to the path parameter.
routes.MapRoute("ImagesFix", "/old_folder/images/{*pathInfo}", new { controller = "Redirect", action = "Index", path="/upload/images/????" });
Thanks
I would do in next way
routes.MapRoute("ImagesFix", "/old_folder/images/{path}", new { controller = "Redirect", action = "Index" });
and in controller like that
public class RedirectController : Controller
{
public ActionResult Index(string path)
{
return RedirectPermanent("/upload/images/" + path);
}
}
first download and install RouteMagic package from this link , then redirect your old address to the new address Like the below code :
var NewPath = routes.MapRoute("new", "new_folder/images/{controller}/{action}");
var OldPath = routes.MapRoute("new", "old_folder/images/{controller}/{action}");
routes.Redirect(OldPath ).To(NewPath );
for more information please check out the following link
Redirecting Routes To Maintain Persistent URLs
Answer above using RouteMagic is a good idea, but the example code is wrong (it's included in Phil's post as a bad example).
From the RouteMagic Github demo site global.asax.cs:
// Redirect From Old Route to New route
var targetRoute = routes.Map("target", "yo/{id}/{action}", new { controller = "Home" });
routes.Redirect(r => r.MapRoute("legacy", "foo/{id}/baz/{action}")).To(targetRoute, new { id = "123", action = "index" });
If you specify two routes, you will be setting up an extra mapping that will catch URLs which you don't want.

MVC Routing to use optional subfolder (like a set of virtual directories)

Here's the problem that I have been tearing hair out over since Friday.
I have a single MVC application that serves several different subgroups for a single client. For branding and for some style elements, the Url's need to be formatted like:
www.site.com/Login
www.site.com/Client1/Login
www.site.com/Client2/Login
www.site.com/Client3/Login
and so on.
We would also like to maintain this structure, moving onto www.site.com/Client1/News, etc.
Static routing is off the table. Even a tool to generate them. The site will have X pages with a unique route for Y clients, and I shudder to think about the performance. And because of teh dynamic nature, creating virtual dirs on the fly is not a route I want to travel down.
At first the solution seemed trivial. I tried two test solutions.
The first derived from CustomRouteBase and was able to determine the correct route in the overridden GetRouteData method and then generate the correct Url using GetVirtualPath. The second solution used constraints to see if a client was in the pattern and route accordingly. Both would hit the correct controllers and generate correct links.
Then I added areas (this is just a prototype, but the real site uses areas).
Both solutions failed. The areas were registered properly and worked as they typically should. But with the first solution, I could not find a way to override GetVirtualPath for area registration. I know there is an extension methods off the Area class, but this doesn't fit what I need.
I also tried using a constraint, but the "client" part of the Url was not being added to any of the action links to the areas and trying to route to a controller using a constraint gave me the good old view not found error (searching from the root even though I had specified the area in the route).
So my first question is am I going about this the wrong way? If there is a better way to accomplish this, I am all eras. If not, then how do I manage areas?
I could put some code up here, but it all works for what I want it to do. I'm sort of lost at how to approach the area issue. But unfortunately as always I inherited this project 3 months before launch and my team simply doesn't have the resources to restructure the site without them.
#Max... I tried something similar, but thh areas still would not display their links correctly when in www.site.com/Client1... This is from prototype 6, I tried a few different ways.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//throws view engine can't find view error. If I use www.website.com/Client1, hvering over area links does not give the correct path
routes.MapRoute("Area",
"{folder}/{area}/{controller}/{action}/{id}",
new { area = "Authentication", controller = "Authentication", action = "Index", id = UrlParameter.Optional },
new { folder = new IsFolderContsraint(), area = new IsArea() }
);
//works fine.. if I use www.website.com/Client1, hovering over regular (non area) links works fine
routes.MapRoute("Branded",
"{folder}/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { folder = new IsFolderContsraint() }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
Here is another way I tried to tackle it. Please don't crack on the implementation, it's just a POC. The problem here was that Areas are not part of RouteBase, so I can't modify the virtual paths for them. So every action link, etc, get's rendered correctly and all works well as long as the action is not in an area.
public class Folders
{
private static Dictionary<string, string> _folders= new Dictionary<string, string>()
{ {"test1", "style1"},
{"test2", "style2"},
{"test3", "style3"}
};
public static Dictionary<string, string> FolderNames { get { return _folders; } }
}
public class AreaDefinitions
{
private static Dictionary<string, string> _areas = new Dictionary<string, string>()
{ {"Authentication", "Authentication"} };
public static Dictionary<string, string> AreaDefinition { get { return _areas; } }
}
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new CustomRouteBase());
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
public class CustomRouteBase : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
//Creates routes based on current app execution pass
//a bit like doing my own routing constraints, but is more dynamic.
//get route data (and CreateVirtualPath) will be called for any action needing to be rendered in the current view.
//But not for areas :( Ugly but just a prototype
string url = httpContext.Request.AppRelativeCurrentExecutionFilePath;
url = url.StartsWith("~/") ? url.Substring(2, url.Length - 2) : url;
string[] urlParts = url.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
RouteData rd = new RouteData(this, new MvcRouteHandler());
if (urlParts.Length == 0)
{
rd.Values.Add("controller", urlParts.Length > 0 ? urlParts[0] : "Home");
rd.Values.Add("action", urlParts.Length > 1 ? urlParts[1] : "Index");
return rd;
}
if (Folders.FolderNames.ContainsKey(urlParts[0]))
{
if (urlParts.Length > 1 && AreaDefinitions.AreaDefinition.ContainsKey(urlParts[1]))
{
rd.DataTokens["area"] = urlParts[1];
rd.Values.Add("controller", urlParts.Length > 2 ? urlParts[2] : "Home");
rd.Values.Add("action", urlParts.Length > 3 ? urlParts[3] : "Index");
}
else
{
rd.Values.Add("controller", urlParts.Length > 1 ? urlParts[1] : "Home");
rd.Values.Add("action", urlParts.Length > 2 ? urlParts[2] : "Index");
}
rd.DataTokens.Add("folder", urlParts[0]);
}
else
{
rd.Values.Add("controller", urlParts.Length > 0 ? urlParts[0] : "Home");
rd.Values.Add("action", urlParts.Length > 1 ? urlParts[1] : "Index");
}
return rd;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//Assembled virtial path here using route and values.
//Worked (ugly but functioned, but was never called for areas.)
string url = "";
if (requestContext.RouteData.DataTokens.ContainsKey("folder"))
url += requestContext.RouteData.DataTokens["folder"] + "/";
if (values.ContainsKey("controller"))
url += values["controller"] + "/";
if (values.ContainsKey("action"))
url += values["action"];
var vpd = new VirtualPathData(requestContext.RouteData.Route, url);
return vpd;
}
}
Thanks Liam... that's similar to how our view engine is customized, to read from a "plugins" folder first for view overrides. But the problem is a little different here. This is one client that already has a site with view overrides, but in turn has multiple clients of their own. The behavior here is just to control style sheets, logos, etc. After a user is logged in, I can identify what the style and branding should be, but for landing pages the client wants to use a(n) url like "www.site.com/Client1" to identify this. I've given up and written a handler that just turns the request into www.site.com/?client=client1 so I can handle landing page styling, but it would be so much nicer to leave the url as www.ste.com/Client1/login, etc. This is a conversion from a classic asp app that used vdirs to host different directories prior to this. Because of the number of potential clients (100's), static routing gets heavy. My solutions all work to a point... it's the areas that are causing all the problems. If I could just find a way to remap their virtual paths dynamically like I can with the routes I create in RouteBase, I would be in business... I think.

How do I get the current Url from within a FilterAttribute?

I am writing an Authorize filter attribute adn I'm having trouble figuring out how to get the current url as a string so I can pass it as a parameter to the LogOn action. The goal is that if a user successfully logs on, they will be redirected to the page they were originally trying to access.
public override void OnAuthorization(AuthorizeContext filterContext)
{
base.OnAuthorization(filterContext);
... my auth code ...
bool isAuth ;
... my auth code ...
if(!isAuth)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "Area", "" },
{ "Controller", "Account" },
{ "Action", "LogOn" },
{ "RedirectUrl", "/Url/String/For/Currnt/Request" } // how do I get this?
}
);
}
}
How do I get the full string Url from the current request?
Try:
var url = filterContext.HttpContext.Request.Url;
To get the complete URL you can try as suggested by the #rboarman but usually the RedirectUrl will be the relative url and for that you have to try the the RawUrl property of the Request object.
filterContext.HttpContext.Request.Url ===> http://somesite.com/admin/manage
filterContext.HttpContext.Request.RawUrl ====> /admin/manage
EDITED: Fixed the second example
In my specific case I was after the UrlReferrer URL.
filterContext.HttpContext.Request.UrlReferrer
This one let me redirect the user back to the page he was before trying to access an action he doesn't have permission to access.
This is the highest ranked result on Google so in Asp.net Core 2.0 this is how I'm doing it:
context.HttpContext.Request.Url();
using this extension method:
/// <summary>
/// Returns the absolute url.
/// </summary>
public static string Url(this HttpRequest request)
{
return $"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}";
}

Resources