I have a single blazor server project. But I will have multiple domains.
What i want to do is to dedicate folders for each domain.
But some razor pages will be the same for all domains.
Example:
www.mydomain1.com/Page1 -> Pages/mydomain1.com/Page1.razor
www.mydomain1.com/Page2 -> Pages/mydomain1.com/Page2.razor
www.mydomain2.com/Page1 -> Pages/mydomain2.com/Page1.razor
www.mydomain2.com/Page2 -> Pages/mydomain2.com/Page2.razor
www.mydomain1.com/CommonPage -> Pages/CommonPage.razor
www.mydomain2.com/CommonPage -> Pages/CommonPage.razor
How can i do that ?
Thanks a lot
This is not supposed to be the answer yet. It's a way to get there. Based on your comments, I'll refine this post and adding examples s, etc. I want to make sure first that I'm going in the right direction.
I see multiple ways to achieve what you have in mind, but there are not "built" in Blazor way to do it.
Since you want to display different components within your Blazor application, it has to be handled by Blazor and not the "outer layer" of ASP. Core. So, classical redirects won't work here.
The only way you have in Blazor is the NavigationManager. Let's start with the component Page1.razor. I'd recommend putting this component inside a wrapper container. This container chooses, based on the URL, which component should be loaded. I call this component Page1Wrapper.razor.
Page1Wrapper.razor
#inject NavigationManager NavManager
#page "/Page1"
#switch (_host)
{
case "www.mydomain1.com":
default:
<Page1ComponentForMyDomain1></Page1ComponentForMyDomain1>
break;
case "www.mydomain2.com":
<Page1ComponentForMyDomain1></Page1ComponentForMyDomain1>
break;
}
#code {
private string _host;
protected override void OnInitialized()
{
_host = new Uri(NavManager.BaseUri).Host;
base.OnInitialized();
}
}
There would be a lot of redundancy for each wrapper component. However, there are ways to generalize it, like create an own attribute for the components that should differ based on the URL and adding logic to create the wrapper components on the fly.
Page1ComponentForMyDomain1
#attribute [DomainSpecificRoute("/Page1","www.mydomain1.com")]
.. existing content goes here
#code {
existing code goes here
}
Please, let me know what you are thinking and what option you would like to explore in more detail.
Related
I have a solution based on the Visual Studio template that is successfully using IdentityServer4.
What think I understand is:
The IS4 integration implements endpoints at /authentication/{action}, e.g. authentication/login
IS4 routes that request to host/Identity/Account/Login.cshtml. I have scaffolded identity so I can see that .cshtml file in my project.
The code-behind Login.cshtml.cs takes care of speaking with SignInManager, etc.
In the client project the "LoginDisplay.razor" component redirects to /authentication/login when I click the login button. I presume that this is where step 1 above is invoked, which redirects to the location in step 2.
Fine. Now, I want to customise the whole login UI.
From instructions at: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-5.0 I can see that:
I can configure authentication paths to be anything I want. For example:
builder.Services.AddApiAuthorization(options => {
options.AuthenticationPaths.LogInPath = "security/login";
})
So, I have created a razor component to handle security/login:
#page "/security/{action}"
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="#Action">
<LoggingIn>
This is the new login page
</LoggingIn>
</RemoteAuthenticatorView>
#code{
[Parameter] public string Action { get; set; }
}
My expectation was that after clicking "Login", I would be routed to the above and presented with a page that just said:
"This is the new login page"
and thought that from there, I would:
Customise the UI within the <LoggingIn> fragment.
Make a call to an API that would then replicate the logic in the scaffolded login.cshtml file that actually logs the user in.
That line of code from the login.cshtml.cs file looks like this:
var result = await _signInManager.PasswordSignInAsync(
Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
However, it seems that the razor component I created for /security/login is simply a 'transient' message that appears before being routed to the scaffolded login.csthml file.
So, to me, it seems that I am unable to actually change the physical page used to collect the user's credentials. I am only able to change the transient screen that appears before the originally scaffolded login page is shown.
Does this mean that if I want a customised UI for logging in I need to directly edit the scaffolded page as opposed to creating a whole new UI in the WebAssembly project that calls an APIController that I create to take care of using SignInManager?
It would actually be a lot less work to do that than to take the approach of creating a Client-side UI that calls an API that I create, etc. so, with hindsight, editing the scaffolded cshtml file is probably the best way to go. But am still confused as to what value is really being brought by being able to configure options.AuthenticationPaths.LogInPath.
Before I start, I am aware there are similar questions to this, but they all pertain to using slashes with parameters in the routes which is not what I am trying to achieve (at least not yet).
I am creating an ASP.NET Core 2 MVC web application and am having problems with slashes in my routes being escaped when creating links to my actions. I have the following controller:
[Route("Manage/Account")]
public class AccountsController : Controller
{
[Route("[Action]")]
public IActionResult Index() => View();
[Route("[Action]")]
public IActionResult Other() => View();
}
And the following links in a view:
<a asp-controller="Manage/Accounts" asp-action="Index">Accounts</a>
<a asp-controller="Manage/Account" asp-action="Other">Accounts (Other)</a>
This generates the following HTML:
Accounts
Accounts (Other)
Notice that the slash has been escaped to %5C which to be fair does work and the links actually take me to the correct actions. Additionally manually putting the slash back in via the browsers address bar also takes me to the correct action. However this does give me some ugly URLs that I would rather not have.
Can anyone suggest a way to stop it from escaping my slashes?
Edit: More details
In my efforts to distil my problem down to the bare minimum I fear I may have left out some important context regarding my ultimate goal.
I am trying to implement a a feature folders setup that I have successfully used with ASP.NET MVC5 that makes use of attribute routing and a custom view engine to allow nested features. To make this work I used the [RoutePrefix(...)]. The controller below would be in the following directory ~/Features/Manage/Accounts:
[RoutePrefix("Manage/Accounts")]
public class Accounts : Controller
{
[Route("Index")]
public ActionResult Index() => View();
[Route("Other")]
public ActionResult Other() => View();
}
Links were then added in views like this:
#Html.ActionLink("Accounts", "Index", "Manage/Accounts")
#Html.ActionLink("Accounts (Other)", "Other", "Manage/Accounts")
Which then renders as:
Accounts
Accounts (Other)
Sadly the [RoutePrefix(...)] attribute is not available in ASP.NET Core MVC, and it would appear that using the standard [Route(...)] attribute is not able to emulate the behaviour found in MVC5.
It appears that you're confusing controllers and routes. When using asp-controller, you're expected to provide the name of a controller class (without the Controller suffix). In your case, you need:
<a asp-controller="Accounts" asp-action="Index">Accounts</a>
<a asp-controller="Accounts" asp-action="Other">Accounts (Other)</a>
Using the above, you should end up with the expected routes:
Accounts
Accounts (Other)
As an aside, you can simplify your attributes, like so:
[Route("Manage/Account/[action]")]
public class AccountsController : Controller
{
public IActionResult Index() => View();
public IActionResult Other() => View();
}
The [action] token can be applied at the controller level, as shown, avoiding the need to add it to each method.
What #Kirk said is exactly correct. I just want to give you an idea of Area in MVC as well, because by looking at the links in your example, it looks like you want to have those links start with /manage.
I previously shared how to set up areas in MVC. You can take a look here.
I'm trying to develop a multi-tenancy project wherein each client can have their own specific view engines.
Specifically I'd like to compile the views to a DLL (using RazorEngine) and have an individual RazorViewEngine for each client but also provide a fallback to the standard RazorViewEngine if no matching views are found, just as the MVC framework does if you have multiple view engines specified.
I have found I can inject view engines using autofac in the Global.asax of my MVC 4 project using:
ViewEngines.Engines.Clear();
var builder = new ContainerBuilder();
builder.RegisterType<WebFormViewEngine>().As<IViewEngine>();
Now I also want to provide tenant specific overrides as mentioned above which I can do with the following code:
var mtc = new MultitenantContainer(tenantIdStrategy, builder.Build());
mtc.ConfigureTenant("Client1", b => b.RegisterType<RazorViewEngine>().As<IViewEngine>());
DependencyResolver.SetResolver(new AutofacDependencyResolver(mtc));
In this example code I just wanted to see if I could set the WebFormsViewEngine as a fallback and then enable the RazorViewEngine for a specific tenant.
Upon loading and browsing to a non-tenant url, mvc will resolve just the WebFormsViewEngine as expected (through calling the DependencyResolver and in turn Autofac), this works as expected however when I then visit a url that would also include the RazorViewEngine no views are found, even though a valid razor view exists.
Conversely if I stop IISExpress or do something to generate an app pool recycle and visit a tenantable url first both view engines are registered.
From what I can tell MVC caches the list of view engines retrieved after the first call to the MultiServiceResolver.
internal IViewEngine[] CombinedItems
{
get
{
IViewEngine[] combinedItems = _combinedItems;
if (combinedItems == null)
{
combinedItems = MultiServiceResolver.GetCombined<IViewEngine>(Items, _dependencyResolver);
_combinedItems = combinedItems;
}
return combinedItems;
}
}
Is there a way to override this caching or another better way to achieve what I'm trying to get here?
TemplateController:
this works:
return PartialView("_Create");
but this does not work:
return PartialView();
The asp.net mvc convention should actually check a View folder with the name of the controller => "Template" and check for a View the same name as the action => "Create".
This is valid for a return View(). Why does a return PartialView() not just consider the underscore?
This answer is specifically for ASP.NET MVC5. It may require slight modification to work with other version of MVC but it should generally be applicable.
To have return Partial(model) respect underscores on partial names, you need a custom view engine. Fortunately this is an exceedingly trivial custom view engine.
public class CustomRazorViewEngine : RazorViewEngine
{
public CustomRazorViewEngine()
{
var underScored = new[] { "~/Views/{1}/_{0}.cshtml", "~/Views/{1}/_{0}.vbhtml" }
PartialViewLocationFormats = underScored.Union(PartialViewLocationFormats).ToArray();
}
}
The following format is the default patterns for Shared views:
~/Views/Shared/{0}.cshtml
~/Views/Shared/{0}.vbhtml
You could include alternates for these too if you wish. If you specifically want to only serve files with the underscore, remove the union and merely use: PartialViewLocationFormats = underScored;
This is with the razor view engine, I assume it would be comparable with the webforms view engine if that's your engine of choice.
Lastly you need to register this to be the view engine:
public class Startup
{
public void Configuration(IAppBuilder app)
{
//View Engines
ViewEngines.Engines.Remove(ViewEngines.Engines.Single(x => x is RazorViewEngine));
ViewEngines.Engines.Add(new CustomRazorViewEngine());
The Startup class is specific to MVC5, this would vary slightly between versions. You could use App_Start files with webactivator or the global.asax in other versions.
It is only a naming convention that partial views should start with underscore.
but strangely /mvc engine doesn't search for partial views with underscore.
so you have to explicitly say return PartialView("_Create").
or
Break the naming convention so that u don't have strings lying in your code.
After reading up on this across the web, opinions are very divided as to why the _ should be there / if it should be there at all.
I would argue that this is not a asp.net mvc naming convention, and the proof is that the framework itself doesn't adhere to this. (as you pointed out in your question)
The origin of the _ comes from webmatrix/asp.net where _ is used for resources that aren't directly servable to the user.
The only thing that could be prefixed are views that are reusable components such as _layout, and maybe a _nav or _datepicker
Razor kind of removed the concept of a partial views anyway (as there is no difference similar to the one between .aspx and .ascx, in razor it's all .cshtml anyway).
I would like to utilize some of the social collaboration features in Sharepoint 2010, such as the Noteboard webpart and tagging, but do not want to use the My Site profile pages.
I have already built a custom control that redirects from the userdisp.aspx page to a custom user profile page. I would like to continue to use that custom profile page. However, it seems like user profile links that are generated by the Noteboard webpart, for example, go directly to the /Sites/MySites/person.aspx page without being routed through the /_layouts/userdisp.aspx page. So my profile redirect control doesn't catch it.
In Sharepoint Central Admin, under Manage Service Applications > User Profile Service Application > Manage User Permissions, I have only checked the box for "Use Social Features", not "Create Personal Site," so I am not sure why the profile page is not linking to the old userdisp.aspx page.
Is it possible to redirect these links back to the userdisp.aspx page?
It appears to be hardcoded into the webpart.
I looked at Microsoft.SharePoint.Portal.WebControls.SocialCommentControl and the link comes from UserProfile.PublicUrl, which is defined as:
public override Uri PublicUrl
{
get
{
string userProfileUrl = UserProfileGlobal.GetUserProfileURL(
this.m_objManager.UserProfileApplicationProxy,
this.m_objManager.PartitionID,
"?accountname=",
this.m_UserProfileFields["AccountName"].ToString());
if (!string.IsNullOrEmpty(userProfileUrl))
return new Uri(userProfileUrl);
else
return (Uri) null;
}
}
which eventually calls:
internal static string GetUserProfileURL(string profileWebUrl, string strIdentifier, string strValue)
{
if (string.IsNullOrEmpty(profileWebUrl))
return (string) null;
else
return PersonalSpaceGlobal.EnsureTrailingSlash(profileWebUrl)
+ "Person.aspx"
+ strIdentifier
+ (strIdentifier.Equals("?accountname=", StringComparison.OrdinalIgnoreCase)
? SPHttpUtility.UrlKeyValueEncode(strValue).Replace(":", "%3A")
: SPHttpUtility.UrlKeyValueEncode(strValue));
}
I can think of two workarounds:
Add jQuery to your page to change the URL (selector = span.socialcomment-username > a)
Create your own webpart containing a custom control that inherits from SocialCommentControl, which overrides RenderComment.
Overriding RenderComment is probably going to be messy. You will need to copy the decompiled code for the method just to change the following into your own code:
SocialCommentControl._GetProperty(
comment,
SocialCommentControl.SocialCommentProperty.PublicPage)
Hopefully, there are no internal method calls within RenderComment's 67 lines of code. Otherwise, it is going to be a lot more difficult to implement. It would be a lot easier if you could simply override _GetProperty, but unfortunately, it is a static method.
All of that to say, I would probably recommend the jQuery option over extending SocialCommentControl.