I'm building a CRUD application in Blazor WebAssembly, and on the Update View I have:
UpdateForecast.razor
#page "/forecast/update/{Id}"
#using System.Text.Json
#using System.Text.Json.Serialization
#using WeatherBA.Shared.Dtos
#using WeatherBA.Shared.Responses
#inject HttpClient Http
#inject NavigationManager NavManager
<PageTitle>Update forecast</PageTitle>
<h1>Update forecast</h1>
#if (forecast != null)
{
<EditForm Model="#forecast" OnValidSubmit="#SaveForecast">
<table>
<tr><label>Id:</label></tr>
<tr><input type="text" #bind-value="forecast.Id"/></tr>
<tr><label>Date:</label></tr>
<tr><input type="text" #bind-value="forecast.Date"/></tr>
<tr><label>Temperature:</label></tr>
<tr><input type="text" #bind-value="forecast.TemperatureC"/></tr>
<tr><label>Summary:</label></tr>
<tr><input type="text" #bind-value="forecast.Summary"/></tr>
<input type="submit" value="Save"/>
</table>
</EditForm>
} else
{
<p>Loadnig...</p>
}
#code {
[Parameter]
public string Id { get; set; }
private ForecastDto? forecast = new ForecastDto();
private CreateForecastCommandResponse? response = null;
protected override async Task OnInitializedAsync()
{
forecast = await Http.GetFromJsonAsync<ForecastDto> ("api/Forecast" + Id);
//not filling form but but api return correct object
}
protected async Task SaveForecast()
{
//response = new CreateForecastCommandResponse();
HttpResponseMessage frame = await Http.PutAsJsonAsync("api/Forecast/" + Id, forecast);
response = await frame.Content.ReadFromJsonAsync<CreateForecastCommandResponse>();
if(response.Success == true)
NavManager.NavigateTo("/forecast/" + Id);
}
}
OnInitializedAsync() has to fetch the data and populate input fields with values of given object.
The problem is it doesn't. Any sugestions why can it be?
Related
I am new to Blazor WebAssembly. I have a simple page that allows users to register a company. It takes the company name, description and the username. At the moment the username is entered by the user but I want to automatically grab the username of the logged in user and post it without the need of user input. Here is my page:
#page "/companies/create"
#attribute [Authorize]
#inject HttpClient Http
#inject NavigationManager Navigation
<h3>Register your company</h3>
<AuthorizeView>
Hello, #context.User.Identity.Name!
</AuthorizeView>
<EditForm Model="Companies" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator />
<div class="form-group">
<label class="control-label">Name</label>
<InputText #bind-Value="Companies.Name" class="form-control" />
<ValidationMessage For="#(() => Companies.Name)" />
</div>
<div class="form-group">
<label class="control-label">Description</label>
<InputText #bind-Value="Companies.Description" class="form-control" />
<ValidationMessage For="#(() => Companies.Description)" />
</div>
<div class="form-group">
<label class="control-label">Username</label>
<InputText #bind-Value="Companies.Username" class="form-control"/>
<ValidationMessage For="#(() => Companies.Username)" />
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Register
</button>
</EditForm>
#code {
private Sprelo.Shared.Companies Companies { get; set; } = new Sprelo.Shared.Companies();
private async void HandleValidSubmit()
{
try
{
var response = await Http.PostAsJsonAsync($"/api/companies", Companies);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var companies = JsonConvert.DeserializeObject<Sprelo.Shared.Companies>(content);
Navigation.NavigateTo($"Companies/edit/{companies.Id}");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
catch (Exception e)
{
}
}
}
This is my model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Sprelo.Shared
{
public class Companies
{
[Key]
public Guid Id { get; set; }
[Required]
public String Name { get; set; }
public String Description { get; set; }
public String Username { get; set; }
}
}
and this is an auto generated API controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Sprelo.Server.Data;
using Sprelo.Shared;
namespace Sprelo.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly ApplicationDbContext _context;
public CompaniesController(ApplicationDbContext context)
{
_context = context;
}
// GET: api/Companies
[HttpGet]
public async Task<ActionResult<IEnumerable<Companies>>> GetCompanies()
{
return await _context.Companies.ToListAsync();
}
// GET: api/Companies/5
[HttpGet("{id}")]
public async Task<ActionResult<Companies>> GetCompanies(Guid id)
{
var companies = await _context.Companies.FindAsync(id);
if (companies == null)
{
return NotFound();
}
return companies;
}
// PUT: api/Companies/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
public async Task<IActionResult> PutCompanies(Guid id, Companies companies)
{
if (id != companies.Id)
{
return BadRequest();
}
_context.Entry(companies).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CompaniesExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Companies
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
public async Task<ActionResult<Companies>> PostCompanies(Companies companies)
{
_context.Companies.Add(companies);
await _context.SaveChangesAsync();
return CreatedAtAction("GetCompanies", new { id = companies.Id }, companies);
}
// DELETE: api/Companies/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCompanies(Guid id)
{
var companies = await _context.Companies.FindAsync(id);
if (companies == null)
{
return NotFound();
}
_context.Companies.Remove(companies);
await _context.SaveChangesAsync();
return NoContent();
}
private bool CompaniesExists(Guid id)
{
return _context.Companies.Any(e => e.Id == id);
}
}
}
I have been struggling with this for quite a while, any help would be appriciated!
Inject the AuthenticationStateProvider object into your component like this:
#inject AuthenticationStateProvider authProvider
Override the OnInitialized life cycle method:
protected override void OnInitialized ()
{
var authenticationStateTask = await
authProvider.GetAuthenticationStateAsync();
var user = authenticationStateTask.User;
// Check if the user is authenticated
if (user.Identity.IsAuthenticated)
{
// Grab the user name and assign it to Companies.Username
Companies.Username = user.Identity.Name
}
}
In a blazor project I used Editform and Fluentvalidation as well as Toolbelt.Blazor.HotKeys for a shortcut (ctrl+s) to submit the form
When I press ctrl+s, the Submit() method is called, but if the form has an error, it does not show the errors. In fact, only the method is called, not the submit form.
What solution do you suggest for this problem?
<EditForm Model="#model" OnValidSubmit="Submit">
<FluentValidationValidator />
...
<button type="submit" >save</button>
</EditForm>
#code
{
[Parameter] public CategoryInfo model { get; set; } = new();
private async Task Submit()
{
var validator = new CategoryValidator();
var result = validator.Validate(model);
if (result.IsValid)
{
...
}
}
}
Here's a working single page component that demos the code needed to implement a form submit on <CTL>S. I've used the DataAnnotationsValidator for simplicity. There are inline comments to explain the methods.
#page "/"
#implements IDisposable
#using Toolbelt.Blazor.HotKeys
#using System.ComponentModel.DataAnnotations;
<h3>EditForm</h3>
<EditForm EditContext="this._editContext" OnValidSubmit="ValidSubmitForm" OnInvalidSubmit="InvalidSubmitForm">
<DataAnnotationsValidator />
<div class="p-2">
<span>Value (100-200):</span>
<InputNumber #bind-Value="_model.Value" />
<ValidationMessage For="() => _model.Value"/>
</div>
<div class="m-2 p-2">
<button class="btn btn-success" type="submit">Submit</button>
</div>
</EditForm>
<div class="m-2 p-2">
<span>#message</span>
</div>
<div class="m-2 p-2">
<button class="btn btn-danger" type="button" #onclick="SubmitFormExternally">Submit Form Externally</button>
</div>
#code {
private string message;
private Model _model = new Model();
[Inject] private HotKeys hotKeys { get; set; }
private HotKeysContext _hotKeysContext;
EditContext _editContext;
// Explicitly setup the Edit context so we have a reference to it
protected override void OnInitialized()
{
_editContext = new EditContext(_model);
_hotKeysContext = this.hotKeys.CreateContext()
.Add(ModKeys.Ctrl, Keys.S, SubmitFormCtlS, "Submit form");
}
// Invalid handler
private Task ValidSubmitForm()
{
message = $"Valid Form Submitted at :{DateTime.Now.ToLongTimeString()}";
return Task.CompletedTask;
}
// Valid Handler
private Task InvalidSubmitForm()
{
message = $" Invalid Form Submitted at :{DateTime.Now.ToLongTimeString()}";
return Task.CompletedTask;
}
// Method to call from external button
// Works and component updates as it's a Blazor Component event
// emulates the private HandleSubmitAsync method in EditForm
private async Task SubmitFormExternally()
{
if (_editContext.Validate())
await this.ValidSubmitForm();
else
await this.InvalidSubmitForm();
}
// Method to call from shortcut key
// The shortcut key mechanism does't wrap the call in a Blazor Component event
// So we wrap the code within one
// The code emulates the private HandleSubmitAsync method in EditForm
private async Task SubmitFormCtlS()
{
var task = Task.CompletedTask;
if (_editContext.Validate())
task = this.ValidSubmitForm();
else
task = this.InvalidSubmitForm();
this.StateHasChanged();
if (!task.IsCompleted || task.IsCanceled)
{
await task;
this.StateHasChanged();
}
}
public void Dispose()
{
_hotKeysContext.Dispose();
}
// Quick Model Class
public class Model
{
[Range(100, 200, ErrorMessage = "Must be between 100 and 200")]
public int Value { get; set; } = 0;
}
}
I have POST query with Form data with files and I need to send the same data to another POST request.
I'm going to use for this HttpClient class.
Is there a way to copy all Request.Form data and insert them to new request? Or I need to add every param?
I mean something like this:
var httpClient = new HttpClient();
var httpResponseMessage = await httpClient.PostAsync("some_url", Request.Form);
Is there a way to copy all Request.Form data and insert them to new request? Or I need to add every param?
You need to add every param like below:
Model in ProjectA:
public class FormData
{
public int Id { get; set; }
public IFormFile File { get; set; }
public string Name { get; set; }
}
View in ProjectA:
#model FormData
<form asp-action="Post" enctype="multipart/form-data">
<div>
Id:<input asp-for="Id"/>
</div>
<div>
Name:<input asp-for="Name"/>
</div>
<div>
FIle:<input asp-for="File" />
</div>
<div>
<input type="submit" value="create" />
</div>
</form>
Controller in ProjectA:
[HttpPost]
public async Task<IActionResult> Post(FormData formData)
{
HttpClient client = new HttpClient();
// var formData = HttpContext.Request.Form;
client.BaseAddress = new Uri("http://localhost:63331");//your applicationUrl
client.DefaultRequestHeaders.Accept.Clear();
var multiContent = new MultipartFormDataContent();
var file = formData.File;
if (file != null)
{
var fileStreamContent = new StreamContent(file.OpenReadStream());
multiContent.Add(fileStreamContent, "File", file.FileName);
}
multiContent.Add(new StringContent(formData.Id.ToString()), "id");
multiContent.Add(new StringContent(formData.Name.ToString()), "name");
var response = await client.PostAsync("/api/values", multiContent);
//do your stuff...
return Ok();
}
Model in ProjectB:
public class FormDataModel
{
public int Id { get; set; }
public IFormFile File { get; set; }
public string Name { get; set; }
}
Controller in ProjectB:
[HttpPost]
public void Post([FromForm]FormDataModel model)
{
//...
}
Result:
If I want to update a model/DB using SignalR, how is this achieved? (The other way around, i.e. from Server to Client is explained in many tutorials, but this way?)
So say we have a simple Model
public class User
{
public int UserID { get; set; }
public string UserName { get; set; }
}
And the corresponding View has an input field for the name. The hub is something like
public class UserHub : Hub
{
public void UpdateName(string value)
{
// now what?
Clients.All.updateTheViewIfNecessary(string newValue);
}
}
Edit
How do I update the model, i.e. how do I achieve the same result as in the regular CRUD edit controller
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
For the purpose of this example we will use a JS client to send a model to the Hub and references official documentation at
ASP.NET SignalR Hubs API Guide - JavaScript Client: How to call server methods from the client
Assuming a User Hub on the server
public interface IUserService {
bool UpdateName(string userName);
// Other methods not shown.
}
public class UserHub : Hub {
private readonly IUserService service;
public UserHob(IUserService service) {
this.service = service;
}
public void UpdateName(string value) {
if(value != null) {
var newValue = value;
if(service.UpdateName(newValue) == true) {
Clients.All.updateTheViewIfNecessary(newValue);
}
}
}
}
Reference documentation at Dependency Injection in SignalR to understand how to inject dependencies into the hub. In the above UserHub when the message is received, it uses the data in the model to update/persist data via the injected dependency and notifies clients based on the result of that operation. This could allow for long running processes that could later update clients as needed.
A JavaScript client code that invokes the server method (assuming with the generated proxy) would look something like this.
//This assumes View has an input field for the name
var message = "Test Name";
userHubProxy.server.updateName(message).done(function () {
console.log ('Invocation of UpdateName succeeded');
}).fail(function (error) {
console.log('Invocation of UpdateName failed. Error: ' + error);
});
The framework will handle whatever model binding needs to be done on the server.
The hub in effect acts as a service endpoint and sends the response of calling the service to all clients connected to it.
Call the server function to pass the values like shown in the javascript below.
I have also include the sample html table containing list of users to call server function against each button click
<table id="tblData">
<tr>
<td><input type="button" value="User 1" /> </td>
<td><button type="button" value="2" title="User 2">User 2</button></td>
<td><input type="button" value="User 3" /></td>
<td><input type="button" value="User 4" /></td>
</tr>
</table>
var userId, userName, strOtherVal;
userId = 1; // sample values
userName = 'ali';
strOtherVal = 'admin';
$(function () {
$("#tblData tr td button,input").click(function (event) {
myHub.server.sendData(userId, userName, strOtherVal)
.fail(function (error) {
console.log('error sending data: ' + error)
});
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
On the server side your function will look like this
public void sendData(int userId, string userName, string strOtherVal)
{
//create model
var UserModel = new User() { UserID = userId, UserName = userName, OtherVal = strOtherVal };
//here call your function to check or post this model to db
}
public class User
{
public int UserID { get; set; }
public string UserName { get; set; }
public string OtherVal { get; set; }
}
I developed a custom HtmlHelper extension method but that data is not
posting Action.
HtmlHelper extension class:
public static class TestHtmlHelper
{
public static MvcHtmlString CreateControl(this HtmlHelper helper, string tagName, IDictionary<string, string> attributes)
{
var newTag = new TagBuilder(tagName);
newTag.MergeAttributes(attributes, true);
return MvcHtmlString.Create(newTag.ToString(TagRenderMode.Normal));
}
public static string Image(this HtmlHelper helper, string id, string url, string alternateText, object htmlAttributes)
{
// Create tag builder
var builder = new TagBuilder("img");
// Create valid id
builder.GenerateId(id);
// Add attributes
builder.MergeAttribute("src", url);
builder.MergeAttribute("alt", alternateText);
builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
// Render tag
return builder.ToString(TagRenderMode.Normal);
}
}
//View code
#using (Html.BeginForm("Go","Home",FormMethod.Post))
{
IDictionary<string, string> d = new Dictionary<string, string>();
d.Add("type", "text");
d.Add("id", "text1");
d.Add("required", "required");
#Html.Raw(Html.CreateControl("input", d))
#Html.Raw(Html.Image("image1", "/Images/bullet.png", "bullet", new { border = "4px" }))
d = null;
d = new Dictionary<string, string>();
d.Add("type", "submit");
d.Add("value", "Go");
#Html.Raw(Html.CreateControl("input", d))
<span></span>
d = null;
d = new Dictionary<string, string>();
d.Add("value", "text");
d.Add("id", "span1");
d.Add("text", "required");
#Html.Raw(Html.CreateControl("span", d))
}
// Controller code
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
[HttpPost]
public ActionResult Go(string test)
{
return Content(test);
}
I didn't get data in string test. I want to submit that data to DB.
To get input values as parameters for an MVC action, you need to include NAME for the input types.
I do not see NAME for any input types in your code.
Also I do not see TEST in your code
For example, if your form is -
#using (Html.BeginForm("Submit","Ajax",FormMethod.Post))
{
<input type="text" name="Rami"/>
<input type="submit" value="Go"/>
}
Output ScreenShot -
Put your inputs inside a form tag. All the input data will be sent to the controller on form submit. Please see the example:
View:
#using (Html.BeginForm("Search", "Events"))
{
#Html.TextBox("name")
<input type="submit" value="Search" />
}
Controller:
public class EventsController: Controller
{
public ActionResult Search(string name)
{
//some operations goes here
return View(); //return some view to the user
}
}
If you need to work with more complex types just lern how to use models in ASP.NET MVC. Here is short example:
Razor:
#model UserModel
#using (Html.BeginForm("Search", "Events"))
{
#Html.TextBoxFor(m => m.FirstName)
#Html.TextBoxFor(m => m.LastName)
<input type="submit" value="Search" />
}
Controller:
public class EventsController: Controller
{
public ActionResult Search(UserModel model)
{
//some operations goes here
return View(); //return some view to the user
}
}
Model (C#):
public class UserModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}