.net core 3.1 - View Components - Not finding my Default.cshtml - razor-pages

I'm just getting to grips with ViewComponents in my Razor pages application.
I have a ViewComponents folder within my project that contains my ViewComponent .cs code:
public class RemoveFromCartViewComponent : ViewComponent
{
public IViewComponentResult Invoke()
{
var result = "123";
return View(result);
}
}
I then have another folder within Pages/Shared/Components called RemoveFromCart. Within this folder I have my default.cshtml
#model string
<h2>
#Model
</h2>
Simply putting the string within a h2 tag.
In my projects Layout.cshtml file I am invoking this ViewComponent:
<div>
#await Component.InvokeAsync("RemoveFromCart")
</div>
When I start my project, the error I get is:
*InvalidOperationException: The view 'Components/RemoveFromCart/123' was not found. The following locations were searched:
/Pages/Components/RemoveFromCart/123.cshtml
/Pages/Shared/Components/RemoveFromCart/123.cshtml
/Views/Shared/Components/RemoveFromCart/123.cshtml*
This is indication my view should be called 123.cshtml which doesnt seem right. What am I doing wrong here? I should simply expect to see the text 123 appear
Thanks

By returning View("123"), you are using this overload:
public ViewViewComponentResult View (string viewName)
Returns a result which will render the partial view with name viewName.
So you are passing the view name, instead of a string value as the view’s model.
You can change that by explicitly calling the View<TModel>(TModel) overload instead:
public IViewComponentResult Invoke()
{
var result = "123";
return View<string>(result);
}
In the long run, I would suggest you to create a model class instead so that you can pass an object instead of just a string. This will avoid having this particular problem and you are also able to easily expand the model contents later on.

Related

How do I get a parameter to not just display in a component, but also be recognized inside of OnInitializedAsync()?

I'm working on a blazor server-side project and I have a component that gets passed a model (pickedWeek) as a parameter. I can use the model fine in-line with the html, but OnInitializedAsync always thinks that the model is null.
I have passed native types in as parameters, from the Page into a component, this way without an issue. I use a NullWeek as a default parameter, so the number getting used in OnInitializedAsync only ever appears to be from the NullWeek. In case this is related, there is a sibling component that is returning the Week model to the Page through an .InvokeAsync call, where StateHasChanged() is being called after the update. It appears that the new Week is getting updated on the problem component, but that OnInitializeAsync() either doesn't see it, or just never fires again- which maybe is my problem, but I didn't think it worked that way.
For instance, the below code will always show "FAILURE" but it will show the correct Week.Number. Code below:
<div>#pickedWeek.Number</div>
#if(dataFromService != null)
{
<div>SUCCESS</div>
}
else
{
<div>FAILURE</div>
}
#code{
[Parameter]
public Week pickedWeek { get; set; }
protected IEnumerable<AnotherModel> dataFromService { get; set; }
protected override async Task OnInitializedAsync()
{
if (pickedWeek.Number > 0)
{
dataFromService = await _injectedService.MakeACall(pickedWeek.Id);
}
}
}
#robsta has this correct in the comments, you can use OnParametersSet for this. Then, you will run into another issue, in that each rerender will set your parameters again and generate another call to your service. I've gotten around this by using a flag field along with the the OnParametersSet method. Give this a shot and report back.
private bool firstRender = true;
protected override async Task OnParametersSetAsync()
{
if (pickedWeek.Number > 0 && firstRender)
{
dataFromService = await _injectedService.MakeACall(pickedWeek.Id);
firstRender = false;
// MAYBE call this if it doesn't work without
StateHasChanged();
}
}
Another alternative is to use the OnAfterRender override, which supplies a firstRender bool in the the method signature, and you can do similar logic. I tend to prefer the first way though, as this second way allows it to render, THEN sets the value of your list, THEN causes another rerender, which seems like more chatter than is needed to me. However if your task is long running, use this second version and build up a loading message to display while the list is null, and another to display if the service call fails. "FAILURE" is a bit misleading as you have it as it's being displayed before the call completes.
I've also found that a call to await Task.Delay(1); placed before your service call can be useful in that it breaks the UI thread loose from the service call awaiter and allows your app to render in a loading state until the data comes back.

change label value using value stored at session

i have two jsf pages (home.jsf and employees.jsf) ,
home page has a button that navigates to employees page,
while navigating i store value in session scope
at (Managed bean)
public void putSessionAL(ActionEvent actionEvent) {
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("key","value");
}
public String navigate() {
return "employees";
}
i want to change Label at employees viewObject from UIHints tab depending on value stored at session using the following groovy expression
adf.context.sessionScope.key
and changed trustMode to trusted but it fires the following exception
oracle.jbo.script.ExprScriptException: JBO-29114 ADFContext is not setup to process messages for this exception. Use the exception stack trace and error code to investigate the root cause of this exception. Root cause error code is JBO-25188. Error message parameters are {0=Employees.FirstName, 1=, 2=oracle.jbo.script.ExprSecurityException}
at oracle.jbo.script.ExprScriptException.throwException(ExprScriptException.java:316)
at oracle.jbo.script.ExprScriptException.throwExceptionWithExprDef(ExprScriptException.java:387)
at oracle.jbo.ExprEval.processScriptException(ExprEval.java:599)
at oracle.jbo.ExprEval.doEvaluate(ExprEval.java:697)
at oracle.jbo.ExprEval.evaluate(ExprEval.java:508)
at oracle.jbo.ExprEval.evaluate(ExprEval.java:487)
at oracle.jbo.common.NamedObjectImpl.resolvePropertyRaw(NamedObjectImpl.java:680)
at oracle.jbo.server.DefObject.resolvePropertyRaw(DefObject.java:366)
One way to do it at the VO UIHint attribute label level will be programmaticaly by doing as follow :
In your VO go to the java tab and add the RowImpl java class
In the VORowImpl Add the following function
public String getMySessionLabel() {
return (String)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("key");
}
In the Label add the following EL expression :
adf.object.getMySessionLabel()
This technique allow you more control than pure EL, if you want to do more than getting from session for example. In your case pure EL, as you did, should work as well. (Would need to check what is wrong with yours, maybe just missing the
#{adf.context.sessionScope.key}
If you attempt to get your label from a method in viewRowImpl. So this will be executed at least once for each row. I think this solution isn't fit for your case.
anyway ADF as a framework added strong policy and validations in EL in general and especially in version 12.2.x.
The solution for you case as following:
Create new class in model layer which extends oracle.jbo.script.ExprSecurityPolicy class
Override checkProperty method.
#Override
public boolean checkProperty(Object object, String string, Boolean b) {
if (object.getClass().getName().equals("oracle.adf.share.http.ServletADFContext") && string.equals("sessionScope")) {
return true;
}
return super.checkProperty(object, string, b);
}
Open adf-config.xml source and in startup tag set your class ExprSecurityPolicy property.
like:
<startup ExprSecurityPolicy="model.CustomExprSecurityPolicy">

No routing convention was found to select an action for the OData path with template '~/entityset'

I have two Odata action methods defined. The one with parameter gets invoked while the other without parameter doesnt get invoked and throws error No routing convention was found to select an action for the OData path with template '~/entityset'.
Here is the code of my action methods
[EnableQuery]
public IQueryable<User> GetUser()
{
return db.Users;
}
// GET: odata/User(5)
[EnableQuery]
public SingleResult<User> GetUser([FromODataUri] int key)
{
return SingleResult.Create(db.Users.Where(user => user.Id == key));
}
The query that I am using are as follows
http://bureauservice/api/odata/UserOdata - Doesnt work
http://bureauservice/api/odata/UserOdata(1) - works
Could someone tell me why the first link doesnt work.
Please change the name of the method which returns entityset to "Get[EntitySetName]" or "Get".
Change from
public IQueryable<User> GetUser()
To
public IQueryable<User> GetUserOdata()
Or
public IQueryable<User> Get()
Set the name of the first action as GetUsers (plural) because you are getting the whole collection of users while in the second you are asking for a single user.
You may want to add the parenthesis to the first URL:
http://bureauservice/api/odata/UserOdata()
If you are just starting to proactise odata, then Odata v4 is good start point, as it is an OASIS standard, but v3 is not.
Here is the v4 version Function sample:
https://github.com/OData/ODataSamples/tree/master/WebApiCore/ODataFunctionSample.

Geb / Groovy - Page content not recognized when used within other Page Object method

When I click a button I have to wait for some dynamic content to be rendered. When I put the waitFor closure in the test it works correctly. However, I wanted to put the waitFor in a method inside the Page object so I do not have to always call the waitFor after every click, but when I do that it fails stating it cannot find the property.
This does not work:
class LandingPage extends Page {
static content = {
resultsBtn(to: ResultsPage) { $("button", id: "showresults") }
}
void getResults() {
resultsBtn.click()
waitFor { ResultsPage.results.displayed }
}
}
class ResultsPage extends Page {
static content = {
results { $("div", id: "listresults") }
}
}
class ShowResults extends GebReportingTest {
#Test
public void displayResults() {
to LandingPage
getResults()
}
}
The error states something like "No such property: results for class ResultsPage".
Is it possible to put references to content from other Page Objects inside other Page Object methods?
EDIT: I feel like this is more of a Groovy specific thing rather than Geb. I'm not sure if it's even possible to access bindings within the content closure. But it also seems like creating a getVariable() function inside the Page Object doesn't help much either.
First you shouldn't assign closures in content blocks (there's unnecessary = in ResultPage) but pass them to an implicit method, you should have:
static content = {
results { $("div", id: "listresults") }
}
The other question is why do you want to model this as two pages? As far as I understand clicking the button doesn't cause a page reload but there's an ajax call to retrieve the results. I would simply put both results and resultsBtn as contents of one page and your problem would be gone.
EDIT:
It turns out that a page change is involved in your case. Assuming that you always want to wait for these results to appear you can either:
put your waitFor condition inside of static at = {} block for ResultsPage - at checks are executed implicitly whenever you use to() which means that it will wait wherever you go to that page
put a waitFor in a page change listener
access current page via the browser property on a page, in LandingPage: waitFor { browser.page.results.displayed } but this seems like a dirty solution to me - reaching from one page to another...

Can I return a string using the #helper syntax in Razor?

I have a RazorHelpers.cshtml file in app_code which looks like:
#using Molecular.AdidasCoach.Library.GlobalConstants
#helper Translate(string key)
{
#GlobalConfigs.GetTranslatedValue(key)
}
However, I have a case where I want to use the result as the link text in an #Html.ActionLink(...). I cannot cast the result to a string.
Is there any way to return plain strings from Razor helpers so that I can use them both in HTML and within an #Html helper?
Razor helpers return HelperResult objects.
You can get the raw HTML by calling ToString().
For more information, see my blog post.
I don't think there is a way to make #helper return other types than HelperResult. But you could use a function with a return type of string, e.g.
#functions {
public static string tr(string key) {
return GlobalConfigs.GetTranslatedValue(key);
}
}
then
#Html.ActionLink(tr("KEY"), "action", "controller")
See also http://www.mikesdotnetting.com/article/173/the-difference-between-helpers-and-functions-in-webmatrix
edit: MVC Razor: Helper result in html.actionlink suggests your helper can return a string by using #Html.Raw(GlobalConfigs.GetTranslatedValue(key));
In your case, I think this would also work:
#(GlobalConfigs.GetTranslatedValue(key))
Additional sample:
#helper GetTooltipContent()
{
if(Model.SubCategoryType == SUBCATTYPE.NUMBER_RANGE)
{
#(string.Format("{0} to {1}", Model.SubCategoryMinimum, Model.SubCategoryMaximum))
}
else if(Model.SubCategoryType == SUBCATTYPE.NUMBER_MAXIMUM)
{
#("<= " + Model.SubCategoryMaximum)
}
else if(Model.SubCategoryType == SUBCATTYPE.NUMBER_MINIMUM)
{
#(">= " + Model.SubCategoryMinimum)
}
}
The following statements have been validated against MVC version 5.2.4.0. I am mostly targeting the part with: Is there any way to return plain strings from Razor helpers so that I can use them both in HTML and within an #Html helper?
I did some research on how the built in MVC helpers work and they are actually properties of System.Web.Mvc.WebViewPage class, so they have nothing to do with #helper feature.
Any #helper encodes strings as HTML and works as if the code is copy pasted to the View inside a Razor code block, aka #{ code }. On the other side, #functions are supposed to be used inside Razor blocks.
Well, if a #helper works as if the code is copy pasted, why not use #Html.Raw("<p>cool</p>")? Because the Html property is null inside helpers. Why? I have no idea.
Ok, but we can use a function to return a string and then apply #Html.Raw on the result. Does that work? Yes, it does. The following example creates a <p> element in the DOM:
#functions
{
static string GetString()
{
return "<p>awesome</p>";
}
}
#Html.Raw(GetString())
If you don't understand why #Html.Raw is necessary, please read this fine article from #SLaks about Razor automatic HTML encoding.
What about the approach with the built in properties? Yes, it is possible to create static classes with public methods that work just like that. The only problem is that you have to include the namespace in the View, with the #using keyword. Can that be improved? Yes, by adding the namespace in the Web.config within the Views folder. Example:
Helpers/Global.cs
namespace WebApp.Helpers
{
public static class Global
{
public static IHtmlString GetString()
{
return new HtmlString("Something <b>cool</b>");
}
}
}
Views/Web.config
<?xml version="1.0"?>
<configuration>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.4.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<!-- Add to the end of namespaces tag. -->
<add namespace="WebApp.Helpers" />
Usage
#Global.GetString()
What is the outcome? The text Something and an additional <b> element will be found in the DOM. If you need access to the Request, simply add an HttpContextBase parameter to the helper method and pass the WebViewPage.Context property when calling it.
Can it get better? Yes, as always. The same output can be created with #helper and #functions:
#helper GetString1()
{
#(new HtmlString("Something <b>awesome</b>"))
}
#functions {
public static IHtmlString GetString2()
{
return new HtmlString("Something <b>awesome</b>");
}
}
#MyHelper.GetString1()
#MyHelper.GetString2()
Answer
Regarding OP's question, I recommend #Spikolynn's approach to create a function that returns string. However, if you need to write many lines of C# code in the helper, I suggest using a static class helper.

Resources