`Feedback` text not shown in bot conversation - botframework

My formflow dialog contains a field with custom validation applied...
var form = builder
.Field(new FieldReflector<CarValuationDialog>(nameof(UserName))
.SetActive(state => string.IsNullOrEmpty(state.UserName)))
.Field(new FieldReflector<CarValuationDialog>(nameof(ValuationOption))
.SetPrompt(new PromptAttribute($"Hello {{UserName}}.<br /><br />Are you looking to get an price estimate for a car you’re selling, or for a car you’re looking to buy? {{||}}")))
.Field(new FieldReflector<CarValuationDialog>(nameof(RegistrationNumber))
.SetDefine(RegistrationNumberDefinitionMethod))
.Field(new FieldReflector<CarValuationDialog>(nameof(Mileage))
.SetValidate(async (state, value) =>
{
var result = new ValidateResult { IsValid = true, Value = value };
if (int.TryParse(value.ToString(), out int mileage))
{
result.IsValid = true;
}
else
{
result.Feedback = "That isn't valid number. Can you enter it again please?";
result.IsValid = false;
}
return await Task.FromResult(result);
}))
.Field(nameof(PreviousOwnerOption),
active: carValuationDialog => carValuationDialog.ValuationOption == ValuationOptions.LookingToSell)
.Field(nameof(ServiceHistoryOption),
active: carValuationDialog => carValuationDialog.ValuationOption == ValuationOptions.LookingToSell)
.Confirm(Confirmation)
.OnCompletion(GetValuationAndDisplaySummaryToUser);
When SetValidate() executes, and the given mileage value is not an int, the result.Feedback = ... line is executed. However, instead of seeing the message "That isn't a valid mileage value. Can you enter it again please?" I see this...
Full discolsure, I do have the following attribute declared on the dialog class
[Template(TemplateUsage.NotUnderstood, "Sorry, I don't understand '{0}'.")]
but I thought the result.Feedback text would be shown to the user in this case? Not using the Template[] results in the following text displayed when the user enters an invalid mileage...
"d" is not a car mileage option.
Which looks rubbish, and is why I used the Template[] override.

EDIT
I was able to accomplish this by applying templates to the fields themselves for validation. This should be the exact behavior you are looking for. Note that I did not do extensive testing, I just wanted to get the general idea working. Heres the code I used:
[Serializable]
public class SandwichOrder
{
//public PreviousOwnerOptions Options;
[Template(TemplateUsage.NotUnderstood, "Sorry, I don't understand '{0}'.")]
[Prompt("What is the {&}?")]
public int Mileage;
[Template(TemplateUsage.NotUnderstood, "Sorry, This is not valid '{0}'.")]
[Prompt("How many {&}?")]
public int Days;
public static IForm<SandwichOrder> BuildForm()
{
var form = new FormBuilder<SandwichOrder>()
.Field(new FieldReflector<SandwichOrder>(nameof(Mileage))
.SetValidate(async (state, value) =>
{
var result = new ValidateResult {IsValid = true, Value = value};
if (int.TryParse(value.ToString(), out int mileage))
{
result.IsValid = true;
}
else
{
result.IsValid = false;
}
return await Task.FromResult(result);
}))
.Field(new FieldReflector<SandwichOrder>(nameof(Days))
.SetValidate(async (state, value) =>
{
var result = new ValidateResult { IsValid = true, Value = value };
if (int.TryParse(value.ToString(), out int days))
{
result.IsValid = true;
}
else
{
result.IsValid = false;
}
return await Task.FromResult(result);
}));
return form.Build();
}
}
This produced this result:

Related

Dynamics crm + how to get attribute value based on the type dynamically in plugin code

I have the below requirement.
I need to perform the sum of each field across multiple records of the same entity However while performing the sum, I also need to check the type and cast them accrodingly. For eg, For whole number cast to Int, For Decimal cast to decimal. Also some of the values are aliased value too. I am looking for a generic function which I can call for both alias fields and direct fields and it will return me the value based on the type
Background on the code written below -
Attribute List is the list of all attributes that belong to the
entity.
Format in which the field values are stored in AttributeList-
AttributeList = { "price ", "quantity", "contact.revenue", "opportunity.sales"}
price, quantity - fields of main entity on which we are querying
contact.revenue, opportunity.sales - fields of the aliased entities,
entity name is appended to understand which entity's field it is
Below is the code which i have tried so far -
I only have decimal and whole number fields in my attributeList.
private void calculate(List<string> attributeList,List<Entity> mainEntityList,Guid targetId,Guid oppId,Guid contactId)
{
var mainentity = new mainEntity();
mainentity.Id = targetId;
var opportunity = new Opportunity();
opportunity.Id = oppId;
var contact = new Contact();
contact.Id = contactId;
foreach (var attribute in attributeList)
{
var fieldSum = new decimal(0);
int intFieldSum = 0;
bool attributeFound = false;
foreach (var entity in mainEntityList)
{
if (entity.Contains(attribute))
{
var type = entity[attribute].GetType().Name;
attributeFound = true;
switch (type)
{
case "AliasedValue":
var aliasedFieldValue = entity.GetAttributeValue<AliasedValue>(attribute);
if (aliasedFieldValue.Value.GetType().Name == "Decimal")
{
decimalFieldSum += (decimal)aliasedFieldValue.Value;
}
else
{
intFieldSum += (int)aliasedFieldValue.Value;
}
break;
case "Decimal":
decimalFieldSum += entity.GetAttributeValue<decimal>(attribute);
break;
case "Int32":
intFieldSum += entity.GetAttributeValue<int>(attribute);
break;
default:
break;
}
}
}
if (attributeFound)
{
if (attribute.Contains("opportunity"))
{
opportunity[attribute] = decimalFieldSum != 0 ? decimalFieldSum : intFieldSum;
}
else if (attribute.Contains("contact"))
{
contact[attribute] = decimalFieldSum != 0 ? decimalFieldSum : intFieldSum;
}
else
{
mainentity[attribute] = decimalFieldSum != 0 ? decimalFieldSum : intFieldSum;
}
}
}
service.update(opportunity);
service.update(contact);
service.update(mainentity);
}
Any help would be appreciated.
Just a little bit edited your code.
...
var fieldSum = new decimal(0);
foreach (var entity in mainEntityList)
{
fieldSum += GetAttrValue(entity, attribute);
}
...
You can use this function to calculate fieldSum variable which is of decimal type.
private decimal GetAttrValue(Entity entity, string attribute)
{
var attrValue = new decimal(0);
if (!entity.Contains(attribute) || entity.Attributes[attribute] == null)
{
return attrValue;
}
var type = entity.Attributes[attribute].GetType().Name;
switch (type)
{
case "AliasedValue":
var aliasedFieldValue = entity.GetAttributeValue<AliasedValue>(attribute);
attrValue = type == "Decimal" ? (decimal)aliasedFieldValue.Value : (int)aliasedFieldValue.Value;
break;
case "Decimal":
attrValue = entity.GetAttributeValue<decimal>(attribute);
break;
case "Int32":
attrValue = entity.GetAttributeValue<int>(attribute);
break;
default:
break;
}
return attrValue;
}
On the other hand if you just need a generic function which will return decimal or int value for an attribute you can use this
private T GetAttrValue<T>(Entity entity, string attribute)
{
if (!entity.Contains(attribute) || entity.Attributes[attribute] == null)
{
return default(T);
}
T result;
var type = entity.Attributes[attribute].GetType().Name;
if (type == "AliasedValue")
{
var aliasedFieldValue = entity.GetAttributeValue<AliasedValue>(attribute);
result = (T)aliasedFieldValue.Value;
}
else
{
result = entity.GetAttributeValue<T>(attribute);
}
return result;
}
--Update--
So, here is the whole code if I understand you requirements right.
First of all add this class.
public class AttributeInfo
{
public string Name { get; set; }
public Type Type { get; set; }
public decimal DecimalSum { get; set; } = new decimal(0);
public int IntSum { get; set; } = 0;
}
And add this function
private void SetValue(Entity entity, AttributeInfo attributeInfo)
{
if (entity.Contains(attributeInfo.Name))
{
switch (attributeInfo.Type.Name)
{
case "Decimal":
entity[attributeInfo.Name] = attributeInfo.DecimalSum;
break;
case "Int32":
entity[attributeInfo.Name] = attributeInfo.IntSum;
break;
default:
break;
}
}
}
Then this is you Calculate function
private void Calculate(List<string> attributeList, List<Entity> mainEntityList, Guid targetId, Guid oppId, Guid contactId)
{
var mainentity = new mainEntity();
mainentity.Id = targetId;
var opportunity = new Opportunity();
opportunity.Id = oppId;
var contact = new Contact();
contact.Id = contactId;
var attributesInfo = new List<AttributeInfo>();
foreach (var attribute in attributeList)
{
var attributeInfo = new AttributeInfo
{
Name = attribute
};
foreach (var entity in mainEntityList)
{
if (entity.Contains(attribute))
{
attributeInfo.Type = entity[attribute].GetType();
switch (attributeInfo.Type.Name)
{
case "AliasedValue":
var aliasedFieldValue = entity.GetAttributeValue<AliasedValue>(attribute);
if (aliasedFieldValue.Value.GetType().Name == "Decimal")
{
attributeInfo.DecimalSum += (decimal)aliasedFieldValue.Value;
}
else
{
attributeInfo.IntSum += (int)aliasedFieldValue.Value;
}
break;
case "Decimal":
attributeInfo.DecimalSum += entity.GetAttributeValue<decimal>(attribute);
break;
case "Int32":
attributeInfo.IntSum += entity.GetAttributeValue<int>(attribute);
break;
default:
break;
}
}
}
attributesInfo.Add(attributeInfo);
}
foreach (var attributeInfo in attributesInfo)
{
if (attributeInfo.Type != null)
{
SetValue(mainentity, attributeInfo);
SetValue(opportunity, attributeInfo);
SetValue(contact, attributeInfo);
}
}
service.update(mainentity);
service.update(opportunity);
service.update(contact);
}
I should say that the structure of the calculate function still seems weird for me. However, here I tried to keep the main structure.

ASP.NET MVC validation return lowercase property name

In my ASP.NET MVC Core web application the Json serialization of properties is set to camel case (with first letter lowercase):
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(opt =>
{
opt.SerializerSettings.ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() };
opt.SerializerSettings.Converters.Add(new StringEnumConverter(true));
});
The serialization to the client is working as expected.
But when the javascript client tries to post data and this data is not valid, he receives a validation message with capital letter properties, this validation messages are the ModelState:
{"Info":["The Info field is required."]}
Is there a way to make ASP.NET return lowercase property in validation messages of the ModelState to reflect the naming strategy?
The solution is to disable the automatic api validation filter and create own json result with the validation messages:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
And in the controller:
protected ActionResult ValidationFailed()
{
var errorList = ModelState.ToDictionary(
kvp => kvp.Key.ToCamelCase(),
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);
return BadRequest(errorList);
}
public async Task<ActionResult> Create([FromBody]TCreateDto model)
{
if (ModelState.IsValid == false)
{
return ValidationFailed();
}
...
}
The string helper method:
public static string ToCamelCase(this string name)
{
if (string.IsNullOrEmpty(name))
{
return name;
}
return name.Substring(0, 1).ToLower() + name.Substring(1);
}
There is an easier solution. Use Fluent Validator's ValidatorOptions.Global.PropertyNameResolver. Taken from here and converted to C# 8 and Fluent Validation 9:
In Startup.cs, ConfigureServices use:
services
.AddControllers()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddFluentValidation(fv =>
{
fv.RegisterValidatorsFromAssemblyContaining<MyValidator>();
// Convert property names to camelCase as Asp.Net Core does https://github.com/FluentValidation/FluentValidation/issues/226
ValidatorOptions.Global.PropertyNameResolver = CamelCasePropertyNameResolver.ResolvePropertyName;
})
.AddNewtonsoftJson(NewtonsoftUtils.SetupNewtonsoftOptionsDefaults);
and resolver itself:
/// <summary>
/// Convert property names to camelCase as Asp.Net Core does
/// https://github.com/FluentValidation/FluentValidation/issues/226
/// </summary>
public class CamelCasePropertyNameResolver
{
public static string? ResolvePropertyName(Type type, MemberInfo memberInfo, LambdaExpression expression)
{
return ToCamelCase(DefaultPropertyNameResolver(type, memberInfo, expression));
}
private static string? DefaultPropertyNameResolver(Type type, MemberInfo memberInfo, LambdaExpression expression)
{
if (expression != null)
{
var chain = PropertyChain.FromExpression(expression);
if (chain.Count > 0)
{
return chain.ToString();
}
}
if (memberInfo != null)
{
return memberInfo.Name;
}
return null;
}
private static string? ToCamelCase(string? s)
{
if (string.IsNullOrEmpty(s) || !char.IsUpper(s[0]))
{
return s;
}
var chars = s.ToCharArray();
for (var i = 0; i < chars.Length; i++)
{
if (i == 1 && !char.IsUpper(chars[i]))
{
break;
}
var hasNext = (i + 1 < chars.Length);
if (i > 0 && hasNext && !char.IsUpper(chars[i + 1]))
{
break;
}
chars[i] = char.ToLower(chars[i], CultureInfo.InvariantCulture);
}
return new string(chars);
}
}
I have faced the same issue. I have overridden DefaultProblemDetailsFactory.cs from the source code and add logic to change the first letters in the 'errors' dictionary.
Steps:
1 - Create new CustomProblemDetailsFactory.cs class:
internal sealed class CustomProblemDetailsFactory : ProblemDetailsFactory
{
private readonly ApiBehaviorOptions _options;
public CustomProblemDetailsFactory(IOptions<ApiBehaviorOptions> options)
{
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public override ProblemDetails CreateProblemDetails(
HttpContext httpContext,
int? statusCode = null,
string? title = null,
string? type = null,
string? detail = null,
string? instance = null)
{
statusCode ??= 500;
var problemDetails = new ProblemDetails
{
Status = statusCode,
Title = title,
Type = type,
Detail = detail,
Instance = instance,
};
ApplyProblemDetailsDefaults(httpContext, problemDetails, statusCode.Value);
return problemDetails;
}
public override ValidationProblemDetails CreateValidationProblemDetails(
HttpContext httpContext,
ModelStateDictionary modelStateDictionary,
int? statusCode = null,
string? title = null,
string? type = null,
string? detail = null,
string? instance = null)
{
if (modelStateDictionary == null)
{
throw new ArgumentNullException(nameof(modelStateDictionary));
}
statusCode ??= 400;
var problemDetails = new ValidationProblemDetails(modelStateDictionary)
{
Status = statusCode,
Type = type,
Detail = detail,
Instance = instance,
};
if (title != null)
{
// For validation problem details, don't overwrite the default title with null.
problemDetails.Title = title;
}
// FIX LOWERCASE, MAKE THE FIRST LETTERS LOWERCASE
///-----------------------------
if (problemDetails.Errors != null)
{
var newErrors = problemDetails.Errors.ToDictionary(x => this.MakeFirstLetterLowercase(x.Key), x => x.Value);
problemDetails.Errors.Clear();
foreach (var keyValue in newErrors)
{
problemDetails.Errors.Add(keyValue.Key, keyValue.Value);
}
}
///-----------------------------
ApplyProblemDetailsDefaults(httpContext, problemDetails, statusCode.Value);
return problemDetails;
}
private void ApplyProblemDetailsDefaults(HttpContext httpContext, ProblemDetails problemDetails, int statusCode)
{
problemDetails.Status ??= statusCode;
if (_options.ClientErrorMapping.TryGetValue(statusCode, out var clientErrorData))
{
problemDetails.Title ??= clientErrorData.Title;
problemDetails.Type ??= clientErrorData.Link;
}
var traceId = Activity.Current?.Id ?? httpContext?.TraceIdentifier;
if (traceId != null)
{
problemDetails.Extensions["traceId"] = traceId;
}
}
private string MakeFirstLetterLowercase(string str)
{
if (!string.IsNullOrEmpty(str) && char.IsUpper(str[0]))
{
return str.Length == 1 ? char.ToLower(str[0]).ToString() : char.ToLower(str[0]) + str[1..];
}
return str;
}
}
2 - In the Startup.cs override the default ProblemDetailsFactory:
services.AddSingleton<ProblemDetailsFactory, CustomProblemDetailsFactory>();
After that all keys in the dictionary 'errors' will start with lowercase

How to allow the user only if he/she type the “ivr” or “IVR” using Form Flow concept

I am working on bot framework technology, in one of my current project I want to allow the user only if he/she type the ‘’ivr” or “IVR” otherwise it shows some feedback to the user.
For that I have wrote below lines of code, but this code shows some wrong output to the user. Even if the user enter ivr or IVR it shows feedback to the user for the first time, but from second time onwards its working correctly.
[Serializable]
class Customer
{
//Create Account Template
[Prompt("Please send any of these commands like **IVR** (or) **ivr**.")]
public string StartingWord;
public static IForm<Customer> BuildForm()
{
OnCompletionAsyncDelegate<Customer> accountStatus = async (context, state) =>
{
await Task.Delay(TimeSpan.FromSeconds(5));
await context.PostAsync("We are currently processing your account details. We will message you the status.");
};
var builder = new FormBuilder<Customer>();
return builder
//.Message("Welcome to the BankIVR bot! To start an conversation with this bot send **ivr** or **IVR** command.\r \n if you need help, send the **Help** command")
.Field(nameof(Customer.StartingWord), validate: async (state, response) =>
{
var result = new ValidateResult { IsValid = true, Value = response };
string str = (response as string);
if (str.ToLower() != "ivr")
{
result.Feedback = "I'm sorry. I didn't understand you.";
result.IsValid = false;
return result;
}
else if (str.ToLower() == "ivr")
{
result.IsValid = true;
return result;
}
else
{
return result;
}
})
.OnCompletion(accountStatus)
.Build();
}
};
Please tell me how to resolve this issue using Form Flow concept.
-Pradeep
Your code looks correct to me - I can only suggest you debug your code with a step-through debugger and see where the logic tests are failing.
That said, if it's not working for people in Turkey, it's because you shouldn't use .ToLower() for normalizing text, for example the .ToLower() method does not work for text that contains the Turkish dotless 'I' character: http://archives.miloush.net/michkap/archive/2004/12/02/273619.html
Also, your else case will never be hit because your two prior checks (!= and ==) cover every possible case (the C# compiler is currently not sophisticated enough to flag the else case as unreachable code).
The correct way to do a case-insensitive comparison is with String.Equals:
if( "ivr".Equals( str, StringComparison.InvariantCultureIgnoreCase ) ) {
result.IsValid = true;
return result;
}
else {
result.Feedback = "I'm sorry. I didn't understand you.";
result.IsValid = false;
}
Finally, I got the result with out any issue.
here is my updated code for to allow only the user enter "ivr or IVR" word, to start a form flow conversation with bot.
.Field(nameof(Customer.StartingWord), validate: async (state, response) =>
{
var result = new ValidateResult { IsValid = true, Value = response };
string str = (response as string);
if ("ivr".Equals(str, StringComparison.InvariantCultureIgnoreCase))
{
//result.IsValid = true;
//return result;
}
else
{
result.Feedback = "I'm sorry. I didn't understand you.";
result.IsValid = false;
//return result;
}
return result;
})
-Pradeep

Trying to save comma-separated list

Trying to save selections from a CheckBoxList as a comma-separated list (string) in DB (one or more choices selected). I am using a proxy in order to save as a string because otherwise I'd have to create separate tables in the DB for a relation - the work is not worth it for this simple scenario and I was hoping that I could just convert it to a string and avoid that.
The CheckBoxList uses an enum for it's choices:
public enum Selection
{
Selection1,
Selection2,
Selection3
}
Not to be convoluted, but I use [Display(Name="Choice 1")] and an extension class to display something friendly on the UI. Not sure if I can save that string instead of just the enum, although I think if I save as enum it's not a big deal for me to "display" the friendly string on UI on some confirmation page.
This is the "Record" class that saves a string in the DB:
public virtual string MyCheckBox { get; set; }
This is the "Proxy", which is some sample I found but not directly dealing with enum, and which uses IEnumerable<string> (or should it be IEnumerable<Selection>?):
public IEnumerable<string> MyCheckBox
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyCheckBox)) return new string[] { };
return Record
.MyCheckBox
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set
{
Record.MyCheckBox = value == null ? null : String.Join(",", value);
}
}
To save in the DB, I am trying to do this in a create class:
proxy.MyCheckBox = record.MyCheckBox; //getting error here
but am getting the error:
Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable'
I don't know, if it's possible or better, to use Parse or ToString from the API for enum values.
I know that doing something like this will store whatever I put in the ("") into the DB, so it's just a matter of figuring out how to overcome the error (or, if there is an alternative):
proxy.MyCheckBox = new[] {"foo", "bar"};
I am not good with this stuff and have just been digging and digging to come up with a solution. Any help is much appreciated.
You can accomplish this using a custom user type. The example below uses an ISet<string> on the class and stores the values as a delimited string.
[Serializable]
public class CommaDelimitedSet : IUserType
{
const string delimiter = ",";
#region IUserType Members
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xSet = x as ISet<string>;
var ySet = y as ISet<string>;
if (xSet == null || ySet == null)
{
return false;
}
// compare set contents
return xSet.Except(ySet).Count() == 0 && ySet.Except(xSet).Count() == 0;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new HashSet<string>();
}
else
{
var splitArray = outValue.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries);
return new HashSet<string>(splitArray);
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as ISet<string>;
object setValue = inValue == null ? null : string.Join(Delimiter, inValue);
NHibernateUtil.String.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
// return new ISet so that Equals can work
// see http://www.mail-archive.com/nhusers#googlegroups.com/msg11054.html
var set = value as ISet<string>;
if (set == null)
{
return null;
}
return new HashSet<string>(set);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.String)}; }
}
public Type ReturnedType
{
get { return typeof(ISet<string>); }
}
public bool IsMutable
{
get { return false; }
}
#endregion
}
Usage in mapping file:
Map(x => x.CheckboxValues.CustomType<CommaDelimitedSet>();

Is there some way to clean up this controller code?

Most of the code I see online on MVC3 has very little code in the controller, but I can't seem to figure out how to make this code more streamlined.
Maybe if you take a look at it you can suggest improvements. If you need to see my UserModel class, let me know.
Here's the code for the Account controller.
namespace WebUI.Controllers
{
public class AccountController : Controller
{
public ActionResult Register()
{
UserModel model = new UserModel();
EFCityRepository cityRepo = new EFCityRepository();
model.Cities = new List<SelectListItem>();
foreach (var city in cityRepo.FindAllCities()) {
model.Cities.Add(new SelectListItem { Text = city.Name, Value = city.CityId.ToString(), Selected = true });
}
EFGenderRepository genderRepo = new EFGenderRepository();
model.Genders = new List<SelectListItem>();
foreach (var gender in genderRepo.FindAllGenders()) {
model.Genders.Add(new SelectListItem { Text = gender.Name, Value = gender.GenderId.ToString(), Selected = true });
}
return View(model);
}
[HttpPost]
public ActionResult Register(UserModel model)
{
EFCityRepository cityRepo = new EFCityRepository();
model.Cities = new List<SelectListItem>();
foreach (var city in cityRepo.FindAllCities())
{
model.Cities.Add(new SelectListItem { Text = city.Name, Value = city.CityId.ToString(), Selected = true });
}
EFGenderRepository genderRepo = new EFGenderRepository();
model.Genders = new List<SelectListItem>();
foreach (var gender in genderRepo.FindAllGenders())
{
model.Genders.Add(new SelectListItem { Text = gender.Name, Value = gender.GenderId.ToString(), Selected = true });
}
if (ModelState.IsValid)
{
Domain.User user = new Domain.User();
user.UserRoleId = 1;
user.Nickname = model.Nickname;
user.Name = model.Name;
user.Lastname = model.Lastname;
user.GenderId = model.GenderId;
user.Address = model.Address;
user.Email = model.Email;
user.Telephone = model.Telephone;
user.MobilePhone = model.MobilePhone;
user.Carnet = model.Carnet;
user.DateOfBirth = model.DateOfBirth;
user.DateOfRegistry = DateTime.Now;
user.LastDateLogin = DateTime.Now;
user.IsActive = false;
user.LanceCreditBalance = 5;
user.LancesSpent = 0;
user.Login = model.Login;
user.Password = model.Password;
user.EmailVerificationCode = "TempTokenString";
user.CityId = model.CityId;
EFUserRepository repo = new EFUserRepository();
var result = repo.CreateUser(user);
if (result == UserCreationResults.Ok)
{
FormsAuthentication.SetAuthCookie(model.Nickname, false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
switch (result)
{
case UserCreationResults.UsernameExists:
ModelState.AddModelError("", "El nombre de usuario ya esta siendo utilizado.");
break;
case UserCreationResults.EmailAlreadyExists:
ModelState.AddModelError("", "Ese correo ya esta en uso.");
break;
case UserCreationResults.NicknameAlreadyExists:
ModelState.AddModelError("", "El nickname ya esta siendo utilizado.");
break;
case UserCreationResults.UnknownError:
ModelState.AddModelError("", "Algo durante el registro. Por favor intente de nuevo.");
break;
default:
break;
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
}
I'm using Entity Framework as my ORM, it generates a User class automatically for me. However, I made a User*Model* class so I could add data annotations for the views to use. Maybe this is the wrong idea?
I have many suggestions. For startes, read up about Dependancy Injection and Inversion of Control (DI and IoC). They will make all that boilerplate object instantiation a thing of the past.
Next, convert those for-each list builders into Linq expressions. Much more succinct and more likely faster as well.
Then, in your post handler, again do the same things there. In addition, get to know AutoMapper, which will automatically map your view to domain classes and make your life much easier.
If you did those things, your code would be reduced by 2x, maybe even 3.
EDIT:
An example linq query, because I really don't know the definition of your objects... would look something like this:
model.Cities = cityRepo.FindAllCities().Select(city => new SelectListItem() {
Text = city.Name, Value = city.CityId.ToString()}).ToList();
Notice how you don't have to new up a new List, since that's returned by the ToList() method. It is also using projection to select the items into a new SelectListItem.
Basically, you could write your method like this, using Dependancy Injection, Linq, and AutoMapper (it looks longer because i had to break lines multiple times to fit the small viewing are of SO):
namespace WebUI.Controllers
{
public class AccountController : Controller
{
private IGenderRepository _genderRepo;
private ICityrRepository _cityRepo;
private IUserRepository _userRepo;
public AccountController(IGenderRepository gr, ICityRepository cr,
IUserRepository ur)
{
_genderRepo = gr;
_cityRepo = cr;
_userRepo = ur;
}
public ActionResult Register()
{
UserModel model = new UserModel();
// Selected property is ignored by MVC on SelectListItems
model.Cities = _cityRepo.FindAllCities().Select(city =>
new SelectListItem() { Text = city.Name,
Value = city.CityId.ToString()}).ToList();
model.Genders = _genderRepo.FindAllGenders().Select(gender =>
new SelectListItem() { Text = gender.Name,
Value = gender.GenderId.ToString()}).ToList();
return View(model);
}
[HttpPost]
public ActionResult Register(UserModel model)
{
model.Cities = _cityRepo.FindAllCities().Select(city =>
new SelectListItem() { Text = city.Name,
Value = city.CityId.ToString()}).ToList();
model.Genders = _genderRepo.FindAllGenders().Select(gender =>
new SelectListItem() { Text = gender.Name,
Value = gender.GenderId.ToString()}).ToList();
if (ModelState.IsValid)
{
Domain.User user = Mapper.Map<Domain.User, Model>(model)
var result = _userRepo.CreateUser(user);
if (result == UserCreationResults.Ok) {
FormsAuthentication.SetAuthCookie(model.Nickname, false);
return RedirectToAction("Index", "Home");
} else {
ModelState.AddModelError("", GetErrorString(result));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
}
your Idea is good. Because it is not a good practice to have entity classes as the view models in MVC layer because it will create a tight coupling between your presentation and persistence logic.
To cleanup yuor code you can use AutoMapper ( http://automapper.codeplex.com/ ) to map your entity classes to view model easy without writing too many codes. http://jasona.wordpress.com/2010/02/05/getting-started-with-automapper/ here is a good article for you.

Resources