Build .NET | Build JS | Nuget | Npm |
---|---|---|---|
EasyData library lets you quickly build a UI for CRUD (Create, Read, Update, Delete) operations in any ASP.NET Core application that uses Entity Framework Core.
Basically, EasyData does the following two things:
-
First, it “reads” your DbContext object in order to obtain the necessary metadata.
-
Then, based on that metadata, it provides an API endpoint for all CRUD operations and renders the UI (pages and dialogs) that communicate with the API endpoint for data-management tasks.
The real advantage here is that whenever you change something in your DbContext (add a new DbSet or a property in the model class), the UI automatically adjusts to those changes.
So, as you can see, EasyData can be very useful for quick prototyping of any database-related ASP.NET Core project. In just minutes you'll have a working web app with all CRUD forms for your database.
First of all, to test EasyData you can open and run one of the sample projects available in this repository.
Installing EasyData to your own project takes the following 3 simple steps:
- EasyData.AspNetCore
- EasyData.EntityFrameworkCore.Relational
Here is an example for .NET 6 app with "Program.cs based" approach:
//Program.cs file
using EasyData.Services;
. . . . .
var app = builder.Build();
. . . . .
app.MapEasyData(options => {
options.UseDbContext<AppDbContext>();
});
endpoints.MapRazorPages();
And here is an example for the old "Startup.cs based" way:
//Startup.cs file
using EasyData.Services;
. . . . .
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
. . . .
app.UseEndpoints(endpoints =>
{
endpoints.MapEasyData(options => {
options.UseDbContext<AppDbContext>();
});
endpoints.MapRazorPages();
});
}
}
In the middleware options we specify the type of the DbContext object that will be used as the source of the metadata.
If you're using Razor Pages, add a new page (for example EasyData.chstml
). If it’s MVC, you'll need a controller and a view.
This page will "catch" all URLs that begin with a certain prefix (/easydata
by default but it's configurable). So, we use a special catch-all parameter in the route definition ("/easydata/{**entity}"
).
We also add EasyData styles and the script file (easydata.min.js
), which renders the data-management UI and handles all CRUD operations on the client-side.
@page "/easydata/{**entity}"
@{
ViewData["Title"] = "EasyData";
}
<link rel="stylesheet" href="https://cdn.korzh.com/ed/1.4.8/easydata.min.css" />
<div id="EasyDataContainer"></div>
@section Scripts {
<script src="https://cdn.korzh.com/ed/1.4.8/easydata.min.js" type="text/javascript"></script>
<script>
window.addEventListener('load', function () {
new easydata.crud.EasyDataViewDispatcher().run()
});
</script>
}
That’s it. Now you can run your web app, open the /easydata
URL and enjoy CRUD functionality.
All aspects of your CRUD UI are controlled by the data structure defined in DbContext. If you need to tune it up (for example, to hide a table or some fields, or, perhaps, to change their names), there are special attributes for your model classes and properties with which to do that.
All data forms and dialogs are rendered automatically by EasyData.JS script according to the metadata acquired from the DbContext and your annotations on model classes and their properties.
The script can be used with any framework or library used on the client-side, such as Razor Pages, MVC Views, Angular, React, Vue, etc.
In the data view mode, EasyData provides a data-filtering functionality, which works out of the box and requires no additional setup or coding.
Sometimes you may need to make some adjustments to the default behavior of EasyData. Here we listed the solutions for the most common problems.
By default, EasyData works with all entities (tables) defined in your DbContext. As well as with all fields in those tables.
However, very often you need to hide some tables or fields from your end-users.
You can do it using MetaEntity
and MetaEntityAttr
annotations that can be specified for model classes and properties correspondingly.
Here is an example of how to hide the table defined by the Customer
model class:
[MetaEntity(false)]
public class Customer
{
. . . .
}
The same approach (but with MetaEntityAttr
attribute now) we can use to hide some property (field):
public class User
{
[MetaEntityAttr(false)]
public string PasswordHash { get; set; }
}
Both these annotations also have other properties that allow you to adjust the way your tables and fields are represented in the CRUD UI.
For example, the following line:
[MetaEntity(DisplayName = "Client", DisplayNamePlural = "Clients", Description = "List of clients")]
public class Customer
{
. . . .
}
will set the display names and the description for the Customers
table.
Here is another example, now for a property in some model class:
public class BlogPost
{
[MetaEntityAttr(DisplayName = "Created", Editable = false, ShowOnView = true, ShowInLookup = false)]
public DateTime DateCreated { get; set;}
. . . . .
}
Here we change the default display name for this field, make it non-editable (since this value is set on record creation and it can’t be changed later), and tell EasyData to show this field in the main view (with data table) but hide it in lookup dialogs.
MetaEntityAttr
annotation also has ShowOnCreate
and ShowOnEdit
properties that allow you to show/hide the field from the "Create Record" or "Edit Record" dialog, respectively.
Annotations on the model classes and properties can make your core code dependant on implementation. It's not a good approach especially considering Clean Architecture principles. To avoid this situation you can use our Fluent API to establish filters and to set different parameters of entities and their properties.
app.UseEndpoints(endpoints => {
endpoints.MapEasyData(options => {
options.UseDbContext<ApplicationDbContext>(opts => {
//here we tell the model loader to skip the Supplier entity completely
opts.Skip<Supplier>();
//the following command will skip some properties in the Customer entity
opts.Skip<Customer>(c => Phone, c => PostCode, c => Fax);
//here we define the procedure that customizes our metadata
//when it's loaded from a DbContext
opts.CustomizeModel(model => {
//setting the names of the Customer entity
var entity = model.Entity<Customer>()
.SetDisplayName("Client")
.SetDisplayNamePlural("Clients");
//hiding Customer.Region and Customer.Address in the View mode
entity
.Attribute(c => c.Region)
.SetShowOnView(false);
entity
.Attribute(c => c.Address)
.SetShowOnView(false);
//chaning the caption and the description of the Customer.Country
entity
.Attribute(c => c.Country)
.SetDisplayName("Country name")
.SetDescription("Country where the client lives");
//setting the display format of the Order.OrderDate
model.Entity<Order>()
.Attribute(o => o.OrderDate)
.SetDisplayFormat("{0:yyyy-MM-dd}");
});
});
});
});
The default EasyData API endpoint is /api/easydata/
but it’s very easy to change it to any possible path.
Server-side configuration:
app.UseEndpoints(endpoints => {
endpoints.MapEasyData(options => {
options.Endpoint = "/api/super-easy-crud";
options.UseDbContext<ApplicationDbContext>();
});
. . . . .
});
On the client-side we can pass some options (including the endpoint) to the dispatcher’s constructor:
<script>
window.addEventListener('load', function () {
new easydata.crud.EasyDataViewDispatcher({
endpoint: '/api/super-easy-crud'
}).run()
});
</script>
Sometimes you don't need CRUD for all entities in your database but only for a few of them (or even only one). So, you don't need to show that root page with entity selection. You just can start with a data table view for one particular entity.
Especially for this case, there is rootEntity
option in EasyDataViewDispatcher
class. If it's set, EasyData will no show the default root page with the list of entities but will render the view page for the specified entity (table) instead.
<script>
window.addEventListener('load', function () {
new easydata.crud.EasyDataViewDispatcher({
rootEntity: 'Order'
}).run()
});
</script>
It’s also possible to set the display format for each entity attribute (table field). The format looks like {0:XX}
where XX
here is a format string that has the same meaning as in string.Format function.
Beloew you will find a few examples of using display formats.
This one tells EasyData to use the default "Long Date" format for OrderDate values:
[MetaEntityAttr(DisplayFormat = "{0:D}")]
public DateTime? OrderDate { get; set; }
Here we use a custom format for date values:
[MetaEntityAttr(DisplayFormat = "{0:yyyy-MMM-dd}")]
public DateTime? OrderDate { get; set; }
Here we make it to use a currency format with 2 decimal digits
[MetaEntityAttr(DisplayFormat = "{0:C2}")]
public decimal Freight { get; set; }
With this format EasyData will show only digits (no grouping by thousands) and will add leading 0-s up to 8 digits totally (if necessary)
[MetaEntityAttr(DisplayFormat = "{0:D8}")]
public int Amount { get; set; }
Q: What versions of .NET and ASP.NET (Core) does EasyData support?
A: Currently, EasyData supports .NET Core 3.1 and .NET 5 and, obviously, all versions of ASP.NET Core and Entity Framework Core that can work with these versions of .NET (Core). It’s not a great deal to add support for previous versions of .NET Core or even .NET Framework 4.x. If you really need it, please create a GitHub issue about that.
Q: Is it possible to use EasyData API only without the UI?
A: Yes, of course. You can follow the issue about this request to get an idea of the possible endpoints and the formats of the requests and responses. We are going to publish a complete API documentation after version 1.4.0 release.
If you want to be the first to know about project updates, just follow Sergiy on Twitter.
If you have a feature request or found a bug in EasyData, please submit an issue. We will try to fix the problem or implement new functionality (if the request is relevant) as soon as possible. However, please don't be too demanding with your requests :). Remember that EasyData is an open-source project, and we maintain it in our spare time.