How to Insert Input into sql table code first ef - sql

I'm trying to Insert my product properties into SQL table, I have a razor page that gets input data that needs to be inserted but i don't know how to insert them
This is my product model:
namespace Market.ViewModels
{
public class ProductListView
{
public Guid ID { get; set; }
public string ProductName { get; set; }
public decimal ProductPrice { get; set; }
}
}
This is my razor page :

I made a simple example, using EF Core code first to create a database and then query the data, the process of binding the value to the page is as follows.
1.The first dependency packages used are as follows:
Model:
public class ProductListView
{
[Key]
public Guid ID { get; set; }
public string ProductName { get; set; }
public double ProductPrice { get; set; }
}
I modified your ProductPrice type, because there will be problems with this type during migration. If you must change the type, refer to this article:
http://jameschambers.com/2019/06/No-Type-Was-Specified-for-the-Decimal-Column/
Create Model and Context Classes:
Now you can add the database context
: name the class TestDbContext and click Add and change the code in TestDbContext.cs as follows:
public class TestDbContext:DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
{
}
public DbSet<ProductListView> productListViews { get; set; }
}
Connection string you need to write inside the appsetting.json file as follows:
"ConnectionStrings": {
"DefaultDatabase": "Your DB"
}
In ASP.NET Core, services such as the DB context must be registered with the dependency injection container. The container provides the service to controllers.
Update Startup.cs with the following highlighted code:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<TestDbContext>(item => item.UseSqlServer(Configuration.GetConnectionString("DefaultDatabase")));
}
In order to create the migration code, we use the "add-migration MigrationName" command. After the add migration command is successfully executed, it will create a folder named "Migration" in the project. We only created the migration responsible for creating the database and its tables. script. But we have not yet created the actual database and tables. So let's execute the migration script and generate the database and tables. Therefore, to execute the migration script we must execute the'update-database' command.
Next, let us create a context class, define the database connection and register the context. Then perform the query work in the controller, and then return the data.
public class TestController : Controller
{
private readonly TestDbContext _context;
public TestController(TestDbContext context)
{
_context = context;
}
public IActionResult Test(ProductListView product)
{
var value = _context.productListViews.SingleOrDefault(item => item.ProductPrice == 12.1);
product.ProductName = value.ProductName;
product.ProductPrice = value.ProductPrice;
return View(product);
}
}
View:
#model WebApplication132.Models.ProductListView
<h1>AddProducts</h1>
<div class="row">
<div class="col-md-12">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="ProductName"></label>
<input asp-for="ProductName" class="form-control" />
</div>
<div class="form-group">
<label asp-for="ProductPrice"></label>
<input asp-for="ProductPrice" class="form-control" />
</div>
<button type="Add Product" class="btn-primary">Add Product</button>
</form>
</div>
</div>
Db data:
Result:

Related

SaveChangesAsync fails with assigned users to object AspNetUsers duplicate key violation

I am trying to create a Project item where a project can have multiple users. You assign the user by typing their name and pressing the button; the controller will find the user and return the user if they exist. They are then added to the List which is then assigned to the project on project creation submit. When the POST is engaged for the project the SaveChangesAsync() produces a http response 500 with the error:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_AspNetUsers'. Cannot insert duplicate key in object 'dbo.AspNetUsers'. The duplicate key value is (fa2f3cdf-6147-4973-9c1e-988a21408610).
The statement has been terminated.
I cannot seem to figure out why this might be happening, I feel the form is being submitted twice or the Editform is incorrectly being submitted via the two buttons. I very new to Blazor and EF so I would appreciate any insight into what might be causing this and how I might go about resolving this issue. Thanks in advance
Create razor page
<EditForm Model="#projectItem" OnValidSubmit="#CreateNewProject">
<DataAnnotationsValidator />
<div class="form-group">
<label class="custom-control-label"> Name </label>
<InputText id="title" #bind-Value="projectItem.ProjectName" class="form-control" />
<InputText id="title" #bind-Value="projectItem.ProjectDescription" class="form-control" />
<ValidationMessage For="#(() => projectItem.ProjectName)" />
<button type="submit" class="btn btn-primary">Create Project</button>
</div>
<ValidationSummary />
</EditForm>
<EditForm Model="#AssignUser" OnValidSubmit="#AddUserToProject">
<DataAnnotationsValidator />
<div class="form-group">
<label class="custom-control-label"> Assign users </label>
<InputText id="title" type="email" #bind-Value="#AssignUser.UserName" class="form-control" />
<button type="submit" class="btn btn-primary">Add User</button>
</div>
</EditForm>
<p> Currently assigned users </p>
#if (ListOfAllUsers.Count() == 0)
{
<p> No users assigned yet</p>
}
else
{
#for(int i = 0; i < ListOfAllUsers.Count(); i++)
{
<li> #ListOfAllUsers[i].UserName </li>
}
}
private Project projectItem { get; set; } = new Project();
private ApplicationUser AssignUser { get; set; } = new ApplicationUser();
private List<ApplicationUser> ListOfAllUsers { get; set; } = new List<ApplicationUser>();
//submit and create project with the assigned users
private async void CreateNewProject()
{
projectItem.AssignedUsersToProject = ListOfAllUsers;
try
{
var response = await Http.PostAsJsonAsync("Projects", projectItem);
var content = await response.Content.ReadAsStringAsync();
var project = JsonConvert.DeserializeObject<Project>(content);
if(response.IsSuccessStatusCode)
Navigation.NavigateTo($"/projects");
}
catch (Exception e)
{
}
}
// find and add user
private async void AddUserToProject()
{
try
{
var response = await Http.PostAsJsonAsync($"Projects/{AssignUser.UserName}", AssignUser);
var content = await response.Content.ReadAsStringAsync();
var userobject = JsonConvert.DeserializeObject<ApplicationUser>(content);
if (response.IsSuccessStatusCode)
{
if (!ListOfAllUsers.Any(n => n.UserName.Equals(userobject.UserName)))
{
ListOfAllUsers.Add(userobject);
} else
{
AssignUser.UserName = "This user is already added to the project.";
}
} else if (!response.IsSuccessStatusCode)
{
AssignUser.UserName = "User not found, try again";
}
}
catch (Exception e)
{
}
}
Controller: Create Project
[HttpPost]
public async Task<ActionResult<Project>> PostProject(Project project)
{
_context.Projects.Add(project);
await _context.SaveChangesAsync(); // error occurs when reaching this point
return CreatedAtAction("GetProject", new { id = project.ProjectId }, project);
}
Controller: find and return user
[HttpPost("{userEmail}")]
public async Task<ActionResult<ApplicationUser>> AddUserToProject(string userEmail)
{
try
{
// Find user
ApplicationUser userValid = _userManager.Users.Where
(s => s.Email == userEmail).First();
return userValid;
}
catch (InvalidOperationException)
{
return BadRequest();
}
}
Models
public class Project
{
[Key]
public Guid ProjectId { get; set; }
[Required]
public string ProjectName { get; set; }
[JsonIgnore]
public ICollection<ApplicationUser> AssignedUsersToProject { get; set; }
public Company assignedCompanyForProject { get; set; }
}
public class ApplicationUser : IdentityUser
{
public string firstName { get; set; }
public string lastName { get; set; }
[JsonIgnore]
public ICollection<Project> Projects { get; set; }
}
Form image
You type the name of the user you want to add to the project and click "Add User" which will find and return the user if they are a user that exists (This does not create the project yet only finds user). This user is then added to the ListOfAllUsers list which is then looped through to display all the current users assigned. Once a project title and description is given alongside the relevant users the "Create Project" button should be pressed which will create the whole project with the assigned users which have been determined. This is what is submitted to the database to add a new Project item
Suggested changes
Implementing EntityState.Unchanged;
foreach(var x in project.AssignedUsersToProject)
{
_context.Entry(x).State = EntityState.Unchanged;
}
_context.Projects.Add(project);
Causing following error:
ystem.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32
Implementing the following in the controller when creating the project solved the issue.
foreach(var x in project.AssignedUsersToProject)
{
_context.Entry(x).State = EntityState.Unchanged;
}
_context.Projects.Add(project);

Razor Pages Custom Tag Helper 2 Way Bind

I am wanting to create a custom tag helper in razor pages which binds to a custom model but the value is not being read back into the modal on post, below is my TagHelper code
[HtmlTargetElement("kenai-date", TagStructure = TagStructure.WithoutEndTag)]
public class Date : TagHelper
{
//public string Value { get; set; }
public ModelExpression AspFor { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "input";
output.TagMode = TagMode.SelfClosing;
output.Attributes.Add("value", this.AspFor.Model);
}
}
I am using the TagHelper with the below code
<kenai-date asp-for="DateValue" />
'DateValue' is a public property on the page, when first rendering the page the value of DateValue is correctly visible in the TagHelper Input element, if I force an OnPost, the value is removed.
I have applied the same to a standard input element with asp-for set and that works fine so suspect I am missing something in my TagHelper.
Asp.net core bind model data with name attribute.You use a custom tag helper,so it will get html like <input value="xxx">.So when form post,you cannot bind model data with name attribute,and when return Page in OnPost handler,model data is null.You need to add name attribute to <kenai-date asp-for="DateValue" />.Here is a demo:
TestCustomTagHelper.cshtml:
<form method="post">
<kenai-date asp-for="DateValue" name="DateValue" />
<input type="submit" />
</form>
TestCustomTagHelper.cshtml.cs:
public class TestCustomTagHelperModel : PageModel
{
[BindProperty]
public string DateValue { get; set; }
public void OnGet()
{
DateValue = "sss";
}
public IActionResult OnPost()
{
return Page();
}
}
result:

Microsoft.AspNetCore.Components.Forms.InputRadioGroup` does not support the type xxx

I want to use radio group in blazor so after implementing edit form and select one of the radio button I got this error :
Microsoft.AspNetCore.Components.Forms.InputRadioGroup`1[EGameCafe.SPA.Models.GameModel] does not support the type 'EGameCafe.SPA.Models.GameModel'.
here is my edit form :
<EditForm Model="ViewModel" OnValidSubmit="HandleCreateGroup">
#if (ViewModel.Games.List.Any())
{
<InputRadioGroup Name="GameSelect" #bind-Value="Gamemodelsample">
#foreach (var game in ViewModel.Games.List)
{
<InputRadio Value="game" />
#game.GameName
<br />
}
</InputRadioGroup>
}
</EditForm>
#code{
public GameModel GameModelSample { get; set; } = new();
}
and GameModel is :
public class GameModel
{
public string GameId { get; set; }
public string GameName { get; set; }
}
The InputRadioGroup, like other Blazor components, supports only a limited amount of types like String or Int32. You had the right idea, but unfortunately, you run into a kind of limitation of Blazor.
You could try to create a wrapper field.
private String _selectedGameId = "<Your Default Id>";
public String SelectedGameId
{
get => _selectedGameId;
set
{
_selectedGameId = value;
// Set the property of the ViewModel used in your Model Property of the EditContext or any other property/field
ViewModel.SelectedGame = ViewModel.Games.List?.FirstOrDefault(x => x.GameId == value);
}
}
Use the property SelectedGameId as the bind value of the InputRadioGroup component.
<InputRadioGroup Name="GameSelect" #bind-Value="SelectedGameId" >
#foreach (var game in ViewModel.Games.List)
{
<InputRadio Value="game.GameId" />
#game.GameName
<br />
}
</InputRadioGroup>
As an alternative, you can create a custom component that inheriting from InputRadioGroup to create a kind of GameBasedInputRadioGroup. If you are interested I can post a sample.
Because in your code #bind-Value="Gamemodelsample",you are trying to bind GameName(string) to Gamemodelsaple(object), which will cause type mismatch problems.
You only need to modify your code to:
#bind-Value="GameModelSample.GameName"

ASP.Net Core Razor Pages: How to return the complex model on post?

I created a new ASP.Net Core 2 (Razor Pages) Project
My model is:
public class FormularioGenerico
{
public FormularioGenerico()
{
}
public string IP { get; set; }
public List<string> items { get; set; } = new List<string>();
}
On the page I put
on the page.cshtml.cs
public class EditarModel : PageModel
{
[BindProperty]
public FormularioGenerico ff { get; set; }
[BindProperty]
public string Message { get; set; }
public void OnGet()
{
this.ff = new FormularioGenerico();
ff.IP = "C# FORM";
ff.items.Add("OK1");
ff.items.Add("OK2");
ff.items.Add("OK3");
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var m = ModelState.IsValid; // true
Debug.WriteLine(this.ff.IP); // is Always returning null
Debug.WriteLine(this.ff.items.Count); // is Always returning null
}
}
on the page.cshtml:
#model Formulario.Pages.EditarModel
...
<h1>#Model.ff.IP</h1>
#foreach (var i in Model.ff.items)
{
<div>#i</div>
}
<button type="submit">Enviar</button>
The items are correctly output. But the complete object does not go to the OnPost.
The problem is: The model is not coming fully populated on the OnPost.
How to receive the full object that was created on the OnGet, plus the changes made by the user on the form, on the post to OnPostAsync() ?
The BindProperty attribute is used to inform ASP.NET Core that the values that the form submitted should be mapped to the specified object. In your case you set the values for the ff property but you do not have the equivalent input values so that ASP.NET Core will get these values in order to store them back to the ff property.
In order to make it work you will have to replace your razor code with the following code:
<form method="post">
<h1>#Model.ff.IP</h1>
<input asp-for="#Model.ff.IP" type="hidden" /> #* create a hidden input for the IP *#
#for (int i = 0; i < Model.ff.items.Count(); i++)
{
<input asp-for="#Model.ff.items[i]" type="hidden" /> #* create a hidden input for each item in your list *#
<div>#Model.ff.items[i]</div>
}
<button type="submit">Enviar</button>
</form>
Very important. To make this work you can not use the foreach loop because ASP.NET core will not be able to find the values. You will have to use a for loop.
The inputs that I added are hidden because I guess you do not want them to be visible but you can remore the type="hidden" so that you will be able to see them. Every change that you make to these inputs will be submitted to the OnPostAsync method.

Same url but different controllers?

Is it possible to use two different controllers for the same url?
This is needed because I need the URL to always remain the same, but it should use different controllers. My controllers (Apples, Bananas, etc.) and views are separated into each own project.
I need a action in my main MVC project to return a action/view from either the Bananas or Apples project depending on some logic.
So how would I go ahead to always have the same url but return actions/views from different controllers?
I'm using MVC 4
Your URLs should be where the logic for selecting your controller is. Maybe you need to reorganise your project to have a single controller and put the other logic in the controller action for filling the model?
However, if you insist on going this route you will likely need to override CreateController in the DefaultControllerFactory, this is the class that instantiates your controller, usually based on your controller name. Here is an example in one of my projects:
public class ErrorHandlingControllerFactory : DefaultControllerFactory
{
/// <summary>
/// Injects a custom attribute
/// on every action that is invoked by the controller
/// </summary>
/// <param name="requestContext">The request context</param>
/// <param name="controllerName">The name of the controller</param>
/// <returns>An instance of a controller</returns>
public override IController CreateController(
RequestContext requestContext,
string controllerName)
{
var controller =
base.CreateController(requestContext,
controllerName);
var c = controller as Controller;
if (c != null)
{
c.ActionInvoker =
new ErrorHandlingActionInvoker(
new HandleErrorWithELMAHAttribute());
}
return controller;
}
}
You will need to set your route up to pass a known controller name (horrible magic strings...), test for this controller name, and if detected run your logic to get the actual controller name and pass this in to base.CreateController.
I wrote these codes. I hope that it helps you. I used hidden field to understand which method will run.
these are my models:
namespace MvcSameController.Models
{
public class RouteModel
{
public SampleModel1 SampleModel1 { get; set; }
public SampleModel2 SampleModel2 { get; set; }
}
public class SampleModel1
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SampleModel2
{
public int Id { get; set; }
public string Surname { get; set; }
}
}
this is controller:
using System.Web.Mvc;
using MvcSameController.Models;
namespace MvcSameController.Controllers
{
public class SameController : Controller
{
//
// GET: /Same/
public ActionResult Index()
{
return View();
}
[HttpPost]
public void Index(RouteModel routeModel, string type)
{
if (type == "1")
{
//Code for type 1
}
else if (type == "2")
{
//Code for type 2
}
}
}
}
and view :
#{
ViewBag.Title = "Index";
}
#model MvcSameController.Models.RouteModel
<section id="loginForm">
<h2>Type1 </h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.Hidden("type",1)
<fieldset>
<legend>Type1 Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.SampleModel1.Name)
#Html.TextBoxFor(m => m.SampleModel1.Name)
#Html.ValidationMessageFor(m => m.SampleModel1.Name)
</li>
</ol>
<input type="submit" value="Run Method1" />
</fieldset>
}
</section>
<section id="loginForm">
<h2>Type2</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.Hidden("type",2)
<fieldset>
<legend>Type2 Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.SampleModel2.Surname)
#Html.TextBoxFor(m => m.SampleModel2.Surname)
#Html.ValidationMessageFor(m => m.SampleModel2.Surname)
</li>
</ol>
<input type="submit" value="Run Method2" />
</fieldset>
}
</section>
you can download my sample from here