Share This!

Friday, June 3, 2016

MVC - JQuery Prompt User to Save Modified Page.

I had users clicking off my page while doing data entry, and I needed to make sure they didn't forget to save their modifications.

 

Here is a simple example HTML page that implements a rudimentary check.  This is not tested for all data types.   Enjoy.

 

 

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title> Safe Exit Test </title>

    <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>

</head>

 

<body>

    <form action="">

        <input type="number" name="value" />

        <input type="submit" name="commit" />

    </form>

 

    <a href="http://209software.com">209software</a>

 

    <script>

        $(document).ready(function () {

            formmodified = 0;

            $('form *').change(function () {

                formmodified = 1;

            });

            window.onbeforeunload = confirmExit;

            function confirmExit() {

                if (formmodified == 1) {

                    return "New information not saved. Do you wish to leave the page?";

                }

            }

            $("input[name='commit']").click(function () {

                formmodified = 0;

            });

        });

    </script>

</body>

</html>

 

Thursday, April 21, 2016

MVC: Comparing old values to postback

I wanted to compare a couple values to their previous values on postback (Edit) to do some special processing.


So rather than put hidden fields on the page with the old values, why not just load the record on postback and do my checking there?  Easy peasy!


Assignment oldAssignment = db.Assignments.Find(assignment.UniqueID);

... then I can make my comparisons:
bool statusBarChg = (CompanyStatusBarSelectList != oldAssignment.StatusBarID);
bool dspChg = (assignment.DSPID != oldAssignment.DSPID);

The only problem is that when I get to my Save(), I get this:
Attaching an entity of type [MODEL] failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

Apparently, Entity Framework thought that my oldAssignment was blocking the assignment object from the postback.  The solution is simple: change this:

Assignment oldAssignment = db.Assignments.Find(assignment.UniqueID);

…to this:
Assignment oldAssignment = db.Assignments.AsNoTracking().Where(d => d.UniqueID == assignment.UniqueID).FirstOrDefault();

The .AsNoTracking() makes it so the oldAssignment loads the old values without telling Entity Framework that it owns the record in the table.

Tuesday, March 1, 2016

MVC: Splitting a list without splitting the dataset

Today I needed to split a list of companies into Companies and Relocation Companies.  The data is in the same table and model, but presentation rules for this project require 2 lists.

SO:

Taking a single dataset from the controller:

        // GET: Companies
        public ActionResult Index()
        {
            return View(db.Companies.ToList());
        }

I took this bit of view/index code from the auto-generated “Code First From Database” tool…

<h3>Companies</h3>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.CompanyName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.isActive)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.isReloCompany)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.ChangedBy)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.LastChangedDate)
        </th>
        <th></th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.CompanyName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.isActive)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.isReloCompany)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ChangedBy)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.LastChangedDate)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.UniqueId }) |
                @Html.ActionLink("Details", "Details", new { id = item.UniqueId })
            </td>
        </tr>
    }

</table>

And I made 2 copies (in my case I will only ever need 2 lists).  I changed the highlighted line in the above to…

    @foreach (var item in Model.Where(d => d.isReloCompany == false).OrderBy(d=>d.CompanyName))

Note that I can add LINQ into the view easily and I did not have to split my list beforehand in the controller.  The second copy of this line has .isReloCompany==true.






Wednesday, February 24, 2016

Data Refactoring made easy

Regenerate Entity Framework models based on existing database in Visual Studio 2013
Sometimes you need to refactor your database as your project progresses, and manually editing the Entity Framework classes may start to fail.  In my case it sent me on a 2 day spiral into brain aneurysms that were - is it turns out - completely unnecessary.

The Errors I was getting?


System.InvalidOperationException: The ForeignKeyAttribute on property 'DSPID' on type 'SA.DS._0._2.Models.Assignments' is not valid. The navigation property 'DSPs_UniqueID' was not found on the dependent type 'SA.DS._0._2.Models.Assignments'. The Name value should be a valid navigation property name.
How to Regenerate your Entity Framework Model classes quickly and correctly, without going to Redmond for 3 weeks to attend a $45,000 class.

First, create a NEW project.  Do NOT attempt to do this inside an ongoing project.

Note that we picked Windows Desktop, Class Library. 

Delete Class1.cs
Now right click the project (not the solution) and Add -> New Item…
Add -> Data -> ADO.NET Entity Data Model
Code First From Database
Choose Database, Save Connection, Change name to "Connection" instead of "Model", Next.
Import as many data objects as you want, or ALL of them.
Click Finish.
This will create all the table, view, and connection objects fresh.  It also includes all key and foreign key data.  You can ignore the warnings that it gives about views not having unique keys, that should really should be fixed in the database.


Now you can basically capture any items you need from the .cs classes that were just generated and either add them to, or cut/paste them into the existing project.







Friday, January 29, 2016

Data to MVC in 10 minutes.



Database

Assuming you created your table in SSMS already, It should look something like this:
Now drag select the 3 columns like and control+C copy them to the clipboard:
…and the clipboard…
ID              uniqueidentifier     Unchecked
International   bit                  Unchecked
Name            nvarchar(20)         Unchecked
Description     nvarchar(200)        Checked
BarPercent      int                  Unchecked
IncludesSteps   varchar(MAX)         Unchecked

Model

Go to Visual Studio and in your Solution Explorer, right click Models and Add, then Class
Make it easy on yourself:  Name it the same as your table.
Make the code look like this…
namespace SA.DS._0._2.Models  //this should match your other models, or leave it how Visual Studio created it
{
        using System;
        using System.Collections.Generic;
        using System.ComponentModel.DataAnnotations;
        using System.ComponentModel.DataAnnotations.Schema;
        using System.Data.Entity.Spatial;

        [Table("Lookups.StatusBarTypes")]  //This is the name of your table.  If the schema is left out, it assumes [dbo]
        public partial class StatusBarTypes
        {
                [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]

                //a default constructor
                public StatusBarTypes()
                {

                }

        }
}
Now, we'll paste in the list from the clipboard.
                //a default constructor
                public StatusBarTypes()
                {

                }


                ID      uniqueidentifier        Unchecked
International   bit     Unchecked
Name    nvarchar(20)    Unchecked
Description     nvarchar(200)   Checked
BarPercent      int     Unchecked
IncludesSteps   varchar(MAX)    Unchecked
                Unchecked
        }
}
Then doctor it into object properties.  DO NOT rename any fields!  Make sure anything that says "Checked" is nullable.
Keep tabs on the string lengths and whether the columns are required or not. (not null means Requred)
                [Key]
                public Guid ID { get; set; }

                [Required]
                public bool International { get; set; }

                [Required]
                [StringLength(20)]
                public string Name { get; set; }

                [StringLength(200)]
                public string? Description { get; set; }

                [Required]
                public int BarPercent { get; set; }

                public int IncludesSteps { get; set; }

If you want, you can add display info, like prettier column names.
                [Key]
                public Guid ID { get; set; }

                [Required]
                [Display(Name="Intl")]
                public bool International { get; set; }

                [Required]             
                [StringLength(20)]
                public string Name { get; set; }

                [StringLength(200)]
                public string Description { get; set; }

                [Required]
                [Display(Name="Bar%")]
                public int BarPercent { get; set; }

                [Display(Name="Incl Steps")]
                public int IncludesSteps { get; set; }

OK save it and BUILD.
If all went well, we can…

Controller and View

In Controllers, right click Add, then Controller
Pick MVC5 Controller with Views, Using Entity Framework
Click Add.
Pick your new Model Class.
Make sure it's your correct Database, pick a layout page, and Add.
Note that it autogenerates your controller…
…and your CRUD views.
These Views are ALL WIRED UP and ready to use.  Open Index.cshtml in Visual Studio and run it.
Your empty data table awaits!  Add some records, edit them, delete them, modify them!