I have a stored procedure that is called to validate a user during login.
If success it returns the user entity, and that works good! My question is if it doesn't work, I'll raise an error in the SP, How do I catch this error and use it in the best way? Right now I'm getting nullrefference, this is the code:
Store procedure:
ALTER PROCEDURE getEmployee
(
#username nvarchar(50),
#password nvarchar(50)
)
AS
DECLARE #Error_MSG nvarchar(50)
BEGIN
IF EXISTS (select * from Employee where eUsername = #username AND pword = #password)
begin
select * from Employee where eUsername = #username AND pword = #password
END
ELSE
BEGIN
SET #Error_MSG = 'Wrong password, or user doesnt exist'
RAISERROR (#Error_MSG, 11,1)
END
END
And in the code it looks like this, the SP is getEmployee
ActivityDatabaseDataContext dc = new ActivityDatabaseDataContext();
Employee emp;
public bool logIn(string piUsername, string piPassword)
{
try
{
emp = dc.getEmployee(piUsername, piPassword).Single();
}
catch (Exception ex)
{
errorMsg = ex.Message + ex.InnerException.Message;
}
if (emp != null)
{
AppHelper.AppHelper.setUser(emp);
return true;
}
else
{
return false;
}
My question is how I should handle the exception?
I wouldn't generally raise an error from a SP unless it was actually a system problem with the operation. Entering the wrong username and password is a user problem, and one you need only deal with at the interface level, so I'd throw most of that SP away and deal with the two use cases (1 row or 0 rows returned) in the business layer or interface code. If 0 rows, throw up the "Wrong username or password" message to the client and if 1, log in.
ALTER PROCEDURE getEmployee
(
#username nvarchar(50),
#password nvarchar(50)
)
AS
BEGIN
select * from Employee where eUsername = #username AND pword = #password
END
Your InnerException is probably null.
You should try to catch and deal with specific exceptions, in this case SqlExceptions.
ALTER PROCEDURE getEmployee
(
#username nvarchar(50),
#password nvarchar(50)
)
AS
BEGIN
select * from Employee where eUsername = #username AND pword = #password
END
...
SqlCommand cmd = new SqlCommand("getEmployee", conn);
cmd.AddWithValue('#username', name);
cmd.AddWithValue('#password', pass);
SqlAdapter da = new SqlAdapter(cmd);
DataSet ds= new DataSet();
da.Fill(ds);
if (ds.Table.Count > 0 && ds.Table.Rows.Count == 1) {
// success
} else {
// fail
}
IF(#Count>0)
BEGIN
SELECT #RetVal = 6
, #ErrMsg = 'A description with the same name exists. Please provide a unique name.'
GOTO ERROR
END
Use the inbuilt StoredProcException in catch, that implies:
catch (StoredProcException spEx)
{
switch (spEx.ReturnValue)
{
case 6:
UserMessageException umEx= new UserMessageException(spEx.Message);
throw umEx;
}
}
You can pass the Message as string instead of spEx.Message
Related
I am working on login page with validation on a local server using SQL Server. I created a login page and sign up page, my sign up page works fine but the login page keeps showing an error of "User not activated"
Here is my code behind for loginpage
public partial class Login : System.Web.UI.Page
{
protected void Validate_User(object sender, EventArgs e)
{
int userId = 0;
string constr = `ConfigurationManager.ConnectionStrings["constr"].ConnectionString;`
using (SqlConnection con = new SqlConnection(constr))
{
using (SqlCommand cmd = new SqlCommand("Validate_User"))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#Username", Login1.UserName);
cmd.Parameters.AddWithValue("#Password", Login1.Password);
cmd.Connection = con;
con.Open();
userId = Convert.ToInt32(cmd.ExecuteScalar());
con.Close();
}
switch (userId)
{
case -1:
Login1.FailureText = "Username and/or password is incorrect.";
break;
case -2:
Login1.FailureText = "Account has not been activated.";
break;
default:
FormsAuthentication.RedirectFromLoginPage(Login1.UserName, Login1.RememberMeSet);
break;
}
}
}
}
and here is the procedure to validate the user
CREATE PROCEDURE [dbo].[Validate_User]
#Username NCHAR(50),
#Password VARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #UserId INT, #LastLoginDate DATETIME
SELECT #UserId = UserId, #LastLoginDate = LastLoginDate
FROM NervSuiteUsers
WHERE Username = #UserName AND [Password] = #Password
IF #UserId IS NOT NULL
BEGIN
IF NOT EXISTS(SELECT UserId FROM NervSuiteUsers WHERE Username = #UserName)
BEGIN
UPDATE NervSuiteUsers
SET LastLoginDate = GETDATE()
WHERE UserId = #UserId
SELECT #UserName [UserName] -- User Valid
END
ELSE
BEGIN
SELECT -2 -- User not activated.
END
END
ELSE
BEGIN
SELECT -1 -- User invalid.
END
END
The problem is even with a user in the database, I still get "Account not Validated"
In addition to glitches in the SP (already discussed), there are problems in the .NET code, associated with whether the result was an integer (failure) or a string (success). One pragmatic way to resolve this would be to always return the same types. Since the user passes in the username, there's not necessarily a huge point in passing it out again, unless your intent is to auto-correct case insensitive strings, but a simple fix would be to simply select 1 (or some other sentinel value) in the success case, instead of select #UserName.
However, the same problem can be fixed in the existing code, simply by testing the value:
object sqlResult = cmd.ExecuteScalar();
switch (sqlResult)
{
case int i when i == -1:
// TODO ...
break;
case int i when i == -2:
// TODO ...
break;
case string s:
// success, and the value was s
// TODO...
break;
default:
// I HAVE NO CLUE
throw new SomeSensibleException(...);
}
Note this uses "new" C# language syntax features, but the same fundamental approach can also be done manually if you're using down-level C#, via:
if (sqlResult is int)
{
switch ((int)sqlResult)
{
// ...
}
}
else if (sqlResult is string)
{
string s = (string)sqlResult;
// ...
}
Your SP makes contradictory statement to me. Below query will give result only when both username/password matches
SELECT #UserId = UserId, #LastLoginDate = LastLoginDate
FROM NervSuiteUsers
WHERE Username = #UserName AND [Password] = #Password
Then this below query, doesn't make sense
IF #UserId IS NOT NULL // will be true when both username/password matches
BEGIN
IF NOT EXISTS(SELECT UserId FROM NervSuiteUsers WHERE Username = #UserName) // Why this???? This will not be TRUE
BEGIN
UPDATE NervSuiteUsers
SET LastLoginDate = GETDATE()
WHERE UserId = #UserId
Thus your else block will gets evaluated and you will get that result you posted
ELSE
BEGIN
SELECT -2 -- User not activated.
END
Apart from all the feedback you have got in comments regarding the issues with the implementation, you have issue with following lines of query.
IF NOT EXISTS(SELECT UserId FROM NervSuiteUsers WHERE Username = #UserName)
BEGIN
UPDATE NervSuiteUsers
SET LastLoginDate = GETDATE()
WHERE UserId = #UserId
SELECT #UserName [UserName] -- User Valid
END
ELSE
BEGIN
SELECT -2 -- User not activated.
END
It should not be NOT EXISTS. It should be IF EXISTS because #UserId NOT NULL mean it exists in the table, change your query like following.
IF EXISTS(SELECT UserId FROM NervSuiteUsers WHERE Username = #UserName)
BEGIN
UPDATE NervSuiteUsers
SET LastLoginDate = GETDATE()
WHERE UserId = #UserId
SELECT #UserName [UserName] -- User Valid
END
ELSE
BEGIN
SELECT -2 -- User not activated.
END
I have following stored procedure :
alter procedure [usp_chkuseridpwd]
as
begin
declare
/*variable declaration */
#sql as varchar(max),
#tblname varchar(max),
#user_name varchar(max),
#user_password varchar(max),
#response varchar(max),
#count int,
#passwd as varchar(max),
#temppasswd as varchar(max)
set #sql = 'select COUNT(*) from user_master where USER_NAME like '+#user_name+' and pasword like '+#user_password+''
exec(#sql)
if(#count > 0)
begin
set #temppasswd=('select password from user_master where USER_NAME='+#user_name+'')
if (#temppasswd = #passwd)
set #response=('the password is incorrect !')
print #response //here I want to return response using message box
end
else
set #response = ('The user Id is not available')
print #response //here I want to return response using message box
end
And here I have following function that send some parameter like table name, user name, password (if stored procedure returns false, then it should be display "invalid user name", else "user is valid user")
public DataTable chkuseridpwd(ref string tname,ref string uname,ref string pwd)
{
try
{
if (cnn.State == System.Data.ConnectionState.Closed)
{
cnn.Open();
cmd = new SqlCommand("usp_chkuseridpwd", cnn);
cmd.CommandTimeout = 5000;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#tblname", tname);
cmd.Parameters.AddWithValue("#user_name", uname);
cmd.Parameters.AddWithValue("#user_password", pwd);
da = new SqlDataAdapter(cmd);
da.Fill(dtTbl);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
cnn.Close();
}
return dtTbl;
}
Use output parameters for this task. Here are some links for more reading.
http://www.codeproject.com/Articles/748619/ADO-NET-How-to-call-a-stored-procedure-with-output
https://www.toadworld.com/platforms/sql-server/w/wiki/10261.stored-procedures-output-parameters-return-values
https://technet.microsoft.com/en-us/library/ms187004(v=sql.105).aspx
Use this RAISERROR function of SQL Server
RAISERROR('Here Your Error Message')
As per your code use RAISERROR(#response)
You will catch it as exception message at front end
I have a stored procedure called lastID like this:
CREATE PROCEDURE lastID(#id varchar(64) OUTPUT)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #f VARCHAR(64);
SELECT TOP 1 #f = work_id
FROM workorder
WHERE (RIGHT(work_id,2)) = (RIGHT(Year(getDate()),2))
ORDER BY work_id DESC;
IF(#f IS NULL)
BEGIN
SET #f = 'No work orders';
SET #id = #f;
RETURN #id;
END
ELSE
BEGIN
SET #id = #f;
RETURN #id;
END
END
This stored procedure returns the last id from the table workorder, now I'm trying to execute this procedure in C#, this is the code:
private void lastWorkId()
{
String strConnString = "Server=.\\SQLEXPRESS;Database=recalls;Integrated Security=true";
SqlConnection con = new SqlConnection(strConnString);
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "lastID";
cmd.Parameters.Add("#id", SqlDbType.VarChar, 64).Direction = ParameterDirection.Output;
cmd.Connection = con;
try
{
con.Open();
cmd.ExecuteNonQuery();
String id = cmd.Parameters["#id"].Value.ToString();
lastid.Text = id.ToString(); //Putting the return value into a label
}
catch (Exception ex)
{
throw ex;
}
finally
{
con.Close();
con.Dispose();
}
}
I don't know what are wrong with my code, because an exception is displayed, and this says
Conversion failed when converting the varchar value ' OT- 003-16 ' to data type int
I was wrong about my first answer, here is the updated answer:
Your stored procedure is setup with an OUTPUT parameter of type VARCHAR(64).
Within your proc you have a couple of RETURN #id; statements, which is actually returning a VARCHAR(64). You only need to set your OUTPUT variable within the stored procedure. The RETURN statement expects an integer expression. Here's the updated fixed sproc using OUTPUT appropriately:
ALTER PROCEDURE [dbo].[lastID](#id varchar(64) OUTPUT)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #f VARCHAR(64);
SELECT TOP 1 #f = work_id FROM workorder WHERE (RIGHT(work_id,2)) = (RIGHT(Year(getDate()),2)) ORDER BY work_id DESC;
IF(#f IS NULL)
BEGIN
SET #f = 'No work orders';
SET #id = #f;
END
ELSE
BEGIN
SET #id = #f;
END
END
Error is basically should get fixed by cast
((RIGHT(work_id,2)) as int)
But code can be further condensed and improved.
CREATE PROCEDURE lastID(#id varchar(64) OUTPUT)
AS
BEGIN
SET NOCOUNT ON;
SELECT TOP 1 #id = isnull(work_id , 'No work orders') FROM workorder WHERE cast ((RIGHT(work_id,2)) as int)= (RIGHT(Year(getDate()),2)) ORDER BY work_id DESC;
RETURN #id;
END
I am unable to read value from stored procedure when executing it.
Here is my stored procedure
ALTER Procedure [dbo].[SaveInvitation]
(
#InvitationID INT OUTPUT,
#UserID INT,
#Email NCHAR(100),
#InvitationGUID UNIQUEIDENTIFIER OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
IF #UserID = -1
BEGIN
SET #UserID = NULL
END
IF NOT EXISTS (SELECT 1 FROM Invitations WHERE LTRIM(RTRIM(Email)) = LTRIM(RTRIM(#Email)))
BEGIN
INSERT INTO Invitations (UserID,
Email,
CreateDate)
VALUES (#UserID,
#Email,
GETDATE())
-- GET NEWLY INSERTED INVITATIONS ID
SET #InvitationID = IDENT_CURRENT('Invitations')
-- GET GUID FROM INVITATION ID
SELECT #InvitationGUID = InvitationGUID
FROM Invitations
WHERE InvitationID = #InvitationID
END
ELSE
BEGIN
RAISERROR('ALREADY_INVITATED', 16, 127)
END
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK
END
EXEC ThrowError
END CATCH
END
I am executing this procedure from this function
My DataBaseModel.Designer.cs:
public ObjectResult<SaveInvitation_Result> SaveInvitation(ObjectParameter invitationID, Nullable<global::System.Int32> userID, global::System.String email, ObjectParameter invitationGUID)
{
ObjectParameter userIDParameter;
if (userID.HasValue)
{
userIDParameter = new ObjectParameter("UserID", userID);
}
else
{
userIDParameter = new ObjectParameter("UserID", typeof(global::System.Int32));
}
ObjectParameter emailParameter;
if (email != null)
{
emailParameter = new ObjectParameter("Email", email);
}
else
{
emailParameter = new ObjectParameter("Email", typeof(global::System.String));
}
return base.ExecuteFunction<SaveInvitation_Result>("SaveInvitation", invitationID, userIDParameter, emailParameter, invitationGUID);
}
It throws an exception
The data reader is incompatible with the specified 'TestModel.SaveInvitation_Result'. A member of the type, 'InvitationGUID', does not have a corresponding column in the data reader with the same name.
I have created a complex type i.e. SaveUserRegistration_Result and imported one function SaveInvitation of return type SaveUserRegistration_Result.
How can I solve above exception? Is there any change in stored procedure?
Screen shot
It's giving you this error because you're not actually SELECTing back a result. If you added a line to the end of your procedure for example:
SELECT #InvitationGUID AS InvitationGUID
It should work just fine for you.
This is a strange one. I have a Dev SQL Server which has the stored proc on it, and the same stored proc when used with the same code on the UAT DB causes it to delete itself!
Has anyone heard of this behaviour?
SQL Code:
-- Check if user is registered with the system
IF OBJECT_ID('dbo.sp_is_valid_user') IS NOT NULL
BEGIN
DROP PROCEDURE dbo.sp_is_valid_user
IF OBJECT_ID('dbo.sp_is_valid_user') IS NOT NULL
PRINT '<<< FAILED DROPPING PROCEDURE dbo.sp_is_valid_user >>>'
ELSE
PRINT '<<< DROPPED PROCEDURE dbo.sp_is_valid_user >>>'
END
go
create procedure dbo.sp_is_valid_user
#username as varchar(20),
#isvalid as int OUTPUT
AS
BEGIN
declare #tmpuser as varchar(20)
select #tmpuser = username from CPUserData where username = #username
if #tmpuser = #username
BEGIN
select #isvalid = 1
END
else
BEGIN
select #isvalid = 0
END
END
GO
Usage example
DECLARE #isvalid int
exec dbo.sp_is_valid_user 'username', #isvalid OUTPUT
SELECT valid = #isvalid
The usage example work all day... when I access it via C# it deletes itself in the UAT SQL DB but not the Dev one!!
C# Code:
public bool IsValidUser(string sUsername, ref string sErrMsg)
{
string sDBConn = ConfigurationSettings.AppSettings["StoredProcDBConnection"];
SqlCommand sqlcmd = new SqlCommand();
SqlDataAdapter sqlAdapter = new SqlDataAdapter();
try
{
SqlConnection conn = new SqlConnection(sDBConn);
sqlcmd.Connection = conn;
conn.Open();
sqlcmd.CommandType = CommandType.StoredProcedure;
sqlcmd.CommandText = "sp_is_valid_user";
// params to pass in
sqlcmd.Parameters.AddWithValue("#username", sUsername);
// param for checking success passed back out
sqlcmd.Parameters.Add("#isvalid", SqlDbType.Int);
sqlcmd.Parameters["#isvalid"].Direction = ParameterDirection.Output;
sqlcmd.ExecuteNonQuery();
int nIsValid = (int)sqlcmd.Parameters["#isvalid"].Value;
if (nIsValid == 1)
{
conn.Close();
sErrMsg = "User Valid";
return true;
}
else
{
conn.Close();
sErrMsg = "Username : " + sUsername + " not found.";
return false;
}
}
catch (Exception e)
{
sErrMsg = "Error :" + e.Source + " msg: " + e.Message;
return false;
}
}
Ok, I have found the answer ... simple when you know how!
I saw this link here :
Disappearing Stored Procedure
Disappearing Stored Procedure
So from the best answer in that I ran :
select syo.name
from syscomments syc
join sysobjects syo on
syo.id = syc.id
where syc.[text] like '%DROP PROC%'
This gave me one of my OTHER stored procs back... sp_is_user_admin, which didn't seem right so I had a quick look ...
create procedure dbo.sp_is_user_admin
#username as varchar(20),
#isadmin as int OUTPUT
AS
BEGIN
declare #profile as varchar(20)
select #profile = profile from CPUserData where username = #username
if #profile = 'admin'
BEGIN
select #isadmin = 1
END
else
BEGIN
select #isadmin = 0
END
END
--*********************************************************************************
-- Check if user is registered with the system
IF OBJECT_ID('dbo.sp_is_valid_user') IS NOT NULL
BEGIN
DROP PROCEDURE dbo.sp_is_valid_user
IF OBJECT_ID('dbo.sp_is_valid_user') IS NOT NULL
PRINT '<<< FAILED DROPPING PROCEDURE dbo.sp_is_valid_user >>>'
ELSE
PRINT '<<< DROPPED PROCEDURE dbo.sp_is_valid_user >>>'
END
Doh!!! There is the blighter... in the C# what happens is that if the user is valid I also choose what to let them see based on if they are admin or not and calling that was blitzing the sp_is_valid_user proc. Nasty side effect!
// check the user is entitled to use the system at all
if (usrData.IsValidUser(sCurrentUserName, ref sErrMsg))
{
// if the user is admin then let them spoof and edit their own data
if (usrData.UserIsAdmin(sCurrentUserName, ref sErrMsg))
{
chkSpoof.Visible = true;
grdvwUserDataFromDB.Visible = true;
}
}
else
{
// redirect them away
Response.Redirect("UserNotRegistered.aspx");
return;
}
I hope this helps someone else out!
PS: DB Artisan is nasty and if I had the full fat SQL Server available in my Development toolkit then I guess I could have used the profiler to see this being called. ;P I can't install SQL Server 2008 as I don't have the right SP / updates to Visual Studio I think and IT here can't sort it out, annoying!!