In Razor, there's a curious rule about only allowing closed HTML within an if block.
See:
Razor doesn't understand unclosed html tags
But I have a situation where I want to exclude some outer, wrapping elements under certain conditions. I don't want to repeat all the inner HTML, which is a fair amount of HTML and logic.
Is the only way around the problem to make yet another partial view for the inner stuff to keep it DRY?
Without any other re-use for this new partial, it feels really awkward, bloaty. I wonder if the rule is a limitation of Razor or simply a nannying (annoying) feature.
You can use Html.Raw(mystring). In myString you can write whatever you want, for example a tag opening or closing, without getting any errors at all. I.e.
if (condition) {
#Html.Raw("<div>")
}
if (condition) {
#Html.Raw("</div>")
}
NOTE: the #Html.Raw("<div>") can be written in a shorter, alternative form, like this: #:<div>
You can also create your own html helpers for simplifying the razor syntax. These helpers could receive the condition parameter, so that you can do something like this:
#Html.DivOpen(condition)
#Html.DivClose(condition)
or more complicated helpers that allow to specify attributes, tag name, and so on.
Nor the Raw, neither the html helpers will be detected as "tags" so you can use them freely.
The Best Way to do it
It would be much safer to implement something like the BeginForm html helper. You can look at the source code. It's easy to implement: you simply have to write the opening tag in the constructor, and the closing tag in the Dispose method. The adavantage of this technique is that you will not forget to close a conditionally opened tag.
You need to implement this html helper (an extension method declared in a static class):
public static ConditionalDiv BeginConditionalDiv(this HtmlHelper html,
bool condition)
{
ConditionalDiv cd = new ConditionalDiv(html, condition);
if (condition) { cd.WriteStart(); }
return cd; // The disposing will conditionally call the WriteEnd()
}
Which uses a class like this:
public class ConditionalDiv : IDisposable
{
private HtmlHelper Html;
private bool _disposed;
private TagBuilder Div;
private bool Condition;
public ConditionalDiv(HtmlHelper html, bool condition)
{
Html = html;
Condition = condition;
Div = new TagBuilder("div");
}
public void Dispose()
{
Dispose(true /* disposing */);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
if (Condition) { WriteEnd(); }
}
}
public void WriteStart()
{
Html.ViewContext.Writer.Write(Div.ToString(TagRenderMode.StartTag));
}
private void WriteEnd()
{
Html.ViewContext.Writer.Write(Div.ToString(TagRenderMode.EndTag));
}
}
You can use this with the same pattern as BeginForm. (Disclaimer: this code is not fully tested, but gives an idea of how it works. You can accept extra parameters for attributes, tag names and so on).
Declare razor helper using #helper HeplerName(), call by #HeplerName() anywhere. And you can add params if you want.
#if (condition)
{
<div class="wrapper">
#MyContent()
</div>
}
else
{
#MyContent()
}
#helper MyContent()
{
<img src="/img1.jpg" />
}
Edit (thanks to #Kolazomai for bringing up the point in comments):
ASP.NET Core 3.0 no longer supports #helper but supports HTML markup in method body. New case will look like this:
#if (condition)
{
<div class="wrapper">
#{ MyContent(); }
</div>
}
else
{
#{ MyContent(); }
}
#{
void MyContent()
{
<img src="/img1.jpg" />
}
}
The following code is based on the answer of #JotaBe, but simplified and extendable:
public static class HtmlHelperExtensions
{
public static IDisposable BeginTag(this HtmlHelper htmlHelper, string tagName, bool condition = true, object htmlAttributes = null)
{
return condition ? new DisposableTagBuilder(tagName, htmlHelper.ViewContext, htmlAttributes) : null;
}
}
public class DisposableTagBuilder : TagBuilder, IDisposable
{
protected readonly ViewContext viewContext;
private bool disposed;
public DisposableTagBuilder(string tagName, ViewContext viewContext, object htmlAttributes = null) : base(tagName)
{
this.viewContext = viewContext;
if (htmlAttributes != null)
{
this.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
this.Begin();
}
protected virtual void Begin()
{
this.viewContext.Writer.Write(this.ToString(TagRenderMode.StartTag));
}
protected virtual void End()
{
this.viewContext.Writer.Write(this.ToString(TagRenderMode.EndTag));
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
this.disposed = true;
this.End();
}
}
}
This can be used the following way within Razor views:
#using (Html.BeginTag("div"))
{
<p>This paragraph is rendered within a div</p>
}
#using (Html.BeginTag("div", false))
{
<p>This paragraph is rendered without the div</p>
}
#using (Html.BeginTag("a", htmlAttributes: new { href = "#", #class = "button" }))
{
<span>And a lot more is possible!</span>
}
This is IMO the most convenient way to achieve this:
[HtmlTargetElement(Attributes = RenderAttributeName)]
public class RenderTagHelper : TagHelper
{
private const string RenderAttributeName = "render";
private const string IncludeContentAttributeName = "include-content";
[HtmlAttributeName(RenderAttributeName)]
public bool Render { get; set; }
[HtmlAttributeName(IncludeContentAttributeName)]
public bool IncludeContent { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (!Render)
{
if (IncludeContent)
output.SuppressOutput();
else
output.TagName = null;
}
output.Attributes.RemoveAll(RenderAttributeName);
output.Attributes.RemoveAll(IncludeContentAttributeName);
}
}
Then, you just use it like this:
<div render="[bool-value]" include-content="[bool-value]">
...
</div>
Related
I have a Blazor server app. Some variables on a specific razor page (main.razor) are defined as static because I want that these variables keep their values when the client navigates to other pages in the same project and comes back again to main.razor. So far it is working good.
But when I refresh the complete page, or even close the tab and reopen my app (login again), I see that the static variables still keep their values. How can prevent this? Of course I want that the values return to their default values (like 0 or ""), when the client makes a login or refreshes the page with F5. How can I do that?
I have defined the related variables in the following way:
private static StringBuilder log = new StringBuilder();
public static string testvar1= "";
public static int testvar2= 0;
Statics exist for the lifetime of the application instance which explains the behaviour you see.
You need to be maintaining state. At one end of the spectrum you can implement a State Management system such as Fluxor. At the other just create a user class, set it up as a service and inject it as a Scoped Service. Or you can build a middle-of-the-road solution.
This is mine.
A generic UIStateService that maintains a Dictionary of (state)objects against a Guid.
public class UIStateService
{
private Dictionary<Guid, object> _stateItems = new Dictionary<Guid, object>();
public void AddStateData(Guid Id, object value)
{
if (_stateItems.ContainsKey(Id))
_stateItems[Id] = value;
else
_stateItems.Add(Id, value);
}
public void ClearStateData(Guid Id)
{
if (_stateItems.ContainsKey(Id))
_stateItems.Remove(Id);
}
public bool TryGetStateData<T>(Guid Id, out T? value)
{
value = default;
if (Id == Guid.Empty)
return false;
var isdata = _stateItems.ContainsKey(Id);
var val = isdata
? _stateItems[Id]
: default;
if (val is T)
{
value = (T)val;
return true;
}
return false;
}
}
Set it up as a service:
builder.Services.AddScoped<UIStateService>();
Next define a simple template ComponentBase page that contains the common page code:
using Blazr.UI;
using Microsoft.AspNetCore.Components;
namespace BlazorApp2.Pages
{
public class StatePage : ComponentBase
{
// this provides a guid for this specific page during the lifetime of the application runtime
// we use this as the reference to store the state data against
private static Guid RouteId = Guid.NewGuid();
[Inject] protected UIStateService UIStateService { get; set; } = default!;
protected void SaveState<T>(T state) where T : class, new()
{
if (RouteId != Guid.Empty)
this.UIStateService.AddStateData(RouteId, state);
}
protected bool GetState<T>( out T value) where T : class, new()
{
value = new T();
if (RouteId != Guid.Empty && this.UIStateService.TryGetStateData<T>(RouteId, out T? returnedState))
{
value = returnedState ?? new T();
return true;
}
else
return false;
}
}
}
And use it in a page:
#page "/"
#inherits StatePage
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<div class="p-2">
<button class="btn btn-primary" #onclick=SetData>Set Data</button>
</div>
<div class="p-3 text-primary">
State Time : #stateData.StateTime;
</div>
#code {
private MyStateData stateData = new MyStateData();
protected override void OnInitialized()
{
if (this.GetState<MyStateData>(out MyStateData value))
this.stateData = value;
else
this.SaveState<MyStateData>(this.stateData);
}
private void SetData()
{
this.stateData.StateTime = DateTime.Now.ToLongTimeString();
SaveState<MyStateData>(this.stateData);
}
public class MyStateData
{
public string StateTime { get; set; } = DateTime.Now.ToLongTimeString();
}
}
You can now navigate around the application and the state will be maintained for the page.
You can apply an observer/notification pattern to the state object to trigger automatic state updates if you wish.
MVC6 introduces Tag Helpers which is a better way compared to using #Html.EditorFor, etc. However I have not found any Tag Helper that would be an alternative to #Html.DisplayFor.
Of course I can use a variable directly on a Razor page, such as #Model.BookingCode. But this does not allow to control formatting.
With MVC6, what's conceptually correct way for displaying a value of a model property?
#Html.DisplayFor still exists and can still be used.
The difference between HtmlHelpers and TagHelpers is that HtmlHelpers choose which html elements to render for you whereas TagHelpers work with html tags that you add yourself so you have more full control over what html element is used. You do have some control over the markup using templates with HtmlHelpers but you have more control with TagHelpers.
So you should think in terms of what html markup do I want to wrap this model property in and add that markup around the property itself using #Model.Property with some markup around it or continue using DisplayFor if you prefer to let the helper decide.
You can create your own tag helper
namespace MyDemo.TagHelpers
{
[HtmlTargetElement("p", Attributes = ForAttributeName)]
public class DisplayForTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
var text = For.ModelExplorer.GetSimpleDisplayText();
output.Content.SetContent(text);
}
}
}
Add use it in view:
<p asp-for="MyProperty" class="form-control-static"></p>
I have been using this as a display tag helper.
[HtmlTargetElement("*", Attributes = ForAttributeName)]
public class DisplayForTagHelper : TagHelper
{
private const string ForAttributeName = "asp-display-for";
private readonly IHtmlHelper _html;
public DisplayForTagHelper(IHtmlHelper html)
{
_html = html;
}
[HtmlAttributeName(ForAttributeName)]
public ModelExpression Expression { get; set; }
public IHtmlHelper Html
{
get
{
(_html as IViewContextAware)?.Contextualize(ViewContext);
return _html;
}
}
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (output == null)
throw new ArgumentNullException(nameof(output));
var type = Expression.Metadata.UnderlyingOrModelType;
if (type.IsPrimitive)
{
output.Content.SetContent(Expression.ModelExplorer.GetSimpleDisplayText());
}
// Special Case for Personal Use
else if (typeof(Dictionary<string, string>).IsAssignableFrom(type))
{
output.Content.SetHtmlContent(Html?.Partial("Dictionary", Expression.ModelExplorer.Model));
}
else
{
var htmlContent = Html.GetHtmlContent(Expression);
output.Content.SetHtmlContent(htmlContent);
}
}
}
public static class ModelExpressionExtensions
{
public static IHtmlContent GetHtmlContent(this IHtmlHelper html, ModelExpression expression)
{
var ViewEngine = html.ViewContext.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
var BufferScope = html.GetFieldValue<IViewBufferScope>();
var htmlContent = new TemplateBuilder(ViewEngine, BufferScope, html.ViewContext, html.ViewContext.ViewData, expression.ModelExplorer, expression.Name, null, true, null).Build();
return htmlContent;
}
public static TValue GetFieldValue<TValue>(this object instance)
{
var type = instance.GetType();
var field = type.GetFields(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).FirstOrDefault(e => typeof(TValue).IsAssignableFrom(e.FieldType));
return (TValue)field?.GetValue(instance);
}
}
try below code
public class movie
{
public int ID { get; set; }
[DisplayName("Movie Title")]
public string Title { get; set; }
}
///////////////////////////////////////////////////
#model IEnumerable<MvcMovie.Models.Movie>
<h1>Show List Movies</h1>
<label asp-for="ToList()[0].Title">< /label>
#foreach (var movie in Model)
{
#movie.Title
}
Since I updated my ASP.NET5 project to beta4 (the one included with Visual Studio 2015 RC), any of my Razor views where I use a helper, such as:
#helper foo()
{
<h2>Bar</h2>
}
results in the following error:
error CS0103: The name 'helper' does not exist in the current context.
Is the #helper directive no longer supported? Can someone point me to anything useful about the issue?
The #helper directive was removed since beta 4 because it imposed too many restrictions on other Razor features: https://github.com/aspnet/Razor/issues/281.
Edit
To be clear: based on the discussion in the GitHub issue(s) Microsoft is not planning to bring the #helper directive back or replace it in ASP.NET Core.
Rather than using a helper method, you can achieve the same functionality using a partial with a view model. Just pass the relevant arguments into the Html.Partial command.
In .Net Core and above you can use the function derivative to achieve the same purpose:
#functions {
private void RenderFoo()
{
return "<h2>Bar</h2>";
}
}
<div>From method: #{ RenderFoo(); }</div>
Templated Razor Delegates will be a good alternative.
Declare foo
#{
Func<dynamic, object> foo =
#<h2>Bar</h2>
;
}
Render foo
#foo(null)
As an advanced example, this can be turned into localization helper.
For example case, localization of text having HTML tags like this.
Please Logout once
Rewrite like the following code:
page.cshtml
#inject SubstitutableHtmlUsecase SubstitutableHtmlUsecase
#inject IStringLocalizer<Msg> Msg
#SubstitutableHtmlUsecase.Render1(
Msg["Please {logout} once"],
"{logout}", ##Msg["Logout"])
SubstitutableHtmlUsecase.cs
using Microsoft.AspNetCore.Html;
using System.Text.Encodings.Web;
namespace App.Usecases
{
public class SubstitutableHtmlUsecase
{
public IHtmlContent Render1(string body, string marker1, Func<object?, IHtmlContent> razor1)
{
var parts = body.Split(marker1);
return new Renderer(
(writer, encoder) =>
{
for (int idx = 0; idx < parts.Length; idx++)
{
bool isLast = (idx + 1 == parts.Length);
encoder.Encode(writer, parts[idx]);
if (!isLast)
{
razor1(null).WriteTo(writer, encoder);
}
}
}
);
}
private class Renderer : IHtmlContent
{
private readonly Action<TextWriter, HtmlEncoder> _writer;
public Renderer(Action<TextWriter, HtmlEncoder> writer)
{
_writer = writer;
}
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
_writer(writer, encoder);
}
}
}
}
I'm in a situation, where I need to do something similar to the following:
public static class mystaticclass
{
public static string filename { get; private set; }
static mystaticclass()
{
filename = "C:\\test.test";
}
}
public class myclass
{
public string filename;
public myclass(string filename)
{
this.filename = filename;
}
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var container = new WindsorContainer().Install(Configuration.FromXmlFile("Windsor.config"));
container.Register(Component.For<myclass>()
.DynamicParameters((k, d) =>
{
d["filename"] = mystaticclass.filename;
}));
var tmp=container.Resolve<myclass>();
}
}
however I'd very much like to configure this in the .config file, rather than in code.... is it possible?? ... probably not... so what would be a good alternative solution
N.B. the 'mystaticclass' is not something I'm able to change, however I'd like to be able to use mystaticclass2.filename in some configurations....
TIA
Sørn
Nope, not possible with XML, mostly because dynamic parameters are... well - dynamnic and that's something that can't be expressed in XML. Your case looks pretty static, so guess if you really think that's what you want you might quite easily implement support for that via IContributeComponentModelConstruction
How come the "Table" classes Generated in the Dbml do not contain useful events like
OnBeforeInsert
OnBeforeUpdate
OnAfterInsert
etc.
Am I missing something?
This question is related to frustration trying to set timestamp columns.
UPDATE
I created the following method of doing this neatly what does everyone think?
public class Model
{
internal virtual void OnBeforeInsert()
{
}
internal virtual void OnBeforeUpdate()
{
}
}
public partial class DbDataContext
{
public override void SubmitChanges(System.Data.Linq.ConflictMode failureMode)
{
foreach (var insert in this.GetChangeSet().Inserts)
{
if (insert is Model)
{
((Model)insert).OnBeforeInsert();
}
}
foreach (var update in this.GetChangeSet().Updates)
{
if (update is Model)
{
((Model)update).OnBeforeUpdate();
}
}
base.SubmitChanges(failureMode);
}
}
public partial class Address : Model
{
internal override void OnBeforeInsert()
{
var created = DateTime.Now;
this._Modified = created;
this._Created = created;
}
}
I had a similar issue like this recently.
There is a partial method in the generated class for "OnValidate". Simply declaring the method in your partial will force it to be called (vb.net does not support partial methods like c#) or in c# simply declare a partial method.
The method is passed a System.Data.Linq.ChangeAction enum that is either: Delete, Insert, Update, or None.
Below is a sample of what you did using the built in partial method.
public partial class Address
{
private partial void OnValidate(System.Data.Linq.ChangeAction action)
{
if (action == System.Data.Linq.ChangeAction.Insert)
{
var created = DateTime.Now;
this._Modified = created;
this._Created = created;
} else if (action == System.Data.Linq.ChangeAction.Update) {
this._Modified = DateTime.Now;
}
}
}