dotnet / dotnet/Scaffolding

OOBE needs a little work.

Open
#877 3 comments 0 reactions 1 assignee Claimed by @danroth27 View on GitHub
area-scaffolding
Dominant language
C#
Stars
818
Forks
260
Avg merge
1d 8h
Merged PRs (30d)
10

Description

In this article :
https://docs.microsoft.com/en-us/aspnet/core/data/ef-rp/crud?view=aspnetcore-2.1 : Create, Read, Update and Delete operations (tutorial 2 of 8)
Note: While this link relates to Razor Pages, the same applies to the MVC implementation.

There are a number of 'fixes' that need to be done, in order for the scaffolded objects to run properly.
By default, the ONLY page that seems to operate out-of-the-box is the Index page.
All the other pages (if they compile without errors) just stare at the user like they are swiss cheese.

Suggested changes are as per the article.

1. Each of CRUD.cshtml pages (except Index.cshtml) needs a
`"{id:int}" `
statement after the @page statement. (Change 'id' to be whatever the Primary Key of the table is.)

2 The OnPostAsync method of the Create page needs to change from

```
public async Task OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}

_context.Student.Add(Student);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}
```
to
```
public async Task OnPostAsync()
{
if (!ModelState.IsValid) //No change
{ //No change
return Page(); //No change
} //No change

var emptyStudent = new Student();

if (await TryUpdateModelAsync(
emptyStudent,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
//List each of the fields in the table (except 'id', [or PK] and possibly timestamps.) in the above line
{
_context.Student.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

return null;
}
```

Since I have over 150 tables to process, I have attempted to write my own conversion script in a Console App.
As you will no doubt see, I am a novice at coding (this took me 4 days to get it working) so please forgive the bad form. (I have left my comments and added a few more to help you understand my logic.)

```
using System;
using System.IO;

namespace ScriptFixer
{
class Program
{
public class FixFiles
{
static void Main()
{
//Default parameters - root path to project folder
//ToDo : make this path dynamic. Select the folder from a dialogue box.
var rootPath = @"C:\Users\Wisdom\Documents\Visual Studio 2017\Projects\";

//Get Parameters
Console.WriteLine("Please type the PROJECT name");
string projName = Console.ReadLine();

//I will need to group my pages (to keep my sanity) so I am using AREAS within the PAGES folder.
//The tables in my database use this naming convention : 'AaaTablename'. (Where 'Aaa' is the AREA)
Console.WriteLine("Please type the FOLDER name of the AREA");
string areaName = Console.ReadLine();

//This is a folder for each Razor PAGE: Ie: My Table name
Console.WriteLine("Please type the SUB FOLDER (Table) name of the AREA");
string fldrName = Console.ReadLine();

//The scaffold tool has a nasty habit of trying to pluralize the names of my tables
//It does change 'bank' to 'banks',
//but not 'community' to communities'
//or 'xType' to 'xTypes' and
//neither does it change 'xCode' to 'xCodes' (where 'x' can be any string)
//So, to be sure that it uses the correct table name, as per the projCONTEXT....
Console.WriteLine("Please type the name of the TABLE");
string tblName = Console.ReadLine();

//ToDo : Give me the option to exit here.

DirectoryInfo rootDir = new DirectoryInfo(rootPath);

//# Region : This is the process to update the CREATEcshtml.cs
//ToDo : write process for the EDIT and the DELETE pages

//Create Source and Destination paths.
//I havent figured out how to edit the existing file, so I am creating a
//new file which will be copied over the old one.
string srcFile = rootPath + projName + @"\" + projName + @"\Pages" + @"\"+ areaName + @"\" + fldrName +@"\CREATE.cshtml.cs";
string dstFile = @"C:\Temp" + @"\" + @"\CREATE.cshtml.cs";

//Debug feedback
Console.WriteLine("srcFile = ");
Console.WriteLine(srcFile);
Console.WriteLine();
Console.WriteLine("dstFile = ");
Console.WriteLine(dstFile);

//Check if file exists......both SOURCE and Destination
if (!(File.Exists(srcFile)))
{
Console.WriteLine("The SOURCE file does not exist. UNABLE to continue!");
}

if (!(File.Exists(dstFile)))
{
using (FileStream fs = System.IO.File.Create(dstFile))
{
Console.WriteLine("Creating the file");
Byte[] info = new System.Text.UTF8Encoding(true).GetBytes("");
// Add some information to the file.
fs.Write(info, 0, info.Length);
}
}

//Process the Document
var sw = new System.IO.StreamWriter(dstFile) ;
StreamReader sr = File.OpenText(srcFile);
string ReplaceStr1 = "_context." + areaName + tblName + ".Add(" + areaName + tblName + ");";
string line;
using (sr)
{
using (sw)
{
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine("line - : "+line);
Console.WriteLine("Repl str = :"+ ReplaceStr1);
if (line.ToLower().Contains(ReplaceStr1.ToLower()))
{
sw.WriteLine("var empty" + fldrName + " = new " + areaName+tblName + "();");
sw.WriteLine();
sw.WriteLine("if (await TryUpdateModelAsync<" +areaName+ tblName + ">(");
sw.WriteLine("empty" + fldrName + ",");
sw.WriteLine("\"" + tblName + "\",");
//I dont know how to cycle through each field yet, so these are placeholders
sw.WriteLine("z=>z.f1, z=>z.f2,z=>z.f3, z=>z.Obsolete))");
sw.WriteLine("{");
sw.WriteLine("_context." + areaName + tblName + ".Add(empty" + fldrName + ");");
sw.WriteLine("return RedirectToPage(\"./ Index\");");
sw.WriteLine("}");
}
else
{
int len = line.Length;
if (len > 14)
{
string x = line.Substring(15);
if (x == "Return Redirect")
{
//Dont do anything
}
else
{
sw.WriteLine(line);
}
}
else
{
sw.WriteLine(line);
}
}
}
}
}
//#endregion
Console.WriteLine("Press any key to end");
Console.ReadLine();
}
//ToDo : Move the updated files to the SOURCE folder.
}
}
}
```

3. Changes required for the EDIT page.
OnGetAsync method
`Student = await _context.Student.FirstOrDefaultAsync(m => m.StudentId == id);`
needs to change to
`Student = await _context.Student.FindAsync(id);`

and the OnPostAsync method needs to change from
``` public async Task OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}

_context.Attach(Student).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!StudentExists(Student.StudentId))
{
return NotFound();
}
else
{
throw;
}
}

return RedirectToPage("./Index");
}
```

to become
```
public async Task OnPostAsync(int? id)
{
if (!ModelState.IsValid)
{
return Page();
}

var studentToUpdate = await _context.Student.FindAsync(id);

if (await TryUpdateModelAsync(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
//Script should list all the fields in the table (except the PK and hidden/timestamps)
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

return Page();
}
```


4. Changed required for the DELETE page.
Update the existing OnGetAsync method as indicated below.

```
//public async Task OnGetAsync(int? id) becomes
public async Task OnGetAsync(int? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}

Student = await _context.Student
.AsNoTracking() //Added
.FirstOrDefaultAsync(m => m.ID == id);

if (Student == null)
{
return NotFound();
}

//Add the following
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = "Delete failed. Try again";
}

return Page();
}
```

Replace the entire OnPostAsync method within
```
public async Task OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}

var student = await _context.Student
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

if (student == null)
{
return NotFound();
}

try
{
_context.Student.Remove(student);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("./Delete",
new { id, saveChangesError = true });
}
}
```

Update the DeleteModel class to inlude an ErrorMessage
`public string ErrorMessage { get; set; }`

Update the Delete Razor Page to include the ErrorMessage
`

@Model.ErrorMessage

`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.