DataAnnotatons with EF and DataBaseFirst not working - entity-framework-4.1

I've modified the code in my model (which is on a separate tier) with metadata hoping this will resolve the issue, but it didn't. It's as if the DataAnnotations from the model are not being picked up? Why???
When viewing the page source, I should be seeing something like the following:
data-val-required="The User name field is required."
However, all I see is the fiollowing:
<div class="editor-label">
<label for="Email">Email</label>
</div>
<div class="editor-field">
<input id="Email" name="Email" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true"></span>
</div>
When looking at the model for standard account log in, I have the following:
YeagerTech.Models.LogOnModel
The above is defined in the following Models folder on the root of the website inside the AccountModels.cs file:
public class LogOnModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
The above (while the model is on the website front end and not in another tier) works fine and has the required attributes set up in the page source fine.What do I need to do to in order to perform data validation with the EF DataBaseFirst method where my model resides on another tier?
My web.config file is set up as follows:
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
On the front-end, I have my view set up as follows:
#model YeagerTech.YeagerTechWcfService.Customer
#{
ViewBag.Title = "Create Customer";
}
<h2>Create</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Ajax.BeginForm("Create", "Customer", new AjaxOptions { HttpMethod = "POST"}))
{
#Html.ValidationSummary(true, "Create was unsuccessful. Please correct the errors and try again.")
<fieldset>
<legend>Customer</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
The definition for YeagerTech.YeagerTechWcfService.Customer is set up as follows (inside the Reference.cs file)
[System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")] [System.Runtime.Serialization.DataContractAttribute(Name="Customer", Namespace="http://schemas.datacontract.org/2004/07/YeagerTechModel")] [System.SerializableAttribute()] public partial class Customer : YeagerTech.YeagerTechWcfService.CustomerDA { }
My model is setup as follows inside the above service:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ServiceModel;
using System.Runtime.Serialization;
namespace YeagerTechModel
{
[Serializable]
[DataContract]
[MetadataType(typeof(CustomerDA))]
public partial class Customer : CustomerDA
{
public Customer()
{
this.Projects = new HashSet<Project>();
}
}
[Serializable]
[DataContract]
public class CustomerDA
{
[DataMember]
public short CustomerID { get; set; }
[Required]
[StringLength(50)]
[DataType(DataType.EmailAddress)]
[DataMember]
public string Email { get; set; }
Base DbContext class
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
namespace YeagerTechModel
{
public partial class YeagerTechEntities : DbContext
{
public YeagerTechEntities()
: base("name=YeagerTechEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Customer> Customers { get; set; }
}
}

I see from your code that you included the jquery.validate.js script and the jquery.validate.unobtrusive.js script. However, I do not see that the jquery script was included. You must include jquery, and it must be the first script. In addition, since you are using MVC's Ajax capabilities, you also need to include the jquery.unobtrusive-ajax.js script as well.

Related

Custom data isn't saved in database

I am trying to make a login/signup system with ASP.NET. I saw a tutorial on how to make it and I started following him. But it was sometimes messy and I couldn't understand him. (For the tutorial).
I used the 'new scaffolded item' to create a new 'identity' for login & registration.
I added custom data of my own (at this moment it's only FirstName&LastName) and when I create A user everything looks excellent, in debug mode I could see the data saved in my class but when it transfers to the database it holds as "NULL".
I am still new to this technique so I am not sure if that's all the code that's needed for you to help spot the problem. If the's anything to add, comment down, please.
here's the HTML for the registration (at the moment the login isn't complete) [Directory: Areas/Identity/Pages/Register.cshtml]
#page
#model RegisterModel
#{ ViewData["Title"] = "Register"; }
#{ Layout = "~/Areas/Identity/Pages/_AuthLayout.cshtml"; }
<form asp-route-returnUrl="#Model.ReturnUrl" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label asp-for="Input.FirstName"></label>
<input asp-for="Input.FirstName" class="form-control" />
<span asp-validation-for="Input.FirstName" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="Input.LastName"></label>
<input asp-for="Input.LastName" class="form-control" />
<span asp-validation-for="Input.LastName" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
#section Scripts {
<partial name="_ValidationScriptsPartial"></partial>
}
here's the C# (the backend) [Directory: Areas/Identity/Pages/Register.cshtml.cs]
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using SvivaTeam.Areas.Identity.Data;
namespace SvivaTeam.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
if (User.Identity.IsAuthenticated)
Response.Redirect("/");
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = Input.Email,
Email = Input.Email,
FirstName = Input.FirstName, //Custom Code
LastName = Input.LastName //Cusom Code
};
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
}
Here's more C# code that holds the custom info (In the video he said to make it). [Directory: Areas/Identity/Data/ApplicationUser.cshtml]
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace SvivaTeam.Areas.Identity.Data
{
public class ApplicationUser : IdentityUser
{
[PersonalData]
[Column(TypeName = "nvarchar(100)")]
public string FirstName { get; set; }
[PersonalData]
[Column(TypeName = "nvarchar(100)")]
public string LastName { get; set; }
}
}

asp.net core pass Model to Layout

I have a layout page with the structure where I use navbar alongside #RenderBody().
later I made a method to generate pages and consequently navbar itself. thus, the navbar is not fixed anymore.
Then I made a partial view to loop through navbar items and display them in the same place in the layout. but I don't know how to import a model in the layout.
I'm wondering is there any tag helper that can trigger an action? to return the needed model
my layout looks like this
<html lang="en">
<head>
...
</head>
<body>
<header>
<partial name="_NavBar" model="????" />
</header>
<main class="mb-3">
<div class="container-fluid">
#RenderBody()
</div>
</main>
<footer id="footer" class="border-1 border-top border-primary">
<div class="container-fluid py-4">
<div class="copyright">
© Copyright
</div>
</div>
</footer>
#RenderSection("Scripts", required: false)
</body>
</html>
Any ideas to display this nav partial? or maybe using another tag helper? or any possible solution?
I agree with #Roars, and pls allow me to show some sample code here.
First, let's create a service which could gather target data for showing nav, e.g. the menus contained in the nav bar are stored in database and we need to query out first. I use mock data instead:
using System.Collections.Generic;
using WebApplication1.Models;
namespace WebApplication1.Services
{
public class ChildService : IChildService
{
public List<Menu> GetNav() {
return new List<Menu> {
new Menu{ MenuId = 1, MenuName = "name1" },
new Menu{ MenuId = 2, MenuName = "name2" }
};
}
}
}
The interface:
using System.Collections.Generic;
using WebApplication1.Models;
namespace WebApplication1.Services
{
public interface IChildService
{
public List<Menu> GetNav();
}
}
Menu Entity:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebApplication1.Models
{
public class Menu
{
public int MenuId { get; set; }
public string MenuName { get; set; }
}
}
Startup.cs -> ConfigureSerivces to config injection:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSingleton<IChildService, ChildService>();
}
After setting the dependency in startup.cs, we can use DI in views. I created a Razor view and inject my query result.
#using WebApplication1.Services
#inject IChildService childService
#{
var menus = childService.GetNav();
}
<h2>hello world</h2>
<div>
#foreach (var item in menus)
{
<div>#item.MenuName</div>
}
</div>
At last, using partial tag to embed this view into other views.
#page
#model WebApplication1.Pages.Childs.IndexModel
#{
}
<h2>index for child</h2>
Edit
<partial name="~/Pages/Childs/test.cshtml" />
Here's testing result:
Thank you so much, guys, #Tiny Wang #Roar S., and #Cake or Death
I found good solution for that by adding something called ViewComponent. And I thought it's better to share it with you all.
It helps to include whatever logic you need in its class and it allows to include a Model in its view.
Here are 3 steps - but first here is the Model
public class Page
{
[Key]
public Guid Id { get; set; }
public int PageOrder { get; set; } = 0;
public string pageName { get; set; }
public string pageDescreption { get; set; }
public string pageHtmlContent { get; set; }
public string NavIcon { get; set; } // svg icon
public bool IsNavBarItem { get; set; }
}
(1) ViewComponent architecture:
(2) ViewComponent class:
public class NavBarViewComponent : ViewComponent
{
private readonly ApplicationDbContext context;
public NavBarViewComponent(ApplicationDbContext _context)
{
context = _context;
}
// https://www.youtube.com/watch?v=exokw7WQQ-A
public IViewComponentResult Invoke()
{
// this is how to avoid error of can't convert IQueryable to IEnumerable
IEnumerable<Page> pages = context.pages.Where(P => P.IsNavBarItem == true).AsEnumerable<Page>();
return View(pages);
}
}
(3) ViewComponent view:
#model IEnumerable<Page>
... whatever
<ul class="navbar-nav">
#foreach (var item in Model)
{
<li class="nav-item">
<a asp-action="PageView" asp-controller="Home" asp-route-PageID="#item.Id" class="nav-link">
#Html.Raw(#item.NavIcon)
#item.pageName
</a>
</li>
}
</ul>
... whatever
And finally to display this nav anywhere just need to include component in the layout
#await Component.InvokeAsync("NavBar")

Blazor Razor Validation Message don't display from component library

I have created a library of blazor components to be able to call the components from the app but the message validation doesn't show. At the moment, the validation is done in a InputText (it validates the format or the length of the Input) but the message or the style of the component is not shown.
The code of the component library:
CustomInputText
<input value="#Value" #oninput="OnValueChanged" placeholder=#placeholderText class="form-control i-component o-my-4" />
<ValidationMessage For="#(() => model)" />
#code {
[Parameter]
public string placeholderText { get; set; }
[Parameter]
public object model { get; set; }
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private Task OnValueChanged(ChangeEventArgs e)
{
Value = e.Value.ToString();
return ValueChanged.InvokeAsync(Value);
}
}
I import the component from a nuget package to be able to use it in the App
The App code:
<CustomInputText placeholderText="Place Holder Test" model="filterPayroll.IPF" #bind-Value="filterPayroll.IPF"/>
When I put the ValidationMessage directly in the app it works correctly, but not in the library. For the two cases, the validation linked to the "filterPayroll" class is done correctly, the difference is that in one the message is displayed and the other does not.
You need to pass the For for the Validation Summary as an expression.
CustomInputText needs to look like this:
<input value="#Value" #oninput="OnValueChanged" placeholder=#placeholderText class="form-control i-component o-my-4" />
<ValidationMessage For="#For" />
#code {
[Parameter]
public string placeholderText { get; set; }
[Parameter] public Expression<Func<string>>? For { get; set; }
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private Task OnValueChanged(ChangeEventArgs e)
{
Value = e.Value.ToString();
return ValueChanged.InvokeAsync(Value);
}
}
And your markup:
<CustomInputText #bind-Value="filterPayRoll.IDF" For="() => filterPayRoll.IDF" />

IFormFile is not bound if nested in view model

I am having problem with binding nested IFormFile in .net core mvc project.
If I put my IFormFile in nested view model it will not be bound to it on post.
For example this does not work:
public class SomeVM
{
public GalleryVM Gallery { get; set; }
}
public class GalleryVM
{
public IFormFile UploadingImage { get; set; }
//gallery properties...
}
View:
#model SomeVM
<form method="post" enctype="multipart/form-data">
<input type="file" name="Gallery.UploadingImage" />
<input type="submit" value="save" />
</form>
Some code was omitted for brevity.
I found the solution to that so I want to share it with you. I found that it is known issue and it should be solved in .net core 2.0 issue on github
Current hack is to send some extra data when uploading file.
public class SomeVM
{
public GalleryVM Gallery { get; set; }
}
public class GalleryVM
{
public IFormFile UploadingImage { get; set; }
public bool FormFileHack { get; set; }
//gallery properties...
}
//the view .cshtml
<input type="file" name="Gallery.UploadingImage" />
<input type="hidden" name="Gallery.FormFileHack" />

button group into razor syntax

I would like to know if I can convert my div button group (btn-group) to razor syntax in my asp.net mvc app? I want the razor syntax so I can preselect and pre activate a button label when entering the page. If razor isn't needed, then can someone please show me how to make a button active and selected on the page being entered from my view model data? It seems that without razor I would have to pass my viewmodel data to javascript to perform, but that doesn't seem right. Here is my html
<div class="form-group">
#Html.LabelFor(model => model.Listing.SpaceType, htmlAttributes: new { #class = "control-label" })
<div class="btn-group form-control" data-toggle="buttons" id="SpaceType">
<label id="SpaceTypeLabel0" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="0"> House
</label>
<label id="SpaceTypeLabel1" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="1"> Apartment
</label>
<label id="SpaceTypeLabel2" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="2"> Studio
</label>
<label id="SpaceTypeLabel3" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="3"> Other
</label>
</div>
</div>
Here is my model
public class Space
{
public int SpaceId { get; set; }
public virtual SpaceOverview Overview { get; set; }
public virtual SpaceDetails Details { get; set; }
public virtual SpaceListing Listing { get; set; }
public virtual SpaceAddress Address { get; set; }
[Required]
public DateTime DateCreated { get; set; }
}
and spacelisting is
public class SpaceListing
{
[Key, ForeignKey("SpaceOf")]
public int SpaceListingId { get; set; }
public SpaceType SpaceType { get; set; }
public SpaceLocation SpaceLocation { get; set; }
public SpaceAccommodation Accommodation { get; set; }
public Space SpaceOf { get; set; } // one-to-one
}
and spacetype is
public enum SpaceType
{
Home,
Apartment,
Studio,
Park,
Beach,
Field,
Backyoard,
FrontYard,
Other
}
Currently you creating a group of radio buttons with name="typeoptions" which have no relationship to the model, and your not even giving the radio buttons a value attribute so nothing would post back anyway.
The syntax should be
#Html.RadioButtonFor(m => m.Listing.SpaceType, "House", new { id = "House" })
#Html.Label("House")
#Html.RadioButtonFor(m => m.Listing.SpaceType, "Apartment", new { id = "Apartment" })
#Html.Label("Apartment")
...
To make this easier, you can create an extension method
public static class RadioButtonHelper
{
public static MvcHtmlString EnumRadioButtonListFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
string name = ExpressionHelper.GetExpressionText(expression);
Type type = Nullable.GetUnderlyingType(metaData.ModelType);
if (type == null || !type.IsEnum)
{
throw new ArgumentException(string.Format("The property {0} is not an enum", name));
}
StringBuilder html = new StringBuilder();
foreach (Enum item in Enum.GetValues(type))
{
string id = string.Format("{0}_{1}", metaData.PropertyName, item);
StringBuilder innerHtml = new StringBuilder();
innerHtml.Append(helper.RadioButtonFor(expression, item, new { id = id }));
innerHtml.Append(helper.Label(id, item.ToDescription()));
TagBuilder div = new TagBuilder("div");
div.AddCssClass("radiobutton");
div.InnerHtml = innerHtml.ToString();
html.Append(div.ToString());
}
TagBuilder container = new TagBuilder("div");
container.AddCssClass("radiobutton-container");
container.InnerHtml = html.ToString();
return MvcHtmlString.Create(container.ToString());
}
}
Note, this uses the following extension method
public static string ToDescription(this Enum value)
{
FieldInfo field = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
{
return attributes[0].Description;
}
return value.ToString();
}
which allows you to decorate the enum values with a 'friendly' name
public enum SpaceType
{
Home,
[Description("2 bed apartment")]
Apartment,
....
}
and in the view
#Html.EnumRadioButtonListFor(m => m.Listing.SpaceType)