One of recurring questions in MVC is how to share data between views and master. The question must be reformulated : how to share data between ACTION and master.
The short answer is : Model of the View returned from Action have to put some data to the Model of the Master
The long answer here in 4 steps
Step 1: ensuring error.aspx page works fine
a)copy \Views\Shared\Site.Master into siteerror.master( the error.aspx inherits from a specialized model)
public class ModelMaster
{
public ModelMaster()
{
DataFromAction = "default data";
}
public string DataFromAction { get; set; }
}
and display this data to the html of \Views\Shared\Site.Master
<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage<MasterDataFromAction.Models.ModelMaster>" %>
//code
<h1>This is data shared from View : <%= Model.DataFromAction%></h1>
Step 3. Make the action return a strongly typed view. We will make , for example, the Index action from Home controller.
Add the ViewModelIndex class
public class ViewModelIndex :ModelMaster
{
public ViewModelIndex()
: base()
{
base.DataFromAction = "data from index";
}
}
and modify controller action and view
First Index action in \Controllers\HomeController.cs
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
ViewModelIndex vmi = new ViewModelIndex();
vmi.DataFromAction = "here comes data from index action";
return View(vmi);
}
There are some moments when you want to fast edit a property ( like a status or a name) and you do not want to load the entire “Edit” form for this.More, you are in an edit formula and do not want to add a form.
So here is the solution in ASP.NET MVC with jquery-1.4.2.min , jquery-ui-1.8.1.custom.min for a modal dialog and some controllers. The most difficult were the javascript, so I let to the final.
We will be editing the name for an employee in 4 steps.The most difficult to understand is the step 4(javascript) , however, it can be re-used with any other object- so you can put it in a common js file.
Step 1
create a view for editing the name , named FastEditEmp
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<BusinessLogic.Employee>" %>
<!-- edit inline the name -->
<%= Html.HiddenFor(Model=>Model.ID) %>
<%= Html.LabelFor(model=>model.Name) %>
<%= Html.EditorFor(model=>model.Name) %>
Step 2
Create the controllers and verify it works – at least the get action .
public ActionResult FastEditEmp(int id)
{
var emp=EmployeeList.LoadFromID(id);
return View(emp);
}
[HttpPost]
public ActionResult FastEditEmp(BusinessLogic.Employee emp)
{
try
{
var dbid = EmployeeList.LoadFromID(emp.ID);
UpdateModel(dbid);
dbid.Save();
return new JsonResult() { Data = new { ok = true, message = "succes" } };
}
catch (Exception ex)
{
//TODO : log
return new JsonResult() { Data = new { ok = false, message = "error : " + ex.Message } };
}
}
Step 3
Create the hyperlink to edit the name of an employee (emp) :
This is the most difficult one, the javascript. However, more of the code is common no matter what you want to edit fast modal :
var wait = '<%= ResolveUrl("~/Content/Images/wait.gif") %>';
function EditModal(empID,url)
{
// load a please wait dialog
var $dialog1 = $('<div></div>')
.html("Loading data <br /> <img src='" + wait + "'></img>")
.dialog({ autoOpen: false,
title: 'please wait'
});
$dialog1.dialog('open');
// so the please wait dialog was shown to the user. Now load the content of the action specified in url
$.get(url,
{
ID: empID
},
function(txt) {
var $dialog = $('#dialog');
$dialog.html('');
$dialog.html(txt)
.dialog({
autoOpen: false,
title: 'Edit',
modal: true,
show: 'blind',
hide: 'explode',
closeOnEscape: false,
buttons: {
"Close": function() {
//just cleanup - no saving
var allInputs = $(":input:not(:button)");
var ser = allInputs.serialize();
allInputs.remove();
allInputs.empty();
$(this).dialog("close");
$(this).dialog('destroy');
},
"Save": function() {
//now saving data : serializing and posting
var allInputs = $(":input:not(:button)");
var ser = allInputs.serialize();
allInputs.remove();
allInputs.empty();
window.alert(ser); -- debug mode
$(this).dialog('close');
$(this).dialog('destroy');
//saving data by posting to same url!
$.post(url,
ser,
function(text) {
$(this).dialog("close");
$(this).dialog('destroy');
if (text.ok) {
window.alert("Saved - you can change here the display id with jquery");
window.location.reload(true);
}
else {
window.alert(text.message);
}
}
);
}
}
});
$dialog1.dialog('close');//closing the wait dialog
$dialog.dialog('open'); // show main editing dialog
});
}
</script>
As always, please find here the saving modal files. Please execute first the emp.sql file, then modify the connection string into the web.config .
One of the biggest challenges in programming was write once- GUI everywhere ( Ok, ORM impedance mismatch is another story)
I mean by that re-using the logic from an application in another application. ASP.NET MVC , with the commitment to strongly viewmodels, make me think that it will be now easier to transfer the viewmodels to an console application.
Let’s see the usual Employee-Department and creation.
First the Database :
Then the ViewModel for creating an employeeand for list of employees
public class ViewModelEmployee
{
public static DepartmentList ListOfDepartments
{
get
{
//TODO : put this into cache to not load every time
DepartmentList dl = new DepartmentList();
dl.Load();
return dl;
}
}
}
public class ViewModelEmployeeCreate : ViewModelEmployee
{
public Employee emp = new Employee();
public static void SaveNew(Employee emp)
{
emp.SaveNew();
}
}
public class ViewModelEmployeeList : ViewModelEmployee
{
public EmployeeList employees;
public void Load()
{
employees = new EmployeeList();
employees.Load();
}
}
Console.WriteLine("choose department");
var listdep=ViewModelEmployee.ListOfDepartments;
foreach (var item in listdep)
{
Console.WriteLine(item.ID + ")" + item.Name);
}
string s=Console.ReadLine();
long DepartmentID;
if (!long.TryParse(s, out DepartmentID))
{
Console.WriteLine("exit : not a long :" + s);
return;
}
if (!listdep.Exists(item => item.ID == DepartmentID))
{
Console.WriteLine("not a valid id:" + s);
return;
}
Employee emp = new Employee();
emp.Department = new Department() { ID = DepartmentID };
Console.Write("employee name ?");
emp.Name= Console.ReadLine();
ViewModelEmployeeCreate.SaveNew(emp);
Now for listing employees:
ASP.NET MVC
DOS
public ActionResult Index()
{
ViewModelEmployeeList vmel = new ViewModelEmployeeList();
vmel.Load();
return View(vmel);
}
ViewModelEmployeeList vmel = new ViewModelEmployeeList();
vmel.Load();
foreach (var item in vmel.employees)
{
Console.WriteLine(item.Name);
}
As you can see , the codes are really similar ( although the console application is filled with first verification and the MVC is not )
Usually the data of the tables should be tracking for who modified it.
Think about inserting/updating/deleting an employee : you must know who did those actions and when. So you create another table, identically as structure, and you add another 3 fields , such as [ModifiedDate](when), [ModifiedBy](who), [ModifiedType] (what : insert, update, delete).
There are several methods to do it :
from database :
you can use triggers and insert data into new table
from programming code – every time you modify an object, you remember to modify the history object with appropiate data.
The drawback with the database approach is that you can not retrieve who done the modifications ( usually the applications connect under a single account and have a roles table)
The drawback with the programming approach is that the programmer must REMEMBER doing so…If he does not(and does not wrote tests for history), you are stuck…
In the following I propose an automatically history – that maps convention over configuration in my template, but it is easy for you to modify.
The solution works with Entity Framework 4.0 and, for more easily spearation of concerns , with POCO generators.
Let’s say you have the following tables :
As you see we have a Employee and a employee_history, an Department and Department_history
The conventions are:
the history table name = “object” table name + “_history” suffix
the history table fields = “object” table name fields +[ModifiedDate], [ModifiedBy], [ModifiedType]
(if you change those conventions , please change the modelhistory.tt file)
If you want to see in action , please download code history and do the following
1. create database tests
2. run history.sql
3. run project
4. if necessay, re-create the model1.edmx with the same name and replace the console application app.config with the new connection string
After works, please add any fields to department table and to department_history table(same field names/type) . Re-compile the application and modify the new field in department. You will see the modifications in the department_history table.
Ok,now how we do the magic :
We create two new tt file that points to the model.edmx .
The first one ModelHistory.tt , takes care of creating the constructor for history entities by taking a parameter from the original entity :
public Department_History(Department original):this()
{
this.IDDepartment=original.IDDepartment;
this.Name=original.Name;
}
How it do this magic ? Simple : the ModelHistory.tt recognize the model and history in the name of tables:
</pre>
string inputFile = @"Model1.edmx";
string History = "_History";
<pre>
then it generate code for constructor :
#>
public <#=code.Escape(entity)#>():base()
{
}
public <#=code.Escape(entity)#>(<#=NameEntityOriginal #> original):this()
{
<#
foreach (EdmProperty edmProperty in entityOriginal.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == entityOriginal))
{
#>
this.<#= code.Escape(edmProperty.Name) #>=original.<#= code.Escape(edmProperty.Name) #>;
<#
}
#>
}
<#
</pre>
Ok, and then how to create the history entity ? I wish that the POCO template has had an event “Database saving” – but the only thing I can have is SaveChanges from the ObjectContext – so I create a new ObjectContext , derived from the default one that comes with the project, and creates a new history object :
public override int SaveChanges(SaveOptions options)
{
this.DetectChanges();
DateTime dtModified=DateTime.Now;
string UserModified=clsUser.UserName;
foreach (ObjectStateEntry ose in this.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified))
{
//could do this way too
//if (ose.Entity != null && ose.Entity.GetType() == typeof(...))
//{
//}
if (ose.Entity != null)
{
string NameType=ose.EntitySet.ElementType.Name;
switch(NameType)
{
case "Department":
var itemDepartment_History = new Department_History(ose.Entity as Department);
//if compile error here, that means you keep tracking
//of which modified with another properties
//please modify the tt accordingly
itemDepartment_History.ModifiedType= ose.State.ToString();
itemDepartment_History.ModifiedDate= dtModified;
itemDepartment_History.ModifiedBy= UserModified;
base.Department_History.AddObject(itemDepartment_History);
break;
case "Employee":
var itemEmployee_History = new Employee_History(ose.Entity as Employee);
//if compile error here, that means you keep tracking
//of which modified with another properties
//please modify the tt accordingly
itemEmployee_History.ModifiedType= ose.State.ToString();
itemEmployee_History.ModifiedDate= dtModified;
itemEmployee_History.ModifiedBy= UserModified;
base.Employee_History.AddObject(itemEmployee_History);
break;
}
}
}
return base.SaveChanges(options);
}
Now all is ready and I made a console application for testing manually (ok, should make a NUnit / MSTest / xUnit )
using (var ctx = new testsEntitiesHistory())
{
var dep = new Department();
dep.Name = "IT";
ctx.Departments.AddObject(dep);
ctx.SaveChanges();
id = dep.IDDepartment;
}
using (var ctx = new testsEntitiesHistory())
{
var dep = ctx.Departments.Where(depart => depart.IDDepartment == id).FirstOrDefault();
dep.Name = "Information tehnology";
ctx.SaveChanges();
//
}
using (var ctx = new testsEntitiesHistory())
{
var dep = ctx.Departments.Where(depart => depart.IDDepartment == id).FirstOrDefault();
ctx.Departments.DeleteObject(dep);
ctx.SaveChanges();
}
using (var ctx = new testsEntitiesHistory())
{
foreach (var dephist in ctx.Department_History)
{
Console.WriteLine("Found {0} with state {1}", dephist.Name,dephist.ModifiedType);
}
}
And the output is :
Now you can add more tables to the edmx or change the fields – all is done automatically when compiling
If you want to see in action , please download code history
For all programmer, just remember :
http://timstall.dotnetdevelopersjournal.com/coding_is_just_the_tip_of_the_iceberg.htm
And he says nothing about clients, marketing, sales, promotion, SEO, SMM, and other usual stuff…
I have had the task to made something to monitor what stored procedure, in Sql Server,in most used. The problem that I have had is that the host_name is easily changed by putting, in the connection string WSID (see http://msdn.microsoft.com/en-us/library/ms178598.aspx )
The code is for sql server 2005 and I have not realized without the help of http://sqlserver.ro/forums/thread/8332.aspx
So there are the three easy steps to have the monitor proceudre in place: 1. Define an audit table :
CREATE TABLE [Audit](
[ID] [int] IDENTITY(1,1) NOT NULL,
[ProcName] [varchar](128)
[IdentifierAccess] [varchar](max)
[DateAccess] [datetime] NULL DEFAULT (getdate())
) ON [PRIMARY]
2. Stored proc to insert data :
alter proc dbo.proc_Log(@procname varchar(100))
as
begin
declare @userid varchar(100)
BEGIN TRY
select @userid=client_net_address from sys.[dm_exec_connections] with(nolock)
where session_id = @@SPID
End TRY
BEGIN CATCH
--GRANT SELECT ON sys.dm_exec_connections TO the user or
--grant view server state to the user ! http://msdn.microsoft.com/en-us/library/ms186717.aspx
set @userid = SUSER_NAME()
End CATCH
INSERT INTO [Audit]
(
[ProcName]
,[IdentifierAccess]
)
VALUES
(@procname,
@userid
)
end
3. In each stored procedure put this at beginning
DECLARE @ProcName nvarchar(128)
SET @ProcName = OBJECT_NAME(@@PROCID)
exec proc_Log @ProcName
And this at the end of stored proc :
exec proc_Log @ProcName
4. See what the procedure calling are and the time (basic)
select a1.*,'--',a2.* , datediff(ss,a2.DateAccess,a1.DateAccess)
from [Audit] a1 inner join [Audit] a2 on
a2.ID =
( select max(ID) from [Audit] a2 where a1.ProcName= a2.ProcName and a1.IdentifierAcces=a2.IdentifierAcces and a1.ID > a2.ID )
order by 1
From there is a simple way to make a group by to have the sum * count or other relevant data ( which proceudres are most used in what times of the day, and so on)
Work from home :
1. From the previous select(put it into a Common table Expression ), see what procedure use most
a) number of uses
b) number of time
2. What is missing from Audit table definition ? (Hint :easy clustered index definition)
Let’s say you have the following data for an car evidence :
Date, StartMiles, Destination, EndMiles
And the data comes from an Excel that has those columns. The requirements is the user can copy/paste from Excel data
When they copy the data comes in this form
01/01/2010 1000 Washington 1550
02/01/2010 1550 Dallas 2550
and so on.
It is clear that you :
have a class with Date, StartMiles, Destination, EndMiles
accomodate for space – but how to perform various data separator ( we suppose that first is the day, then comes the month).
Now the code in logical steps :
1. Accomodate for dates :
internal class ConvertDate : ConverterBase
{
/// <summary>
/// different forms for date separator : . or / or space
/// </summary>
/// <param name="from">the string format of date - first the day</param>
/// <returns></returns>
public override object StringToField(string from)
{
DateTime dt;
if (DateTime.TryParseExact(from, "dd.MM.yyyy", null, DateTimeStyles.None, out dt))
return dt;
if (DateTime.TryParseExact(from, "dd/MM/yyyy", null, DateTimeStyles.None, out dt))
return dt;
if (DateTime.TryParseExact(from, "dd MM yyyy", null, DateTimeStyles.None, out dt))
return dt;
throw new ArgumentException("can not make a date from " + from, "from");
}
}
2. Create the class that will hold one record:
[IgnoreEmptyLines(true)]
[DelimitedRecord(",")]
internal class DestinationReader
{
//[FieldConverter(ConverterKind.Date,"dd.MM.yyyy")]
[FieldConverter(typeof(ConvertDate))]
public DateTime Date;
[FieldConverter(ConverterKind.Int32)]
public int StartMiles;
[FieldQuoted(QuoteMode.OptionalForBoth)]
public string Destination;
[FieldConverter(ConverterKind.Int32)]
public int EndMiles;
}
3. Now read the entire string:
string Text = text that comes from the user
string TextDelim = Text.Substring(10, 1);// the date has 10 chars - so the eleven is the separator
while (Text.IndexOf(TextDelim + TextDelim) > 0)//consolidate for 2 delimiters
{
Text = Text.Replace(TextDelim + TextDelim, TextDelim);
}
DelimitedFileEngine<DestinationReader> flh=new DelimitedFileEngine<DestinationReader>();
flh.Options.Delimiter = TextDelim;
var data =flh.ReadString(Text);
In data you have a list of DestinationReader
So for any structured import of data use FileHelpers from http://www.filehelpers.com/