I have an ASP.NET app. My app has a _ViewStart.cshtml file. That file looks like this:
#using MyCompany.MyApp;
#{
Layout = "/Views/Shared/_Layout.cshtml";
var p = HttpContext.Current.Request.QueryString["parameter"];
ViewBag.QSParameter = p;
}
When I execute this code, I get the following error:
The name 'HttpContext' does not exist in the current context
I don't understand. Isn't _ViewStart.cshtml kind of the "shell" for the views? I'm trying to figure out how to globally read a query string parameter and set a value on the ViewBag for each request. I thought this would be the way to do it.
Thanks
You should have access to Request in your _ViewStart file.
Try this:
#using MyCompany.MyApp;
#{
Layout = "/Views/Shared/_Layout.cshtml";
var p = Request.QueryString["parameter"];
ViewBag.QSParameter = p;
}
EDIT: For ASP.NET 5
I don't have ASP.NET 5 on my machine but have looked at the source code for the framework. It looks like there is a Context property on RazorPage that returns an HttpContext. Alternatively, you can access the HttpContext through the ViewContext. See below:
#{
Layout = "/Views/Shared/_Layout.cshtml";
var p = Context.Request.Query["parameter"];
// or this...
// var p = ViewContext.HttpContext.Request.Query["parameter"];
ViewBag.QSParameter = p;
}
To retrieve it from _ViewStart.cshtml, you can use:
ViewBag.QSParameter = Context.Request.Query["parameter"];
Note: Use Query now (over QueryString) in ASP.NET 5
However, I might ellect to go a different route and take advantage of IResultFilter:
public class QSParameterFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
var QSParameter = context.HttpContext.Request.Query["parameter"];
((Controller)context.Controller).ViewBag.QSParameter = QSParameter;
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
Then, register it within your Startup.cs:
services.AddMvc();
services.Configure<MvcOptions>(options => {
options.Filters.Add(new QSParameterFilter());
});
Make the "Build Action = Content" in the file properties. This will solve the issue.
Related
In an ASP.NET Core 3.1 web app, I can change the target route for unauthorised requests in my Startup.cs like so:
services.ConfigureApplicationCookie(o =>
{
o.AccessDeniedPath = "/Home/Error";
});
That will return /Home/Error?ReturnUrl=... where ... is whatever page I was trying to access.
But I actually just want it to return simply "/Home/Error?code=401"
I tried e.g.
o.AccessDeniedPath = "/Home/Error?code=401"
but that brings back simply
"/Home/Error?code=401?ReturnUrl=%2FAdmin"
I then realised there's a ReturnUrlParameter in the options, like this:
o.ReturnUrlParameter = "code";
o.AccessDeniedPath = "/Home/Error";
which gets me this far in the redirect:
/Home/Error?code=%2FAdmin
But I want to specify the keyvalue value (e.g. 401), i.e. replace the page the request came from, so the final result would be
/Home/Error?code=401
According to your code, it seems that you are using cookie Authentication, I suggest yo could try to change the redirect URL using the CookieAuthenticationEvents.OnRedirectToAccessDenied Property, check the following sample code:
services.AddAuthentication("CookieAuthentication")
.AddCookie("CookieAuthentication", config =>
{
config.Cookie.Name = "UserLoginCookie"; // Name of cookie
config.LoginPath = "/Login/UserLogin"; // Path for the redirect to user login page
config.AccessDeniedPath = "/Login/UserAccessDenied";
config.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents()
{
OnRedirectToAccessDenied = ctx =>
{
var redirectPath = ctx.RedirectUri;
if (redirectPath.Contains("?ReturnUrl"))
{
//remove the ReturnURL
var url = redirectPath.Substring(0, redirectPath.LastIndexOf("?ReturnUrl"));
ctx.Response.Redirect(url + "?code=401");
}
// Or, directly using the following code:
//ctx.Response.Redirect("/Login/UserAccessDenied?code=401");
return Task.CompletedTask;
}
};
});
The output like this:
I am trying out the Finbuckle multi tenant library now and have managed to set the tenant information for the user.
However, when I redirect to another page and load the tenantinfo in the view, it shows up as null. Am I doing something wrong?
This is how I get and set my tenantinfo:
var OrganizationId = HttpContext.User.Claims.Where(xc => xc.Type == MultiTenancy.ClassLibrary.Globals.CookieGlobals.OrganizationId).Select(xc => xc.Value).SingleOrDefault();
var tenantInfo = await store.TryGetByIdentifierAsync(OrganizationId);
HttpContext.TrySetTenantInfo(tenantInfo, resetServiceProvider: true);
var ti = HttpContext.GetMultiTenantContext()?.TenantInfo;
return RedirectToAction("Sample", "Home", new { __tenant__ = tenantInfo.Name });
This is the Sample View:
#using Microsoft.AspNetCore.Identity
#{
ViewData["Title"] = "Sample";
Layout = "~/Views/Shared/_Layout.cshtml";
var tenantInfo = Context.GetMultiTenantContext()?.TenantInfo;
}
#using Finbuckle.MultiTenant.AspNetCore
<h2>#tenantInfo.Name</h2>
EDIT:
So far what works is if I do this:
public async Task<ActionResult> Sample()
{
var OrganizationId = HttpContext.User.Claims.Where(xc => xc.Type == MultiTenancy.ClassLibrary.Globals.CookieGlobals.OrganizationId).Select(xc => xc.Value).SingleOrDefault();
var tenantInfo = await store.TryGetByIdentifierAsync(OrganizationId);
HttpContext.TrySetTenantInfo(tenantInfo, resetServiceProvider: true);
var ti = HttpContext.GetMultiTenantContext()?.TenantInfo;
return View(ti);
}
But that means that the tenantinfo has to always be set. Is there a way to make it persistent?
This is my Startup.cs:
services.AddMultiTenant()
.WithEFCoreStore<MultiTenancyDbContext, TenantInfo>()
.WithDelegateStrategy(context => {
var organizationId = ((HttpContext)context).User.Claims.Where(xc => xc.Type == MultiTenancy.ClassLibrary.Globals.CookieGlobals.OrganizationId).Select(xc => xc.Value).SingleOrDefault();
return Task.FromResult(organizationId);
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}"
);
});
Thanks for pinging me over on the GitHub site for Finbuckle.MultiTenant.
The reason the tenant information is lost is because the Redirect to Action redirects the user browser so its like an entirely new HTTP request which basically resets everything. The second approach you posts doesn't actually redirect the user browser so it doesn't lose the info. Finbuckle.MultiTenant should work with either approach--I'll try to clarify how it works below.
It looks like you are setting the tenant info yourself, but while this is possible it is not the intended flow. The library was designed so that its middleware would use a strategy, query the store, and set the tenant info before your controller code is ever hit. It would look something like this (where WithStrategy and WithStore are simplified for this example):
// In your services configuration in your Startup class:
services.AddMultiTenant().WithStrategy().WithStore();
// In your app pipeline in your Startup class:
app.UseMultiTenant(); // <-- This sets the tenant info
So in normal situations you don't need to call store.TryGetByIdentifierAsync(...) or HttpContext.SetTenantInfo(...).
Instead you want to define a strategy that gets the organization ID from the cookie like you did. The middleware will use the strategy at the start of every request to set the tenant info. There is a DelegateStrategy that should work for your situation. It requires you to set a delegate or lambda that will return the tenant identifier, in your case the organization id:
services.AddMultiTenant().WithStore().
WithDelegateStrategy(context =>
{
var organizationId = HttpContext.User.Claims.Where(xc => xc.Type == MultiTenancy.ClassLibrary.Globals.CookieGlobals.OrganizationId).Select(xc => xc.Value).SingleOrDefault();
return Task.FromResult(organizationId);
});
Please check out the delegate strategy sample in the repository to see a working project that uses a similar approach.
I'm building a system using ASP.NET Core 2.0 Razor Pages (not MVC) and I'm having trouble adding multiple routes for pages. For example, all pages should be able to be reached by abc.com/language/segment/shop/mypage or abc.com/language/shop/mypage, where both paths point to the same page. The segment path section is optional, then the pages do stuff with the optional segment info.
The question mark syntax in the CombineTemplates markup doesn't seem to work, it seems to only work in the last section of the path. Browsing to a url without a value in the {segment?} section resulted in 404. For example:
AttributeRouteModel.CombineTemplates("{language}/{segment?}/shop", selector.AttributeRouteModel.Template);
I tried code like this below but it only appends the two paths to each other, and I need to be able to enable them both as valid.
options.Conventions.Add(new DefaultPageRouteModelConvention());
options.Conventions.Add(new SegmentPageRouteModelConvention());
In ASP.NET MVC, I could just add two different routes pointing to the same area/controller/action with two different named MapRouteWithName.
Any ideas how to do this with .NET Razor Page syntax?
This code works:
Add a single convention (not two different conventions):
options.Conventions.Add(new CombinedPageRouteModelConvention());
In the new convention, add both route selectors:
private class CombinedPageRouteModelConvention : IPageRouteModelConvention
{
private const string BaseUrlTemplateWithoutSegment = "{language}/shop";
private const string BaseUrlTemplateWithSegment = "{language}/{segment}/shop";
public void Apply(PageRouteModel model)
{
var allSelectors = new List<SelectorModel>();
foreach (var selector in model.Selectors)
{
//setup the route with segment
allSelectors.Add(CreateSelector(selector, BaseUrlTemplateWithSegment));
//setup the route without segment
allSelectors.Add(CreateSelector(selector, BaseUrlTemplateWithoutSegment));
}
//replace the default selectors with new selectors
model.Selectors.Clear();
foreach (var selector in allSelectors)
{
model.Selectors.Add(selector);
}
}
private static SelectorModel CreateSelector(SelectorModel defaultSelector, string template)
{
var fullTemplate = AttributeRouteModel.CombineTemplates(template, defaultSelector.AttributeRouteModel.Template);
var newSelector = new SelectorModel(defaultSelector)
{
AttributeRouteModel =
{
Template = fullTemplate
}
};
return newSelector;
}
}
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.
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.