nice overload structure - asp.net

I have created a library function and want to add an overload that does a very similar thing with an additional parameter. The existing code looks like this:
public class MealsAllocation
{
public int mealId;
public List<CrewSummary> crew;
private MealsAllocation() { }
public MealsAllocation(int MealId) {
mealId = MealId;
string connStr = ConfigurationManager.ConnectionStrings["LocalSqlServer"].ConnectionString;
SqlConnection conn = new SqlConnection(connStr);
//first fill an ienumerable redemption object for the meal
List<MealRedemption> mealRedemptions = new List<MealRedemption>();
SqlCommand cmdRed = new SqlCommand("tegsGetMealsRedemption", conn);
cmdRed.CommandType = CommandType.StoredProcedure;
cmdRed.Parameters.Add(new SqlParameter("#mealId", MealId));
conn.Open();
SqlDataReader drRed = cmdRed.ExecuteReader();
while (drRed.Read())
{
MealRedemption mr = new MealRedemption(Convert.ToInt32(drRed["crewId"]), Convert.ToDateTime(drRed["creation"]), Convert.ToInt32(drRed["redeemed"]));
mealRedemptions.Add(mr);
}
conn.Close();
//then fill the crew list
crew = new List<CrewSummary>();
SqlCommand cmdCrew = new SqlCommand("tegsGetMealsAllocation", conn);
cmdCrew.CommandType = CommandType.StoredProcedure;
cmdCrew.Parameters.Add(new SqlParameter("#mealId", MealId));
conn.Open();
SqlDataReader drCrew = cmdCrew.ExecuteReader();
while (drCrew.Read())
{
int drCid = Convert.ToInt32(drCrew["id"]);
List<MealRedemption> drMr = mealRedemptions.FindAll(red => red.crewId == drCid) ;
CrewSummary cs = new CrewSummary(drCid, Convert.ToInt32(drCrew["allocation"]), drMr );
crew.Add(cs);
}
conn.Close();
}
So then now I wish to add a new overload that will look a bit like this:
public MealsAllocation(int MealId, int crewId)
{
}
and essentially this will do much the same but slightly different from the above.
What would be a good strategy to avoid "copy and paste inheritance" ?
ie a nice way to refactor the above so that it lends itself more easily to the overload?

How about moving your logic to an internal function so it's only accessible in this assembly, and to this class and use optional parameters... something like this:
public class MealsAllocation
{
public int mealId;
public List<CrewSummary> crew;
private MealsAllocation()
{
}
public MealsAllocation(int MealId)
{
DoWork(MealId);
}
public MealsAllocation(int MealId, int crewId)
{
DoWork(MealId, crewId);
}
internal void DoWork(int MealId, int crewId = -1)
{
// have your logic here based on your parameter list
// valid crewId passed, then add new param for DB proc
if (crewId > -1)
{
cmdCrew.Parameters.Add(new SqlParameter("#crewId", crewId));
}
}
}

You can user object initializer
var mealRedemption = new
{
MealId = yourvlue,
Crew = crew
};
Link : http://msdn.microsoft.com/en-us/library/bb384062.aspx

Since you want to overload your constructor, you could also try such an approach:
public MealsAllocation(int MealId) : this (MealId, null)
{
}
public MealsAllocation(int MealId, int? crewId)
{
// Initialize your instance as needed
if (crewId.HasValue)
{
// Do some more stuff
}
}

Although I don't recommend doing all that inside the constructor, you can simply add an optional param to the end:
public class MealsAllocation
{
public int MealId { get; set; }
public int CrewId { get; set; }
public List<CrewSummary> Crew { get; set; };
public MealsAllocation(int mealId, int crewId = 0)
{
this.MealId = mealId;
this.CrewId = crewId;
if(this.CrewId = 0) // etc...
}
Side note: You need to add the using statement around your SqlConnection, SqlCommand and SqlDataReader object or you could run into connection and/or memory leaks. Personally, I'd create a Data Access layer and put all of the data related methods there to make them reusable across your entire business layer.
Also, I think this might be a good candidate for the Lazy<T> object: http://msdn.microsoft.com/en-us/library/dd642331.aspx

The first thing that comes to mind is to split that big chunk of code in two different methods
giving at each method a specialized functionality
public MealsAllocation(int MealId)
{
List<MealRedemption> mealRedemptions = LoadMealRedemptions(MealID);
LoadCrewSummaryByMeal(mealRedemptions, MealID);
}
while the other constructor could be
public MealsAllocation(int MealId, int crewId)
{
List<MealRedemption> mealRedemptions = LoadMealRedemptions(MealID);
LoadCrewSummaryByCrew(mealRedemptions, MealID, crewID);
}
In the first constructor you call the private method where you load the MealRedemptions list, get its output and pass to a specialized method that load the CrewSummary list using only the MealID and the list obtained from the first method.
In the second constructor you could use the same method used in the first one and then use a different one for the loading of the CrewSummary. The requirements of your second constructor are not clear and could change the design of this second method (I mean, how do you use the crewID parameter to change the inner workings to build the CrewSummary list?)

Related

How to add data to associative table in asp.net core

I am new to asp.net core. I am building a web application for book management. I have a table called Author and books. Being a many to many relationships I made an associative entity that consists of the bookId and authorId. When I try to create I am able to create author and book. I successfully added the author and book to the database.
My author class looks like this
public class Author
{
private int _ID
private string _Name;
public string ID {
get { return _ID; }
set { _ID = value; }
public string Name {
get { return _Name; }
set { _Name = value; }
}
My book class is
public class Author
{
private int _ID
private string _Name;
private string _Title;
public string ID {
get { return _ID; }
set { _ID = value; }
}
public string Title {
get { return _Name; }
set { _Name = value; }
}
public string Name {
get { return _Name; }
set { _Name = value; }
}
I have a data access called db.cs to help to create the book and author in database.
public static int AddAuthor(Author A)
{
int renum = -1;
SqlConnection conn = null;
conn = new SqlConnection(ConnectionString);
conn.Open();
SqlCommand comm = new SqlCommand("sproc_AuthorAdd", conn);
comm.CommandType = System.Data.CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#Name", A.Name);
comm.Parameters.AddWithValue("#Title", a.Title);
SqlParameter output = new SqlParameter();
output.ParameterName = "#AuthorID";
output.DbType = System.Data.DbType.Int32;
output.Direction = System.Data.ParameterDirection.Output;
comm.Parameters.Add(output);
int affect = comm.ExecuteNonQuery();
renum = affect;
c.ID = (int)output.Value;
I have done the same for books as well. I want to fill out the association table as well when the user filled out a book and author using their ID. I tried to do various things like using a cookie to pass data. But I cannot store data. Any kind of help is appreciated. Thanks in advance.
I'm not really sure I understand your last code snippet, but if you're having issues managing your many-to-many relationship between Books and Authors, have you considered just using Entity Framework Core?
Instead of writing a bunch of code that accesses your database, you just create models of your tables (similar to the classes you have defined above), and it handles the many-to-many relationship for you. The code to query for Authors and/or Books could then look as simple as:
using (var db = new dbContext())
{
var books = db.Books
.Where(b => b.ID > 1234)
.OrderBy(b => b.Title)
.ToList();
}
And creating a new Book or Author would be similarly simple:
using (var db = new dbContext())
{
var book = new Book { ID = 1234, Title = "Some Title", Name = "Some Name" };
db.Books.Add(book);
db.SaveChanges();
}
You might have to reimplement a bunch of things to take advantage of Entity Framework Core in your app, but it sounds like it would save you time in the long run.

ASP.NET Core 2.1 API not taking parameter

I am trying to pass a parameter from my API, but it does not seem to be taking it. I have a lastName (eventually will be replaced with a unique id) that I want to pass, which will ultimately route to a profile view or that person.
Here is my API Controller:
public class GetInquiryController : BaseController
{
private GetInquiry manager;
public GetInquiryController(IConfiguration config) : base(config)
{
manager = new GetInquiry(config);
}
[HttpGet]
[Route("api/[controller]")]
public IEnumerable<InquiryModel> Get(string lastName)
{
return manager.GetInquiriesByUser(lastName);
}
}
And my logic for my stored procedure that gets the last name
public class GetInquiry
{
private readonly IConfiguration configuration;
public GetInquiry(IConfiguration config)
{
configuration = config;
}
public IEnumerable<InquiryModel> GetInquiriesByUser(string lastName)
{
string ApplicationDB = configuration.GetConnectionString("ApplicationDB");
List<InquiryModel> lstinquiry = new List<InquiryModel>();
using (SqlConnection con = new SqlConnection(ApplicationDB))
{
SqlCommand cmd = new SqlCommand("spGetInquiriesByUser", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter lastNameParameter = cmd.Parameters.Add("lastName", SqlDbType.Char, 10);
lastNameParameter.Value = lastName;
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
InquiryModel inquiry = new InquiryModel();
//inquiry.FormID = rdr["FormID"].ToString();
inquiry.firstName = rdr["firstName"].ToString();
inquiry.lastName = rdr["lastName"].ToString();
inquiry.SID = rdr["SID"].ToString();
inquiry.email = rdr["email"].ToString();
inquiry.phone = rdr["phone"].ToString();
inquiry.email = rdr["email"].ToString();
inquiry.employer = rdr["street"].ToString();
inquiry.city = rdr["city"].ToString();
inquiry.state = rdr["state"].ToString();
inquiry.zip = rdr["zip"].ToString();
inquiry.date = rdr["date"].ToString();
inquiry.comment = rdr["comment"].ToString();
lstinquiry.Add(inquiry);
}
con.Close();
}
return lstinquiry;
}
}
}
I am using VUE.js and calling my API
methods: {
editItem(lastName) {
let id = lastName
this.$http.get('http://localhost:61601/api/GetInquiry/', {
id: lastName
}),
I have tried using my logic without IEnumerable, which i didn't think will make a difference in this case.
I guess the good thing is that I am getting,
"SqlException: Procedure or function 'spGetInquiriesByUser' expects parameter '#lastName', which was not supplied"
when type my API route into the browser, but when I add the lastName to the end of my route or Postman, I get a blank page.
Hope this makes sense, please let me know if i need to provide more detail and if someone can point me into the right direction!
You need to define from where your lastName parameter should be read.
If you want to read it from the route, use FromRoute attribute
[HttpGet("{lastName}")] //api/GetInquiry/yourlastname
public IEnumerable<InquiryModel> Get([FromRoute]string lastName)
{
return manager.GetInquiriesByUser(lastName);
}
If you want to read it from the querystring, use FromQuery attribute
[HttpGet] //api/GetInquiry?lastName=yourlastname
public IEnumerable<InquiryModel> Get([FromQuery]string lastName)
{
return manager.GetInquiriesByUser(lastName);
}
Note: Above solution works when moving the [Route("api/[controller]")] ontop of the GetInquiryController class.
Update
When you added your vue.js api call it makes more sence why this is not working, you are actually calling the lastName parameter by the id name. Changing your id prop to lastName would work with above
methods: {
editItem(lastName) {
this.$http.get('http://localhost:61601/api/GetInquiry/', {
lastName: lastName
}),

ExecuteXmlReader Fails on Specific JSON from SQL Server

I have an existing working process which receives generated JSON from store procedures in SQL Server using the for JSON directive. But upon receiving a specific text in the column data, there is a failure by ExecuteXmlReader in the Read operation.
Exception
XmlException: '=' is an unexpected token. The expected token is ';'.
Line 1, position 94
Which if I controlled the output, I would most likely put it in a CDATA section.
Data Returned from SQL Server
JSON_F52E2B61-18A1-11d1-B105-00805F49916B
{"photoId":1000000007,"photoType":"image\/gif","photoUrl":"https:\/\/slack-imgs.com\/?c=1&url=https%3A%2F%2Fmedia0.giphy.com%2Fmedia%2F3o84U9arAYRM73AIvu%2Fgiphy-downsized.gif" }
Ultimate JSON String Which Should be Returned by Read
{
"photoId": 1000000007,
"photoType": "image/gif",
"photoUrl": "https://slack-imgs.com/?c=1&url=https%3A%2F%2Fmedia0.giphy.com%2Fmedia%2F3o84U9arAYRM73AIvu%2Fgiphy-downsized.gif",
"isActive": true
}
URL saved to Table
https://slack-imgs.com/?c=1&url=https%3A%2F%2Fmedia0.giphy.com%2Fmedia%2F3o84U9arAYRM73AIvu%2Fgiphy-downsized.gif
Ultimately this is a SQL Server 2016 change but I will need a fix sooner than what can be provided from Microsoft. So, is there a work around to handle this either by SQL or C# .Net code?
Oddly enough when one clicks on the table column value JSON in SSMS, it gives the same error.
I'll go ahead and propose that you are using the wrong APIs to read the FOR JSON query results. Here's a little helper class that implements a SqlCommand.ExecuteJsonReader() extension method.
static class SqlJsonUtils
{
public static Newtonsoft.Json.JsonReader ExecuteJsonReader(this SqlCommand cmd)
{
var rdr = cmd.ExecuteReader();
var jr = new Newtonsoft.Json.JsonTextReader(new SqlJSONReader(rdr));
return jr;
}
class SqlJSONReader : System.IO.TextReader
{
SqlDataReader rdr;
string currentLine = "";
int currentPos = 0;
public SqlJSONReader(SqlDataReader rdr)
{
this.rdr = rdr;
}
public override int Peek()
{
return GetChar(false);
}
public override int Read()
{
return GetChar(true);
}
public int GetChar(bool Advance)
{
while (currentLine.Length == currentPos)
{
if (!rdr.Read())
{
return -1;
}
currentLine = rdr.GetString(0);
currentPos = 0;
}
int rv = (int)currentLine[currentPos];
if (Advance) currentPos += 1;
return rv;
}
public override void Close()
{
rdr.Close();
}
}
}
I have since made this a downloadable NUGET package. See SQLJSONReader with updates for async.
I took David's advice and marked his as the answer, but I needed the whole raw JSON.
So I added removed the call to to Newtonsoft.Json.JsonTextReader because it wouldn't return me just a string and modified David's extension class to return the whole JSON by calling ReadAll.
Note that it uses Newtonsoft's JsonConvert.DeserializeObject.
Code
public static SqlJSONReader ExecuteJsonReader(this SqlCommand cmd)
{
var rdr = cmd.ExecuteReader();
return new SqlJSONReader(rdr);
}
public class SqlJSONReader : System.IO.TextReader
{
private SqlDataReader SqlReader { get; set; }
private string CurrentLine { get; set; }
private int CurrentPostion { get; set; }
public SqlJSONReader(SqlDataReader rdr)
{
CurrentLine = "";
CurrentPostion = 0;
this.SqlReader = rdr;
}
public override int Peek()
{
return GetChar(false);
}
public override int Read()
{
return GetChar(true);
}
public int GetChar(bool Advance)
{
while (CurrentLine.Length == CurrentPostion)
{
if (!SqlReader.Read())
{
return -1;
}
CurrentLine = SqlReader.GetString(0);
CurrentPostion = 0;
}
var rv = CurrentLine[CurrentPostion];
if (Advance)
CurrentPostion += 1;
return rv;
}
public string ReadAll()
{
var sbResult = new StringBuilder();
if (SqlReader.HasRows)
{
while (SqlReader.Read())
sbResult.Append(SqlReader.GetString(0));
}
else
return string.Empty;
// Clean up any JSON escapes before returning
return JsonConvert.DeserializeObject(sbResult.ToString()).ToString();
}
public override void Close() { SqlReader.Close(); }
}
Usage
using (SqlConnection conn = new SqlConnection(connectionString))
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "exec [dbo].[GetPhoto] #PhotoId=4";
conn.Open();
var rdr = cmd.ExecuteJsonReader();
string jsonResult = rdr.ReadAll();
conn.Close();
}

Storage for DataMappers in ASP.NET WebApplication

In Martin Fowler's "Patterns of Enterprise Application Architecture"
is described approach for organizing DAL like a set of mappers for entities. Each has it's own IdentityMap storing specific entity.
for example in my ASP.NET WebApplication:
//AbstractMapper - superclass for all mappers in DAL
public abstract class AbstractMapper
{
private readonly string _connectionString;
protected string ConnectionString
{
get { return _connectionString; }
}
private readonly DbProviderFactory _dbFactory;
protected DbProviderFactory DBFactory
{
get { return _dbFactory; }
}
#region LoadedObjects (IdentityMap)
protected Hashtable LoadedObjects = new Hashtable();
public void RegisterObject(long id, DomainObject obj)
{
LoadedObjects[id] = obj;
}
public void UnregisterObject(long id)
{
LoadedObjects.Remove(id);
}
#endregion
public AbstractMapper(string connectionString, DbProviderFactory dbFactory)
{
_connectionString = connectionString;
_dbFactory = dbFactory;
}
protected virtual string DBTable
{
get
{
throw new NotImplementedException("database table is not defined in class " + this.GetType());
}
}
protected virtual T Find<T>(long id, IDbTransaction tr = null) where T : DomainObject
{
if (id == 0)
return null;
T result = (T)LoadedObjects[id];
if (result != null)
return result;
IDbConnection cn = GetConnection(tr);
IDbCommand cmd = CreateCommand(GetFindStatement(id), cn, tr);
IDataReader rs = null;
try
{
OpenConnection(cn, tr);
rs = cmd.ExecuteReader(CommandBehavior.SingleRow);
result = (rs.Read()) ? Load<T>(rs) : null;
}
catch (DbException ex)
{
throw new DALException("Error while loading an object by id in class " + this.GetType(), ex);
}
finally
{
CleanUpDBResources(cmd, cn, tr, rs);
}
return result;
}
protected virtual T Load<T>(IDataReader rs) where T : DomainObject
{
long id = GetReaderLong(rs["ID"]);
T result = (T)LoadedObjects[id];
if (result != null)
return result;
result = (T)DoLoad(id, rs);
RegisterObject(id, result);
return result;
}
// another CRUD here ...
}
// Specific Mapper for entity Account
public class AccountMapper : AbstractMapper
{
internal override string DBTable
{
get { return "Account"; }
}
public AccountMapper(string connectionString, DbProviderFactory dbFactory) : base(connectionString, dbFactory) { }
public Account Find(long id)
{
return Find<Account>(id);
}
public override DomainObject DoLoad(long id, IDataReader rs)
{
Account account = new Account(id);
account.Name = GetReaderString(rs["Name"]);
account.Value = GetReaderDecimal(rs["Value"]);
account.CurrencyID = GetReaderLong(rs["CurrencyID"]);
return account;
}
// ...
}
The question is: where to store these mappers? How system services (entities) should call mappers?
I decided to create MapperRegistry containing all mappers. So services can call mappers like:
public class AccountService : DomainService
{
public static Account FindAccount(long accountID)
{
if (accountID > 0)
return MapperRegistry.AccountMapper.Find(accountID);
return null;
}
...
}
But where can I store MapperRegistry instance? I see following variants, but don't like any of them:
MapperRegistry is global for application (Singleton)
Not applicable because of necessity of synchronization in multi-thread ASP.NET application (at least Martin says that only mad can choose this variant)
MapperRegistry per Session
Seems not so good too. All ORMs (NHibernate, LINQ to SQL, EntityFramework) masters advise to use DataContext (NHibernateSession, ObjectContext) per Request and not to store context in Session.
Also in my WebApp almost all requests are AJAX-requests to EntityController.asmx (with attribute ScriptService) returning JSON. And session is not allowed.
MapperRegistry per Request
There are a lot of separate AJAX calls. In this case life cycle of MapperRegistry will be too small. So the data almost always will be retrieved from database, as a result - low performance.
Dear Experts, please help me with architectural solution.

TDD problem in ASP.NET MVC3 (with DI)

I am attempting to write some Tests for a small project of mine but they seem to fail (I am starting with 1 test 'Create_Class')
I use the repository pattern and use Constructor Dependency Injection:
public KlasController() {
db = ObjectContextPerHttpRequest.Context;
KlasRepo = new KlasRepository(db);
LesRepo = new LesRepository(db);
OpdrachtRepo = new OpdrachtRepository(db);
}
//dependency injection constructor
public KlasController(IKlasRepository KlasRepo, ILesRepository LesRepo,
IOpdrachtRepository OpdrachtRepo) {
this.KlasRepo = KlasRepo;
this.LesRepo = LesRepo;
this.OpdrachtRepo = OpdrachtRepo;
}
here is my TestClass with testinitializer (which runs before every test) and the first test
[TestClass()]
public class KlasControllerTest
{
private KlasController Controller;
private IOpdrachtRepository OpdrachtRepo;
//Use TestInitialize to run code before running each test
[TestInitialize()]
public void MyTestInitialize()
{
OpdrachtRepo = new DummyOpdrachtRepository();
Controller = new KlasController(new DummyKlasRepository(),
new DummyLesRepository(), OpdrachtRepo);
Opdracht TestOpdracht = new Opdracht
{
OpdrachtID = 1,
VakID = 1,
StamNummer = "im1"
};
Vak TestVak = new Vak { VakID = 1, VakNaam = "FOOP" };
TestOpdracht.Vak = TestVak;
OpdrachtRepo.addOpdracht(TestOpdracht);
}
/// <summary>
///A test for Index
///</summary>
[TestMethod()]
public void CreateKlasDirectsToToonKlassen()
{
Klas Klas = new Klas { KlasNaam = "2dNet" };
RedirectToRouteResult view = Controller.Create(1) as RedirectToRouteResult;
Assert.IsNotNull(view);
Assert.AreEqual("ToonKlassen", view.RouteValues["action"]);
}
}
at the moment I get a nullreferenceException on the view (assert.isNotNull fails)
and here is one of my DummyRepository's:
class DummyOpdrachtRepository : IOpdrachtRepository
{
List<Opdracht> opdrachten;
public DummyOpdrachtRepository()
{
opdrachten = new List<Opdracht>();
}
public void addOpdracht(Opdracht opdracht)
{
opdrachten.Add(opdracht);
}
public string GetDocentID(int OpdrachtID)
{
var opdracht = opdrachten.Where(o => o.OpdrachtID == OpdrachtID).FirstOrDefault();
return opdracht.StamNummer;
}
public Opdracht Find(int id)
{
return opdrachten.Where(o => o.OpdrachtID == id).FirstOrDefault();
}
}
Normally I should have written the tests Before writting the code, I know (and I am convinced off TDD, as I have used it successfully in my latest Java-project). but it just doesn't seem to work..
here is the code for KlasController.Create action
public ActionResult Create(int id) //id = opdrachtID
{
var Opdracht = OpdrachtRepo.Find(id);
Vak vak;
if(Opdracht != null)
vak = Opdracht.Vak;
else
throw new NullReferenceException("Deze opdracht werd niet gevonden");
return View(new CreateKlasModel(id,vak));
}
I know this is a lot of code, but I really want to make this work.
Thanks for helping me out in advance :)
As vladimir77 already says in his comment, the method public ActionResult Create(int id) is of type ViewResult, so either you change you method to do areturn RedirectToRoute() or you change your test to
ViewResult view = Controller.Create(1);
Assert.IsNotNull(view);
A ViewResult can not be cast as a RedirectToRouteResult.

Resources