Here's my old code.
public override Task ExecuteAsync(string generator, WebHookHandlerContext context)
{
DatawarehouseEntities db = new DatawarehouseEntities();
// Get JSON from WebHook
JObject data = context.GetDataOrDefault<JObject>();
var tableName = data["Table_Name"].ToString();
var columnNames = db.Database.SqlQuery<string>(String.Format("SELECT name FROM sys.columns WHERE object_id = OBJECT_ID('{0}'); ", tableName)).ToList();
var table = db.GetType().GetProperty(tableName).GetValue(db, null);
var assembly = AppDomain.CurrentDomain.GetAssemblies()
.SingleOrDefault(a => a.GetName().Name == "DSI.Data");
var type = assembly.GetTypes().FirstOrDefault(t => t.Name == tableName);
var dbset = Activator.CreateInstance(type);
//var dbset = db.Set(type);
var jsonParams = data.Properties().Select(x => x.Name).ToList();
var selectedColumnNames = columnNames.Intersect(jsonParams);
foreach (var columnName in selectedColumnNames)
{
var property = dbset.GetType().GetProperties().FirstOrDefault(x => x.Name == columnName);
property.SetValue(dbset, data[columnName].ToString(), null);
}
db.Set(type).Add(dbset);
db.SaveChanges();
return Task.FromResult(true);
}
Here's what I try to post http://localhost:port/api/webhooks/incoming/genericjson?code=secret&Table_Name=Table_Name. The type always comes back null. How can I select a table using the string that I pass in?
To make this easier, faster and less error-prone enumerate your entity types and add them to a lookup. EG
public static Dictionary<string, Type> EntityTypesByName { get; } = new Dictionary<string, Type>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
foreach (var et in modelBuilder.Model.GetEntityTypes())
{
EntityTypesByName.Add(et.Name, et.ClrType);
}
}
This is what finally worked for me.
public override Task ExecuteAsync(string generator, WebHookHandlerContext context)
{
DatawarehouseEntities db = new DatawarehouseEntities();
// Get JSON from WebHook
JObject data = context.GetDataOrDefault<JObject>();
var tableName = data["Table_Name"].ToString();
var columnNames = db.Database.SqlQuery<string>(String.Format("SELECT name FROM sys.columns WHERE object_id = OBJECT_ID('{0}'); ", tableName)).ToList();
var table = db.GetType().GetProperty(tableName).GetValue(db, null);
var assembly = AppDomain.CurrentDomain.GetAssemblies()
.SingleOrDefault(a => a.GetName().Name == "DSI.Data");
var type = assembly.GetTypes().FirstOrDefault(t => t.Name == tableName);
var dbset = Activator.CreateInstance(type);
var jsonParams = data.Properties().Select(x => x.Name).ToList();
var selectedColumnNames = columnNames.Intersect(jsonParams);
foreach (var columnName in selectedColumnNames)
{
var property = dbset.GetType().GetProperties().FirstOrDefault(x => x.Name == columnName);
property.SetValue(dbset, data[columnName].ToString(), null);
}
db.Set(type).Add(dbset);
db.SaveChanges();
return Task.FromResult(true);
}
Related
I have an action that inserts a file in a database. It works fine. But the unit test fails when the insert is successful. The RedirectToRouteResult is null. This is the code for the action:
[HttpPost]
public ActionResult FileUpload(FileUploadViewModel model)
{
try
{
if (ModelState.IsValid)
{
var uploadFile = new byte[model.File.InputStream.Length];
model.File.InputStream.Read(uploadFile, 0, uploadFile.Length);
//var file = new FilesUpload();
var fileExt = Path.GetExtension(model.File.FileName);
if (fileExt == ".txt" || fileExt == ".docx")
{
//file.FileName = model.File.FileName;
//file.FileSize = model.File.ContentLength;
//file.ContentType = fileExt;
//file.FileData = uploadFile;
//file.UploadedOn = DateTime.Now;
//var user = _userRepository.GetUserId(User.Identity.Name);
//file.UserId = user.UserId;
bool result = _userRepository.UploadFile(model, User.Identity.Name, fileExt, uploadFile);
//file.User = (from u in _context.Users where u.UserId == 29 select u).FirstOrDefault();
//_context.FilesUploads.Add(file);
//int result = _context.SaveChanges();
if (result)
{
return RedirectToAction("Index");
}
else
{
ModelState.AddModelError("", "An error occured");
return View(model);
}
}
else
{
ModelState.AddModelError("", "You can upload only files with extensions .txt and .docx");
}
}
return View(model);
}
catch (Exception ex)
{
throw(ex);
}
}
And this is the code for the unit test:
private SuperUserController _controller;
private Mock<IUserRepository> _repositoryMock;
private Mock<IIntrinsicObjects> _intrinsicMock;
private Mock<ControllerContext> _contextMock;
private string username = "test#abv.bg";
private FileUploadViewModel _fileUpload;
[SetUp]
public void Setup()
{
_fileUpload = new FileUploadViewModel();
_repositoryMock = new Mock<IUserRepository>();
_intrinsicMock = new Mock<IIntrinsicObjects>();
_contextMock = new Mock<ControllerContext>();
_contextMock.SetupGet(c => c.HttpContext.User.Identity.Name).Returns(username);
_contextMock.SetupGet(c => c.HttpContext.User.Identity.IsAuthenticated).Returns(true);
_controller = new SuperUserController(_repositoryMock.Object, _intrinsicMock.Object);
_controller.ControllerContext = _contextMock.Object;
}
[Test]
public void FileUpload_ShouldRedirectToIndexOnSuccess()
{
string filePath = Path.GetFullPath(#"C:\Users\Test\Desktop\test.txt");
var fileStream = new FileStream(filePath, FileMode.Open);
var fileExt = Path.GetExtension(filePath);
var fileToUpload = new Mock<HttpPostedFileBase>();
fileToUpload.Setup(f => f.ContentLength).Returns(10);
fileToUpload.Setup(f => f.FileName).Returns("test.txt");
fileToUpload.Setup(f => f.ContentType).Returns(fileExt);
fileToUpload.Setup(f => f.InputStream).Returns(fileStream);
//var model = new FileUploadViewModel();
_fileUpload.File = fileToUpload.Object;
var fileData = new byte[fileToUpload.Object.InputStream.Length];
_repositoryMock.Setup(m => m.UploadFile(_fileUpload, _controller.ControllerContext.HttpContext.User.Identity.Name, fileToUpload.Object.ContentType, fileData)).Returns(true);
var result = _controller.FileUpload(_fileUpload) as RedirectToRouteResult;
Assert.IsNotNull(result);
}
The problem is that UploadFile always returns false.
I will be very grateful if somebody helps me.
Your controller action creates a new array with the file data:
var uploadFile = new byte[model.File.InputStream.Length];
That is never going to match the fileData argument set on the UploadFile of your _repositoryMock:
_repositoryMock.Setup(m => m.UploadFile(_fileUpload, _controller.ControllerContext.HttpContext.User.Identity.Name, fileToUpload.Object.ContentType, fileData)).Returns(true);
By default the array will match by reference, not if the contents are the same. As the controller creates a new instance, uploadFile is a different reference than fileData.
You want to setup moq so when the expected data array is submitted your UploadFile function returns true. For that you can use an custom matcher that matches all elements in an enumerable:
public T[] MatchCollection<T>(T[] expectation)
{
//This checks all elements in input are find in destination, regardless of order or duplicates
return Match.Create<T[]>(inputCollection => (expectation.All((i) => inputCollection.Contains(i))));
//This will check the arrays are exactly the same
return Match.Create<T[]>(inputCollection => StructuralComparisons.StructuralEqualityComparer.Equals(inputCollection, expectation) );
}
And setup your mock using that matcher for the data array:
_repositoryMock.Setup(m => m.UploadFile(_fileUpload, _controller.ControllerContext.HttpContext.User.Identity.Name, fileToUpload.Object.ContentType, MatchCollection(fileData))).Returns(true);
Hope it helps!
EDIT
I have tried with the following test based on your code that passes a valid array data to the mock setup and doesn´t need to actually read a file in the unit test:
public void FileUpload_ShouldRedirectToIndexOnSuccess()
{
var fileData = UTF8Encoding.UTF8.GetBytes("This is a test");
var fileStream = new MemoryStream(fileData);
var fileToUpload = new Mock<HttpPostedFileBase>();
fileToUpload.Setup(f => f.ContentLength).Returns((int)fileStream.Length);
fileToUpload.Setup(f => f.FileName).Returns("test.txt");
fileToUpload.Setup(f => f.ContentType).Returns("txt");
fileToUpload.Setup(f => f.InputStream).Returns(fileStream);
_fileUpload.File = fileToUpload.Object;
_repositoryMock
.Setup(m => m.UploadFile(_fileUpload, username, ".txt", MatchCollection(fileData)))
.Returns(true);
var result = _controller.FileUpload(_fileUpload) as RedirectToRouteResult;
Assert.IsNotNull(result);
}
This is the final version of the test:
[Test]
public void FileUpload_ShouldRedirectToIndexOnSuccess()
{
string filePath = Path.GetFullPath(#"C:\Users\Yordanka\Desktop\test.txt");
var fileStream = new FileStream(filePath, FileMode.Open);
var fileExt = Path.GetExtension(filePath);
var testarray = new byte[414];
var fileToUpload = new Mock<HttpPostedFileBase>();
fileToUpload.Setup(f => f.ContentLength).Returns(414);
fileToUpload.Setup(f => f.FileName).Returns("test.txt");
fileToUpload.Setup(f => f.ContentType).Returns(fileExt);
fileToUpload.Setup(f => f.InputStream).Returns(fileStream);
fileToUpload.Setup(f => f.InputStream.Length).Returns(testarray.Length);
_fileUpload.File = fileToUpload.Object;
var fileData = new byte[414];
_fileUpload.File.InputStream.Read(fileData, 0, fileData.Length);
_repositoryMock.Setup(
m =>
m.UploadFile(_fileUpload, _controller.ControllerContext.HttpContext.User.Identity.Name,
fileToUpload.Object.ContentType, MatchCollection(fileData))).Returns(true);
var result = _controller.FileUpload(_fileUpload) as RedirectToRouteResult;
Assert.IsNotNull(result);
Assert.AreEqual("Index", result.RouteValues["Action"]);
Assert.AreEqual("SuperUser", result.RouteValues["Controller"]);
}
Once again, thank you Daniel J.G.!!!
I have a Class that return a List of Users
public IList<RegisterUsers> GetUsers()
{
var query = (from up in _context.UserProfile
join um in _context.Membership on up.UserID equals um.UserID
select new RegisterUsers { ID = ua.UserID, FirstName = ua.FirstName, LastName = ua.LastName, LastLogIn = up.LastLogIn, Status = up.Status });
return query.ToList();
}
I call this class by doing the following on my Action
var UserRepository = new UserRepository();
var AllUsers = UserRepository.GetUsers();
var result = from c in AllUsers
select new List<string> { c.ID.ToString(), c.FirstName, c.LastName, c.LastLogIn,c.Status };
return Json(new { sEcho = param.sEcho,
iTotalRecords = result.Count(),
iTotalDisplayRecords = result.Count(),
aaData = result
},
JsonRequestBehavior.AllowGet);
The above code works fine and it return JSON data. But I'm trying o figure out why I get an error if I just attach the AllUsers directly to the aaData variable. Please see below.
var UserRepository = new UserRepository();
var AllUsers = UserRepository.GetUsers();
return Json(new { sEcho = param.sEcho,
iTotalRecords = AllUsers.Count(),
iTotalDisplayRecords = AllUsers.Count(),
aaData = AllUsers
},
JsonRequestBehavior.AllowGet);
I was able to Return a List by doing the following
var AllUsers = UserRepository.GetUsers().Select(p => new List<string> { p.UserID.ToString(), p.FirstName, p.LastName }).ToList();
Basically the above code just eliminates the following line of code
var result = from c in AllUsers
select new List<string> { c.ID.ToString(), c.FirstName, c.LastName, c.LastLogIn,c.Status };
Hopefully I was able to answer your question.
Hi Im using kendo ui grid in my project.
This is my code to insert records in database.
public static void Insert(StudentViewModel student)
{
student.StudentId = All().OrderByDescending(p => p.StudentId).First().StudentId + 1;
//All().Insert(0, student);
UniRegEntities uniRegEntities = new UniRegEntities();
Student stu =new Student();
stu.FName = student.FirstName;
stu.LName = student.LastName;
stu.Gender = uniRegEntities.Genders.Where(x => x.Title == student.Gender).FirstOrDefault();
stu.Id = student.StudentId;
uniRegEntities.Students.Add(stu);
uniRegEntities.SaveChanges();
}
And this is my update statement.
public static void Update(StudentViewModel student)
{
UniRegEntities context = new UniRegEntities();
var studentToUpdate = context.Students.Where(x => x.Id == student.StudentId).FirstOrDefault();
studentToUpdate.FName = student.FirstName;
studentToUpdate.LName = student.LastName;
studentToUpdate.Gender = context.Genders.Where(x => x.Title == student.Gender).FirstOrDefault();
context.SaveChanges();
}
Anyone can suggest me the delete method?
You can either get an entity from the DB and then delete it or create one and then delete it.
So:
var e = // Get
ctx.DeleteObject(e);
ctx.SaveChanges();
or
var e = new Foo() { FooId = id };
ctx.Entity.Attach(e);
ctx.DeleteObject(e);
ctx.SaveChanges();
Applied to your situation:
You are getting a record so you want to use DeleteObject()
public static void Update(StudentViewModel student)
{
UniRegEntities context = new UniRegEntities();
var studentToDelete = context.Students.Where(x => x.Id == student.StudentId).FirstOrDefault();
context.Students.DeleteObject(studentToUpdate);
context.SaveChanges();
}
context.Students.Remove(context.students.Single(x=>x.Id==student.Id));
Can you please try with below code snippet?
using (var db= new AppContext(ConnectionStr))
{
try
{
con.Configuration.AutoDetectChangesEnabled = false;
var o = new Student { StudentId = student.StudentId };
db.Students.Attach(o);
db.Students.Remove(o);
db.SaveChanges();
}
catch (Exception ex)
{
throw new Exception(ex.InnerException.Message);
}
finally
{
con.Configuration.AutoDetectChangesEnabled = true;
}
}
While i build the Project, It have a error like this:
Server Error in '/' Application.
The model item passed into the dictionary is of type
'System.Data.DataTable', but this dictionary requires a model item of
type 'System.Collections.Generic.IList`1[TLayout.Models.DemoTable]'.
This is my Controller
public ActionResult Index()
{
var dm = new DemoTable();
string connstring = "Server=localhost;Port=5432;User Id=postgres;Password=123456;Database=test";
NpgsqlConnection conn = new NpgsqlConnection(connstring);
conn.Open();
string sql = "select * from demo";
NpgsqlDataAdapter da = new NpgsqlDataAdapter(sql, conn);
ds.Reset();
da.Fill(ds);
dt = ds.Tables[0];
var demoid = dm.demoid.ToString();
var demoname = dm.demoname;
for (int i = 0; i < dt.Rows.Count; i++)
{
List<DataTable> dtb = new List<DataTable>();
demoid = dt.Rows[i]["demoid"].ToString();
demoname = dt.Rows[i]["demoname"].ToString();
dtb.Add(dt);
}
return View(dt);
}
This is my View, to show data to layout:
foreach (var item in Model)
{
fields.Add(Html.X().ModelField().Mapping(#item.demoid.ToString()).Name("grid-alarm"));
fields.Add(Html.X().ModelField().Mapping(#item.demoname.ToString()).Name("grid-check"));
}
var list = dt.AsEnumerable()
.Where(row => (int)row["demoid"] > 5)
.Select(row => new
{
demoid = Convert.ToInt32(row["demoid"]),
demoname = row["demoname"] != null ?
row["demoname"].ToString() :
String.Empty
}).ToList();
Or you can define class:
public class myClass
{
public int demoid;
public string demoname;
}
and then:
List<myClass> list = dt.AsEnumerable()
.Where(row => (int)row["demoid"] > 5)
.Select(row => new myClass
{
demoid = Convert.ToInt32(row["demoid"]),
demoname = row["demoname"] != null ?
row["demoname"].ToString() :
String.Empty
}).ToList<myClass>();
In an ASP.NET MVC3 application, if I wanted to model bind my form post data to an ExpandoObject (or my own object derived from DynamicObject where I implement my own Try... members) would I need to write my own custom model binder?
If I do:
public ActionResult Save(ExpandoObject form)
{
....
}
The value of form is null.
Or if I have:
public class MyDynamicThing : DynamicObject
{
public int Id { get; set; }
public override bool TrySetMember(SetMemberBinder binder, object value)
{
// Set breakpoint here but doesn't get hit when model binding
return base.TrySetMember(binder, value);
}
}
...and in my controller:
public ActionResult Save(MyDynamicThing form)
{
....
}
In the above example Id is set to the value from the form. However if I set a breakpoint in TrySetMember this doesn't get hit.
Are there any magical incantations I can invoke to coerce the built-in model binder to work with ExpandoObjects or my own classes derived from DynamicObject?
I could resort to picking up the raw form post collection but I have to serialise this data to JSON which would mean an extra and untidy step to harvest those values.
No, this is not possible with the built-in model binder. You could of course write a custom model binder. The only property that the built-in model binder is capable of binding is the one that it seems from the MyDynamicThing type and that's why it can only set the Id property. It has no knowledge of other properties.
Try this:
public class ExpandoObjectBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException("bindingContext");
IDictionary<string, object> model = new ExpandoObject();
string modelName = bindingContext.ModelName;
var form = controllerContext.HttpContext.Request.Unvalidated.Form;
var keys = form.AllKeys.Where(k => k.StartsWith(modelName + "."));
Debug.Write("ExpandoObjectBinder keys count is " + keys.Count());
foreach (var key in keys)
{
var propName = key.Replace(model + ".", "");
model.Add(propName, form[key]);
}
if (model.Count == 0)
throw new Exception("Data is empty.");
return model;
}
}
Register the binder mvc initialization :
ModelBinders.Binders.Add(typeof(ExpandoObject), new ExpandoObjectBinder());
Type support added (int, byte, long, decimal, string, array and subobjects):
public class ExpandoObjectBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException("bindingContext");
string modelName = bindingContext.ModelName;
var form = controllerContext.HttpContext.Request.Unvalidated.Form;
var model = ParseProperties(modelName, form);
return model;
}
public object ParseProperties(string modelName, NameValueCollection form)
{
var keys = form.AllKeys.Where(k => k.StartsWith(modelName + "."));
Debug.WriteLine("ExpandoObjectBinder keys count is " + keys.Count() + " for " + modelName);
IDictionary<string, object> model = new ExpandoObject();
List<string> subModelNames = new List<string>();
List<string> arrModelNames = new List<string>();
//ModelName: Dialog.Attributes[0].Options
foreach (var key in keys)
{
//Dialog.Attributes[0].Options.Byte
//Dialog.Attributes[0].Options.Inner.Byte
//Dialog.Attributes[0].Options.Inner.Integer
//Dialog.Attributes[0].Options.SimpleArray[0]
//Dialog.Attributes[0].Options.SimpleArray[1]
var propName = key.Replace(modelName + ".", "");
//Byte
//Inner.Byte
//Inner.Integer
//SimpleArray[0]
//SimpleArray[1]
if (!(propName.Contains('[') || propName.Contains('.')))
{
model.Add(propName, GetValue(form, key));
}
//array (can allow sub objects)
if (propName.Contains('['))
{
var names = propName.Split('[');
var arrModelName = names[0];
if (!arrModelNames.Contains(arrModelName))
arrModelNames.Add(arrModelName);
}
//not array but can has sub objects
if (!propName.Contains('[') && propName.Contains('.'))
{
var names = propName.Split('.');
var subModelName = names[0];
if (!subModelNames.Contains(subModelName))
subModelNames.Add(subModelName);
}
}
foreach (var subModelName in subModelNames)
{
var key = modelName + "." + subModelName;
object val = form[key];
val = ParseProperties(key, form);
model.Add(subModelName, val);
}
foreach (var arrModelName in arrModelNames)
{
//Dialog.Attributes[0].Options.SimpleArray[
var key = modelName + "." + arrModelName + "[";
var arrKeys = form.AllKeys.Where(k => k.StartsWith(key));
var isComplexArray = false;
int length = 0;
foreach (var arrKey in arrKeys)
{
var aKey = arrKey.Replace(key, "");
if (aKey.Contains("."))
isComplexArray = true;
var parsed = aKey.Split(']');
var num = int.Parse(parsed[0]);
if (num > length)
length = num;
}
List<object> vals = new List<object>();
if (isComplexArray)
{
for (int i = 0; i < length + 1; i++)
{
var arrKey = key + i + "]";
object val = ParseProperties(arrKey, form);
vals.Add(val);
}
}
else
{
for (int i = 0; i < length + 1; i++)
{
var arrKey = key + i + "]";
vals.Add(GetValue(form, arrKey));
}
}
model.Add(arrModelName, vals);
}
return model;
}
object GetValue(NameValueCollection form, string key)
{
object val = form[key];
if (decimal.TryParse(form[key], out decimal decimalVal))
val = decimalVal;
if (long.TryParse(form[key], out long longVal))
val = longVal;
if (int.TryParse(form[key], out int intVal))
val = intVal;
if (byte.TryParse(form[key], out byte byteVal))
val = byteVal;
if (bool.TryParse(form[key], out bool boolVal))
val = boolVal;
return val;
}
}