sorting and filtering in asp.net core razor pages - razor-pages

I'm following Working with Data in ASP.NET Core tutorial in Microsoft documentation,(Sort, filter, page, and group section) but I have a hard time trying to figure out how the following code works...(I'm new to razor pages and asp.net core).
this is the PageModel for students index page.
namespace Contoso.Pages.Students
{
public class IndexModel : PageModel
{
//
public string NameSort { get; set; }
public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
public async Task OnGetAsync(string sortOrder,string currentFilter, string searchString, int? pageIndex)
{
CurrentSort = sortOrder;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
CurrentFilter = searchString;
IQueryable<Student> studentIQ = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentIQ = studentIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
}
}
}
and this is a from in the index page for the model.
#*other markup commented out*#
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="#Model.NameSort"
asp-route-currentFilter="#Model.CurrentFilter">
#Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
</th>
<th>
#Html.DisplayNameFor(model => model.Student[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="#Model.DateSort"
asp-route-currentFilter="#Model.CurrentFilter">
#Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
</table>
#*other markup commented out*#
now my question is how is for example asp-route-sortOrder="#Model.NameSort"
related to sortOrder parameter in the OnGetAsync method ? and how exactly does the value sortOrder get set by clicking on the link in the index page?

how is asp-route-sortOrder="#Model.NameSort" related to sortOrder parameter in the OnGetAsync method ?
The route attribute on the select tag helper allows you to specify values for route data parameters. If the key (sortOrder in this case) is included as part of a route template, the value will appear as a segment in the generated URL. Otherwise it is appended to the URL as a query string value. Those are matched to handler methods by Model Binding. You can read more about how model binding works in Razor Pages here: https://www.learnrazorpages.com/razor-pages/model-binding

Related

inherited classes and razor asp.net mvc 5

Good morning, I have a problem with returning my database information (generated using the EF 6 code-first method) for my View Razor. The issue is that I'm wanting to return the information from inherited classes in the View, but they are not available, only the properties of the base class are presented, not those of the dependent classes.
The following are the Model, Controller, and View classes used:
Class ClientModel
public class Client
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ClientId { get; set; }
[DataType(DataType.Date)]
public DateTime Birth { get; set; }
[Display(Name = "Telefone principal")]
public string Phone1 { get; set; }
[Display(Name = "Telefone Alternativo")]
public string Phone2 { get; set; }
public ICollection<OrcamentoContato> Contacts { get; set; }
public ICollection<Contrato> Contracts { get; set; }
}
Class FisicModel
public class PessoaFisica : Client
{
public TipoPessoa PersonType { get; set; }
[Required]
[Display(Name = "Nome completo*")]
[StringLength(250, ErrorMessage = "O campo é obrigatório.")]
public string Name { get; set; }
public string RG { get; set; }
[Required(ErrorMessage = "O campo CPF é obrigatório.")]
[StringLength(14)]
public string CPF { get; set; }
[Display(Name = "Filiação")]
public string Filiacao { get; set; }
[Display(Name = "Endereço")]
public string Address { get; set; }
}
Class JuridicModel
public class PessoaJuridica : Client
{
public TipoPessoa PersonType { get; set; }
[Required]
[Display(Name = "Razão Social*")]
[StringLength(200, ErrorMessage = "O campo é obrigatório.")]
public string SocialName { get; set; }
[Required]
[Display(Name = "CNPJ*")]
[StringLength(200, ErrorMessage = "O campo é obrigatório.")]
public string CNPJ { get; set; }
[Display(Name = "Inscrição Estadual")]
public string InscricaoEstadual { get; set; }
[Display(Name = "Inscrição Municipal")]
public string InscricaoMunicipal { get; set; }
[Display(Name = "Endereço")]
public string Address { get; set; }
public string ContactWith { get; set; }
}
Controller
public ActionResult Index()
{
var clients = db.Clients.ToList();
return View(clients);
}
Index View
#model IEnumerable<CabinePhoto.Models.Entidades.Client>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Birth)
</th>
<th>
#Html.DisplayNameFor(model => model.Phone1)
</th>
<th>
#Html.DisplayNameFor(model => model.Phone2)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Birth)
</td>
<td>
#Html.DisplayFor(modelItem => item.Phone1)
</td>
<td>
#Html.DisplayFor(modelItem => item.Phone2)
</td>
</tr>
}
IdentityModel
public DbSet<Client> Clients { get; set; }
public DbSet<PessoaFisica> PessoaFisica { get; set; }
public DbSet<PessoaJuridica> PessoaJuridica { get; set; }
All the information is stored in the same client table, since I'm using the form of inheritance by hierarchy, but in the view only the client model information is returned
I've been able to solve the problem. I'll leave here recorded what I did to solve the problem in respect to inheritance.
First, I created a ViewModel and put two ICollection properties, I modified the controller by adding the queries referring to the client table, but specifically bringing the required types and finally, I passed the ViewModel to the Index.cshtml and I used two foreachs to retrieve the information from According to the type specified, shown below:
ClientesiewModel.cs
public class ClientesViewModel
{
public IEnumerable<PessoaFisica> Fisica { get; set; }
public IEnumerable<PessoaJuridica> Juridica { get; set; }
}
controlle.cs
public ActionResult Index()
{
var cliente_fisico = db.Clientes.OfType<PessoaFisica>().ToList();
var cliente_juridico = db.Clientes.OfType<PessoaJuridica>().ToList();
var cliente = db.Clientes.ToList();
ClientesViewModel clientes = new ClientesViewModel()
{
Fisica = cliente_fisico,
Juridica = cliente_juridico
};
return View(clientes);
}
View Index.cshtml
#model CabinePhoto.ViewModels.ClientesViewModel
<table class="table">
<tr>
<th>
#Html.DisplayName("Nome")
</th>
<th>
#Html.DisplayName("Telefone")
</th>
<th>
#Html.DisplayName("Telefone 2")
</th>
<th></th>
</tr>
#if (Model.Fisica != null || Model.Juridica != null)
{
foreach (var fisica in Model.Fisica)
{
<tr>
<td>
#Html.DisplayFor(modelItem => fisica.NomeCompleto)
</td>
<td>
#Html.DisplayFor(modelItem => fisica.TelefonePrincipal)
</td>
<td>
#Html.DisplayFor(modelItem => fisica.TelefoneAlternativo)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = fisica.ClienteId }) |
#Html.ActionLink("Details", "Details", new { id = fisica.ClienteId }) |
#Html.ActionLink("Delete", "Delete", new { id = fisica.ClienteId })
</td>
</tr>
}
foreach (var juridica in Model.Juridica)
{
<tr>
<td>
#Html.DisplayFor(modelItem => juridica.PessoaContato)
</td>
<td>
#Html.DisplayFor(modelItem => juridica.CNPJ)
</td>
<td>
#Html.DisplayFor(modelItem => juridica.TelefonePrincipal)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = juridica.ClienteId }) |
#Html.ActionLink("Details", "Details", new { id = juridica.ClienteId }) |
#Html.ActionLink("Delete", "Delete", new { id = juridica.ClienteId })
</td>
</tr>
}
}
</table>
Thanks for the help previously assigned
Suppose you have a variable like this in your controller.
Client c = new Client();
If you later wrote
c.ClientId = 1;
it would work perfectly.
Similarly, if you wrote
PessoaFisica p = new PessoaFisica ();
and later
p.Name = "abc";
it would also work.
However, if you wrote
Client c = new PessoaFisica();
c.Name = "abc";
It will fail to compile.
Along the same lines
#model IEnumerable <CabinePhoto.Models.Entidades.Client>
means static type of your Model is a collection of Client objects, it will only allow you to bind to properties defined in Client class.
Entity Framework actually returns the correct types, but you're effectively upcasting everything to Client by the variable type you're storing into and the model definition of the view. Essentially, you just need to cast to the right type. Unfortunately, there's no way to just know what type it should be after it's been upcasted. You'll have to conditionally check:
if (item is PessoaFisica)
{
var pessoaFiscica = (PessoaFisica)item;
// now you can access those derived typed properties off of `pessoaFiscica`
}
You can also use as and rely on the fact that it returns null when something can't be casted:
var pessoaFiscica = item as PessoaFisica;
if (pessoaFiscica != null)
{
// access PessoaFiscica properties
}
With C# 7.0, you can use the pattern matching syntax to streamline it a little:
if (item is PessoaFiscica pessoaFiscica)
{
// use `pessoaFiscica`
}
The pattern matching syntax also allows you use switch blocks, which may make things much easier on you:
switch (item)
{
case PessoaFisica pessoaFisica:
// do something with `PessoaFisica` instance
break;
case PessoaJuridica pessoaJuridica:
// do something with `PessoaJuridica` instance
break;
default:
// do something with generic `Client` instance
break;
}

Asp.net mvc 5: Partial view with strongly typed model of type list is not displaying

I am calling partial view controller through J_Query, controller action(ListInventoryProduct) is called and execute without error( the list is populated). But the partial view is not displayed.
In browser Developer tool says it is internal server error.
I can't figure out what is the problem.
The following is my code.
Model:
public class InventoryProductsViewModel
{
public long Id { get; set; }
[Display(Name = "Product Name")]
public string Title { get; set; }
[Display(Name = "SubCategory Name")]
public string SubCategory { get; set; }
[Display(Name = "Balance")]
public int Balance { get; set; }
[Display(Name = "Count")]
public int InventoryCount { get; set; }
[Display(Name = "Difference")]
public string Difference { get; set; }
[Display(Name = "IMEINumber")]
public string IMEINumber { get; set; }
[Display(Name = "BarrcodesString")]
public string BarrcodesString { get; set; }
public long subId { get; set; }
// public List<Category> lstCategory { get; set; }
}
Controller Action
public ActionResult LoadInventoryProducts(long categoryId)
{
Session["Products"] = null;
Session["InventoryMissing"] = null;
var userSession = Session.GetSessionUserInfo();
if (userSession != null)
{
List<InventoryProductsViewModel> products = db.Products.Where(p => !p.IsDeleted && p.CompanyId == userSession.CompanyId && (categoryId == 0 || p.SubCategory.CategoryId == categoryId)).Select(p => new InventoryProductsViewModel { Id = p.Id, Title = p.Title, SubCategory = p.SubCategory.Title, IMEINumber = p.IMEINumber, Balance = (p.PurchasedQuantity - p.SoldQuantity) }).ToList(); //&& (subCategoryId == 0 || p.SubCategoryId == subCategoryId)
Session["Products"] = products;
if (Session["InventoryMissing"] == null)
{
Session["InventoryMissing"] = new List<InventoryMissing>();
return PartialView("ProductsPartialView", products);
}
else
{
return Redirect("~/Error/Error");
}
}
}
PartialView
#model List<ViewModel.InventoryProductsViewModel>
<table >
<tr>
<th>#Html.DisplayNameFor(Model[0].Title)</th>
<th>#Html.DisplayNameFor(Model[0].SubCategory)</th>
<th>#Html.Label("Balance")</th>
<th>#Html.Label("Count")</th>
<th>#Html.Label("Difference")</th>
<th>#Html.Label("IMEI Number")</th>
</tr>
#for (int i = 0; i < Model.Count(); i++ )
{
<tr id="#Model[i].Id">
<td>
#Html.Hidden(Model[i].subId)
#Html.DisplayFor(Model[i].Title)
</td>
<td>#Html.DisplayFor(Model[i].SubCategory)</td>
<td class="balance">#Html.DisplayFor(Model[i].Balance)</td>
<td>#Html.EditorFor(Model[i].InventoryCount)</td>
<td class="difference">0</td>
<td>#Html.DisplayFor(modelItem =>Model[i].IMEINumber)</td>
</tr>
}
</table>
Html Helpers For Model were used which was creating problem.
Correct Helpers are as follow.
#model List<HiTechPOS.ViewModel.InventoryProductsViewModel>
<table class=" table table-striped table-advance table-hover">
<tr>
<th>
#Html.Label("Product Name")
</th>
<th>
#Html.Label("SubCategory Name")
</th>
<th>
#Html.Label("Balance")
</th>
<th>
#Html.Label("Count")
</th>
<th>
#Html.Label("Difference")
</th>
<th>
#Html.Label("IMEI Number")
</th>
</tr>
#for (int i = 0; i < Model.Count(); i++ )
{
<tr id="#Model[i].Id">
#*<td>
#Html.DisplayFor(modelItem => item.Id)
</td>*#
<td>
#Html.Hidden(Model[i].subId.ToString())
#Html.Label(Model[i].Title)
</td>
<td>
#Html.Label(Model[i].SubCategory)
</td>
<td class="balance">
#Html.Label(Model[i].Balance.ToString())
</td>
<td>
#Html.Editor(Model[i].InventoryCount.ToString())
</td>
<td class="difference">
#Html.Label(Model[i].Difference.ToString())
</td>
<td>
if(Model[i].IMEINumber == null)
{
#Html.Label("")
}
else
{
#Html.Label(Model[i].IMEINumber)
}
</td>
</tr>
}

MVC 5 - Show item details in the same page when item is selected

I use the sample code located here: http://code.msdn.microsoft.com/MVC5-Demo-with-Entity-c6bc81df
Basically this is a CRUD sample for movies database where you can create read, update and delete them.
My question is: how can I show the details of a selected movie when clicking on Details link on the same page?
At the moment when clicking details you are redirected to the controller and action that will display the info about that movie.
Can I show the details on the same page where movies are listed?
There are a number of ways to do this, but they all come down to making some sort of Ajax call to load the partial view. The following is a working implementation, albeit minimal:
The Model - Movie.cs
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
I'd recommend a view model here, but that seems a bit out of the scope of the question at hand.
The Controller - MoviesController.cs
public class MoviesController : Controller
{
private readonly List<Movie> movies = new List<Movie>
{
new Movie { Title = "SomeTitle", Price = 23.25M, ID = 1 },
new Movie { Title = "AnotherTitle", Price = 123.25M, ID = 2 }
};
public ActionResult Index()
{
return this.View(this.movies);
}
public PartialViewResult Details(int id)
{
return this.PartialView("_details", this.movies.First(x => x.ID == id));
}
}
Partial View - _Details.cshtml
#model YourProject.Models.Movie
<hr/>
<h3>Details</h3>
#Html.DisplayFor(m => m.Title)
#Html.DisplayFor(m => m.Price)
...etc
<button type="button" id="clearDetails">Details</button>
Main View - Index.cshtml
#model IEnumerable<YourProject.Models.Movie>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script type="text/javascript">
$(function () {
var detailsPartial = $('#details');
$('.details-link').click(function (event) {
event.preventDefault();
var url = $(this).attr('href');
$.ajax({
url: url,
type: 'get',
success: function(data) {
detailsPartial.html(data);
$('#clearDetails').click(function () {
detailsPartial.html(null);
});
}
});
});
});
</script>
<h2>Movies</h2>
<table>
<thead>
<tr>
<th></th>
<th>#Html.DisplayNameFor(x => x.Title)</th>
<th>#Html.DisplayNameFor(x => x.Price)</th>
</tr>
</thead>
<tbody>
#foreach (var movie in Model)
{
<tr>
<td>#Html.ActionLink("Details", "Details", new { id = movie.ID }, new { #class = "details-link" })</td>
<td>#Html.DisplayFor(x => movie.Title)</td>
<td>#Html.DisplayFor(x => movie.Price)</td>
</tr>
}
</tbody>
</table>
<div id="details"></div>
Put this altogether and you're left with a view wherein one can click details, Ajax will retrieve the partial view HTML from the server and insert it into the specified div tag.

Orchard Projection Page Default View

I am using Orchard 1.8 and have created a new content type (called PressRelease), query for the results and projection to view the query with a custom template (using URL Alternates in the format List-ProjectionPage-url-PressRelease.cshtml) and all of that is working fine.
The one part that has me stumped is, if I use The Theme Machine as my theme (untouched), this projection view will show up in an unordered list with the corresponding AutoRoute links to the individual ContentItem entities, their metadata and so on. I'm trying to figure out how I access things such as the AutoRoute URL for a specific item, the metadata (create/publish dates) and so on for use with things like a Facebook Share button. Essentially I'm trying to recreate that default view, albeit with customizations.
Here is the code for List-ProjectionPage-url-PressRelease.cshtml:
#using Orchard.Utility.Extensions;
#using System.Linq
#functions
{
public class PressRelease
{
public PressRelease()
{
this.Attachments = new List<Attachment>();
}
public string Title { get; set; }
public string Source { get; set; }
public DateTime PublishDate { get; set; }
public string Body { get; set; }
public List<Attachment> Attachments { get; set; }
}
public class Attachment
{
public string Filename { get; set; }
public string Path { get; set; }
}
}
#{
//add list of dynamic objects to strongly typed class
var releases = new List<PressRelease>();
foreach (var item in #Model.Items)
{
var release = new PressRelease
{
Title = item.ContentItem.TitlePart.Title,
Source = item.ContentItem.PressRelease.Source.Value,
PublishDate = item.ContentItem.PressRelease.Date.DateTime,
Body = item.ContentItem.BodyPart.Text
};
//load attachment(s) to class
var attachments = (Orchard.MediaLibrary.Fields.MediaLibraryPickerField)item.ContentItem.PressRelease.Attachment;
if (attachments.MediaParts.Count() > 0)
{
foreach (var part in attachments.MediaParts)
{
release.Attachments.Add(new Attachment { Filename = part.FileName, Path = part.MediaUrl });
}
}
releases.Add(release);
}
}
#{
foreach (var item in releases)
{
<div class="press-release">
<div class="press-release-title">#item.Title</div>
<div class="press-release-meta">
<span class="press-release-source">Source: #item.Source</span>
#if (item.PublishDate != DateTime.MinValue)
{
<span class="press-release-date">#item.PublishDate.ToShortDateString()</span>
}
</div>
#if (item.Attachments.Count() > 0)
{
<div class="press-release-attachments">
<span class="press-release-attachments-title">Attached: </span>
#foreach (var attachment in item.Attachments)
{
var linkText = attachment.Filename;
var url = attachment.Path;
#Html.Link(linkText, url);
if (attachment != item.Attachments.Last())
{
<span>, </span>
}
}
</div>
}
<div class="press-release-body">
<p>#Html.Raw(item.Body.Replace("\r\n", "<br />"))</p>
</div>
</div>
<div class="social">
<!-- ** This is where I need AutoRoute URL so I can do FB share link **-->
<div class="fb-share-button" data-href="" data-type="button_count"></div>
</div>
if (item != releases.Last())
{
<hr />
}
}
}
Thoughts?
Utilizing the Shape Tracer (in conjunction with #Bertrand's assistance in the comments above) helped me get to where I need. Here is the final layout code I went with (which has some super hacky stuff in it):
#using Orchard.Utility.Extensions;
#using System.Linq
#functions
{
public class PressRelease
{
public PressRelease()
{
this.Attachments = new List<Attachment>();
}
private string _NavigateUrl = string.Empty;
public string Title { get; set; }
public string Source { get; set; }
public DateTime PublishDate { get; set; }
public string Body { get; set; }
public List<Attachment> Attachments { get; set; }
public string NavigateUrl
{
get { return string.Format("{0}://{1}/{2}", HttpContext.Current.Request.Url.Scheme, HttpContext.Current.Request.Url.Authority, _NavigateUrl); }
set { this._NavigateUrl = value; }
}
}
public class Attachment
{
public string Filename { get; set; }
public string Path { get; set; }
}
}
#{
//add list of dynamic objects to strongly typed class
var releases = new List<PressRelease>();
foreach (var item in #Model.Items)
{
var release = new PressRelease
{
Title = item.ContentItem.TitlePart.Title,
Source = item.ContentItem.PressRelease.Source.Value,
PublishDate = item.ContentItem.PressRelease.Date.DateTime,
//this is super hacky to get a chopped version of the HTML submitted for a summary
Body = item.ContentItem.BodyPart.Text,
NavigateUrl = item.ContentItem.AutoroutePart.Path
};
//load attachment(s) to class
var attachments = (Orchard.MediaLibrary.Fields.MediaLibraryPickerField)item.ContentItem.PressRelease.Attachment;
if (attachments.MediaParts.Count() > 0)
{
foreach (var part in attachments.MediaParts)
{
release.Attachments.Add(new Attachment { Filename = part.FileName, Path = part.MediaUrl });
}
}
releases.Add(release);
}
}
#{
foreach (var item in releases)
{
<div class="press-release">
<div class="press-release-title">#item.Title</div>
<div class="press-release-meta">
<span class="press-release-source">Source: #item.Source</span>
#if (item.PublishDate != DateTime.MinValue)
{
<span class="press-release-date">#item.PublishDate.ToShortDateString()</span>
}
</div>
#if (item.Attachments.Count() > 0)
{
<div class="press-release-attachments">
<span class="press-release-attachments-title">Attached: </span>
#foreach (var attachment in item.Attachments)
{
#attachment.Filename
if (attachment != item.Attachments.Last())
{
<span>, </span>
}
}
</div>
}
<div class="press-release-body">
#{
var body = new HtmlString(Html.Excerpt(item.Body, 200).ToString().Replace(Environment.NewLine, "</p>" + Environment.NewLine + "<p>"));
<p>#body (read more)</p>
}
</div>
</div>
<div class="social">
<div class="fb-share-button" data-href="#item.NavigateUrl" data-type="button_count"></div>
</div>
if (item != releases.Last())
{
<hr />
}
}
}

Unable to Update Many to Many Relationship. Entity Framework Code First

I am trying to update a many to many relationship that I have setup in Entity Framework using Code First. I've created the following Models.
[Serializable]
public class ClientFormField : FormField
{
public ClientFormField()
{
CanDisable = true;
}
public virtual ClientFormGroup Group { get; set; }
public virtual ICollection<ClientValue> Values { get; set; }
public virtual ICollection<LetterTemplate> LetterTemplates { get; set; }
}
[Serializable]
public class CaseFormField : FormField
{
public CaseFormField()
{
CanDisable = true;
}
public virtual CaseFormGroup Group { get; set; }
public virtual ICollection<LetterTemplate> LetterTemplates { get; set; }
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
foreach (var val in base.Validate(validationContext))
yield return val;
}
}
[Serializable]
public class SystemField : TrackableEntity
{
public string Name { get; set; }
public string Value { get; set; }
public string VarName { get; set; }
public virtual SystemFieldType SystemFieldType { get; set; }
public int TypeId { get; set; }
public ICollection<LetterTemplate> LetterTemplates { get; set; }
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(Name))
yield return new ValidationResult("System field must have a name.", new[] { "SystemFieldName" });
if (string.IsNullOrWhiteSpace(Value))
yield return new ValidationResult("System field must have a value.", new[] { "SystemFieldValue" });
var regex = new Regex(#"^[a-zA-Z0-9-_]+$");
if (!string.IsNullOrWhiteSpace(VarName) && !regex.IsMatch(VarName))
yield return
new ValidationResult("Varname can only contain alphanumeric, underscore, or hyphen",
new[] { "SystemFieldVarName" });
if (TypeId <= 0)
yield return new ValidationResult("System Field must have a type.", new[] { "SystemFieldType" });
}
}
[Serializable]
public class LetterTemplate : TrackableEntity
{
public LetterTemplate()
{
ClientFields = new Collection<ClientFormField>();
CaseFields = new Collection<CaseFormField>();
SystemFields = new Collection<SystemField>();
}
public string Name { get; set; }
public string Data { get; set; }
public virtual ICollection<ClientFormField> ClientFields { get; set; }
public virtual ICollection<CaseFormField> CaseFields { get; set; }
public virtual ICollection<SystemField> SystemFields { get; set; }
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(string.IsNullOrWhiteSpace(Name))
yield return new ValidationResult("Form Template must have a name", new[] { "Name" });
if(string.IsNullOrWhiteSpace(Data))
yield return new ValidationResult("Form Template must have content", new[] { "Data" });
}
}
Below is the configuration for the LetterTemplate class.
public class LetterTemplateConfiguration : BaseTrackableEntityConfiguration<LetterTemplate>
{
public LetterTemplateConfiguration()
{
HasMany(c => c.ClientFields).WithMany(c => c.LetterTemplates)
.Map(m =>
{
m.MapLeftKey("LetterTemplateId");
m.MapRightKey("ClientFormFieldId");
m.ToTable("LetterTemplateClientFields");
});
HasMany(c => c.CaseFields).WithMany(c => c.LetterTemplates)
.Map(m =>
{
m.MapLeftKey("LetterTemplateId");
m.MapRightKey("CaseFormFieldId");
m.ToTable("LetterTemplateCaseFields");
});
HasMany(c => c.SystemFields).WithMany(c => c.LetterTemplates)
.Map(m =>
{
m.MapLeftKey("LetterTemplateId");
m.MapRightKey("SystemFieldId");
m.ToTable("LetterTemplateSystemFields");
});
}
}
Here is the controller method for Add/Update and the server method that holds the business logic for Add/Update
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Manage(LetterTemplate template)
{
if(ModelState.IsValid)
{
if (_letterTemplateService.Save(template) != null)
return RedirectToAction("List");
}
ViewBag.ClientFields = _clientFieldService.GetAllFields().OrderBy(f => f.Name);
ViewBag.CaseFields = _caseFieldService.GetAllFields().OrderBy(f => f.Name);
ViewBag.SystemFields = _systemFieldService.GetAllFields().OrderBy(f => f.Name);
return View(template);
}
public LetterTemplate Save(LetterTemplate template)
{
var dbTemplate = template;
if (template.Id > 0)
{
dbTemplate = _letterTemplateRepo.GetById(template.Id);
dbTemplate.Name = template.Name;
dbTemplate.Data = template.Data;
}
dbTemplate.ClientFields.Clear();
foreach (var field in _clientFieldRepo.All().Where(field => template.Data.Contains("~~" + field.VarName + "~~")))
dbTemplate.ClientFields.Add(field);
dbTemplate.CaseFields.Clear();
foreach (var field in _caseFieldRepo.All().Where(field => template.Data.Contains("~~" + field.VarName + "~~")))
dbTemplate.CaseFields.Add(field);
dbTemplate.SystemFields.Clear();
foreach (var field in _systemFieldRepo.All().Where(field => template.Data.Contains("~~" + field.VarName + "~~")))
dbTemplate.SystemFields.Add(field);
return template.Id <= 0 ? _letterTemplateRepo.Add(dbTemplate) : _letterTemplateRepo.Update(dbTemplate);
}
Here is the view for Add/Update of the Letter Template.
#section RightContent
{
<h4>Manage</h4>
<form method="POST" action="/LetterTemplate/Manage" id="templateForm">
<div id="toolbar">
<div style="float: left;padding: 3px 0px 0px 10px;">
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
</div>
<div class="item" onclick=" $('#templateForm').submit(); ">
<span class="save"></span>Save
</div>
<div class="item" onclick=" window.location = '/LetterTemplate/List'; ">
<span class="list"></span>Back To List
</div>
</div>
<div class="formErrors">
#Html.ValidationSummary()
</div>
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.Active)
#Html.HiddenFor(m => m.IsDeleted)
#Html.TextAreaFor(m => m.Data)
#Html.AntiForgeryToken()
</form>
}
When I am creating a new template from the view everything is working fine. I get fields populated in my Many to Many relationship tables as I expect. When I attempt to update the relationship which should clear out all existing relations and create new relations nothing happens. The tables are not affected at all. I've read through several different posts about issues with updating many to many tables but haven't found anything that fixes my issue. The is the first time I have attempted many to many with EF code first and followed many tutorials before hand but it seems that no matter what I do, EF will not update the relationship tables.
UPDATE:
Queries generated when adding a new template:
DECLARE #0 nvarchar = N'Test',
#1 nvarchar = N'<p>~~case_desc~~</p>
<p>~~fname~~</p>
<p>~~lname~~</p>
',
#2 bit = 1,
#3 bit = 0,
#4 int = 2,
#5 int = 2,
#6 DateTime2 = '2013-04-08T16:36:09',
#7 DateTime2 = '2013-04-08T16:36:09'
insert [dbo].[LetterTemplates]([Name], [Data], [Active], [IsDeleted], [CreatedById], [ModifiedById], [DateCreated], [DateModified])
values (#0, #1, #2, #3, #4, #5, #6, #7)
DECLARE #0 int = 2,
#1 int = 1
insert [dbo].[LetterTemplateClientFields]([LetterTemplateId], [ClientFormFieldId])
values (#0, #1)
DECLARE #0 int = 2,
#1 int = 2
insert [dbo].[LetterTemplateClientFields]([LetterTemplateId], [ClientFormFieldId])
values (#0, #1)
DECLARE #0 int = 2,
#1 int = 3
insert [dbo].[LetterTemplateClientFields]([LetterTemplateId], [ClientFormFieldId])
values (#0, #1)
Query Generated on update:
DECLARE #0 nvarchar = N'Test',
#1 nvarchar = N'<p>~~case_desc~~</p>
<p> </p>
<p>~~fname~~</p>
<p> </p>
<p>~~dob~~</p>
',
#2 bit = 1,
#3 bit = 0,
#4 int = 2,
#5 int = 2,
#6 DateTime2 = '2013-04-08T16:23:12',
#7 DateTime2 = '2013-04-08T16:33:15',
#8 int = 1
update [dbo].[LetterTemplates]
set [Name] = #0, [Data] = #1, [Active] = #2, [IsDeleted] = #3, [CreatedById] = #4, [ModifiedById] = #5, [DateCreated] = #6, [DateModified] = #7
where ([Id] = #8)
UPDATE
My repository pattern has 2 base generic classes. A Base Trackable Entity Repository and a Base Repository. The base trackable entity repo handles making sure deleted items are soft deleted, getting non deleted items, and managing the createdby/modifiedby and createdDate/UpdatedDate. The base repo handles the rest of the basic CRUD operations. Below is the update method and associated methods that get called when I call update through the LetterTemplateRepository. Since this repo inherits the base trackable entity repo it runs update from the base class.
public override T Update(T entity)
{
return Update(entity, false);
}
public override T Update(T entity, bool attachOnly)
{
InsertTeData(ref entity);
entity.ModifiedById = CurrentUserId;
entity.DateModified = DateTime.Now;
_teDB.Attach(entity);
_db.SetModified(entity);
if (!attachOnly) _db.Commit();
return entity;
}
private void InsertTeData(ref T entity)
{
if (entity == null || entity == null) return;
var dbEntity = GetById(entity.Id);
if (dbEntity == null) return;
_db.Detach(dbEntity);
entity.CreatedById = dbEntity.CreatedById;
entity.DateCreated = dbEntity.DateCreated;
entity.ModifiedById = dbEntity.ModifiedById;
entity.DateModified = dbEntity.DateModified;
}
The SetModified method in by DbContext just sets the EntityState to Modified. I use a Fake DbContext and DbSet in my unit tests so any EF specific calls I extend through the DbContext to allow my tests to work without having to create a bunch of Fake Repositories.
Turns out the issue was in the InsertTeData Method. When it was detaching the entity that I pulled from the db to make sure createdby and created date it caused the entity I was working with to lose all information about the many to many relationships. My guessing is the way the entity tracking works and they both had the same key.
I've removed the InsertTeData method and now manage everything as default values in the constructor of the abstract TrackableEntity class and everything is working now.
public override T Update(T entity, bool attachOnly)
{
entity.ModifiedById = CurrentUserId;
entity.DateModified = DateTime.Now;
_teDB.Attach(entity);
_db.SetModified(entity);
if (!attachOnly) _db.Commit();
return entity;
}
After running all my unit tests, integration tests, and some manual tests, everything passed so I am fine with this change.

Resources