React Native iOS原生模块开发实战|教程|心得|如何创建React Native iOS原生模块

尊重版权,未经授权不得转载

本文出自:贾鹏辉的技术博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691432)

前言

一直想写一下我在React Native原生模块封装方面的一些经验和心得,来分享给大家,但实在抽不开身,今天看了一下日历发现马上就春节了,所以就赶在春节之前将这篇博文写好并发布(其实是两篇:要看Android篇的点这里《React Native Android原生模块开发》)。

我平时在用React Native开发App时会用到一些原生模块,比如:在做社会化分享、第三方登录、扫描、通信录,日历等等,想必大家也是一样。

关于在React Native中使用原生模块,在这里引用React Native官方文档的一段话:

有时候App需要访问平台API,但在React Native可能还没有相应的模块。或者你需要复用一些Java代码,而不想用JavaScript再重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者一些高级扩展等等。

我们把React Native设计为可以在其基础上编写真正的原生代码,并且可以访问平台所有的能力。这是一个相对高级的特性,我们并不期望它应当在日常开发的过程中经常出现,但它确实必不可少,而且是存在的。如果React Native还不支持某个你需要的原生特性,你应当可以自己实现对该特性的封装。

上面是我翻译React Native官方文档上的一段话,大家如果想看英文版可以点这里:Native Modules

在这篇文章中呢,我会带着大家来开发一个从相册获取照片并裁切照片的项目,并结合这个项目来具体讲解一下如何一步步开发React Native iOS原生模块的。

提示:本文中所开发的项目的源码已开源到GitHub,供大家学习使用。

首先,让我们先看一下,开发iOS原生模块的主要流程。

开发iOS原生模块的主要流程

在这里我把构建React Native iOS原生模块的流程概括为以下三大步:

  1. 编写原生模块的相关iOS代码;
  2. 暴露接口与数据交互;
  3. 导出React Native原生模块;

接下来让我们一起来看一下每一步所需要做的一些事情。

原生模块开发实战

在这里我们就以开发一个从相册获取照片并裁切照片的实战项目,来具体讲解一下如何开发React Native iOS原生模块的。

编写原生模块的相关iOS代码

这一步我们需要用到XCode。

首先我们用XCode打开React Native项目根目录下的iOS项目,如图:

接下来呢,我们就可以编写iOS代码了。

首先呢,我们先来实现一个Crop接口:

@interface Crop:NSObject<UIImagePickerControllerDelegate,UINavigationControllerDelegate>
-(instancetype)initWithViewController:(UIViewController *)vc;
typedef void(^PickSuccess)(NSDictionary *resultDic);
typedef void(^PickFailure)(NSString* message);
@property (nonatomic, retain) NSMutableDictionary *response;
@property (nonatomic,copy)PickSuccess pickSuccess;
@property (nonatomic,copy)PickFailure pickFailure;
@property(nonatomic,strong)UIViewController *viewController;

-(void)selectImage:(NSDictionary*)option successs:(PickSuccess)success failure:(PickFailure)failure;
@end

我们创建一个Crop.m,在这个类中呢,我们实现了从相册选择照片以及裁切照片的功能:

/**
 * React Native iOS原生模块开发
 * Author: CrazyCodeBoy
 * 技术博文:http://www.devio.org
 * GitHub:https://github.com/crazycodeboy
 * Email:[email protected]
 */

#import "Crop.h"
#import <AssetsLibrary/AssetsLibrary.h>
@interface Crop ()
@property(strong,nonatomic)NSDictionary*option;
@end

@implementation Crop
-(instancetype)initWithViewController:(UIViewController *)vc{
  self=[super init];
  self.viewController=vc;
  return self;
}

-(void)selectImage:(NSDictionary*)option successs:(PickSuccess)success failure:(PickFailure)failure{
  self.pickSuccess=success;
  self.pickFailure=failure;
  self.option=option;
  UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
  pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  pickerController.delegate = self;
  pickerController.allowsEditing = YES;
  [self.viewController presentViewController:pickerController animated:YES completion:nil];
}

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
  UIImage*image=[info objectForKey:@"UIImagePickerControllerEditedImage"];
  image=[self scaleToSize:image size:CGSizeMake([[self.option objectForKey:@"aspectX"]integerValue], [[self.option objectForKey:@"aspectY"]integerValue])];
  if(image){
    [self writeToFileWithImage:image outPut:[self getTempFile:[self getFileName:info]] handler:^(NSString *path) {
      [picker dismissViewControllerAnimated:YES completion:nil];
      self.pickSuccess(@{@"imageUrl": path});
    }];

  }else{
    self.pickFailure(@"获取照片失败");
    [picker dismissViewControllerAnimated:YES completion:nil];
  }

}
#pragma mark 裁剪照片
-(UIImage *)scaleToSize:(UIImage *)image size:(CGSize)size
{
  //并把他设置成当前的context
  UIGraphicsBeginImageContext(size);
  //绘制图片的大小
  [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
  //从当前context中创建一个改变大小后的图片
  UIImage *endImage=UIGraphicsGetImageFromCurrentImageContext();

  UIGraphicsEndImageContext();
  return endImage;
}
#pragma mark 将image写入沙盒
-(void)writeToFileWithImage:(UIImage*)image outPut:(NSString*)imagePath handler:(void(^)(NSString *path))handler{
  NSData *data= UIImageJPEGRepresentation(image, 1);
  [data writeToFile:imagePath atomically:YES];
  dispatch_async(dispatch_get_main_queue(), ^{
    handler(imagePath);
  });

}
#pragma mark 将指定url对于的图片写入沙盒
-(void)writeToFileWithUrl:(NSURL*)url outPut:(NSString*)imagePath handler:(void(^)(NSString *path))handler{
  ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init];
  if (url) {
    [assetLibrary assetForURL:url resultBlock:^(ALAsset *asset) {
      ALAssetRepresentation *rep = [asset defaultRepresentation];
      Byte *buffer = (Byte*)malloc((unsigned long)rep.size);
      NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:((unsigned long)rep.size) error:nil];
      NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
      [data writeToFile:imagePath atomically:YES];
      dispatch_async(dispatch_get_main_queue(), ^{
        handler(imagePath);
      });
    } failureBlock:^(NSError *error) {
      dispatch_async(dispatch_get_main_queue(), ^{
        handler(@"获取图片失败");
      });
    }];
  }
}
#pragma mark 获取临时文件路径
-(NSString*)getTempFile:(NSString*)fileName{
  NSString *imageContent=[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:@"/temp"];
  NSFileManager * fileManager = [NSFileManager defaultManager];
  if (![fileManager fileExistsAtPath:imageContent]) {
    [fileManager createDirectoryAtPath:imageContent withIntermediateDirectories:YES attributes:nil error:nil];
  }
  return [imageContent stringByAppendingPathComponent:fileName];
}
-(NSString*)getFileName:(NSDictionary*)info{
  NSString *fileName;
  NSString *tempFileName = [[NSUUID UUID] UUIDString];
  fileName = [tempFileName stringByAppendingString:@".jpg"];
  return fileName;
}
@end

查看源码

实现了从相册选择照片以及裁切照片的功能之后呢,接下来我们需要将iOS原生模块暴露给React Native,以供js调用。

暴露接口与数据交互

接下了我们就向React Native暴露接口以及做一些数据交互部分的操作。为了暴露接口以及进行数据交互我们需要借助React Native的React/RCTBridgeModule.h,在这里我们创建一个ImageCrop类让它实现RCTBridgeModule协议。

创建一个ImageCrop

ImageCrop.h

/**
 * React Native iOS原生模块开发
 * Author: CrazyCodeBoy
 * 技术博文:http://www.devio.org
 * GitHub:https://github.com/crazycodeboy
 * Email:crazycodeboy@gmail.com
 */

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface  ImageCrop: NSObject <RCTBridgeModule>

@end

查看源码

ImageCrop.m

/**
 * React Native iOS原生模块开发
 * Author: CrazyCodeBoy
 * 技术博文:http://www.devio.org
 * GitHub:https://github.com/crazycodeboy
 * Email:[email protected]
 */

#import "ImageCrop.h"
#import "Crop.h"

@interface ImageCrop ()
@property(strong,nonatomic)Crop *crop;
@end

@implementation ImageCrop

RCT_EXPORT_MODULE();

- (dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}
RCT_EXPORT_METHOD(selectWithCrop:(NSInteger)aspectX aspectY:(NSInteger)aspectY resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
    while (root.presentedViewController != nil) {
      root = root.presentedViewController;
    }
  NSString*aspectXStr=[NSString stringWithFormat: @"%ld",aspectX];
  NSString*aspectYStr=[NSString stringWithFormat: @"%ld",aspectY];
  [[self _crop:root] selectImage:@{@"aspectX":aspectXStr,@"aspectY":aspectYStr} successs:^(NSDictionary *resultDic) {
    resolve(resultDic);
  } failure:^(NSString *message) {
    reject(@"fail",message,nil);
  }];
}
-(Crop*)_crop:(UIViewController*)vc{
  if(self.crop==nil){
    self.crop=[[Crop alloc] initWithViewController:vc];
  }
  return self.crop;
}

@end

ImageCrop类中,我们调用了Crop类来实现从iOS相册中获取图片并裁切图片的功能,在调用Crop的时候我们用的是懒加载的方式。为什么要用懒加载呢?这是为了避免当我们多次调用原生模块从相册选择照片的时候创建多个Crop实例情况的发生。

另外,需要特别提到的是,我们对Crop实例设置了强引用,这是为了防止在我们调用相册的时候Crop被回收,如果Crop被回收我们就无法收到选择照片之后的回调了,也就无法获取到照片。

暴露接口

在上述代码中我们通过RCT_EXPORT_METHOD宏来声明向React Native暴露的接口,这样以来我们就可以在js文件中通过ImageCrop.selectWithCrop来调用我们所暴露给React Native的接口了。

接下来呢,我们来看一下原生模块和JS模块是如何进行数据交互的?

原生模块和JS进行数据交互

在我们要实现的从相册选择照片并裁切的项目中,JS模块需要告诉原生模块照片裁切的比例,等照片裁切完成后,原生模块需要对JS模块进行回调来告诉JS模块照片裁切的结果,在这里我们需要将照片裁切后生成的图片的路径告诉JS模块。

提示:在所有的情况下js和原生模块之前进行通信都是在异步的情况下进行的。

接下来我们就来看下一JS是如何向原生模块传递数据的?

JS向原生模块传递数据:

为了实现JS向原生模块进行传递数据,我们可以直接通过调用原生模块所暴露出来的接口,来为接口方法设置参数。这样以来我们就可以将数据通过接口参数传递到原生模块中,如:

RCT_EXPORT_METHOD(selectWithCrop:(NSInteger)aspectX aspectY:(NSInteger)aspectY resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)

通过上述代码我们可以看出,JS模块可以通过selectWithCrop方法来告诉原生模块要裁切照片的宽高比,最后两个参数是一个Promise,照片裁剪完成之后呢,原生模块可以通过Promise来对JS模块进行回调,来告诉裁切结果。

既然是js和Object-c进行数据传递,那么他们两者之间是如何进行类型转换的呢:

在上述例子中我们通过RCT_EXPORT_METHOD宏来声明需要暴露的接口,被 RCT_EXPORT_METHOD标注的方法支持如下几种数据类型。

RCT_EXPORT_METHOD标注的方法支持如下几种数据类型的参数:

string (NSString)
number (NSInteger, float, double, CGFloat, NSNumber)
boolean (BOOL, NSNumber)
array (NSArray) 包含本列表中任意类型
object (NSDictionary) 包含string类型的键和本列表中任意类型的值
function (RCTResponseSenderBlock)

原生模块向JS传递数据:

原生模块向JS传递数据我们可以借助Callbacks与Promises,接下来就讲一下如何通过他们两个进行数据传递的。

Callbacks

原生模块支持一个特殊类型的参数-Callbacks,我们可以通过它来对js进行回调,以告诉js调用原生模块方法的结果。

将我们selectWithCrop的参数改为Callbacks之后:

RCT_EXPORT_METHOD(selectWithCrop:(NSInteger)aspectX aspectY:(NSInteger)aspectY success:(RCTResponseSenderBlock)success failure:(RCTResponseErrorBlock)failure)
{
    UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
    while (root.presentedViewController != nil) {
      root = root.presentedViewController;
    }
  NSString*aspectXStr=[NSString stringWithFormat: @"%ld",aspectX];
  NSString*aspectYStr=[NSString stringWithFormat: @"%ld",aspectY];
  [[self _crop:root] selectImage:@{@"aspectX":aspectXStr,@"aspectY":aspectYStr} successs:^(NSDictionary *resultDic) {
    success(@[[NSNull null],@[@"imageUrl",[resultDic objectForKey:@"imageUrl"]]]);
  } failure:^(NSString *message) {
    failure(nil);
  }];
}

在上述代码中我们实现了通过Callback来对js进行回调。

接下来呢,我们在js中就可以这样来调用我们所暴露的接口:

ImageCrop.selectWithCrop(parseInt(x),parseInt(y),(error, result)=>{
 if (error) {
    console.error(error);
  } else {
    console.log(result);
  }
})

提示:另外要告诉大家的是,无论是Callback还是我接下来要讲的Promise,我们只能调用一次,也就是”you call me once,I can only call you once”。

Promises

除了上文所讲的Callback之外React Native还为了我们提供了另外一种回调js的方式叫-Promise。如果我们暴露的接口方法的最后一个参数是Promise时,如:

RCT_EXPORT_METHOD(selectWithCrop:(NSInteger)aspectX aspectY:(NSInteger)aspectY resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)

那么当js调用它的时候将会返回一个Promsie:

ImageCrop.selectWithCrop(parseInt(x),parseInt(y)).then(result=> {
    this.setState({
        result: result
    })
}).catch(e=> {
    this.setState({
        result: e
    })
});

另外,我们也可以使用ES2016的 async/await语法,来简化我们的代码:

async onSelectCrop() {
    var result=await ImageCrop.selectWithCrop(parseInt(x),parseInt(y));
}

这样以来代码就简化了很多。

因为,基于回调的数据传递无论是Callback还是Promise,都只能调用一次。但,在实际项目开发中我们有时会向js多次传递数据,比如二维码扫描原生模块,针对这种多次数据传递的情况我们该怎么实现呢?

接下来我就为大家介绍一种原生模块可以向js多次传递数据的方式:

向js发送事件

在原生模块中我们可以向js发送多次事件,即使原生模块没有被直接的调用。为了向js传递事件我们需要用到RCTEventDispatcher,它是原生模块和js之间的一个事件调度管理器。

#import "RCTBridge.h"
#import "RCTEventDispatcher.h"

@implementation CalendarManager

@synthesize bridge = _bridge;

- (void)calendarEventReminderReceived:(NSNotification *)notification
{
  NSString *eventName = notification.userInfo[@"name"];
  [self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder"
                                               body:@{@"name": eventName}];
}

在上述方法中我们可以向JS模块发送任意次数的事件,其中eventName是我们要发送事件的事件名,params是此次事件所携带的数据,接下来呢我们就可以在JS模块中监听这个事件了:

import { NativeAppEventEmitter } from ‘react-native‘;

var subscription = NativeAppEventEmitter.addListener(
  ‘EventReminder‘,
  (reminder) => console.log(reminder.name)
);
...

另外,不要忘记在组件被卸载的时候移除监听:

componentWillUnmount(){
    subscription.remove();
}

到现在呢,暴露接口以及数据传递已经进行完了,接下来呢,我们就需要导出React Native原生模块了。

导出React Native原生模块

为了方面我们使用刚才创建的原生模块,我们需要为它导出一个相应的JS模块。

我们创建一个ImageCrop.js文件,然后添加如下代码:

import { NativeModules } from ‘react-native‘;
export default NativeModules.ImageCrop;

这样以来呢,我们就可以在其他地方通过下面方式来使用我们所导出的这个模块了:

import ImageCrop from ‘./ImageCrop‘ //导入ImageCrop.js
//...省略部分代码

    onSelectCrop() {
        let x=this.aspectX?this.aspectX:ASPECT_X;
        let y=this.aspectY?this.aspectY:ASPECT_Y;
        ImageCrop.selectWithCrop(parseInt(x),parseInt(y)).then(result=> {
            this.setState({
                result: result
            })
        }).catch(e=> {
            this.setState({
                result: e
            })
        });
    }
//...省略部分代码
}

现在呢,我们这个原生模块就开发好了,而且我们也使用了我们的这个原生模块。

关于线程

React Native在一个独立的串行GCD队列中调用原生模块的方法。在我们为React Native开发原生模块的时候,如果有耗时的操作比如:文件读写、网络操作等,我们需要新开辟一个线程,不然的话,这些耗时的操作会阻塞JS线程。通过实现方法- (dispatch_queue_t)methodQueue,原生模块可以指定自己想在哪个队列中被执行。

具体来说,如果模块需要调用一些必须在主线程才能使用的API,那应当这样指定:

- (dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}

类似的,如果一个操作需要花费很长时间,原生模块不应该阻塞住,而是应当声明一个用于执行操作的独立队列。举个例子,RCTAsyncLocalStorage模块创建了自己的一个queue,这样它在做一些较慢的磁盘操作的时候就不会阻塞住React本身的消息队列:

- (dispatch_queue_t)methodQueue
{
  return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
}

指定的methodQueue会被模块里的所有方法共享。如果你的方法中“只有一个”是耗时较长的(或者是由于某种原因必须在不同的队列中运行的),你可以在函数体内用dispatch_async方法来在另一个队列执行,而不影响其他方法:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    if (url) {
      [assetLibrary assetForURL:url resultBlock:^(ALAsset *asset) {
        ALAssetRepresentation *rep = [asset defaultRepresentation];
        Byte *buffer = (Byte*)malloc((unsigned long)rep.size);
        NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:((unsigned long)rep.size) error:nil];
        NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
        NSString * imagePath = [imageContent stringByAppendingPathComponent:fileName];
        [data writeToFile:imagePath atomically:YES];
        handler(imagePath);
      } failureBlock:^(NSError *error) {
        handler(@"获取图片失败");
      }];
    }
  });

在上述代码中我们将文件写入操作放到了一个独立的线程队列中,这样以来即使文件写入的时间再长也不会阻塞其他线程。

还有一个需要告诉大家的是,如果原生模块中需要更新UI,我们需要获取主线程,然后在主线程中更新UI,如:

    dispatch_async(dispatch_get_main_queue(), ^{
        [picker dismissViewControllerAnimated:YES completion:dismissCompletionBlock];
    });

关于React Native iOS原生模块的多线程无外乎这些东西。

另外,告诉大家一个好消息,我已经本博文所用到的项目的源码放到了Github上供大家学习使用:项目源码

如果,大家在开发原生模块中遇到问题可以在本文的下方进行留言,我看到了后会及时回复的哦。

另外也可以关注的新浪微博@CrazyCodeBoy,或者关注我的Github来获取更多有关React Native开发的技术干货

时间: 2024-10-06 00:12:32

React Native iOS原生模块开发实战|教程|心得|如何创建React Native iOS原生模块的相关文章

React Native Android原生模块开发实战|教程|心得|如何创建React Native Android原生模块

尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691503) 前言 一直想写一下我在React Native原生模块封装方面的一些经验和心得,来分享给大家,但实在抽不开身,今天看了一下日历发现马上就春节了,所以就赶在春节之前将这篇博文写好并发布(其实是两篇:要看iOS篇的点这里<React Native iOS原生模块开发>). 我平时在用React Native开发App时会

React Native Android原生模块开发实战|教程|心得|怎样创建React Native Android原生模块

尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691503) 告诉大家一个好消息.为大家精心准备的React Native视频教程公布了,大家现能够看视频学React Native了. 前言 一直想写一下我在React Native原生模块封装方面的一些经验和心得.来分享给大家,但实在抽不开身.今天看了一下日历发现立即就春节了.所以就赶在春节之前将这篇博文写好并公布(事实上是两篇

《iOS 7 应用开发实战详解》

<iOS 7 应用开发实战详解> 基本信息 作者: 朱元波    管蕾 出版社:人民邮电出版社 ISBN:9787115343697 上架时间:2014-4-25 出版日期:2014 年5月 开本:16开 页码:382 版次:1-1 所属分类:计算机 > 软件与程序设计 > 移动开发 > iPhone 更多关于>>><iOS 7 应用开发实战详解> 编辑推荐 新版本 全面讲解了iOS 7开发的各种技术 热门技术 基本控件.数据存储.多场景处理.界

Joomla模块开发实战06-语言文件

Joomla模块开发实战06-语言文件 在这一节中我们来介绍一下joomla的语言文件.如何实现让我们的模块能够轻松支持多语言. 关于joomla多语言的基础,在这里就不多说.如果不清楚,请查看本站多语言相关的文章.首先我们需要新建两个语言文件.然后将语言文件包含到xml文件中,好让安装程序能够将语言文件放到正确的地方.其他的事情就交给joomla来做了. 重点看一下XML文件是如何包含语言文件的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <files> <fil

嵌入式Linux驱动开发实战教程

嵌入式Linux驱动开发实战教程(内核驱动.看门狗技术.触摸屏.视频采集系统) http://www.ibeifeng.com/goods-475.html 咨询QQ2110053820 课程讲师:韩老师 课程分类:Linux 适合人群:高级 课时数量:109课时 更新程度:完成 用到技术:嵌入式 Linux 涉及项目:驱动开发.看门狗技术.触摸屏.视频采集 课程简介:    嵌入式软件开发无疑是当今最热门的行业,嵌入式软件工程师的薪资比普通的软件工 程师的薪资平均高50%以上.随着智能控制.物

Swift游戏开发实战教程(大学霸内部资料)

Swift游戏开发实战教程(大学霸内部资料) 试读下载地址:http://pan.baidu.com/s/1sj7DvQH 介绍:本教程是国内第一本Swift游戏开发专向资料. 本教程详细讲解记忆配对.太空侵略者.Simon记忆.迷你高尔夫.银河大战五个游戏的开发.在项目讲解同时,还着详细介绍了图形绘制.游戏引擎.音频引擎.用户交互.传感器等专向技术.最后,教程讲解苹果专用游戏框架Sprit Kit的使用.为了帮助读者充分了解实际开发,教程还详细讲解游戏开发的必备知识,如帐号绑定.发布游戏等内容

asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发4- 后台模板html页面创建

上一篇教程<asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发3-登录模块开发>完成了本项目的登录模块,登录后就需要进入后台管理首页了,需要准备一个后台模板,本文主要讲解如何创建这个后台模板,本文创建的后台模板不集成到项目内部,只是静态html页面.后台模板是系统开发必须的,一般小公司有个3套后台模板就够用了.本项目后台模板主要是jquery easyui插件创建的,不需要美工设计就可以创建出来,而且效果还可以,具体效果图如下: 这个版面的缺点是,只能适应2层

ASP.NET MVC5微信公众平台整合开发实战教程

<ASP.NET MVC5&微信公众平台整合开发实战(响应式布局.JQuery Mobile,Windows Azure.微信核心开发)> 课程讲师:57Code 课程分类:ASP.NET MVC 适合人群:中级 课时数量:29课时 用到技术:深入MVC开发模式.C#核心语言特性.C#核心语言特性(二).视图引擎Razor 涉及项目:体育商店.微信公众平台开发 咨询QQ:1337192913(小公子) 1.1.1.背景分析 庞大的微信用户数是微信公众平台重要性的根本 微信用户的真实性使

asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发2-Model层建立

上篇(asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发1-准备工作)文章讲解了开发过程中的准备工作,主要创建了项目数据库及项目,本文主要讲解项目M层的实现,M层这里讲的主要是通过Codefirst方式实现的. 一.M层简单介绍 1.M层很形象的将数据库里面的各个表格映射成了C#当中的类,比如上篇文章创建的用户表: ? 1 2 3 4 5 6 7 8 9 10 11 12 CREATE TABLE [dbo].[SYS_USER](          [ID]