Get site url on mvc - c#

I want to write a little helper function that returns the site url.
Coming from PHP and Codeigniter, I'm very upset that I can't get it to work the way I want.
Here's what I'm trying:
#{
var urlHelper = new UrlHelper(Html.ViewContext.RequestContext);
var baseurl = urlHelper.Content("~");
}
<script>
function base_url(url) {
url = url || "";
return '#baseurl' + url;
}
</script>
I want to return the base url of my application, so I can make ajax calls without worrying about paths. Here's how I intend to use it:
// Development
base_url(); // http://localhost:50024
// Production
base_url("Custom/Path"); // http://site.com/Custom/Path
How can I do something like that?
EDIT
I want absolute paths because I have abstracted js objects that makes my ajax calls.
So suppose I have:
function MyController() {
// ... js code
return $resource('../MyController/:id');
}
// then
var my_ctrl = MyController();
my_ctrl.id = 1;
my_ctrl.get(); // GET: ../MyController/1
This works when my route is http://localhost:8080/MyController/Edit but will fail when is http://localhost:8080/MyController .

I managed to do it like this:
#{
var url = Request.Url;
var baseurl = url.GetLeftPart(UriPartial.Authority);
}
Thank you all!

Are you aware of #Url.Action("actionname") and #Url.RouteUrl("routename") ?
Both of these should do what you're describing.

Instead of manually creating your URL's, you can use #Url.Action() to construct your URLs.
<p>#Url.Action("Index", "Home")</p>
/Home/Index
<p>#Url.Action("Edit", "Person", new { id = 1 })</p>
/Person/Edit/1
<p>#Url.Action("Search", "Book", new { title = "Gone With The Wind" })</p>
/Book/Search?title="Gone+With+The+Wind"
Now the absolute best reason to go with this option is that #Url.Action automatically applies any vanity URL routes you have defined in your Global.asax file. DRY as the sub-saharan desert! :)
In your case, your can create a 'custom path' in two ways.
Option A)
<p>#Url.Action("Path", "Custom")</p>
/Custom/Path
Option B)
You can create a route using the Global.asax file. So your controller/action combo can be anything you want, and you can create a custom vanity route url - regardless of the controller/action combo.

Related

How to handle paths setup for multi tenant ASP MVC site

I am working on a site that need to support host based Multi Tenancy, and I got this whole part figured out. The issue I have is that I have in the CSS folder a subfolder for every tenant (1,2,3).
CSS
|_ tenant_1
|_ tenant_2
|_ tenant_3
|_ tenant (virtual)
in the tenant_X folder there are custom css files used for stypling every specific tenant.
My idea was to somehow create a virtual location (tenant) that would be mapped to the tenant's folder and only one additional line of coude would be needed in the _Layout. I am not profound in MVC and so far I know, I think I can get this to work with a custom route.
One other reason for this approach is that the tenants user is not allowed to see that there are other tenants. I have to exclude the possibility to have the user loaded the wrong files.
Is this the right approach? can you suggest any better way?
A possible implementation to achieve this just by adding 1 line to the _Layout page, could be to get a css file from a controller as text/css.
So assuming that the current tenant ID is available on front-end you could call a method on controller with that id
For example something like this:
#Styles.Render(string.Format("/CustomizationController/GetCssForTenant?tenantId={0}", loggedTeanant == null ? (int?) null : loggedTenant.Id))
And now create a customization controller with the method as follows
public class CustomizationController : Controller
{
//this will cache cliente side the css file but if the duration expires
// or the tenantId changes it will be ask for the new file
[OutputCache(Duration = 43200, VaryByParam = "tenantId")]
public FileResult GetCssForTenant(int? tenantId)
{
var contentType = "text/css";
//if teanant id is null return empty css file
if(!tenantID.HasValue)
return new FileContentResult(new byte[0], contentType);
//load the real css file here <-
var result = ...
//---
//if having problems with the encoding use this ...
//System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
//var content = encoding.GetBytes(result);
//---
Response.ContentType = contentType;
return new FileContentResult(result, contentType);
//return new FileContentResult(content, contentType);
}
}
Hope that this help achieve what you need. Remember that this is a sketch of an possible implementation.
Edit
If you want to make a quick try of my suggested implementation use this
public class CustomizationController : Controller
{
//this will cache cliente side the css file but if the duration expires
// or the tenantId changes it will be ask for the new file
[OutputCache(Duration = 43200, VaryByParam = "tenantId")]
public FileResult GetCssForTenant(int? tenantId)
{
var contentType = "text/css";
//if teanant id is null return empty css file
if(!tenantID.HasValue)
return new FileContentResult(new byte[0], contentType);
//load the real css file here <-
var result = Environment.NewLine;
if(tenantID = 1)
result "body{ background-color: black !important;}"
else
result "body{ background-color: pink !important;}"
result += Environment.NewLine;
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
var content = encoding.GetBytes(result);
Response.ContentType = contentType;
return new FileContentResult(result, contentType);
}
}
And change the _Layout
#Styles.Render(string.Format("/CustomizationController/GetCssForTenant?tenantId={0}", 1))
Now the background-color of the page should change to black if you send 1 and to pink if you send 2.
You also can see in the network that if you ask 2 time with the same id the status will be 304 this means that the file comes from cache.
If you change the id the status will be 200 that is a not cached response.
If you pass null the css file will come empty so it will fallback to your default css.

Render dynamic HTML with embedded Razor variables using MVC

I have some encoded Html which have any number of 1000s of different Razor variables embedded within it that I have stored and need to retrieve from the database. I want to be able to render this in a MVC/razor view.
Just one simple example of the html saved on the database (it can be more complex):
"<span>Your page is #Config.PageColour and you have page size of #Config.PageSize</span>"
MessageController.cs
public ActionResult ShowMessage()
{
var htmlToDisplay = _messageDAL.getHtmlMessage();
var messageVm = new MessageVm
{
DisplayMessage = htmlToDisplay;
};
return View("Index.cshtml", "", messageVm);
}
Index.cshtml
<html>
#Html.Raw(#model.DisplayMessage)
</html>
Results
When I run this the rendered page looks like this:
Your page is #Config.PageColour and you have page size of #Config.PageSize
But I want it to interpret the value of the Razor variable with the html block and should look like this:
Your page is Blue and you have page size of A4
Really stuck on this so any help would be appreciated!
Use this line. I hope this may help.
#Html.Raw(System.Web.HttpUtility.HtmlDecode(#model.DisplayMessage))
EDIT 1
You can use any Razor Compiler like the one mentioned below
RazorEngine:
string result = RazorEngine.Razor.Parse(#model.DisplayMessage, new { Name = "Name" });
RazorEngine does not support any of the Mvc helpers such as Html and Url. Since these libraries are supposed to exist outside of Mvc and thus require more work to get them to work with those helpers.**
EDIT 2
You can use a Razor compiler that allows you to use HTML templates called RazorEngine which can be found at https://github.com/Antaris/RazorEngine
From Visual Studio, using the Package Manager Console command:
Install-Package RazorEngine
After installation I changed my controller as follows:
MessageController.cs
public ActionResult ShowMessage()
{
var htmlTemplate = _messageDAL.getHtmlMessage();
var htmlToDisplay = Engine.Razor.RunCompile(htmlTemplate , "messageTemplateKey", null, new { Name = "some model data" });
var messageVm = new MessageVm
{
DisplayMessage = htmlToDisplay;
};
return View("Index.cshtml", "", messageVm);
}
You can use a Razor compiler that allows you to use HTML templates called RazorEngine which can be found at https://github.com/Antaris/RazorEngine
From Visual Studio, using the Package Manager Console command:
Install-Package RazorEngine
After installation I changed my controller as follows:
MessageController.cs
public ActionResult ShowMessage()
{
var htmlTemplate = _messageDAL.getHtmlMessage();
var htmlToDisplay = Engine.Razor.RunCompile(htmlTemplate , "messageTemplateKey", null, new { Name = "some model data" });
var messageVm = new MessageVm
{
DisplayMessage = htmlToDisplay;
};
return View("Index.cshtml", "", messageVm);
}
And it worked first time. Big thanks to #Mukesh Kumar who provided the vital clues to rewrite the code which I've posted as a complete and working answer here.

UrlHelper.Action includes undesired additional parameters

I have a method in the controller ApplicationsController, in which I need to get the base URL for an action method:
public ActionResult MyAction(string id)
{
var url = Url.Action("MyAction", "Applications");
...
}
The problem is that this includes the string id from the current route data, when I need the URL without (the URL is used to fetch content from a CMS on a URL-based lookup).
I have tried passing null and new { } as the routeValues parameter to no avail.
The matching route is as follows (above all other routes):
routes.MapLowercaseRoute(
name: "Applications",
url: "applications/{action}/{id}",
defaults: new { controller = "Applications",
action = "Index", id = UrlParameter.Optional });
I've seen a couple of other questions touch on this but none of them seem to have a viable solution. At present, I am resorting to hardcoding the path in the controller; however, I'd like to be able to abstract this into an action filter, so I need to be able to generate the URL.
Is there a clean/conventional way to prevent this behaviour?
Ok, I see the problem. It's something called "Segment variable reuse". When generating the routes for outbound URLs, and trying to find values for each of the segment variables in a route’s URL pattern, the routing system will look at the values from the current request. This is a behavior that confuses many programmers and can lead to a lengthy debugging session. The routing system is keen to make a match against a route, to the extent that it will reuse segment variable values from the incoming URL. So I think you have to override the value like Julien suggested :
var url = Url.Action("MyAction", "Applications", new { id = "" })
Ended up getting around this with a different approach. The only way I could come up with to prevent arbitrarily-named route values from being inserted into the generated URL was to temporarily remove them from RouteData when calling Url.Action. I've written a couple of extension methods to facilitate this:
public static string NonContextualAction(this UrlHelper helper, string action)
{
return helper.NonContextualAction(action,
helper.RequestContext.RouteData.Values["controller"].ToString());
}
public static string NonContextualAction(this UrlHelper helper, string action,
string controller)
{
var routeValues = helper.RequestContext.RouteData.Values;
var routeValueKeys = routeValues.Keys.Where(o => o != "controller"
&& o != "action").ToList();
// Temporarily remove routevalues
var oldRouteValues = new Dictionary<string, object>();
foreach (var key in routeValueKeys)
{
oldRouteValues[key] = routeValues[key];
routeValues.Remove(key);
}
// Generate URL
string url = helper.Action(routeValues["Action"].ToString(),
routeValues["Controller"].ToString());
// Reinsert routevalues
foreach (var kvp in oldRouteValues)
{
routeValues.Add(kvp.Key, kvp.Value);
}
return url;
}
This allows me to do this in an action filter where I won't necessarily know what the parameter names for the action are (and therefore can't just pass an anonymous object as in the other answers).
Still very much interested to know if someone has a more elegant solution, however.
Use a null or empty value for id to prevent Url.Action from using the current one:
var url = Url.Action("MyAction", "Applications", new { id = "" })
I was not entirely comfortable with the altering, transient or otherwise, of the RouterData in #AntP's otherwise fine solution. Since my code for creating the links was already centralized, I borrowed #Tomasz Jaskuλa and #AntP to augment the ExpandoObject, I was already using.
IDictionary<string,object> p = new ExpandoObject();
// Add the values I want in the route
foreach (var (key, value) in linkAttribute.ParamMap)
{
var v = GetPropertyValue(origin, value);
p.Add(key, v);
}
// Ideas borrowed from https://stackoverflow.com/questions/20349681/urlhelper-action-includes-undesired-additional-parameters
// Null out values that I don't want, but are already in the RouteData
foreach (var key in _urlHelper.ActionContext.RouteData.Values.Keys)
{
if (p.ContainsKey(key))
continue;
p.Add(key, null);
}
var href = _urlHelper.Action("Get", linkAttribute.HRefControllerName, p);

How do I create a url with a bookmark?

I want to have a link to the knowledge base but I want to also create the bookmark using the id attribute.
// var url = "/KnowledgeBase/Index#Bookmark"
so in my view I want to add that bookmark using the value from my model
var url = Url.Content(string.Concat(Url.Action("Index", "KnowledgeBase"), "#", Model.Bookmark));
This doesn't seem elegant. Is there a better way?
This works for me:
TextLink
Try this,
#Html.Action("Index","KnowledgeBase",new{id= Model.Bookmark})
or you have to redirect using script
window.location.href = '#Url.Action( "Index", "KnowledgeBase" )?id='+#Model.Bookmark

ASP.NET MVC: Redirect from query string params to a canonical url

In my Asp.Net Mvc project I'd like to have a good looking urls, e.g. mysite.com/Page2, and I want to redirect from my old style urls (such as mysite.com?page=2) with 301 state so that there won't be two urls with identical content. Is there a way to do it?
As far as I know Asp.Net binding framework doesn't make difference between query string and curly brace params
I am not sure, I got your question right. It seems, your current setup relies on those GET parameters (like mysite.com?page=2). If you dont want to change this, you will have to use those parameters further. There would be no problem in doing so, though. Your users do not have to use or see them. In order to publish 'new style URLs' only, you may setup a URL redirect in your web server. That would change new style URLs to old style URLs.
The problem is the 301. If the user requests an old style URL, it would be accepted by the webserver as well. Refusing the request with a 301 error seems hard to achieve for me.
In order to get around this, I guess you will have to change your parameter scheme. You site may still rely on GET parameters - but they get a new name. Lets say, your comments are delivered propery for the following (internal) URL in the old scheme:
/Article/1022/Ms-Sharepoint-Setup-Manual?newpage=2
Note the new parameter name. In your root page (or master page, if you are using those), you may handle the redirect permanent (301) manually. Therefore, incoming 'old style requests' are distinguishable by using old parameter names. This could be used to manually assemble the 301 in the response in ASP code.
Personally, I would sugesst, to give up the 301 idea and just use URL redirection.
Well, as far as I can see performing such redirection in ASP.NET MVC might be tricky. This is how I did it:
global.asax:
routes.Add(new QueryStringRoute());
routes.MapRoute(null, "Article/{id}/{name}",
new { controller = "Article", action = "View", page = 1 },
new { page = #"\d+" }
);
routes.MapRoute(null, "Article/{id}/{name}/Page{page}",
new { controller = "Article", action = "View" },
new { page = #"\d+" }
);
QueryStringRoute.cs:
public class QueryStringRoute : RouteBase
{
private static string[] queryStringUrls = new string[]
{
#"~/Article/\d{1,6}/.*?page=\d{1,3}"
};
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string url = httpContext.Request.AppRelativeCurrentExecutionFilePath;
foreach (string queryStringUrl in queryStringUrls)
{
Regex regex = new Regex(queryStringUrl);
if (regex.IsMatch(url))
{
long id = 0; /* Parse the value from regex match */
int page = 0; /* Parse the value from regex match */
string name = ""; /* Parse the value from regex match */
RouteData rd = new RouteData(this, new MvcRouteHandler());
rd.Values.Add("controller", "QueryStringUrl");
rd.Values.Add("action", "Redirect");
rd.Values.Add("id", id);
rd.Values.Add("page", page);
rd.Values.Add("name", name);
rd.Values.Add("controllerToRedirect", "Article");
rd.Values.Add("actionToRedirect", "View");
return rd;
}
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
QueryStringUrlController.cs:
public class QueryStringUrlController : Controller
{
public RedirectToRouteResult Redirect(long id, int page, string name,
string controllerToRedirect, string actionToRedirect)
{
return RedirectToActionPermanent(actionToRedirect, controllerToRedirect, new { id = id, page = page, name = name });
}
}
Assuming you have such routing as in my global.asax file (listed above) you can create a custom Route class that will handle incoming requests and map them on a special redirection controller which will then redirect them to appropriate urls with 301 state. Then you must add this route to global.asax before your "Article" routes
If you're using IIS 7, the URL Rewrite Module should work for your scenario.

Categories

Resources