Interpret Data produced by View Component ASP Razor MVC - c#

I am using a View Component to create a portion of an MVC Page. The View component goes to a database, and produces a table of results if the query produces results. The query producing results means there's a problem, so I've set up the view component to only display if there were results. This query is meant to be re-usable in different parts of the site, too, because its results can impact multiple pages.
Works great for showing the error only when it's an issue!
However, the main page has a form handler for sending an E-Mail message, and I want to disable that capability when the data is invalid. I use ViewData.ModelState.IsValid to check whether the model is valid or not. However, because my Component is indicating whether or not the data is valid, I've run into an issue!
I have no idea how to check whether the component is populated or not, without somehow hardcoding it back to the original page. I can't seem to pass view data from the Component to the calling page (although the other direction works great), I can't even subscribe the page to an event because I see no way to associate the Component instance with the page!
I don't need to check the contents of the component before invalidating the page, I just need to know whether or not the component produced anything at all.
Can anybody help me do this?
Here is the code for the Component:
namespace Reports.Shared.Validation
{
public class IdentifierValidationViewComponent : ViewComponent
{
private readonly IdentifierValidationDB _IdentifierValidationContext;
public IdentifierValidationViewComponent(IdentifierValidationDB IdentifierValidationContext)
{
_IdentifierValidationContext = IdentifierValidationContext;
}
public List<IdentifierValidation> InvalidIdentifiers { get; set; }
public async Task<IViewComponentResult> InvokeAsync(string date)
{
InvalidIdentifiers = await _IdentifierValidationContext.IdentifierValidations.FromSqlRaw("EXEC Reports.IdentifierValidation {0}", date).ToListAsync();
return View(InvalidIdentifiers);
}
}
}
Here is the Partial View that the Component is rendering:
#model List<Reports.Models.Shared.Validation.IdentifierValidation>
#if (Model.Count() > 0)
{
<div id="InvalidIdentifiers" class="alert-danger">
<h2>Values are missing Identifiers!</h2>
<ul>
#foreach (var invalid in Model)
{
<li>
#invalid.SecName has no Identifier.
</li>
}
</ul>
</div>
}
Finally, I invoke the component on the main page with this line
#await Component.InvokeAsync("IdentifierValidation", new { date = Model.Date })
All I want to do is have the main page check if the component is actually producing any HTML or not, and then invalidate ViewData.ModelState.IsValid if so.

I figured it out!
In my case, because I only need to check during the initial load of the page whether or not the table has produced results, and because the status doesn't need to be retained between refreshes, I can use ViewContext.HttpContext.Items[] to store a flag indicating whether or not the table has been populated. I needed to make changes to the Component and the main page, but with the following changes, it seems to work!
Controller:
namespace Reports.Shared.Validation
{
public class IdentifierValidationViewComponent : ViewComponent
{
private readonly IdentifierValidationDB _IdentifierValidationContext;
public IdentifierValidationViewComponent(IdentifierValidationDB IdentifierValidationContext)
{
_IdentifierValidationContext = IdentifierValidationContext;
}
public List<IdentifierValidation> InvalidIdentifiers { get; set; }
public async Task<IViewComponentResult> InvokeAsync(string date)
{
InvalidIdentifiers = await _IdentifierValidationContext.IdentifierValidations.FromSqlRaw("EXEC Reports.IdentifierValidation {0}", date).ToListAsync();
if(InvalidIdentifiers.Count() > 0)
{
ViewContext.HttpContext.Items["InvalidIdentifier"] = "Detected";
}
return View(InvalidIdentifiers);
}
}
}
And now, we can invoke the controller as such:
#await Component.InvokeAsync("IdentifierValidation", new { date = Model.Date })
#if (ViewContext.HttpContext.Items["InvalidIdentifier"] != null)
{
ViewData.ModelState.AddModelError("InvalidIdentifier", "An Invalid Identifier has been Detected!");
}
When we do this, we are invalidating the model state only if the partial produced results when it was loaded on page refresh!
Of course, if you're doing something fancier with it, like using javascript/ajax to refresh the content of the partial on the client side, this will not work.

Related

Blazor cascading component preventing StateHasChanged

So I'm new to Blazor and I've just found out about CascadingValue and their possible use for showing error messages. I am creating an Outlook Add In for saving mails and attachments in Sharepoint, so I'm dynamically retrieving the current Outlook mail using Office-js and JSInterop, such that my Add In always uses the selected mail.
But now that I'm trying to implement a cascading component for error messages I've run into problems with StateHasChanged.
Following code is my cascading error component:
<CascadingValue Value=this>
#ChildContent
</CascadingValue>
<div class="error-container #(ErrorMessage == null ? "hidden" : "")">
<span class="error-message">
#ErrorMessage
</span>
<a class="dismiss-error" #onclick="DismissError">x</a>
</div>
#code {
[Parameter]
public RenderFragment ChildContent { get; set; }
public string ErrorMessage { get; set; }
public void ProcessError(Exception ex)
{
ErrorMessage = ex.Message;
StateHasChanged();
}
public void DismissError()
{
ErrorMessage = null;
StateHasChanged();
}
}
And the component I'm having troubles with (atleast the important parts):
<div>
{Showing details from OutlookMail here}
</div>
#code {
[CascadingParameter] public Error Error { get; set; }
public OutlookMail OutlookMail { get; set; }
private static Action<OutlookMail> _receiveMailAction;
protected override async Task OnInitializedAsync()
{
_receiveMailAction = UpdateMail;
await base.OnInitializedAsync();
}
private void UpdateMail(OutlookMail mail)
{
InvokeAsync(() =>
{
OutlookMail = mail;
StateHasChanged();
});
}
[JSInvokable]
public static void ReceiveMail(OutlookMail mail)
{
_receiveMailAction?.Invoke(mail);
}
public async Task MethodWithError()
{
try
{
await DoStuffWithPossibleError();
}
catch (Exception e)
{
Error.ProcessError(e);
}
}
}
Everything works fine until Error.ProcessError gets called and the error message is shown, after that StateHasChanged stops rerendering my component which gives inconsistent data to the users.
I am probably doing something wrong, since I can't really find anyone else with the same problem, but I'm not seeing it, so I'm hoping someone on here has a better understanding of where things are going wrong, or maybe even a better way of going about showing error messages if the problem is with using CascadingValue like this.
All the help is appreciated.
Edit 1: After some more looking into this, it seems that it's actually not updating OutlookMail, even when I move it outside of InvokeAsync. So there might be something weird happening with instances of my component
After my first edit, I found out it was indeed a problem with instances, since I'm overwriting a static Action within OnInitialized, each time it creates a new instance, the old instances no longer works. And the code which returned the error happened to have a auth dialog with a redirect callback to the same page, it closes directly after, but loading the page actually creates a new instance of my component, which in turn invalidates the one my user works with. So I'll probably just have to create a singleton service that handles the incoming mail data, so that my component can always access the correct data.

Render Blazor child component with parameters

I'm new to Blazor and I'm currently working on a Blazor Webassembly .net 5.0 application.
I try to figure out the correct way to
render a child component
from a parent component
on button click (form submit)
pass parameters from the parent component to => the child component
My current solution seems to work, but unfortunately it ends in an infinite rendering loop: I use the OnParametersSetAsync method in the child component to handle the data loading.
Side note: I use Telerik Blazor components, but it should have no impact.
My parent component looks like this:
View (parent)
// I want to submit a form to set a bool = true, and then to rend the child component - is that ok?
<EditForm OnValidSubmit="#(async () => await StartEverything())">
<label for="OrderNumber">OrderNumber: </label>
<TelerikTextBox #bind-Value="OrderNumber" Id="OrderNumber" />
<TelerikButton ButtonType="#ButtonType.Submit">Start Everything</TelerikButton>
</EditForm>
#if (ShowChild)
{
<MyChildComponent OrderNumber="OrderNumber"/>
}
else
{
<div>Please enter an order number.</div>
}
Code Behind (parent)
public class MyParentComponent : ComponentBase {
protected int OrderNumber { get; set; }
protected bool ShowChild { get; set; }
protected async Task StartEverything()
{
if (OrderNumber > 0)
{
await Task.FromResult(ShowChild = true);
}
}
}
My child component looks like this:
View (child)
#if (Customer != null)
{
<p>#Customer.CustomerName</p>
<p>#Customer.AgencyName</p>
}
Code Behind (child)
public class MyChildComponent : ComponentBase {
// I need this Parameter sent from my parent component
[Parameter]
public int OrderNumber { get; set; }
protected CustomerViewModel Customer { get; set; }
protected override async Task OnParametersSetAsync()
{
var parameterForQuery = OrderNumber; // this should hold the value sent from the parent component
// Load Customer ViewModel Data here - is this the correct event? What is the best approach?
}
}
Item ViewModel
public class CustomerViewModel
{
public string CustomerName { get; set; }
public string AgencyName { get; set; }
}
Do you know how to correctly render a Child Component within a Parent Component and pass parameters from the Parent Component to the child component - then render the child component ONLY ON BUTTON CLICK (form submit, no infinite render loop)?
Do you know how to solve this problem?
I recommend going through https://blazor-university.com. It's the site that kind of got me kick-started when I first started with Blazor.
In regard to your question, I recommend the following:
https://blazor-university.com/components/component-lifecycles/
In particular, the following statement should prove useful in your case (from that link):
OnInitialized / OnInitializedAsync
This method is only executed once when the component is first created.
If the parent changes the component’s parameters at a later time, this
method is skipped.
It seems likely that simply changing which method you override will solve your problem, since OnParametersSetAsync behaves as you've described, and 'OnInitializedAsync' behaves as you want. :D

Wrong value binding with Html.DropDownListFor

Problem description
I'm currently working on a Wizard mechanism in our ASP.NET MVC application. However, I've faced a problem while trying to bind model in the view. In short:
I've got a wizard model which looks more or less like this:
class WizardViewModel {
public IList<StepViewModel> Steps { get; set; }
// ...
}
Each step except for last has got its own model. The last step (summary) takes whole WizardStepModel and is used only to display data (via disabled controls). Displaying values from all steps leads to this kind of code in the view:
#Html.DropDownListFor(
m => ((ConcreteStepModel)Model.Steps[0]).SelectedValue,
((ConcreteStepModel)Model.Steps[0]).SelectList,
new { disabled = "disabled" }
)
The code works, but continuous casting base step model to a concrete class only to get the value:
Is uncomfortable,
makes code less readable.
What I tried to do?
I thought that I could create an alias for each step:
#{
ConcreteStepModel stepOne = (ConcreteStepModel)Model.Steps[0];
}
And then:
#Html.DropDownListFor(
m => stepOne.SelectedValue, stepOne.SelectList, new { disabled = "disabled" }
)
It works for most of controls, but not for DropDownList. For some reason, value of the dropdown is bound incorrectly and shows first option instead of the selected one.
Question
Is there another way which I could use for creating some kind of aliases for steps from the wizard so that I don't have to perform casting each time I need to get a value? Or maybe I am doing something wrong? I'd be grateful for any help.
Since your 'last step' is just a summary and presumably used as a final 'confirmation step' before saving to the database, then you should not be generating disabled form controls for your properties. It would be rendering anywhere 2-5 times the html that is necessary which will just degrade performance.
Instead just generate the value of the property as text, for example
<div class="field">
<div class="field-label">#Html.DisplayNameFor(m => m.Steps[0].SomeProperty)</div>
<div class="field-value">#Html.DisplayFor(m => m.Steps[0].SomeProperty)</div>
</div>
and use the class names to style the layout (and the result would no doubt save a lot of screen space as well).
For the dropdownlist properties, you would need to add an additional property associated with the selected text, so in addition to public int SelectedValue { get; set; } used in the edit view, you would include an associated public string SelectedText { get; set; } property for the 'final step' view.
It also appears from your code that StepViewModel is an abstract base class and you have a concrete class for each 'step'. If that is the case, then it would be better for your WizardViewModel to contain properties for each step
public class WizardViewModel
{
public Step1Model Step1 { get; set; }
public Step2Model Step2 { get; set; }
....
}
which means if you really did want to generate form controls, it would not be be necessary to cast the type. The code would just be
#Html.DropDownListFor(m => m.Step1.SelectedValue, Model.Step1.SelectList, new { disabled = "disabled" })

N2 NullReferenceException on "Html.DroppableZone("h1").Render()"

I'm currently evaluating N2 CMS for use for multiple websites.
We would only like to offer the 'drag' functionality to our clients, which means they can add parts to Zones on the page, fill them out, and drag them around. The "backend" functionality of the management zone will be reserved for the developers.
Therefore, I don't use the SlidingCurtain control to render. Right now, I've made a custom Admin Panel that appears when a user with the correct role is logged in. Normally, the Sliding Curtain adds a "?edit=drag" query string to your URL when you click the 'drag' functionality button, so I add this querystring automatically after logging in.
If I do this, I get a NullReferenceException to the following line:
Html.DroppableZone("H1").Render();
As of this moment, there are no parts on this DroppableZone yet, and I suppose this is what is causing this problem. How do I get around this?
I have the following H1Controller:
namespace EmptyCMS.Controllers
{
[Controls(typeof(Models.H1))]
public class H1Controller : ContentController<Models.H1>
{
public override ActionResult Index()
{
return PartialView("H1", CurrentItem);
}
}
}
And the following partialview:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.H1>" %>
<h1><%= Model.Text %></h1>
And this is my model:
namespace EmptyCMS.Models
{
[PartDefinition("H1")]
[AllowedZones("H1")]
public class H1 : ContentItem
{
[EditableTextBox("Text", 100)]
public virtual string Text { get; set; }
}
}
Can anyone spot what I'm doing wrong? Thanks a lot for any help you can give me.
Just check for null?
var zone = Html.DroppableZone("H1");
if(zone != null)
zone.Render();

ASP.NET MVC navigation route that changes based on viewed data

I am in the process of creating tabbed navigation where the route location can vary. The parameter used to render the tabs should be based on the presently viewed data (which when it is a user, may not be the logged in user).
In the example image this is a user. Therefore, if I am looking at Andrew Steele then the links should be contextual to Andrew Steele (Andrew's summary, computers, accounts etc.). When I am looking at Bruce Hamilton the links should be contextual to Bruce Hamilton (Bruce's summary, computers, accounts etc.).
I've solved this by sticking the necessary parameter value in each ViewModel and then passing the value onto a partial to render the links; this feels kludgey. I'd prefer to not shove the linking parameter data into each ViewModel. It would seem reasonable to use Html.Action or Html.RenderAction combined with a ViewData Dictionary value, but I tend to shy away from using "magic strings" where possible.
Is there a better way to get the parameter value to the view that I am missing?
I wrote a site a while back where I had different tabs that went to different places for different users. I'll walk you through my solution hopefully I understood the question correctly and some of this helps.
As far as getting data to and from the View, I do use the ViewDataDictionary. To the best of my knowledge, that's what it's for when your model doesn't consist of a single simple object. In order to get around the "magic strings" of view keys, I create a bunch of extension methods on the ViewDataDictionary. This has the drawback that you end up with a slew of extra methods, but at least all of your string keys are isolated in a single location. You could even go the extra step of create constants in the class, but it seems redundant when only this class uses them. Extension properties would be better but...
/// <summary>
/// Gets the list of tabs to show.
/// </summary>
/// <param name="dictionary"></param>
/// <returns></returns>
public static IList<TabItemDisplay> TabListGet(this ViewDataDictionary dictionary)
{
IList<TabItemDisplay> result;
if (dictionary.ContainsKey("TabList"))
result = dictionary["TabList"] as IList<TabItemDisplay>;
else
result = null;
return result;
}
/// <summary>
/// Sets the list of tabs to show.
/// </summary>
/// <param name="dictionary"></param>
/// <param name="tabList"></param>
/// <returns></returns>
public static IList<TabItemDisplay> TabListSet(this ViewDataDictionary dictionary, IList<TabItemDisplay> tabList)
{
dictionary["TabList"] = tabList;
return tabList;
}
You'll notice that I have an explicit view object, TabItemDisplay, that I pass into the dictionary. This contains all of the values necessary to pass to Html.ActionLink.
public class TabItemDisplay
{
public string Name { get; set; }
public string Action { get; set; }
public string Controller { get; set; }
public object RouteValues { get; set; }
}
Since this view is not the main content of the page, I prefer to put the logic of creating the tab items, including destination parameters, into an ActionFilter. This allows me to reuse the tab creation logic across different actions and controllers. Any View that contains the tab partial control gets the CreatTabAttribute slapped across the corresponding Action or Controller and it's good to go.
This may be more than you needed, but I hope some of it helps.
EDIT: Just realized I didn't include what this looks like in the partial view. I actually have an HtmlHelper extension that renders a more intricate tab, but you get the idea.
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<div id="tabs">
<%
if (null != ViewData.TabListGet()) {
foreach(var item in ViewData.TabListGet()) {
%>
<%= Html.ActionLink(item.Name, item.Action, item.Controller, item.RouteValues, null)%>
<%
}
}
%>
</div>
EDIT: Adding a short example of the ActionFilter I use.
public class CreateContentTabsAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResultBase;
if (null == result) return;
var routeValues = filterContext.RouteData.Values;
var repository = ObjectFactory.GetInstance<ITabRepository>();
var context = filterContext.HttpContext;
var userName = context.User.Identity.Name; // Or get id from Membership.
var tabs = repository.ReadByUserId(userName);
TabItemDisplay defaultTab = null;
var tabItems = new List<TabItemDisplay>();
foreach (var tab in tabs)
{
var tabItem = new TabItemDisplay
{
Name = tab.Name,
Action = "View",
Controller = "Tab",
RouteValues = new { key = tab.Key }
};
tabItems.Add(tabItem);
}
if (context.Request.IsAuthenticated)
{
tabItems.Add(new TabItemDisplay
{
Name = "Account",
Action = "ChangePassword",
Controller = "Account",
RouteValues = new { siteKey = site.Key }
});
}
result.ViewData.TabListSet(tabItems);
}
}
This is only a basic example of pulling tabs from a repository (instantiated using StructureMap), and a simple check to see if the user is authenticated. But you can do other things such as pull the requested user id for the user being displayed from routeValues.
You can create a viewmodel for this that exposes the tab preference. If each tab is totally different you could have a viewmodel base class that exposes the tab preference and each of the tabs could have their own view model.
Personally, I'd use either RenderAction from the MVC futures or a render partial call here depending on performance requirements and taste.
Advantage to RenderAction is you won't need to pass all the required rendering data to the delegate controller action as it could do the lookup itself. Whereas a partial view would require your main page have enough view data to render the tab. Either way its just two for loops (one for the tab list, one for the tab rendering) and a bit of extra data in your Model.
one solution is to create base controller and have all ur controllers inherit from it. in base controller u can add necessary value into viewmodel
public applicationController:Controller
{
ViewData["linkvals"] = someValues;
}
public HomeContorller:ApplicationController{}
second solution is to create a base view model and have all ur viewmodels inherit it. in constructor of base viewmodel u can create link values and assign them to ur properties
public baseViewModel
{
public LinkVal{get;set;}
public baseViewModel()
{
//calculate link vals
}
}
public ViewModelA:baseViewModel{}

Categories