How to postback a checkbox in MVC - html

I have a controller:
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using MvcApplication5.Models;
namespace MvcApplication5.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var car = new Car { Sold = true };
return View(car);
}
[HttpPost]
public ActionResult Index(Car car)
{
return View(car);
}
public ActionResult About()
{
return View();
}
}
}
Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication5.Models
{
public class Car
{
public bool Sold { get; set; }
}
}
Here's the view:
#model MvcApplication5.Models.Car
#using (Html.BeginForm())
{
#Html.CheckBoxFor(m => m.Sold, new { #class = "sold" })
#Html.HiddenFor(m => m.Sold)
<div class="disable">Disable</div>
<button type="submit" value="Submit">Submit</button>
}
<script type="text/javascript">
$(function () {
$('.disable').click(function () {
$('.sold').attr("disabled", "disabled");
});
})
</script>
So what I'm trying to achieve here is you come in and the checkbox is checked. If you click submit the value is posted back fine. However if you click disable the checkbox becomes disabled. You then click submit. I was expecting because the checkbox is still checked and I've added a hidden field the value posted back for sold would be true. That's the whole point of why I added the hidden field.
Can anyone tell me why the checkbox when checked but disabled posts back false for Sold when there is a hidden field that should be maintaining the value?

disabled=disabled doesn't send values, what you want to preserve the value by to disable it from being updated is readonly=readonly. i.e.
$('.sold').attr("readonly", "readonly");
You'll also need to apply some CSS to make it look disabled.

Related

Routing to named element in Blazor (use anchor to navigate to specific element)

I cannot use an HTML anchor to navigate to a specific HTML element of a page in the Blazor Server. For example:
#page "/test"
<nav>
<!-- One version I've tried -->
Section2
<!-- Another version I've tried -->
<NavLink href="#section2">Section2</NavLink>
</nav>
#* ... *#
<h2 id="section2">It's Section2.</h2>
#* ... *#
When I click the link to Section2, I get redirected to the route http://localhost:5000/test#section2, however, will be at the top of the page. In my opinion, the browser should scroll down to the proper element, as specified by the Element Selector, but it can't.
Does it have to be done in a special way in Blazor?
I use Blazor 6 in .Net6 with Visual Studio 2022 (ver:17.0.2).
After loading a page, a browser automatically scrolls to the element identified by its id in the fragment part of the URL. It does the same when you click on an anchor with an href of the kind #element-id.
The page load behavior doesn't work for a Blazor Server because the element doesn't exist yet on page load.
The solution is to manually create a scroller using javascript and a razor component:
First of all, create a razor component like this
#inject IJSRuntime JSRuntime
#inject NavigationManager NavigationManager
#implements IDisposable
#code {
protected override void OnInitialized()
{
NavigationManager.LocationChanged += OnLocationChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await ScrollToFragment();
}
public void Dispose()
{
NavigationManager.LocationChanged -= OnLocationChanged;
}
private async void OnLocationChanged(object sender, LocationChangedEventArgs e)
{
await ScrollToFragment();
}
private async Task ScrollToFragment()
{
var uri = new Uri(NavigationManager.Uri, UriKind.Absolute);
var fragment = uri.Fragment;
if (fragment.StartsWith('#'))
{
// Handle text fragment (https://example.org/#test:~:text=foo)
// https://github.com/WICG/scroll-to-text-fragment/
var elementId = fragment.Substring(1);
var index = elementId.IndexOf(":~:", StringComparison.Ordinal);
if (index > 0)
{
elementId = elementId.Substring(0, index);
}
if (!string.IsNullOrEmpty(elementId))
{
await JSRuntime.InvokeVoidAsync("BlazorScrollToId", elementId);
}
}
}
}
Then add this javascript code somewhere before the Blazor script renders. You can wrap it with script tags and place it in the head.
function BlazorScrollToId(id) {
const element = document.getElementById(id);
if (element instanceof HTMLElement) {
element.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest"
});
}
}
Finally implement it in your pages if needed. You can also place it inside your layouts, so it will work for every page you create.
#page "/"
<PageTitle>Index</PageTitle>
<a href="#my-id">
<h1>Hello, world!</h1>
</a>
<SurveyPrompt Title="How is Blazor working for you?" />
<div style="height: 2000px">
</div>
<div id="my-id">
Hello!
</div>
<AnchorNavigation />
Source: link
You can also use an ElementReference and FocusAsync which uses the built in Blazor JS. To use it you need to use a small hack to make the component "Focusable" which is to set a tabindex. I've used a span but you can use what you like. I've used #alessandromanzini's code to get the element from the NavigationManager.
Here's a component:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Routing;
using System.Diagnostics.CodeAnalysis;
namespace SO75358165;
public class Bookmark : ComponentBase, IDisposable
{
private bool _setFocus;
[Inject] private NavigationManager NavManager { get; set; } = default!;
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public string? BookmarkName { get; set; }
[DisallowNull] public ElementReference? Element { get; private set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "span");
builder.AddAttribute(2, "tabindex", "-1");
builder.AddContent(3, this.ChildContent);
builder.AddElementReferenceCapture(4, this.SetReference);
builder.CloseElement();
}
protected override void OnInitialized()
=> NavManager.LocationChanged += this.OnLocationChanged;
protected override void OnParametersSet()
=> _setFocus = this.IsMe();
private void SetReference(ElementReference reference)
=> this.Element = reference;
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
if (this.IsMe())
{
_setFocus = true;
this.StateHasChanged();
}
}
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (_setFocus)
await this.Element!.Value.FocusAsync(false);
_setFocus = false;
}
private bool IsMe()
{
string? elementId = null;
var uri = new Uri(this.NavManager.Uri, UriKind.Absolute);
if (uri.Fragment.StartsWith('#'))
{
elementId = uri.Fragment.Substring(1);
return elementId == BookmarkName;
}
return false;
}
public void Dispose()
=> NavManager.LocationChanged -= this.OnLocationChanged;
}
Here's my test page:
#page "/"
<PageTitle>Index</PageTitle>
<NavLink href="#me">To me</NavLink>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
//.....
<h1>Hello, world!</h1>
<Bookmark BookmarkName="me" >
<h1 id="me">Focus on Me</h1>
</Bookmark>

Creating a plain non-component class using a razor file

Is it possible to create a plain non-component class using a razor file? The idea is to take advantage of the simplified syntax for generating RenderFragments, from a class that is not intended to be a component or a page. e.g.
#inherits object
#code {
// some other stuff
public RenderFragment Foo => #<span class="foo"></span>;
public RenderFragment Render(string? name) => __builder =>
{
// some other stuff
<text>#name</text>
};
}
where the resulting class should be equivalent to:
public class Whatever /*: object*/
{
public RenderFragment Foo => __builder =>
{
__builder.OpenElement(0, "span");
// etc
};
public RenderFragment Render(string? name) => __builder =>
{
__builder.AddContent(0, name); // or whatever
};
}
Trying exactly the above results in the generated code reporting an error:
Whatever.BuildRenderTree(RenderTreeBuilder): no suitable method to override
which is not entirely surprising, since the generated code includes an override for that method, which of course does not exist in a plain class.
I was hoping that by not including anything outside of #code the compiler would not attempt to override that method, but sadly that does not appear to be the case. Is there some other trick that can make this work?
Revised Answer
Yes you can. You can create a minimum requirement class that the Razor compiler will accept.
It doesn't need to implement IComponent as there is no intention of using it as a component.
It needs a virtual BuildRenderTree(RenderTreeBuilder builder) method for the Razor compiler to override: it's where it compiles all the markup in the main block.
public abstract class MinRazor
{
protected virtual void BuildRenderTree(RenderTreeBuilder builder) {}
}
You're Razor file then inherits from it.
RenderStuff.razor
#inherits MinRazor
#code {
public string Message { get; set; } = "No one set me!";
public RenderFragment GiveMeADiv => __builder =>
{
<div class="p-2 m-2 bg-light">
<div class="p-2 m-2 bg-primary text-white">
#Message
</div>
</div>
};
}
The only baggage that the compiled code has is a blank BuildRenderTree(RenderTreeBuilder builder).
Here's the output file the compiler produces:
public partial class RenderStuff : MinRazor
{
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
}
public string Message { get; set; } = "No one set me!";
public RenderFragment GiveMeADiv => __builder =>
{
__builder.OpenElement(0, "div");
__builder.AddAttribute(1, "class", "p-2 m-2 bg-light");
__builder.OpenElement(2, "div");
__builder.AddAttribute(3, "class", "p-2 m-2 bg-primary text-white");
__builder.AddContent(4, Message);
__builder.CloseElement();
__builder.CloseElement();
};
}
Your problem comes form #inherits object.
That does indeed make a "non-component class", but why?
Just leave that out and make your helpers static:
Whatever.razor
#inherits ComponentBase
#code {
// some other stuff
internal static RenderFragment Foo => #<span class="foo"></span>;
internal static RenderFragment Render(string? name) => __builder =>
{
// some other stuff
<text>#name</text>
};
}
And then you can use it like any static function:
#Whatever.Render("J Doe")
It appears that this is (currently at least) not possible; the closest working approach seems to be to implement it as a component anyway, but indicate that it is not intended to be used as such:
#code {
protected override void OnInitialized()
{
throw new NotSupportedException();
}
// some other stuff
public RenderFragment Foo => #<span class="foo"></span>;
public RenderFragment Render(string? name) => __builder =>
{
// some other stuff
<text>#name</text>
};
}
This unfortunately stills means that it will carry around some extra state baggage from inheriting ComponentBase, and it can't inherit any other (non-component) base class.
Making the render methods static and simply calling them from the "real" class as Henk Holterman suggests is probably the best compromise, although it would still need something like the OnInitialized override to discourage misuse.
Ideally you'd mark the static methods component as #internal too, to further reduce the scope of possible mis-use, but sadly that's not implemented yet.

How to add blazor component loaded to page from jquery and ajax call

I have a new ASP .NET Razor pages app that I've configured for Blazor.
In the program.cs I've added
builder.Services.AddServerSideBlazor();
...
app.MapBlazorHub();
In the Layout I've added:
<head>
<base href="~/" />
...
</head>
...
<body>
...
<script src="_framework/blazor.server.js"></script>
...
</body>
I added a Components folder with a Test.razor component:
#using System.Net.Http
#using Microsoft.AspNetCore.Authorization
#using Microsoft.AspNetCore.Components.Authorization
#using Microsoft.AspNetCore.Components.Forms
#using Microsoft.AspNetCore.Components.Routing
#using Microsoft.AspNetCore.Components.Web
#using Microsoft.AspNetCore.Components.Web.Virtualization
#using Microsoft.JSInterop
#namespace RazorPagesApp.Components
<p>Current count: #currentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
private int currentCount = 0;
[Parameter]
public int InitialValue { get; set; }
private void IncrementCount()
{
currentCount++;
}
protected override void OnParametersSet()
{
currentCount = InitialValue;
}
}
In my Pages folder, I added a Test.cshtml page:
#page
#{
ViewData["Title"] = "Test";
}
<h1>#ViewData["Title"]</h1>
<button onclick="getComponentPartial();">Add Component Via Jquery</button>
<div id="componentPlaceholder"></div>
And a ComponentPartial.cshtml page:
#page
#{
Layout = null;
}
<div id="PartialComponentDiv">
<p>ServerPrerendered</p>
<component type="typeof(Components.Test)" render-mode="ServerPrerendered" param-InitialValue="20" />
</div>
Finally, in my site.js I added my ajax call:
function getComponentPartial() {
try {
$.get({
url: 'ComponentPartial',
dataType: 'HTML',
success: function (data) {
$('#componentPlaceholder').html(data);
}
});
}
catch (e) {
console.log(e.message);
}
}
After running the project and navigating to https://localhost:7048/Test , I press the button to load the ComponentPartial page onto my test page. The Test.razor component renders but is not interactive, I believe because the blazer.server.js does not have this listed as a component. Is there a way to get this functional, i.e. have this component that was loaded via ajax and jquery be responsive via the blazor.server.js?

Apply aspect to all methods/actions in a class

I'm working on an ASP.NET MVC 5, I want to log all exceptions that occurs in the controller's actions.
To accomplish this I'm creating a custom aspect using PostSharp (in a dll), there I've already created the code to write the log files, now I want that the aspect can be controller-wide (do not want to apply it by hand to all methods).
The aspect's code looks like this:
using System;
using PostSharp.Aspects;
namespace Banlinea.Ceb.Domain.Aspects
{
public class LogException : OnExceptionAspect
{
public LogException()
{
ApplyToStateMachine = true;
}
public override void OnException(MethodExecutionArgs args)
{
//Code for logging the exception
args.FlowBehavior = FlowBehavior.ThrowException;
}
}
}
Now, what I want in my controller is to do something like this:
[LogException]
public class MyController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Other()
{
return View();
}
public ActionResult Another()
{
return View();
}
}
Just decorate the class, How can I do that?
you can do this byimplementing IAspectProvider
http://doc.postsharp.net/iaspectprovider
public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
{
Type type = (Type)targetElement;
return type.GetMethods().Select(
m => return new AspectInstance(targetElement, new LogException()) );
}
You can apply PostSharp aspects across your codebase by using a feature called attribute multicasting.
For example, when you apply a method-level aspect on a class level or assembly level, then it is automatically copied to all the methods in the corresponding class or assembly. You can additionally filter the target elements by setting the attribute properties, such as AttributeTargetTypes, AttributeTargetMemberAttributes etc.
The sample code from your question should actually work as you expect.

Linking to another .cshtml

I need to link to Login.cshtml page from Home.cshtml and it can't seem to find the link.
Home.cshtml
#{
Layout = "~/Shared/Master.cshtml";
ViewBag.Title = "Home";
}
#section menu
{
Login
}
HomeController.cs
public class HomeController : Controller
{
//
// GET: /Home/Home
public ActionResult Home()
{
return View();
}
//
// GET: /Home/Login
public ActionResult Login()
{
return View();
}
}
RouteConfig
routes.MapRoute(
name: "Default",
url: "",
defaults: new { controller = "Home", action = "Home" }
);
Project Structure:
Your Login Page would be at /Home/Login
if you are wanting a page for /Login you need to create a new controller called LoginController with an action result method of Index.
You need to have this to serve the pages in your RouteConfig:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/",
defaults: new { controller = "Home", action = "Home" }
);
This should also sort out your default home page