Currently in my web application I have plenty of lines like this:
Response.Redirect("~/Default.aspx");
etc. Now, I'm in the process of reorganizing aspx pages structure, as it's pretty flat - everything is in one folder. So I have to thoroughly search all the code in order to fix aspx paths. So my question is: is there any way to manage/organize web site pages structure in ASP .NET - some kind of SiteMap, so that I have a code similar to:
Response.Redirect(Pages.Default);
so that my code will be closed for modifications where some aspx pages are moved between folders?
I've written an open source project that will help you, http://navigation.codeplex.com/.
You list your pages and transitions in a config file. Here's an example:
<state key="Page1" page="~/Page1.aspx">
<transition key="Next" to="Page2"/>
</state>
<state key="Page2" page="~/Page2.aspx"/>
Then in your code you can move from Page1.aspx to Page2.aspx like this:
StateController.Navigate("Next");
Let me know if you're interested or need any help.
I don't think there there's any automatic solution, you'll probably end up building something yourself. You may want to check out the Asp.Net Sitemap stuff, although it's mainly focused on Navigation controls.
http://msdn.microsoft.com/en-us/library/yy2ykkab.aspx
I ran into the same issue you're facing on a few projects. One simple solution is to create constant/static/enums to represent the page names/urls:
public static class SiteMap {
public static readonly string Default = "/default.aspx";
}
which of course lets you do
Response.Redirect(SiteMap.Default);
But that was outgrown quickly as the pages required parameters passed in, and sooner or later you run into exactly the same problem with the parameter names. So we expanded on that idea by adding an Url factory class like
public static class SiteMap {
public static readonly string Article = "/blog/article.aspx";
}
public static class PageParams {
public static readonly string ArticleId = "aid";
}
public static class UrlFactory {
public static string GetUrlBlogArticle(int articleId){
return string.Format("/{0}?{1}={2}",SiteMap.Article, PageParams.ArticleId, articleId.ToString());
}
}
That approach ensures all the links work the same way and include the same parameters. By setting up a PageParams constants class the landing pages/controls code are a little cleaner too, since you can do
public void method(){
var articleId = Request[PageParams.ArticleId];
}
rather than have random request parameter names everywhere.
Hope that gives you some ideas.
Related
I need to run some code to get a user's username and department when they first connect to my Blazor Server Side application. I could just do this using OnInitialized() but that appears to only work on the one page in which it was placed. Users will likely be sent separate links to different pages though and I don't want to have to place this code on every page. I discovered that I can place code in my main layout and it will run no matter what page I start on but it runs on every page change and it doesn't allow me to run things asynchronously so that's not ideal. I'm looking for something like a Global.asax but in Blazor if that makes sense.
Edit: Turns out I can run things asynchronously in my layout! I just needed to create a code block like any other razor page. Makes sense. Though It's still weird that we have to put this type of code in the layout. It just doesn't feel right.
This is what I do:
Create a state object (class) that can be injected where needed. This is somewhat like session, but can also have global events. See here.
Add it to IoC in Startup.cs. Background info here
services.AddScoped<MyState>();
Initialize it in MainLayout.razor or elsewhere:
if (MyState.User == null)
{
MyState.User = authService.User;
}
Instantiate in pages/components as needed:
[Inject]
public MyState myState { get; set; }
...
myObj.CreatedBy = myState.User.UserName;
Background: We run dozens of sites for clients; all use an identical code base (ASP.NET WebForms) but completely different designs. Page structure is generated programmatically from SQL Server meta-data, using controls such as Panel, which are added to the ASP.NET page's Controls collection, and become DIVs in the rendered HTML.
Objective: We want to migrate eventually to ASP.NET CORE. However, there seems to be no equivalent to the page's controls collection. The closest thing I can find is the RenderTreeBuilder to add a Blazor component.
Question: Is it possible use BuildRenderTree to add a component which contains our home-baked HTML (for instance, to contain everything between <body> and </body>?
I've read articles such as:
https://chrissainty.com/building-components-via-rendertreebuilder/
https://learn.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#manual-rendertreebuilder-logic
... and experimented with adding HTML elements, but it's extremely cumbersome, and I'd like to programmatically generate the HTML for pretty much the whole page, and add it as one RenderFragment (is that the right term?).
Is this possible? Is there an alternative?
Edit:
#Henk's answer, using the MarkupString struct, and mine, using RenderTreeBuilder.AddMarkupContent seem similar in terms of effort and plumbing required.
Are there any pros and cons of the two approaches I should consider?
If you just want HTML (plain, dead) then you don't need a rendertree:
<h1>My Page</h1>
#MyHtmlComposer()
#code{
private MarkupString MyHtmlComposer()
{
string html = "<p>Hello</p>";
return new MarkupString(html);
}
}
I hadn't come across the MarkupString struct, so #Henk's answer is really helpful. I've now also come across the RenderTreeBuilder.AddMarkupContent method, so I'll offer this as an alternate answer:
My markup:
#page "/"
<PageBuilder></PageBuilder>
PageBuilder is a class that inherits from ComponentBase:
public class PageBuilder : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder b)
{
base.BuildRenderTree(b);
b.OpenElement(0, "div");
b.AddMarkupContent(1, TheContent());
b.CloseElement();
}
public string TheContent()
{
return "<div>This is the generated content</div>";
}
I'll edit my original question a little, as I'd like to know whether there's anything to choose between this approach and #Henk's.
You can also render a string as HTML like this:
#page "/test";
<h1>My Page</h1>
#((MarkupString)content)
#code{
//Get content from Database
string content = "<p>Hello</p>";
}
See section "Raw HTML" here
I have used <cache> tag helper in my Asp.Net Core 3.1 website to cache some parts of the page. However, in some situations I want to clear these cache entries and make the website re-create those. How can I achieve that? I tried the code mentioned in this answer but couldn't get it to work. When I register the service the application seems to ignore it and no entries get cached in my custom service.
Here is a way to do it:
private readonly CacheTagHelperMemoryCacheFactory factory;
public MyClass(CacheTagHelperMemoryCacheFactory factory)
{
this.factory = factory;
}
and later
(this.factory.Cache as MemoryCache).Compact(1.0);
I am trying to include different scripts on different pages in Sitecore and can't seem to find a very good way of doing this. In a normal mvc project I could add the #Section{} helper and use that on different partial views to specify where I want those scripts in the layout view, but I haven't been able to find an equivalent for the way that Razor helper is implemented with Sitecore. I'd like to do this without using a place holder, I don't want to add a view in Sitecore every time I need to add a script file.
Thanks in advance.
I'm afraid you're out of luck here.
#Section is not supported because Sitecore doesn't render the Razor views in the same way as MVC does.
A Sitecore MVC layout is basically just a regular view that is rendering several other partial views or controller actions.
So when the placeholders in the <body> of your layout view are being rendered, the <head> section of that layout has already been rendered.
There is no such thing as deferred rendering in Sitecore MVC like you can do with #Section.
Everything in the view is executed from top to bottom, so if you can put your scripts at the end of your layout (like before the </body>), you can still manipulate data in the views or actions that are executed earlier.
The way I have it setup in my current Sitecore MVC solution is my layout has an extension method call to RenderScripts() at the bottom before the closing body tag.
#Html.RenderScripts()
That extension method looks like this:
public static IHtmlString RenderScripts(this HtmlHelper htmlHelper)
{
var templates = (from object key in htmlHelper.ViewContext.HttpContext.Items.Keys
where key.ToString().StartsWith("_script_")
select htmlHelper.ViewContext.HttpContext.Items[key]).OfType<Func<object, HelperResult>>()
.Select(template => template(null)).ToList();
foreach (var template in templates)
{
htmlHelper.ViewContext.Writer.Write(template);
}
return MvcHtmlString.Empty;
}
Then on each MVC Razor View when I want to include a .js file that is specific to that rendering I call something like below at the bottom of the file:
#Html.Script(
#<script src="#Url.Content("~/js/custom/orderdetail.js?t=11172015")" type="text/javascript"></script>
)
Below is the Script extension method:
public static MvcHtmlString Script(this HtmlHelper htmlHelper, Func<object, HelperResult> template)
{
htmlHelper.ViewContext.HttpContext.Items["_script_" + Guid.NewGuid()] = template;
return MvcHtmlString.Empty;
}
This has worked out well for us and I think it is what you are trying to do.
Indeed, the Section-helper isn't supported in Sitecore. If you're using MVC4 you can maybe use Bundles to solve your problem. For more information see: http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification
You can also create multiple bundles for specific views. In a single Bundle you can add multiple script and output it in your view by adding #Scripts.Render()
I'm starting a new ASP.NET MVC project, and I decided to put my controllers in a different assembly. Evertyhing works fine, but I have hit a problem: I created a new area in my MVC Project, called Administration. I have an AdminController Class in my seperate assembly which is supposed to return views from my Admin area, but everytime it tries to return a view,
it looks for it in the wrong place (~/Admin/SomeView.cshtml Instead of ~/Administration/Admin/SomeView.cshtml)
How can I tell the controller to look for views in the wanted area?
Please take a look into this article. And also you problem was answered here.
Basically you will need to extend MvcViewEngine, to tell MVC to look for your Views in the different from standatd pathes:
public class YourMegaViewEngine : WebFormViewEngine
{
public YourMegaViewEngine ()
{
ViewLocationFormats = new string[]
{
"~/Views/Administration/{1}/{0}.cshtml" //I may be wrong for you case, but this is the place to puth you path
};
}
}