I have a controller that will be shared with three different pages in sitefinity. The API will take in different parameters for each of the three pages. Is there a page setting values I can set the parameters in and be able to use then in the Controller?
You can add public properties to the controller and these will appear in the designer of the widget - when you "Edit" the widget in the backend you can set different values to these properties.
So, each of the 3 pages can set unique values there.
Example:
[ControllerToolboxItem(Name = "TemporaryRedirectController", Title = "Temporary Redirect", SectionName = "Custom Widgets", CssClass = "sfMvcIcn")]
public class TemporaryRedirectController : Controller
{
public string RedirectUrl { get; set; }
public ActionResult Index()
{
if (SystemManager.IsDesignMode)
{
return Content($"This widget will 302 redirect to {RedirectUrl}");
}
if (!this.RedirectUrl.IsNullOrEmpty())
{
return Redirect(this.RedirectUrl);
}
return new EmptyResult();
}
}
In the sample above, you can drag this widget to 3 different pages and when you edit it, you will be able to set a different value to the RedirectUrl field.
Related
I am trying to write a specific value to a page in ASP.NET Core 6. I found multiple solutions with iterators but I am not able to write a single value from non-iteratable models / instances (no enumerators & lists) to a page.
public class UserViewModel
{
public string Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
This models purpose is to get filled with values from the Identity Framework.
public UserViewModel umodel { get; set; }
Said model gets instanced, filled without any problems - it holds values (checked via console) in page.cshtml.cs:
var user = await _userManager.FindByIdAsync(id);
UserViewModel UserModel = new UserViewModel();
UserModel.UserName = user.UserName;
UserModel.Email = user.Email;
UserModel.Id = user.Id;
Console.WriteLine(UserModel.UserName);
Console.WriteLine(UserModel.Id);
Console.WriteLine(UserModel.Email);
If I try to access it on the corresponding page (page.cshtml) I can only access its name without any problems:
#Html.DisplayNameFor(model => model.umodel.Email)
When I want to access its content there is no value on the page.
#Html.DisplayFor(model => model.umodel.Email)
How can I access the values in this model on a razor page? All the solutions I found based on some kind of iterator and therefore models that had some kind of enumerator or where instanced and filled as a list.
From the code you posted, you aren't populating the page's UserViewModel property. You instantiated a different UserViewModel instance. You wrote the values of that to the Console, but the actual model property (umodel) has not been populated.
Try this in the OnGet method:
var user = await _userManager.FindByIdAsync(id);
umodel.UserName = user.UserName;
umodel.Email = user.Email;
umodel.Id = user.Id;
When rendering property values, you don't need the DisplayFor helper (unless you are using display templates). You just need to prefix the property with #:
#Model.umodel.UserName
I figured it out. There hast to be an instance of the model / class but in a specific way and naming. See following example:
Model:
public class IdentityUserModel
{
public string Id { get; set; }
public string ?UserName { get; set; }
public string ?Email { get; set; }
}
Reference to model in the Main class of the Page (page.cshtml.cs):
public IdentityUserModel IUserModel { get; set; }
Then the important part in the OnGet/OnGetAsync function(?) (page.cshtml.cs):
IUserModel = new(); // <-- instance
var user = [whatever...]
IUserModel.UserName = user.UserName;
IUserModel.Email = user.Email;
IUserModel.Id = user.Id;
Then to write on the page.cshtml:
#Model.IUserModel.Id
My understanding is that there has to be an instance of the class in die page context with exactly the same name (therefore = new() without instance name). I may have been blind but reading throug microsofts learn pages again this is was not clear at all to me.
Thanks to Mike Brind for sending me in the right direction with his input.
I'm working on a project where there is an endpoint that receives a list of strings in the parameter, as shown in the code below:
public class PaymentFilter
{
public List<string> Status {get; set; }
}
[HttpGet]
public IActionResult Get([FromQuery] PaymentFilter filter)
{
...
}
However, in the swagger interface the status list is not reflected, as shown in the image:
How do I enable entries for the status list?
Click Try it out button and click Add string item then you can add
parameter
I have utilized the code for a left side nav-bar found here. However, I only need it to appear on specific pages. When it is present, I will need to pass data from the View model to the links in the nav bar in order to successfully navigate to the correct page selected.
Is there a best practice for this other than copy-pasting the code on each page? Seems like I could put the nav bar on _Layout.cshtml, have it appear on specified pages, then pass the data to the links either using MVC or jQuery.
You can have a look at the ASP.NET Core view components. In your _Layout.cshtml, you add some logic to determine if you want to display the component, then you render it with the appropriate parameters.
Here's an example where the links are selected based on a name passed from the controller, but you can add any logic you need, including getting them from your database.
HomeController.cs
public class HomeController : Controller
{
public IActionResult Index()
{
// data to be passed to the component;
// will also be used to determine if the navbar should be displayed
ViewData["NavMenuPage"] = "Index";
return View();
}
}
_Layout.cshtml
// condition to render the navigation menu
#if (ViewData["NavMenuPage"] != null)
{
// ASP.NET Core will search for a component named NavMenu,
// and invoke it with the provided data (here, the NavMenuPage set in the controller)
#await Component.InvokeAsync("NavMenu", ViewData["NavMenuPage"])
}
NavMenuViewComponent.cs
Here, NavMenu is the name of your component, and the class name must be suffixed by ViewComponent for it to be found by ASP.NET Core. That class can be put anywhere in your project.
public class NavMenuViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(string pageName)
{
var links = GetLinksForPage(pageName);
return View(links);
}
private Dictionary<string, string> GetLinksForPage(string pageName)
{
Dictionary<string, string> links = null;
switch (pageName)
{
case "Index":
links = new Dictionary<string, string> {
{ "Link 1", "https://www.google.com" },
{ "Link 2", "https://www.example.com" },
};
break;
case "Privacy":
links = new Dictionary<string, string> {
{ "Link 3", "https://www.stackoverflow.com" },
};
break;
}
return links;
}
}
/Views/Shared/Components/NavMenu/Default.cshtml
This is the code that will render your links. Note that this file can't be put anywhere: it has to follow certain naming conventions, as explained in the doc. Here, I put it in the shared folder.
// type of the object passed to View() call in NavMenuComponent.InvokeAsync
#model Dictionary<string, string>
<nav>
#foreach (var link in Model)
{
<div>#link.Key</div>
}
</nav>
I'm trying to have different login pages based on the client_id.
Use case :
My default login page is a classic username/password type login, but for a specific client_id, the login page asks for 3 different infos that are found one a piece of paper that he received in the mail (sent by a third party).
Once i have these 3 infos, i can validate and find the associated user.
Technicals : So far, i have made it so that once IdentityServer4 redirects /connect/authorize to it's default login route (/account/login), i then redirect to my second login based on the client_id. It works but it is not elegant at all (feels hackish).
I'm sure there is a better way to achieve this, probably thru a middleware that would redirect from connect/authorize to my second login page, directly ?
Any ideas / tips ?
On the very initial Login call to IdentityServer, you call:
/// <summary>
/// Show login page
/// </summary>
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
// build a model so we know what to show on the login page
var vm = await accountService.BuildLoginViewModelAsync(returnUrl);
// some more code here
return View(vm);
}
In the called accountService.BuildLoginViewModelAsync, you have var context = await interaction.GetAuthorizationContextAsync(returnUrl); and in this context you have the clientId. You can extend the LoginViewModel class to include some custom property (of your own) and based on this property, in the AccountController, to return a different view. Then all you need is in the Views folder to create your specific view.
By this way, you can have as many views as you want.
Instead of creating hard coded separate views I simply use the appsettings.json file and specify different client configurations for each clientId. This way I can easily edit the file in the future any time there is a new client without having to re-deploy.
Then within the AccountController Login method I set the title and image of the current LoginViewModel (you have to add Title and Image to the LoginViewModel class) by matching the current clientid to a matching object within the appsettings.json file. Then set the ViewBag.Title and ViewBag.Image right before returning the view.
For information on how to wire-up the appsettings see my answer on this SO article
in the BuildLoginViewModelAsync(string returnUrl) method within the AccountController I do the following:
if (context?.ClientId != null)
{
try
{
_customClients.Value.ForEach(x => {
if (x.Name == context.ClientId)
{
title = x.Title;
image = x.Image;
}
});
}
catch (Exception){}
...
}
Here is my login method within the AccountController:
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
// build a model so we know what to show on the login page
var vm = await BuildLoginViewModelAsync(returnUrl);
if (vm.IsExternalLoginOnly)
{
// we only have one option for logging in and it's an external provider
return RedirectToAction("Challenge", "External", new { provider = vm.ExternalLoginScheme, returnUrl });
}
ViewBag.Title = vm.Title;
ViewBag.Image = vm.Image;
return View(vm);
}
Then in the _Layout.cshtml I use this;
#using IdentityServer4.Extensions
#{
string name = null;
string title = "SomeDefaultTitle";
string image = "~/somedefaulticon.png";
if (!true.Equals(ViewData["signed-out"]))
{
name = Context.User?.GetDisplayName();
}
try
{
title = ViewBag.Title;
image = ViewBag.Image;
}
catch (Exception)
{
}
}
later in the razor I use #title or #image wherever I need either.
So I have a small html table (Calendar) that I need to return to a page. At the parent page level, I use
#{Html.RenderAction("Calendar", "GlobalShared");}
Now, the question is I tried the following 4 ways to return the html and they all work
1. String
[ChildActionOnly]
public String Calendar()
{
return GetCalendarHTML()
}
2. MvcHtmlString
[ChildActionOnly]
public MvcHtmlString Calendar()
{
return MvcHtmlString.Create(GetCalendarHTML());
}
3. ContentResult
[ChildActionOnly]
public ActionResult Calendar()
{
return Content(GetCalendarHTML());
}
4. PartialViewResult
[ChildActionOnly]
public PartialViewResult Calendar()
{
return PartialView("_Calendar", GetCalendarHTML());
}
_Calendar.cshtml
#model String
#{
Layout = null;
}
#Html.Raw(Model)
Which way is the correct way (or better way) to return html and why?
Thanks
PartialViewResult is what you are looking for as it is used for breaking down components of a page and making smaller sections which is what is was designed for. From my experience that is also what I have seen used a lot.
http://msdn.microsoft.com/en-us/library/dd410269(v=vs.98).aspx
"Renders a partial view, which defines a section of a view that can be rendered inside another view"