Specify alternate VirtualDirectory when using UrlHelper class - c#

I have the following web application architecture using ASP.Net MVC 5.2.2 with virtual directories for each website.
localhost/auth
localhost/web1
localhost/web2
I'm looking to use the standard Url.Action() routing from all three (n) applications.
I have achieved a partly working example by creating an additional project to house my routing for my web applications to reference.
var routeCollection = new RouteCollection();
var requestContext = HttpContext.Current.Request.RequestContext;
SampleApp.Routing.Web1RouteConfig.RegisterRoutes(routeCollection);
var Web1UrlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext, routeCollection);
This allows me to call
#Web1UrlHelper.Action("Test", "Test", { entityId = new Guid() });
and will render me the correct URL format of {guid}/{controller}/{action} as defined by my Web1RouteConfig class in the SampleApp.Routing project.
The issue is the virtual directory of the calling UrlHelper class is retained by the URL builder.
This means (when called from web2) my url is incorrectly formatted as:
localhost/web2/997d0d58-5f09-4431-9b2e-88a247bd334b/test/test
Rather than:
localhost/web1/997d0d58-5f09-4431-9b2e-88a247bd334b/test/test
After digging I've found that I can pass into my RouteCollection class an instance of a VirtualPathProvider. This is where I've hit a dead-end.
Am I going down a rabbit hole? It sounds like I'm trying to hand-crank too much to achieve what sounds like a simple task.
Many thanks.

Related

Strongly typed web api route endpoints in mvc

So I have two separate projects (one Web Api 2 and one MVC) like this diagram:
The MVC has controllers and a service layer. The services from the MVC app call to the web api controllers. For example:
await _service.GetGiftCards("/api/GiftCards/ViewAllByUser", email);
The Web Api controllers have their routes defined like so:
[RoutePrefix("api/giftcards")]
[Route("ViewAllByUser")]
public async Task<List<GiftCard>> GetGiftCardsForUser(string email){}
So to define the endpoint route in the MVC app I simply pass a string like "/api/GiftCards/ViewAllByUser" above.
My question is, is there a better way to sort of "strongly type" the endpoints of the Web Api routes that are defined so I can do something like?:
await _service.GetGiftCards(GiftCardController.ViewAllByUser, email);
I guess at a minimum I could always just store the endpoint strings in a static class like so, so they at least can all be updated in one place:
public static class ApiEndpoints(){
public string GetAllGiftCards = "api/GiftCards/ViewAllByUser";
}
but I'm looking to know if there are better ways or other suggestions. Thanks!
API routes shouldn't specify actions. You want your routes to be logical paths to a record or group of records. Example, in your case the route should look something like this:
GET
api/giftcards/{userID:int:min(1)}
You want to be able to walk up the url basically and get what you would expect. In the case of the example route you would get gift cards based on the user id. If you were to take off the user id page and just call api/giftcards you would expect to get all gift cards by all users. I'm using an ID here but you would do the same with email.
Pleas try with 'ActionName' Attribute on action like this :
[ActionName("SelectAll")]
public IEnumerable<Customer> Get()
{
...
}
calling this action name like:
$("#getdata").click(function () {
var options = {};
options.url = "/api/customer/SelectAll";
options.type = "GET";
...
...
$.ajax(options);
});
note: 'getdata' is id of control which click event will be fired and calling 'api method' and getdata from api
Here's a library that may be what you're looking for.
Although I like static strings so you don't always have to show future developers on your team how to use said library when updates are needed on the clients.

Web API 2.2 return custom 404 when resource (url) not found

I want to be able to take over the 404 response from web api (iis) when the resource does not exist.
I have done my research and came across only one solution that made this work, but i am not sure how "safe" it is since the "routeTemplate" is just {*url}
This post is kinda to ask for help and explanation.
My App uses MVC and WebAPI... would this template affect MVC as well?
Is there a way to add "api" with {*url} in the template? (to make sure only requests with ".../api/..." get affected)
config.Routes.MapHttpRoute("Error404", "{*url}", new { controller = "Error", action = "Handle404" });
Has anyone come across a cleaner way of doing this and handling 404 in web api?
EDIT 1
The above code DOES affect my MVC routes.
How can i add "api" to "{*url}"?... if tried many different ways and no dice.
Had the exact same issue. After some research and trial and error, I was able to find a working solution.
I went with a RoutePrefix solution and I tried to implement something similar to how the MVC controllers used HandleUnknownAction in a base controller.
With the help from this post: .NET WebAPI Attribute Routing and inheritance to allow for route inheritance, I created a base controller for my web APIs with a HandleUnknownAction method like this:
public abstract class WebApiControllerBase : ApiController {
[Route("{*actionName}")]
[AcceptVerbs("GET", "POST", "PUT", "DELETE")]//Include what ever methods you want to handle
[AllowAnonymous]//So I can use it on authenticated controllers
[ApiExplorerSettings(IgnoreApi = true)]//To hide this method from helpers
public virtual HttpResponseMessage HandleUnknownAction(string actionName) {
var status = HttpStatusCode.NotFound;
//This is custom code to create my response content
//....
var message = status.ToString().FormatCamelCase();
var content = DependencyService
.Get<IResponseEnvelopeFactory>()
.CreateWithOnlyMetadata(status, message);
//....
return Request.CreateResponse(status, content);
}
}
If you don't want to go down the inheritance path, you can always put the method directly into the controller you want to apply the functionality on.
This allows me to use route prefixes that handle custom not found messages that pertain to specific controllers as I have both back-office and public public facing APIs.
If a URL does not apply to an ApiController the default Error controller will handle the not found as usual.

Deploying MVC Area as a self standing web site

I have an MVC Application with multiple Areas. They share a lot of common code and components, so I do not want to break them up into separate Projects. But I would like to deploy them to separate web sites.
The normal routing is:
www.mysharedsite.com/Area1
www.mysharedsite.com/Area2
...
But I would like to deploy them as:
www.area1site.com/
www.area2site.com/
...
I was thinking of putting a field in the web.config and then adding logic in the RouteConfig and the RegisterAreas of each area to change the Routes and turn off Routes to Controllers altogether. But this seems kludgy.
Is there a clean way of doing this?
What I would do is create and install a custom ActionInvoker which reads the hostname from the request, and based on it, sets the appropriate Area path for you:
protected override ActionResult InvokeActionMethod(...)
{
// Get hostname
var hostname = controllerContext.HttpContext.Request.Url.Host;
if (hostname == "some value you want")
{
controllerContext.RouteData.DataTokens["area"] = "your area here";
}
return base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);
}
You could specify a route based on the hostname, mapping it to an area. Based on the URL format in your question:
routes.Add("DomainRoute", new DomainRoute(
"{area}site.com", // Domain with parameters
"{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
));
See this post for the DomainRoute class:
http://blog.maartenballiauw.be/post/2009/05/20/ASPNET-MVC-Domain-Routing.aspx
Why not put the common code in a seperate dll
and link your websites to this dll?
Your solution will be a lot bigger if you add another website that also shares the common code.

How can I generate a link to a resource not in the WebAPI routing table?

I'm attempting to make a Virtual directory relative link to some unknown (to WebAPI) resource form an ApiController.
Example:
http://some/path/webapi/does/not/know
The WebAPI Url helper seems to be tightly coupled with Routing and does not have a Content() method like the MVC variant. I'm trying to avoid using any non-mockable HTTP context information to make this call (such as HttpContext.Current).
Thanks for the help!
You can always do,
var urlBuilder =
new System.UriBuilder(Request.Url.AbsoluteUri)
{
Path = "webapi/does/not/know"
};
var uri = urlBuilder.Uri
In that way, you don't need to rely on the UrlHelper. The base url is inferred from the current request.
Here are two options you can try:
HostingEnvironment.MapPath()
Create a System.Web.Mvc.UrlHelper in your API controller

Allow user when registered to browse to username.host.com

I am using Asp.net MVC3 and C# and IIS 7.5. I want that once user is registered he can browse my site using username.host.com and this username should be available to me in my query string so I can show the data related to that particular username only. All the logic is the same for all users. I don't want to do any fancy thing like if user1.host.com is entered then I want to redirect to a separate controller and action etc. All the application logic is the same for all users. I just want to change the way the url is shown in the browser.
Note: I am not talking about really creating dynamic subdomains. That is a lot of task!
Because routing is so powerful in MVC, I assume that it can be done alone using routing. Also, if possible I want this to work on localhost also in IIS/Cassini.
Eg: If I browse to jaggu.localhost:19883. It should send me to localhost:19883/Home/index/Jaggu (because by default Home is the controller and index is the method)
I am completely clueless on how to achieve this. Any help would be appreciated.
Thanks.
In terms of ASP.NET MVC routing it's easy. Simply write a custom route:
public class MyRoute : Route
{
public MyRoute(string url, object defaults)
: base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{ }
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
var tokens = httpContext.Request.Url.Host.Split('.');
if (tokens.Length > 1)
{
rd.Values["username"] = tokens[0];
}
return rd;
}
}
and then register this route:
routes.Add(
"Default",
new MyRoute(
"{controller}/{action}/{username}",
new { controller = "Home", action = "Index", username = UrlParameter.Optional }
)
);
Now when someone requests http://foo.host.com, automatically the Index action of the HomeController will be invoked and passed username="foo" parameter.
Then comes the difficult part. The registration and management of subdomains and web server configuration. A topic better suited to be discussed on http://serverfault.com
It is significantly easier to work with www.{site}.com/{username} than what you are trying to do.
subdomains are meant to section off separate websites; not content areas.
Even if you get it to work, you will discover more issues like dealing with SSL certs (if you need them) and the browser's always fun "same origin policy."

Categories