Generate anchor tag using Razor Pages - razor-pages

I'm familiar with the CRUD examples of how to generate an anchor link using asp-page and asp-route-id.
There are a lot of these examples but how do I construct an anchor tag that looks like this and is on a regular page that a visitor (and not an admin(CRUD)) would visit - for instance on a list-of-blog-posts-page:
My first blog post
My first blog post
I'm looking to use Razor syntax, and not by concatenating strings - something like this:
<a asp-page="blog/#{seoblogtitle}" asp-route-id="#model.id">#item.Title</a>
Where #{seoblogtitle} is a route template and #model.id is a GUID.
TL;DR;
How do you contruct an anchor link like the one below using Razor in a Razor Page?
My first blog post

It's not entirely clear what you are after. You mention a Guid, but you don't feature it in the generated URL. I am working on the assumption that you need to pass the Guid along with the seo-friendly title. What you can do is construct a Dictionary<string, string> and pass that to the asp-all-route-values attribute of the Anchor tag helper.
Your Blog page has this template: #page "{title}/{id}"
Your model includes a List<BlogTitle> property (Links) where the BlogTitle class looks like this (at a minimum):
public class BlogLink
{
public string Title { get; set; }
public Guid Id { get; set; } = Guid.NewGuid();
}
In the content page, you would do this:
#foreach(var link in Model.Links)
{
var routeValues = new Dictionary<string, string>
{
{ "title", SeoFriendly(link.Title) },
{ "id", link.Id.ToString() }
};
<a asp-page="blog" asp-all-route-data="routeValues">#link.Title</a><br />
}
The SeoFriendly method is used to convert "My First Blog Title" etc to its seo-friendly version. It could even be defined in a #functions section in the page:
#functions{
public static string SeoFriendly(string s)
{
return s.Replace(" ", "-").ToLower(); // you need something more robust than this
}
}
See Note 1 here: https://www.learnrazorpages.com/razor-pages/tag-helpers/anchor-tag-helper
Note: if you want to keep the dictionary generation code out of the content page, add a Dictionary<string, string> property to your BlogTitle class and generate the values in the PageModel.

Related

How to use ServiceStack Templates to support dynamic results based on request type?

With ServiceStack's Razor Story we have a variety of ways of selecting which Razor View we want to use to render a page. Even better, and critical in my case, is we can pass in a Content-Type header (or query string parameter, or even page "suffix") as well to return the raw model in a variety of formats.
Is there any way to use ServiceStack Templates (now known as SharpScript) to do the same thing? I follow the example here but I just get back the standard HTML format response. It doesn't use my template, no matter how named.
Following the example in the v5.5 Release Notes:
[Route("/hello/{Name}")]
public class Hello : IReturn<HelloResponse>
{
public string Name { get; set; }
}
public class HelloResponse
{
public string Result { get; set; }
}
public class HelloService : Service
{
public object Any(Hello request) => new HelloResponse { Result = $"Hello, {request.Name}!" };
}
Going to /hello/World?format=html provides me the standard HTML report, not my template. I followed another example to force it to use the template ....
public object Any(Hello request) =>
new PageResult(Request.GetPage("examples/hello")) {
Model = request.Name
};
... and it ALWAYS returns my template, even if I specify /hello/World?format=json.
Is there any way to have Razor-like view selection for ServiceStack + ScriptSharp pages, but also support different response formats?
It's hard to answer a vague question like this without details of a specific scenario you want to achieve that's not working.
You can return Sharp Pages in a number of ways:
When it's requested directly as a content page, e.g /dir/page -> /dir/page.html
Using Page Based Routing, e.g /dir/1 -> /dir/_id.html
As a View Page in response to a Service when it's named after the Request DTO or Response DTO, e.g /contacts/1 -> /Views/GetContact.html or /Views/GetContactResponse.html
Select which view to render inside your Service by returning your Response DTO inside a custom HttpResult:
public object Any(MyRequest request)
{
...
return new HttpResult(response)
{
View = "CustomPage", // -> /Views/CustomPage.html
//Template = "_custom-layout",
};
}
Add the [ClientCanSwapTemplates] Request Filter attribute to let the View and Template by modified on the QueryString, e.g: ?View=CustomPage&Template=_custom-layout
[ClientCanSwapTemplates]
public object Any(MyRequest request) => ...
Choosing which page you want to render inside your Model View Controller Service by returning a custom PageResult:
public class CustomerServices : Service
{
public object Any(ViewCustomer request) =>
new PageResult(Request.GetPage("examples/customer")) {
Model = TemplateQueryData.GetCustomer(request.Id)
};
}
Note: That the SharpPagesFeature resolves pages using your cascading AppHost.VirtualFileSources. In .NET Core it's configured to use its WebRoot, e.g /wwwroot.
For Sharp Pages to return its Response in Multiple Content Types:
as well to return the raw model in a variety of formats.
You need to use a Sharp APIs which return a value, e.g. /hello/_name/index.html:
{{ { result: `Hello, ${name}!` } | return }}
To succinctly answer my own question, the first option from #mythz is what I needed. After calling Plugins.Add(new SharpPagesFeature()) in my AppHost, I needed to return HttpResult from my service method:
public object Any(MyRequest request)
{
...
return new HttpResult(response)
{
View = "CustomPage", // -> /Views/CustomPage.html
//Template = "_custom-layout",
};
}

How to send Model to _Layout.cshtml (I need to send another model to Index view too)

I need to send 2 different Models, one to Index view and another one to _Layout.cshtml, how I can do it?
My HomeController:
[Route("")]
public ActionResult Index()
{
HomeViewModel model = new HomeViewModel();
model.A = _repoA.GetLatest(4);
model.B = _repoB.GetLatest(4);
model.C = _repoC.GetLatest(4);
return View(model);
}
I don't like using ViewBag, ViewData & ..., I'm looking for passing the model in same way as we passing model to Views.
You can place this in your Layout to load a partial each time... Pretty useful for loading in a piece of a dynamic menu or a widget on each page.
Along with this line in your layout you can just do your Index page as you normally would.
#{ Html.RenderAction("_widget", "Home"); }
You'll need to send it along in the ViewBag. I found the best bet was to make an abstract controller:
public abstract class ApplicationController : Controller
{
protected ApplicationController()
{
UserStateViewModel = new UserStateViewModel();
//Modify the UserStateViewModel here.
ViewBag["UserStateViewModel"] = UserStateViewModel;
}
public UserStateViewModel UserStateViewModel { get; set; }
}
Then, have all of your controllers inherit from this abstract controller.
In your _Layout.cshtml (or whatever you called it), you'll need to include the following at the top:
#{
var userState = (UserStateViewModel)ViewBag.UserStateViewModel;
}
Duplicate but refined from the 2nd answer to ASP.NET MVC Razor pass model to layout.

Orchard ContentItem's AutoroutePart DisplayUrl

I have a custom content type built through the UI (e.g. not via a module) that has a couple of fields on it, one of which is a ContentItemPicker. I managed to get everything with the front-end working for this with the exception of finding the friendly URL off of the ContentItem from the Model's collection of items. I'm seeing some examples where I'm supposed to use Url.ImageDisplayUrl([ContentItem]), but that gives me this error: 'System.Web.Mvc.UrlHelper' has no applicable method named 'ItemDisplayUrl' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
My using statements at the top are as follows:
#using Orchard.ContentPicker.Fields
#using Orchard.Utility.Extensions;
#using System.Linq
#using Orchard.ContentManagement;
#using Orchard.Mvc.Html;
#using Orchard.Utility.Extensions;
I'd assume I'm missing something with those, but can't seem to figure out what. The way I'm building out my view is below, and the URL I am trying to get is off of tab.ContentItem.HomepageTab.NavigationItem:
/** #Model.Items is a collection of my custom content types that were created through the UI **/
foreach (var tab in #Model.Items)
{
var t = new SliderTab
{
DisplayOrder = tab.ContentItem.HomepageTab.DisplayOrder.Value,
ButtonText = tab.ContentItem.HomepageTab.ButtonText.Value,
Description = tab.ContentItem.HomepageTab.Description.Value,
ImageUrl = tab.ContentItem.HomepageTab.Image.Url,
Title = tab.ContentItem.TitlePart.Title,
ContentItem = tab.ContentItem,
TabText = tab.ContentItem.HomepageTab.TabText.Value
};
/** HomepageTab is the custom content type created in the Orchard UI which has a ContentPickerField associated with it. The name on that is NavigationItem, so I just need the friendly URL off of a ContentPickerField's associated ContentItem **/
if (tab.ContentItem.HomepageTab.NavigationItem != null && tab.ContentItem.HomepageTab.NavigationItem.Ids != null)
{
//this is way, super hacky - getting the actual friendly URL would be better
t.NavigateUrl = "/Contents/Item/Display/" + tab.ContentItem.HomepageTab.NavigationItem.Ids[0];
}
tabs.Add(t);
}
** Edit **
I have a class declaration for HomepageTab at the top which does not correlate to the tab.ContentItem.HomepageTag as that is dynamic off the ContentItem property. It is structured like this:
public class HomepageTab
{
public dynamic DisplayOrder { get; set; }
public string ImageUrl { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ButtonText { get; set; }
public dynamic ContentItem { get; set; }
public string TabText { get; set; }
public string NavigateUrl { get; set; }
public string TabId
{
get { return "t" + this.DisplayOrder.ToString(); }
}
}
Thoughts?
tab.ContentItem.HomepageTab.NavigationItem is your content item picker field, but expressed this way, it's a dynamic object, so the compiler can get all sorts of confused if you try to use it without casting it. So first I'd recommend casting:
var pickerField = tab.ContentItem.HomepageTab.NavigationItem as ContentPickerField;
if (pickerField != null) {
Then you can get the first and only item in the field (caution, we're likely causing a select N+1 issue here, see below):
var firstItem = pickerField.ContentItems.FirstOrDefault();
Finally, we can ask for the display URL for that item:
if (firstItem != null) {
var url = Url.ItemDisplayUrl(firstItem);
This should work just fine. Be careful however: as I said above, getting the collection of items for each tab may trigger one new database query per tab, degrading performance. To avoid that problem, you could pre-fetch the related content items with a technique similar to what I describe in this post: https://weblogs.asp.net/bleroy/speeding-up-pages-with-lots-of-media-content-items
Instead of pre-fetching images, what you'd do here is first get a list of all the related ids (that's free, those ids come stored in the fields, which are stored with the content items you already have). Then you'd build a local cache of ids to items using a GetMany. And finally you'd use that cache instead of the ContentItems collection to look-up items from the ids.
I hope this makes sense.

MVC Shared partial view with a dropdown and separate controller

What I am after is having a partial view that hosts a dropdown list of all available languages in the system. This partial view will be used in many edit templates and will be loaded from a separate controller.
Following the articles and information on the net I have the following implementation:
ViewModel
public class LanguagesViewModel
{
public int SelectedID { get; set; }
public virtual SelectList Languages { get; set; }
public LanguagesViewModel(int selectedID, IEnumerable<Language> languages)
{
SelectedID = selectedID;
Languages = new SelectList(languages, "LanguageId", "Name");
}
}
In the Shared folder I have a file: _LanguageDropDownList.cshtml with
#model XNETProductQuote.Web.Models.LanguagesViewModel
#Html.DropDownListFor(model => model.SelectedID, Model.Languages)
I have a LanguageController that has the following implementation
public ActionResult GetAllLanguages()
{
var languages = service.GetAll();
return PartialView("_LanguageDropDownList", new LanguagesViewModel(1, languages));
}
So the above is meant to load the drop down list in that partial view so I can use it in other templates.
From a template that is loaded from a different controller (ApplicationSetting) I call the partial view using:
#Html.Action("GetAllLanguages", "LanguageController")
This doesn't work. It throws an exception:
The controller for path '/ApplicationSetting/Edit/1' was not found or does not implement IController.
What is the correct implementation for such scenario?
Thanks
In Asp.Net MVC when we make a new controller then 'Controller' postfix is automatically attached to the Controller Name for ex:- in your case if you give 'Language' name to the controller then controller's complete name will be like 'LanguageController',so where ever you want to give controller name you have to use only 'Language' not 'LanguageController' and one of the overloads of #Html.Action() is ControllerName which is only 'Language' and not 'LanguageController' ,So in your problem just change #Html.Action("GetAllLanguages", "LanguageController") with #Html.Action("GetAllLanguages", "Language") and your problem will be solved.

Swagger UI displaying extra parameter using ServiceStack

I'm having issues with displaying the correct notes in swagger using ServiceStack.
Given this structure:
[Route("/Widget/{WidgetId}", Summary = "Updates a widget by id", Verbs = "POST",
Notes = "Updates a widget by id.")]
public class UpdateReqWidget : IReturnVoid
{
[ApiMember(Name = "WidgetId", Description = "The id of widget to delete.")]
public int WidgetId { get; set; }
}
public class WidgetService
{
public void Put(UpdateReqWidget req)
{
//do code here
}
}
It's producing:
I would expect the parameters list only to have WidgetId, but it's displaying WidgetId and UpdageReqWidget, the class name for the request. any ideas what I'm doing wrong?
EDIT: I'm using versions 3.9.55 for both ServiceStack and ServiceStack.API.Swagger. I've changed the templates to better suit our needs.
There is a recent addition to ServiceStack.Api.Swagger to automatically generate a request body parameter in the Swagger UI. I think there's an edge case in which the request DTO has no properties that aren't designated as path or query parameters, as in your case here, the code will still generate that UpdateReqWidget request body parameter, but it will be an empty object in Swagger.

Resources