At first I’m thinking of doing this using webservice and basic soap header authentication.
but then after thinking a while , considering of lightweight, cross platform services, and easy to code and structural. I finally make my decision to move this type of project to be under asp.net mvc2.
- by default it uses IHttpAsyncHandler,
- i don’t have a View except for the Home->Index , which is a perfect place for documentation of the service
- on Controller now Return ActionResult, which i than can reuse the code on other method
- Use Routing which than make easier for other user to remember
- Straight forward HttpPost and Get with Nice Folder Structural, “a mail without the envelope”
under Model ,
- I add folders based on controller Name , and put the Class file in it
- For what ever relates to database i named it [Controller]Repository.cs and also create the interface and implement it
- for other than which related to business logic let’s called it [Controller]ViewModel.cs
For Global/Common Class:
- I simply make new folder called it common, here is the utility class, Error Number Enum and all class act as global related to that application
View
- By default add View under a View->[Controller] Folder
- add Master Page on the Shared
Now we make new class which Inherit from Action Result, Action Result only have a abstract method ExecuteResult.
1: public class MyResult<T>:ActionResult
2: {
3: public T Data { get; set; }
4: public MyResult(T data)
5: {
6: this.Data = data;
7: }
8: public override void ExecuteResult(ControllerContext context)
9: {
10: string dataTypeReturn=context.RouteData.Values["datatype"].ToString();
11: if (dataTypeReturn == "xml" && this.Data != null)
12: {
13: var xmlserializer = new System.Xml.Serialization.XmlSerializer(this.Data.GetType());
14: context.HttpContext.Response.ContentType = "text/xml";
15: xmlserializer.Serialize(context.HttpContext.Response.Output, this.Data);
16:
17: }
18: else if (dataTypeReturn == "json" && this.Data!=null)
19: {
20: HttpResponseBase response = context.HttpContext.Response;
21: JsonTextWriter writer = new JsonTextWriter(response.Output) { Formatting = Formatting.Indented };
22: var SerializerSettings = new JsonSerializerSettings();
23: JsonSerializer serializer = JsonSerializer.Create(SerializerSettings);
24: serializer.Serialize(writer, Data);
25: writer.Flush();
26: context.HttpContext.Response.ContentType = "application/json";
27: }
28: }
29: }
I use the NewtonSoft Json dll for the json, yes i know on 3.5 there is jsonserializer. but i choose this one.Below is just an example on how to use it, just to get the idea
1: public ActionResult Login(string dataType, string username,string password)
2: {
3: try
4: {
5: if (Membership.ValidateUser(username, password))
6: {
7: var result = memberDetailsRepository.Login(username);
8: if (result == null)
9: return Utility.SetError((int)ErrorNumber.AccountNotExists, "Account does not exists");
10: return new MyResult<LoginResult>(result);
11: }
12: else
13: return Utility.SetError((int)ErrorNumber.AccountPasswordIsWrong, "Password is wrong");
14:
15: }
16: catch (Exception ex)
17: {
18: return Utility.SetError((int)ErrorNumber.Unknown, "Unkown");
19:
20: }
21:
22: }
what really nice on mvc is the separation, and i can use the logic of the controller in another controller Method. because of the action result being returned you can than examine the result.
the pattern of the service is : {controller}/{action}/{methodname}/{datatype}/{parameter1}/{parameter2} .. n
now another thing that we will dealing with is , does my Url works with Routing, does it Forwarding correctly?
- Use Route Debugger to see, whether the url you type in , fall into the correct Routing you add on global.asax
- if you declare parameter1 and parameter2, on your controller you must have the Exact Same name or else it will passed in Null
1: routes.MapRoute(
2: "Custom",
3: "{controller}/{action}/{datatype}/{id}",
4: new { controller = "home", action = "Index", datatype = "xml", id = UrlParameter.Optional }, new { datatype = "xml|json" }
5: );
6: routes.MapRoute(
7: "Custom2",
8: "Login/{action}/{datatype}/{username}/{password}",
9: new { controller = "home", action = "Index", datatype = "xml", username = UrlParameter.Optional, password = UrlParameter.Optional }, new { datatype = "xml|json", username = @"\w", password =@"\w"}
10: );
For Default Parameters, it’s important as What MVC must have is Controller and Action
if you don’t have on your URL pattern you supply it on the defaultparameters Object(2nd parameter), as for the 3rd is the constraint you can put any Regex for the parameter.
the Validation work on conjuction with dataAnnotation model from 3.5, which you can define as an external partial class using metaAttribute .
I think the Must follow rule is: Never make a Response.write or anything that relate to the View on the Controller Action, or else you will break the idea of separation.
the controller method must return something even yield an error object, It make sense so that your Controller Action can be reused somewhere else and testable.
when you are using UnitTesting and on you controller action logic there is tryupdatemodel , will not work. this bugs is known on MVC RC2 but still occured on released version.(ah it’s because the controllerContext is null, i don’t see the message
)
Unit Testing With Form Collection, Post Action
wow after go here and there and try to see the source code.
there 2 ways to debug and step into the mvc source:
We can Step through using the mvc source code or Download the debug symbol from microsoft debug server. I finally understand how it works. So in order to test this type of Post:
- we need to fake the ControllerContext using moq
- set the Controller.ValueProvider.
- and if our action method is Strong typed, which mvc will try to mapped it using the DefaultModelBinder class on the GetparametersValue. the end result is if it’s not there it will be the default value, if string than that property is null. so let’s assume it’s already converted and just pass it in
1: [TestMethod]
2: public void ContactInsertLastNameRequired()
3: {
4: var ordercontroller=new OrderController();
5: var formcollection = new ContactInsertReturnContacttIDModel();
6: formcollection.FirstName="Cipto";
7:
8: //ordercontroller.ValueProvider = formcollection;
9: var request = new Mock<HttpRequestBase>();
10: request.Setup(r => r.HttpMethod).Returns("POST");
11: //mock the httpcontext
12: var mockHttpContext = new Mock<HttpContextBase>();
13: mockHttpContext.Setup(c => c.Request).Returns(request.Object);
14: //fake controllercontext
15: var controllerContext = new ControllerContext(mockHttpContext.Object
16: , new RouteData(), new Mock<ControllerBase>().Object);
17:
18: ordercontroller.ControllerContext= controllerContext;
19: //fake one, just so that it doesn't failed
20: ordercontroller.ValueProvider = new NameValueCollectionValueProvider(new System.Collections.Specialized.NameValueCollection(),new System.Globalization.CultureInfo("en-US"));
21: var isrequestvalid=ordercontroller.ValidateRequest;
22: //MVC will use the DefaultModelBinder to bind each form keys collection to mapped to the mvc model
23: //if not found that model property will result null, so let's just assume it's already Converted to the strong typed object
24: //just pass it through
25: var returnresult=ordercontroller.ContactInsertReturnContacttID("xml", formcollection);
26:
27: var expectedResult = new MyResult<Error>(new Error() { Code = (int)ErrorNumber.LastNameRequired });
28: Assert.AreEqual(((MyResult<Error>)returnresult).Data.Code, expectedResult.Data.Code);
29: //
30: }