Profile System: User share the same id - asp.net-mvc

I have a strange effect on my site when it is under heavy load.
I randomly get the properties of other users settings. I have my own implementation of the profile system so I guess I can not blame the profile system itself.
I just need a point to start debugging from. I guess there is a cookie-value that maps to an Profile entry somewhere. Is there any chance to see how this mapping works?
Here is my profile provider:
using System;
using System.Text;
using System.Configuration;
using System.Web;
using System.Web.Profile;
using System.Collections;
using System.Collections.Specialized;
using B2CShop.Model;
using log4net;
using System.Collections.Generic;
using System.Diagnostics;
using B2CShop.DAL;
using B2CShop.Model.RepositoryInterfaces;
[assembly: log4net.Config.XmlConfigurator()]
namespace B2CShop.Profile
{
public class B2CShopProfileProvider : ProfileProvider
{
private static readonly ILog _log = LogManager.GetLogger(typeof(B2CShopProfileProvider));
// Get an instance of the Profile DAL using the ProfileDALFactory
private static readonly B2CShop.DAL.UserRepository dal = new B2CShop.DAL.UserRepository();
// Private members
private const string ERR_INVALID_PARAMETER = "Invalid Profile parameter:";
private const string PROFILE_USER = "User";
private static string applicationName = B2CShop.Model.Configuration.ApplicationConfiguration.MembershipApplicationName;
/// <summary>
/// The name of the application using the custom profile provider.
/// </summary>
public override string ApplicationName
{
get
{
return applicationName;
}
set
{
applicationName = value;
}
}
/// <summary>
/// Initializes the provider.
/// </summary>
/// <param name="name">The friendly name of the provider.</param>
/// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
public override void Initialize(string name, NameValueCollection config)
{
if (config == null)
throw new ArgumentNullException("config");
if (string.IsNullOrEmpty(config["description"]))
{
config.Remove("description");
config.Add("description", "B2C Shop Custom Provider");
}
if (string.IsNullOrEmpty(name))
name = "b2c_shop";
if (config["applicationName"] != null && !string.IsNullOrEmpty(config["applicationName"].Trim()))
applicationName = config["applicationName"];
base.Initialize(name, config);
}
/// <summary>
/// Returns the collection of settings property values for the specified application instance and settings property group.
/// </summary>
/// <param name="context">A System.Configuration.SettingsContext describing the current application use.</param>
/// <param name="collection">A System.Configuration.SettingsPropertyCollection containing the settings property group whose values are to be retrieved.</param>
/// <returns>A System.Configuration.SettingsPropertyValueCollection containing the values for the specified settings property group.</returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
string username = (string)context["UserName"];
bool isAuthenticated = (bool)context["IsAuthenticated"];
//if (!isAuthenticated) return null;
int uniqueID = dal.GetUniqueID(username, isAuthenticated, false, ApplicationName);
SettingsPropertyValueCollection svc = new SettingsPropertyValueCollection();
foreach (SettingsProperty prop in collection)
{
SettingsPropertyValue pv = new SettingsPropertyValue(prop);
switch (pv.Property.Name)
{
case PROFILE_USER:
if (!String.IsNullOrEmpty(username))
{
pv.PropertyValue = GetUser(uniqueID);
}
break;
default:
throw new ApplicationException(ERR_INVALID_PARAMETER + " name.");
}
svc.Add(pv);
}
return svc;
}
/// <summary>
/// Sets the values of the specified group of property settings.
/// </summary>
/// <param name="context">A System.Configuration.SettingsContext describing the current application usage.</param>
/// <param name="collection">A System.Configuration.SettingsPropertyValueCollection representing the group of property settings to set.</param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
string username = (string)context["UserName"];
CheckUserName(username);
bool isAuthenticated = (bool)context["IsAuthenticated"];
int uniqueID = dal.GetUniqueID(username, isAuthenticated, false, ApplicationName);
if (uniqueID == 0)
{
uniqueID = dal.CreateProfileForUser(username, isAuthenticated, ApplicationName);
}
foreach (SettingsPropertyValue pv in collection)
{
if (pv.PropertyValue != null)
{
switch (pv.Property.Name)
{
case PROFILE_USER:
SetUser(uniqueID, (UserInfo)pv.PropertyValue);
break;
default:
throw new ApplicationException(ERR_INVALID_PARAMETER + " name.");
}
}
}
UpdateActivityDates(username, false);
}
// Profile gettters
// Retrieve UserInfo
private static UserInfo GetUser(int userID)
{
return dal.GetUser(userID);
}
// Update account info
private static void SetUser(int uniqueID, UserInfo user)
{
user.UserID = uniqueID;
dal.SetUser(user);
}
// UpdateActivityDates
// Updates the LastActivityDate and LastUpdatedDate values
// when profile properties are accessed by the
// GetPropertyValues and SetPropertyValues methods.
// Passing true as the activityOnly parameter will update
// only the LastActivityDate.
private static void UpdateActivityDates(string username, bool activityOnly)
{
dal.UpdateActivityDates(username, activityOnly, applicationName);
}
/// <summary>
/// Deletes profile properties and information for the supplied list of profiles.
/// </summary>
/// <param name="profiles">A System.Web.Profile.ProfileInfoCollection of information about profiles that are to be deleted.</param>
/// <returns>The number of profiles deleted from the data source.</returns>
public override int DeleteProfiles(ProfileInfoCollection profiles)
{
int deleteCount = 0;
foreach (ProfileInfo p in profiles)
if (DeleteProfile(p.UserName))
deleteCount++;
return deleteCount;
}
/// <summary>
/// Deletes profile properties and information for profiles that match the supplied list of user names.
/// </summary>
/// <param name="usernames">A string array of user names for profiles to be deleted.</param>
/// <returns>The number of profiles deleted from the data source.</returns>
public override int DeleteProfiles(string[] usernames)
{
int deleteCount = 0;
foreach (string user in usernames)
if (DeleteProfile(user))
deleteCount++;
return deleteCount;
}
// DeleteProfile
// Deletes profile data from the database for the specified user name.
private static bool DeleteProfile(string username)
{
CheckUserName(username);
return dal.DeleteAnonymousProfile(username, applicationName);
}
// Verifies user name for sise and comma
private static void CheckUserName(string userName)
{
if (string.IsNullOrEmpty(userName) || userName.Length > 256 || userName.IndexOf(",") > 0)
throw new ApplicationException(ERR_INVALID_PARAMETER + " user name.");
}
/// <summary>
/// Deletes all user-profile data for profiles in which the last activity date occurred before the specified date.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are deleted.</param>
/// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate value of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
/// <returns>The number of profiles deleted from the data source.</returns>
public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
{
string[] userArray = new string[0];
dal.GetInactiveProfiles((int)authenticationOption, userInactiveSinceDate, ApplicationName).CopyTo(userArray, 0);
return DeleteProfiles(userArray);
}
/// <summary>
/// Retrieves profile information for profiles in which the user name matches the specified user names.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
/// <param name="usernameToMatch">The user name to search for.</param>
/// <param name="pageIndex">The index of the page of results to return.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
/// <returns>A System.Web.Profile.ProfileInfoCollection containing user-profile information
// for profiles where the user name matches the supplied usernameToMatch parameter.</returns>
public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
CheckParameters(pageIndex, pageSize);
return GetProfileInfo(authenticationOption, usernameToMatch, null, pageIndex, pageSize, out totalRecords);
}
/// <summary>
/// Retrieves profile information for profiles in which the last activity date occurred on or before the specified date and the user name matches the specified user name.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
/// <param name="usernameToMatch">The user name to search for.</param>
/// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate value of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
/// <param name="pageIndex">The index of the page of results to return.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
/// <returns>A System.Web.Profile.ProfileInfoCollection containing user profile information for inactive profiles where the user name matches the supplied usernameToMatch parameter.</returns>
public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
{
CheckParameters(pageIndex, pageSize);
return GetProfileInfo(authenticationOption, usernameToMatch, userInactiveSinceDate, pageIndex, pageSize, out totalRecords);
}
/// <summary>
/// Retrieves user profile data for all profiles in the data source.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
/// <param name="pageIndex">The index of the page of results to return.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
/// <returns>A System.Web.Profile.ProfileInfoCollection containing user-profile information for all profiles in the data source.</returns>
public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
{
CheckParameters(pageIndex, pageSize);
return GetProfileInfo(authenticationOption, null, null, pageIndex, pageSize, out totalRecords);
}
/// <summary>
/// Retrieves user-profile data from the data source for profiles in which the last activity date occurred on or before the specified date.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
/// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
/// <param name="pageIndex">The index of the page of results to return.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
/// <returns>A System.Web.Profile.ProfileInfoCollection containing user-profile information about the inactive profiles.</returns>
public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
{
CheckParameters(pageIndex, pageSize);
return GetProfileInfo(authenticationOption, null, userInactiveSinceDate, pageIndex, pageSize, out totalRecords);
}
/// <summary>
/// Returns the number of profiles in which the last activity date occurred on or before the specified date.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
/// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
/// <returns>The number of profiles in which the last activity date occurred on or before the specified date.</returns>
public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
{
int inactiveProfiles = 0;
ProfileInfoCollection profiles = GetProfileInfo(authenticationOption, null, userInactiveSinceDate, 0, 0, out inactiveProfiles);
return inactiveProfiles;
}
//Verifies input parameters for page size and page index.
private static void CheckParameters(int pageIndex, int pageSize)
{
if (pageIndex < 1 || pageSize < 1)
throw new ApplicationException(ERR_INVALID_PARAMETER + " page index.");
}
//GetProfileInfo
//Retrieves a count of profiles and creates a
//ProfileInfoCollection from the profile data in the
//database. Called by GetAllProfiles, GetAllInactiveProfiles,
//FindProfilesByUserName, FindInactiveProfilesByUserName,
//and GetNumberOfInactiveProfiles.
//Specifying a pageIndex of 0 retrieves a count of the results only.
private static ProfileInfoCollection GetProfileInfo(ProfileAuthenticationOption authenticationOption, string usernameToMatch, object userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
{
ProfileInfoCollection profiles = new ProfileInfoCollection();
totalRecords = 0;
// Count profiles only.
if (pageSize == 0)
return profiles;
int counter = 0;
int startIndex = pageSize * (pageIndex - 1);
int endIndex = startIndex + pageSize - 1;
DateTime dt = new DateTime(1900, 1, 1);
if (userInactiveSinceDate != null)
dt = (DateTime)userInactiveSinceDate;
/*
foreach(CustomProfileInfo profile in dal.GetProfileInfo((int)authenticationOption, usernameToMatch, dt, applicationName, out totalRecords)) {
if(counter >= startIndex) {
ProfileInfo p = new ProfileInfo(profile.UserName, profile.IsAnonymous, profile.LastActivityDate, profile.LastUpdatedDate, 0);
profiles.Add(p);
}
if(counter >= endIndex) {
break;
}
counter++;
}
*/
return profiles;
}
}
}
This is how I use it in the controller:
public ActionResult AddTyreToCart(CartViewModel model)
{
string profile = Request.IsAuthenticated ? Request.AnonymousID : User.Identity.Name;
}
I would like to debug: How can 2 users who provide different cookies get the same profileid?
EDIT
Here is the code for getuniqueid
public int GetUniqueID(string userName, bool isAuthenticated, bool ignoreAuthenticationType, string appName)
{
SqlParameter[] parms = {
new SqlParameter("#Username", SqlDbType.VarChar, 256),
new SqlParameter("#ApplicationName", SqlDbType.VarChar, 256)};
parms[0].Value = userName;
parms[1].Value = appName;
if (!ignoreAuthenticationType)
{
Array.Resize(ref parms, parms.Length + 1);
parms[2] = new SqlParameter("#IsAnonymous", SqlDbType.Bit) { Value = !isAuthenticated };
}
int userID;
object retVal = null;
retVal = SqlHelper.ExecuteScalar(ConfigurationManager.ConnectionStrings["SQLOrderB2CConnString"].ConnectionString, CommandType.StoredProcedure, "getProfileUniqueID", parms);
if (retVal == null)
userID = CreateProfileForUser(userName, isAuthenticated, appName);
else
userID = Convert.ToInt32(retVal);
return userID;
}
And this is the SP:
CREATE PROCEDURE [dbo].[getProfileUniqueID]
#Username VarChar( 256),
#ApplicationName VarChar( 256),
#IsAnonymous bit = null
AS
BEGIN
SET NOCOUNT ON;
/*
[getProfileUniqueID]
created
08.07.2009 mf
Retrive unique id for current user
*/
SELECT UniqueID FROM dbo.Profiles WHERE Username = #Username
AND ApplicationName = #ApplicationName
AND IsAnonymous = #IsAnonymous or #IsAnonymous = null
END

I haven't looked at this in depth, but one thing did jump out at me.
In your where clause change
#IsAnonymous = null
to
#IsAnonymous IS NULL
What do you want to happen when #IsAnonymous is null? You might need to add a few parenthesis to the where clause.
SELECT UniqueID FROM dbo.Profiles
WHERE Username = #Username
AND ApplicationName = #ApplicationName
AND (IsAnonymous = #IsAnonymous OR #IsAnonymous IS NULL)
Do you two users who received the same profile data? If so start there. I would run them through the stored proc first. It would be easy and quick to do.

This is a very difficult type of issue to troubleshoot. We had a similar issue in an ASP.NET app (not MVC). I'd suggest that you can start by setting a separate cookie to identify the user, completely bypassing the ASP.NET profile system. You can then check whether the identity in that cookie matches the identity in the profile. If it doesn't then you can 1) log out the user so at least they don't get access to someone else's data and 2) gather some diagnostic information - the complete HTTP request details would be a start. If that doesn't establish any sort of pattern you could start logging all HTTP requests by user to see if there's a pattern in their request history that helps you to reproduce the problem.
In our case the issue ended up being a silly home-grown caching mechanism. The application would try to cache static files (eg. images) in ASP.NET and cookies got cached with them. Under heavy load a user would sometimes load an image with another user's cookies and assume the other user's identity. Your case may be completely different, but this might give you some clue of the type of thing to look for.

The given sources seem to be okay, but i cannot make a profound statement without haveing all the corresponding sourcecode (eg. all used methods of class B2CShop.DAL.UserRepository), perhaps even the database scheme.
First would be to check if perhaps the data in the database is corrupted, so to say that some user may literally own the same unique id or the same username/application/isanonymous combination. This can be excluded if uniqueid is a primary key.

Whenever these kinds of issue happen... I have seen the following to be the causes...
Cache
Static variables
Can you try to write this code without static variables and check if the issue resolves?
Regards,
Rahul

Related

dynamically generating json from json for highcharts heap map chart using ajax

i have data in my datatable (c#), i plan to send the data to the JS so that highcharts heatmap can understand and plot the heatmap.
following is the structure of datatable (c#)
xaxis yaxis value color
0 0 50 green
0 1 60 yellow
1 0 66 red
1 1 60 yellow
i want json in the below format so that highchart can understand
[
{x:0,y:0,value:50,color:'green'},
{x:0,y:1,value:60,color:'yelow'},
{x:1,y:0,value:66,color:'red'},
{x:1,y:1,value:50,color:'green'}
]
please help me in getting the desired output.once i get the desired output i will set to the chart using
chart.series[0].setdata(jsondata);
Left out code and error handling but I think this is sort of what you are looking for.
/// <summary>
/// serializable class that represent one data point
/// </summary>
[Serializable()]
class heatmap
{
public int x { get; set; }
public int y { get; set; }
public int value { get; set; }
public string color { get; set; }
/// <summary>
/// Use a public static function to copy data from the data table
/// to the list object
/// </summary>
/// <param name="table"></param>
/// <returns></returns>
public static List<heatmap> createHeatMap(ref DataTable table)
{
List<heatmap> list = new List<heatmap>();
foreach(DataRow row in table.Rows)
{
heatmap map = new heatmap();
map.x = Convert.ToInt32(row["x"]);
map.y = Convert.ToInt32(row["y"]);
map.value = Convert.ToInt32(row["value"]);
map.color = Convert.ToString(row["color"]);
list.Add(map);
}
return list;
}
}
// your code that populates the datatable
//blah blah blah
List<heatmap> map = heatmap.createHeatMap(ref datatable);
// I use newtonsoft for serialization
string jsonMap = Newtonsoft.Json.JsonConvert.SerializeObject(map);
return jsonMap;

Docusign Embedded Signing(MVC website) - tags not showing up and the document is coming as free form signing

I am trying to integrate my website with Docusign via embedded signing. I have been pretty successful - thanks to the documentation and pointers from SO).
My issue is that I have a website and on initial sign up I need the users to e-sign a document before they proceed to shop at my site. So I have set up a docusign embedded signing experience once they login - which will take them seamlessly(without login to docusign server etc) to Docusign - where in the document for signing shows up - this document is coming thru fine - but the tags are not showing up and it is showing as free form signing. There are "FIELDS" to the left of my document and I need to drag and drop these on the form ( I have populated these fields with values).
The real issue is docusign lets me "FINISH" without signing since the document is showing up as free form - Please find my code below - I am using the DocuSign REST API to created an embedded signing for a predefined document template using the /envelopes/{envelopeID}/views/recipient call. I am using RESTSHARP to connect to docusign. Thanks much for your help!
protected const string IntegratorKey = "XX";
protected const string Environment = "https://demo.docusign.net";
protected const string templateRole = "Applicant";
protected const string templateId = "XX";
private static Logger logger = LogManager.GetCurrentClassLogger();
protected const string AccountEmail = "XX#XX.com";
protected const string AccountPassword = "***";
private RestSharp.RestClient client = new RestClient();
private RestSharp.RestRequest request;
bool docuSignCallresult = false;
//
// GET: /Docusign/
public ActionResult launchDocusign(int id)
{
RestSettings.Instance.IntegratorKey = IntegratorKey;
RestSettings.Instance.DocuSignAddress = Environment;
RestSettings.Instance.WebServiceUrl = Environment + "/restapi/v2";
Domain.Account currentAccount = null;
using (var accountRepo = new AccountRepository())
{
currentAccount = accountRepo.AccountGet(id);
}
string RecipientEmail = currentAccount.Email;
string RecipientName = currentAccount.FullName;
Account docuSignAcct = GetDocusignAcctDetails();
Envelope docuSignEnvelope = GetDocusignEnvelopeDetails(docuSignAcct,RecipientEmail,RecipientName);
RecipientView rv = GetRecipientView(RecipientEmail, RecipientName);
client = new RestSharp.RestClient(Environment);
request = new RestRequest("/restapi/{apiVersion}/accounts/{accountId}/envelopes/{envelopeId}/views/recipient");
request.AddUrlSegment("apiVersion", "v2");
request.AddUrlSegment("accountId", docuSignAcct.AccountId);
request.AddUrlSegment("envelopeId", docuSignEnvelope.EnvelopeId);
Mysite.Web.Models.DocuSignData.AuthenticationHeader header = new Mysite.Web.Models.DocuSignData.AuthenticationHeader();
var jsonHeader = JsonConvert.SerializeObject(header);
request.AddHeader("X-DocuSign-Authentication", jsonHeader);
request.Method = Method.POST;
request.RequestFormat = DataFormat.Json;
request.AddJsonBody(rv);
var response = client.Execute(request);
char[] charstoTrim = { '\r', '\n', ' ', '\'' };
var json = response.Content.Trim(charstoTrim);
var jo = JObject.Parse(json);
var recipientUrl = jo["url"].ToString();
return Redirect(recipientUrl);
}
/// <summary>
/// Get the recipient view to launch the docusign(Embedded signing experience)
/// </summary>
/// <param name="RecipientEmail"></param>
/// <param name="RecipientName"></param>
/// <returns></returns>
private RecipientView GetRecipientView(string RecipientEmail, string RecipientName)
{
RecipientView rv = new RecipientView();
rv.authenticationMethod = "email";
rv.returnUrl = Request.Url.Scheme + System.Uri.SchemeDelimiter + Request.Url.Host + "/MySiteController/MySiteActionMethod";
rv.email = RecipientEmail;
rv.userName = RecipientName;
rv.clientUserId = "1";
return rv;
}
/// <summary>
/// Create an Envelope using the template on docusign
/// </summary>
/// <param name="acct"></param>
/// <param name="recipientEmail"></param>
/// <param name="recipientName"></param>
/// <returns></returns>
private Envelope GetDocusignEnvelopeDetails(Account acct,string recipientEmail,string recipientName)
{
Envelope envelope = new Envelope();
envelope.Login = acct;
envelope.Status = "sent";
envelope.EmailSubject = "Testing";
envelope.TemplateId = templateId;
envelope.TemplateRoles = new TemplateRole[]
{
new TemplateRole()
{
email = recipientEmail,
name = recipientName,
roleName = templateRole,
clientUserId = "1"
}
};
try
{
docuSignCallresult = envelope.Create();
}
catch (Exception ex)
{
logger.Error("Login to docusign failed due to {0} and the exception generated is {2}", envelope.RestError.message, ex.Message);
}
return envelope;
}
/// <summary>
/// Access Docusign Account information
/// </summary>
/// <returns></returns>
private Account GetDocusignAcctDetails()
{
Account docuSignAcct = new Account();
docuSignAcct.Email = AccountEmail;
docuSignAcct.Password = AccountPassword;
try
{
docuSignCallresult = docuSignAcct.Login();
}
catch (Exception ex)
{
logger.Error("Login to docusign failed due to {0} and the exception generated is {2}", docuSignAcct.RestError.message, ex.Message);
}
return docuSignAcct;
}
}
}
As Jeff has mentioned in the comments this is most likely caused by you not matching your recipient correctly to a template (placeholder) role on your template. From your code it looks like you are sending the value Applicant as the template role name - this means that you need to have a role called Applicant in your Template in the Web Console.
For instance, in the below screenshot the role name is Signer1:
To fix either login to the Console and name the role on your template "Applicant" or whatever name it currently has copy that into the code and send that in the API request.

OpenISO8583.Net BCD fix format unpack error

I downloaded the code from code.google and get the last version v0.5.2
I set a field in bcd fix format,which is N-6 in bcd format(bit._003_proc_code)
For ex:
*Field definition:
DefaultTemplate =new Template
{
{ Bit._002_PAN, FieldDescriptor.BcdVar(2, 19,Formatters.Ascii) },
{ Bit._003_PROC_CODE, FieldDescriptor.BcdFixed(3)},
{ Bit._004_TRAN_AMOUNT, FieldDescriptor.BcdFixed(6) },
..............
}
usage:
Iso8583 msg =new Iso8584();
msg[3]="000000";
when i unpack the message ,i can only get “0000” from message 3 .
is this a bug or error in definition
I would wait to hear from John Oxley to see if this is just a coding error. Now that I said that, I think it is a bug.
I had problems with the BcdFixed definition and ended up creating a new Fixed Length BCD formatter to work around the problem.
Here is what I did:
I created a class called FixedLengthBcdFormatter which is a variation of the FixedLengthFormatter class.
///
/// Fixed field formatter
///
public class FixedLengthBcdFormatter : ILengthFormatter
{
private readonly int _packedLength;
private readonly int _unPackedLength;
///<summary>
/// Fixed length field formatter
///</summary>
///<param name = "unPackedLength">The unpacked length of the field.</param>
public FixedLengthBcdFormatter(int unPackedLength)
{
_unPackedLength = unPackedLength;
double len = _unPackedLength;
_packedLength = (int)Math.Ceiling(len / 2);
}
#region ILengthFormatter Members
/// <summary>
/// Get the length of the packed length indicator. For fixed length fields this is 0
/// </summary>
public int LengthOfLengthIndicator
{
get { return 0; }
}
/// <summary>
/// The maximum length of the field displayed as a string for descriptors
/// </summary>
public string MaxLength
{
get { return _unPackedLength.ToString(); }
}
/// <summary>
/// Descriptor for the length formatter used in ToString methods
/// </summary>
public string Description
{
get { return "FixedBcd"; }
}
/// <summary>
/// Get the length of the field
/// </summary>
/// <param name = "msg">Byte array of message data</param>
/// <param name = "offset">offset to start parsing</param>
/// <returns>The length of the field</returns>
public int GetLengthOfField(byte[] msg, int offset)
{
return _unPackedLength;
}
/// <summary>
/// Pack the length header into the message
/// </summary>
/// <param name = "msg">Byte array of the message</param>
/// <param name = "length">The length to pack into the message</param>
/// <param name = "offset">Offset to start the packing</param>
/// <returns>offset for the start of the field</returns>
public int Pack(byte[] msg, int length, int offset)
{
return offset;
}
/// <summary>
/// Check the length of the field is valid
/// </summary>
/// <param name = "packedLength">the packed length of the field</param>
/// <returns>true if valid, false otherwise</returns>
public bool IsValidLength(int packedLength)
{
return packedLength == _packedLength;
}
#endregion
}
Modified the FieldDescription class / BcdFixed declaration
///
/// The bcd fixed.
///
///
/// The length.
///
///
///
public static IFieldDescriptor BcdFixed(int unpackedLength)
{
return Create(new FixedLengthBcdFormatter(unpackedLength), FieldValidators.N, Formatters.Bcd, null);
}
Then change your formatter declaration to provide the unpacked length as the paramter.
Bit._003_PROC_CODE, FieldDescriptor.BcdFixed(6)},
Again, all this may have been unnecessary because I didn't know something about the existing code, but it is working for me.
I hope this helps.

Where to find C# sample code to implement password recovery in ASP .NET MVC2

How to implement password reset in MVC2 application?
Passwords are hashed using ASP .NET membership provider. Password recovery question is not used. Standard ASP .NET MVC2 project template with standard AccountController class is used.
If user forgots password, email with temporary link or with new password should sent to user e-mail address .
Where to find code to implement this in MVC 2 C# ?
stack overflow contains two answers which discuss methods about implementing this. There is not sample code.
I googled for "asp .net mvc password reset c# sample code download" but havent found sample code for this.
I'm new to MVC. Where to find sample code for password recovery? This is missing from VS2010 generated project template.
Update
I tried this code in Mono 2.10 but got exception:
CspParameters not supported by Mono
at line
des.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, new byte[8]);
How to run it in Mono ?
Stack Trace:
System.NotSupportedException: CspParameters not supported by Mono
at System.Security.Cryptography.PasswordDeriveBytes.CryptDeriveKey (string,string,int,byte[]) [0x0001b] in /usr/src/redhat/BUILD/mono-2.10.2/mcs/class/corlib/System.Security.Cryptography/PasswordDeriveBytes.cs:197
at store2.Helpers.Password.EncodeMessageWithPassword (string,string) <IL 0x00055, 0x000f3>
at store2.Helpers.AccountHelper.GetTokenForValidation (string) <IL 0x00033, 0x00089>
at MvcMusicStore.Controllers.AccountController.PasswordReminder (MvcMusicStore.Models.PasswordReminderModel) <IL 0x001ac, 0x00495>
at (wrapper dynamic-method) System.Runtime.CompilerServices.ExecutionScope.lambda_method (System.Runtime.CompilerServices.ExecutionScope,System.Web.Mvc.ControllerBase,object[]) <IL 0x00020, 0x0005b>
at System.Web.Mvc.ActionMethodDispatcher.Execute (System.Web.Mvc.ControllerBase,object[]) <IL 0x00008, 0x0001b>
at System.Web.Mvc.ReflectedActionDescriptor.Execute (System.Web.Mvc.ControllerContext,System.Collections.Generic.IDictionary`2<string, object>) <IL 0x00072, 0x00103>
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod (System.Web.Mvc.ControllerContext,System.Web.Mvc.ActionDescriptor,System.Collections.Generic.IDictionary`2<string, object>) <IL 0x00003, 0x00019>
at System.Web.Mvc.ControllerActionInvoker/<>c__DisplayClassd.<InvokeActionMethodWithFilters>b__a () <IL 0x0002d, 0x00068>
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter (System.Web.Mvc.IActionFilter,System.Web.Mvc.ActionExecutingContext,System.Func`1<System.Web.Mvc.ActionExecutedContext>) <IL 0x00031, 0x000b6>
--------------------------------------------------------------------------------
Version information: Mono Runtime Version: 2.10.2 (tarball Mon Apr 18 18:57:39 UTC 2011); ASP.NET Version: 2.0.50727.1433
Here is my approach. In MVC you will have an action called RetrievePassword where you will ask for the user's email address and pass it in a post
[HttpGet]
public ActionResult RetrievePassword()
{
return View();
}
[HttpPost]
public ActionResult RetrievePassword(PasswordRetrievalModel model)
{
if (ModelState.IsValid)
{
string username = Membership.GetUserNameByEmail(model.Email);
if (!String.IsNullOrEmpty(username))
{
// This is a helper function that sends an email with a token (an MD5).
NotificationsHelper.SendPasswordRetrieval(model.Email, this.ControllerContext);
}
else
{
Trace.WriteLine(String.Format("*** WARNING: A user tried to retrieve their password but the email address used '{0}' does not exist in the database.", model.Email));
}
return RedirectToAction("Index", "Home");
}
return View(model);
}
An email message will be sent with a url that redirects to http://example.com/Account/Validate?email=xxxxxxxx&token=xxxxxxxx
If the token is valid for the email, you will probably display a password reset form so they choose a new password.
So you need a Validate Action:
[HttpGet]
[CompressFilter]
public ActionResult Validate(string email, string token)
{
bool isValid = false;
if (AccountHelper.IsTokenValid(token, email))
{
string username = Membership.GetUserNameByEmail(email);
if (!String.IsNullOrEmpty(username))
{
// Get the user and approve it.
MembershipUser user = Membership.GetUser(username);
user.IsApproved = true;
Membership.UpdateUser(user);
isValid = true;
// Since it was a successful validation, authenticate the user.
FormsAuthentication.SetAuthCookie(username, false);
}
else
{
isValid = false;
}
}
return View(isValid);
}
Here are some of the helpers you see in this code:
Account Helper
/// <summary>
/// Gets the token for invitation.
/// </summary>
/// <param name="email">The email.</param>
/// <returns></returns>
public static string GetTokenForInvitation(string email)
{
if (String.IsNullOrEmpty(email))
throw new ArgumentException("The email cannot be null");
string token = Password.EncodeMessageWithPassword(String.Format("{0}#{1}", email, DateTime.Now), SEED);
return token;
}
/// <summary>
/// Gets the email from token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="email">The email.</param>
/// <returns></returns>
public static bool GetEmailFromToken(string token, out string email)
{
email = String.Empty;
string message = Password.DecodeMessageWithPassword(token, SEED);
string[] messageParts = message.Split('#');
if (messageParts.Count() != 2)
{
return false;
// the token was not generated correctly.
}
else
{
email = messageParts[0];
return true;
}
}
/// <summary>
/// Helper function used to generate a token to be used in the message sent to users when registered the first time to confirm their email address.
/// </summary>
/// <param name="email">The email address to encode.</param>
/// <returns>The token generated from the email address, timestamp, and SEED value.</returns>
public static string GetTokenForValidation(string email)
{
if (String.IsNullOrEmpty(email))
throw new ArgumentException("The email cannot be null");
string token = Password.EncodeMessageWithPassword(String.Format("{0}#{1}", email, DateTime.Now), SEED);
return token;
}
/// <summary>
/// Validates whether a given token is valid for a determined email address.
/// </summary>
/// <param name="token">The token to validate.</param>
/// <param name="email">The email address to use in the validation.</param>
/// <returns><c>true</c> if the token is valid, <c>false</c> otherwise.</returns>
public static bool IsTokenValid(string token, string email)
{
return IsTokenValid(token, email, DateTime.Now);
}
/// <summary>
/// Core method to validate a token that also offers a timestamp for testing. In production mode should always be DateTime.Now.
/// </summary>
/// <param name="token">The token to validate.</param>
/// <param name="email">the email address to use in the validation.</param>
/// <param name="timestamp">The timestamp representing the time in which the validation is performed.</param>
/// <returns><c>true</c> if the token is valid, <c>false</c> otherwise.</returns>
public static bool IsTokenValid(string token, string email, DateTime timestamp)
{
if (String.IsNullOrEmpty(token))
throw new ArgumentException("The token cannot be null");
try
{
string message = Password.DecodeMessageWithPassword(token, SEED);
string[] messageParts = message.Split('#');
if (messageParts.Count() != 2)
{
return false;
// the token was not generated correctly.
}
else
{
string messageEmail = messageParts[0];
string messageDate = messageParts[1];
// If the emails are the same and the date in which the token was created is no longer than 5 days, then it is valid. Otherwise, it is not.
return (String.Compare(email, messageEmail, true) == 0 && timestamp.Subtract(DateTime.Parse(messageDate)).Days < 5);
}
}
catch (Exception)
{
// could not decrypt the message. The token has been tampered with.
return false;
}
}
And Finally here some code to encrypt, decript a token...
I have it in a Password class that is intended to be a helper.
/// EDIT:
Removed the two functions I referenced before and show the full helper class.
Here is the Password static class with all helper functions.
using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Data;
using System.Resources;
namespace MySolution.Common.Util
{
/// <summary>
/// Implements some functions to support password manipulation or generation
/// </summary>
public class Password
{
/// <summary>
/// Takes a string and generates a hash value of 16 bytes.
/// </summary>
/// <param name="str">The string to be hashed</param>
/// <param name="passwordFormat">Selects the hashing algorithm used. Accepted values are "sha1" and "md5".</param>
/// <returns>A hex string of the hashed password.</returns>
public static string EncodeString(string str, string passwordFormat)
{
if (str == null)
return null;
ASCIIEncoding AE = new ASCIIEncoding();
byte[] result;
switch (passwordFormat)
{
case "sha1":
SHA1 sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
result = sha1.ComputeHash(AE.GetBytes(str));
break;
case "md5":
MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
result = md5.ComputeHash(AE.GetBytes(str));
break;
default:
throw new ArgumentException("Invalid format value. Accepted values are 'sha1' and 'md5'.", "passwordFormat");
}
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < result.Length; i++)
{
sb.Append(result[i].ToString("x2"));
}
return sb.ToString();
}
/// <summary>
/// Takes a string and generates a hash value of 16 bytes. Uses "md5" by default.
/// </summary>
/// <param name="str">The string to be hashed</param>
/// <returns>A hex string of the hashed password.</returns>
public static string EncodeString(string str)
{
return EncodeString(str, "md5");
}
/// <summary>
/// Takes a string and generates a hash value of 16 bytes.
/// </summary>
/// <param name="str">The string to be hashed</param>
/// <param name="passwordFormat">Selects the hashing algorithm used. Accepted values are "sha1" and "md5".</param>
/// <returns>A string of the hashed password.</returns>
public static string EncodeBinary(byte[] buffer, string passwordFormat)
{
if (buffer == null)
return null;
byte[] result;
switch (passwordFormat)
{
case "sha1":
SHA1 sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
result = sha1.ComputeHash(buffer);
break;
case "md5":
MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
result = md5.ComputeHash(buffer);
break;
default:
throw new ArgumentException("Invalid format value. Accepted values are 'sha1' and 'md5'.", "passwordFormat");
}
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < result.Length; i++)
{
sb.Append(result[i].ToString("x2"));
}
return sb.ToString();
}
/// <summary>
/// Encodes the buffer using the default cryptographic provider.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns></returns>
public static string EncodeBinary(byte[] buffer)
{
return EncodeBinary(buffer, "md5");
}
/// <summary>
/// Creates a random alphanumeric password.
/// </summary>
/// <returns>A default length character string with the new password.</returns>
/// <remarks>The default length of the password is eight (8) characters.</remarks>
public static string CreateRandomPassword()
{
//Default length is 8 characters
return CreateRandomPassword(8);
}
/// <summary>
/// Creates a random alphanumeric password on dimension (Length).
/// </summary>
/// <param name="Length">The number of characters in the password</param>
/// <returns>The generated password</returns>
public static string CreateRandomPassword(int Length)
{
Random rnd = new Random(Convert.ToInt32(DateTime.Now.Millisecond)); //Creates the seed from the time
string Password="";
while (Password.Length < Length )
{
char newChar = Convert.ToChar((int)((122 - 48 + 1) * rnd.NextDouble() + 48));
if ((((int) newChar) >= ((int) 'A')) & (((int) newChar) <= ((int) 'Z')) | (((int) newChar) >= ((int) 'a')) & (((int) newChar) <= ((int) 'z')) | (((int) newChar) >= ((int) '0')) & (((int) newChar) <= ((int) '9')))
Password += newChar;
}
return Password;
}
/// <summary>
/// Takes a text message and encrypts it using a password as a key.
/// </summary>
/// <param name="plainMessage">A text to encrypt.</param>
/// <param name="password">The password to encrypt the message with.</param>
/// <returns>Encrypted string.</returns>
/// <remarks>This method uses TripleDES symmmectric encryption.</remarks>
public static string EncodeMessageWithPassword(string plainMessage, string password)
{
if (plainMessage == null)
throw new ArgumentNullException("encryptedMessage", "The message cannot be null");
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.IV = new byte[8];
//Creates the key based on the password and stores it in a byte array.
PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new byte[0]);
des.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, new byte[8]);
MemoryStream ms = new MemoryStream(plainMessage.Length * 2);
CryptoStream encStream = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainMessage);
encStream.Write(plainBytes, 0, plainBytes.Length);
encStream.FlushFinalBlock();
byte[] encryptedBytes = new byte[ms.Length];
ms.Position = 0;
ms.Read(encryptedBytes, 0, (int)ms.Length);
encStream.Close();
return Convert.ToBase64String(encryptedBytes);
}
/// <summary>
/// Takes an encrypted message using TripleDES and a password as a key and converts it to the original text message.
/// </summary>
/// <param name="encryptedMessage">The encrypted message to decode.</param>
/// <param name="password">The password to decode the message.</param>
/// <returns>The Decrypted message</returns>
/// <remarks>This method uses TripleDES symmmectric encryption.</remarks>
public static string DecodeMessageWithPassword(string encryptedMessage, string password)
{
if (encryptedMessage == null)
throw new ArgumentNullException("encryptedMessage", "The encrypted message cannot be null");
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.IV = new byte[8];
//Creates the key based on the password and stores it in a byte array.
PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new byte[0]);
des.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, new byte[8]);
//This line protects the + signs that get replaced by spaces when the parameter is not urlencoded when sent.
encryptedMessage = encryptedMessage.Replace(" ", "+");
MemoryStream ms = new MemoryStream(encryptedMessage.Length * 2);
CryptoStream decStream = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
byte[] plainBytes;
try
{
byte[] encBytes = Convert.FromBase64String(Convert.ToString(encryptedMessage));
decStream.Write(encBytes, 0, encBytes.Length);
decStream.FlushFinalBlock();
plainBytes = new byte[ms.Length];
ms.Position = 0;
ms.Read(plainBytes, 0, (int)ms.Length);
decStream.Close();
}
catch(CryptographicException e)
{
throw new ApplicationException("Cannot decrypt message. Possibly, the password is wrong", e);
}
return Encoding.UTF8.GetString(plainBytes);
}
}
}
Set a Reset password GUID in user table. You may also use an expiration time. If user tried to reset password, update the field with a new GUID and datetime for expiration.
Send a link containing the link to reset password with the GUID.
A sample function like this can be created for that
GUID res = objPasswordResetService.resetPassword(Convert.ToInt64(objUserViewModel.UserID), restpasswordGuid, resetPasswordExpiryDateTime);
The value in res can be the GUID updated in DB. Send a link with this GUID. You can check the expiration time also. This is just an idea only
I've got an example of how to implement password recovery in a standard ASP.NET MVC application in my blog.
This blog post assumes that you already have the login process working (database and all) and that you only need to wire the password recovery process.
http://hectorcorrea.com/Blog/Password-Recovery-in-an-ASP.NET-MVC-Project
Answer to implement password reset in MVC2 application
public string ResetPassword(string userName)
{
MembershipUser user = _provider.GetUser(userName, false);
if (user.IsLockedOut)
user.UnlockUser();
user.Comment = null;
_provider.UpdateUser(user);
string newPassword = user.ResetPassword();
string friendlyPassword = GenerateNewPassword();
_provider.ChangePassword(userName, newPassword, friendlyPassword);
return friendlyPassword;
}
private string GenerateNewPassword()
{
string strPwdchar = "abcdefghijklmnopqrstuvwxyz0123456789##$ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string strPwd = "";
Random rnd = new Random();
for (int i = 0; i <= 8; i++)
{
int iRandom = rnd.Next(0, strPwdchar.Length - 1);
strPwd += strPwdchar.Substring(iRandom, 1);
}
return strPwd;
}
here the russian version password recovery

MVCContrib Grid showing headers when empty?

The elegant Action Syntax in the MVCContrib Grid gives us the Empty() method. However, the default behavior of MvcContrib.UI.Grid.GridRenderer<T>.RenderHeader() is to hide the table column headers when the grid is empty. Is there a way to show headers when data is not present that does not require a major refactoring?
Now I have heard of hiding the headers by default and hard-coding something but this is not cool to me.
By the way, this is what is happening under the hood (in MvcContrib.UI.Grid.GridRenderer<T>):
protected virtual bool RenderHeader()
{
//No items - do not render a header.
if(! ShouldRenderHeader()) return false;
RenderHeadStart();
foreach(var column in VisibleColumns())
{
//Allow for custom header overrides.
if(column.CustomHeaderRenderer != null)
{
column.CustomHeaderRenderer(new RenderingContext(Writer, Context, _engines));
}
else
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
}
RenderHeadEnd();
return true;
}
protected virtual bool ShouldRenderHeader()
{
return !IsDataSourceEmpty();
}
protected bool IsDataSourceEmpty()
{
return DataSource == null || !DataSource.Any();
}
You can override the ShouldRenderHeader() method of the HtmlTableGridRenderer class.
public class AlwaysRenderHeaderRenderer<T>
: HtmlTableGridRenderer<T> where T : class
{
protected override bool ShouldRenderHeader()
{
return true;
}
}
<%= Html.Grid(Model).RenderUsing(new AlwaysRenderHeaderRenderer<TypeOfModel>()) %>
A side effect of this approach is that when the grid is empty, an empty table body will be rendered instead of a message. Any text provided to Empty() is ignored. This wasn't a problem for me since I'm manipulating the table on the client side with JavaScript anyway, but be warned.
When inheriting from HtmlTableGridRenderer you can also override RenderEmpty to eliminate the problem Brant ran into.
protected override void RenderEmpty()
{
RenderHeadStart();
foreach(var column in VisibleColumns())
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
RenderHeadEnd();
RenderBodyStart();
RenderText("<tr><td colspan=\"" + VisibleColumns().Count() + "\">" + GridModel.EmptyText + "</td></tr>");
RenderBodyEnd();
}
In your custom Grid Renderer (subclass GridRenderer<T>) use overrides like these:
/// <summary>
/// Renders the <c>table</c> header.
/// </summary>
/// <returns>
/// Returns the negative of the results
/// of <see cref="GridRenderer<T>.IsDataSourceEmpty"/>.
/// </returns>
/// <remarks>
/// The return value of <see cref="GridRenderer<T>.IsDataSourceEmpty"/>
/// in this override has no effect on whether the Grid header is rendered.
///
/// However, this return value is used
/// by <see cref="GridRenderer<T>.Render"/>
/// to run <see cref="GridRenderer<T>.RenderItems"/>
/// or <see cref="GridRenderer<T>.RenderEmpty"/>.
/// </remarks>
protected override bool RenderHeader()
{
RenderHeadStart();
foreach(var column in VisibleColumns())
{
//Allow for custom header overrides.
if(column.CustomHeaderRenderer != null)
{
column.CustomHeaderRenderer(new RenderingContext(Writer, Context, _engines));
}
else
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
}
RenderHeadEnd();
return !base.IsDataSourceEmpty();
}
…
protected override void RenderEmpty()
{
RenderBodyStart();
WriteNoRecordsAvailable(base.Writer, this._numberOfTableColumns);
RenderBodyEnd();
}
Note that WriteNoRecordsAvailable() is my custom method so it can be ignored.
Finally:
/// <summary>
/// This private member is duplicated
/// in order to override <see cref="GridRenderer<T>.RenderHeader"/>.
/// </summary>
readonly ViewEngineCollection _engines;
…
/// <summary>
/// Initializes a new instance of the <see cref="CrmHtmlTableGridRenderer<T>"/> class.
/// </summary>
/// <param name="engines">The engines.</param>
public CrmHtmlTableGridRenderer(ViewEngineCollection engines)
: base(engines)
{
_engines = engines;
}
Can't you just comment out the check to see if it should render the header. Am I missing something or do you want it to always display the header?
If so then comment out that line.
//if(! ShouldRenderHeader()) return false;
I haven't looks at all the code but from your code snippet it seems like that should work.
A combination of David's and Brant's answers:
protected override bool ShouldRenderHeader()
{
return true;
}
// Oddly Render relies on ShouldRenderHeader to return IsDataSourceEmpty
// so RenderItems will always be called.
protected override void RenderItems()
{
if (IsDataSourceEmpty())
RenderEmpty();
else
base.RenderItems();
}
protected override void RenderEmpty()
{
RenderBodyStart();
RenderText("<tr><td colspan=\"" + VisibleColumns().Count() + "\">" + GridModel.EmptyText + "</td></tr>");
RenderBodyEnd();
}

Resources